前言
本篇文章主要记录如何实现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数据写模块的介绍。