正点原子 发表于 2020-12-1 15:40:13

【正点原子FPGA连载】第三十章DDR3读写测试

本帖最后由 正点原子 于 2021-1-23 15:54 编辑

1)实验平台:正点原子达芬奇FPGA开发板
2)购买链接:https://detail.tmall.com/item.htm?id=624335496505
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入:





第三十章DDR3读写测试


DDR3 SDRAM常简称DDR3,是当今较为常见的一种储存器,在计算机及嵌入式产品中得到广泛应用,特别是应用在涉及到大量数据交互的场合,比如电脑的内存条。对DDR3的读写操作大都借助IP核来完成,本次实验将采用Xilinx公司MIG IP核来实现DDR3读写测试。
本章包括以下几个部分:
3030.1简介
30.2实验任务
30.3硬件设计
30.4程序设计
30.5下载验证

30.1简介
DDR3 SDRAM(Double-Data-Rate Three Synchronous Dynamic Random Access Memory)是DDR SDRAM的第三代产品,相较于DDR2,DDR3有更高的运行性能与更低的电压。DDR SDRAM是在SDRAM技术的基础上发展改进而来的,同SDRAM相比,DDR SDRAM的最大特点是双沿触发,即在时钟的上升沿和下降沿都能进行数据采集和发送,同样的工作时钟,DDR SDRAM的读写速度可以比传统的SDRAM快一倍。本次实验使用的DDR3芯片是南亚的NT5CB128M16CP-DI,NT5CB128M16CP-DI的bank位宽为3,行位宽为14,列位宽为10,所以它的地址大小等于2^3*2^14*2^10(即2^27=128M),数据位宽为16bit,所以容量大小为128M*16bit,也就是256MByte。
由于DDR3的时序非常复杂,如果直接编写DDR3的控制器代码,那么工作量是非常大的,且性能难以得到保证。值得一提的是,Artix7系列FPGA自带了DDR3控制器的硬核,用户可以直接借助IP核来实现对DDR3的读写操作,从而大大降低了DDR3的开发难度。本次实验将使用Xilinx公司MIG(Memory Interface Generators) IP核来实现DDR3读写测试。
MIG IP核是Xilinx公司针对DDR存储器开发的IP,里面集成存储器控制模块,实现DDR读写操作的控制流程,下图是7系列的MIG IP 核结构框图。MIG IP核对外分出了两组接口。左侧是用户接口,就是用户(FPGA)同MIG 交互的接口,用户只有充分掌握了这些接口才能操作MIG。右侧为DDR物理芯片接口,负责产生具体的操作时序,并直接操作芯片管脚。这一侧用户只负责分配正确的管脚,其他不用关心。

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


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

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

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

图 30.1.4 非背靠背写时序
如上图所示,写数据有三种情形均可以正确写入:
(1)写数据时序和写命令时序发生在同一拍;
(2)写数据时序比写命令时序提前一拍;
(3)写数据时序比写命令时序至多延迟晚两拍;
结合上图,写时序总结如下:首先需要检查app_wdf_rdy,该信号为高表明此时IP核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据(app_wdf_data)。这样加上发起的写命令操作就可以成功向IP核写数据。这里有一个信号app_wdf_mask,它是用来屏蔽写入数据的,该信号为高则屏蔽相应的字节,该信号为0默认不屏蔽任何字节。
这里需要指出的是DDR3的读或者写操作都可以分为背靠背和非背靠背两种情形。背靠背,即读或者写每个时钟都连续进行,中间没有间隙。非背靠背写则是非连续的读写。
对于背靠背写,其实也有三种情形,唯一点不同的是,它没有最大延迟限制,如下图所示。


图 30.1.5 背靠背写时序
接着来看读数据,如下图所示:

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

