首页 > 其他分享 >《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​

时间:2022-12-24 11:03:01浏览次数:37  
标签:MPSoc FPGA IP DFZU2EG ddr4 DDR4 output c0 app

DDR4读写测试实验​

DDR4 SDRAM(Double-Data-Rate Fourth Generation Synchronous Dynamic Random Access Memory,简称为DDR4 SDRAM),是一种高速动态随机存取存储器,它属于SDRAM家族的存储器产品,提供了相较于DDR3 SDRAM更高的运行性能与更低的电压。​

本章包括以下几个部分:​

  1. MIG简介​
  2. 实验任务​
  3. 硬件设计​
  4. 程序设计​
  5. 下载验证​


MIG简介

DFZU2EG/4EV MPSoC开发板板载了五片镁光的DDR4颗粒,它们的型号是MT40A256M16,这5片DDR4芯片有4片位于PS端,有1片位于PL端,本节实验使用的是位于PL端的DDR4芯片。下面我们来简单了解一下这款板载的DDR4芯片,DDR4内部结构图如下所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据


图31.1.1 DDR4结构图​

从上图我们可以看到,DFZU2EG/4EV MPSoC开发板板载的这款DDR4芯片的行地址是15bit位宽,列地址是10bit位宽,而整个存储区域分为两个BANK组,每个BANK组又由4个子BANK组成,所以整片DDR4的容量就是2^15*2^10*8*16bit=256M*16bit。DDR4相较于DDR3在指令引脚上也发生了变化,DDR4取消了我们所熟悉的使能WE、行激活RAS和列激活CAS这三个命令引脚,而是将这三个命令引脚和地址线A14、A15以及A16复用了。除此之外在寻址的时候也不再是直接去寻址BANK,而是先寻址BANK组,然后再找到这个BANK组中的某个子BANK。整个Shu据的吞吐是8倍预取,因此Shu据在读写的时候就是16bit*8=128bit的Shu据量进行吞吐(注意虽然是8倍预取,但是每一次IO引脚上的Shu据传输依旧是16bit,因为Shu据线就16根,至于为何可以达到8倍预取和DDR4内部的双沿采样,FIFO缓冲,写Shu据逻辑结构有关)。

关于DDR4更详细的介绍大家可以去参考镁光的官方Shu据手册,这里不再赘述。

由于DDR4的时序非常复杂,如果直接编写DDR4的控制器代码,那么工作量是非常大的,且性能难以得到保证。值得一提的是,Vivado软件自带了DDR4控制器IP核,用户可以直接借助IP核来实现对DDR4的读写操作,从而大大降低了DDR4的开发难度。本次实验将使用Xilinx公司MIG(Memory Interface Generators) IP核来实现DDR4读写测试。​

MIG IP核是Xilinx公司针对DDR存储器开发的IP,里面集成存储器控制模块,实现DDR读写操作的控制流程,下图是MIG IP 核结构框图。MIG IP核对外分出了两组接口,左侧是用户接口,就是用户(FPGA)同MIG 交互的接口,用户只有充分掌握了这些接口才能操作MIG;右侧为DDR物理芯片接口,负责产生具体的操作时序,并直接操作芯片管脚,这一侧用户只负责分配正确的管脚,其他不用关心。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_02


图31.1.2核结构框图​

使用这个IP 核,用户将可以进行DDR4的读写操作,而不必熟悉DDR4具体的读写控制时序,当然用户必须掌握用户接口侧的操作时序,并严格遵照时序来编写代码,这样才能正确实现对DDR4的读写操作。在了解具体时序之前,大家有必要先了解相关的信号定义。下图给出了MIG IP核用户接口的信号及其说明。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_03


图31.1.3用户接口信号定义​

MIG IP核用户侧端口数量共26个,当然用户并不用去关心所有的信号,只需要了解本实验要用到几组重要信号。下面将对这些信号逐一讲解并以表格的形式呈现给大家。为了与官方的文档保持一致,表中标明的信号的方向是以MIG IP核作为参照的,例如表格中的信号方向定义为输出,那么相对于用户端(FPGA)来说实际上是输入。​

表31.1.1核用户接口部分信号定义​

信号名​

方向​

端口说明​

ui_clk​

output​

用户时钟,由MIG IP核输出​

ui_clk_sync_rst​

output​

复位,由MIG IP核提供,高电平有效​

init_calib_complete​

output​

初始化完成,表明DDR4芯片初始化完成,高电平有效,​

app_addr​

[ADDR_WIDTH–1:0]​



input​

用户地址输入,这是MIG IP核根据具体的物理芯片,整合给用户使用的地址空间,这个地址和芯片的地址是对应的,地址的位宽ADDR_WIDTH等于BANK位宽+ ROW位宽 + COL位宽,根据计算公式,本次实验ADDR_WIDTH的值为28。​


app_en​



input​

MIG IP核命令写入使能,高有效。写命令时需要拉高该信号​

app_cmd[2:0]​

input​

读写控制命令​

读:001;写:000。其他值保留。​

app_rdy​


output​

MIG IP核读写命令接收准备完成,高电平有效。​

app_wdf_wren​


input​

MIG IP核数据写使能,高电平有效。写数据时需要拉高该信号。​

app_wdf_rdy​


output​

MIG IP核数据接收准备完成,高电平有效。​

app_wdf_end​

input​

当前时钟是突发写过程的最后一个时钟,高电平有效。​

