SDRAM 内部存储体是利用电容能够保持电荷以及可充放电的特性制成,而电容所存储的电荷会随时间不断流失,会造成存储数据的丢失。为保证 SDRAM 中数据的可靠性,需要对 SDRAM 进行不断刷新。SDRAM 的刷新方式分为自刷新和自动刷新两种,这两种刷新方式,在实现和作用上存在差异。
自动刷新模式:作用是在 SDRAM 的正常操作过程中,保证数据不丢失,自动刷新过程需要外部时钟的参与,但刷新行地址由内部刷新计数器控制,无需外部写入。
自刷新模式则主要用于休眠模式低功耗状态下的数据保存,自刷新过程无需外部时钟参与,与自动刷新相同的是,刷新行地址由内部刷新计算器控制,无需外部写入。
两者的操作命令相同,当 CKE信号保持高电平时,写入刷新指令,进入自动刷新模式;当CKE 信号为低电平时,写入刷新指令,进入自刷新模式。
目前国际公认的标准是,存储体中电容的数据有效保存期上限是 64ms, 也就是说每一行刷新的循环周期最大为 64ms,那么刷新速度就是:行数/64ms。我们在 SDRAM 的数据手册中经常会看到 4096 Refresh Cycles/64ms 或 8192 Refresh Cycles/64ms 的相关介绍, 这里的 4096 与 8192 就代表SDRAM 芯片中单个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化, 当单个 L-Bank 为 4096 行时,刷新间隔最大为 15.625μs,单个 L-Bank 为 8192 行时,刷新间隔最大为 7.8125μs。(但是通常不会在边缘刷新,所以我们采用7.5us刷新一次)
SDRAM的所有操作都需要在完成初始化操作后才能进行,自动刷新操作自然也不例外。下图是MT48LC64M4A2的自动刷新时序图:

各信号说明如下:
CK:工作时钟,具体时钟频率视不同的芯片而不同
CKE:时钟使能,在整个初始化过程中都需要拉高
COMMAND:SDRAM命令,由4根线拼接而成,分别是CS#(片选信号),RAS#(行选通信号),CAS#(列选通信号),WE#(写使能信号),通过这4根命令线,再结合SDRAM的地址、输入输出数据等,就可以对SDRAM进行各种命令操作
DQM/DQML,DQMU:数据掩码,通过数据掩码可以实现对输入或输出数据的某一位进行“掩埋”,也就是使某一位失效
A[9:0],A[12:11]:数据地址线,同时也可用来设置模式寄存器
A10:数据地址线,同时也可以用来使能一些具体操作,比如控制自动预充电使能、使能预充电bank数量
BA[1:0]:bank地址
DQ数据:在初始化过程中无数据输出,保持高阻态就行
补充:
1.对所有BANK进行预充电操作,A10拉高即是选中所有BANK
2.进行预充电操作后需要等待一定的时间,即tRP,在此期间同样需要发送NOP空指令(发送空指令是为了防止对SDRAM进行误操作)
3.等待结束后发送自动刷新指令
4.进行自动刷新操作后需要等待一定的时间,即tRC,在此期间同样需要发送NOP空指令(发送空指令是为了防止对SDRAM进行误操作)
5.重复进行发送自动刷新指令与等待tRFC
6.tRFC等待时间结束后,SDRAM 自动刷新完成
模块图及端口:

时序图:

