本期将接收如何驱动DDR3存储器,当然不会像SDRAM那样,自己手写驱动;而是借助Vivado提供的MIG IP来完成这项工作。但是建议在学习DDR3之前,可以学习一下且写一下SDRAM的驱动,因为它们的涉及到的存储原理和框架一样,只不过DDR3在其基础上增加了一些功能和特性而变得复杂了起来,学会了SDRAM可以约等于学会了DDR3,是不是很nice。
IP核的创建就不作过多的介绍了,点点点就可以,在关键的位置注意一下就可以了(与硬件电路是否一致)。
MIG IP核的端口信号有很多,但也不是特别多,端口信号的例化如下(Vivado版本为2022),信号的定义可以看注释。
mig_ddr3 mig_ddr3_hp(
//与DDR3 硬件相连接的IO,是不是和SDRAM一样?
.ddr3_dq ( ddr3_dq ),
.ddr3_dqs_n ( ddr3_dqs_n ),
.ddr3_dqs_p ( ddr3_dqs_p ),
.ddr3_addr ( ddr3_addr ),
.ddr3_ba ( ddr3_ba ),
.ddr3_ras_n ( ddr3_ras_n ),
.ddr3_cas_n ( ddr3_cas_n ),
.ddr3_we_n ( ddr3_we_n ),
.ddr3_reset_n ( ddr3_reset_n ),
.ddr3_ck_p ( ddr3_ck_p ),
.ddr3_ck_n ( ddr3_ck_n ),
.ddr3_cke ( ddr3_cke ),
.ddr3_dm ( ddr3_dm ),
.ddr3_odt ( ddr3_odt ),
// Inputs
// Single-ended system clock
//输入给MIG的时钟与复位,时钟速率和复位的有效电平在配置IP核的时候,自己给定
.sys_clk_i ( mig_ddr3_clk_i ),
.sys_rst ( sys_rst_n ),
// user interface signals
//操作DDR3的地址
.app_addr ( app_addr ), //地址为 {bank,row,col}(配置不一样顺序可能也不一样)
//命令是读还是写
.app_cmd ( app_cmd ),
//命令使能,当前命令有效
.app_en ( app_en ),
//写DDR3相关的信号
.app_wdf_data ( mig_ddr3_write_data_i ), //数据位宽为128bit
.app_wdf_end ( app_wdf_end ),
.app_wdf_mask ( 'd0 ),
.app_wdf_wren ( app_wdf_wren ),
//读DDR3相关的信号
.app_rd_data ( mig_ddr3_read_data_o ), //数据位宽为128bit
.app_rd_data_end ( ),
.app_rd_data_valid ( app_rd_data_valid ),
//MIG 准备就绪信号输出
.app_rdy ( app_rdy ),
.app_wdf_rdy ( ),
//一般不需要关心
.app_sr_req ( 1'b0 ),
.app_ref_req ( 1'b0 ),
.app_zq_req ( 1'b0 ),
.app_sr_active (),
.app_ref_ack (),
.app_zq_ack (),
//用户参考时钟,所有的读写操作都是以这个时钟为基准的.应该为100M,复位为高电平有效
.ui_clk ( ui_clk ),
.ui_clk_sync_rst ( ui_clk_sync_rst ),
//MIG 初始化DDR3完成信号(拉高表示完成)和 设备温度
.init_calib_complete ( init_calib_complete ),
.device_temp ( )
);
简单分下来,需要用户操作的信号是非常少的,所以这个IP核使用起来还是非常简单的。
等init_calib_complete信号拉高之后,就可以进行读写操作了。
写时序图如下(数据发送框图只截取了最简单的一直显示),写时序是在一个时钟周期内完成的,在app_en为高电平的时候,只需要将要写的地址和数据和写命令给出即可(app_wdf_end和app_wdf_wren同步app_en拉高)。同时要注意app_rdy是否为高电平,只有app_en和app_rdy同时为高的时候,当前的命令才有效。
读时序图如下,app_en和app_rdy同时为高的时候,app_cmd为READ的时候,读有效,若干个时钟周期后,数据读出,伴随app_rd_data_valid信号拉高,这里是不是和读SDRAM的潜伏期类似,同一个概念?
弄懂这两个时序图就可以轻轻松松操作DDR3啦。
基于MIG IP,我们需要将其封装一下,让不了解MIG 时序的朋友也可以轻松的使用;同时封装的接口时序也是大家更为理解的。
封装后的写操作,用户给个请求信号和写入数据的长度以及写入数据的基地址后,在mig_ddr3_write_update_o拉高后,依次更新写入的数据,当写入数据的个数达到设置的长度后,mig_ddr3_write_ack_o会拉高,表示当前写操作完成。
//user write interface
input mig_ddr3_write_req_i , //写请求
input[5:0] mig_ddr3_write_len_i , //写入数据的长度
input[25:0] mig_ddr3_write_addr_i , //写入的地址
input[127:0] mig_ddr3_write_data_i , //写入的数据
output mig_ddr3_write_update_o , //数据更新
output mig_ddr3_write_ack_o , //数据写入完成
封装后的读操作,同写封装一样,在mig_ddr3_read_valid_o为高的时候,用户接收mig_ddr3_read_data_o读出的数据,当读出的数据达到设置的长度的时候,mig_ddr3_read_ack_o会拉高,表示当前读操作完成。
//user read interface
input mig_ddr3_read_req_i , //读请求
input[5:0] mig_ddr3_read_len_i , //读出数据的长度
input[25:0] mig_ddr3_read_addr_i , //读出的地址
output[127:0] mig_ddr3_read_data_o , //读出的数据
output mig_ddr3_read_valid_o , //读出数据有效
output mig_ddr3_read_ack_o , //读出数据完成
Vivado非常nice,直接提供了一个MIG IP仿真的例程,右键MIG IP选择打开历程即可。就可以直接仿真了,仿真的时候会输出一些操作DDR3的信息(仿真过SDRAM的朋友一定非常熟悉),打开这个工程的目前不是为了仿真,而是为了得到它里面的仿真模型。
这个路径下的这个两个文件就是其提供的仿真文件,将其添加到我们自己的工程里面,这里我们就得到了DDR3 的仿真模型,弄个SDRAM的朋友,肯定也会对仿真文件非熟悉。
本设计将使用DDR3来存储一帧帧连续的图片数据。读写时钟可能不一样(以及MIG IP输出的用户时钟),需要设计到跨时钟域处理,这里在读写的中间使用一个FIFO来缓存,根据FIFO里面的数据大于或小于设置的数据大小时,来对DDR3 进行写入和读出的操作;这部分的操作和封装SDRAM基本一样。
另外读写的时候,不会从DDR3内部从头读到尾,而是根据缓存图片大小以及帧数来开辟出对应的地址范围,来进行循环读写。
整个框图如下
欢迎关注 FPGA之旅 回复 FPGA驱动DDR3 获取驱动代码。
qq交流群: 649098696