图 30.1.7 背靠背读时序图
这里还需要注意一点,在连续读的时候,读到的数据顺序跟请求的命令/地址是相对应的。通常使用DDR3的时候,为了最大限度地提高DDR3效能,充分利用突发写的特点,非背靠背很少用,而更多地采用背靠背操作。本章实验的读写操作就是基于背靠背模式进行的。
30.2实验任务
本节的实验任务是先向DDR3的存储器地址0至999分别写入数据0~999;写完之后再读取存储器地址0~999中的数据,若读取的值全部正确则LED灯常亮,否则LED灯闪烁。
30.3硬件设计
达芬奇开发板上使用了一片南亚的DDR3颗粒NT5CB128M16CP-DI,硬件原理图如下图所示。在PCB的设计上,完全遵照Xilinx的DDR3硬件设计规范,严格保证等长设计和阻抗控制,从而保证高速信号的数据传输的可靠性。


图 30.3.1 DDR3硬件原理图
如下表所示为本次实验用到的所有管脚。当然因为管脚太多,大家分配的时候直接导入工程目录下的ddr3_rw_test.xdc文件即可。
表 30.3.1 DDR3读写测试实验部分管脚分配



如上表所示,本次实验用到了三种电平,分别是LVCMOS、SSTL和DIFF_SSTL,对于三种电平,下面分别给出中英文解释。
LVCMOS:全称Low Voltage Complementary Metal Oxide Semiconductor,低压互补金属氧化物半导体;
SSTL:全称Stub Series Terminated Logic,短截线串联端接逻辑;
DIFF_SSTL:全称Difference Stub Series Terminated Logic,差分短截线串联端接逻辑。
LVCMOS的特点是噪声容限大,速度较SSTL慢;SSTL速度快,通常要匹配合适的端接电阻,常用于高速内存接口如DDR3;DIFF_SSTL则是带有差分功能的SSTL。
DDR3的管脚使用的是SSTL或者DIFF_SSTL,这里要注意一下DDR3复位信号ddr3_reset_n,它并没有使用SSTL,原因是SSTL的噪声容限不够大,容易受干扰造成误复位。
本实验对应的XDC约束语句如下所示。注意,在这里仅给出了时钟,复位还有LED读写指示信号的管脚约束。DDR3部分的管脚约束在文档中不再给出,大家可以从提供的例程中查看。
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS33}
30.4程序设计
根据实验任务,可以大致规划出系统的控制流程:首先FPGA通过调用MIG IP核向DDR3芯片写入数据,写完之后通过MIG IP核从DDR3芯片读出所写入的数据,并判断读出的数据与写入的数据是否相同,如果相同则LED灯常亮,否则LED灯闪烁。由此画出系统的功能框图如下图所示:

图 30.4.1 DDR3 读写测试实验系统框图
由系统总体框图可知,FPGA顶层模块例化了以下三个模块,分别是读写模块(ddr3_rw)、MIG IP核模块(mig_7series_0)和时钟模块(clk_wiz_0)。
各模块端口及信号连接如下图所示:

图 30.4.2 顶层模块原理图
ddr3_rw模块产生读写MIG IP核用户接口的时序,实现与MIG IP核的数据及信号交互,此外,也在该模块实现读写测试结果的判断,并送出判断结果的指示信号LED。clk_wiz_0是本实验的时钟模块,负责产生MIG IP核所需的系统时钟sys_clk_i和参考时钟clk_ref_i,本次实验这两个时钟都是200M。mig_7series_0模块就是在简介部分中提到的MIG IP核,该模块一方面负责与用户(FPGA)进行数据交互,另一方面还产生控制DDR3读写的各种时序,并实现对DDR芯片的读写操作。用户可以把mig_7series_0看作是FPGA与DDR3芯片交流信息的桥梁,当然这个“桥梁”的实现代码不需要用户自己写,Xilinx官方已帮大家写好,并封装成了一个IP供用户调用,用户只需要根据实际应用配置该IP即可。这个配置过程很重要,下面就一起来学习MIG IP核的配置。
首先在Vivado环境里新建一个工程,本实验取名为ddr3_rw_top。再点击Project Manager界面下的IP Catalog,打开IP Catalog界面。如下图所示。