总结:
1、所有的操作都需要在完成初始化后进行,即init_end信号为高电平,才可以完成其他操作;
2、周期性自动刷新,需要定义一个寄存器cnt_ref,对刷新时间间隔进行计数;
3、cnt_ref计数到最大值max,完成一次计数后,输出自动刷新的请求信号aref_req,并发送给仲裁模块,仲裁模块同意请求后发送自动刷新aref_en;
4、内部收到aref_en后产生一个响应信号aref_ack,保持一段时间高电平,响应信号有效时拉低请求信号;
5、利用状态机(6个状态)实现
AREF_IDLE:初始状态,直到初始化完成后且仲裁模块发送自动刷新使能信号后,跳转至下一状态AREF_PCH,在此状态发送NOP指令
AREF_PCH:发送预充电指令状态、只维持一个时钟周期、下个时钟就跳转到状态AREF_TRP,在此状态发送预充电指令
AREF_TRP:预充电指令等待状态、在此状态等待时间满足TRP后就跳转到下一个状态AUTO_REF,在此状态发送NOP指令
AUTO_REF:发送自动刷新指令状态、只维持一个时钟周期、下个时钟就跳转到状态AREF_TRF,在此状态发送自动刷新指令
AREF_TRF:自动刷新指令等待状态、在此状态等待时间满足TRF时进行判断,若自动刷新次数满足要求(2次)后就跳转到下一个状态AREF_END,在此状态发送NOP指令,若不满足自动刷新次数要求就继续进行自动刷新操作,跳转到状态AUTO_REF
AREF_END:自动刷新结束状态,完成自动刷新后停留在这个状态一个周期;在此状态发送NOP指令,并发送一个自动刷新完成脉冲信号以通知仲裁模块自动刷新结束
代码:
- //----------------------------------------------------------------------------------------------------
- //--SDRAM自动刷新模块
- //----------------------------------------------------------------------------------------------------
-
- //------------<模块及端口声明>------------------------------------------------------------------------
- module sdram_aref(
- input wire sys_clk , //时钟信号,100M
- input wire sys_rst_n , //复位信号,低电平有效
- input wire init_end , //初始化完成信号,只有完成初始化后才能进行自动刷新操作
- input wire aref_en , //自动刷新使能信号,该信号拉高后才可以进行自动刷新操作
-
- output reg [3:0] aref_cmd , //4位SDRAM命令,组成{CS#,RAS#,CAS#,WE#}
- output reg [1:0] aref_ba , //2位BANK地址,共4个BANK
- output reg [12:0] aref_addr , //13位SDRAM地址
- output reg aref_end , //自动刷新完成信号,自动刷新完成后拉高一个时钟周期,通知给仲裁模块
- output reg aref_req //自动刷新请求信号,输出给仲裁模块,发起一次自动刷新请求
- );
-
- //------------<参数定义>----------------------------------------------------------------------------------
- //计数器最大值、刷新次数定义
- parameter CNT_REF_MAX = 10'd749 ; //自动刷新间隔时间,周期10ns,64ms/8192=7.812us,这里留些裕量,7.5us=7500ns,系统周期10ns,也就是计数750
- //等待时间参数定义
- parameter TRP = 3'd2 , //发送预充电指令后进行下一个操作需要等待的时间
- TRFC = 3'd7 ; //发送自动刷新指令后进行下一个操作需要等待的时间
- //命令指令参数
- parameter P_CHARGE = 4'b0010 , //预充电指令
- A_REF = 4'b0001 , //自动刷新指令
- NOP = 4'b0111 ; //空操作指令
- //状态机状态编码,用格雷码编码,也可以使用独热码(但是资源消耗多些)
- parameter AREF_IDLE = 3'b000, //自动刷新初始状态
- AREF_PCH = 3'b001, //预充电状态
- AREF_TRP = 3'b011, //预充电等待状态
- AUTO_REF = 3'b010, //自动刷新状态
- AREF_TRF = 3'b110, //自动刷新等待状态
- AREF_END = 3'b111; //自动刷新结束状态
- //------------<reg定义>----------------------------------------------------------------------------------
- reg [9:0] cnt_ref ; //自动刷新计时器,最大计数10.24us
- reg [2:0] aref_state ; //三段式状态机现态
- reg [2:0] next_state ; //三段式状态机次态
- reg [2:0] cnt_clk ; //状态机计数器,用于计数各个状态以实现状态跳转
- reg cnt_clk_rst ; //状态机计数器复位信号,高电平有效
- reg [1:0] cnt_aref ;
- //------------<wire定义>----------------------------------------------------------------------------------
- wire aref_ack ; //自动刷新响应信号,用来拉低刷新响应信号
- wire trp_end ; //预充电等待时间结束标志
- wire trf_end ; //自动刷新等待时间结束标志
-
- //=========================================================================================================
- //===========================<main code>==================================================================
- //=========================================================================================================
-
- //自动刷新计数器,每计数到一次最大值清零,表示需要进行一次刷新操作
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- cnt_ref <= 10'd0;
- else if(cnt_ref >= CNT_REF_MAX)
- cnt_ref <= 10'd0;
- else if(init_end)
- cnt_ref <= cnt_ref + 1'd1;
- end
-
- //自动刷新请求信号,每次计数到时间就发起自动刷新信号,发送出自动刷新指令后拉低
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- aref_req <= 1'b0;
- else if(cnt_ref == CNT_REF_MAX - 1'b1) //提前一个时钟周期拉高
- aref_req <= 1'b1;
- else if(aref_ack) //发送了刷新指令
- aref_req <= 1'b0;
- else
- aref_req <= aref_req;
- end
-
- //发送预充电时,即代表自动刷新模块响应了仲裁模块给出的自动刷新使能,所以拉高aref_ack
- assign aref_ack = (aref_state == AREF_PCH) ? 1'b1 : 1'b0;
-
- //------------<三段式状态机>----------------------------------------------------------------------------------
-
- //--状态机第一段:同步时序描述状态转移
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- aref_state <= AREF_IDLE;
- else
- aref_state <= next_state;
- end
-
- //--状态机第二段:组合逻辑判断状态转移条件,描述状态转移规律以及输出
- always@(*)begin
- next_state = AREF_IDLE;
- case(aref_state)
- AREF_IDLE:
- if(aref_en && init_end) //200us等待标志拉高则跳转到下一个状态,否则保持在该状态
- next_state = AREF_PCH;
- else
- next_state = AREF_IDLE;
- AREF_PCH:
- next_state = AREF_TRP; //跳转到TRP等待状态
- AREF_TRP:
- if(trp_end) //TRP等待标志拉高则跳转到下一个状态,否则保持在该状态
- next_state = AUTO_REF;
- else
- next_state = AREF_TRP;
- AUTO_REF:
- next_state = AREF_TRF; //跳转到TRFC等待状态
- AREF_TRF:
- if(trf_end)begin
- if(cnt_aref == 2'd2) //TRFC等待标志拉高且自动刷新次数满足时序要求则跳转到下一个状态
- next_state = AREF_END;
- else //自动刷新次数要求不达标,继续刷新
- next_state = AUTO_REF;
- end
- else
- next_state = AREF_TRF;
- AREF_END:
- next_state = AREF_IDLE; //一直维持改状态
- default:next_state = AREF_IDLE; //默认在初始状态
- endcase
- end
-
- //--状态机第三段:时序逻辑描述输出
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)begin //复位输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_cmd <= NOP;
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- else
- case(aref_state)
- AREF_IDLE:begin //输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_cmd <= NOP;
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- AREF_PCH:begin
- aref_cmd <= P_CHARGE; //输出预充电指令,A10拉高选中所有BANK、BANK地址不关心
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- AREF_TRP:begin
- aref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- AUTO_REF:begin
- aref_cmd <= A_REF; //输出自动刷新指令,地址、BANK地址不关心,全拉高就行
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- AREF_TRF:begin
- aref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- AREF_END:begin
- aref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- default:begin //默认状态输出NOP指令,地址、BANK地址不关心,全拉高就行
- aref_cmd <= NOP;
- aref_ba <= 2'b11;
- aref_addr <= 13'h1fff;
- end
- endcase
- end
-
-
- //用于计数各个状态以实现状态跳转,计数复位信号cnt_clk_rst有效时复位,其他时间累加
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- cnt_clk <= 3'd0;
- else if(cnt_clk_rst)
- cnt_clk <= 3'd0;
- else
- cnt_clk <= cnt_clk + 1'd1;
- end
-
- //工作状态计数器的复位信号
- always@(*)begin
- case(aref_state)
- AREF_IDLE: cnt_clk_rst = 1'b1; //计数器清零
- AREF_TRP: cnt_clk_rst = (trp_end)? 1'b1 : 1'b0; //完成TRP等待则计数器清零,否则计数
- AREF_TRF: cnt_clk_rst = (trf_end)? 1'b1 : 1'b0; //完成TRFC等待则计数器清零,否则计数
- AREF_END: cnt_clk_rst = 1'b1; //计数器清零
- default: cnt_clk_rst = 1'b0; //计数器清零
- endcase
- end
-
-
- //因为状态跳转是时序逻辑,所以在前一个周期拉高时间等待参数的标志信号,用来进行状态跳转
- assign trp_end = ((aref_state == AREF_TRP) && (cnt_clk == TRP - 1'b1))? 1'b1 : 1'b0;
- assign trf_end = ((aref_state == AREF_TRF) && (cnt_clk == TRFC - 1'b1))? 1'b1 : 1'b0;
-
- //自动刷新结束信号,只有在处于结束状态时拉高一个时钟周期,其他时间一律保持低电平
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- aref_end <= 1'b0;
- else if(trf_end && cnt_aref == 2'd2)
- aref_end <= 1'b1;
- else
- aref_end <= 1'b0;
- end
-
- //刷新次数计数器,每进一次状态(INIT_AR,自动刷新指令)则+1,
- always@(posedge sys_clk or negedge sys_rst_n)begin
- if(!sys_rst_n)
- cnt_aref <= 2'd0;
- else if(aref_state == AREF_IDLE)
- cnt_aref <= 2'd0;
- else if(aref_state == AUTO_REF)
- cnt_aref <= cnt_aref + 1'd1;
- else
- cnt_aref <= cnt_aref;
- end
-
- endmodule