FPGA通过MIG IP读写DDR3

一. 简介

本期将接收如何驱动DDR3存储器,当然不会像SDRAM那样,自己手写驱动;而是借助Vivado提供的MIG IP来完成这项工作。但是建议在学习DDR3之前,可以学习一下且写一下SDRAM的驱动,因为它们的涉及到的存储原理和框架一样,只不过DDR3在其基础上增加了一些功能和特性而变得复杂了起来,学会了SDRAM可以约等于学会了DDR3,是不是很nice。

二. MIG IP介绍

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同时为高的时候,当前的命令才有效。
FPGA通过MIG IP读写DDR3_第1张图片

读时序图如下,app_en和app_rdy同时为高的时候,app_cmd为READ的时候,读有效,若干个时钟周期后,数据读出,伴随app_rd_data_valid信号拉高,这里是不是和读SDRAM的潜伏期类似,同一个概念?

FPGA通过MIG IP读写DDR3_第2张图片

弄懂这两个时序图就可以轻轻松松操作DDR3啦。

三. 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的朋友一定非常熟悉),打开这个工程的目前不是为了仿真,而是为了得到它里面的仿真模型。

FPGA通过MIG IP读写DDR3_第3张图片

这个路径下的这个两个文件就是其提供的仿真文件,将其添加到我们自己的工程里面,这里我们就得到了DDR3 的仿真模型,弄个SDRAM的朋友,肯定也会对仿真文件非熟悉。

请添加图片描述

五. 自定义封装

本设计将使用DDR3来存储一帧帧连续的图片数据。读写时钟可能不一样(以及MIG IP输出的用户时钟),需要设计到跨时钟域处理,这里在读写的中间使用一个FIFO来缓存,根据FIFO里面的数据大于或小于设置的数据大小时,来对DDR3 进行写入和读出的操作;这部分的操作和封装SDRAM基本一样。

另外读写的时候,不会从DDR3内部从头读到尾,而是根据缓存图片大小以及帧数来开辟出对应的地址范围,来进行循环读写。

整个框图如下

FPGA通过MIG IP读写DDR3_第4张图片
已上板测试

六. 踩坑

  1. PLL时钟输出延时问题。在仿真的时候,输出时钟会滞后输入时钟许多个周期,因此复位时间要足够长。

请添加图片描述

  1. MIG IP接口,在例化IP核的时候,所有的输入信号端口均需要给,不可悬空,否则综合会报错。
  2. DDR3初始化完成,MIG IP核初始化DDR3的时间比较长,大概在106us的时候(时间可能根据配置IP不同以及VIvado版本不一样而不同,也有可能在60us),init_calib_complete才会拉高,所以说在不确定初始化时间的时候,最好跑一下例程,确定一下大概在多少us之后init_calib_complete才会拉高。

欢迎关注 FPGA之旅 回复 FPGA驱动DDR3 获取驱动代码。
qq交流群: 649098696

你可能感兴趣的:(FPGA,fpga开发,DDR3,MIG)