app_wdf_data​

[APP_DATA_WIDTH-1:0]​


input​

该信号为用户写数据输入,位宽这里设定为128​

app_rd_data [APP_DATA_WIDTH–1:0]​


output​

该信号用于用户读取IP核发来的数据,位宽同样设置为128。​

app_rd_data_valid​


output​

读数据有效信号,高电平有效。​

以上是用户需要用到的信号,其他信号请大家自行了解。​

DDR4的读或者写都包含写命令操作,其中写操作命令(app_cmd)的值等于0,读操作app_cmd的值等于1。首先来看写命令时序,如下图所示。首先检查app_rdy,为高则表明此时IP核命令接收处于准备好状态,可以接收用户命令,在当前时钟拉高app_en,同时发送命令(app_cmd)和地址(app_addr),此时命令和地址被写入。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_04


图31.1.4写命令时序​

下面来看写数据的时序,如下图所示。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_05


图31.1.5非背靠背写时序​

如上图所示,写数据有三种情形均可以正确写入:​

(1)写数据时序和写命令时序发生在同一拍;​

(2)写数据时序比写命令时序提前一拍;​

(3)写数据时序比写命令时序至多延迟晚两拍;​

结合上图,写时序总结如下:首先需要检查app_wdf_rdy,该信号为高表明此时IP核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据(app_wdf_data)。这样加上发起的写命令操作就可以成功向IP核写数据。这里有一个信号app_wdf_mask,它是用来屏蔽写入数据的,该信号为高则屏蔽相应的字节,该信号为0默认不屏蔽任何字节。​

这里需要指出的是,DDR4的读或者写操作都可以分为背靠背和非背靠背两种情形。背靠背,即读或者写每个时钟都连续进行,中间没有间隙;非背靠背写则是非连续的读写。​

对于背靠背写,其实也有三种情形,唯一点不同的是,它没有最大延迟限制,如下图所示。​


《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_06


图31.1.6背靠背写时序​

接着来看读数据,如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_07


图31.1.7读时序​

读时序比较简单,发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此时数据总线上的数据是有效的返回数据。需要注意的是,在发出读命令后,有效读数据要晚若干周期才出现在数据总线上。下面是背靠背读的情况,如下图所示。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_写数据_08


图31.1.8背靠背读时序图​

需要注意的是,在连续读的时候,读到的数据顺序跟请求的命令/地址是相对应的。通常使用DDR4的时候,为了最大限度地提高DDR4效能,充分利用突发写的特点,非背靠背很少用,而更多地采用背靠背操作。本章实验的读写操作就是基于背靠背模式操作的。​

实验任务

本节的实验任务是先向DDR4的存储器地址0至999分别写入数据0~999;写完之后再读取存储器地址0~999中的数据,若读取的值全部正确则LED灯常亮,否则LED灯闪烁。​

硬件设计

本节实验使用的是PL端的DDR4,如果想使用PS端的DDR4,可以参考正点原子的嵌入式Vitis开发指南教程。PL端DDR4的硬件原理图如下图所示。在PCB的设计上,完全遵照Xilinx的DDR4硬件设计规范,严格保证等长设计和阻抗控制,从而保证高速信号的数据传输的可靠性。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_09


图31.3.1 DDR4硬件原理图​

接下来我们来看看本节实验所用到的管脚,管脚分配可以放在工程的XDC文件中,也可以打开io planning窗口在io ports栏中分配管脚。我们本节实验用到的管脚如下表所示(因为管脚太多这里只贴出部分管脚):​

表31.3.1读写测试实验部分管脚分配​

信号名​

方向​

管脚​

端口说明​

电平标准​

sys_clk_p​

input​

AE5​

系统差分输入时钟​

DIFF_HSTL_I_12​

sys_clk_n​

input​

AF5​

系统差分输入时钟​

DIFF_HSTL_I_12​

sys_rst_n​

input​

AH11​

系统复位按键,低电平有效​

LVCMOS33​

led​

output​

AE10​

读写结果指示灯​

LVCMOS33​

c0_ddr4_dq[15]​

inout​

AC4​

DDR4数据​

POD12​

c0_ddr4_dq[14]​

inout​

AC3​

DDR4数据​

POD12​

c0_ddr4_dq[13]​

inout​

AB4​

DDR4数据​

POD12​

c0_ddr4_dq[12]​

inout​

AB3​

DDR4数据​

POD12​

c0_ddr4_dq[11]​

inout​

AB2​

DDR4数据​

POD12​

c0_ddr4_dq[10]​

inout​

AC2​

DDR4数据​

POD12​

c0_ddr4_dq[9]​

inout​

AB1​

DDR4数据​

POD12​

c0_ddr4_dq[8]​

inout​

AC1​

DDR4数据​

POD12​

c0_ddr4_dq[7]​

inout​

AG3​

DDR4数据​

POD12​

c0_ddr4_dq[6]​

inout​

AH3​

DDR4数据​

POD12​

c0_ddr4_dq[5]​

inout​

AE3​

DDR4数据​

POD12​

c0_ddr4_dq[4]​

inout​

AF3​

DDR4数据​

POD12​

c0_ddr4_dq[3]​

inout​

AH2​

DDR4数据​

POD12​

c0_ddr4_dq[2]​

inout​

AH1​

DDR4数据​

POD12​

c0_ddr4_dq[1]​

inout​

AF1​

DDR4数据​

POD12​