图 30.4.3 “IP Catalog”按钮
在搜索栏中输入MIG,此时出现MIG IP核,直接双击打开。如下图所示。

图 30.4.4 搜索栏中输入关键字
下面让确认工程的信息,主要是芯片信息和编译环境的信息,如果没什么问题,直接点击“Next”。如下图所示。

图 30.4.5 工程信息展示
如下图所示,这一页选择“Create Design”,在“Component Name”一栏设置该IP元件的名称,这里取默认软件的名称,再往下选择控制器数量,默认为“1”即可。最后关于AXI4接口,因为本工程不去使用,所以不勾选。配置完成点击“Next”。

图 30.4.6 IP顶层配置
如下图所示,这一页主要是让用户选择可以兼容的芯片,本工程默认不勾选,即不需要兼容其他的FPGA芯片。配置完成点击“Next”。

图 30.4.7 FPGA芯片管脚兼容设置
如下图所示,这一页选择第一个选项“DDR3 SDRAM”,因为本实验用的就是DDR3芯片。配置完成点击“Next”。

图 30.4.8 存储器类型选择
如下图所示,从这页开始,下面来讲解如何配置MIG IP核,大家可以对照图片和文字来详细了解各个选项和本次实验的配置参数。


图 30.4.9 时钟及物理芯片配置(上)

图 30.4.10 时钟及物理芯片配置(下)
Clock Period:DDR3芯片运行时钟周期,这个参数的范围和FPGA的芯片类型以及具体类型的速度等级有关。本实验选择2500ps,对应400M,这是本次实验所采用芯片可选的最大频率。注意这个时钟是MIG IP核产生,并输出给DDR3物理芯片使用的,它关系到DDR3芯片具体的运行带宽。比如本次实验的开发板板载了一颗DDR3芯片,数据位宽总共16位,因为是双沿触发,这里带宽达到了12.8Gb/s(800M*16bit)。
PHY to Controller Clock Ratio:DDR3物理芯片运行时钟和MIG IP核的用户端(FPGA)的时钟之比,一般有4:1和2:1两个选项,本次实验选4:1。由于DDR芯片的运行时钟是400Mhz,因此MIG IP核的用户时钟(ui_clk)就是100Mhz。一般来说高速传输的场合选择4:1,要求低延时的场合选择2:1。这里还要指出,当DDR3时钟选择选择了350M到最高的400M,比例默认只为4:1,低于350M才有4:1和2:1两个选项。
VCCAUX_IO:这是FPGA高性能bank(High Performance bank)的供电电压。它的设置取决于MIG控制器运行的周期/频率。当用户让控制器工作在最快频率的时候,系统会默认为1.8V,当然在1.8V下用户可以运行低一点的频率。本实验默认1.8V。
Memory Type:DDR3储存器类型选择。本实验选择Component。
Memory Part:DDR3芯片的具体型号。本实验选择MT41J128M16XX-125,这个型号其实和实际硬件原理图上的型号NT5CB128M16CP-DI是不同的,这个没关系,只要用户的DDR3芯片容量和位宽一致大部分是可以兼容的,其他的型号也是可以的,大家有兴趣可以去尝试。
Memory Voltage:是DDR3芯片的电压选择,本实验选1.5v。
Data Width:数据位宽选择,这里选择16。
ECC:ECC校验使能,数据位宽为72位的时候才能使用。本实验不使用它。
Data Mask:数据屏蔽管脚使能。勾选它才会产生屏蔽信号,本实验没用到数据屏蔽,但是在这里还是把它勾选上。
Number of Bank Machines:Bank Machine的数量是用来对具体的每个或某几个来单独控制的,选择多了控制效率就会高,相应的占用的资源也多,本实验选择4个,平均一个Bank Machine控制两个BANK(本次实验的DDR3芯片是八个bank)。
ORDERING:该信号用来决定MIG控制器是否可以对它收到的指令进行重新排序,选择Normal则允许,Strict则禁止。本实验选择Normal,从而获得更高效率。
点击“NEXT”按钮,界面如下图所示。

