【SDRAM】从官方数据手册出发,使用verilog实现SDRAM控制器(自动刷新模块)

前言

本篇文章主要记录如何实现SDRAM的自动刷新模块,关于SDRAM的引脚、预充电命令和自动刷新命令介绍以及SDRAM初始化流程见上篇文章。SDRAM初始化部分

一、SDRAM为什么需要进行自动刷新?

SDRAM需要进行自动刷新的主要原因是为了防止存储在内存中的数据丢失。SDRAM 是动态存储器,它使用电容来存储数据,而电容会随时间泄漏电荷,因此必须定期刷新以保持数据的完整性。由于存储体中电容的数据有效保存上限是64ms,因此要保证至少每隔64ms要将所有bank刷新完

在进行刷新时,地址由内部刷新控制器生成。因此,在自动刷新命令期间,地址位是“无关的”。256Mb SDRAM 每 64 毫秒(tREF)需要 8,192 次自动刷新周期,无论宽度选项如何(这句话的意思是不管SDRAM的数据位宽是16bit还是8bit、4bit,它们均是8192行,都需要每64 毫秒(tREF)进行8,192 次自动刷新)。每 7.81 微秒发出一次自动刷新命令,将满足刷新要求并确保每行都得到刷新。另外,在64ms内,这8192次自动刷新命令之间的最小间隔为tRFC(以上引自上篇文章 )

二、如何控制SDRAM进行自动刷新

前文提到,需要每64ms内需要8192次自动刷新,之所以是8192,是因为每一个Bank都由8192行存储单元构成,每发送一次自动刷新命令就会对4个bank中的某一行存储单元进行刷新。当执行完8192次自动刷新命令后,就把每个bank的所有存储单元都刷新了一遍。

在本文中,我们所设计的自动刷新模块的作用就是在每64ms内,发送8192个自动刷新命令,确保存储的数据不丢失,需要平均约每7.81us发送一次自动刷新命令。

自动刷新这一块唯一的要求就是,每64ms内,发送完8192个自动刷新命令就可以,至于在这64ms内的哪个时间点发没有严格要求。所以可以选择以下方式

1、每过64ms,集中发完8192的自动刷新命令(假设发送需要的时间为t)。在t期间内不进行数据读写,64ms-t时间内在进行数据读写。

2、每隔64ms/8192=7.81us 发送一次刷新命令(假设发送需要的时间为t),在t期间内不进行数据读写,7.81us-t时间内在进行数据读写。

我们对上述两种方式进行一下分析:

方式一是集中发完自动刷新的命令,那么在发送8192个自动刷新命令期间就不可以进行数据的读写,由于SDRAM通常是在不断的进行数据读写,并且在读写侧都需要使用FIFO进行缓冲,在集中发送8192个自动刷新命令期间,SDRAM不进行读写,很容易造成写端口对应的FIFO溢出,读端口对应的FIFO 被读空。

方式二是把8192次自动刷新命令分成相等的时间间隔,每过7.81us进行一次自动刷新。方式二需要考虑到一种情况,可能某一时刻可能正在进行数据读或者写,但是这个时候刚好计数器计数到了7.81us,需要进行一次新的刷新,但是需要等待这次读或者写操作结束后我们才能执行自动刷新命令。

三、自动刷新模块流程

在介绍自动刷新命令时提到过:在发出自动刷新命令之前,所有激活的bank必须先进行预充电命令顺序 先预充电 在自动刷新 )。自动刷新命令不应在预充电命令后的最小 tRP 时间未满足之前发出。

为了保险起见,我们设置每隔7.81us进行两次刷新操作。32ms内就可以把8192行全部刷新玩 。每次计数时间到7.81us就拉高自动刷新请求(假设此时正在进行SDRAM数据的读/写),计数器开始重新计数新的周期,但是此时并没有执行自动刷新命令,当本次SDRAM数据的读/写完毕后,仲裁模块响应自动刷新模块请求信号,然后才开始执行自动刷新命令,此时拉低自动刷新请求。

*会不会存在这么一个情况,计数时间到7.81us就拉高自动刷新请求(假设此时正在进行SDRAM数据的读/写),开始重新计数新的周期,新的周期再次计数到7.81us时,上次的刷新请求还没有被响应,导致少进行一次刷新?(可以思考一下这个问题,我们可以评论区讨论)

因此自动刷新模块的流程为:初始化结束后,每隔7.81us发出读请求→ 预充电命令→等待tRP时间→自动刷新命令→等待tRFC时间→自动刷新命令→等待tRFC时间

