SDRAM-学习2-SDRAM初始化模块和时钟生成模块

138 阅读9分钟

设计SDRAM

SDRAM写入10个字节的数据并且读出来,通过串口调试助手传给电脑。

sdram_write 写模块

sdram_read 读模块

sdram_init 初始化模块

sdram_aref 自动刷新模块 : 电容会放电,需要刷新

sdram_arbit 仲裁模块 : 判断处理优先级

sdram_init 初始化模块

image.png

init_end 结束信号

init_addr 行列地址

init_ba bank地址

init_cmd 控制命令

sdram_aref 自动刷新模块

image.png

sdram_write 写模块

image.png

wr_addr: 写地址

wr_data: 写数据

wr_brust_len: 突发长度

wr_cmd: 写控制命令

wr_ba:bank地址

wr_data:写入数据

wr_addr: 地址总线

wr_end:结束信号

写响应信号 wr_ack

sdram_read 读模块

读响应信号 rd_ack image.png

rd_addr: 读地址

init_end:初始化结束信号

rd_brust_len: 突发长度

rd_data读出数据

image.png

sdram_arbit 仲裁模块

image.png

sdram_dq: 数据

系统上电后要进行初始化,初始化完成信号init_end,仲裁模块是为了避免各个操作之间的冲突。发送请求信号aref_req,仲裁模块作出判断,使能信号,aref_en信号,如果有效,传入地址和命令,完成自动刷新后,传入自动刷新结束信号。

如果要进行数据写入也要进行请求,写使能信号后,传入地址和命令,数据。 写入完成。

读操作也是如此,发送请求信号,使能,反馈地址,数据,命令,读出完成。

仲裁模块每次只能执行一个操作。 传出时钟使能信号。 片选信号,行选通,列选通,写使能,bank地址,地址总线,数据端口输入输出

1 sdram_ctrl 控制模块

包括上述子模块。 image.png

image.png

2 fifo控制模块

作用:

使用fifo进行数据缓存,进行跨时钟域处理,同时为sdram控制模块提供sdram读写地址和读写请求信号

信号:

输入数据:

写时钟 写请求 写开始地址 写寄书地址 突发长度 复位信号 有效信号

读和写类似

初始化结束信号 写响应和 读响应信号 读出的数据

输出:

sdram_wr_req 写数据请求信号 写地址 写数据

读请求 读地址 读数据 rd_fifo_rd_data

对读出数据进行计数 rd_fifo_num

image.png

image.png

3 例化这两个模块,生成顶层模块 sdram_top

还需要时钟信号 串口发送 串口接收 模块

sdram 读出数据时钟频率高通过串口进行输出,这里需要fifo作为跨时钟域处理。

fifo_read 模块

image.png

clk_gen 模块

image.png

uart_tx 模块 uart_rx 模块

image.png

4 绘制该工程顶层模块 uart_sdram

image.png

无操作命令: 防止其他操作写入

SDRAm_init初始化模块设计

sdram初始化至少两次自动刷新

上电后 必须进行初始化。

初始化命令: 预充电命令+自动刷新命令+寄存器配置命令

上电后有效时钟信号,等待大于等于100us,等待过程中写入无操作NOP指令。 等待结束后,写入预充电指令A10给入高电平对all bank 进行预充电。 预充电写入后要进行等待tRP,同时写入NOP指令。 时间结束后,写入自动刷新指令,也需要一段时间等待,tRFC,同时写入NOP指令。等待时间结束后,随后开始第二次自动刷新。 开始配置模式寄存器,同时使用地址端口辅助模式寄存器配置。 需要等待tMRD时间,同时写入NOP。 等待时间结束后,初始化完成。

image.png

tRP tRFC tMRD 等待时间。

上电之后的等待时间 可以延长提高芯片适配度

代码编写

初始化 适合状态机来表示。

状态参数

IDLE : NOP

PR

PR_T

