近期学习使用Verilog编写DDR3接口读写测试,在编写过程中遇到许多问题,最终功夫不负有心人,实现了DDR3数据写入和数据读取功能。同时在问题排查过程中,也学习到了很多新的东西。
现将我实现DDR3读写测试功能过程与大家一起分享,在此感谢我的朋友们对我的指点和帮助。在本例中只讲解如何添加IP核,实现DDR3数据的读写功能。因作者水平有限,文档中难免会有疏漏之处,欢迎读者批评指正。
目录
1:使用器件
2:开发环境
3:IP核添加
4:实例工程生成
5:实例工程讲解
6:参考代码
7:仿真测试
a):FPGA器件型号:xc7vx485tffg1761-2;
b):DDR3器件型号:MT41K256M16XX-125;
a):开发环境:Vivado 2017.2
b):开发语言:Verilog
在新建工程中添加IP核,添加步骤如下所示:
a):点击project manager 菜单下IP Catalog选项,打开IP界面;
b):在IP核界面搜索栏输入mig ,会自动出现Memory Interface Generator选项,双击该选项进入到DDR配置界面;
c):等待Vivado运行完成,会出现Memory Interface Generator配置界面,检查器件型号等信息无误后,点击next,(左下角选项 User Guide可以点击下载官方资料文件);
d):选择Create Design,Component Name选项可以修改名称,Number of Controllers选项可以选择DDR的片数,本例中只选择一片,配置完成后点击next;
e):此处不选择其他型号,点击next;
f): 本例器件类型为DDR3,选择DDR3 SDRAM,点击next;
g): clock period选项选择1250ps,800MHz时钟,Memory Type选项中选择Components(贴片式组件),Memory Part选择:MT41K256M16XX-125,点击Create Custom Part,在出现的对话框Enter New Memory Part Name选项中输入MT41K512M16XX-125,Row Address下拉选项中选择16位宽(这里设置按照我的硬件资源环境设置的,可以根据个人硬件环境进行调整,在后面代码中修改位宽即可),设置完成后点击next;
h):在Input Clock Period选项中选择5000ps,200MHz,完成后点击next;
i):System Clock选项选择No Buffer,Reference Clock选择Use System Clock,设置完成后点击next;
j):默认设置,点击next;
k):选择New Deign,本例不提供引脚配置文件,点击next;
l):采用默认分配,点击next;
m):采用默认分配,点击next;
n):在界面中检查配置选项是否存在错误,确认无误后点击next;
o):点击选择Accept,点击next,接下来几个页面都可以直接点击next;
p):等待vivado运行完成,出现生成界面,点击Generate;
q):等待vivado完成IP核生成;
r):继续等待vivado运行完成,运行完成后IP核就添加完成;
a):添加IP核完成后,在sources管理界面下方选择IP sources界面,选中IP核。
b):点击右键,选择Open IP Example Design打开实例工程;
c):选择实例工程保存位置,点击ok后vivado会自动运行生成一个实例工程文件;
d):运行完成后打开工程文件,生成的实例工程文件是可以直接进行仿真的,点击project manager 管理栏中的run simulation即可进行仿真。我们的重点不是查看官方的仿真实例,我们要自己编写一个读写实例,现在可以关掉IP核的工程文件,重点放在实例工程上;
a):双击打开example_top文件,里面有很多描述和参数定义,输入输出端口定义,这些都不用修改,了解一下就好。往下翻到第412行Application interface ports,重点关注第413行到423行,这些端口都是数据的读写需要用到的端口 。可以在文件中查看端口的定义,有助于后面写代码端口的定义。第450行以后的代码都可以删掉,不用生成的实例文件,注意不要把endmodule删除了;
b):在工程中添加一个新的文件,用于编写我们自己的DDR3读写模块;
c):在编写代码前我们还是要分析一下DDR3的读写时序要求,下图为写数据时的时序要求,内容来源于前面Use rGuide下载问文档里面。
d):从上面写数据时序图可以看出,写数据需要10个输入输出信号来实现。我们来一一讲解信号的作用:
clk:系统时钟;
app_cmd:读写操作命令,3'b000为写操作,3'b001为读操作;
app_addr:地址线,写数据地址和读数据地址都走这条线;
app_en:地址线有效使能信号,高有效;
app_rdy:DDR3给你的信号,为高时表明UI已准备好接受命令;
app_wdf_mask:app_wdf_data 的掩码;
app_wdf_rdy:DDR3给你的信号,为高时表明写入数据FIFO已准备好接收数据;
app_wdf_data:写入的数据;
app_wdf_wren:数据有效使能信号,高有效;
app_wdf_end:高电平有效输入,表明当前时钟周期是app_wdf_data 上输入数据的最后一个周期,一般情况下可让app_wdf_end == app_wdf_wren;
e):对于这10个信号又可分为2个系统,地址系统和数据系统,clk为系统时钟。
地址系统:app_cmd,app_addr,app_en,app_rdy;
数据系统:app_wdf_mask,app_wdf_rdy,app_wdf_data,app_wdf_wren,app_wdf_end;
地址系统只关联地址有关的信号,数据系统就关联数据有关的信号,两者之间不应纠缠在一起,不然容易造成一些bug出现。从写数据时序图还可以看出,地址系统与数据系统之间的时序关系有三种:
第一种:地址与数据严格对其;
第二种:数据比地址先给一个周期;
第三种:数据比地址最多延迟2个周期;
地址系统有关信号严格按照时序要求对齐,数据系统有关信号严格按照时序要求对齐,地址系统与数据系统之间选择哪种关系,根据个人需求进行选择。
f):地址系统信号之间的关系:
当DDR3发出app_rdy信号时表明已经准备好接收命令,可以发送app_cmd命令和app_addr地址,同时标志地址有效app_en信号也要同时发出,只有app_en为高时写入的地址才是有效的。
数据系统信号之间关系:
当DDR3发出app_wdf_rdy信号时,表明写入数据FIFO准备好接收数据,这时就要将要写入的数据发送给app_wdf_data,写入多少个地址就只能写入多少个数据,多给的数据会丢失。在写入app_wdf_data同时拉高app_wdf_wren信号,只有app_wdf_wren为高时写入的数据才是有效的。前面文章中讲过app_wdf_end == app_wdf_wren,所以可以将app_wdf_end进行app_wdf_wren同样的处理。app_wdf_mask信号为app_wdf_data数据的掩码,app_wdf_mask为1的位对应的数据将被屏蔽掉,本例中不使用掩码,可以赋值为全0(要注意app_wdf_mask信号的位宽);
g):读数据时序要求,下图为读数据时序图。
通过上图可以看出,读数据也需要用到地址系统,但是读数据只需要你给出地址和读出的数据个数就可以,数据个数可以理解为给了多少个地址,这个可以自由决定。发送地址后不是马上读出数据,从上图也可以看出发送完addr0之后时钟信号用双波浪符号表示中间省略多少个周期,毕竟DDR3也需要反应时间。DDR3准备好数据后,就会通过app_rd_data端口发出数据,同时给出app_rd_data_valid信号,只有 app_rd_data_valid为高时读出的数据才是有效数据;
h):读写时序要求就分析这么多,分析的比较简单,重在理解。需要详细分析,请在CSND里面搜索其他作者写的时序分析文章,这里就不在过多赘述。到此IP核、DDR3实例工程、时序分析都已完成,剩下的就是编写代码实现功能了,这个就交给天赋异禀的读者了,后面我会提供我的实例代码,供读者参考。
以下代码可实现DDR3读写数据功能。
`timescale 1ns / 1ps
module ddr_app_module (
input i_sys_clk, //时钟
input i_sys_rst, //复位
input init_calib_complete, //DDR3初始完成
input app_rdy, //app_rdy
output reg [ 2 : 0 ] app_cmd, //读写命令
output reg app_en, //地址en
output reg [ 29 : 0 ] app_addr, //操作地址
input app_wdf_rdy, //app_wdf_rdy
output reg app_wdf_end, //app_wdf_end
output reg app_wdf_wren, //写数据有效en
output reg [ 127 : 0 ] app_wdf_data, //写数据
input [ 127 : 0 ] app_rd_data, //读数据
input app_rd_data_valid //读数据有效
);
localparam TEST_LEN = 8'd99;
reg [ 29 : 0 ] write_addr;
reg [ 29 : 0 ] read_addr;
reg app_write_req;
reg app_write_req_r0;
reg app_write_req_r1;
reg write_data_en;
reg app_read_req;
reg app_read_req_r0;
reg app_read_req_r1;
reg read_data_en;
reg [ 7 : 0 ] write_addr_cnt;
reg [ 7 : 0 ] write_data_cnt;
reg [ 7 : 0 ] read_addr_cnt;
reg [ 7 : 0 ] read_data_cnt;
always @ ( posedge i_sys_clk ) begin //写数据请求
if ( init_calib_complete == 1'b0 ) begin
app_write_req <= 1'b0;
app_write_req_r0 <= 1'b0;
app_write_req_r1 <= 1'b0;
end else begin
app_write_req_r0 <= init_calib_complete;
app_write_req_r1 <= app_write_req_r0;
app_write_req <= app_write_req_r0 & ( ~app_write_req_r1 );
end
end
always @ ( posedge i_sys_clk ) begin //写数据en
if ( init_calib_complete == 1'b0 ) begin
write_data_en <= 1'b0;
end else begin
if ( app_write_req == 1'b1 ) begin
write_data_en <= 1'b1;
end else begin
if ( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
write_data_en <= 1'b0;
end else begin
write_data_en <= write_data_en;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //读数据请求
if ( init_calib_complete == 1'b0 ) begin
app_read_req <= 1'b0;
app_read_req_r0 <= 1'b0;
app_read_req_r1 <= 1'b0;
end else begin
app_read_req_r0 <= write_data_en;
app_read_req_r1 <= app_read_req_r0;
app_read_req <= app_read_req_r1 & ( ~app_read_req_r0 );
end
end
always @ ( posedge i_sys_clk ) begin //读数据en
if ( init_calib_complete == 1'b0 ) begin
read_data_en <= 1'b0;
end else begin
if ( app_read_req == 1'b1 ) begin
read_data_en <= 1'b1;
end else begin
if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
read_data_en <= 1'b0;
end else begin
read_data_en <= read_data_en;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //app_en信号产生
if ( init_calib_complete == 1'b0 ) begin
app_en <= 1'b0;
end else begin
if ( app_write_req == 1'b1 || app_read_req == 1'b1 ) begin
app_en <= 1'b1;
end else begin
if ( ( write_addr_cnt == TEST_LEN || read_addr_cnt == TEST_LEN ) && app_rdy == 1'b1 ) begin
app_en <= 1'b0;
end else begin
app_en <= app_en;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //app_amd命令产生
if ( init_calib_complete == 1'b0 ) begin
app_cmd <= 3'b000;
end else begin
if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
app_cmd <= 3'b000;
end else begin
if ( app_read_req == 1'b1 ) begin
app_cmd <= 3'b001;
end else begin
app_cmd <= app_cmd;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //写数据地址
if ( init_calib_complete == 1'b0 ) begin
write_addr <= 30'h0;
end else begin
if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
write_addr <= write_addr + 4'h8;
end else begin
if ( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
write_addr <= write_addr + 4'h8;
end else begin
write_addr <= write_addr;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //读数据地址
if ( init_calib_complete == 1'b0 ) begin
read_addr <= 30'h0;
end else begin
if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
read_addr <= read_addr + 4'h8;
end else begin
if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
read_addr <= read_addr + 4'h8;
end else begin
read_addr <= read_addr;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //app_addr数据地址
if ( init_calib_complete == 1'b0 ) begin
app_addr <= 30'h0;
end else begin
if ( app_write_req == 1'b1 ) begin
app_addr <= write_addr;
end else begin
if ( app_read_req == 1'b1 ) begin
app_addr <= read_addr;
end else begin
if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
app_addr <= write_addr + 4'h8;
end else begin
if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
app_addr <= read_addr + 4'h8;
end else begin
app_addr <= app_addr;
end
end
end
end
end
end
always @ ( posedge i_sys_clk ) begin //写地址计数器
if ( init_calib_complete == 1'b0 ) begin
write_addr_cnt <= 8'd0;
end else begin
if( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
write_addr_cnt <= 8'd0;
end else begin
if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
write_addr_cnt <= write_addr_cnt + 1'b1;
end else begin
write_addr_cnt <= write_addr_cnt;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //读地址计数器
if ( init_calib_complete == 1'b0 ) begin
read_addr_cnt <= 8'd0;
end else begin
if( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
read_addr_cnt <= 8'd0;
end else begin
if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
read_addr_cnt <= read_addr_cnt + 1'b1;
end else begin
read_addr_cnt <= read_addr_cnt;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //写数据有效
if ( init_calib_complete == 1'b0 ) begin
app_wdf_wren <= 1'b0;
end else begin
if ( write_addr_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
app_wdf_wren <= 1'b0;
end else begin
if ( app_write_req == 1'b1 ) begin
app_wdf_wren <= 1'b1;
end else begin
app_wdf_wren <= app_wdf_wren;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //app_wdf_end == app_wdf_wren
if ( init_calib_complete == 1'b0 ) begin
app_wdf_end <= 1'b0;
end else begin
if ( write_addr_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
app_wdf_end <= 1'b0;
end else begin
if ( app_write_req == 1'b1 ) begin
app_wdf_end <= 1'b1;
end else begin
app_wdf_end <= app_wdf_end;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //产生写入的数据
if ( i_sys_rst == 1'b0 ) begin
app_wdf_data <= 128'h0;
end else begin
if ( app_wdf_rdy == 1'b1 && app_wdf_wren == 1'b1 ) begin
app_wdf_data <= app_wdf_data + 1'b1;
end else begin
app_wdf_data <= app_wdf_data;
end
end
end
always @ ( posedge i_sys_clk ) begin //写数据计数器
if ( i_sys_rst == 1'b0 ) begin
write_data_cnt <= 8'd0;
end else begin
if ( write_data_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
write_data_cnt <= 8'd0;
end else begin
if ( app_wdf_wren == 1'b1 && app_wdf_rdy == 1'b1 ) begin
write_data_cnt <= write_data_cnt + 1'b1;
end else begin
write_data_cnt <= write_data_cnt;
end
end
end
end
always @ ( posedge i_sys_clk ) begin //读数据计数器
if ( i_sys_rst == 1'b0 ) begin
read_data_cnt <= 8'd0;
end else begin
if ( read_data_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
read_data_cnt <= 8'd0;
end else begin
if ( app_rd_data_valid == 1'b1 ) begin
read_data_cnt <= read_data_cnt + 1'b1;
end else begin
read_data_cnt <= read_data_cnt;
end
end
end
end
endmodule
本例中测试数据长度为100,写入0~99,读出0~99即为读写数据正确,仿真结果如下,供参考。DDR3数据读写测试讲解到此结束,感谢各位读者阅读,希望对你有所帮助。后附工程文件下载链接,欢迎下载测试。
工程文件下载链接:
https://download.csdn.net/download/ForeveryMissYou/21358108