四、代码设计

根据分析可知,在自动刷新阶段各个状态跳转已经非常明确了,我们可以使用状态机去设计自动刷新模块流程,并设计一个计数器用于判断每个命令等待的时间是否满足,如果满足就可以跳转至下一个状态。完整的自动刷新模块代码以及仿真测试文件可以从github下载,SDRAM-Controller

状态机定义如下

 //自动刷新阶段的状态机
    localparam A_REF_IDLE  = 3'b000 ;//初始状态,a_ref_en为1并且初始化结束时跳转至预充电状态
    localparam A_REF_PRE   = 3'b001 ;//预充电命令
    localparam A_REF_TRP   = 3'b010 ;//预充电状态,等待tRP时间跳转至自动刷新
    localparam A_REF_A_R   = 3'b011 ;//自动刷新命令
    localparam A_REF_TRFC  = 3'b100 ;//等待tRFC时间,两次自动刷新后跳转至自动刷新结束
    localparam A_REF_END   = 3'b101 ;

sdram_a_ref.v 模块代码

/*
 * @Author: bit_stream 
 * @Date: 2024-12-14 11:21:39 
 * @Last Modified by: bit_stream
 * @Last Modified time: 2024-12-14 17:06:17
 */

//自动刷新流程如下
//每隔7.81us发出读请求→ 预充电命令→等待tRP时间→自动刷新命令→等待tRFC时间→自动刷新命令→等待tRFC时间