ARF

ARF_T

ARF

ARF_T

MRS

MRS_T

END

module sdram_init(
input wire sys_clk,
input wire sys_rst_n,

output wire [3:0] init_cmd,
output wire [1:0] init_ba,
output wire [12:0] init_addr,
output wire init_end
)
reg  [2:0] init_state;
reg [14:0] cnt_200us;

wire  wait_end;


reg [2:0] cnt_clk;
reg cnt_clk_rst;
reg trp_end;
reg trfc_end;
reg tmrd_end;
reg [3:0] cnt_aref;
//格雷码表示状态
parameter INIT_IDLE = 3'b000
INIT_PRE = 3'b001,
INIT_TRP = 3'b011,
INIT_AR = 3'b010,
INIT_TRF = 3'b110,
INIT_MRS = 3'b111,
INIT_TMRD = 3'b101,
INIT_END = 3'b100;

// 10ns 完成200us
parameter WAIT_MAX= 15'd20000;


parameter TRP = 3'd2,
TRF = 3'd7,
TMRD = 3d'3;     // 2个周期  7个周期  3个周期

//四个命令  空命令 预充电 自动刷新  模式寄存器配置
parameter NOP = 4'b0111,
P_CHARGE =4'b0010,
AUTO_REF =4'b0001,
M_REG_SET = 4'b0000;