c0_ddr4_dq[0]​

inout​

AG1​

DDR4数据​

POD12​

c0_ddr4_dqs_t[1]}​

inout​

AD2​

DDR4数据选取脉冲​

DIFF_ POD12​

c0_ddr4_dqs_c[1]}​

inout​

AD1​

DDR4数据选取脉冲​

DIFF_ POD12​

c0_ddr4_dqs_t[0]}​

inout​

AE2​

DDR4数据选取脉冲​

DIFF_ POD12​

c0_ddr4_dqs_c[0]}​

inout​

AF2​

DDR4数据选取脉冲​

DIFF_ POD12​

c0_ddr4_adr[16]​

output​

AB5​

DDR4地址线​

SSTL12​

c0_ddr4_adr[15]​

output​

AB7​

DDR4地址线​

SSTL12​

c0_ddr4_adr[14]​

output​

AF6​

DDR4地址线​

SSTL12​

c0_ddr4_adr[13]​

output​

AD9​

DDR4地址线​

SSTL12​

c0_ddr4_adr[12]​

output​

AC6​

DDR4地址线​

SSTL12​

c0_ddr4_adr[11]​

output​

AH9​

DDR4地址线​

SSTL12​

c0_ddr4_adr[10]​

output​

AE7​

DDR4地址线​

SSTL12​

c0_ddr4_adr[9]​

output​

AC9​

DDR4地址线​

SSTL12​

c0_ddr4_adr[8]​

output​

AH8​

DDR4地址线​

SSTL12​

c0_ddr4_adr[7]​

output​

AE9​

DDR4地址线​

SSTL12​

c0_ddr4_adr[6]​

output​

AH7​

DDR4地址线​

SSTL12​

c0_ddr4_adr[5]​

output​

AD7​

DDR4地址线​

SSTL12​

c0_ddr4_adr[4]​

output​

AF7​

DDR4地址线​

SSTL12​

c0_ddr4_adr[3]​

output​

AC8​

DDR4地址线​

SSTL12​

c0_ddr4_adr[2]​

output​

AF8​

DDR4地址线​

SSTL12​

c0_ddr4_adr[1]​

output​

AB8​

DDR4地址线​

SSTL12​

c0_ddr4_adr[0]​

output​

AG8​

DDR4地址线​

SSTL12​

c0_ddr4_ba[1]​

output​

AC7​

DDR4 Bank地址​

SSTL12​

c0_ddr4_ba[0]​

output​

AH6​

DDR4 Bank地址​

SSTL12​

c0_ddr4_bg​

output​

AE8​

DDR4 Bank组地址​

SSTL12​

c0_ddr4_reset_n​

output​

AG9​

DDR4 复位信号​

LVCMOS12​

c0_ddr4_cke​

output​

AE4​

DDR4 时钟使能信号​

SSTL12​

c0_ddr4_cs_n​

output​

AB6​

DDR4 片选信号​

SSTL12​

c0_ddr4_dm_dbi_n[1]​

output​

AD5​

DDR4 数据掩码​

POD12​

c0_ddr4_dm_dbi_n[0]​

output​

AG4​

DDR4 数据掩码​

POD12​

c0_ddr4_odt​

output​

E2​

DDR4 终端电阻使能​

SSTL12​

c0_ddr4_ck_t​

output​

AG6​

DDR4差分时钟正​

DIFF_ SSTL12​

c0_ddr4_ck_c​

output​

AG5​

DDR4 差分时钟负​

DIFF_ SSTL12​


如上表所示,本次实验用到了五种电平,分别是LVCMOS、SSTL、POD12、DIFF_SSTL以及DIFF_ POD12,下面对于这五种电平我们做一个简单的介绍。​

LVCMOS:全称Low Voltage Complementary Metal Oxide Semiconductor,低压互补金属氧化物半导体;​

SSTL:全称Stub Series Terminated Logic,短截线串联端接逻辑;​

DIFF_SSTL:全称Difference Stub Series Terminated Logic,差分短截线串联端接逻辑;​

POD12:POD12是由VDDQ端提供的电压,一般用于DDR4,当POD12电压驱动端的上拉电路导通,电路处于高电平时,回路上没有电流流过,这样的设计较少了功耗;​

DIFF_ POD12:与POD12一样,只不过是差分电压。​

除此之外,LVCMOS的特点是噪声容限大,速度较SSTL慢;SSTL速度快,通常要匹配合适的端接电阻,常用于高速内存接口如DDR4;DIFF_SSTL则是带有差分功能的SSTL。POD和SSTL的最大区别在于接收端的终端电压(POD为VDDQ,SSTL为VDDQ/2)。POD可以降低寄生引脚电容和I/O终端功耗,并且即使在VDD电压降低的情况下也能稳定工作。​

DDR4的管脚使用的是SSTL、POD12、DIFF_SSTL以及DIFF_ POD12,这里要注意一下DDR4复位信号c0_DDR4_reset_n,它并没有使用SSTL,原因是SSTL的噪声容限不够大,容易受干扰造成误复位。​

本实验对应的XDC约束语句如下所示。注意,在这里仅给出了时钟、复位、和LED读写指示信号的管脚约束。DDR4部分的管脚约束在文档中不再给出,大家可以从提供的例程中查看。​