`timescale  1ns/1ns

module sdram_a_ref (
    input   wire            sys_clk     ,   //系统时钟,频率100MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            init_end    ,   //初始化结束信号
    input   wire            a_ref_en     ,   //自动刷新使能,当没有进行数据读写时进行自动刷新

    output  reg             a_ref_req    ,   //自动刷新请求
    output  reg     [3:0]   a_ref_cmd    ,   //自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}
    output  reg     [1:0]   a_ref_ba     ,   //自动刷新阶段Bank地址
    output  reg     [12:0]  a_ref_addr   ,   //地址数据,辅助预充电操作,A12-A0,13位地址
    output  wire            a_ref_end        //自动刷新结束标志
);

    //自动刷新阶段使用到的SDRAM  指令集
    localparam NOP                = 4'b0111 ;//空命令
    localparam PRECHARGE          = 4'b0010 ;//预充电命令
    localparam AUTO_REFRESH       = 4'b0001 ;//自动刷新命令
    
    //自动刷新阶段的状态机
    localparam A_REF_IDLE  = 3'b000 ;//初始状态,a_ref_en为1并且初始化结束时跳转至预充电状态
    localparam A_REF_PRE   = 3'b001 ;//预充电命令
    localparam A_REF_TRP   = 3'b010 ;//预充电状态,等待tRP时间跳转至自动刷新
    localparam A_REF_A_R   = 3'b011 ;//自动刷新命令
    localparam A_REF_TRFC  = 3'b100 ;//等待tRFC时间,两次自动刷新后跳转至自动刷新结束
    localparam A_REF_END   = 3'b101 ;
   
    //各个阶段等待的周期数,一个时钟周期为10ns,0.01us
    localparam A_REF_WAIT_CLK  = 781 ;    //7.81us
    localparam A_REF_TRP_CLK   = 2      ; //根据数据手册可知,最小时间为20ns
    localparam A_REF_TRFC_CLK  = 7      ; //根据数据手册可知,最小时间为66ns

    //自动刷新次数,每隔7.81us进行两次刷新操作。32ms内就可以把8192行全部刷新玩 
    localparam A_REF_A_R_TIME  = 2 ;


    //定义状态机
    reg [2:0] a_ref_state;

    //定义计数器,计数需要等待的时间
    reg [4:0] cnt_clk;

    //定义计数器使能信号,在特定的状态使能计数器计数
    reg cnt_clk_en; //其为1时,使能计数,其为0时,计数器清零

    
    //定义计数器,计数发出刷新请求的时间
    reg [11:0] cnt_areq;//位宽大一些,计数器计数到最大值归零

    always @(posedge sys_clk) begin
        if (~sys_rst_n ) begin
            cnt_areq <= 0;
        end else if (cnt_areq == A_REF_WAIT_CLK -1) begin
            cnt_areq <= 0;
        end else if(init_end) begin
            cnt_areq <= cnt_areq + 1;
        end
    end

    //a_ref_req每隔7.81us拉高一次,开始进行自动刷新后拉低
    always @(posedge sys_clk) begin
        if (~sys_rst_n) begin
            a_ref_req <= 0;
        end else if (cnt_areq == A_REF_WAIT_CLK -1) begin
            a_ref_req <= 1;
        end else if (a_ref_state == A_REF_PRE) begin
            a_ref_req <= 0;
        end 
    end
    
    

    always @(posedge sys_clk) begin
        if (~sys_rst_n) begin
            cnt_clk <= 'd0;
        end else if (cnt_clk_en) begin
            cnt_clk <= cnt_clk + 1'b1;
        end else if (~cnt_clk_en) begin
            cnt_clk <= 'd0;
        end 
    end 

    
    always @(*) begin //信号立刻变化
        if (~sys_rst_n) begin
            cnt_clk_en <= 0;
        end else begin
            case (a_ref_state)
                A_REF_TRP,A_REF_TRFC:begin
                    cnt_clk_en <= 1'b1;
                end 
                A_REF_IDLE,A_REF_PRE,A_REF_A_R,A_REF_END:begin
                    cnt_clk_en <= 1'b0;
                end
                default: begin
                    cnt_clk_en <= cnt_clk_en;
                end
            endcase
        end
    end




    //定义计数器,计数自动刷新的次数,每次回到初始状态需要清零
    reg [1:0] cnt_a_r_times;
    always @(posedge sys_clk) begin
        if (~sys_rst_n) begin
            cnt_a_r_times <= 'd0;
        end if (a_ref_state == A_REF_IDLE) begin
            cnt_a_r_times <= 'd0;
        end else if (a_ref_state == A_REF_A_R) begin
            cnt_a_r_times <= cnt_a_r_times + 1'b1;
        end
    end

    

    always @(posedge sys_clk) begin
        if (~sys_rst_n) begin
            a_ref_state <= A_REF_IDLE;
        end else begin
            case (a_ref_state)
                A_REF_IDLE:begin
                    if (a_ref_en & init_end) begin
                        a_ref_state <= A_REF_PRE;
                    end 
                end 
                A_REF_PRE:begin
                    a_ref_state <= A_REF_TRP;
                end
                A_REF_TRP:begin
                    if (cnt_clk == A_REF_TRP_CLK - 'd1) begin
                        a_ref_state <= A_REF_A_R;
                    end 
                end
                A_REF_A_R:begin
                    a_ref_state <= A_REF_TRFC;
                end
                A_REF_TRFC:begin
                    if ((cnt_clk == A_REF_TRFC_CLK - 'd1)&&(cnt_a_r_times < A_REF_A_R_TIME)) begin
                        a_ref_state <= A_REF_A_R;
                    end else if ((cnt_clk == A_REF_TRFC_CLK - 'd1)&&(cnt_a_r_times == A_REF_A_R_TIME)) begin
                        a_ref_state <= A_REF_END;
                    end 
                end
                A_REF_END:begin
                    a_ref_state <= A_REF_IDLE;
                end
                default: begin
                    a_ref_state <= a_ref_state;
                end
            endcase
        end
    end


    always @(posedge sys_clk) begin
        if (~sys_rst_n) begin
            a_ref_cmd  <= NOP;
            a_ref_ba   <= 2'b11;
            a_ref_addr <= 13'h1fff;
        end else begin
            case (a_ref_state)
                A_REF_IDLE:begin
                    a_ref_cmd  <= NOP;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end 
                A_REF_PRE:begin //A10为1,所有bank进行预充电
                    a_ref_cmd  <= PRECHARGE;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end 
                A_REF_TRP:begin
                    a_ref_cmd  <= NOP;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end 
                A_REF_A_R:begin
                    a_ref_cmd  <= AUTO_REFRESH;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end 
                A_REF_TRFC:begin
                    a_ref_cmd  <= NOP;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end 
                A_REF_END:begin
                    a_ref_cmd  <= NOP;
                    a_ref_ba   <= 2'b11;
                    a_ref_addr <= 13'h1fff;
                end
                
                default: begin
                    a_ref_cmd  <= a_ref_cmd;
                    a_ref_ba   <= a_ref_ba;
                    a_ref_addr <= a_ref_addr;
                end
            endcase
        end
    end

    assign a_ref_end = (a_ref_state == A_REF_END)? 1'b1:1'b0 ;

endmodule

五、Modelsim仿真结果


在这里插入图片描述
在这里插入图片描述

总结

以上是关于SDRAM控制器自动刷新模块的内容,下一篇进行SDRAM数据写模块的介绍。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值