DDR2 SDRAM(五)初始化

因为DDR2本质上只是更高级的一种SRAM,底层操作原理和SRAM是一样的,所以很多基础的东西就不再赘述了。

一、原理

在初始化之前,DDR2芯片需要先上电,芯片有多个需要提供的电压,其大小和顺序也有要求,这部分算是物理层的,在此不作研究。

在上电稳定之后,还需要等待时钟稳定才能进行操作。

时钟稳定后,开始第一轮对LMR的操作,配置四个(E)MRS,实现激活DLL和DLL复位的功能。

随后进行至少两次自动刷新,再开始第二轮对LMR的操作,配置四个(E)MRS,实现停止DLL复位和退出OCD的功能。

稳定后即完成了初始化,DDR2可以正常工作。

二、模块框图和接口

三、时序图

注意,这些延迟的具体值和数据速率(Data Rate)密切相关,即和串口时钟频率相关,在这里假设串口时钟频率为400MHz(即下图中的-25),那么一个周期就是T=2.5ns,延迟周期数将使用2.5ns计算。

假设容量为8Meg*16*4banks(512Gb),因此dq位宽为16,ba位宽为2,行addr位宽为13,列addr位宽为10。

根据芯片手册的时序图,舍弃部分无用的信号,增添具体信号值,查阅手册得到准确的延时(延时部分内容太多,涉及到的都放在本文末尾),画出更清晰的时序图如下。

其中第三条之后的虚线为系统时钟sys_clk的上升沿,用于输出值的改变;输给DDR2的接口时钟clk&clk_n应当比sys_clk延迟半个周期,用于采样。

这里又遇到了如何生成sys_clk和clk&clk_n的问题,放在后文详述。(又挖坑了)

其中addr总线的具体值描述如下。
EMR(2):不用考虑高温自刷新的问题,全为0即可。
EMR(3):保留,全为0。
EMR_1:此处需要打开DLL功能,所以A0为0;还有两个参数需要关注,A3~A5和A7~A9。前者是关于AL的长度(详见DDR2 SDRAM(二)信号和时序-优快云博客),后者是OCD校准,在这里为了简化,都设为0。另外,也去掉一些不必要的信号和接口,所以A10为1(关闭dqs_n),A11为0(关闭rdqs)。此处ODT必须禁用。
MR_1:此处需要将DLL复位,所以A8为1,其余全为0。注意复位后要至少200个周期才能读。
MR_2:此处需要停止DLL复位,所以A8为0;A0~A2是突发长度,设为011(8);A4~A6是CAS延迟,根据上文的假设,设为110(6);A9~A11是自动预充电的写恢复时间,本次设计中不打算加入此功能,只需要知道这个时间是tWR,可以通过查表得到,此处为了方便直接设为101(6)。
EMR_2:想要退出OCD校准,要先在此处设置DLL default,即A7~A9都为1。ODT就设为75Ω好了,所以A6为0,A2为1。
EMR_3:此处需要退出OCD校准,A7~A9都为0。

注意:为了提高代码的灵活性,以上提到的参数都应该使用parameter定义。

四、状态机

  1. 复位撤销后,开始计数,计数到80000时拉高cke并将init_amd设置为NOP。
  2. 计数到80000+160时,拉高A10选中所有bank,并将init_amd设置为PREC,等待tRPA。
  3. 将init_amd设置为LM,BA设为10,init_addr设为13’b0,等待tMRD。
  4. 将init_amd设置为LM,BA设为11,init_addr设为13’b0,等待tMRD。
  5. 将init_amd设置为LM,BA设为01,init_addr[10]设为1,其余为0,等待tMRD。
  6. 将init_amd设置为LM,BA设为00,init_addr[8]设为1,其余为0,等待tMRD。
  7. 拉高A10选中所有bank,并将init_amd设置为PREC,等待tRPA。
  8. 将init_amd设置为REF,等待tRFC。
  9. 将init_amd设置为REF,等待tRFC。
  10. 将init_amd设置为LM,BA设为00,init_addr[2:0]为011,init_addr[6:4]为110,init_addr[11:9]为101,其余为0,等待tMRD。
  11. 将init_amd设置为LM,BA设为01,init_addr[9:7]为111,init_addr[10]为1,init_addr[2]为1,其余为0,等待tMRD。
  12. 将init_amd设置为LM,BA设为01,init_addr[10]设为1,init_addr[2]为1,其余为0,等待tMRD。
  13. 初始化完成。