set_property -dict {PACKAGE_PIN AE5 IOSTANDARD DIFF_HSTL_I_12} [get_ports c0_sys_clk_p]​
set_property -dict {PACKAGE_PIN AF5 IOSTANDARD DIFF_HSTL_I_12} [get_ports c0_sys_clk_n]​
set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]​
set_property -dict {PACKAGE_PIN AE10 IOSTANDARD LVCMOS33} [get_ports led]

程序设计

根据实验任务,可以大致规划出系统的控制流程:首先FPGA通过调用MIG IP核向DDR4芯片写入数据,写完之后通过MIG IP核从DDR4芯片读出所写入的数据,并判断读出的数据与写入的数据是否相同,如果相同则LED灯常亮,否则LED灯闪烁。由此画出系统的功能框图如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_10


图31.4.1 DDR4读写测试实验系统框图​

由系统总体框图可知,FPGA顶层模块例化了以下两个模块,分别是读写模块(ddr4_rw)和MIG IP核模块。​

各模块端口及信号连接如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_11


图31.4.2顶层模块原理图​

其中DDR4读写模块的作用是生成DDR4的读写数据和MIG IP核的驱动时序,并且在DDR4模块里比较写入DDR4的数据与读出的数据是否一致,如果读写一致,说明DDR4读写正确,此时LED指示灯常亮;否则说明DDR4读写失败,LED指示灯处于闪烁状态。而MIG IP核内部包含了整个DDR4的驱动,一方面它负责驱动板载的DDR4芯片,另一方面和用户模块进行数据交互,在本节实验中用户模块就是DDR4读写模块,MIG IP核负责和DDR4读写模块进行数据交互。​

需要注意的是,本节实验的系统时钟是一对差分时钟,频率为100Mhz(c0_sys_clk_p,c0_sys_clk_n),这对差分时钟直接连接到MIG IP核,由MIG IP核生成DDR4的驱动时钟(c0_DDR4_ck_t,c0_DDR4_ck_c),输出给外部DDR4(MT40A256M16GE-083E)芯片使用。除此之外,FPGA内部的用户模块时钟也是由MIG IP核提供,本节实验的用户模块时钟是c0_DDR4_ui_clk,作为DDR4读写模块的操作时钟。​

接下来我们介绍下如何对MIG IP核进行配置。首先在Vivado环境里新建一个工程,本实验取名为ddr4_rw_top。再点击Project Manager界面下的IP Catalog,打开IP Catalog界面。如下图所示。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_12


图31.4.3IP Catalog”按钮​

在搜索栏中输入MIG,此时出现MIG IP核,我们找到DDR4 SDRAM(MIG),如下图所示。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_写数据_13


图31.4.4搜索栏中输入关键字​

双击打开DDR4 SDRAM(MIG),出现如下图所示界面。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_写数据_14


《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_15


图31.4.5 IP核配置​

上图所示的是MIG IP 核的Basic配置界面,这里我们对几个重要的配置信息作出说明:​

Component Name:MIG IP核的命名,可以保持默认,也可以自己取一个名字。​

Mode and Interface:控制器的模式和接口选项,可以选择AXI4接口或者普通模式,并生成对应的PHY组件(详情请参考官方文档pg150)。​

Memory Device Interface Speed:板载DDR4芯片的IO总线时钟频率,这里可以最大支持938ps(1066MHz),IO总线时钟频率等于2倍DDR4芯片核心频率(533 MHz)。​

PHY to controller clock frequency ratio:用户时钟分频系数,这里只能选择4比1,因此本节实验的用户时钟频率等于DDR4芯片核心频率的四分之一,即133.25MHz。​

Reference input Clock Speed:参考时钟,本节实验选择10006ps(参考时钟频率和系统时钟频率保持一致即100MHz)。​

Controller Options:控制器配置栏,如果使用MIG IP核内部默认的DDR4芯片,则只需要在Memory Part栏选中对应的DDR4芯片型号即可,例如我们板载的DDR4芯片型号为MT40A256M16GE-083E(如果多片DDR4联用注意修改数据位宽)。如果使用的DDR4芯片型号不在MIG IP和的默认配置中就需要手动定义DDR4芯片的参数文件,这个时候就需要使能定制型号(EnableCustom Parts Data File),然后加载配置文件(Custom Parts Data File)。​

Memory Options:配置突发长度和CAS延迟的,这里保持默认即可(如果需要修改请参考DDR4芯片数据手册)。​

接下来我们选择MIG IP核配置界面的Advanced Clocking界面,如下所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_16


图31.4.6 IP核配置界面​

Advanced Clocking界面只需要关注一下Additional Clock Outputs配置,这里可以额外输出四路时钟,如果有需求这里可以生成,我们本节实验不需要用到额外时钟,所以全部“None”即可。​

接下来切换至Advanced Options界面,如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_17


图31.4.7 IP核配置界面​

Advanced Options界面的配置信息如下:​

Debug Signals for controller:在Xilinx示例设计中,启用此功能会把状态信号连接到ChipScope ILA核中。​

Microblaze MCS ECC option:Microblaze的配置选项,选中它Microblaze的MCS ECC尺寸会增加。​

Simulation Options:此选项仅对仿真有效。在选择BFM选项时,为XiPhy库使用行为模型,以加快模拟运行时间。选择Unisim则对XiPhy原语使用Unisim库。​

Example Design Options:示例工程仿真文件的选择。​

Advance Memory Options:提高运行性能的选项,可以选择自刷新和校准功能,并将这些信息保存在XSDB BRAM中,也可以把XSDB BRAM中的信息存储在外部存储器中。​