//对状态变量赋值

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        init_state <= INIT_IDLE;
    //状态跳转
     case(init_state)
         INIT_IDLE:
             if(wait_end == 1'b1)
                 init_state <= INIT_PRE;
             else
                 inite_state <= init_state;
         
         INIT_PRE :
                inite_state <= INIT_TRP; 
       //使用cnt_clk计数,trp_end 拉高
        INIT_TRP :
             if(trp_end== 1'b1)
                 init_state <= INIT_AR;
               else
                 inite_state <= init_state;
                
        INIT_AR :
                inite_state <= INIT_TRP; 
         //至少两次自动刷新,结束信号判断和次数判断 8次自动刷新
        INIT_TRF :
               if(trfc_end == 1'b1)
                   if(cnt_aref == 4'd8)
                       init_state <= INIT_MRS;
                    else
                        init_state <= INIT_AR;
                else
                    init_state <= init_state;
 
        INIT_MRS :
                inite_state <= INIT_TMRD; 
        INIT_TMRD :
            if(tmrd_end == 1'b1)
                init_state <= INIT_END;
             else
                 init_state <= init_state;
            
        INIT_END:
            init_state <=INIT_END;
        
        default:init_state <= INIT_IDLE;
        
    endcase
    
    // 200us计数器赋值  上电后的等待时间
 always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_200us <= 15'd0;
    else if(cnt_200us == WAIT_MAX)
        cnt_200us <= WAIT_MAX;
        
     else
         cnt_200us <= cnt_200us + 1'b1;
 // 使得等待结束信号。只拉高一个时钟周期 
assign  wait_end = (cnt_200us == WAIT_MAX -1'b1) ?1'b1:1'b0;
        
  //cnt_clk 计数器的赋值
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
         cnt_clk <= 3'd0;
     else if(cnt_clk_rst == 1'b1)
         cnt_clk <= 3'd0;
     else
         cnt_clk<= cnt_clk +1'b1;
//复位信号的赋值  组合逻辑赋值  case 语句状态跳转条件

always@(*)
    begin
        case(init_state)
            INIT_IDLE: cnt_clk_rst <= 1'b1;
            INIT_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1:1'b0;
            INIT_TRF :cnt_clk_rst <= (trfc_end ==1'b1)?1'b1:1'b0;
             INIT_TMRD:cnt_clk_rst <= (tmrd_end==1'b1)?1'b1:1'b0;
             INIT_END: cnt_clk_rst <=1'b1;
             default: cnt_clk_rst <= 1'b0;           
        endcase
    end
    
    // tmrd_end trfc_end trp_end  进行赋值  组合逻辑,定义参数进行判断
    
   
   assign trp_end = (cnt_clk == TRP)? 1'b1:1'b0;
   assign  trfc_end =  (cnt_clk == TRF)? 1'b1:1'b0;
   assign tmrd_end = (cnt_clk == TMRD)? 1'b1:1'b0;
   
 //自动刷新次数计数器 cnt_aref  ,当状态机处于初始状态进行清零
 always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_aref <= 4'd0;
     else if(init_state == INIT_IDLE)
         cnt_aref <= 4'd0;
      else if(init_state == INIT_AR)
          cnt_aref <= cnt_aref + 1'b1;
       else
           cnt_aref <= cnt_aref;
    
    // 以上所有中间变量完成。以下为四路输出信号
    //命令和地址是同时起作用,故同时进行赋值   这几个命令定义为参数
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
        end
       //结合状态机进行赋值
       case(init_state)
                       INIT_IDLE,INIT_TRP,INIT_TRP,INIT_TMRD,INIT_END:
           begin
           init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
           
         INIT_PRE:
          begin
           init_cmd <=   P_CHARGE;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
         
         
         INIT_AR:
              begin
           init_cmd <=   AUTO_REF;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
         
         //模式寄存器下地址总线和bank地址都要改一下
         // 地址总线高三位为0 ,第三位为突发长度选择
         
         INIT_MRS:
               begin
           init_cmd <=   M_REG_SET;
            init_ba <= 2'b00;
            init_addr <= {3'b0,1'b0,2'b00,3'b011'1b0,3'b11)
           end
           
           default;
           
            begin
            init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
        end
   
       endcase
    // 结束信号 init_end 用组合逻辑赋值
    
assign init_end = (init_state == INIT_END)?1'b1:1'b0;
    




endmodule

image.png

cnt_200us 为上电后的等待时间

预充电 寄存器配置 自动刷新等的等待时间通过 cnt_clk来统计,通过tmrd_end等的信号来辅助判断。

wait_end 充电后等待时间结束的判断信号

模式寄存器的bank地址为全0

用来写模式寄存器的地址总线为: image.png

四路输出信号分析:

init_cmd:

init_ba: bank地址初值给2'b11。寄存器配置时给定2'b00,

因为在配置时候bank地址要给定00.

image.png

init_addr:地址总线,预充电时要高电平,直接13h1fff。然后模式寄存器配置的时候给定 需要的值。

SDRAM初始化的 验证, 进行全编译检查错误:

完整代码如下:

module sdram_init(
input wire sys_clk,
input wire sys_rst_n,

output reg [3:0] init_cmd,
output reg [1:0] init_ba,
output reg [12:0] init_addr,
output wire init_end
);

reg  [2:0] init_state;
reg [14:0] cnt_200us;

wire  wait_end;


reg [2:0] cnt_clk;
reg cnt_clk_rst;
wire trp_end;
wire trfc_end;
wire tmrd_end;
reg [3:0] cnt_aref;
//格雷码表示状态
parameter INIT_IDLE = 3'b000,
INIT_PRE = 3'b001,
INIT_TRP = 3'b011,
INIT_AR = 3'b010,
INIT_TRF = 3'b110,
INIT_MRS = 3'b111,
INIT_TMRD = 3'b101,
INIT_END = 3'b100;

// 10ns 完成200us
parameter WAIT_MAX= 15'd20000;


parameter TRP = 3'd2,
TRF = 3'd7,
TMRD = 3'd3;     
// 2个周期  7个周期  3个周期

//四个命令  空命令 预充电 自动刷新  模式寄存器配置
parameter NOP = 4'b0111,
P_CHARGE =4'b0010,
AUTO_REF =4'b0001,
M_REG_SET = 4'b0000;





//对状态变量赋值

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        init_state <= INIT_IDLE;
    //状态跳转
	 else
     case(init_state)
         INIT_IDLE:
             if(wait_end == 1'b1)
                 init_state <= INIT_PRE;
             else
                 init_state <= init_state;
         
         INIT_PRE :
                init_state <= INIT_TRP; 
       //使用cnt_clk计数,trp_end 拉高
        INIT_TRP :
             if(trp_end== 1'b1)
                 init_state <= INIT_AR;
               else
                 init_state <= init_state;
                
        INIT_AR :
                init_state <= INIT_TRF; 
         //至少两次自动刷新,结束信号判断和次数判断 8次自动刷新
        INIT_TRF :
               if(trfc_end == 1'b1)
                   if(cnt_aref == 4'd8)
                       init_state <= INIT_MRS;
                    else
                        init_state <= INIT_AR;
                else
                    init_state <= init_state;
 
        INIT_MRS :
                init_state <= INIT_TMRD; 
        INIT_TMRD :
            if(tmrd_end == 1'b1)
                init_state <= INIT_END;
             else
                 init_state <= init_state;
            
        INIT_END:
            init_state <=INIT_END;
        
        default:init_state <= INIT_IDLE;
        
    endcase
    
    // 200us计数器赋值  上电后的等待时间
 always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_200us <= 15'd0;
    else if(cnt_200us == WAIT_MAX)
        cnt_200us <= WAIT_MAX;
        
     else
         cnt_200us <= cnt_200us + 1'b1;
 // 使得等待结束信号。只拉高一个时钟周期 
assign  wait_end = (cnt_200us == WAIT_MAX -1'b1) ?1'b1:1'b0;
        
  //cnt_clk 计数器的赋值
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
         cnt_clk <= 3'd0;
     else if(cnt_clk_rst == 1'b1)
         cnt_clk <= 3'd0;
     else
         cnt_clk<= cnt_clk +1'b1;
//复位信号的赋值  组合逻辑赋值  case 语句状态跳转条件

always@(*)
    begin
        case(init_state)
            INIT_IDLE: cnt_clk_rst <= 1'b1;
            INIT_TRP: cnt_clk_rst <= (trp_end == 1'b1) ? 1'b1:1'b0;
            INIT_TRF :cnt_clk_rst <= (trfc_end ==1'b1)?1'b1:1'b0;
             INIT_TMRD:cnt_clk_rst <= (tmrd_end==1'b1)?1'b1:1'b0;
             INIT_END: cnt_clk_rst <=1'b1;
             default: cnt_clk_rst <= 1'b0;           
        endcase
    end
    
    // tmrd_end trfc_end trp_end  进行赋值  组合逻辑,定义参数进行判断
    
   
   assign trp_end = ((init_state <= INIT_TRP)&&(cnt_clk == TRP))? 1'b1:1'b0;
   assign  trfc_end =  ((init_state <= INIT_TRF)&&(cnt_clk == TRF))? 1'b1:1'b0;
   assign tmrd_end =((init_state <= INIT_TMRD)&& (cnt_clk == TMRD))? 1'b1:1'b0;
   
 //自动刷新次数计数器 cnt_aref  ,当状态机处于初始状态进行清零
 always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_aref <= 4'd0;
     else if(init_state == INIT_IDLE)
         cnt_aref <= 4'd0;
      else if(init_state == INIT_AR)
          cnt_aref <= cnt_aref + 1'b1;
       else
           cnt_aref <= cnt_aref;
    
    // 以上所有中间变量完成。以下为四路输出信号
    //命令和地址是同时起作用,故同时进行赋值   这几个命令定义为参数
  always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
        end
       //结合状态机进行赋值
	 else
       case(init_state)
                       INIT_IDLE,INIT_TRP,INIT_TRP,INIT_TMRD,INIT_END:
           begin
           init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
           
         INIT_PRE:
          begin
           init_cmd <=   P_CHARGE;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
         
         
         INIT_AR:
              begin
           init_cmd <=   AUTO_REF;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
           end
         
         //模式寄存器下地址总线和bank地址都要改一下
         // 地址总线高三位为0 ,第三位为突发长度选择
         
         INIT_MRS:
               begin
           init_cmd <=   M_REG_SET;
            init_ba <= 2'b00;
            init_addr <= {3'b0,1'b0,2'b00,3'b011,1'b0,3'b11};
           end
           
           default:
           
            begin
            init_cmd <=   NOP;
            init_ba <= 2'b11;
            init_addr <= 13'h1fff;
        end
   
       endcase
    // 结束信号 init_end 用组合逻辑赋值
    
assign init_end = (init_state == INIT_END)?1'b1:1'b0;
    




endmodule


时钟生成模块 clk_gen

clk50 串口用

clk100m sdram控制使用的信号

clk100m 有偏移 sdram芯片使用的信号

时钟信号生成: clk_100 是sdram工作时钟

clk_100_shift:经过相位偏移的时钟,传入直接传出了。

需要两个时钟信号

image.png

调用ip 核:

image.png

image.png

image.png

image.png

输入时钟频率选择 50MHZ:

image.png

使用复位信号同时使用锁定信号:

image.png

输出100Mhz,并且不使用相位偏移

image.png 输出频率100mhz,暂且使用30度的时钟偏移。

image.png

image.png

选择生成例化模板:clk_gen_inst.v

image.png

复制例化模板

	.areset ( areset_sig ),
	.inclk0 ( inclk0_sig ),
	.c0 ( c0_sig ),
	.c1 ( c1_sig ),
	.c2 ( c2_sig ),
	.locked ( locked_sig )
	);

关于sdram复位信号:

image.png

assign rst_n = sys_rst_n & locked;

新生成的复位信号 rst_n

locked锁定信号。

锁定信号有效时候才可以输出有效时钟信号。

clk50 串口用

clk100m sdram控制使用的信号

clk100m 有偏移 sdram芯片使用的信号

仿真程序:

`imescale 1ns/1ns
module tb_sdtam_init();

wire clk_50m;
wire clk_100m;
wire clk_100m_shift;
wire locked;

wire  rst_n;
wire [3:0] init_cmd;

wire  [1:0] init_ba;

wire  [12:0] init_addr;

wire  init_end;


reg sys_clk;
reg sys_rst_n;

initial 
	begin
		sys_clk = 1'b1;
		sys_rst_n <= 1'b0;
		#30
		sys_rst_n <= 1'b1;
	end
always #10 sys_clk = ~sys_clk
	
assign  	rst_n = sys_rst_n & locked;
	
	
	
	//pll锁相环 复位信号为高电平有效,
dram_init  dram_init_inst(
 .sys_clk(sys_clk),
 .sys_rst_n(rst_n),

 .init_cmd(init_cmd),
 .init_ba(init_ba),
.init_addr(init_addr),
 .init_end(init_end)
);

//仿真需要用到sdram仿真模型。 这个模块我没有 可以自己在网上找找


clk_gen	clk_gen_inst (
	.areset ( sys_clk ),
	.inclk0 ( ~sys_rst_n ),
	.c0 ( clk_50m ),
	.c1 ( clk_100m ),
	.c2 ( clk_100m_shift ),
	.locked ( locked )
	);



endmodule 

回到工程,添加仿真代码:

image.png

仿真模型一并添加。

全编译 检查问题

进行仿真设置:添加ip核等

image.png

然后开始功能仿真

image.png

发现问题:

赋值条件不充分: 改为:

   assign  trfc_end =  ((init_state <= INIT_TRF)&&(cnt_clk == TRF))? 1'b1:1'b0;
   assign tmrd_end =((init_state <= INIT_TMRD)&& (cnt_clk == TMRD))? 1'b1:1'b0;