五、代码实现

/***************************************************************************************
作者:not_lubao
2024-11-1
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××*/

// DDR2初始化模块,初始化步骤:上电--延时200us--拉高cke--延时400ns--预充电--寄存器配置--刷新n次--寄存器配置--完成

module ddr2_init(
input          sys_clk     ,     //系统时钟信号100MHz
input          rstn        ,     //系统复位信号

output         cke         ,     //时钟使能
output   [1:0] init_ba     ,     //Bank
output   [3:0] init_cmd    ,     //命令
output  [12:0] init_addr   ,     //地址
output         init_end          //初始化结束标志
);

/***********************参数************************/
//状态机
parameter      IDLE           = 6'd0       ,
               WAIT_IDLE      = 6'd1       ,
               CLK_EN         = 6'd2       ,
               WAIT_CKE       = 6'd3       ,
               P_CHAR         = 6'd4       ,
               WAIT_PREC      = 6'd5       ,
               LM_EMR2        = 6'd6       ,
               LM_EMR3        = 6'd7       ,
               LM_EMR_1       = 6'd8       ,
               LM_MR_1        = 6'd9       ,
               LM_MR_2        = 6'd10      ,
               LM_EMR_2       = 6'd11      ,
               LM_EMR_3       = 6'd12      ,
               WAIT_LM        = 6'd13      ,
               AUTO_REF       = 6'd14      ,
               WAIT_REF       = 6'd15      ,
               WAIT_DLL_RST   = 6'd16      ,
               END            = 6'd17      ;

//等待时间 比实际要求最小值多一个周期
parameter      T_INIT         = 17'd80000  ,  //上电后等待
               T_CKE          = 17'd160    ,  //拉高cke后等待
               T_tRPA         = 17'd6      ,  //充电后等待
               T_tMRD         = 17'd1      ,  //配置寄存器后等待
               T_tRFC         = 17'd42     ,  //刷新后等待
               T_DLL_RST      = 17'd110    ;  //拉高cke后等待(大于99)

//指令                                     
parameter      LM             = 4'b0000    ,
               REF            = 4'b0001    ,
               PREC           = 4'b0010    ,
               ACT            = 4'b0011    ,
               WR             = 4'b0100    ,
               RD             = 4'b0101    ,
               NOP            = 4'b0111    ;

//自刷新次数                    
parameter      REF_TIME       = 4'd2       ;

//模式寄存器的值                
parameter      EMR2           = 13'b0                 ,  //​不用考虑高温自刷新的问题,全为0
               EMR3           = 13'b0                 ,  //​保留
               EMR_1          = 13'b0_0010_0000_0000  ,  //​[0]为0(打开DLL功能);[5:3](AL长度)和[9:7](OCD校准)都为0;[10]为1(关闭dqs_n),[11]为0(关闭rdqs);{[6],[2]}为01(ODT=75Ω)
               MR_1           = 13'b0_0000_1000_0000  ,  //[8]为1(DLL复位),其余全为0
               MR_2           = 13'b0_0101_0011_0011  ,  //[8]为0(停止DLL复位);[2:0]为011(突发长度8);[6:4]为110(CAS延迟6);[11:9]为101(自动预充电的写恢复时间6)
               EMR_2          = 13'b0_0011_1100_0010  ,  //[9:7]为111(OCD default);{[6],[2]}为01(ODT=75Ω)
               EMR_3          = 13'b0_0010_0000_0010  ;  //[9:7]为000(退出OCD校准)
               