Migration Options:引脚兼容选项,如果想兼容UlitraScale和UltraScale+ fpga,就把这个选项选中。​

最后我们再来看看IO Planning and Design Checklist界面,如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_18


图31.4.8 IP核配置界面​

IO Planning and Design Checklist界面提示我们DDR4 IO引脚分配的方式发生改变,不再像之前DDR3那样,需要在MIG IP核中就分配好管脚,DDR4可以在IO Planning窗口分配管脚(或者直接编写XDC文件)。​

当所有的配置信息全部修改完成后,点击“OK”按钮,弹出的界面如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_19


图31.4.9 生成IP核​

点击Generate按钮生成IP核,IP核生成完成后,开始编写DDR4读写实验的代码。​

顶层模块的代码,如下所示:​

1 module ddr4_rw_top(​
2 output c0_ddr4_act_n ,​
3 output [16:0] c0_ddr4_adr ,​
4 output [1:0] c0_ddr4_ba ,​
5 output [0:0] c0_ddr4_bg ,​
6 output [0:0] c0_ddr4_cke ,​
7 output [0:0] c0_ddr4_odt ,​
8 output [0:0] c0_ddr4_cs_n ,​
9 output [0:0] c0_ddr4_ck_t ,​
10 output [0:0] c0_ddr4_ck_c ,​
11 output c0_ddr4_reset_n ,​
12 inout [1:0] c0_ddr4_dm_dbi_n,​
13 inout [15:0] c0_ddr4_dq ,​
14 inout [1:0] c0_ddr4_dqs_c ,​
15 inout [1:0] c0_ddr4_dqs_t , ​
16 ​
17 //Differential system clocks​
18 input c0_sys_clk_p,​
19 input c0_sys_clk_n,​
20 output[1:0] led,​
21 input sys_rst_n​
22 ); ​
23 ​
24 //wire define ​
25 ​
26 wire error_flag;​
27 ​
28 wire c0_ddr4_ui_clk ;​
29 wire c0_ddr4_ui_clk_sync_rst ;​
30 wire c0_ddr4_app_en ;​
31 wire c0_ddr4_app_hi_pri ;​
32 wire c0_ddr4_app_wdf_end ;​
33 wire c0_ddr4_app_wdf_wren ;​
34 wire c0_ddr4_app_rd_data_end ; ​
35 wire c0_ddr4_app_rd_data_valid ; ​
36 wire c0_ddr4_app_rdy ; ​
37 wire c0_ddr4_app_wdf_rdy ; ​
38 wire [27 : 0] c0_ddr4_app_addr ;​
39 wire [2 : 0] c0_ddr4_app_cmd ;​
40 wire [127 : 0] c0_ddr4_app_wdf_data;​
41 wire [15 : 0] c0_ddr4_app_wdf_mask ;​
42 wire [127 : 0] c0_ddr4_app_rd_data ;​
43 ​
44 ​
45 ​
46 wire locked; //锁相环频率稳定标志​
47 wire clk_ref_i; //DDR4参考时钟​
48 wire sys_clk_i; //MIG IP核输入时钟​
49 wire clk_200; //200M时钟​
50 wire ui_clk_sync_rst; //用户复位信号​
51 wire init_calib_complete; //校准完成信号​
52 wire [20:0] rd_cnt; //实际读地址计数​
53 wire [1 :0] state; //状态计数器​
54 wire [23:0] rd_addr_cnt; //用户读地址计数器​
55 wire [23:0] wr_addr_cnt; //用户写地址计数器​
56 ​
57 //*****************************************************​
58 //** main code​
59 //*****************************************************​
60 ​
61 //读写模块​
62 ddr4_rw u_ddr4_rw(​
63 .ui_clk (c0_ddr4_ui_clk), ​
64 .ui_clk_sync_rst (c0_ddr4_ui_clk_sync_rst), ​
65 .init_calib_complete (c0_init_calib_complete),​
66 .app_rdy (c0_ddr4_app_rdy),​
67 .app_wdf_rdy (c0_ddr4_app_wdf_rdy),​
68 .app_rd_data_valid (c0_ddr4_app_rd_data_valid),​
69 .app_rd_data (c0_ddr4_app_rd_data),​
70 ​
71 .app_addr (c0_ddr4_app_addr),​
72 .app_en (c0_ddr4_app_en),​
73 .app_wdf_wren (c0_ddr4_app_wdf_wren),​
74 .app_wdf_end (c0_ddr4_app_wdf_end),​
75 .app_cmd (c0_ddr4_app_cmd),​
76 .app_wdf_data (c0_ddr4_app_wdf_data),​
77 .state (state),​
78 .rd_addr_cnt (rd_addr_cnt),​
79 .wr_addr_cnt (wr_addr_cnt),​
80 .rd_cnt (rd_cnt),​
81 ​
82 .error_flag (error_flag),​
83 .led (led)​
84 );​
85 ddr4_0 u_ddr4_0 (​
86 .c0_init_calib_complete(c0_init_calib_complete),//初始化完成信号 ​
87 .dbg_clk(), ​
88 .c0_sys_clk_p(c0_sys_clk_p), // 系统差分时钟p​
89 .c0_sys_clk_n(c0_sys_clk_n), // 系统差分时钟n​
90 .dbg_bus(), ​
91 .c0_ddr4_adr(c0_ddr4_adr), // 行列地址​
92 .c0_ddr4_ba(c0_ddr4_ba), // bank地址​
93 .c0_ddr4_cke(c0_ddr4_cke), // 时钟使能​
94 .c0_ddr4_cs_n(c0_ddr4_cs_n), // 片选信号​
95 .c0_ddr4_dm_dbi_n(c0_ddr4_dm_dbi_n), // 数据掩码​
96 .c0_ddr4_dq(c0_ddr4_dq), // 数据线​
97 .c0_ddr4_dqs_c(c0_ddr4_dqs_c), // 数据选通信号​
98 .c0_ddr4_dqs_t(c0_ddr4_dqs_t), // 数据选通信号​
99 .c0_ddr4_odt(c0_ddr4_odt), // 终端电阻​
100 .c0_ddr4_bg(c0_ddr4_bg), // bank组地址​
101 .c0_ddr4_reset_n(c0_ddr4_reset_n), // 复位信号​
102 .c0_ddr4_act_n(c0_ddr4_act_n), // 激活指令引脚​
103 .c0_ddr4_ck_c(c0_ddr4_ck_c), //时钟信号​
104 .c0_ddr4_ck_t(c0_ddr4_ck_t), // 时钟信号​
105 //user interface​
106 .c0_ddr4_ui_clk(c0_ddr4_ui_clk), // 用户时钟​
107 .c0_ddr4_ui_clk_sync_rst(c0_ddr4_ui_clk_sync_rst), // 用户复位​
108 .c0_ddr4_app_en(c0_ddr4_app_en), // 指令写入使能​
109 .c0_ddr4_app_hi_pri(1'b0), ​
110 .c0_ddr4_app_wdf_end(c0_ddr4_app_wdf_end), // 写数据最后一个时钟​
111 .c0_ddr4_app_wdf_wren(c0_ddr4_app_wdf_wren), // 写数据使能​
112 .c0_ddr4_app_rd_data_end(c0_ddr4_app_rd_data_end),// 读数据最后一个数据​
113 .c0_ddr4_app_rd_data_valid(c0_ddr4_app_rd_data_valid), // 读数据有效 ​
114 .c0_ddr4_app_rdy(c0_ddr4_app_rdy), // 指令接收准备完成,可以开始接收指令​
115 .c0_ddr4_app_wdf_rdy(c0_ddr4_app_wdf_rdy), // 数据接收准备完成,可以开始接收数据​
116 .c0_ddr4_app_addr(c0_ddr4_app_addr), // 用户地址​
117 .c0_ddr4_app_cmd(c0_ddr4_app_cmd), // 读写控制命令​
118 .c0_ddr4_app_wdf_data(c0_ddr4_app_wdf_data), // 写数据​
119 .c0_ddr4_app_wdf_mask(16'b0), // input wire [15 : 0] c0_ddr4_app_wdf_mask​
120 .c0_ddr4_app_rd_data(c0_ddr4_app_rd_data), // 读数据​
121 .sys_rst(~sys_rst_n) //系统复位​
122 ); ​
123 ​
124 endmodule

顶层模块的代码共例化了两个子模块,分别是DDR4读写模块(ddr4_rw)和MIG IP核模块(ddr4_0),主要实现各模块的数据交互。​

DDR4读写模块的代码如下:​

1 module ddr4_rw ( ​
2 input ui_clk, //用户时钟​
3 input ui_clk_sync_rst, //复位,高有效​
4 input init_calib_complete, //DDR4初始化完成​
5 input app_rdy, //MIG 命令接收准备好标致​
6 input app_wdf_rdy, //MIG数据接收准备好​
7 input app_rd_data_valid, //读数据有效​
8 input [127:0] app_rd_data, //用户读数据​
9 output reg [27:0] app_addr, //DDR4地址 ​
10 output app_en, //MIG IP发送命令使能​
11 output app_wdf_wren, //用户写数据使能​
12 output app_wdf_end, //突发写当前时钟最后一个数据 ​
13 output [2:0] app_cmd, //MIG IP核操作命令,读或者写​
14 output reg [127:0] app_wdf_data, //用户写数据​
15 output reg [1 :0] state, //读写状态​
16 output reg [23:0] rd_addr_cnt, //用户读地址计数​
17 output reg [23:0] wr_addr_cnt, //用户写地址计数​
18 output reg [20:0] rd_cnt, //实际读地址标记​
19 output reg error_flag, //读写错误标志​
20 output reg led //读写测试结果指示灯​
21 );​
22 ​
23 //parameter define​
24 parameter TEST_LENGTH = 10;​
25 parameter L_TIME = 25'd25_000_000;​
26 parameter IDLE = 2'd0; //空闲状态​
27 parameter WRITE = 2'd1; //写状态​
28 parameter WAIT = 2'd2; //读到写过度等待​
29 parameter READ = 2'd3; //读状态​
30 ​
31 //reg define​
32 reg [24:0] led_cnt; //led计数​
33 ​
34 //wire define​
35 wire error; //读写错误标记​
36 wire rst_n; //复位,低有效

代码第2行到第20行是模块的接口定义,大部分是MIG IP核中要操作的用户接口信号,这些信号在本章简介部分中已经以表格的形式详细呈现给大家,有不清楚的地方可以回去查看。​

代码第24行和第29行是参数定义,首先定义了TEST_LENGTH(测试长度),注意这里的测试长度是相对于用户端来说的,一个测试长度对应到DDR芯片需要访问是8个地址,即8个16Byte(共128位)。接下来定义了L_TIME(闪烁间隔),该参数用来控制LED闪烁频率。最后定义了读写测试的四个状态,IDLE(空闲)、WRITE(写)、WAIT(等待)和READ(读)。​

37 ​
38 //*****************************************************​
39 //** main code​
40 //***************************************************** ​
41 ​
42 assign rst_n = ~ui_clk_sync_rst;​
43 //读信号有效,且读出的数不是写入的数时,将错误标志位拉高​
44 assign error = (app_rd_data_valid && (rd_cnt! = app_rd_data));​
45 ​
46 //在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号,​
47 assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy))​
48 ||(state == READ && app_rdy)) ? 1'b1:1'b0;​
49 ​
50 //在写状态,命令接收和数据接收都准备好,此时拉高写使能​
51 assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;​
52 ​
53 //由于DDR4芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同​
54 assign app_wdf_end = app_wdf_wren; ​
55 ​
56 //处于读的时候命令值为1,其他时候命令值为0​
57 assign app_cmd = (state == READ) ? 3'd1 :3'd0; ​
58

