一、对SDRAM的初步认识
1.1 什么是SDRAM
SDRAM(Synchronous Dynamic Random Access Memory),同步动态随机存取存储器。
同步:工作频率与对应控制器的系统时钟频率相同,且内存内部的命令以及数据的传输都以此为基准
动态:SDRAM利用电容存储数据,掉电数据丢失,因此存储序列需要通过不断刷新来保证数据不丢失
随机:数据不是线性存储的(fifo),可随机指定地址进行读写。
1.2 SDRAM的行列地址
首先给出一位数据存取电路如下:
要想将这一位数据读取出,我们需要先打开行地址(激活/active),然后打开列地址,则电容的电平状态即可呈现在data_bit上,从而实现了一位数据的读取。或者说,数据线上的电平状态被送到了电容上,实现一位数据的写入。
从打开行地址到能够打开列地址之间有一个时间间隔——tRCD(器件不同,该值也不同)
.
列地址打开,到数据呈现在data_bit上也有一定的时钟延迟,称为列选通潜伏期(CL)
由此我们可得到SDRAM内部行列地址的简化模型,其中每一行列,只展示1bit的数据。
当我们指定行列地址线,即可得到对应的存储单元。
如图中指定行地址=3,列地址=5,可找到对应的黑色处的存储单元。
上图中的存储单元可存放宽位为8/16/32的数据。对于整个行列组成的块来说,称为逻辑Bank(L-Bank)。
若采用一个Bank可能导致寻址冲突,因此大部分SDRAM内部都以4个逻辑bank设计,数据读写的流程:
指定逻辑Bank地址——指定行地址——指定列地址——确定寻址存储单元。
注意:一次只能对一个 Bank 的一个存储单元进行操作。
1.3 SDRAM容量的计算
一个存储单元的容量等于数据总线的位宽,单位是bit。因此总的SDRAM存储容量:
SDRAM总存储容量 = L-Bank的数量×行数×列数×存储单元的容量
若SDRAM的行地址13bit,列地址9bit,4个bank,16bit数据,则
SDRAM容量 = 4 × 2^13 × 2^9 × 16 = 268435456 bit / 8 = 33554432 (B )= 33554432 / 1024 (KB) = 32768 /1024 MB = 32MB
1.4 SDRAM引脚与定义
x4 - x16:分别表示4、8、16位宽
#:表示低电平有效;
— :表示 x8 和 x4 引脚功能与 x16 引脚功能相同。
定义如下:
CLK:SDRAM工作时钟,时钟上升沿进行输入采样
BAn[1:0]:Bank地址线
DQn [15:0]:双向数据总线
An[12:0]:地址线,当选择某个Bank的行地址时,要用到A0-A11;当选择列地址的时候,用A0-A8,A10信号可以用来控制Auto-precharge自动预充电
跟着大佬学习SDRAM
1.5 SDRAM内部结构框图
通常SDRAM有四个Bank,如Bank0 ~ Bank3;
为实现寻址及其他功能,其内部还有一系列控制器,命令解码器(通过发送来的命令操作内存条,命令操作如:SDRAM初始化及读写命令等)、逻辑控制单元、地址寄存器、数据寄存器等。
详细来说:外部通过CS_N、RAC_N、CAS_N、WE_N 这四个信号构成命令信号以及地址总线(片选信号确定bank,行列选通信号给定行列地址,WE给予写使能),然后命令经过命令解码器进行译码后,将控制参数保存到模式寄存器中,逻辑控制单元就能控制逻辑运行。
二、SDRAM操作命令
基本操作命令如下:
#:表示低电平有效
NOP:空操作
预充电:就是将数据写回到电容,因此要打开行地址,为保证下一次读的时候数据未丢失
2.1 空命令
不论 SDRAM 处于何种状态,此命令均可被写 入,该命令能给被选中的SDRAM 传递一个空操作,目的是为了防止 SDRAM 处于空闲或等待状态时,其他命令被写入。如上图中的前两个,没有选中bank以及选中bank,但没有给与行列及写使能的情况下,都为NOP命令。
2.2 加载模式寄存器命令
1、内部结构框图中我们看到有mode register ,在SDRAM上电初始化的时候(所有的Bank都处于空闲状态),执行配置模式寄存器命令后,SDRAM 必须等待相应的响应时间 tMRD(Register Set Cycle)后,才可执行新的命令。
2、模式寄存器命令通过地址总线 A0-A11 配置,不同的赋值对应寄存器配置的不同模式,未使用的地址总线A12设置为低电平,同时模式寄存器将会一直保存配置信息,直到下一次编程或者掉电为止。
如下是模式寄存器地址总线定义A12-A0 :
A0-A2 控制突发长度,Brust Length:
突发是同一行中相邻存储单元连续对数据传输,其中连续的数据量就是突发长度BL。
典型的突发方式是1,2,4,8,Full Page,其中全页突发传输是指逻辑Bank里一行中的所有存储单元从头到尾的传输,因此全页一次可传输一整行的数据量。
A3控制突发类型,BT:
A3 = 0:顺序突发;A3 = 1:隔行突发
A6,A5,A4控制列选通潜伏期,CAS Latency
从读命令被寄存,到 数据出现在数据总线上的时间间隔称为列选通给潜伏期,可被设置为 2 或 3 个时钟周期
可看到,读命令给出后,在T2的时候数据总线上出现第一个有效数据,因此CL = 2;
同理,上图CL = 3 ;
A7,A8控制运行模式,Operating Mode
SDRAM 存在标准模式、测试模式等多种模式,{A7,A8} = {0,0} ,进入标准模式.。
A9控制写模式,WB
当 A9 =0,SDRAM 的读/写均采用突发方式,突发长度由突发长度寄存器(A0-A2)设定;
当 A9 =1 时, SDRAM 的读采用突发,突发长度由突发长度寄存器(A0-A2)设定,但 写不使用突发方式,每一个写命令只能写入一个数据。
A10-A12预留位,Reserver
对模式寄存器的配置不起作用,赋值为 0 即可。
有关命令的解释:
激活命令(active)
控制激活命令 {CS_N,RAS_N,CAS_N,WE_N} = 4‘b0011.
激活命令是用来打开一个特定的Bank(由BA0和BA1决定)和指定的行(由A0-A12决定)。
该行会一直保持激活状态并可进行读写,当执行一个预充电命令后,该行才被关闭。
读命令(read)
控制激活命令 {CS_N,RAS_N,CAS_N,WE_N} = 4‘b0101.
特定的Bank(由BA0和BA1决定)和指定的列(由A0-A9决定,A10控制是否在突发读取完成后立即自动执行预充电操作,低表示不执行)
读命令用来启动对一个已经激活行的突发读取操作
上图看出,当出现读命令,经过2个clk的列选通潜伏期后,数据总线上出现第一个有效数据,突发写长度我们指定为4,因此写了n~n+3,又因为第二个读命令出现的时候,同样在2个clk后,数据总线上可出现第一个有效数据b……
写命令(write)
控制激活命令 {CS_N,RAS_N,CAS_N,WE_N} = 4‘b0100.
写命令用来启动对一个已经激活行的突发写操作。(A10控制是否在突发写入完成后立即自动执行预充电操作,低电平不执行)
可看到,写突发长度为2,因此当写命令出现,同时指定相应bank和列,因此此时即可向数据总线上写入数据。
预充电命令(Precharge)
控制激活命令 {CS_N,RAS_N,CAS_N,WE_N} = 4‘b0010.
预充电命令是关闭指定的bank或者全部的bank(单一或者全部bank由A10决定,高电平表示对所有bank行预充电,低电平表示对指定的bank中的行进行预充电)
该命令执行后,必须等待对应的**tRP(**预充电命令周期),相对应的Bank才可以被重新操作。
预充电命令在初始化过程、自动刷新和读写操作中都会用到。
自动预充电命令(Auto Precharge)
不增加额外执行指令的前提下,达到预充电指令一样的效果。该功能是在对 SDRAM 发出读写命令时,使用 A10 指明是
否在突发读写完成后立即自动执行预充电操作来实现的
突发终止命令(BURST TERMINATE)
控制激活命令 {CS_N,RAS_N,CAS_N,WE_N} = 4‘b0110.
突发中断命令用来截断固定长度或者整页长度的突发。(不会关闭行,只是会中断)
自动刷新命令(Auto Refresh)
SDRAM 掉电数据丢失,只有通过刷新操作才能保证数据的可靠性。刷新包括自动刷新和自刷新。发送命令后CKE时钟为有效时,使用自动刷新操作,否则使用自刷新(自我刷新)操作(主要用于休眠模式低功耗状态下的数据保存)。
SDRAM 的刷新操作是周期性的,在两次刷新的间隔可以进行数据的相关操作。我们在看SDRAM芯片参数时,经常会看到4096 Refresh Cycles/64ms的标识,这里的4096就代表芯片中每个Bank的行数。
刷新命令一次仅对一行有效,也就是说在64ms内芯片需要完成4096次刷新操作,因此平均15.625μs刷新一次,也可以一次全部刷新完,却决于你的数据读写时序。
数据掩码
数据掩码用来屏蔽不需要的数据,当突发长度为4的时候,说明连续传输4bit数据,若其中第二bit数据不需要,那我们就可采用数据掩码DQM技术,每个DQM信号线对应一个字节,通过DQM,内存可控制I/O端口对输入或者输出数据取消。
三、SDRAM上电初始化
SDRAM芯片要想正常使用,必须在上电后进行初始化操作,否则不能正常使用。
如下是初始化流程:
上电后首先等待至少100us,一般直接200us,等待期间给予空命令操作。(不同芯片时间不同)
.
延时200us后,对所有bank预充电,从而关闭所有bank的所有行(A10为高电平),使得所有bank进入空闲状态
.
进入空闲状态后,至少需要执行两个周期的自动刷新命令,完成后,进行加载模式寄存器命令
分析初始化时序:
关于等待时间:
tRP:发出预充电命令后需要等待的时间,一般是2个clk
tRFC:发出自动刷新命令后需要等待的时间,一般是7个clk
tMRD:发送设置模式寄存器命令后需要等待的时间,一般是3个clk
通过时序图,我们对初始化进行如下的总结:
1、首先上电且CKE设置为低,等待100us,同时在100us的某时刻将CKE信号变为高电平,同时发送空命令(防止对SDRAM产生误操作)。
2、第二个clk,A10拉高,对所有的Bank进行预充电。
3、进行预充电后等待tRP时间,在此期间仍然发送空命令。
4、经过tRP时间后,在第3个clk,进入到自动刷新命令,经过tRFC时间,此过程也发送空命令。
5、经过tRFC时间后,再次进行自动刷新操作(不同芯片的刷新次数不同,至少两个clk的自动刷新命令)。
6、重复自动刷新并经过tRFC后,进入到加载模式寄存器命令,地址位A0-11来写入模式寄存器的值。
7、等待tMRD时间,且期间发送空命令,tMRD时间后ACTIVE,—— SDRAM初始化完成。
SDRAM初始化的状态图:
八根据上面分析得到的如下的八个状态及跳转条件,此时我们即可对SDRAM初始化模块进行设计。
初始化模块的输入输出信号:
四、SDRAM自动刷新
通过时序图,我们对自动刷新进行如下的总结:
1、初始化完成之后
,CKE拉高,仲裁模块发送自动刷新使能信号
,并进入预充电命令
2、等待tRP时间,期间发送空命令
3、tRP时间
过后,发送自动刷新命令
,等待tRFC
时间,期间发送空命令
4、tRFC时间后,发送自动刷新命令,再等待tRFC时间,期间发送空命令
5、经过tRFC时间后,自动刷新操作完成
自动刷新模块的输入输出信号:
五、页突发读时序
通过时序图,我们对突发读进行如下的总结:
1、发送激活ACTIVE命令
,同时发送对应的bank地址以及行地址
2、等待tRCD时间,期间发送空命令
3、tRCD时间
过后,发送读命令
,指定bank地址以及列地址
4、等待列选通潜伏期
(CL = 2个clk),期间发送空命令
5、2个clk后看到读到的数据依次呈现在了数据总线上,完成全页读操作。
6、Tn+2时刻,发出了突发终止命令
,因此全页读被中断,由此,可采用突发终止命令控制突发长度。
7、突发读终止后,发送预充电命令
(手动预充电A10拉高),开关闭所有bank的行,等待tRP时间,
期间发送空命令,tRP时间过后,一次突发读操作完成
。
注意:绝大部分SDRAM都不支持在全页突发的情况下自动预充电,所以我们的读操作时序中包含手动预充电,也就是上述总结中的7
.
思考1:为什么依次读数据,会由m,m+1,m+2,到了m-1?
因此数据的读写的回环的,比如从0开始读,一行是512,那么就是0-511,如果从100开始读,那么就是100-511,再就是0-99
.
思考2:为什么Dout m-1后,又出现了m?
因为全页突发后不能自动停止,因此我们要想让它停止操作,需要采用突发终止命令。
突发读模块的输入输出端口:
六、页突发写时序
1、发送激活ACTIVE命令
,同时发送对应的bank地址以及行地址
2、等待tRCD时间,期间发送空命令
3、tRCD时间
过后,发送写命令
,指定写入的bank地址以及列地址
4、写的时候无列选通潜伏期,直因此写命令发出的时候,数据总线上依次输出写入的数据。
5、不断写入,完成全页写操作。
6、但在Tn+2时刻,发出了突发终止命令
,因此全页写被中断。
7、突发写终止后,发送预充电命令
(手动预充电A10拉高),开关闭所有bank的行,等待tRP时间
,期间发送空命令,tRP时间过后,一次突发写操作完成
。
状态转换图如下:
七、初始化模块的verilog代码实现
//--SDRAM初始化模块
module sdram_init(
input init_clk , //时钟信号,100M
input init_rst_n ,
output reg [12:0] init_addr, //13位SDRAM地址
output reg [3:0] init_cmd , //4位SDRAM命令,组成{CS#,RAS#,CAS#,WE#}
output reg [1:0] init_bank , //4个BANK
output reg init_end //初始化完成信号
);
//定义计数器最大值、刷新次数
localparam T_WAIT = 15'd20_000; //100M时钟频率10ns,上电后等待200us,计数200us/10ns = 20000次
localparam AR_MAX = 4'd8 ; //自动刷新次数8次
//等待时间参数定义
localparam TRP = 3'd2 ; //发送预充电指令后等待的时间
localparam TRFC = 3'd7 ; //发送自动刷新指令后等待的时间
localparam TMRD = 3'd3 ; //发送设置模式寄存器指令后等待的时间
//命令指令参数
localparam PRECHARGE = 4'b0010 , //预充电指令
AT_REF = 4'b0001 , //自动刷新指令
NOP = 4'b0111 , //空指令
MREG_SET = 4'b0000 ; //模式寄存器设置指令
//状态机状态格雷码编码
localparam INIT_WAIT = 3'b000, //上电后等待状态
INIT_PRE = 3'b001, //预充电状态
INIT_TRP = 3'b011, //预充电等待状态
INIT_AR = 3'b010, //自动刷新状态
INIT_TRFC = 3'b110, //自动刷新等待状态
INIT_MRS = 3'b111, //模式寄存器设置状态
INIT_TMRD = 3'b101, //模式寄存器设置等待状态
INIT_END = 3'b100; //初始化完成状态
reg [14:0] cnt_wait ; //200us延时等待计数器,20000次
reg [2:0] state ;
reg [2:0] next_state ;
reg [3:0] cnt_ar ; //自动刷新次数计数器,记录刷新次数
reg [3:0] cnt_fsm ; //状态机计数器,用于计数各个状态以实现状态跳转
reg cnt_fsm_reset; //状态机计数器复位信号,高电平有效
wire wait_end_flag ; //上电等待时间结束标志
wire trp_end_flag ; //预充电等待时间结束标志
wire trfc_end_flag ; //自动刷新等待时间结束标志
wire tmrd_end_flag ; //模式寄存器配置等待时间结束标志
assign wait_end_flag = (cnt_wait == T_WAIT - 'd1)? 1'b1 : 1'b0; //计数200us拉高
assign trp_end_flag = ((state == INIT_TRP) && (cnt_fsm == TRP - 1'b1))? 1'b1 : 1'b0;
assign trfc_end_flag = ((state == INIT_TRFC) && (cnt_fsm == TRFC - 1'b1))? 1'b1 : 1'b0;
assign tmrd_end_flag = ((state == INIT_TMRD) && (cnt_fsm == TMRD - 1'b1))? 1'b1 : 1'b0;
//初始化完成信号
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)
init_end <= 1'b0;
else if(state == INIT_END)
init_end <= 1'b1;
else
init_end <= 1'b0;
end
//自动刷新次数计数器
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)
cnt_ar <= 4'd0;
else if(state == INIT_WAIT)
cnt_ar <= 4'd0;
else if(state == INIT_AR)
cnt_ar <= cnt_ar + 1'd1;
else
cnt_ar <= cnt_ar;
end
//200us计数器,计数到最大值后,计数器一直保持不变
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)
cnt_wait <= 15'd0;
else if(cnt_wait == T_WAIT)
cnt_wait <= cnt_wait;
else
cnt_wait <= cnt_wait + 1'd1;
end
//状态机复位计数器
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)
cnt_fsm <= 4'd0;
else if(cnt_fsm_reset)
cnt_fsm <= 4'd0;
else
cnt_fsm <= cnt_fsm + 1'd1;
end
//工作状态计数器的复位信号
always@(*)begin
case(state)
INIT_WAIT: cnt_fsm_reset = 1'b1; //计数器清零
INIT_TRP: cnt_fsm_reset = (trp_end_flag)? 1'b1 : 1'b0; //完成TRP等待则计数器清零,否则计数
INIT_TRFC: cnt_fsm_reset = (trfc_end_flag)? 1'b1 : 1'b0; //完成TRFC等待则计数器清零,否则计数
INIT_TMRD: cnt_fsm_reset = (tmrd_end_flag)? 1'b1 : 1'b0; //完成TMRD等待则计数器清零,否则计数
INIT_END: cnt_fsm_reset = 1'b1; //计数器清零
default: cnt_fsm_reset = 1'b0; //计数器清零
endcase
end
//第一段状态,时序逻辑描述状态转移
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)
state <= INIT_WAIT;
else
state <= next_state;
end
//第二段状态机,组合逻辑描述状态转移
always@(*)begin
next_state = INIT_WAIT;
case(state)
INIT_WAIT: next_state = (wait_end_flag ) ? INIT_PRE : INIT_WAIT;
INIT_PRE:
next_state = INIT_TRP;
INIT_TRP:next_state = (trp_end_flag ) ? INIT_AR : INIT_TRP;
INIT_AR:
next_state = INIT_TRFC;
INIT_TRFC:
if(trfc_end_flag)begin
if(cnt_ar == AR_MAX)
next_state = INIT_MRS;
else
next_state = INIT_AR;
end
else
next_state = INIT_TRFC;
INIT_MRS:
next_state = INIT_TMRD;
INIT_TMRD:
if(tmrd_end_flag)
next_state = INIT_END;
else
next_state = INIT_TMRD;
INIT_END:
next_state = INIT_END;
default:next_state = INIT_WAIT;
endcase
end
//第三段状态机,时序逻辑描述输出
always@(posedge init_clk or negedge init_rst_n)begin
if(!init_rst_n)begin //复位输出NOP指令,地址、BANK地址不关心,全拉高就行
init_cmd <= NOP;
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
else
case(state)
INIT_WAIT:begin //输出NOP指令,地址、BANK地址不关心,全拉高就行
init_cmd <= NOP;
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_PRE:begin
init_cmd <= PRECHARGE; //输出预充电指令,A10拉高选中,关闭所有BANK、BANK地址不关心
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_TRP:begin
init_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_AR:begin
init_cmd <= AT_REF; //输出自动刷新指令,地址、BANK地址不关心,全拉高就行
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_TRFC:begin
init_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_MRS:begin
init_cmd <= MREG_SET; //输出模式寄存器配置指令,A0~A12地址进行模式配置、BANK地址全拉低
init_bank <= 2'b00;
init_addr <=
{
3'b000, //A12-A10:预留
1'b0 , //A9=0:读写方式,0:突发读&突发写,1:突发读&单写
2'b00 , //{A8,A7}=00:标准模式,默认
3'b011, //{A6,A5,A4}=011:CAS 潜伏期,010:2,011:3,其他:保留
1'b0 , //A3=0:突发传输方式,0:顺序,1:隔行
3'b111 //{A2,A1,A0}=111:突发长度,000:单字节,001:2 字节
};
end
INIT_TMRD:begin //输出NOP指令,地址、BANK地址不关心,全拉高就行
init_cmd <= NOP;
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
INIT_END:begin
init_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
default:begin //默认状态输出NOP指令,地址、BANK地址不关心,全拉高就行
init_cmd <= NOP;
init_bank <= 2'b11;
init_addr <= 13'h1fff;
end
endcase
end
endmodule
生成的状态转换图:
八、自动刷新模块的verilog代码实现
module sdram_atref(
input atref_clk,
input atref_rst_n,
input atref_en, //仲裁模块发出的自动刷新使能信号
input init_end, //初始化完成信号
output reg [1:0] atref_bank,
output reg [12:0] atref_addr,
output reg [3:0] atref_cmd,
output reg atref_end,
output reg atref_req //当不满足刷新次数时,向仲裁模块发送一次自动刷新请求信号
);
//命令定义
localparam PRECHARGE = 3'b001; //预充电指令
localparam AT_REF = 3'b010; //自动刷新指令
localparam NOP = 3'b100; //空指令
//状态机定义
localparam ATREF_IDLE = 3'b000;
localparam ATREF_PRE = 3'b001;
localparam ATREF_TRP = 3'b011;
localparam ATREF_AR = 3'b010;
localparam ATREF_TRFC = 3'b110;
localparam ATREF_END = 3'b111;
//参数定义
localparam AR_MAX = 4'd2; //自动刷新次数
localparam T_ATREF = 10'd700; //自动刷新时间间隔,100Mhz,因此周期10ns,64ms/8192= 7.812us,取7us,计数700次
localparam TRP = 3'd2 ;
localparam TRFC = 3'd7 ;
reg [2:0] state , next_state;
reg [1:0] cnt_ar;
reg [3:0] cnt_fsm;
reg [9:0] cnt_atref;
reg cnt_fsm_reset;
wire cnt_trp_end;
wire cnt_trfc_end;
wire aref_ack;
assign cnt_trp_end = ((state == ATREF_TRP)&&(cnt_fsm == TRP -1)) ? 1'b1 : 0;
assign cnt_trfc_end = ((state == ATREF_TRFC)&&(cnt_fsm == TRFC -1)) ? 1'b1 : 0;
//发送预充电时,即代表自动刷新模块响应了仲裁模块给出的自动刷新使能
assign aref_ack = (state == ATREF_PRE) ? 1'b1 : 1'b0;
//自动刷新请求信号,每次计数到时间就发起自动刷新请求信号,发送出自动刷新指令后拉低
always@(posedge atref_clk or negedge atref_rst_n)begin
if(!atref_rst_n)
atref_req <= 1'b0;
else if(cnt_atref == T_ATREF - 1'b1)
atref_req <= 1'b1;
else if(aref_ack) //已经发出刷新指令
atref_req <= 1'b0;
else
atref_req <= atref_req;
end
//自动刷新完成信号
always@(posedge atref_clk or negedge atref_rst_n)begin
if(!atref_rst_n)
atref_end <= 1'b0;
else if(state == ATREF_END)
atref_end <= 1'b1;
else
atref_end <= 1'b0;
end
//自动刷新计数器,每计数到一次最大值清零,表示需要进行一次刷新操作
always@(posedge atref_clk or negedge atref_rst_n)begin
if(!atref_rst_n)
cnt_atref <= 10'd0;
else if(init_end)begin
if(cnt_atref == T_ATREF -1'b1 )
cnt_atref <= 10'd0;
else
cnt_atref <= cnt_atref + 1'd1;
end
else
cnt_atref <= cnt_atref;
end
//自动刷新次数计数器
always@(posedge atref_clk or negedge atref_rst_n)begin
if(!atref_rst_n)
cnt_ar <= 2'd0;
else if(state == ATREF_IDLE)
cnt_ar <= 2'd0;
else if(state == ATREF_AR)
cnt_ar <= cnt_ar + 1'd1;
else
cnt_ar <= cnt_ar;
end
//用于计数各个状态以实现状态跳转
always@(posedge atref_clk or negedge atref_rst_n)begin
if(!atref_rst_n)
cnt_fsm <= 4'd0;
else if(cnt_fsm_reset)
cnt_fsm <= 4'd0;
else
cnt_fsm <= cnt_fsm + 1'd1;
end
//状态计数器复位信号,cnt_fsm_reset
always@(*)begin
case(state)
ATREF_IDLE: cnt_fsm_reset = 1'b1; //计数器清零
ATREF_TRP: cnt_fsm_reset = (cnt_trp_end)? 1'b1 : 1'b0; //完成TRP等待则计数器清零,否则计数
ATREF_TRFC: cnt_fsm_reset = (cnt_trfc_end)? 1'b1 : 1'b0; //完成TRFC等待则计数器清零,否则计数
ATREF_END: cnt_fsm_reset = 1'b1; //计数器清零
default: cnt_fsm_reset = 1'b0; //计数器清零
endcase
end
//第一段,描述状态寄存器
always@(posedge atref_clk or negedge atref_rst_n)
if(!atref_rst_n)
state <= ATREF_IDLE;
else
state <= next_state;
//第二段,组合逻辑描述状态转移
always @ (*)begin
next_state = ATREF_IDLE;
case(state)
ATREF_IDLE: next_state = (init_end && atref_en ) ? ATREF_PRE : ATREF_IDLE;
ATREF_PRE : next_state = ATREF_TRP;
ATREF_TRP : begin
if (cnt_trp_end)
next_state = ATREF_AR;
else
next_state = ATREF_TRP;
end
ATREF_AR : next_state = ATREF_TRFC;
ATREF_TRFC: begin
if(cnt_trfc_end)begin
if(cnt_ar == AR_MAX)
next_state = ATREF_END;
else
next_state = ATREF_AR;
end
else
next_state = ATREF_TRFC;
end
ATREF_END : next_state = ATREF_IDLE;
default : next_state = ATREF_IDLE;
endcase
end
//第三段,时序逻辑描述输出
always@(posedge atref_clk or negedge atref_rst_n)
if(!atref_rst_n)begin //复位输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_cmd <= NOP;
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
else
case(state)
ATREF_IDLE:begin //输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_cmd <= NOP;
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
ATREF_PRE:begin
atref_cmd <= PRECHARGE; //输出预充电指令,A10控制预充电拉高,关闭所有的bank,BANK地址不关心
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
ATREF_TRP:begin
atref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
ATREF_AR:begin
atref_cmd <= AT_REF; //输出自动刷新指令,地址、BANK地址不关心,全拉高就行
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
ATREF_TRFC:begin
atref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
ATREF_END:begin
atref_cmd <= NOP; //输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
default:begin //默认状态输出NOP指令,地址、BANK地址不关心,全拉高就行
atref_cmd <= NOP;
atref_bank <= 2'b11;
atref_addr <= 13'h1fff;
end
endcase
endmodule
生成的状态图如下:
九、突发读模块的verilog实现
module sdram_read(
input rd_clk ,
input rd_rst_n ,
input rd_en,
input init_end,
input [23:0] rd_addr,
input [15:0] rd_data,
input [9:0] rd_burst_len,
output rd_ack,//读SDRAM响应信号
output rd_end, //一次突发读完成
output reg [12:0] rd_sdram_addr,
output reg [3:0] rd_sdram_cmd ,
output reg [1:0] rd_sdram_bank ,
output [15:0] rd_sdram_data
);
//定义时间参数
parameter TRP = 3'd2;
parameter TCL = 3'd3; //列潜伏期
parameter TRCD = 3'd2; //激活等待周期
//定义命令
parameter NOP = 3'b000;
parameter ACTIVE = 3'b001;
parameter READ = 3'b011;
parameter BURST_STOP = 3'b010;
parameter PRECHARGE = 3'b110;
//定义状态
parameter RD_IDLE = 4'b0000 , //空闲
RD_ACT = 4'b0001 , //行激活
RD_TRCD = 4'b0011 , //激活等待
RD_READ = 4'b0010 , //读操作
RD_CL = 4'b0100 , //潜伏期等待
RD_DATA = 4'b0101 , //读数据
RD_PRE = 4'b0111 , //预充电状态
RD_TRP = 4'b0110 , //预充电等待
RD_END = 4'b1100 ; //一次突发读结束
reg [3:0] state,next_state;
reg [9:0] cnt_fsm;
reg [15:0] rd_data_reg;
reg cnt_fsm_reset;
wire cnt_trcd_end;
wire cnt_cl_end ;
wire cnt_rd_end ;
wire cnt_trp_end;
wire cnt_rdburst_end;
assign cnt_trcd_end = ((state == RD_TRCD) && (cnt_fsm == TRCD ) ) ? 1'b1 : 0;
assign cnt_cl_end = ((state == RD_CL) && (cnt_fsm == TCL - 1'b1) ) ? 1'b1 : 0;
assign cnt_rd_end = ((state == RD_DATA) && (cnt_fsm == rd_burst_len) ) ? 1'b1 : 0; //读突发结束
assign cnt_trp_end = ((state == RD_TRP) && (cnt_fsm == TRP ) ) ? 1'b1 : 0;
assign cnt_rdburst_end = ((state == RD_DATA) && (cnt_fsm == rd_burst_len - 4) ) ? 1'b1 : 0;//读突发终止
//状态机计数器
always @(posedge rd_clk or negedge rd_rst_n )
if(!rd_rst_n)
cnt_fsm <= 0;
else if (cnt_fsm_reset)
cnt_fsm <= 0;
else
cnt_fsm <= cnt_fsm + 1'b1;
//cnt_fsm_reset,状态机计数器复位信号,高电平复位
always@(*)begin
case(state)
RD_IDLE: cnt_fsm_reset <= 1'b1;
RD_TRCD: cnt_fsm_reset <= (cnt_trcd_end == 1'b1) ? 1'b1 : 1'b0;
RD_READ: cnt_fsm_reset <= 1'b1;
RD_CL: cnt_fsm_reset <= (cnt_cl_end == 1'b1) ? 1'b1 : 1'b0;
RD_DATA: cnt_fsm_reset <= (cnt_rd_end == 1'b1) ? 1'b1 : 1'b0; //读突发长度结束
RD_TRP: cnt_fsm_reset <= (cnt_trp_end == 1'b1) ? 1'b1 : 1'b0;
RD_END: cnt_fsm_reset <= 1'b1;
default: cnt_fsm_reset <= 1'b0;
endcase
end
// 一次突发读结束
assign rd_end = (state == RD_END )? 1'b1 :0 ;
//rd_data_reg,因为从SDRAM读出的数据与本模块的时钟存在相位差异,故需同步化处理
always@(posedge rd_clk or negedge rd_rst_n)begin
if(!rd_rst_n)
rd_data_reg <= 16'd0;
else
rd_data_reg <= rd_data;
end
//rd_ack:读SDRAM响应信号
assign rd_ack = (state == RD_DATA)&& (cnt_fsm >= 10'd1)&& (cnt_fsm < (rd_burst_len + 2'd1));
//rd_sdram_data:SDRAM读出的数据
assign rd_sdram_data = (rd_ack == 1'b1) ? rd_data_reg : 16'b0;
//第一段,状态寄存器
always @(posedge rd_clk or negedge rd_rst_n )
if(!rd_rst_n)
state <= RD_IDLE;
else
state <= next_state;
//第二段,组合逻辑描述状态转移
always @ (*) begin
next_state = RD_IDLE;
case(state)
RD_IDLE: next_state = ((init_end)&&(rd_en)) ? RD_ACT : RD_IDLE;
RD_ACT : next_state = RD_TRCD ;
RD_TRCD: next_state = (cnt_trcd_end) ? RD_READ: RD_TRCD;
RD_READ: next_state = RD_CL;
RD_CL : next_state = (cnt_cl_end) ? RD_DATA: RD_CL;
RD_DATA: next_state = (cnt_rd_end) ? RD_PRE: RD_DATA;
RD_PRE : next_state = RD_TRP;
RD_TRP : next_state = (cnt_trp_end) ? RD_END: RD_TRP;
RD_END : next_state = RD_IDLE;
default: next_state = RD_IDLE;
endcase
end
//第三段,时序逻辑描述输出
always @(posedge rd_clk or negedge rd_rst_n )
if(!rd_rst_n)begin
rd_sdram_cmd <= NOP;
rd_sdram_bank <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
else
case(state)
RD_IDLE,RD_TRCD,RD_CL,RD_END,RD_TRP:begin
rd_sdram_cmd <= NOP;
rd_sdram_bank <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
RD_ACT :begin
rd_sdram_cmd <= ACTIVE;
rd_sdram_bank <= rd_addr[23:22]; //BANK地址是输入地址rd_addr的高两位
rd_sdram_addr <= rd_addr[21:9]; //行地址是21-9位(13位地址总线)
end
RD_READ:begin
rd_sdram_cmd <= READ;
rd_sdram_bank <= rd_addr[23:22]; //BANK地址是输入地址rd_addr的高两位
rd_sdram_addr <= {4'b0000,rd_addr[8:0]};//列地址只有9位,因共用13位地址总线,高4位补0
end
RD_DATA:begin
rd_sdram_bank <= 2'b11;
rd_sdram_addr <= 13'h1fff;
if(cnt_rdburst_end)
rd_sdram_cmd <= BURST_STOP; //突发传输终止指令
else
rd_sdram_cmd <= NOP; //突发读取未结束,发送空指令
end
RD_PRE :begin
rd_sdram_cmd <= PRECHARGE; //预充电命令
rd_sdram_bank <= rd_addr[23:22]; //BANK地址是输入地址rd_addr的高两位
rd_sdram_addr <= 13'h0400; //A10拉高选中所有BANK,对所有BANK进行预充电,其他位不关心
end
default:begin
rd_sdram_cmd <= NOP;
rd_sdram_bank <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
endcase
endmodule
十、突发写的verilog实现
module sdram_write(
input wr_clk ,
input wr_rst_n ,
input init_end ,
input wr_en ,
input [23:0] wr_addr , //写SDRAM地址
input [15:0] wr_data , //待写入SDRAM的数据
input [9:0] wr_burst_len, //SDRAM突发写长度
output wr_ack , //写响应信号
output wr_end , //写结束信号,写操作完成后拉高一个周期
output reg [12:0]wr_sdram_addr,
output reg [3:0] wr_sdram_cmd ,
output reg [1:0] wr_sdram_bank ,
output reg wr_sdram_en , //数据总线输出使能,用于后续仲裁模块输出
output [15:0]wr_sdram_data
);
//等待时间参数定义
localparam TRP = 3'd2 , //预充电等待周期
TRCD = 3'd2 ; //激活等待周期
//命令指令参数
localparam NOP = 4'b0111 ,
PRECHARGE = 4'b0010 ,
ACTIVE = 4'b0011 ,
WRITE = 4'b0100 ,
BURST_STOP = 4'b0110 ; //突发终止指令
//状态机状态格雷码编码
localparam WR_IDLE = 3'b000, //写操作初始状态
WR_ACT = 3'b001, //行激活状态
WR_TRCD = 3'b011, //行激活等待状态
WR_WR = 3'b010, //写操作状态
WR_DATA = 3'b100, //突发写操作状态,突发写入多个数据,直到突发终止
WR_PRE = 3'b101, //预充电状态
WR_TRP = 3'b111, //预充电状态等待状态
WR_END = 3'b110; //结束状态
reg [2:0] state,next_state ;
reg [9:0] cnt_fsm ; //状态机计数器,位宽应满足最大突发长度的要求
reg cnt_fsm_reset ; //状态机计数器复位信号,高电平有效
wire trp_end_flag ;
wire trcd_end_flag ; //行激活等待时间结束标志
wire wr_end_flag ; //突发写结束标志
//一次突发写结束
assign wr_end = (state == WR_END) ? 1'b1 : 1'b0;
//写 SDRAM 响应信号
assign wr_ack = (state == WR_WR) || ((state == WR_DATA) && (cnt_fsm <= wr_burst_len - 2'd2));
//wr_sdram_data:写入 SDRAM 的数据
assign wr_sdram_data = (wr_sdram_en == 1'b1) ? wr_data : 16'b0;
//wr_sdram_en:数据总线输出使能
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)
wr_sdram_en <= 1'b0;
else
wr_sdram_en <= wr_ack;
end
assign trp_end_flag = ((state == WR_TRP) && (cnt_fsm == TRP - 1'b1))? 1'b1 : 1'b0;
assign trcd_end_flag = ((state == WR_TRCD) && (cnt_fsm == TRCD - 1'b1))? 1'b1 : 1'b0;
assign wr_end_flag = ((state == WR_DATA) && (cnt_fsm == wr_burst_len - 1'b1))? 1'b1 : 1'b0;
//用于计数各个状态以实现状态跳转
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)
cnt_fsm <= 10'd0;
else if(cnt_fsm_reset)
cnt_fsm <= 10'd0;
else
cnt_fsm <= cnt_fsm + 1'd1;
end
//工作状态计数器的复位信号
always@(*)begin
case(state)
WR_IDLE: cnt_fsm_reset = 1'b1; //计数器清零
WR_TRCD: cnt_fsm_reset = (trcd_end_flag)? 1'b1 : 1'b0; //完成TRCD等待则计数器清零,否则计数
WR_WR: cnt_fsm_reset = 1'b1; //完计数器清零
WR_DATA: cnt_fsm_reset = (wr_end_flag)? 1'b1 : 1'b0; //完成突发数据写入则计数器清零,否则计数
WR_TRP: cnt_fsm_reset = (trp_end_flag)? 1'b1 : 1'b0; //完成TRP等待则计数器清零,否则计数
WR_END: cnt_fsm_reset = 1'b1; //计数器清零
default: cnt_fsm_reset = 1'b0; //计数器累加计数
endcase
end
//第一段状态机,状态寄存器
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)
state <= WR_IDLE;
else
state <= next_state;
end
//第二段状态机,组合逻辑描述状态转移
always@(*)begin
next_state = WR_IDLE;
case(state)
WR_IDLE: next_state = (wr_en && init_end) ? WR_ACT : WR_IDLE;
WR_ACT:
next_state = WR_TRCD;
WR_TRCD: next_state = (trcd_end_flag) ? WR_WR : WR_TRCD;
WR_WR:
next_state = WR_DATA;
WR_DATA: next_state = (wr_end_flag) ? WR_PRE : WR_DATA;
WR_PRE:
next_state = WR_TRP;
WR_TRP: next_state = (trp_end_flag) ? WR_END : WR_TRP;
WR_END:
next_state = WR_IDLE;
default:next_state = WR_IDLE;
endcase
end
//第三段状态机,描述输出
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)begin
wr_sdram_cmd <= NOP;
wr_sdram_bank <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
else
case(state)
WR_IDLE,WR_TRCD,WR_TRP,WR_END:begin //初始输出NOP指令,地址、BANK地址不关心,全拉高就行
wr_sdram_cmd <= NOP;
wr_sdram_bank <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
WR_ACT:begin
wr_sdram_cmd <= ACTIVE; //输出激活指令,BANK地址是输入地址wr_addr的高两位,行地址是21-9位(13位地址总线)
wr_sdram_bank <= wr_addr[23:22];
wr_sdram_addr <= wr_addr[21:9]; //13位地址线
end
WR_WR:begin
wr_sdram_cmd <= WRITE; //输出写指令,BANK地址是输入地址wr_addr的高两位,列地址是8-0位(13位地址总线)
wr_sdram_bank <= wr_addr[23:22];
wr_sdram_addr <= {4'b0000,wr_addr[8:0]}; //列地址只有9位,但共13位地址总线,所以高4位补0
end
WR_DATA:begin
wr_sdram_bank <= 2'b11;
wr_sdram_addr <= 13'h1fff;
if(wr_end_flag)
wr_sdram_cmd <= BURST_STOP; //输出突发终结指令
else
wr_sdram_cmd <= NOP;
end
WR_PRE:begin
wr_sdram_cmd <= PRECHARGE; //输出预充电指令,BANK地址是输入地址wr_addr的高两位,地址不关心,只需要拉高A10选中所有BANK
wr_sdram_bank <= wr_addr[23:22];
wr_sdram_addr <= 13'h0400; //A10拉高,关闭所有BANK
end
default:begin
wr_sdram_cmd <= NOP;
wr_sdram_bank <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
endcase
end
endmodule