/* 模式寄存器的具体值受芯片型号影响,以下仅做参考
MR[2:0]       : 突发长度  4(010)或8(011)
  [3]         : 突发类型  0(顺序)或1(插入)
  [6:4]       : CAS延迟  010(2)到111(7),一般大于3
  [7]         : 测试模式  0(正常工作)或1(测试)
  [8]         : DLL复位  0(无效)或1(有效)
  [11:9]      : 带自动预充电的写恢复时间  010(2)到111(7),一般大于2
  [12]        : 活跃低功耗模式的退出时间  0(快退出 tXARD)或1(慢退出 tXARDS)

EMR[0]        : DLL功能  0(开启)或1(关闭)
   [1]        : D.I.C 输出驱动阻抗  0(满强度)或1(降低强度)
   {[6],[2]}  : Rtt  00(无效)或01(75Ω)或10(150Ω)或11(50Ω)
   [5:3]      : AL 额外延时  000(0)到110(6),111为保留
   [9:7]      : OCD校准  000(退出)或001(驱动1)或010(驱动0)或100(调整)或111(default)
   [10]       : 差分DQS  0(启用)或1(禁用)
   [11]       : RDQS  0(禁用)或1(启用)
   [12]       : Qoff输出控制 0(启用)或1(禁用),包括DQ/DQS/RDQS
*/

/***********************寄存器************************/
reg      [5:0] r_state, r_next_state  ;
reg     [16:0] r_latency_T            ;  //延时长度寄存器
reg     [16:0] r_cnt_wait           ;  //计时计数器值
reg      [3:0] r_ref_cnt            ;  //刷新次数计数
reg      [7:0] r_cke_cnt              ;  //CKE拉高时间计数器
                                      
reg            r_cke                  ;
reg      [1:0] r_init_ba              ; 
reg      [3:0] r_init_cmd             ;
reg     [12:0] r_init_addr            ;
reg            r_init_end             ;
 
/***********************时序逻辑************************/
always@(posedge sys_clk or negedge rstn) begin  //计时器模块
    if(!rstn)
        r_cnt_wait <= 17'd0;
    else if(r_cnt_wait == r_latency_T)  //计满自动清零
        r_cnt_wait <= 17'd0;
    else
        r_cnt_wait <= r_cnt_wait +17'd1;
end

always@(posedge sys_clk or negedge rstn) begin  //刷新次数计数器
    if(!rstn)
        r_ref_cnt <= 4'd0;
    else if((r_ref_cnt == REF_TIME) && (r_cnt_wait == T_tRFC))  //达到设定刷新次数且本次刷新后等待时间tRFC结束
        r_ref_cnt <= 4'd0;
    else if(r_next_state == AUTO_REF)  //下一个状态是刷新
        r_ref_cnt <= r_ref_cnt + 4'd1;
    else
        r_ref_cnt <= r_ref_cnt;
end

/*各个state持续时间的设定
在切换状态的一瞬间,计数器执行清零操作,目标计数值被重新赋值,这样就实现了延时参数的传递
那样就可以只使用一个计数器*/
always@(posedge sys_clk or negedge rstn) begin 
    if(!rstn)
        r_latency_T <= 10'd0;
    else
        case(r_next_state)
            IDLE          :  r_latency_T <= 10'd0      ;
            WAIT_IDLE     :  r_latency_T <= T_INIT     ;
            CLK_EN        :  r_latency_T <= 10'd0      ;   
            WAIT_CKE      :  r_latency_T <= T_CKE      ;
            P_CHAR        :  r_latency_T <= 10'd0      ;
            WAIT_PREC     :  r_latency_T <= T_tRPA     ;
            LM            :  r_latency_T <= 10'd0      ;
            WAIT_LM       :  r_latency_T <= T_tMRD     ;
            AUTO_REF      :  r_latency_T <= 10'd0      ;
            WAIT_REF      :  r_latency_T <= T_tRFC     ;
            WAIT_DLL_RST  :  r_latency_T <= T_DLL_RST  ;
            END           :  r_latency_T <= 10'd0      ;
            default       :  r_latency_T <= 10'd0      ;
        endcase
end

/***********************组合逻辑************************/
assign cke        =  r_cke        ;
assign init_cmd   =  r_init_cmd   ;
assign init_addr  =  r_init_addr  ;
assign init_ba    =  r_init_ba    ;
assign init_end   =  r_init_end   ;

/***********************状态机************************/
always@(posedge sys_clk or negedge rstn) begin//状态转移
    if(!rstn)
        r_state <= IDLE;
    else
        r_state <= r_next_state;
end

