当前位置:   article > 正文

FPGA通过MIG IP读写DDR3_fpga ddr3读写驱动

fpga 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            (                           )
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

简单分下来,需要用户操作的信号是非常少的,所以这个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啦。

三. 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    ,  //数据写入完成 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

封装后的读操作,同写封装一样,在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     , //读出数据完成
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

四. 仿真

Vivado非常nice,直接提供了一个MIG IP仿真的例程,右键MIG IP选择打开历程即可。就可以直接仿真了,仿真的时候会输出一些操作DDR3的信息(仿真过SDRAM的朋友一定非常熟悉),打开这个工程的目前不是为了仿真,而是为了得到它里面的仿真模型。

请添加图片描述

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

请添加图片描述

五. 自定义封装

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

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

整个框图如下

请添加图片描述
已上板测试

六. 踩坑

  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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/539361
推荐阅读
相关标签
  

闽ICP备14008679号