这次学习ddr4的读写时序和仿真操作。在学习这节知识的时候,最好是要有ram,rom,FIFO等存储器编写仿真的基础,还有ddr4的基础内容的学习,详情可以去看一下上两期的讲解博客。
目录
一、写时序
对于用户app接口来说,写数据的时候,命令通道和写数据通道的前后延时最大不超过2个时钟周期,即在允许的范围内,写命令通道可以不与写数据通道对齐。因此在有效范围内有三种写时序的操作,作为初学还是一对齐为主,那我就只演示对齐仿真了。
看上面的图是不是很懵逼?没事,听我慢慢道来。
首先我们控制的端口叫做用户端就是含有app前缀的一端,另一端是由mig控制器自动编码译码去控制的不需要我们去管,但要回看(后面仿真会带你看的)。
所以在用户端我们要知道我们写时序应该是怎么样的就行。
1.app_cmd,写时序时控制为000,读时序控制为001,相当于ram存储时的wr_en,rd_en信号。
2.app_en,这个信号就是在每次你的读写过程中要向mig发送一个请求信号让其知道用户端这边想要写数据或者读数据,就是一个握手信号只是它不是一个脉冲信号,而是一个持续信号,直到你的读写结束才停止。
3.app_rdy,这个不需要你控制,它是由mig控制发送给你的,这个表示的是ddr那端准备好了,你可以发送数据过去了。
4.app_wdf_rdy,这个信号也不需要你去控制,它也是由mig控制发送给你的,这个表示的是mig控制内的缓冲FIFO准备好了,你可以发送数据给到mig了。
5.app_wdf_wren,这个就很重要了,是由你来操控的,它也是一个使能请求信号,相当于ram里面的wr_en,只有它拉高表示用户端开始向ddr写数据,它对应ddr端的we_n信号写使能。
6.app_wdf_end,表示的是写数据的最后一个数据,我们在往ddr写数据时一般都是默认所有数据都是最后一个数据,那么我们就可以让它和app_wdf_wren连在一起。
7.app_addr,地址的控制必须要了解ddr的存储过程,因为我的例子数据写的是128的位宽,我只有16根数据线,所以我每次数据跳变就相当于我存储了8个字节的数据。就相当于我使用了8列的地址线,所以在地址跳变的时候地址就要一次跳8位,即app_addr+8(aap_addr+4‘b1000).
8.app_wdf_data,这个就是数据了,按照你自己的设定自己选择多大的数据宽度就行。
所以综上所述,时序就是当app_en,app_rdy,app_wdf_rdy,app_wdf_wren拉高时,app_cmd为000,给上地址和数据就可以写数据了,然后一次写多少个数据就看你自己了。
二、读时序
老样子,慢慢讲解。
app_en,app_rdy,这个就不讲了上面说过。那就很好看了,只要当app_en,app_rdy信号拉高,app_cmd为001时给上地址就可以读数据了,然后延迟CL时间,当app_rd_data_valid出现时就是数据已经取出并且是有效数据。
三、仿真代码
其实代码就相当于ram的编写,那我就直接代码了,就不再啰嗦了。
DDR4用户端控制代码
`timescale 1ns / 1ps
module ctrl(
input wire clk ,
input wire rst_n ,
input wire [127:0] app_rd_data ,
input wire app_rd_data_end ,
input wire app_rd_data_valid ,
input wire app_rdy ,
input wire app_wdf_rdy ,
output wire [15:0] app_wdf_mask ,
output wire [27:0] app_addr ,
output wire [2:0] app_cmd ,
output wire app_en ,
output wire [127:0] app_wdf_data ,
output wire app_wdf_end ,
output wire app_wdf_wren
);
parameter DEPTH=120;//写数据和读的总深度
reg cmd_en ;
reg [31:0] data_cnt;
reg [2:0] cmd ;
reg wr_en ;
reg [27:0] addres ;
reg [127:0] data ;
assign app_en=cmd_en&app_rdy;
assign app_cmd=cmd;
assign app_wdf_end=wr_en;
assign app_wdf_wren=wr_en;
assign app_addr=addres;
assign app_wdf_data=data;
assign app_wdf_mask=16'd0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cmd_en<= 0;
cmd<= 3'b000;
end
else if(data_cnt<DEPTH/2-1)begin
cmd_en<= app_rdy;
cmd<= 3'b000;
end
else if(data_cnt>=(DEPTH/2-1))begin
cmd_en<= app_rdy;
cmd<= 3'b001;
end
else begin
cmd_en<= 0;
cmd<= cmd;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_en<= 0;
end
else if(data_cnt<DEPTH/2-1)begin
wr_en<= app_wdf_rdy;
end
else if(data_cnt>=(DEPTH/2-1))begin
wr_en<= 0;
end
else begin
wr_en<= wr_en;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
addres<= 0;
data_cnt<=0;
end
else if(app_rdy)begin
if((data_cnt<DEPTH/2-1)&&wr_en&&app_wdf_rdy)begin
addres<= addres+4'b1000;
data_cnt<=data_cnt+1;
end
else if((data_cnt>DEPTH/2-1)&&(data_cnt<DEPTH-1))begin
addres<= addres+4'b1000;
data_cnt<=data_cnt+1;
end
else if(data_cnt==DEPTH/2-1)begin
addres<=0;
data_cnt<=data_cnt+1;
end
else if(data_cnt>=DEPTH-1)begin
addres<=0;
data_cnt<=0;
end
else begin
addres<= addres;
data_cnt<=data_cnt;
end
end
else begin
addres<= addres;
data_cnt<=data_cnt;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data<= 0;
end
else if(cmd==3'b000)begin
if(wr_en&app_wdf_rdy&app_rdy)begin
if(data_cnt<(DEPTH-1))
data<= data+1;
else
data<=0;
end
else
data<=data;
end
else if(cmd==3'b001)
data<=0;
else
data<= data;
end
endmodule
TOP代码
`timescale 1ns / 1ps
module top(
input clk_200M_p ,
input clk_200M_n ,
input sys_rst_n ,
output [16:0] ddr4_addr ,
output [1 :0] ddr4_ba ,
output ddr4_bg ,
output ddr4_act_n ,
output ddr4_cs_n ,
output ddr4_ck_c ,
output ddr4_ck_t ,
output ddr4_cke ,
output ddr4_reset_n ,
output [1 :0]ddr4_dm_dbi_n,
output ddr4_odt ,
inout [15:0] ddr4_dq ,
inout [1:0] ddr4_dqs_c ,
inout [1:0] ddr4_dqs_t
);
wire init_calib_complete;
wire ui_clk ;
wire ui_clk_sync_rst ;
wire [15:0] app_wdf_mask ;
wire [27:0] app_addr ;
wire [2:0] app_cmd ;
wire app_en ;
wire [127:0] app_wdf_data ;
wire app_wdf_end ;
wire app_wdf_wren ;
wire [127:0] app_rd_data ;
wire app_rd_data_end ;
wire app_rd_data_valid ;
wire app_rdy ;
wire app_wdf_rdy ;
wire addn_ui_clkout1 ;
ila_0 ila_u (
.clk(addn_ui_clkout1), // input wire clk
.probe0 (app_wdf_data ), // input wire [127:0] probe0
.probe1 (app_rd_data ), // input wire [127:0] probe1
.probe2 (app_addr ), // input wire [27:0] probe2
.probe3 (app_cmd ), // input wire [2:0] probe3
.probe4 (init_calib_complete ), // input wire [0:0] probe4
.probe5 (app_en ), // input wire [0:0] probe5
.probe6 (app_wdf_end ), // input wire [0:0] probe6
.probe7 (app_wdf_wren ), // input wire [0:0] probe7
.probe8 (app_rd_data_valid ), // input wire [0:0] probe8
.probe9 (app_rdy ), // input wire [0:0] probe9
.probe10(app_wdf_rdy ) // input wire [0:0] probe10
);
ctrl DDR4_ctrl(
.clk (ui_clk ) ,
.rst_n (!ui_clk_sync_rst&init_calib_complete ) ,
.app_wdf_mask (app_wdf_mask ) ,
.app_addr (app_addr ) ,
.app_cmd (app_cmd ) ,
.app_en (app_en ) ,
.app_wdf_data (app_wdf_data ) ,
.app_wdf_end (app_wdf_end ) ,
.app_wdf_wren (app_wdf_wren ) ,
.app_rd_data (app_rd_data ) ,
.app_rd_data_end (app_rd_data_end ) ,
.app_rd_data_valid(app_rd_data_valid) ,
.app_rdy (app_rdy ) ,
.app_wdf_rdy (app_wdf_rdy )
);
DDR4 u_DDR4 (
.c0_init_calib_complete (init_calib_complete),// output wire c0_init_calib_complete 初始化结束信号
.dbg_clk ( ),// output wire dbg_clk debug信号 悬空
.c0_sys_clk_p (clk_200M_p ),// input wire c0_sys_clk_p 差分时钟200M +
.c0_sys_clk_n (clk_200M_n ),// input wire c0_sys_clk_n -
.dbg_bus ( ),// output wire [511 : 0] dbg_bus 总线 悬空
.c0_ddr4_adr (ddr4_addr ),// output wire [16 : 0] c0_ddr4_adr 17位地址{0-9列地址,0-14行地址,15we_n,16cas,17ras}
.c0_ddr4_ba (ddr4_ba ),// output wire [1 : 0] c0_ddr4_ba bank选择
.c0_ddr4_cke (ddr4_cke ),// output wire [0 : 0] c0_ddr4_cke 时钟使能信号
.c0_ddr4_cs_n (ddr4_cs_n ),// output wire [0 : 0] c0_ddr4_cs_n 片选 ,高电平时所有命令无效
.c0_ddr4_dm_dbi_n (ddr4_dm_dbi_n ),// inout wire [1 : 0] c0_ddr4_dm_dbi_n 数据掩码和数据总线反转
.c0_ddr4_dq (ddr4_dq ),// inout wire [15 : 0] c0_ddr4_dq 16位数据总线,DDR大小128M * 16bits = 2Gb
.c0_ddr4_dqs_c (ddr4_dqs_c ),// inout wire [1 : 0] c0_ddr4_dqs_c 命令信号数据总线的中心对齐时钟
.c0_ddr4_dqs_t (ddr4_dqs_t ),// inout wire [1 : 0] c0_ddr4_dqs_t 数据信号数据总线的中心对齐时钟
.c0_ddr4_odt (ddr4_odt ),// output wire [0 : 0] c0_ddr4_odt 输出电平完整性
.c0_ddr4_bg (ddr4_bg ),// output wire [0 : 0] c0_ddr4_bg 存储体组输入
.c0_ddr4_reset_n (ddr4_reset_n ),// output wire c0_ddr4_reset_n 复位
.c0_ddr4_act_n (ddr4_act_n ),// output wire c0_ddr4_act_n 高电平时we_n/A14,cas/A15,ras/A16为地址
.c0_ddr4_ck_c (ddr4_ck_c ),// output wire [0 : 0] c0_ddr4_ck_c 反向时钟
.c0_ddr4_ck_t (ddr4_ck_t ),// output wire [0 : 0] c0_ddr4_ck_t 正常时钟
.c0_ddr4_ui_clk (ui_clk ),// output wire c0_ddr4_ui_clk 用户接口时钟,必须是1/2或1/4的DDR时钟频率,参考时钟频率
.c0_ddr4_ui_clk_sync_rst (ui_clk_sync_rst ),// output wire c0_ddr4_ui_clk_sync_rst 高电平有效的用户接口复位信号
.c0_ddr4_app_en (app_en ),// input wire c0_ddr4_app_en 高电平有效的选通信号,使能APP的地址和命令等
.c0_ddr4_app_hi_pri (1'b0 ),// input wire c0_ddr4_app_hi_pri 输入保留强制0
.c0_ddr4_app_wdf_end (app_wdf_end ),// input wire c0_ddr4_app_wdf_end 当前时钟的写数据为最后写命令的最后一笔写数据
.c0_ddr4_app_wdf_wren (app_wdf_wren ),// input wire c0_ddr4_app_wdf_wren 写使能
.c0_ddr4_app_rd_data_end (app_rd_data_end ),// output wire c0_ddr4_app_rd_data_end 当前时钟的数据为最后一笔数据,只有在app_rd_data_valid为高电平时才有效
.c0_ddr4_app_rd_data_valid (app_rd_data_valid ),// output wire c0_ddr4_app_rd_data_valid 高电平指示app_rd_data有效
.c0_ddr4_app_rdy (app_rdy ),// output wire c0_ddr4_app_rdy 指示当前用户接口(UI)准备好接收新命令,如果在app_en使能后该信号撤消,命令及地址要重新发送
.c0_ddr4_app_wdf_rdy (app_wdf_rdy ),// output wire c0_ddr4_app_wdf_rdy 指示写数据缓冲区FIFO可以接收新数据,数据在app_wdf_rdy=1'b1与app_wdf_wren=1'b1都有效时被接受写
.c0_ddr4_app_addr (app_addr ),// input wire [27 : 0] c0_ddr4_app_addr 当前请求地址,[ROW:15+BANK GROUP:1+BANK:2+COLUMN:10]
.c0_ddr4_app_cmd (app_cmd ),// input wire [2 : 0] c0_ddr4_app_cmd 当前请求命令,000-写,001-读
.c0_ddr4_app_wdf_data (app_wdf_data ),// input wire [127 : 0] c0_ddr4_app_wdf_data 写命令之后跟的写数据
.c0_ddr4_app_wdf_mask (app_wdf_mask ),// input wire [15 : 0] c0_ddr4_app_wdf_mask 写数据掩码,固定全 0 表示不使用
.c0_ddr4_app_rd_data (app_rd_data ),// output wire [127 : 0] c0_ddr4_app_rd_data 128位读数据
.addn_ui_clkout1 (addn_ui_clkout1 ),// output wire addn_ui_clkout1
.sys_rst (!sys_rst_n )// input wire sys_rst 输出时钟200M
);
endmodule
代码加入了ila在线调试,因为DDR4仿真只能写仿真不能读仿真,需要DDR4的模拟器才可以仿真,在我博客里有提到过。
ddr3仿真初始化失败,DDR4仿真没有效果_兵棒的博客-CSDN博客
tb代码就不写了,就是一个时钟和复位,自己写。
四、仿真
1.写数据用户端仿真
可以看到app_en等信号的跳动是按照我上述所说的。
然后再看地址的跳动和数据,地址时十六进制显示的一次刚好跳动了8位,数据也是跟着地址按时跳动。
2.读数据用户端仿真
看各个信号刚好是我上述所说的一样。地址也是按照8位跳动。
数据的出现也时刚好在app_rd_data_valid信号来的时候。
3.写ddr端仿真
看到这样的跳动,不要慌不要以为是错的其实是正确的,再仔细看一下,上面讲的那些信号都是正常的,所以没必要惊慌,只是ddr端的输入输出跟你想的不一样而已。
看到c0_ddr4_act_n信号,当它拉低的时候就是在第一期学习的时候讲到的激活,这里的激活就是线选BG位置再选BA位置再选择ROW位置就可以了,就相当于激活了DDR4中的一行地址,只要更改列的地址就可以运行存储运行。
上方两个箭头就是列地址的变换,为什么是10000开始的,原因就是列地址我们只有10位,位地址线是有17位,最后一位置1表示行不选中,因为我们已经激活这一行了不需要去选中,然后第16位和第十五位分别是列选中、写使能打开,记住它们是选通信号所以低电平有效,后面的第十四位到第十一位不用管直接位零,因此就是10000地址开始存储。
下面的箭头就是数据的跳变, 那我们就放大看一下,传输是有10位的传输,第一位和最后一位是检错,就相当于uart的起始位和停止为一样(不用去管他),因为我写入的第一个数据 是128’h0,所以才显示全部是0。可以看到下面那个,那是其中的一个数据把它拿出来看更形象一些。
4.读ddr端仿真
读端的地址变化也是一样,都没有任何错误。读数据也是正常连接。
要注意的就是有效信号的产生才可以表示此时数据正常读出
看到两根线之后就是很长的数据读出了,你也可以数一数延时是否与你的ddr mig IP核里的CL是否一致。
那么DDR4的学习到此就结束了,如果有问题的,评论区见。