代码第42行,信号ui_clk_sync_rst是MIG IP核产生,用于复位用户逻辑,其高电平有效。在代码中将ui_clk_sync_rst信号取反,是为了转换成代码中习惯使用的复位低电平有效。​

代码第44行,是错误标志的逻辑判断。读数据有效信号(app_rd_data_valid)为高时去比较app_rd_data和rd_cnt的值,不相等则读写错误。关于rd_cnt的含义将会在下文详细介绍。​

代码第47行到48行,首先产生了MIG IP核命令发送使能信号,这里可以看出,读或者写状态都要拉高该信号。需要特别指出的是,在写的时候,app_en拉高的条件是:app_rdy(MIG命令接收准备好标志)和app_wdf_rdy(MIG数据接收准备好标致)同时为高,根据数据手册,用户可以只判断app_rdy信号。当app_rdy信号为高电平时,可以拉高使能app_en,此处之所以同时判断app_rdy和app_wdf_rdy,是因为本实验只考虑DDR4写状态命令发送和数据发送同拍的情形,这样的做法基本上不会对DDR4读写操作效率造成很大影响,但是却能大大简化代码编写难度。​

代码第51行,产生写使能,如上所述,本实验只考虑写命令和写数据同时发起的情形,当检测到命令接收和数据接收标志同时拉高的时候,写操作也在同一时间被发起,即拉高app_wdf_wren(写数据使能)。​

