赞
踩
SDRAM是“Synchronous Dynamic Random Access Memory”的缩写,也叫同步、动态、 随机、 存取器。因为其单位存储量大、高数据带宽、读写速度快、价格相对便宜等优点被广泛使用在各行各业。同时,其升级版的DDR作为电脑内存也被广泛使用。
SDRAM存取数据结构不同于FIFO和RAM,可以把SDRAM存取结构类比成表格结构,如下图所示。想要对一个存储单元进行读写操作,可以通过行地址和列地址来定位到所想要操作的存储单元。
把这种N行N列的“表格”称为一个逻辑BANK也叫 L-bank。通常SDRAM里面存在多个逻辑BANK,因此想要读写操作需要先指定一个逻辑BANK然后通过行地址和列地址定位到想要读写的存储单元。“表格”中的一个单元格是SDRAM的一个存储单元,一个存储单元里可以存放一个或者多个bit的数据,其中存放一个bit的存储单元结构入下图所示:
由上图可以看出,一个存储单元是由两个选通三极管以及电容组成。当电容的电荷高于设定阈值时,表示存储的为高电平,否者为低电平。由于电容不是理想的电容,因此电容的电荷量会随着时间的移动慢慢减少。所以需要定时不断地给电容充电来保持数据的稳定,这就体现了SDRAM的动态特性。
打开芯片数据手册,我们可以看到SDRAM功能框图如下:
由功能框图可以看出,SDRAM结构包含一个逻辑控制单元,其中包含了(命令解码单元和模式寄存器)、包含了一个地址寄存器(行列地址复用)、刷新计数器、BANK控制器、四个BANK、以及数据输出寄存器等等。外部通过 CS_N、RAC_N、CAS_N、WE_N 以及地址总线向逻辑控制单元输入命令,命令经过命令解码器进行译码后,将控制参数保存到模式寄存器中,逻辑控制单元进而控制逻辑运行。SDRAM器件引脚图如下所示:
# 符号表示该信号为低电平有效。破折号 (-) 表示 x8 和 x4 引脚功能与 x16 引脚功能相同。以镁光生产的4 Meg x 16 x 4 banks的SDRAM为例,引脚说明如下:
引脚名称 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
CLK | 1bit | input | SDRAM工作所需的系统时钟:所有信号都在CLK上升沿采样 |
CKE | 1bit | input | 时钟使能信号:高电平时才能操作SDRAM |
CS# | 1bit | input | 片选信号:低电平时有效,高电平时屏蔽所有输入输出端口,CLK,CKE,DQM除外 |
CAS# | 1bit | input | 列选通信号:低电平时A[8:0]为输入的列地址 |
RAS# | 1bit | input | 行选通信号:低电平时A[12:0]为输入的行地址 |
WE# | 1bit | input | 写使能信号:低电平时有效,{CS#,CAS#,RAS#,WE#}构成SDRAM操作命令 |
DQM[1:0] | 2bit | input | 数据掩码信号:高位掩码高字节数据,低位掩码低字节数据 |
BA[1:0] | 2bit | input | BANK地址信号 |
A[12:0] | 13bit | input | 地址总线,不同命令下有不同含义 |
DQ[15:0] | 16bit | inout | 输入和输入数据复用的数据总线 |
打开芯片数据手册,最开始位置我们可以看到本数据手册支持这三种SDRAM。
SDRAM容量数=一个bank存储单元数量✖一个存储单元的位宽✖bank数量。例如:4 Meg x 16 x 4 banks的SDRAM容量= 8192✖4096✖16✖4 = 2147483648 bit = 268435456 Byte=256MB
由上图可以看出,不同速度等级对应的最大系统时钟频率不同。CL为列选通潜伏期,
t
R
C
D
^tRCD
tRCD,
t
R
P
^tRP
tRP分别为自激活等待时间和预充电等待时间。
对SDRAM的操作都是通过由{CS_N、RAS_N、CAS_N、WE_N} 四路控制信号构成指令来的, 数据手册也提供了SDRAM的指令集,如图所示:
无论CKE是否有效,COMMAND INHIBIT (禁止命令)都会阻止器件执行新命令。该设备被取消选择,已经进行的操作命令不受影响。
该命令给被选中的 SDRAM 芯片传递一个空操作信息,目的是为了防止 SDRAM 处于空闲或等待状态时,SDRAM被其他命令写入,已经进行的操作命令不受影响。
该命令用于激活指定bank中的某一行。{BA0,BA1}的值用于选择哪个bank,地址总线A[12:0]用于选择哪一行。激活该行后,该行一直保持激活状态,并可以进行后续读/写操作,操作完成后,只有执行一次预充电命令(Precharge)后,被激活的特定行被关闭。每次激活只能激活一个Bank,同一个Bank 中每次只能激活一行,当需要对同一 L-Bank 中其他行进行操作时, 必须先执行一个预充电命令关闭当前行,再激活另一行进行操作。激活命令示意图如下:
读命令用于对已经激活的bank和行进行读操作。A10的值决定是否在读操作完成后对该行进行预充电来关闭该行。如果为低电平,则继续保持该行的激活状态,后续也能继续进行读写操作。{BA1,BA0}选择想要读取的bank,A0-A9选择哪一行,读命令操作示意图如下:
写命令用于对已经激活的bank和行进行写操作。A10的值决定是否在读操作完成后对该行进行预充电来关闭该行。如果为低电平,则继续保持该行的激活状态,后续也能继续进行读写操作。{BA1,BA0}选择想要写入的bank,A0-A9选择哪一行,写命令操作示意图如下:
SDRAM 处于读/写操作过程中可被写入,突发停止操作被用来截断固定长度或者整页长度的突发,执行突发停止命令后,最近执行的数据读写操作被终止。
该命令用于关闭指定bank中打开行或所有bank中打开的行。在发出预充电命令后的指定时间 ( t R P ^tRP tRP)后,相对应的bank才能用于后续的行访问。输入 A10 确定是对指定bank还是所有bank进行预充电,如果仅对指定bank进行预充电,则输入 BA0 和 BA1 选择该bank。Bank 预充电后,它处于空闲状态,必须在向该 Bank 发出任何 READ 或 WRITE 命令之前重新激活它。
前面提到,由于电容会产生漏电流,因此必须在电容电荷量泄露完成之前对电容进行充电,这就叫刷新。目前国际公认的标准是,存储体中电容的数据有效保存期上限是 64ms,也就是说每一行刷新的循环周期最大为 64ms,那么刷新速度就是:行数/64ms。例如8192行的SDRAM刷新周期就为7.8125us。
刷新命令分为自动刷新和自刷新。在CKE为高电平时,执行自动刷新前必须执行预充电命令,来关闭所有bank。每次刷新后,需要等待相应周期后才能进行读写操作。在CKE为低电平时,执行自刷新,主要用于休眠状态下,对数据的保存。
该命令只有所有bank 均处于空闲状态时才可被写入,否则配置出错,而且在执行此命令后,SDRAM 必须等待相应的响应时间 t R S C ^tRSC tRSC(Register Set Cycle)后,才可写入新的命令。在配置模式寄存器时,需要使用地址总线来辅助配置,如下图所示:
由图可以看出,若使用突发传输,只要指定起始列地址和突发长度,内存就会依次地自动对后面相应数量的存储单元进行读写操作,这样,除了第一笔数据传输需要若干个(CL)周期外,其后的每个数据只要一个周期即可获得。下图是突发长度不设置突发长度的读操作时序图:
一般情况下都设置为顺序突发。
A4,A5,A6设置列选通潜伏期,是指从读命令被寄存到数据总线上到出现第一个有效数据之间的时钟周期间隔,列选通潜伏期可被设置为 2 个或 3 个时钟周期,如下图所示。
A7,A8设置操作模式,SDRAM 存在标准模式、测试模式等多种模式,但对于普通用户,只开放了标准模式,在使用 SDRAM 时只需将 A7,A8 设置为低电平进入标准模式即可。
A9设置写突发模式:当 A9 = 0 时,通过 A[2:0] 编程的突发长度适用于读和写突发;当M9 = 1时,编程的突发长度适用于读突发,但是写操作不是突发,一次只能写一个数据
A12,A11,A10 保留。
本文的实验目:从uart接收数据写入到SDRAM中,然后从SDRAM读出来再通过uart发送出去。
整个系统框图如上所示,由两个子功能模块组成:fifo_ctrl负责对数据进行跨时钟处理,sdram_ctrl模块负责实现sdram操作命令以及时序。外部模块把需要写入的数据以及写地址,写突发长度等信息写入到FIFO里,fifo_ctrl模块读出信息,然后向sdram_ctrl模块发出写操作请求,sdram_ctrl就会根据信息去读出fifo里要写入sdram的数据然后进行写操作,读操作亦是如此。
整个SDRAM控制模块,由五个子模块组成:SDRAM初始化模块、SDRAM自动刷新模块、SDRAM写模块、SDRAM读模块以及SDRAM仲裁模块。整个SDRAM的读写数据请求由fifo_ctrl模块给出,由于读写请求和自动刷新请求的时间不确定,因此仲裁模块的作用就是协调其他模块的操作不会产生冲突。
在数据手册里找到初始化时序,如下图所示:
由上图可知,在SDRAM上电后需要等待最少100us的时间才能开始初始化。第一步是对所有bank进行预充电;第二步是写入几次自动刷新命令;第三步是写入配置模式寄存器命令,然后初始化就完成了。
整个初始化模块的波形图如下所示,不同芯片的手册上电等待时间不同,这里设置等待200us可以兼容更多的器件,手册建议自动刷新次数大于2次,代码里设置的8次,也是兼容更多的器件。
`timescale 1ns / 1ps
module sdram_init(
input clk ,
input rst_n ,
output reg [3:0] init_cmd ,
output reg [1:0] init_ba ,
output reg [12:0] init_addr ,
output wire init_done
);
parameter INIT_IDLE = 8'b0000_0001, //初始状态
INIT_PRE = 8'b0000_0010, //预充电指令状态
INIT_TRP = 8'b0000_0100, //预充电等待时间(tRP)状态
INIT_AR = 8'b0000_1000, //自动刷新指令状态
INIT_TRF = 8'b0001_0000, //自动刷新等待时间(tRFC)状态
INIT_MRS = 8'b0010_0000, //配置模式寄存器指令状态
INIT_TMRD = 8'b0100_0000, //配置模式寄存器等待时间(tMRD)状态
INIT_END = 8'b1000_0000; //初始化完成状态
parameter CNT_200US_MAX = 15'd20_000 ; //100M系统时钟计数200us所需的计数值
parameter TRP_CLK = 2'd2, //预充电等待周期
TPFC_CLK = 3'd7, //自动刷新等待周期
TMRD_CLK = 2'd2; //配置模式寄存器等待周期
parameter P_CHARGE = 4'b0010, //预充电命令
NOP = 4'b0111, //空操作命令
AUTO_REF = 4'b0001, //自动刷新命令
M_REG_SET = 4'b0000; //配置模式寄存器命令
reg [14:0] cnt_200us ; //200us计数器
reg [7:0] init_state ; //状态机
reg [3:0] cnt_clk ; //时钟周期计数器
reg [3:0] ar_cnt ; //自动刷新计数器
reg cnt_rst ; //时钟周期计数器复位信号
wire wait_end ; //等待结束信号
//上电后等待200us完成信号
assign wait_end =(cnt_200us == CNT_200US_MAX - 1)? 1'b1 : 1'b0;
//整个初始化完成信号
assign init_done =(init_state == INIT_END)? 1'b1 : 1'b0;
//200us计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_200us <= 'd0;
else if(cnt_200us == CNT_200US_MAX)
cnt_200us <= CNT_200US_MAX;
else
cnt_200us <= cnt_200us + 1'b1;
end
//时钟周期计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_clk <= 'd0;
else if(cnt_rst == 1'b1)
cnt_clk <= 'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
init_state <= INIT_IDLE;
else
case (init_state)
INIT_IDLE:
if(wait_end == 1'b1) //上电等待200us完成后进入预充电状态
init_state <= INIT_PRE;
else
init_state <= INIT_IDLE;
INIT_PRE: //预充电状态给出预充电命令,然后跳转预充电等待状态
init_state <= INIT_TRP;
INIT_TRP:
if(cnt_clk == TRP_CLK) //等待预充电周期后跳转自动刷新状态
init_state <= INIT_AR;
else
init_state <= INIT_TRP;
INIT_AR:
init_state <= INIT_TRF; //给出自动刷新命令后,跳转到自动刷新等待状态
INIT_TRF:
if((cnt_clk == TPFC_CLK)&&(ar_cnt == 'd8)) //当自动刷新完成8次后跳转配置模式寄存器状态
init_state <= INIT_MRS;
else if(cnt_clk == TPFC_CLK)
init_state <= INIT_AR;
else
init_state <= INIT_TRF;
INIT_MRS:
init_state <= INIT_TMRD; //给出配置模式寄存器命令后。跳转到等待状态
INIT_TMRD: //等待完成后,跳转到结束状态
if(cnt_clk ==TMRD_CLK)
init_state <= INIT_END;
else
init_state <= INIT_TMRD;
INIT_END:
init_state <= INIT_END;
default: init_state <= INIT_IDLE;
endcase
end
//在每个状态等待的周期完成后清空时钟周期计数器
always @(*) begin
case (init_state)
INIT_IDLE:
cnt_rst <= 1'b1;
INIT_TRP:
cnt_rst <= (cnt_clk == TRP_CLK)? 1'b1 : 1'b0;
INIT_TRF:
cnt_rst <= (cnt_clk == TPFC_CLK)? 1'b1 : 1'b0;
INIT_TMRD:
cnt_rst <= (cnt_clk == TMRD_CLK)? 1'b1 : 1'b0;
INIT_END:
cnt_rst <= 1'b1;
default: cnt_rst <= 1'b0;
endcase
end
//每次进入自动刷新状态,自动刷新计数器+1
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
ar_cnt <= 'd0;
else if(init_state == INIT_IDLE)
ar_cnt <= 'd0;
else if(init_state == INIT_AR)
ar_cnt <= ar_cnt + 1'b1;
else
ar_cnt <= ar_cnt;
end
//每个状态给出的不同命令
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
else
case (init_state)
INIT_IDLE:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_PRE:
begin
init_cmd <= P_CHARGE;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_TRP:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_AR:
begin
init_cmd <= AUTO_REF;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_TRF:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_MRS:
begin
init_cmd <= M_REG_SET;
init_ba <= 2'b00;
init_addr <=
{ //地址辅助配置模式寄存器,参数不同,配置的模式不同
3'b000, //A12-A10:预留
1'b0, //A9=0:读写方式,0:突发读&突发写,1:突发读&单写
2'b00, //{A8,A7}=00:标准模式,默认
3'b011, //CAS潜伏期,{A6,A5,A4}=011:3,010:2,001:1,其他:保留
1'b0, //A3=0:突发传输方式,0:顺序,1:隔行
3'b111 //{A2,A1,A0}=111:突发长度,000:单字节,001:2字节
//010:4字节,011:8字节,111:整页,其他:保留
};
end
INIT_TMRD:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
INIT_END:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
default:
begin
init_cmd <= NOP;
init_ba <= 2'b11;
init_addr <= 13'h1FFF;
end
endcase
end
endmodule
从网上下载一个SDRAM仿真模型,然后例化到仿真文件里面,因为SDRAM内部是上升沿采样,因此通过PLL设置一个用户工作时钟和SDRAM工作时钟,时钟频率一致,相位偏移 -30°。这样可以确保SDRAM内部正确采样,仿真代码如下:
`timescale 1ns/1ns
module tb_sdram_init();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_done ;
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
always #10 clk = ~clk;
assign sys_rst_n = rst_n & locked;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
sdram_clk u_sdram_clk
(
.clk_out1(sdram_clk),
.clk_out2(sdram_clk_shift),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk));
sdram_init u_sdram_init(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_cmd ( init_cmd ),
.init_ba ( init_ba ),
.init_addr ( init_addr ),
.init_done ( init_done )
);
sdram_model_plus u_sdram_model_plus(
.Dq (),
.Addr (init_addr),
.Ba (init_ba),
.Clk (sdram_clk_shift),
.Cke (1'b1),
.Cs_n (init_cmd[3]),
.Ras_n (init_cmd[2]),
.Cas_n (init_cmd[1]),
.We_n (init_cmd[0]),
.Dqm (2'b00),
.Debug (1'b1)
);
endmodule
执行仿真后查看打印信息:先是对所有bank进行预充电,然后自动刷新8次,最后配置模式寄存器为,cas=3,整夜突发,顺序突发,和程序一致,接下来看仿真波形:
和设置的波形图一致,仿真完成。
由上面SDRAM原理可知,SDRAM依靠电容充放电来实现数据的保存,因此需要不断地对电容进行充电操作以确保数据的可靠性,这就是刷新操作。国际上公认最迟64ms就需要对每一行进行充电,因此例如一个sdram的地址位宽位13,则由2^13=8192行。所以每次间隔64ms/8192 =7810ns就要对一行进行刷新操作。刷新又分为自动刷新和自刷新。自动刷新是指CKE有效时对SDRAM进行刷新操作。自刷新是指CKE无效时,SDRAM待机状态时候内部进行自动刷新的操作。
打开数据手册,找到自动刷新操作时序图如下:
由操作时序图可以看出,自动刷新步骤为:
`timescale 1ns / 1ps
module sdram_auto_ref(
input clk ,
input rst_n ,
input init_done ,
input aref_en ,
output reg aref_req ,
output aref_done ,
output reg [1:0] aref_ba ,
output reg [3:0] aref_cmd ,
output reg [12:0] aref_addr
);
parameter CNT_REF_MAX = 10'd750; //SDRAM地址位宽13位,一共2^13=8192行。64ms/8192=7812ns 提前一点做预留时间就是7500ns,时钟频率100m
parameter P_CHARGE = 4'b0010, //预充电命令
NOP = 4'b0111, //空操作命令
AUTO_REF = 4'b0001; //自动刷新命令
parameter AREF_IDLE = 6'b00_0001, //空闲状态
AREF_PRE = 6'b00_0010, //预充电指令状态
AREF_TRP = 6'b00_0100, //预充电等待时间(tRP)状态
AREF_AR = 6'b00_1000, //自动刷新指令状态
AREF_TRF = 6'b01_0000, //自动刷新等待时间(tRFC)状态
AREF_END = 6'b10_0000; //自动刷新完成状态
parameter TRP_CLK = 2'd2, //预充电等待周期
TPFC_CLK = 3'd7; //自动刷新等待周期
reg [9:0] cnt_ref ;
reg [5:0] aref_state ;
reg [2:0] cnt_clk ;
reg cnt_rst ;
reg [2:0] ar_cnt ;
assign aref_done = (aref_state == AREF_END)? 1'b1 : 1'b0;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_ref <= 'd0;
else if(cnt_ref == CNT_REF_MAX - 1)
cnt_ref <= 'd0;
else if(init_done == 1'b1)
cnt_ref <= cnt_ref + 1'b1;
else
cnt_ref <= cnt_ref;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
aref_req <= 1'b0;
else if(cnt_ref == CNT_REF_MAX - 1)
aref_req <= 1'b1;
else if(aref_en == 1'b1)
aref_req <= 1'b0;
else
aref_req <= aref_req;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
aref_state <= AREF_IDLE;
else
case (aref_state)
AREF_IDLE:
if((init_done == 1'b1)&&(aref_en == 1'b1)) //SDRAM初始化完成并且仲裁模块允许自动刷新信号来临时跳转
aref_state <= AREF_PRE;
else
aref_state <= AREF_IDLE;
AREF_PRE:
aref_state <= AREF_TRP;
AREF_TRP:
if(cnt_clk == TRP_CLK)
aref_state <= AREF_AR;
else
aref_state <= AREF_TRP;
AREF_AR:
aref_state <= AREF_TRF;
AREF_TRF:
if((cnt_clk == TPFC_CLK)&&(ar_cnt == 'd2))
aref_state <= AREF_END;
else if(cnt_clk == TPFC_CLK)
aref_state <= AREF_AR;
else
aref_state <= AREF_TRF;
AREF_END:
aref_state <= AREF_IDLE;
default: aref_state <= AREF_IDLE;
endcase
end
//每次进入自动刷新状态,自动刷新计数器+1
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
ar_cnt <= 'd0;
else if(aref_state == AREF_IDLE)
ar_cnt <= 'd0;
else if(aref_state == AREF_AR)
ar_cnt <= ar_cnt + 1'b1;
else
ar_cnt <= ar_cnt;
end
//时钟周期计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_clk <= 'd0;
else if(cnt_rst == 1'b1)
cnt_clk <= 'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
always @(*) begin
case (aref_state)
AREF_IDLE:
cnt_rst <= 1'b1;
AREF_TRP:
cnt_rst <= (cnt_clk == TRP_CLK)? 1'b1 : 1'b0;
AREF_TRF:
cnt_rst <= (cnt_clk == TPFC_CLK)? 1'b1 : 1'b0;
AREF_END:
cnt_rst <= 1'b1;
default: cnt_rst <= 1'b0;
endcase
end
//SDRAM操作指令控制
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
else
case(aref_state)
AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_PRE: //预充电指令
begin
aref_cmd <= P_CHARGE;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_AR: //自动刷新指令
begin
aref_cmd <= AUTO_REF;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
AREF_END: //一次自动刷新完成
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
default:
begin
aref_cmd <= NOP;
aref_ba <= 2'b11;
aref_addr <= 13'h1fff;
end
endcase
endmodule
仿真代码和初始化模块几乎一致
`timescale 1ns/1ns
module tb_sdram_auto_ref();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_done ;
wire aref_req ;
wire aref_done ;
wire [1:0] aref_ba ;
wire [3:0] aref_cmd ;
wire [12:0] aref_addr ;
reg aref_en ;
wire [1:0] sdram_ba ;
wire [3:0] sdram_cmd ;
wire [12:0] sdram_addr ;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
assign sys_rst_n = rst_n & locked;
//根据是否初始化完成来判断当前给初始化命令还是自动刷新命令
assign sdram_ba =(init_done == 1'b1)? aref_ba : init_ba ;
assign sdram_cmd =(init_done == 1'b1)? aref_cmd : init_cmd ;
assign sdram_addr =(init_done == 1'b1)? aref_addr : init_addr;
//模拟仲裁模块给出aref_en信号
always @(posedge sdram_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
aref_en <= 1'b0;
else if((init_done == 1'b1)&&(aref_req == 1'b1))
aref_en <= 1'b1;
else if(aref_done == 1'b1)
aref_en <= 1'b0;
else
aref_en <= aref_en;
end
always #10 clk = ~clk;
sdram_clk u_sdram_clk
(
.clk_out1(sdram_clk),
.clk_out2(sdram_clk_shift),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk));
sdram_init u_sdram_init(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_cmd ( init_cmd ),
.init_ba ( init_ba ),
.init_addr ( init_addr ),
.init_done ( init_done )
);
sdram_auto_ref u_sdram_auto_ref(
.clk ( sdram_clk ),
.rst_n ( rst_n ),
.init_done ( init_done ),
.aref_en ( aref_en ),
.aref_req ( aref_req ),
.aref_done ( aref_done ),
.aref_ba ( aref_ba ),
.aref_cmd ( aref_cmd ),
.aref_addr ( aref_addr )
);
sdram_model_plus u_sdram_model_plus(
.Dq (),
.Addr (sdram_addr),
.Ba (sdram_ba),
.Clk (sdram_clk_shift),
.Cke (1'b1),
.Cs_n (sdram_cmd[3]),
.Ras_n (sdram_cmd[2]),
.Cas_n (sdram_cmd[1]),
.We_n (sdram_cmd[0]),
.Dqm (2'b00),
.Debug (1'b1)
);
endmodule
查看打印信息,SDRAM先是经过了初始化,然后再执行了自动刷新,接下来观看仿真波形:
和设置的波形图一致,仿真完成。
打开数据手册,我们可以看到很多写操作模式,这里我们选择使用整页突发写操作,不自动预充电模式;时序图如下:
整体写模块也由状态机完成,当收到仲裁模块给的wr_en时,状态机按照数据手册的时序图跳转。在写满突发长度的数据后,给出突发中止指令,然后对所有bank进行预充电。
`timescale 1ns / 1ps
module sdram_write(
input clk ,
input rst_n ,
input init_done ,
input wr_en , //写使能
input [23:0] wr_addr , //高两位为bank地址,其次13位为行地址,最后9位位列地址
input [15:0] wr_data , //写数据
input [9:0] wr_burst_len , //一次写突发长度
output reg [3:0] wr_cmd ,
output reg [1:0] wr_ba ,
output reg [12:0] wr_sdram_addr ,
output reg wr_sdram_en ,
output [15:0] wr_sdram_data ,
output wr_done ,
output reg wr_fifo_req
);
parameter WR_IDLE = 8'b0000_0001, //写空闲状态
WR_ACTIVE = 8'b0000_0010, //激活指令状态
WR_TRCD = 8'b0000_0100, //激活指令等待时间(tRCD)状态
WR_WRITE = 8'b0000_1000, //写指令状态
WR_DATA = 8'b0001_0000, //写数据状态
WR_PRE = 8'b0010_0000, //预充电状态
WR_TRP = 8'b0100_0000, //预充电等待状态
WR_END = 8'b1000_0000; //写完成
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
WRITE = 4'b0100 , //数据写指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令
parameter TRCD_CLK = 10'd2 , //激活周期
TRP_CLK = 10'd2 ; //预充电周期
reg [7:0] wr_state ;
reg [9:0] cnt_clk ;
reg cnt_rst ;
assign wr_done =(wr_state == WR_END)? 1'b1:1'b0;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_state <= WR_IDLE;
else
case (wr_state)
WR_IDLE:
if((init_done==1'b1)&&(wr_en == 1'b1))
wr_state <= WR_ACTIVE;
else
wr_state <= WR_IDLE;
WR_ACTIVE:
wr_state <= WR_TRCD;
WR_TRCD:
if(cnt_clk == TRCD_CLK)
wr_state <= WR_WRITE;
else
wr_state <= WR_TRCD;
WR_WRITE:
wr_state <= WR_DATA;
WR_DATA:
if(cnt_clk == wr_burst_len)
wr_state <= WR_PRE;
else
wr_state <= WR_DATA;
WR_PRE:
wr_state <= WR_TRP;
WR_TRP:
if(cnt_clk == TRP_CLK)
wr_state <= WR_END;
else
wr_state <= WR_TRP;
WR_END:
wr_state <= WR_IDLE;
default: wr_state <= WR_IDLE;
endcase
end
//时钟周期计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_clk <= 'd0;
else if(cnt_rst == 1'b1)
cnt_clk <= 'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
always @(*) begin
case (wr_state)
WR_IDLE:
cnt_rst <= 1'b1;
WR_TRCD:
cnt_rst <= (cnt_clk == TRCD_CLK)? 1'b1 : 1'b0;
WR_DATA:
cnt_rst <= (cnt_clk == wr_burst_len)? 1'b1 : 1'b0;
WR_TRP:
cnt_rst <= (cnt_clk == TRP_CLK)? 1'b1 : 1'b0;
WR_END:
cnt_rst <= 1'b1;
default: cnt_rst <= 1'b0;
endcase
end
always @(*) begin
if(rst_n == 1'b0)
wr_fifo_req <= 1'b0;
else if(wr_state == WR_WRITE)
wr_fifo_req <= 1'b1;
else if((wr_state == WR_DATA)&&(cnt_clk == wr_burst_len))
wr_fifo_req <= 1'b0;
else
wr_fifo_req <= wr_fifo_req;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_sdram_en <= 1'b0;
else
wr_sdram_en <= wr_fifo_req;
end
assign wr_sdram_data = (wr_sdram_en == 1'b1)? wr_data : 16'd0;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
else
case (wr_state)
WR_IDLE: begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
WR_ACTIVE: begin
wr_cmd <= ACTIVE; //给出激活指令和bank地址以及行地址
wr_ba <= wr_addr[23:22];
wr_sdram_addr <= wr_addr[21:9];
end
WR_TRCD: begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
WR_WRITE: begin
wr_cmd <= WRITE; //给出写指令和bank地址以及列地址
wr_ba <= wr_addr[23:22];
wr_sdram_addr <= {4'b0000,wr_addr[8:0]};
end
WR_DATA: begin
if(cnt_clk == wr_burst_len)
wr_cmd <= B_STOP;
else begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
end
WR_PRE: begin
wr_cmd <= P_CHARGE; //给出预充电指令,A10位1时选择所有bank
wr_ba <= wr_addr[23:22];
wr_sdram_addr <= 13'h0400;
end
WR_TRP: begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
WR_END: begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
default: begin
wr_cmd <= NOP;
wr_ba <= 2'b11;
wr_sdram_addr <= 13'h1fff;
end
endcase
end
endmodule
在初始化模块仿真代码中添加写模块:
`timescale 1ns/1ns
module tb_sdram_write();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_done ;
reg wr_en ;
wire wr_done ;
reg [15:0] wr_data ;
wire wr_fifo_req ;
wire [15:0] sdram_dq;
wire wr_sdram_en ;
wire [15:0] wr_sdram_data;
wire [1:0] sdram_ba ;
wire [3:0] sdram_cmd ;
wire [12:0] sdram_addr ;
wire [1:0] wr_ba ;
wire [3:0] wr_cmd ;
wire [12:0] wr_sdram_addr ;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
always #10 clk = ~clk;
assign sys_rst_n = rst_n & locked;
always @(posedge sdram_clk or negedge sys_rst_n) begin //当初始化完成后打开写使能,当写操作完成后拉低写使能
if(sys_rst_n == 1'b0)
wr_en <= 1'b0;
else if(wr_done == 1'b1)
wr_en <= 1'b0;
else if(init_done == 1'b1)
wr_en <= 1'b1;
else
wr_en <= wr_en;
end
always @(posedge sdram_clk or negedge sys_rst_n) begin //当收到写模块的读数据请求后,数据累加
if(sys_rst_n == 1'b0)
wr_data <= 'd0;
else if(wr_data == 'd10)
wr_data <= 'd0;
else if(wr_fifo_req == 1'b1)
wr_data <= wr_data + 1'b1;
else
wr_data <= wr_data;
end
assign sdram_ba =(init_done == 1'b1)? wr_ba : init_ba ;
assign sdram_cmd =(init_done == 1'b1)? wr_cmd : init_cmd ;
assign sdram_addr =(init_done == 1'b1)? wr_sdram_addr : init_addr;
assign sdram_dq = (wr_sdram_en == 1'b1)?wr_sdram_data : 16'hz; //在写操作时控制dq总线,写完成后释放
sdram_clk u_sdram_clk
(
.clk_out1(sdram_clk),
.clk_out2(sdram_clk_shift),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk));
sdram_init u_sdram_init(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_cmd ( init_cmd ),
.init_ba ( init_ba ),
.init_addr ( init_addr ),
.init_done ( init_done )
);
sdram_write u_sdram_write(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_done ( init_done ),
.wr_en ( wr_en ),
.wr_addr ( 24'd0 ),
.wr_data ( wr_data ),
.wr_burst_len ( 10'd10 ),
.wr_cmd ( wr_cmd ),
.wr_ba ( wr_ba ),
.wr_sdram_addr ( wr_sdram_addr ),
.wr_sdram_en ( wr_sdram_en ),
.wr_sdram_data ( wr_sdram_data ),
.wr_done ( wr_done ),
.wr_fifo_req ( wr_fifo_req )
);
sdram_model_plus u_sdram_model_plus(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk_shift ),
.Cke (1'b1 ),
.Cs_n (sdram_cmd[3] ),
.Ras_n (sdram_cmd[2] ),
.Cas_n (sdram_cmd[1] ),
.We_n (sdram_cmd[0] ),
.Dqm (2'b00 ),
.Debug (1'b1 )
);
endmodule
查看打印信息发现,首先对sdram进行初始化操作;初始化完成后先是对所选择的bank和行地址进行激活,然后执行写命令,写突发完成后,对所有bank进行预充电,观看仿真波形如下:
观测波形发现和所画的波形图一致。
打开数据手册,我们可以看到很多读操作模式,这里我们选择使用整页突发读操作,不自动预充电模式;时序图如下:
`timescale 1ns / 1ns
module sdram_read(
input clk ,
input rst_n ,
input init_done ,
input rd_en , //读使能信号
input [23:0] rd_addr , //本次读开始地址
input [9:0] rd_burst_len , //读突发长度
input [15:0] rd_data , //从SDRAM读出的数据
output rd_done , //本次读操作完成信号
output reg rd_data_valid , //读数据有效信号
output [15:0] rd_sdram_data , //读出的数据
output reg [3:0] rd_cmd ,
output reg [1:0] rd_ba ,
output reg [12:0] rd_sdram_addr
);
parameter RD_IDLE = 9'b0_0000_0001, //读空闲状态
RD_ACTIVE = 9'b0_0000_0010, //激活指令状态
RD_TRCD = 9'b0_0000_0100, //激活指令等待时间(tRCD)状态
RD_READ = 9'b0_0000_1000, //读指令状态
RD_CL = 9'b0_0001_0000, //等CL周期状态
RD_DATA = 9'b0_0010_0000, //读数据状态
RD_PRE = 9'b0_0100_0000, //预充电指令状态
RD_TRP = 9'b0_1000_0000, //预充电等待周期状态
RD_END = 9'b1_0000_0000; //读完成
parameter TRCD_CLK = 10'd2 , //激活周期
TCL_CLK = 10'd3 , //CAL周期
TRP_CLK = 10'd2 ; //预充电周期
parameter NOP = 4'b0111 , //空操作指令
ACTIVE = 4'b0011 , //激活指令
READ = 4'b0101 , //数据读指令
B_STOP = 4'b0110 , //突发停止指令
P_CHARGE = 4'b0010 ; //预充电指令
reg [8:0] rd_state ;
reg [9:0] cnt_clk ;
reg cnt_rst ;
reg [15:0] rd_data_reg ;
assign rd_done = (rd_state == RD_END)? 1'b1:1'b0;
//因为SDRAM工作时钟和用户时钟存在180°相位差,因此需要同步到用户时钟域
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_data_reg <= 16'd0;
else
rd_data_reg <= rd_data;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_state <= RD_IDLE;
else
case (rd_state)
RD_IDLE:
if((init_done == 1'b1)&&(rd_en == 1'b1)) //当初始化完成并且收到读使能信号后挑战
rd_state <= RD_ACTIVE;
else
rd_state <= RD_IDLE;
RD_ACTIVE: //选择bank和行地址激活
rd_state <= RD_TRCD;
RD_TRCD:
if(cnt_clk == TRCD_CLK)
rd_state <= RD_READ;
else
rd_state <= RD_TRCD;
RD_READ: //选择bank和列地址写入读指令
rd_state <= RD_CL;
RD_CL: //等待CAS潜伏期
if(cnt_clk == TCL_CLK)
rd_state <= RD_DATA;
else
rd_state <= RD_CL;
RD_DATA:
if(cnt_clk == TCL_CLK + rd_burst_len -1'b1)
rd_state <= RD_PRE;
else
rd_state <= RD_DATA;
RD_PRE: //选择所有bank和行地址进行预充电
rd_state <= RD_TRP;
RD_TRP:
if(cnt_clk == TRP_CLK)
rd_state <= RD_END;
else
rd_state <= RD_TRP;
RD_END:
rd_state <= RD_IDLE;
default: rd_state <= RD_IDLE;
endcase
end
//时钟周期计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt_clk <= 'd0;
else if(cnt_rst == 1'b1)
cnt_clk <= 'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
always @(*) begin
case (rd_state)
RD_IDLE:
cnt_rst <= 1'b1;
RD_TRCD:
cnt_rst <= (cnt_clk == TRCD_CLK)? 1'b1 : 1'b0;
RD_CL:
cnt_rst <= (cnt_clk == TCL_CLK)? 1'b1 : 1'b0;
RD_DATA:
cnt_rst <= (cnt_clk == TCL_CLK + rd_burst_len -1'b1)? 1'b1 : 1'b0;
RD_TRP:
cnt_rst <= (cnt_clk == TRP_CLK)? 1'b1 : 1'b0;
RD_END:
cnt_rst <= 1'b1;
default: cnt_rst <= 1'b0;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_data_valid <= 1'b0;
else if((rd_state == RD_DATA)&&(cnt_clk < rd_burst_len))
rd_data_valid <= 1'b1;
else
rd_data_valid <= 1'b0;
end
assign rd_sdram_data = (rd_data_valid == 1'b1) ? rd_data_reg : 16'd0;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
else
case (rd_state)
RD_IDLE: begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
RD_ACTIVE: begin
rd_cmd <= ACTIVE; //给出激活指令和bank地址以及行地址
rd_ba <= rd_addr[23:22];
rd_sdram_addr <= rd_addr[21:9];
end
RD_TRCD: begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
RD_READ: begin
rd_cmd <= READ; //给出读指令和bank地址以及列地址
rd_ba <= rd_addr[23:22];
rd_sdram_addr <= {4'b0000,rd_addr[8:0]};
end
RD_CL:begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
RD_DATA: begin
if(cnt_clk == rd_burst_len - TCL_CLK -1'b1)
rd_cmd <= B_STOP;
else begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
end
RD_PRE: begin
rd_cmd <= P_CHARGE; //给出预充电指令,A10位1时选择所有bank
rd_ba <= rd_addr[23:22];
rd_sdram_addr <= 13'h0400;
end
RD_TRP: begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
RD_END: begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
default: begin
rd_cmd <= NOP;
rd_ba <= 2'b11;
rd_sdram_addr <= 13'h1fff;
end
endcase
end
endmodule
在写模块的仿真代码中添加读模块:
`timescale 1ns/1ns
module tb_sdram_read();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
//初始化
wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr ;
wire init_done ;
//写模块
reg wr_en ;
wire wr_done ;
reg [15:0] wr_data ;
wire wr_fifo_req ;
wire wr_sdram_en ;
wire [15:0] wr_sdram_data;
wire [1:0] wr_ba ;
wire [3:0] wr_cmd ;
wire [12:0] wr_sdram_addr ;
//读模块
reg rd_en ;
wire [15:0] rd_sdram_data ;
wire rd_done ;
wire rd_data_valid ;
wire [1:0] rd_ba ;
wire [3:0] rd_cmd ;
wire [12:0] rd_sdram_addr ;
//SDRAM模块
wire [1:0] sdram_ba ;
wire [3:0] sdram_cmd ;
wire [12:0] sdram_addr ;
wire [15:0] sdram_dq;
//中间变量
wire [1:0] wr_rd_ba ;
wire [3:0] wr_rd_cmd ;
wire [12:0] wr_rd_sdram_addr ;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
always #10 clk = ~clk;
assign sys_rst_n = rst_n & locked;
//写操作
always @(posedge sdram_clk or negedge sys_rst_n) begin //一开始打开写使能,当写操作完成后拉低写使能
if(sys_rst_n == 1'b0)
wr_en <= 1'b1;
else if(wr_done == 1'b1)
wr_en <= 1'b0;
else
wr_en <= wr_en;
end
always @(posedge sdram_clk or negedge sys_rst_n) begin //当收到写模块的读数据请求后,数据累加
if(sys_rst_n == 1'b0)
wr_data <= 'd0;
else if(wr_data == 'd10)
wr_data <= 'd0;
else if(wr_fifo_req == 1'b1)
wr_data <= wr_data + 1'b1;
else
wr_data <= wr_data;
end
//读操作
always @(posedge sdram_clk or negedge sys_rst_n) begin //当写操作完成后打开读使能
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(rd_done == 1'b1)
rd_en <= 1'b0;
else if(wr_en == 1'b0)
rd_en <= 1'b1;
else
rd_en <= rd_en;
end
assign sdram_ba =(init_done == 1'b1)? wr_rd_ba : init_ba ;
assign sdram_cmd =(init_done == 1'b1)? wr_rd_cmd : init_cmd ;
assign sdram_addr =(init_done == 1'b1)? wr_rd_sdram_addr : init_addr;
assign wr_rd_ba =(wr_en == 1'b1)? wr_ba : rd_ba ;
assign wr_rd_cmd =(wr_en == 1'b1)? wr_cmd : rd_cmd ;
assign wr_rd_sdram_addr =(wr_en == 1'b1)? wr_sdram_addr : rd_sdram_addr;
assign sdram_dq = (wr_sdram_en == 1'b1)?wr_sdram_data : 16'hz; //在写操作时控制dq总线,写完成后释放
sdram_clk u_sdram_clk
(
.clk_out1(sdram_clk),
.clk_out2(sdram_clk_shift),
.resetn(rst_n),
.locked(locked),
.clk_in1(clk));
sdram_init u_sdram_init(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_cmd ( init_cmd ),
.init_ba ( init_ba ),
.init_addr ( init_addr ),
.init_done ( init_done )
);
sdram_write u_sdram_write(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_done ( init_done ),
.wr_en ( wr_en ),
.wr_addr ( 24'd0 ),
.wr_data ( wr_data ),
.wr_burst_len ( 10'd10 ),
.wr_cmd ( wr_cmd ),
.wr_ba ( wr_ba ),
.wr_sdram_addr ( wr_sdram_addr ),
.wr_sdram_en ( wr_sdram_en ),
.wr_sdram_data ( wr_sdram_data ),
.wr_done ( wr_done ),
.wr_fifo_req ( wr_fifo_req )
);
sdram_read u_sdram_read(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.init_done ( init_done ),
.rd_en ( rd_en ),
.rd_addr ( 24'd0 ),
.rd_burst_len ( 10'd10 ),
.rd_data ( sdram_dq ),
.rd_done ( rd_done ),
.rd_data_valid ( rd_data_valid ),
.rd_sdram_data ( rd_sdram_data ),
.rd_cmd ( rd_cmd ),
.rd_ba ( rd_ba ),
.rd_sdram_addr ( rd_sdram_addr )
);
sdram_model_plus u_sdram_model_plus(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk_shift ),
.Cke (1'b1 ),
.Cs_n (sdram_cmd[3] ),
.Ras_n (sdram_cmd[2] ),
.Cas_n (sdram_cmd[1] ),
.We_n (sdram_cmd[0] ),
.Dqm (2'b00 ),
.Debug (1'b1 )
);
endmodule
查看打印信息发现:
仿真波形如下:
观测波形发现和所画的波形图一致。
至此,我们已经完成了SDRAM的初始化模块、自动刷新模块以及读写模块,不同模块请求的指令以及请求操作的时间都是不确定的,因此需要一个仲裁模块,来判断各个模块的优先级,确保不会发生操作冲突,操作优先级设置为 自动刷新>数据写操作>数据读操作,状态机跳转图如下:
当上电开始时,状态机停留在IDLE状态,等待初始化模块完成后,跳转到仲裁判断状态,然后根据不同的模块请求操作,来跳转到对应的操作状态,等当前操作完成后,再跳回仲裁判断模块以此循环。
`timescale 1ns / 1ps
module sdram_arbit(
input clk ,
input rst_n ,
//sdram_init
input [3:0] init_cmd ,
input [1:0] init_ba ,
input [12:0] init_addr ,
input init_done ,
//sdram_auto_ref
input aref_req ,
input [3:0] aref_cmd ,
input [1:0] aref_ba ,
input [12:0] aref_addr ,
input aref_done ,
//sdram_write
input wr_req ,
input [3:0] wr_cmd ,
input [1:0] wr_ba ,
input [12:0] wr_addr ,
input wr_done ,
input wr_sdram_en ,
input [15:0] wr_sdram_data ,
//sdram_read
input rd_req ,
input [3:0] rd_cmd ,
input [1:0] rd_ba ,
input [12:0] rd_addr ,
input rd_done ,
output reg aref_en ,
output reg wr_en ,
output reg rd_en ,
//sdram_interface
output wire sdram_cke , //SDRAM时钟使能
output wire sdram_cs_n , //SDRAM片选信号
output wire sdram_ras_n , //SDRAM行地址选通
output wire sdram_cas_n , //SDRAM列地址选通
output wire sdram_we_n , //SDRAM写使能
output reg [1:0] sdram_ba , //SDRAM Bank地址
output reg [12:0] sdram_addr , //SDRAM地址总线
inout wire [15:0] sdram_dq //SDRAM数据总线
);
parameter IDLE = 5'b0_0001 , //初始状态
ARBIT = 5'b0_0010 , //仲裁状态
AREF = 5'b0_0100 , //自动刷新状态
WRITE = 5'b0_1000 , //写状态
READ = 5'b1_0000 ; //读状态
parameter CMD_NOP = 4'b0111 ; //空操作指令
reg [3:0] sdram_cmd ;
reg [4:0] state ;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
state <= IDLE;
else
case (state)
IDLE:
if(init_done == 1'b1)
state <= ARBIT;
else
state <= IDLE;
ARBIT:
if(aref_req == 1'b1)
state <= AREF;
else if(wr_req == 1'b1)
state <= WRITE;
else if(rd_req == 1'b1)
state <= READ;
else
state <= ARBIT;
AREF:
if(aref_done == 1'b1)
state <= ARBIT;
else
state <= AREF;
WRITE:
if(wr_done == 1'b1)
state <= ARBIT;
else
state <= WRITE;
READ:
if(rd_done == 1'b1)
state <= ARBIT;
else
state <= READ;
default: state <= IDLE;
endcase
end
//如果自动刷新请求信号拉高
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
aref_en <= 1'b0;
else if((state == ARBIT)&&(aref_req == 1'b1))
aref_en <= 1'b1;
else if(aref_done == 1'b1)
aref_en <= 1'b0;
else
aref_en <= aref_en;
end
//如果自动刷新没有请求,并且写请求信号拉高
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_en <= 1'b0;
else if((state == ARBIT)&&(aref_req == 1'b0)&&(wr_req == 1'b1))
wr_en <= 1'b1;
else if(wr_done == 1'b1)
wr_en <= 1'b0;
else
wr_en <= wr_en;
end
//如果自动刷新没有请求,写请求信号也没拉高,并且读请求信号拉高
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_en <= 1'b0;
else if((state == ARBIT)&&(aref_req == 1'b0)&&(wr_req == 1'b0)&&(rd_req == 1'b1))
rd_en <= 1'b1;
else if(rd_done == 1'b1)
rd_en <= 1'b0;
else
rd_en <= rd_en;
end
always @(*) begin
case(state)
IDLE: begin
sdram_cmd <= init_cmd;
sdram_ba <= init_ba;
sdram_addr <= init_addr;
end
AREF: begin
sdram_cmd <= aref_cmd;
sdram_ba <= aref_ba;
sdram_addr <= aref_addr;
end
WRITE: begin
sdram_cmd <= wr_cmd;
sdram_ba <= wr_ba;
sdram_addr <= wr_addr;
end
READ: begin
sdram_cmd <= rd_cmd;
sdram_ba <= rd_ba;
sdram_addr <= rd_addr;
end
default: begin
sdram_cmd <= CMD_NOP;
sdram_ba <= 2'b11;
sdram_addr <= 13'h1fff;
end
endcase
end
//SDRAM时钟使能
assign sdram_cke = 1'b1;
//SDRAM数据总线
assign sdram_dq = (wr_sdram_en == 1'b1) ? wr_sdram_data : 16'bz;
//片选信号,行地址选通信号,列地址选通信号,写使能信号
assign {sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n} = sdram_cmd;
endmodule
把SDRAM初始化模块、SDRAM自动刷新模块、SDRAM写模块、SDRAM读模块、SDRAM仲裁模块全都用 SDRAM_CTRL顶层调用起来作为一个整体模块,和4.1章画的控制模块框图一致。
`timescale 1ns/1ns
module tb_sdram_ctrl();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
//fifo控制模块
reg wr_req ;
reg [15:0] wr_data ;
reg rd_req ;
wire wr_fifo_req ;
wire [15:0] rd_sdram_data ;
wire rd_data_valid ;
//仲裁模块
wire sdram_cke ;
wire sdram_cs_n ;
wire sdram_ras_n ;
wire sdram_cas_n ;
wire sdram_we_n ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
wire [15:0] sdram_dq ;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
defparam u_sdram_ctrl.u_sdram_auto_ref.CNT_REF_MAX = 50; //修改一下自动刷新模块里的计数值,让仿真可以跑得快一点
initial begin
clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end
always #10 clk = ~clk;
assign sys_rst_n = rst_n & locked;
//写操作
always @(posedge sdram_clk or negedge sys_rst_n) begin //一开始打开写使能,当写操作完成后拉低写使能
if(sys_rst_n == 1'b0)
wr_req <= 1'b1;
else if(wr_data == 16'd20)
wr_req <= 1'b0;
else
wr_req <= wr_req;
end
always @(posedge sdram_clk or negedge sys_rst_n) begin //当收到写模块的读数据请求后,数据从10开始累加
if(sys_rst_n == 1'b0)
wr_data <= 'd10;
else if(wr_data == 'd20)
wr_data <= 'd10;
else if(wr_fifo_req == 1'b1)
wr_data <= wr_data + 1'b1;
else
wr_data <= wr_data;
end
//读操作
always @(posedge sdram_clk or negedge sys_rst_n) begin //当写操作完成后打开读使能
if(sys_rst_n == 1'b0)
rd_req <= 1'b0;
else if(wr_req == 1'b0)
rd_req <= 1'b1;
else
rd_req <= rd_req;
end
sdram_clk u_sdram_clk
(
.clk_out1 (sdram_clk ),
.clk_out2 (sdram_clk_shift),
.resetn (rst_n ),
.locked (locked ),
.clk_in1 (clk )
);
sdram_ctrl u_sdram_ctrl(
.clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.wr_req ( wr_req ),
.wr_addr ( 24'd0 ),
.wr_data ( wr_data ),
.wr_burst_len ( 10'd10 ),
.rd_req ( rd_req ),
.rd_addr ( 24'd0 ),
.rd_burst_len ( 10'd10 ),
.wr_fifo_req ( wr_fifo_req ),
.rd_sdram_data ( rd_sdram_data ),
.rd_data_valid ( rd_data_valid ),
.sdram_cke ( sdram_cke ),
.sdram_cs_n ( sdram_cs_n ),
.sdram_ras_n ( sdram_ras_n ),
.sdram_cas_n ( sdram_cas_n ),
.sdram_we_n ( sdram_we_n ),
.sdram_ba ( sdram_ba ),
.sdram_addr ( sdram_addr ),
.sdram_dq ( sdram_dq )
);
sdram_model_plus u_sdram_model_plus(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk_shift ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (2'b00 ),
.Debug (1'b1 )
);
endmodule
观察打印信息可以知道:数据实现了正常的读写操作,并且初始化模块、读写模块、自动刷新模块都正常,没有发生指令冲突。
fifo_ctrl模块里面有四个fifo:写数据FIFO、写信息FIFO、读数据FIFO、读信息FIFO。
写操作:
将要写入的数据写到写FIFO里,再写入一个数据信息(突发长度+bank地址+行地址+列地址)到写信息FIFO里,控制模块就会读取信息FIFO里的数来向sdram_ctrl模块请求写操作。
读操作:
将要读取的数据信息(突发长度+bank地址+行地址+列地址)写到读信息FIFO里,控制模块就会读取读信息FIFO里的数来向sdram_ctrl模块请求读操作。sdram_ctrl模块将从SDRAM读出来的数据写到读数据FIFO里给外部模块读取。
`timescale 1ns / 1ps
module fifo_ctrl(
input sys_clk ,
input rst_n ,
//wr_fifo_signal
input wr_fifo_wr_clk , //写fifo写时钟
input [15:0] wr_data_fifo_wr_data , //写sdram缓存到fifo的数据,突发长度+地址
input wr_data_fifo_wreq , //写数据FIFO写使能
input [33:0] wr_imfor_fifo_data , //写SDRAM突发长度和地址的数据
input wr_imfor_fifo_wreq , //写信息fifo的写使能
input wr_fifo_rst , //写fifo复位信号
//rd_fifo_signal
input rd_fifo_rd_clk , //读fifo读时钟
input [33:0] rd_imfor_fifo_data , //读sdram的信息数据,突发长度+地址
input rd_imfor_fifo_wreq , //读sdram信息数据FIFO写使能
input rd_fifo_rst , //读fifo复位
input rd_data_fifo_rdreq , //读数据FIFO读使能
output [15:0] rd_fifo_data , //读数据FIFO读数据
output [8:0] rd_fifo_cnt , //读数据FIFO读数量
input rd_data_fifo_wreq , //SDRAM读数据有效信号
input [15:0] rd_data_fifo_data , //SDRAM读出的数据
//wr_sdram_signal
input wr_data_fifo_rdreq , //读写数据FIFO读使能
output reg sdram_wr_req , //SDRAM写数据请求
output reg [23:0] sdram_wr_addr , //SDRAM写数据地址
output [15:0] sdram_wr_data , //SDRAM写数据
output reg [9:0] sdram_wr_burst_len , //SDRAM写突发长度
//rd_sdram_sginal
output reg sdram_rd_req , //SDRAM读请求信号
output reg [9:0] sdram_rd_burst_len , //SDRAM读数据突发长度
output reg [23:0] sdram_rd_addr //SDRAM读数据地址
);
//wr_signal
wire wr_data_fifo_full ;
wire wr_data_fifo_empty ;
wire [8:0] wr_data_fifo_wcnt ;
wire [8:0] wr_data_fifo_rcnt ;
wire wr_imfor_fifo_full ;
wire wr_imfor_fifo_empty ;
wire wr_data_fifo_rdreq_fall ;
reg wr_imfor_fifo_rdreq ;
wire [33:0] wr_imfor_fifo_rdata ;
reg [2:0] wr_state ;
reg wr_data_fifo_rdreq_d1 ;
//rd_signal
wire rd_data_fifo_full ;
wire rd_data_fifo_empty ;
wire [8:0] rd_data_fifo_wcnt ;
reg rd_imfor_fifo_rdreq ;
wire [33:0] rd_imfor_fifo_rdata ;
wire rd_imfor_fifo_full ;
wire rd_imfor_fifo_empty ;
reg [2:0] rd_state ;
reg rd_data_fifo_rdreq_d1 ;
wire rd_data_fifo_rdreq_fall ;
//wr_sdram_state
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_data_fifo_rdreq_d1 <= 1'b0;
else
wr_data_fifo_rdreq_d1 <= wr_data_fifo_rdreq;
end
//一次写突发完成
assign wr_data_fifo_rdreq_fall =(wr_data_fifo_rdreq_d1 & ~wr_data_fifo_rdreq);
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_state <= 3'd0;
else
case (wr_state)
3'd0:begin
if(wr_imfor_fifo_empty ==1'b0)
wr_state <= 3'd1;
else
wr_state <= 3'd0;
end
3'd1:wr_state <= 3'd2;
3'd2:begin
if(wr_data_fifo_rdreq_fall == 1'b1)
wr_state <= 3'd0;
else
wr_state <= 3'd2;
end
default: wr_state <= 3'd0;
endcase
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
wr_imfor_fifo_rdreq <= 1'b0;
else if(wr_state == 3'd1)
wr_imfor_fifo_rdreq <= 1'b1;
else
wr_imfor_fifo_rdreq <= 1'b0;
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
sdram_wr_req <= 1'b0;
sdram_wr_addr <= 24'd0;
sdram_wr_burst_len <= 10'd0;
end
else if(wr_state == 3'd2)begin
sdram_wr_req <= 1'b1;
sdram_wr_addr <= wr_imfor_fifo_rdata[23:0];
sdram_wr_burst_len <= wr_imfor_fifo_rdata[33:24];
end
else begin
sdram_wr_req <= 1'b0;
sdram_wr_addr <= 24'd0;
sdram_wr_burst_len <= 10'd0;
end
end
//rd_state
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_data_fifo_rdreq_d1 <= 1'b0;
else
rd_data_fifo_rdreq_d1 <= rd_data_fifo_rdreq;
end
//一次读突发完成
assign rd_data_fifo_rdreq_fall =(rd_data_fifo_rdreq_d1 & ~rd_data_fifo_rdreq);
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_state <= 3'd0;
else
case (rd_state)
3'd0:begin
if(rd_imfor_fifo_empty ==1'b0)
rd_state <= 3'd1;
else
rd_state <= 3'd0;
end
3'd1:rd_state <= 3'd2;
3'd2:begin
if(rd_data_fifo_rdreq_fall == 1'b1)
rd_state <= 3'd0;
else
rd_state <= 3'd2;
end
default: rd_state <= 3'd0;
endcase
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_imfor_fifo_rdreq <= 1'b0;
else if(rd_state == 3'd1)
rd_imfor_fifo_rdreq <= 1'b1;
else
rd_imfor_fifo_rdreq <= 1'b0;
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
sdram_rd_req <= 1'b0;
sdram_rd_addr <= 24'd0;
sdram_rd_burst_len <= 10'd0;
end
else if(rd_state == 3'd2)begin
sdram_rd_req <= 1'b1;
sdram_rd_addr <= rd_imfor_fifo_rdata[23:0];
sdram_rd_burst_len <= rd_imfor_fifo_rdata[33:24];
end
else begin
sdram_rd_req <= 1'b0;
sdram_rd_addr <= 24'd0;
sdram_rd_burst_len <= 10'd0;
end
end
//wr_fifo
wr_sdram_data_fifo u1_wr_sdram_data_fifo (
.rst (wr_fifo_rst ),
.wr_clk (wr_fifo_wr_clk ),
.rd_clk (sys_clk ),
.din (wr_data_fifo_wr_data ),
.wr_en (wr_data_fifo_wreq ),
.rd_en (wr_data_fifo_rdreq ),
.dout (sdram_wr_data ),
.full (wr_data_fifo_full ),
.empty (wr_data_fifo_empty ),
.rd_data_count (wr_data_fifo_rcnt ),
.wr_data_count (wr_data_fifo_wcnt )
);
wr_sdram_imfor_fifo u1_wr_sdram_imfor_fifo (
.rst (wr_fifo_rst ),
.wr_clk (wr_fifo_wr_clk ),
.rd_clk (sys_clk ),
.din (wr_imfor_fifo_data ),
.wr_en (wr_imfor_fifo_wreq ),
.rd_en (wr_imfor_fifo_rdreq ),
.dout (wr_imfor_fifo_rdata ),
.full (wr_imfor_fifo_full ),
.empty (wr_imfor_fifo_empty )
);
//rd_fifo
rd_sdram_data_fifo u1_rd_sdram_data_fifo (
.rst (rd_fifo_rst ),
.wr_clk (sys_clk ),
.rd_clk (rd_fifo_rd_clk ),
.din (rd_data_fifo_data ),
.wr_en (rd_data_fifo_wreq ),
.rd_en (rd_data_fifo_rdreq ),
.dout (rd_fifo_data ),
.full (rd_data_fifo_full ),
.empty (rd_data_fifo_empty ),
.rd_data_count (rd_fifo_cnt ),
.wr_data_count (rd_data_fifo_wcnt )
);
rd_sdram_imfor_fifo u1_rd_sdram_imfor_fifo (
.rst (rd_fifo_rst ),
.wr_clk (rd_fifo_rd_clk ),
.rd_clk (sys_clk ),
.din (rd_imfor_fifo_data ),
.wr_en (rd_imfor_fifo_wreq ),
.rd_en (rd_imfor_fifo_rdreq ),
.dout (rd_imfor_fifo_rdata ),
.full (rd_imfor_fifo_full ),
.empty (rd_imfor_fifo_empty )
);
endmodule
将sdram_ctrl模块和fifo_ctrl模块用顶层模块相连,与4.1节所画的框图一致,如下图所示:
`timescale 1ns / 1ns
module tb_sdram_top();
reg clk ;
reg rst_n ;
wire sdram_clk ;
wire sdram_clk_shift ;
wire locked ;
wire sys_rst_n ;
//wr_sdram_singnal
reg wr_data_fifo_wreq ;
reg [15:0] wr_data_fifo_wr_data ;
reg wr_fifo_wr_clk ;
reg [33:0] wr_imfor_fifo_data ;//高十位为突发长度,其次两位是bank地址,接下来13位位行地址,最后9位为列地址
reg wr_imfor_fifo_wreq ;
//rd_sdram_singnal
reg [33:0] rd_imfor_fifo_data ;
reg rd_imfor_fifo_wreq ;
reg rd_data_fifo_rdreq ;
wire [15:0] rd_fifo_data ;
wire [8:0] rd_fifo_cnt ;
//sdram_interface
wire sdram_cke ;
wire sdram_cs_n ;
wire sdram_ras_n ;
wire sdram_cas_n ;
wire sdram_we_n ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
wire [15:0] sdram_dq ;
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
defparam u_sdram_top.u_sdram_ctrl.u_sdram_auto_ref.CNT_REF_MAX = 39;
initial begin
clk = 0;
wr_fifo_wr_clk = 0;
rst_n = 0;
#1000;
rst_n = 1;
end
always #10 clk = ~clk;
always #7 wr_fifo_wr_clk = ~wr_fifo_wr_clk;
assign sys_rst_n = rst_n & locked;
//wr_sdram
always @(posedge wr_fifo_wr_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)begin
wr_data_fifo_wr_data <= 10'd100;
wr_data_fifo_wreq <= 1'b0;
end
else if(wr_data_fifo_wr_data == 10'd110)begin
wr_data_fifo_wr_data <= 10'd110;
wr_data_fifo_wreq <= 1'b0;
end
else begin
wr_data_fifo_wr_data <= wr_data_fifo_wr_data + 1'b1;
wr_data_fifo_wreq <= 1'b1;
end
end
always @(posedge wr_fifo_wr_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)begin
wr_imfor_fifo_data <= 'd0;
wr_imfor_fifo_wreq <= 1'b0;
end
else if(wr_data_fifo_wr_data == 10'd109)begin
wr_imfor_fifo_data <= {10'd10,2'b01,13'd20,9'd0}; //突发长度为10,在bank1 ,第20行,第0列开始写数据
wr_imfor_fifo_wreq <= 1'b1;
end
else begin
wr_imfor_fifo_data <= 'd0;
wr_imfor_fifo_wreq <= 1'b0;
end
end
//rd_sdram
always @(posedge wr_fifo_wr_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)begin
rd_imfor_fifo_data <= 'd0;
rd_imfor_fifo_wreq <= 1'b0;
end
else if(wr_data_fifo_wr_data == 10'd109)begin
rd_imfor_fifo_data <= {10'd10,2'b01,13'd20,9'd0}; //突发长度为10,在bank1 ,第20行,第0列开始读数据
rd_imfor_fifo_wreq <= 1'b1;
end
else begin
rd_imfor_fifo_data <= 'd0;
rd_imfor_fifo_wreq <= 1'b0;
end
end
always @(posedge wr_fifo_wr_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
rd_data_fifo_rdreq <= 1'b0;
else if(rd_fifo_cnt != 'd0)
rd_data_fifo_rdreq <= 1'b1;
else
rd_data_fifo_rdreq <= 1'b0;
end
sdram_clk u_sdram_clk
(
.clk_out1 (sdram_clk ),
.clk_out2 (sdram_clk_shift),
.resetn (rst_n ),
.locked (locked ),
.clk_in1 (clk )
);
sdram_top u_sdram_top(
.sys_clk ( sdram_clk ),
.rst_n ( sys_rst_n ),
.wr_fifo_wr_clk ( wr_fifo_wr_clk ),
.wr_data_fifo_wr_data ( wr_data_fifo_wr_data ),
.wr_data_fifo_wreq ( wr_data_fifo_wreq ),
.wr_imfor_fifo_data ( wr_imfor_fifo_data ),
.wr_imfor_fifo_wreq ( wr_imfor_fifo_wreq ),
.wr_fifo_rst ( 1'b0 ),
.rd_fifo_rd_clk ( wr_fifo_wr_clk ),
.rd_imfor_fifo_data ( rd_imfor_fifo_data ),
.rd_imfor_fifo_wreq ( rd_imfor_fifo_wreq ),
.rd_fifo_rst ( 1'b0 ),
.rd_data_fifo_rdreq ( rd_data_fifo_rdreq ),
.rd_fifo_data ( rd_fifo_data ),
.rd_fifo_cnt ( rd_fifo_cnt ),
.sdram_cke ( sdram_cke ),
.sdram_cs_n ( sdram_cs_n ),
.sdram_ras_n ( sdram_ras_n ),
.sdram_cas_n ( sdram_cas_n ),
.sdram_we_n ( sdram_we_n ),
.sdram_ba ( sdram_ba ),
.sdram_addr ( sdram_addr ),
.sdram_dq ( sdram_dq )
);
sdram_model_plus u_sdram_model_plus(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk_shift ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (2'b00 ),
.Debug (1'b1 )
);
endmodule
仿真代码和sdram_ctrl模块类似,我们模拟一个读写时钟,来向读写fifo里写数据,然后读出读fifo里读数据看数据是否和写入的数据一致,突发长度设置为10,bank地址为01,行地址为20,列地址为0。写入从101~110十个数据。
观察modelsim打印信息,写入数据和读出数据一致,接下来观看波形图:
复位完成向写数据fifo写入十个数据,同时向写信息FIFO写入突发长度+地址。再向读信息FIFO里写入读信息数据。
在sdram初始化完成后进行读写操作,最后将读出的数据写入读数据FIFO里,读出的数据和写入的数据一致。
在本次实验中,添加uart的接收模块以及发送模块,具体参考《详解UART通信协议以及FPGA实现》;本次实现一次性突发读写20个数据,因此需要增加一个uart_ctrl模块,来控制SDRAM的读写位置以及控制uart的接收和发送。代码如下:
`timescale 1ns / 1ps
module uart_ctrl(
input sys_clk , //输入和uart接收发送一致的系统时钟
input rst_n ,
//uart_rx
input [7:0] rx_data ,
input rx_data_valid ,
//uart_tx
output reg [7:0] tx_data ,
output reg tx_data_valid ,
//sdram_fifo_ctrl_signal
output reg [15:0] wr_data_fifo_wr_data ,
output reg wr_data_fifo_wreq ,
output reg [33:0] wr_imfor_fifo_data ,
output reg wr_imfor_fifo_wreq ,
input [15:0] rd_fifo_data ,
input [8:0] rd_fifo_cnt , //读数据FIFO读数量
output reg [33:0] rd_imfor_fifo_data ,
output reg rd_imfor_fifo_wreq ,
output reg rd_data_fifo_rdreq
);
parameter wait_time_max = 9'd500; //115200波特率一个码元计数周期是434 再加上其它时间,设置500比较合理
reg [4:0] rx_data_cnt ; //uart接收数据计数器
reg rd_fifo_flag ; //读数据fifo标志信号
reg [8:0] wait_time_cnt ; //uart 发送数据间隔时间
reg rd_data_fifo_rdreq_reg ; //读fifo读使能打一拍
//从rx接收数据到写入SDRAM 的fifo里
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rx_data_cnt <= 5'd0;
else if(rx_data_cnt == 5'd20)
rx_data_cnt <= 5'd0;
else if(rx_data_valid == 1'b1)
rx_data_cnt <= rx_data_cnt + 1'b1;
else
rx_data_cnt <= rx_data_cnt;
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
wr_data_fifo_wr_data <= 16'd0;
wr_data_fifo_wreq <= 1'b0;
end
else if(rx_data_valid == 1'b1) begin
wr_data_fifo_wr_data <= {8'd0,rx_data};
wr_data_fifo_wreq <= 1'b1;
end
else begin
wr_data_fifo_wr_data <= 16'd0;
wr_data_fifo_wreq <= 1'b0;
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
wr_imfor_fifo_data <= 34'd0;
wr_imfor_fifo_wreq <= 1'b0;
end
else if(rx_data_cnt == 5'd20) begin
wr_imfor_fifo_data <= {10'd20,2'b10,13'd33,9'd0}; //突发长度为20,在bank10,33行0列开始写
wr_imfor_fifo_wreq <= 1'b1;
end
else begin
wr_imfor_fifo_data <= 34'd0;
wr_imfor_fifo_wreq <= 1'b0;
end
end
//从SDRAM读fifo里读数据到uart的tx端
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
rd_imfor_fifo_data <= 34'd0;
rd_imfor_fifo_wreq <= 1'b0;
end
else if(wr_imfor_fifo_wreq == 1'b1)begin //当写SDRAM信息完成后,开始写入读信息
rd_imfor_fifo_data <= {10'd20,2'b10,13'd33,9'd0}; //突发长度为20,在bank10,33行0列开始读
rd_imfor_fifo_wreq <= 1'b1;
end
else begin
rd_imfor_fifo_data <= 34'd0;
rd_imfor_fifo_wreq <= 1'b0;
end
end
//如果读数据fifo 有一次突发长度的数据后,就拉高rd_fifo_flag,直到全部数据读完
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_fifo_flag <= 1'b0;
else if(rd_fifo_cnt == 9'd0)
rd_fifo_flag <= 1'b0;
else if(rd_fifo_cnt == 9'd20)
rd_fifo_flag <= 1'b1;
else
rd_fifo_flag <= rd_fifo_flag;
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
wait_time_cnt <= 9'd0;
else if(wait_time_cnt == wait_time_max - 1)
wait_time_cnt <= 9'd0;
else if(rd_fifo_flag == 1'b1)
wait_time_cnt <= wait_time_cnt + 1'b1;
else
wait_time_cnt <= wait_time_cnt;
end
//在rd_fifo_flag有效期间,每间隔一个码元周期,读一次数据直到读完
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_data_fifo_rdreq <= 1'b0;
else if(wait_time_cnt == wait_time_max - 1)
rd_data_fifo_rdreq <= 1'b1;
else
rd_data_fifo_rdreq <= 1'b0;
end
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)
rd_data_fifo_rdreq_reg <= 1'b0;
else
rd_data_fifo_rdreq_reg <= rd_data_fifo_rdreq;
end
//uart发送数据
always @(posedge sys_clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
tx_data <= 8'd0;
tx_data_valid <= 1'b0;
end
else if(rd_data_fifo_rdreq_reg == 1'b1) begin
tx_data <= rd_fifo_data[7:0];
tx_data_valid <= 1'b1;
end
else begin
tx_data <= 8'd0;
tx_data_valid <= 1'b0;
end
end
endmodule
uart_crtl模块将接收到的rx_data写入到wr_data_fifo里;写满20个数据后,再写入SDRAM的突发长度20,写bank地址10,写行地址33,写列地址0。再写入这些完成后,再向读SDRAM信息fifo写入读信息;最后等待rd_data_fifo写满20个从SDRAM读出的数据,再从uart_tx发送出去。
将uart_tx、uart_rx、uart_ctrl模块和sdram_top模块连接起来,如图所示:
由于输入端口就三个信号,因此仿真代码可以参考uart_rx的仿真代码,然后再加上sdram仿真模型即可,代码如下:
`timescale 1ns / 1ns
module tb_uart_sdram_top();
reg clk_50m ;
reg rst_n ;
reg rx ;
wire tx ;
//sdram_interface
wire sdram_cke ;
wire sdram_cs_n ;
wire sdram_ras_n ;
wire sdram_cas_n ;
wire sdram_we_n ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
wire [15:0] sdram_dq ;
//重定义SDRAM仿真模型的一些参数
defparam u_sdram_model_plus.addr_bits = 13;
defparam u_sdram_model_plus.data_bits = 16;
defparam u_sdram_model_plus.col_bits = 9;
defparam u_sdram_model_plus.mem_sizes = 2*1024*1024;
defparam u_uart_sdram_top.u_sdram_top.u_sdram_ctrl.u_sdram_auto_ref.CNT_REF_MAX = 39;
//初始化
initial begin
clk_50m = 0;
rst_n = 0;
rx = 1;
#100
rst_n = 1;
end
//调用task函数,连续发送20个数据
initial begin
#300
tx_data(8'h6);
tx_data(8'h6);
tx_data(8'h1);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
tx_data(8'hE);
tx_data(8'hF);
tx_data(8'h5);
tx_data(8'h2);
tx_data(8'h0);
tx_data(8'h1);
tx_data(8'h3);
tx_data(8'h1);
tx_data(8'h4);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
end
//定义task函数
task tx_data(
input [7:0] tx_data
);
integer i;
for (i=0; i<10; i=i+1 ) begin
case(i)
0: rx <= 1'b0;
1: rx <= tx_data[0];
2: rx <= tx_data[1];
3: rx <= tx_data[2];
4: rx <= tx_data[3];
5: rx <= tx_data[4];
6: rx <= tx_data[5];
7: rx <= tx_data[6];
8: rx <= tx_data[7];
9: rx <= 1'b1;
endcase
#(434*20);//每发送完一个bit数据后,延迟一个码元的时间
end
endtask
always #10 clk_50m = ~ clk_50m;
sdram_model_plus u_sdram_model_plus(
.Dq (sdram_dq ),
.Addr (sdram_addr ),
.Ba (sdram_ba ),
.Clk (sdram_clk_shift ),
.Cke (sdram_cke ),
.Cs_n (sdram_cs_n ),
.Ras_n (sdram_ras_n ),
.Cas_n (sdram_cas_n ),
.We_n (sdram_we_n ),
.Dqm (2'b00 ),
.Debug (1'b1 )
);
uart_sdram_top u_uart_sdram_top(
.clk_50m ( clk_50m ),
.rst_n ( rst_n ),
.rx ( rx ),
.tx ( tx ),
.sdram_clk_shift ( sdram_clk_shift ),
.sdram_cke ( sdram_cke ),
.sdram_cs_n ( sdram_cs_n ),
.sdram_ras_n ( sdram_ras_n ),
.sdram_cas_n ( sdram_cas_n ),
.sdram_we_n ( sdram_we_n ),
.sdram_ba ( sdram_ba ),
.sdram_addr ( sdram_addr ),
.sdram_dq ( sdram_dq )
);
endmodule
由代码可以看到,我们模拟uart连续发送了20个数据,分别是:8’h6、8’h6、8’h1、8’hA、8’hB、8’hC、8’hD、8’hE、8’hF、8’h5、8’h2、8’h0、8’h1、8’h3、8’h1、8’h4、8’hA、8’hB、8’hC、8’hD、
运行仿真后,我们首先看uart_rx模块,具体波形图如下:
通过波形图我们可以看到,uart_rx模块能正确的接收到这20个数据。
其次我们来看sdram_fifo_ctrl模块是否正确的将这些数据以及写sdram的信息读出fifo,具体波形图如下:
在写入20个数据后,写入信息FIFO数据为:{10’d20,2’b10,13’d33,9’d0}转换成二进制就是:{0000010100,10,0000000100001,000000000},和波形图显示的数据一致;
然后我们来看SDRAM的打印信息,是否将这些数据正确的写入到sdram的指定位置,打印信息如下所示:
由打印信息可知,我们写入的20个数据正常的写入到了sdram指定位置:bank10,第33行,第0列开始。
在sdram写入成功后,我们观察sdram是否正常读取,打印信息如下:
由打印信息可以知道,我们成功的从sdram指定位置一次性读出了20个数据,读出的20个数据和写入的20个数据一致。
在sdram读写都没问题后,我们最后观察uart_tx模块,波形图如下:
通过波形图我们可以看到,从最开始uart_rx接收到的20个数据,完整的由到了uart_tx模块发送出去,因此本次成功实现了uart到sdram的环回测试。
《野火征途FPGA Verilog 开发实战指南》
《MT48LC16M16A2TG-75ITD》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。