图 30.4.11 储存器配置
Input Clock Period:MIG IP核的系统输入时钟周期,该输入时钟是由FPGA内部产生的,本次实验选择的时钟频率为200MHz(5000ps)。
Read Burst Type and Length:突发类型选择,突发类型有顺序突发和交叉突发两种,本实验选择顺序突发(Sequential),其突发长度固定为8。
Output Driver Impdance Control:输出阻抗控制。本实验选择RZQ/7。
RTT:终结电阻,可进行动态控制。本次实验选择RZQ/4。
Controller Chip Select Pin:片选管脚引出使能。本实验选择enable,表示把片选信号cs#引出来,由外部控制。
BANK_ROW_COLUMN:寻址方式选择。本实验选择第二种,即BANK-ROW-COLUMN的形式,这是一种最常规的DDR3寻址方式,即要指定某个地址,先指定bank,再指定行,最后指定列,这样就确定了一个具体地址。一般来说这样寻址方式有利于降低功耗,但是读写性能(效率)上不如“ROW_BANK_COLUMN”。配置完成点击“Next”。
如下图所示这是对MIG IP系统时钟的属性设置。

图 30.4.12 系统时钟配置
System Clock:MIG IP核输入时钟。本实验选择“No Buffer”, 因为IP核的输入系统时钟是单端时钟,是由内部的MMCM产生的,MMCM所产生的时钟默认添加了buffer。
Reference Clock:MIG IP核参考时钟。同样选择“No Buffer”,将由时钟模块生成。感兴趣的用户也可以选择“Use System Clock”这个选项,这时候的MIG IP系统时钟同时作为了参考时钟,IP核参考时钟要求是200Mhz,而MIG IP核的系统时钟刚好也使用了200Mhz的系统时钟。
System Reset Polarity:复位有效电平选择。本实验选择“ACTIVE LOW”低电平有效。
Debug Signals Control:该选项用于控制MIG IP核是否把一些调试信号引出来,它会自动添加到ILA,这些信号包括一些DDR3芯片的校准状态信息。本实验选择选择“OFF”,不需要让IP核生产各种调试信号。
Sample Data Depth:采样深度选择。当“Debug Signals Control”选择“OFF”时,所有采样深度是不可选的。
Internal Vref:内部参考管脚,表示将某些参考管脚当成普通的输入管脚来用。由于开发板的IO资源较为紧张,因此这里需要选择“ON”,把参考管脚当做普通的输入管脚来用。
IO Power Reduction:IO管脚节省功耗设置。本实验选择“ON”,即开启。
XADC Instantiation:XADC模块例化。使用MIG IP核运行的时候需要进行温度补偿,可以直接选择XADC模块的温度数据引到MIG IP核来使用,否则需要额外提供温度数据,所以本实验选择“Enable”。
继续点击“NEXT”按钮,界面如下图所示。

图 30.4.13 端接阻抗
上图界面是内部高性能bank端接匹配阻抗的设置,这里不去改它,默认50欧姆即可。接下来点击“NEXT”按钮,界面如下图所示。

图 30.4.14 “Pin/Bank Selection Mode”选项卡
Pin/Bank Selection Mode:管脚模式选择。本次实验选择第二种。
继续点击“Next”按钮,界面如下图所示。

图 30.4.15 管脚分配导入选项卡
选择“Read XDC/UCF”,直接导入管脚分配文件,紧接着弹出如下图所示界面。