always@(*) begin  //状态转移判断模块,根据计时时间判断是否已经持续相应时间长度
    if(!rstn)
        r_next_state = IDLE;
    else
        case(r_state)
            IDLE          :
                r_next_state = WAIT_IDLE;
            WAIT_IDLE     :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = CLK_EN;
                else
                    r_next_state = r_state;
            CLK_EN        :
                r_next_state = WAIT_CKE;
            WAIT_CKE      :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = P_CHAR;
                else
                    r_next_state = r_state;
            P_CHAR        :
                r_next_state = WAIT_PREC;
            WAIT_PREC     :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_EMR2;
                else
                    r_next_state = r_state;
            LM_EMR2       :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_EMR3;
                else
                    r_next_state = r_state;
            LM_EMR3       :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_EMR_1;
                else
                    r_next_state = r_state;
            LM_EMR_1      :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_MR_1;
                else
                    r_next_state = r_state;
            LM_MR_1       :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = P_CHAR;
                else
                    r_next_state = r_state;
            P_CHAR        :
                r_next_state = WAIT_PREC;
            WAIT_PREC     :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = AUTO_REF;
                else
                    r_next_state = r_state;
            AUTO_REF      :
                r_next_state = WAIT_REF;
            WAIT_REF      :    
                if((r_ref_cnt == REF_TIME) && (r_cnt_wait == r_latency_T))  //达到设定刷新次数且本次刷新后等待时间tRC结束
                    r_next_state = LM_MR_2;
                else if(r_cnt_wait == r_latency_T)
                    r_next_state = AUTO_REF;
                else
                    r_next_state = r_state;
            LM_MR_2       :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_EMR_2;
                else
                    r_next_state = r_state;
            LM_EMR_2      :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = LM_EMR_3;
                else
                    r_next_state = r_state;
            LM_EMR_3      :
                r_next_state = WAIT_LM;
            WAIT_LM       :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = WAIT_DLL_RST;
                else
                    r_next_state = r_state;
            WAIT_DLL_RST  :
                if(r_cnt_wait == r_latency_T)
                    r_next_state = END;
                else
                    r_next_state = r_state;
            END           :    
                r_next_state = END;
            default       : 
                r_next_state = IDLE;
        endcase
end

always@(posedge sys_clk or negedge rstn) begin //输出模块,三段式状态机
    if(!rstn) begin
        r_init_cmd  <= NOP      ;
        r_init_ba   <= 2'b11    ;
        r_init_addr <= 13'h1fff ;
        r_init_end  <= 1'b0     ;
    end 
    else 
        case(r_state)
            IDLE,WAIT_IDLE,WAIT_CKE,WAIT_PREC,WAIT_LM,WAIT_REF,WAIT_DLL_RST : begin
                r_init_cmd  <= NOP       ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= 13'h1fff  ;
            end
            CLK_EN    : begin
                r_cke       <= 1'b1      ;
            end
            P_CHAR    : begin
                r_init_cmd  <= PREC      ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= 13'h1fff  ;
            end
            LM_EMR2   : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b10     ;
                r_init_addr <= EMR2      ;
            end
            LM_EMR3   : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= EMR3      ;
            end
            LM_EMR_1  : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b01     ;
                r_init_addr <= EMR_1     ;
            end
            LM_MR_1   : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b00     ;
                r_init_addr <= MR_1      ;
            end
            LM_MR_2   : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b00     ;
                r_init_addr <= MR_2      ;
            end
            LM_EMR_2  : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b01     ;
                r_init_addr <= EMR_2     ;
            end
            LM_EMR_3  : begin
                r_init_cmd  <= LM        ;
                r_init_ba   <= 2'b01     ;
                r_init_addr <= EMR_3     ;
            end
            AUTO_REF  : begin
                r_init_cmd  <= REF       ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= 13'h1fff  ;
            end
            END       : begin
                r_init_cmd  <= NOP       ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= 13'h1fff  ;
                r_init_end  <= 1'b1      ;
            end
            default   : begin
                r_init_cmd  <= NOP       ;
                r_init_ba   <= 2'b11     ;
                r_init_addr <= 13'h1fff  ;
            end
        endcase
end

endmodule

注:本模块和后续模块不再进行单独仿真,可能会有bug,最终仿真完成会统一修改,然后删掉这句话。

附:延时查找表



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值