这里要解释一下代码第54行。上文说过app_wdf_end表明当前时钟是突发写过程的最后一个时钟周期,由于本实验采用的是4:1模式,每一个用户时钟周期实际上都完成了一个写突发操作,所以在突发写有效时,即app_wdf_wren拉高,每个时钟,app_wdf_end也跟着拉高,所以在4:1模式信号app_wdf_end和app_wdf_wren是同步变化的。​

59 //DDR4读写逻辑实现​
60 always @(posedge ui_clk or negedge rst_n) begin​
61 if((~rst_n)||(error_flag)) begin ​
62 state <= IDLE; ​
63 app_wdf_data <= 128'd0; ​
64 wr_addr_cnt <= 24'd0; ​
65 rd_addr_cnt <= 24'd0; ​
66 app_addr <= 28'd0; ​
67 end​
68 else if(init_calib_complete)begin //MIG IP核初始化完成​
69 case(state)​
70 IDLE:begin​
71 state <= WRITE;​
72 app_wdf_data <= 128'd0; ​
73 wr_addr_cnt <= 24'd0; ​
74 rd_addr_cnt <= 24'd0; ​
75 app_addr <= 28'd0; ​
76 end​
77 WRITE:begin​
78 if(wr_addr_cnt == TEST_LENGTH - 1 &&(app_rdy && app_wdf_rdy))​
79 state <= WAIT; //写到设定的长度跳到等待状态​
80 else if(app_rdy && app_wdf_rdy)begin //写条件满足​
81 app_wdf_data <= app_wdf_data + 1; //写数据自加​
82 wr_addr_cnt <= wr_addr_cnt + 1; //写地址自加​
83 app_addr <= app_addr + 8; //DDR4 地址加8​
84 end​
85 else begin //写条件不满足,保持当前值​
86 app_wdf_data <= app_wdf_data; ​
87 wr_addr_cnt <= wr_addr_cnt;​
88 app_addr <= app_addr; ​
89 end​
90 end​
91 WAIT:begin ​
92 state <= READ; //下一个时钟,跳到读状态​
93 rd_addr_cnt <= 24'd0; //读地址复位​
94 app_addr <= 28'd0; //DDR4读从地址0开始​
95 end​
96 READ:begin //读到设定的地址长度 ​
97 if(rd_addr_cnt == TEST_LENGTH - 1 && app_rdy)​
98 state <= IDLE; //则跳到空闲状态 ​
99 else if(app_rdy)begin //若MIG已经准备好,则开始读​
100 rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一​
101 app_addr <= app_addr + 8; //DDR4地址加8​
102 end​
103 else begin //若MIG没准备好,则保持原值​
104 rd_addr_cnt <= rd_addr_cnt;​
105 app_addr <= app_addr; ​
106 end​
107 end​
108 default:begin​
109 state <= IDLE;​
110 app_wdf_data <= 128'd0;​
111 wr_addr_cnt <= 24'd0;​
112 rd_addr_cnt <= 24'd0;​
113 app_addr <= 28'd0;​
114 end​
115 endcase​
116 end​
117 end ​
118 ​
119 //对DDR4实际读数据个数编号计数​
120 always @(posedge ui_clk or negedge rst_n) begin​
121 if(~rst_n) ​
122 rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则置0 ​
123 else if(app_rd_data_valid && rd_cnt == TEST_LENGTH - 1)​
124 rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1​
125 else if (app_rd_data_valid )​
126 rd_cnt <= rd_cnt + 1;​
127 end​
128 ​
129 //寄存状态标志位​
130 always @(posedge ui_clk or negedge rst_n) begin​
131 if(~rst_n) ​
132 error_flag <= 0;​
133 else if(error)​
134 error_flag <= 1;​
135 end​
136 ​
137 //led指示效果控制​
138 always @(posedge ui_clk or negedge rst_n) begin​
139 if((~rst_n) || (~init_calib_complete )) begin​
140 led_cnt <= 25'd0;​
141 led <= 1'b0;​
142 end​
143 else begin​
144 if(~error_flag) //读写测试正确 ​
145 led <= 1'b1; //led灯常亮​
146 else begin //读写测试错误​
147 led_cnt <= led_cnt + 25'd1;​
148 if(led_cnt == L_TIME - 1'b1) begin​
149 led_cnt <= 25'd0;​
150 led <= ~led; //led灯闪烁​
151 end ​
152 end​
153 end​
154 end​
155 ​
156 endmodule

代码60行起的always模块是DDR4读写逻辑的实现。采用了一段式状态机来编写整个读写测试逻辑,如下图所示,一共分为四个状态。描述如下:上电一开始处于IDLE(空闲)状态,初始化完成后跳到WRITE(DDR4写),写到测试长度跳到WAIT(等待)状态,在此消耗一个实在周期后无条件跳到READ(DDR4读),读到测试长度跳回空闲状态。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_20


图31.4.10读写测试状态跳转图​

下面来讲解具体的各状态,主要是写和读状态。​

首先来看看写过程,为了便于理解,下面给出了ILA中采集到的DDR4写的波形,同时为了方便观察完整的写过程,把本次实验的一次读写长度设为10。如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_写数据_21


图31.4.11写时序​

写状态首先判断MIG IP核发送过来的信号app_rdy和app_wdf_rdy,当这两个信号同时为高时,拉高app_en和app_wdf_wren,同时时给写出命令,即app_cmd为0,此刻正式进行DDR4写过程。写的过程中,写数据app_wdf_data和地址计数wr_addr_cnt在每个时钟自加1。另外大家注意到app_addr每次自加8,前面其实提到,用户端在每一个用户时钟进行一个128bit的数据的传输,在DDR4物理芯片端需要分8次传输,每次传输一个地址位宽16bit,8次就需要8个地址。通过写时序图和ILA图进行对比,可以发现两者是一致的,即说明本次实验是成功的​

代码第96到107行是DDR4读过程,在读状态,判断MIG IP核发送过来的信号app_rdy,当这个信号为高时,拉高app_en,同时给出读命令,即app_cmd为1,此时开始进行读操作。在进行读操作的时候,app_addr同样每次自加8。下面还是给出ILA上观察到的读信号波形,方便大家理解查看。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_IP_22


图31.4.12读时序​

读操作结束又跳回空闲状态,如代码第98行所示。​

代码第120行到127行的always模块,产生rd_cnt,实现了对读出的地址数据的编号。为什么会有这样的处理,根本原因在于读数据的返回和读操作并不是在同一拍出现的,会晚若干周期出现。这样意味着不能用读数据直接和当前的读地址去比较,所以本次实验单独构造了rd_cnt这个信号,它代表一次读操作真正返回的有效的读数据个数。​

代码第130行到135行的always模块,实现了错误标识的寄存。代码第138行到154行的always模块实现读写测试LED灯的显示逻辑。错误则闪烁,正确则常亮。​

最后通过采集到的波形来看读写测试结果,在读数据有效信号app_rd_data_valid为高时,数据总线上的app_rd_data分别和地址编号rd_cnt相等。按照本次实验的设计,写进DDR4的数值是它的地址编号,也即等于数据编号,说明是正确的读写结果。当然大家可以看到错误指示信号error_flag始终为0,即没有出现读写错误。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_23


图31.4.13读结果​

下载验证

将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,接下来连接电源线后拨动开关按键给开发板上电。​

点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Progam Device”下载程序,在弹出的界面中选择“Program”下载程序。​

程序下载完成后,开发板上的PL_LED1在短暂延时之后,始终处于常亮的状态,如下图所示: ​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_数据_24


图31.5.1板子测试结果​

此时说明DDR4读写测试成功。​

在这里再额外补充一点,将带有DDR4 MIG IP核的工程下载进开发板时,会出现一个MIG自带的状态检测核,如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第三十一章 DDR4读写测试实验​_写数据_25


图31.5.2 DDR4状态​

从上图中可以看到MIG运行的一些状态信息,如果运行正常会显示“PASS”。到这里DDR4读写实验就给大家全部讲解完了。​


标签:MPSoc,FPGA,IP,DFZU2EG,ddr4,DDR4,output,c0,app
From: https://blog.51cto.com/u_15046463/5967082

相关文章