图 30.4.16 导入管脚分配
在工程目录下,已经为大家准备好了一个ddr3_rw_test.xdc文件,大家可以直接从例程中拷贝。用户只要直接导入这个xdc文件,就可以完成DDR3的管脚分配。
如下图,导入后点击“Validate” ,此时会跳出对话框,表明已经验证通过,点击“OK”,此时“Next”变成可选,点击“Next”完成管脚分配。

图 30.4.17 分配验证管脚
如下图所示,这里软件默认设置,直接点击“Next”。

图 30.4.18 系统信号选择选项卡
如下图所示,这里显示DDR3 IP配置的概况,查看一下,没有问题就点击“Next”。

图 30.4.19 MIG IP 核配置信息汇总
如下图所示,选择“Accept”,然后点击“Next”。

图 30.4.20 仿真选项卡
如下图,这是PCB信息选项卡,直接点击“Next”。

图 30.4.21 PCB信息选项卡
如下图,这是整个MIG IP核设计的汇总,直接点击“Generate”。

图 30.4.22 “Design Notes” 选项卡
如下图所示,选择“Global”,然后点击“Generate”。到这里完成了MIG IP核的所有配置。并最终生成IP核。

图 30.4.23 “Generate Output Products”选项卡
下面来看具体代码:
1   module ddr3_rw_top(
2      input            sys_clk,         //系统时钟
3      input            sys_rst_n,       //复位,低有效
4       // DDR3
5      inout      DDR3_dq,         //DDR3数据
6      inout       DDR3_dqs_n,      //DDR3 dqs负
7      inout       DDR3_dqs_p,      //DDR3 dqs正
8      output       DDR3_addr,       //DDR3 地址   
9      output        DDR3_ba,         //DDR3 banck 选择
10   output             DDR3_ras_n,      //DDR3 行选择
11   output             DDR3_cas_n,      //DDR3 列选择
12   output             DDR3_we_n,       //DDR3 读写选择
13   output             DDR3_reset_n,    //DDR3 复位
14   output        DDR3_ck_p,       //DDR3 时钟正
15   output        DDR3_ck_n,       //DDR3 时钟负
16   output        DDR3_cke,      //DDR3 时钟使能
17   output        DDR3_cs_n,       //DDR3 片选
18   output        DDR3_dm,         //DDR3_dm
19   output        DDR3_odt,      //DDR3_odt
20   //用户
21   output             led            //错误指示信号
22      );               
23                        
24   //wire define
25wire                  ui_clk ;             //用户时钟
26wire                  error_flag;          //读写错误标志
27wire          app_addr;            //DDR3 地址
28wire             app_cmd;             //用户读写命令
29wire                  app_en;            //MIG IP命令写使能
30wire                  app_rdy;             //MIG IP命令接收准备好
31wire           app_rd_data;         //用户读数据
32wire                  app_rd_data_end;   //突发读当前时钟最后一个数据
33wire                  app_rd_data_valid;   //读数据有效
34wire           app_wdf_data;      //用户写数据
35wire                  app_wdf_end;         //当前时钟突发写最后一个数据
36wire          app_wdf_mask;      //写数据屏蔽
37wire                  app_wdf_rdy;         //写数据接收准备好
38wire                  app_sr_active;       //保留
39wire                  app_ref_ack;         //刷新请求
40wire                  app_zq_ack;          //ZQ 校准请求
41wire                  app_wdf_wren;      //DDR3 写使能                  
42wire                  locked;            //锁相环频率稳定标志
43wire                  clk_ref_i;         //DDR3参考时钟
44wire                  sys_clk_i;         //MIG IP核输入时钟
45wire                  clk_200m;            //200M时钟
46wire                  ui_clk_sync_rst;   //用户复位信号
47wire                  init_calib_complete; //校准完成信号
48wire          rd_cnt;            //实际读地址计数
49wire          state;               //状态计数器
50wire          rd_addr_cnt;         //用户读地址计数器
51wire          wr_addr_cnt;         //用户写地址计数器
52
53//*****************************************************
54//**                  main code
55//*****************************************************
56
57//读写模块
58   DDR3_rw u_DDR3_rw(
59      .ui_clk               (ui_clk),               
60      .ui_clk_sync_rst      (ui_clk_sync_rst),      
61      .init_calib_complete(init_calib_complete),
62      .app_rdy            (app_rdy),
63      .app_wdf_rdy          (app_wdf_rdy),
64      .app_rd_data_valid    (app_rd_data_valid),
65      .app_rd_data          (app_rd_data),
66      
67      .app_addr             (app_addr),
68      .app_en               (app_en),
69      .app_wdf_wren         (app_wdf_wren),
70      .app_wdf_end          (app_wdf_end),
71      .app_cmd            (app_cmd),
72      .app_wdf_data         (app_wdf_data),
73      .state                (state),
74      .rd_addr_cnt          (rd_addr_cnt),
75      .wr_addr_cnt          (wr_addr_cnt),
76      .rd_cnt               (rd_cnt),
77      .error_flag         (error_flag),
78      .led                  (led)
79      );
80      
81//MIG IP核模块
82mig_7series_0 u_mig_7series_0 (
83      // Memory interface ports
84      .DDR3_addr                      (DDR3_addr),    // output DDR3_addr
85      .DDR3_ba                        (DDR3_ba),      // output DDR3_ba
86      .DDR3_cas_n                     (DDR3_cas_n),   // output       DDR3_cas_n
87      .DDR3_ck_n                      (DDR3_ck_n),    // output DDR3_ck_n
88      .DDR3_ck_p                      (DDR3_ck_p),    // output DDR3_ck_p
89      .DDR3_cke                     (DDR3_cke),   // output DDR3_cke
90      .DDR3_ras_n                     (DDR3_ras_n),   // output       DDR3_ras_n
91      .DDR3_reset_n                   (DDR3_reset_n), // output       DDR3_reset_n
92      .DDR3_we_n                      (DDR3_we_n),    // output       DDR3_we_n
93      .DDR3_dq                        (DDR3_dq),      // inout DDR3_dq
94      .DDR3_dqs_n                     (DDR3_dqs_n),   // inout DDR3_dqs_n
95      .DDR3_dqs_p                     (DDR3_dqs_p),   // inout DDR3_dqs_p
96      .init_calib_complete            (init_calib_complete),
97                                                      // init_calib_complete
98      .DDR3_cs_n                      (DDR3_cs_n),    // output DDR3_cs_n
99      .DDR3_dm                        (DDR3_dm),      // output DDR3_dm
100   .DDR3_odt                     (DDR3_odt),   // output DDR3_odt
101   // Application interface ports                  
102   .app_addr                     (app_addr),   // input app_addr
103   .app_cmd                        (app_cmd),      // input app_cmd
104   .app_en                         (app_en),       // input      app_en
105   .app_wdf_data                   (app_wdf_data), // input app_wdf_data
106   .app_wdf_end                  (app_wdf_end),// input      app_wdf_end
107   .app_wdf_wren                   (app_wdf_wren), // input      app_wdf_wren
108   .app_rd_data                  (app_rd_data),// output app_rd_data
109   .app_rd_data_end                (app_rd_data_end),
110                                                   // output       app_rd_data_end
111   .app_rd_data_valid            (app_rd_data_valid),
112                                                   // output       app_rd_data_valid
113   .app_rdy                        (app_rdy),      // output       app_rdy
114   .app_wdf_rdy                  (app_wdf_rdy),// output       app_wdf_rdy
115   .app_sr_req                     (),             // input      app_sr_req
116   .app_ref_req                  (),             // input      app_ref_req
117   .app_zq_req                     (),             // input      app_zq_req
118   .app_sr_active                  (app_sr_active),// output       app_sr_active
119   .app_ref_ack                  (app_ref_ack),// output       app_ref_ack
120   .app_zq_ack                     (app_zq_ack),   // output       app_zq_ack
121   .ui_clk                         (ui_clk),       // output       ui_clk
122   .ui_clk_sync_rst                (ui_clk_sync_rst),
123                                                   // output       ui_clk_sync_rst
124   .app_wdf_mask                   (31'b0),      // input app_wdf_mask
125   // System Clock Ports
126   .sys_clk_i                      (clk_200),
127   // Reference Clock Ports
128   .clk_ref_i                      (clk_200),
129   .sys_rst                        (sys_rst_n)   // input         sys_rst
130   );
131   
132 //时钟模块
133 clk_wiz_0 u_clk_wiz_0
134    (
135   // Clock out ports
136   .clk_out1(clk_200),      // output clk_out1
137   // Status and control signals
138   .reset(1'b0),         // input resetn
139   .locked(locked),      // output locked
140    // Clock in ports
141   .clk_in1(sys_clk)
142   );                      // input clk_in1
143   
144 endmodule
这是顶层模块的代码,大家可以看到顶层模块一共例化了三个子模块,分别是DDR3读写模块(ddr3_rw_top)、MIG IP核模块(mig_7series_0)和时钟模块(clk_wiz_0)。每个模块的功能在上文已经详细提及,这里给出了源代码以及详细的注释,请大家自行查看。
下面来重点讲解DDR3读写模块。
1   module ddr3_rw (         
2       input                  ui_clk,                //用户时钟
3       input                  ui_clk_sync_rst,       //复位,高有效
4       input                  init_calib_complete,   //DDR3初始化完成
5       input                  app_rdy,               //MIG 命令接收准备好标致
6       input                  app_wdf_rdy,         //MIG数据接收准备好
7       input                  app_rd_data_valid,   //读数据有效
8       input             app_rd_data,         //用户读数据
9       output reg       app_addr,            //DDR3地址                     
10      output                   app_en,                //MIG IP发送命令使能
11      output                   app_wdf_wren,          //用户写数据使能
12      output                   app_wdf_end,         //突发写当前时钟最后一个数据
13      output            app_cmd,               //MIG IP核操作命令,读或者写
14      output reg      app_wdf_data,          //用户写数据
15      output reg       state,               //读写状态
16      output reg       rd_addr_cnt,         //用户读地址计数
17      output reg       wr_addr_cnt,         //用户写地址计数
18      output reg       rd_cnt,                //实际读地址标记
19      output reg               error_flag,            //读写错误标志
20      output reg               led                  //读写测试结果指示灯
21      );
22
23//parameter define
24parameterTEST_LENGTH = 10;
25parameterL_TIME = 25'd25_000_000;
26parameterIDLE      = 2'd0;            //空闲状态
27parameterWRITE       = 2'd1;            //写状态
28parameterWAIT      = 2'd2;            //读到写过度等待
29parameterREAD      = 2'd3;            //读状态
30
31//reg define
32regled_cnt;    //led计数
33
34//wire define
35wire         error;   //读写错误标记
36wire         rst_n;   //复位,低有效
37
38   //*****************************************************
39//**                  main code
40//*****************************************************
41
42assign rst_n = ~ui_clk_sync_rst;
43//读信号有效,且读出的数不是写入的数时,将错误标志位拉高
44assign error = (app_rd_data_valid && (rd_cnt! = app_rd_data));
45
46//在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号,
47assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy))
48                  ||(state == READ && app_rdy)) ? 1'b1:1'b0;
49                  
50//在写状态,命令接收和数据接收都准备好,此时拉高写使能
51assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
52
53//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
54assign app_wdf_end = app_wdf_wren;
55
56//处于读的时候命令值为1,其他时候命令值为0
57assign app_cmd = (state == READ) ? 3'd1 :3'd0;
58      
59//DDR3读写逻辑实现
60always @(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;      //DDR3 地址加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;                //DDR3读从地址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;       //DDR3地址加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 //对DDR3实际读数据个数编号计数
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;
135end
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
代码第2行到第20行是模块的接口定义,其实大部分是MIG IP核中要操作的用户接口信号,这些信号在上文已经以表格的形式详细呈现给大家,有不清楚的地方请回去查看。
代码第24行和第29行是参数定义,首先定义了L_TIME(闪烁间隔),该参数用来控制LED闪烁频率。接着,定义了TEST_LENGTH(测试长度),注意这里的测试长度是相对于用户端来说的,一个测试长度对应到DDR芯片需要访问是8个地址。最后定义了读写测试的四个状态,IDLE(空闲)、WRITE(写)、WAIT(等待)、READ(读)。
代码第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是因为本实验只考虑DDR3写状态命令发送和数据发送同拍的情形,这样的做法基本上不会对DDR3读写操作效率造成很大影响,但是却能大大简化代码编写难度。
代码第51行,产生写使能,如上所述,本实验只考虑写命令和写数据同时发起的情形,当检测到命令接收和数据接收标志同时拉高的时候,写操作也在同一时间被发起,即拉高app_wdf_wren(写数据使能)。
这里要解释一下代码第54行。上文说过app_wdf_end表明当前时钟是突发写过程的最后一个时钟周期,由于本实验采用的是4:1模式,每一个用户时钟周期实际上都完成了一个写突发操作,所以在突发写有效时,即app_wdf_wren拉高,每个时钟,app_wdf_end也跟着拉高,所以在4:1模式信号app_wdf_end和app_wdf_wren是同步变化的。
代码60行起的always模块是DDR3读写逻辑的实现。采用了一段式状态机来编写整个读写测试逻辑,如下图所示,一共分为四个状态。描述如下:上电一开始处于IDLE(空闲)状态,初始化完成后跳到WRITE(DDR3写),写到测试长度跳到WAIT(等待)状态,在此消耗一个实在周期后无条件跳到READ(DDR3读),读到测试长度跳回空闲状态。

图 30.4.24 读写测试状态跳转图
下面来讲解具体的各状态,主要是写和读状态。
首先来看看写过程,为了便于理解,下面给出了ILA中采集到的DDR3写的波形,同时为了方便观察完整的写过程,把本次实验的一次读写长度设为10。如下图所示:

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

图 30.4.26 DDR3读时序
读操作结束又跳回空闲状态,如代码第98行所示。
代码第120行到127行的always模块,产生rd_cnt,实现了对读出的地址数据的编号。为什么会有这样的处理,其实根本原因就是读数据的返回和读操作并不跟写那样是在同一拍出现的,会晚若干周期出现。这样意味着不能用读数据直接和当前的读地址去比较,所以本次实验单独构造了rd_cnt这个信号,它代表一次读操作真正返回的有效的读数据个数。
代码第130行到135行的always模块,实现了错误标识的寄存。代码第138行到154行的always模块实现读写测试LED灯的显示逻辑。错误则闪烁,正确则常亮。
最后通过采集到的波形来看读写测试结果,在读数据有效信号app_rd_data_valid为高时,数据总线上的app_rd_data分别和地址编号相等rd_cnt相等。按照本次实验的设计,写进DDR3的数值其实就是它的地址编号,也即等于数据编号,这就是正确的读写结果。当然大家可以看到错误指示信号error_flag始终为0,即没有出现读写错误。

图 30.4.27 DDR3读结果
以上就是所有本次实验的代码讲解。
30.5下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,连接电源线,并打开开发板的电源开关。
点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Progam Device”下载程序,在弹出的界面中选择“Program”下载程序。
程序下载完成后,开发板上的LED0在短暂延时之后,始处于常亮的状态,如下图所示:

图 30.5.1 DDR3板子测试结果
此时说明的DDR3读写测试成功。
页: [1]
查看完整版本: 【正点原子FPGA连载】第三十章DDR3读写测试