OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 653|回复: 0

[国产FPGA] 《ATK-DFPGL22G 之FPGA开发指南》第三十八章 DDR3读写测试实验

[复制链接]

1061

主题

1072

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4407
金钱
4407
注册时间
2019-5-8
在线时间
1196 小时
发表于 2023-12-18 17:06:10 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-12-18 17:06 编辑

第三十八章 DDR3读写测试实验

1)实验平台:正点原子 ATK-DFPGL22G开发板

2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-PGL22G.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)FPGA技术交流QQ群:435699340

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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

1.1 DDR3简介
DDR3SDRAM(Double-Data-Rate Three Synchronous Dynamic Random Access Memory)是DDR SDRAM的第三代产品,相较于DDR和DDR2,DDR3有更高的运行性能与更低的电压。DDR SDRAM是在SDRAM技术的基础上发展改进而来的,同SDRAM相比,DDR SDRAM的最大特点是双沿触发,即在时钟的上升沿和下降沿都能进行数据采集和发送。同样的工作时钟,DDR SDRAM的读写速度可以比传统的SDRAM快一倍。本次实验使用的DDR3芯片是南亚的NT5CC256M16。
由于DDR3的时序非常复杂,如果直接编写DDR3的控制器代码,那么工作量是非常大的,且性能难以得到保证。值得一提的是,PGL22系列FPGA自带了DDR3控制器的硬核,用户可以直接借助IP核来实现对DDR3的读写操作,从而大大降低了DDR3的开发难度。本次实验将使用紫光公司的Logos HMIC_H IP核来实现DDR3读写测试。
HMIC_HIP是深圳市紫光同创电子有限公司FPGA产品中用于实现对SDRAM读写而设计的IP,通过紫光同创公司Pango Design Suite套件(后文简称 PDS)中IP Compiler工具(后文简称IPC)例化生成IP模块。
HMIC_HIP系统框图如下图所示:                             
image001.png
图 38.1.1 HMIC_H IP系统框图
HMIC_HIP包括了DDR Controller、DDR PHY和PLL,用户通过AXI4接口实现数据的读写,通过APB接口可配置DDR Controller内部寄存器,PLL用于产生需要的各种时钟。
AXI4 接口:HMIC_H IP提供三组AXI4 Host Port:AXI4 Port0(128bit)、AXI4 Port1(64bit)、AXI4 Port2(64bit)。用户通过HMIC_H IP界面可以选择使能这三组AXI4 Port。三组AXI4 Host Port均为标准AXI4接口。
APB接口:HMIC_H IP提供一个APB配置接口,通过该接口,可配置DDR Controller内部寄存器。HMIC_H IP可通过APB接口对内部DDRC配置寄存器进行读写,在初始化阶段,IP将配置DDRC内部的配置寄存器,如果用户需要读写DDRC内部寄存器,需要在初始化完成后进行操作。 由于IP初始化阶段已将DDRC内部寄存器进行了正确的配置,因此不建议用户在初始化完成后随意更改配置寄存器的值。
各个接口具体的端口说明可以详见紫光的《Logos 系列产品 HMIC_H IP用户指南》文档,该文档我们放在了工程文件夹下的doc文件夹中,路径如下图所示。
image003.png
图 38.1.2 《Logos 系列产品 HMIC_H IP用户指南》所在路径
本设计AXI4接口为标准的AXI4协议接口,接口时序可参考AXI4协议,下表为部分关键信号的接口说明。
QQ截图20231218155309.png
表 38.1.1 AXI4协议部分关键信号接口说明

AXI总线共有5个独立的通道,分别是read address channel (ARxxx),write address channel(AWxxx),read data channel(Rxxx),write data channel(Wxxx),write response channel(Bxxx)。每一个AXI传输通道都是单方向的,且都包含一个信息信号和一个双路的VALID、READY握手机制。信息源通过VALID信号来指示通道中的数据和控制信息什么时候有效。目地源用READY信号来表示何时能够接收数据。读数据和写数据通道都包括一个LAST信号,用来指明一个事物传输的最后一个数据。
主机/设备之间的握手过程以及READY和VALID握手信号的关系如下:
全部5个通道使用相同的VALID/READY握手机制传输数据及控制信息。传输源产生VALID信号来指明何时数据或控制信息有效。而目地源产生READY信号来指明已经准备好接受数据或控制信息。传输发生在VALID和READY信号同时为高的时候。VALID和READY信号的出现有三种关系。分别为VALID先变高READY后变高(时序如图 38.1.3所示)、READY先变高VALID后变高(时序如图 38.1.4所示)和VALID和READY信号同时变高(时序如图 38.1.5所示)。图中箭头处信息传输发生。
image004.png
图 38.1.3 VALID先变高READY后变高时序图
image006.png
图 38.1.4 READY先变高VALID后变高时序图
image008.png
图 38.1.5 VALID和READY信号同时变高时序图
地址、读、写和写响应通道之间的关系是灵活的。例如,写数据可以出现在接口上早于与其相关联的写地址。也有可能写数据与写地址在一个周期中出现。但是有两种关系必须被保持:一是读数据必须总是跟在与其数据相关联的地址之后;二是写响应必须总是跟在与其相关联的写事务的最后出现。
通道握手信号之间是有依赖性的,读事务握手依赖关系如图 38.1.6所示,读事务握手时,设备可以在ARVALID出现的时候再给出ARREADY信号,也可以先给出ARREADY信号,再等待ARVALID信号;但是设备必须等待ARVALID和ARREADY信号都有效才能给出RVALID信号,开始数据传输。
image009.png
图 38.1.6 读事务握手依赖关系图
写事务握手依赖关系如图 38.1.7所示,写事务握手时,主机不能等设备给出AWREADY或WREADY信号后再给出信号AWVALID或WVALID;设备可以等待信号AWVALID或WVALID信号有效或者两个都有效之后再给出AWREADY信号。
image010.png
图 38.1.7 写事务握手依赖关系图
AXI4 读时序:均以AXI4 Port0为例。
AXI4接口单次读操作的时序如图 38.1.8所示,主设备发送地址,一个周期后从设备接收。主设备在发送地址的同时也发送了一些控制信息标志了Burst(突发)的程度和类型,为了保持图片的清晰性,在此省略这些信号。用户拉高arvalid_0信号后等待arready_0拉高,当arvalid_0和arready_0信号同时为高时,表示读地址有效,此后在读数据通道上发生数据的传输。
同理用户拉高rready_0信号后等待rvalid_0拉高,当rready_0和rvalid_0信号同时为高时,表示读数据有效。当rlast_0拉高时,表示在告诉用户当前为此次读操作的最后一个数据。
image011.png
图 38.1.8 AXI4接口单次读时序
AXI4接口连续读操作的时序如图 38.1.9所示,主设备在从设备接收第一次读操作的地址后发送下一次读操作的地址。这样可以保证一个从设备在完成第一次读操作的同时可以开始处理第二次读操作的数据。下图中进行了两次连续读操作,可以看出也相应的拉高了两次rlast_0信号,对应第一次读操作的最后一个数据和第二次读操作的最后一个数据。
image013.png
图 38.1.9 AXI4接口连续读时序
AXI4 写时序:均以AXI4 Port0为例。
AXI4 接口单次写操作的时序如图 38.1.10所示,当主设备发送地址和控制信息到写地址通道之后,写操作开始。然后主设备通过写数据通道发送每一个写数据,当为最后一个需要发送的数据时,主设备将wlast_0信号置高。当从设备接收完所有的数据时,从设备返回给主设备一个写响应标志本次写操作的结束。连续写操作与连续读操作类似,即主设备在从设备接收第一次写操作的地址后发送下一次写操作的地址。例如:用户拉高awvalid_0信号后等待awready_0信号拉高,当awvalid_0和awready_0信号同时为高时,表示写地址有效。同理用户拉高wvalid_0信号后等待wready_0信号拉高,当wvalid_0和wready_0信号同时为高时,表示写数据有效。当用户拉高wlast_0信号时,表示当前为此次写数据操作的最后一个数据。
image015.png
图 38.1.10 AXI4接口单次写时序

1.2 实验任务
本节的实验任务是先向DDR3的存储器中写入5120个数据,写完之后再从存储器中读取相同地址的数据。若初始化成功,则LED0常亮,否则LED0不亮;若读取的值全部正确则LED1常亮,否则LED1闪烁。

1.3 硬件设计
ATK-DFPGL22G开发板上使用了一片南亚的DDR3颗粒NT5CC256M16,硬件原理图如下图所示。在PCB的设计上,完全遵照紫光的DDR3硬件设计规范,严格保证等长设计和阻抗控制,从而保证高速信号的数据传输的可靠性。
image017.png
图 38.3.1 DDR3硬件原理图
本次实验用到的所有管脚的管脚分配如下表所示。
QQ截图20231218155216.png
QQ截图20231218155236.png
QQ截图20231218155248.png
表 38.3.1 DDR3读写测试实验管脚分配

相关的管脚约束如下所示:
  1. <font face="Tahoma">create_clock-name {sys_clk} [get_ports {sys_clk}] -period {20.000} -waveform {0.000 10.000}
  2. define_attribute{p:sys_clk} {PAP_IO_DIRECTION} {INPUT}
  3. define_attribute{p:sys_clk} {PAP_IO_LOC} {B5}
  4. define_attribute{p:sys_clk} {PAP_IO_VCCIO} {3.3}
  5. define_attribute{p:sys_clk} {PAP_IO_STANDARD} {LVCMOS33}
  6. define_attribute{p:sys_clk} {PAP_IO_PULLUP} {TRUE}
  7. define_attribute{p:sys_rst_n} {PAP_IO_DIRECTION} {INPUT}
  8. define_attribute{p:sys_rst_n} {PAP_IO_LOC} {G5}
  9. define_attribute{p:sys_rst_n} {PAP_IO_VCCIO} {1.5}
  10. define_attribute{p:sys_rst_n} {PAP_IO_STANDARD} {SSTL15_I}
  11. define_attribute{p:sys_rst_n} {PAP_IO_PULLUP} {TRUE}
  12. define_attribute{p:led_error} {PAP_IO_DIRECTION} {OUTPUT}
  13. define_attribute{p:led_error} {PAP_IO_LOC} {J6}
  14. define_attribute{p:led_error} {PAP_IO_VCCIO} {1.5}
  15. define_attribute{p:led_error} {PAP_IO_STANDARD} {SSTL15_I}
  16. define_attribute{p:led_error} {PAP_IO_DRIVE} {7.5}
  17. define_attribute{p:led_error} {PAP_IO_NONE} {TRUE}
  18. define_attribute{p:led_error} {PAP_IO_SLEW} {SLOW}
  19. define_attribute{p:led_ddr_init_done} {PAP_IO_DIRECTION} {OUTPUT}
  20. define_attribute{p:led_ddr_init_done} {PAP_IO_LOC} {F3}
  21. define_attribute{p:led_ddr_init_done} {PAP_IO_VCCIO} {1.5}
  22. define_attribute{p:led_ddr_init_done} {PAP_IO_STANDARD} {SSTL15_I}
  23. define_attribute{p:led_ddr_init_done} {PAP_IO_DRIVE} {7.5}
  24. define_attribute{p:led_ddr_init_done} {PAP_IO_NONE} {TRUE}
  25. define_attribute{p:led_ddr_init_done} {PAP_IO_SLEW} {SLOW}</font>
复制代码
由于约束文件过长,为减少篇幅这里只展示了部分,需要注意的是ddr3相关的引脚约束不需要我们进行手动约束,可以将官方生成的约束文件中ddr3相关的约束代码复制进我们的约束文件即可。官方约束文件所在路径如下图所示:
image019.png
图 38.3.2 官方约束文件所在路径
因为我们的主控芯片是PGL22G-6MBG324,我们硬件设计中ddr3引脚分配在L1和L2 bank,所以我们选择将ddr_324_left.fdc文件中ddr3相关的约束代码复制进我们的约束文件。
除了将ddr3相关的约束代码复制进来外,还要再将如下约束复制进我们工程的约束文件中。
define_attribute{i:u_ipsl_hmic_h_top.u_pll_50_400.u_pll_e1} {PAP_LOC} {PLL_82_71}
复制进来后需要将路径定位改成我们工程中的定位,针对本例程的修改后代码如下。
define_attribute{i:u_ddr3_top.u_ddr3_ip.u_pll_50_400.u_pll_e1} {PAP_LOC} {PLL_82_71}
上述约束语句格式是固定的,其中只有第一个{}和第三个{}内的代码是用户修改的。第一个{}内“i:”后是的代码是用户定义的,此处代码是为了定位到所需约束的PLL在工程中的位置,这里位置信息不包括顶层。第三个{}内为约束PLL编号,PLL编号可从tcl中查找。方法如下。
image021.png
图 38.3.3 打开Physical Constraint Editor界面
打开Physical Constraint Editor界面后点击TCL图标。
image023.png
图38.3.4 点击TCL图标
下图中六个红框分别对应六个PLL编号(由上到下分别表示PLL0~PLL5),点击我们需要约束的PLL,即可在下方看到其对应编号。
image024.png
图38.3.5 查看PLL编号
如何确定需要对哪个PLL进行约束可参照下图
image026.png
图38.3.6 Logos的I/O时钟网络输入源
因为我们这里是对ddr3 IP核中用到的PLL进行约束,而我们的ddr3芯片位于bankL1和bankL2,我们的主控芯片是PLG22G系列,所以从图中可知,我们应该将其约束到PPL3的编号或者PLL4的编号。这里我们选择的是PLL4对应的编号。
这里简单的介绍一下PAP_LOC属性设置。
功能:PAP_LOC是位置约束,map时会转化为pcf中的位置约束命令def_ inst_site。
对象:作用对象通常是一些可以place到特殊资源(如APM)的一些instance。
属性值:属性值的形式是一个具体的device instance的名字。
描述:PAP_LOC属性是通过DB文件向下传递的,作用对象通常是一些可以place到特殊资源的一些instance,在map的过程中,这个属性会传递给这个top转化的gop对象。
使用说明:在UCE中设置PAP_LOC属性时,支持对属性对象和属性值进行检查。此命令只在约束文本中编写。
表现形式:在UCE的Device界面可以看到初始阶段的约束,最终属性的表现形式在PCE中可以看到,指定的约束对象会在属性值所指定的位置上被约束。

1.4 程序设计
根据实验任务,可以大致规划出系统的控制流程:首先FPGA调用ddr3测试数据模块向ddr3控制模块写入数据,写完之后ddr测试数据模块从ddr3控制模块读出所写入的数据,并判断读出的数据与写入的数据是否相同,如果相同则LED1灯常亮,否则LED1灯闪烁。由此画出系统的功能框图如下图所示:                             
image028.png
图 38.4.1 DDR3读写实验系统框图
由系统总体框图可知,FPGA顶层模块例化了以下三个模块,分别是ddr3控制模块(ddr3_top)、ddr测试数据模块(ddr_test)和led显示模块(led_disp)。
ddr3控制模块产生读写DDR3 IP核用户接口的时序,实现与DDR3 IP核的数据及信号交互。ddr3控制模块一方面负责与用户(FPGA)进行数据交互,另一方面还产生控制DDR3读写的各种时序,并实现对DDR芯片的读写操作。ddr测试数据模块的作用是写入和读出ddr3控制器的数据并且将读写数据进行比较。led显示模块是根据读写错误信号的高低来判断是否翻转 LED 灯的电平,以及显示ddr3初始化完成情况。
首先在PDS环境里新建一个工程,工程命名为ddr3_rw_top。点击菜单栏中的Tools下拉菜单打开IP Compiler如下图所示。
image030.png
图 38.4.2 打开IP Compiler界面
因为PDS软件默认安装是不带有DDR3 IP核的,所以这里需要我们手动将ddr3的IP核添加进来,添加方法如下所述:
首先在IP Compiler界面中选择菜单栏中的File下的Update,如下图所示。
image032.png
图 38.4.3 打开Update IP界面
点击Update IP界面中的Add Packages,然后添加DDR3 IP核如下图所示。
image033.png
图 38.4.4 点击Add Packages添加IP核
点击后找到本次实验所需的DDR3IP核(1_ipsl_hmic_h_v1_1.iar),该文件存放在工程文件夹下的prj文件夹内的ipcore文件夹中,如图 38.4.5所示,单击选中后点击open,操作如图 38.4.6所示。
image034.png
图 38.4.5 1_ipsl_hmic_h_v1_1.iar所在路径
image035.png
图 38.4.6添加DDR3 IP核
然后点击Install安装DDR3 IP核即可完成添加。
image037.png
图 38.4.7安装DDR3 IP核
此时可以看到IP Compiler界面已经添加了新的IP“Logos HMIC_H(1.1)”,选择Logos HMIC_H(1.1)并在右侧命名为ddr3_ip,然后点击Customize进行配置,如下图所示。
image039.png
图 38.4.8创建DDR3 IP核
HMIC_HIP配置分为四个页面,分别为Step1: Basic Options,Step2: Memory Options,Step3:Interface Options,Step4:Summary,请务必按照该页面顺序配置。
Step 1:Basic Options,是IP的基本配置页面,页面如下图所示:
image041.png
图 38.4.9 Step 1: Basic Options设置
MemoryType:使用的SDRAM类型,目前支持的类型有DDR3、DDR2和LPDDR。这里我们选择DDR3类型。
ControllerLocation:Controller在FPGA芯片的位置,目前PGL22支持的位置有Right (BANK R1 + BANK R2)和Left (BANK L1+ BANK L2)两种(注:HMIC_H为硬核,位置固定不可变动)。因为开发板的硬件设计中,DDR3芯片接到的BANK L1和BANK L2,所以这里我们选择Lift (BANK L1 + BANK L2)。
IOStandard:接口标准选项,DDR3支持的接口标准有SSTL15_I和SSTL15_II两种,这里我们保持默认即可。
OperatingMode:HMIC_H运行模式选择。目前只支持Controller + PHY模式,所以保持默认即可。
TotalData Width:与HMIC_H连接的片外SDRAM总共的DQ宽度,目前支持的总宽度有8和16。我们的数据位宽为16,所以这里保持默认值16即可。
InputClock Frequency:HMIC_H的输入时钟,单位MHz。和开发板板载晶振频率保持一致,因为板载晶振保频率为50MHz,默认值也为50MHz,所以此处保持默认即可。
DesiredData Rate:期望的数据速率, DDR3支持的最高速率为1066Mbps。保持默认即可,因为DDR3是双沿触发,即在时钟的上升沿和下降沿都能进行数据采集和发送,所以数据时钟配置实际上为数据速率的一半,例如:800Mbps的数据速率,实际上只需要输出400MHz的时钟即可。
ActualData Rate:实际能达到的数据速率,尽可能接近期望的速率。保持默认即可。
Step 2:Memory Options,是Memory参数的配置页面,页面如下图所示:
image043.png
图 38.4.10 Step 2: Memory Options设置
MemoryPart:SDRAM芯片的具体型号。我们板载的DDR3芯片为NT5CC256M16,因为PDS器件选项中没有NT5CC256M16,所以这里我们选择与之兼容的MT41J256M16XX-15E。
CustomMemory Part:该选项框在勾选Create Custom Part选项时显示,用于定制新的SDRAM类型。包含选项:Base Part、Timing Parameters、Row Address、Column Address、Bank ddress。我们不需要定制新的SDRAM类型,所以这里保持默认不做勾选。
DriveOptions:驱动能力选项,DDR3支持的驱动选项为Output Driver ImpedanceControl和T(nominal)-ODT。是内部高性能bank端接匹配阻抗的设置,这里不去改它,选择默认即可。
Step 3:Interface Options,是接口参数的配置页面,页面如下图所示:
image045.png
图 38.4.11 Step 3: Interface Options设置
APBinterface:APB接口配置,包括使能和APB接口的时钟频率。APB(Advanced Peripheral Bus)外围总线,是一种常见的总线协议。APB属于AMBA 3协议系列,它提供了一个低功耗的接口,并降低了接口的复杂性。APB接口用在低带宽和不需要高性能总线的外围设备上。APB是非流水线结构,所有的信号仅与时钟上升沿相关,这样就可以简化APB外围设备的设计流程,每个传输至少耗用两个周期。APB可以与AMBA高级高性能总线(AHB-Lite)和AMBA高级可扩展接口(AXI)连接。因为这里我们没有用到APB接口,所以保持默认不启用即可。
AXIinterface: 三组AXI4接口的配置,包括使能、读写方向、时钟频率。这里我们只用到了第一组接口,所以保持默认启用AXI Port0即可。
MemoryAddress Mapping Selection: SDRAM的地址与AXI4地址映射选项。无需改动,保持默认即可。需要注意的是AXI4的读写地址为32位,但是只有A1-A31有效,A0为无效位。
Step 4:Summary页面,用于打印当前的配置信息,不需要配置参数,页面如下图所示:
image047.png
图 38.4.12 Step 4: Summary页面
配置完成后,点击保存(上图1号标签),保存后再点击Generate(上图2号标签)生成IP,即可生成相应于用户特定设置的HMIC_H IP代码。生成IP的信息报告界面如下所示。
image049.png
图 38.4.13 生成IP的信息报告界面
至此ddr3 IP核创建完成,点击关闭窗口即可。
DDR3 IP核的接口信号很多,使用时如果每次都要进行新的配置会很繁琐,所以我们将DDR3控制器封装成类似于FIFO的接口,再使用时只需要像读写FIFO那样给出读/写使能即可,这么做就方便了我们在以后的其他工程中对其进行调用。
rd_fifoIP核参数配置如下图所示:
image051.png
图 38.4.14 rd_fifo IP核参数配置界面
wr_fifoIP核参数配置如下图所示:
image053.png
图 38.4.15 wr_fifo IP核参数配置界面
系统的顶层模块代码如下:
  1. 1   module ddr3_rw_top(
  2. 2        input            sys_clk          , //系统时钟50M
  3. 3        input            sys_rst_n        , //系统复位
  4. 4        output           led_error        , //读写错误led灯
  5. 5        output           led_ddr_init_done, //ddr3初始化完成led灯
  6. 6   
  7. 7        //DDR3接口
  8. 8        input            pad_loop_in      , //低位温度补偿输入
  9. 9        input            pad_loop_in_h    , //高位温度补偿输入
  10. 10       output           pad_rstn_ch0     , //Memory复位
  11. 11       output           pad_ddr_clk_w    , //Memory差分时钟正端
  12. 12       output           pad_ddr_clkn_w   , //Memory差分时钟负端
  13. 13       output           pad_csn_ch0      , //Memory片选
  14. 14       output [15:0    pad_addr_ch0     , //Memory地址总线
  15. 15       inout  [16-1:0  pad_dq_ch0       , //数据总线
  16. 16       inout  [16/8-1:0 pad_dqs_ch0     , //数据时钟正端
  17. 17       inout  [16/8-1:0 pad_dqsn_ch0    , //数据时钟负端
  18. 18       output [16/8-1:0 pad_dm_rdqs_ch0 , //数据Mask
  19. 19       output           pad_cke_ch0      , //Memory差分时钟使能
  20. 20       output           pad_odt_ch0      , //On DieTermination
  21. 21       output           pad_rasn_ch0     , //行地址strobe
  22. 22       output           pad_casn_ch0     , //列地址strobe
  23. 23       output           pad_wen_ch0      , //写使能
  24. 24       output [2:0      pad_ba_ch0       , //Bank地址总线
  25. 25       output           pad_loop_out     , //低位温度补偿输出
  26. 26       output           pad_loop_out_h     //高位温度补偿输出   
  27. 27      );
  28. 28  
  29. 29  //parameter define
  30. 30  parameter  APP_ADDR_MIN = 28'd0    ;  //ddr3读写起始地址,以一个16bit的数据为一个单位
  31. 31  //APP_ADDR_MAX =BURST_LENGTH * 8 * (n+1)(n表示突发次数)
  32. 32  parameter  APP_ADDR_MAX = 28'd5120 ;  //ddr3读写结束地址,以一个16bit的数据为一个单位
  33. 33  parameter  BURST_LENGTH = 8'd64    ;  //ddr3读写突发长度,64个128bit的数据
  34. 34  parameter  DATA_MAX = APP_ADDR_MAX - APP_ADDR_MIN;  //读写ddr3的最大数据量
  35. 35  
  36. 36  //wire define
  37. 37  wire  [15:0  wr_data        ;  //DDR3控制器模块写数据
  38. 38  wire  [15:0  rd_data        ;  //DDR3控制器模块读数据
  39. 39  wire          wr_en          ;  //DDR3控制器模块写使能
  40. 40  wire          rd_en          ;  //DDR3控制器模块读使能
  41. 41  wire          ddr_init_done  ;  //ddr3初始化完成信号
  42. 42  wire          error_flag     ;  //ddr3读写错误标志
  43. 43  
  44. 44  ////*****************************************************
  45. 45  ////**                    main code
  46. 46  ////*****************************************************
  47. 47  //ddr3控制器顶层模块
  48. 48  ddr3_top u_ddr3_top(
  49. 49   .refclk_in             (sys_clk         ),
  50. 50   .rst_n                 (sys_rst_n       ),
  51. 51   .app_addr_rd_min       (APP_ADDR_MIN    ),
  52. 52   .app_addr_rd_max       (APP_ADDR_MAX    ),
  53. 53   .rd_bust_len           (BURST_LENGTH    ),
  54. 54   .app_addr_wr_min       (APP_ADDR_MIN    ),
  55. 55   .app_addr_wr_max       (APP_ADDR_MAX    ),
  56. 56   .wr_bust_len           (BURST_LENGTH    ),
  57. 57   .wr_clk                (sys_clk         ),
  58. 58   .rd_clk                (sys_clk         ),
  59. 59   .datain_valid          (wr_en           ),
  60. 60   .datain                (wr_data         ),
  61. 61   .rdata_req             (rd_en           ),
  62. 62   .dataout               (rd_data         ),
  63. 63   .ddr_init_done         (ddr_init_done   ),
  64. 64   //DDR3接口
  65. 65   .pad_loop_in           (pad_loop_in     ),
  66. 66   .pad_loop_in_h         (pad_loop_in_h   ),
  67. 67   .pad_rstn_ch0          (pad_rstn_ch0    ),
  68. 68   .pad_ddr_clk_w         (pad_ddr_clk_w   ),
  69. 69   .pad_ddr_clkn_w        (pad_ddr_clkn_w  ),
  70. 70   .pad_csn_ch0           (pad_csn_ch0     ),
  71. 71   .pad_addr_ch0          (pad_addr_ch0    ),
  72. 72   .pad_dq_ch0            (pad_dq_ch0      ),
  73. 73   .pad_dqs_ch0           (pad_dqs_ch0     ),
  74. 74   .pad_dqsn_ch0          (pad_dqsn_ch0    ),
  75. 75   .pad_dm_rdqs_ch0       (pad_dm_rdqs_ch0 ),
  76. 76   .pad_cke_ch0           (pad_cke_ch0     ),
  77. 77   .pad_odt_ch0           (pad_odt_ch0     ),
  78. 78   .pad_rasn_ch0          (pad_rasn_ch0    ),
  79. 79   .pad_casn_ch0          (pad_casn_ch0    ),
  80. 80   .pad_wen_ch0           (pad_wen_ch0     ),
  81. 81   .pad_ba_ch0            (pad_ba_ch0      ),
  82. 82   .pad_loop_out          (pad_loop_out    ),
  83. 83   .pad_loop_out_h        (pad_loop_out_h  )
  84. 84   );  
  85. 85  
  86. 86  //ddr3测试数据模块
  87. 87  ddr_test u_ddr_test(
  88. 88       .clk_50m       (sys_clk        ),    //时钟
  89. 89       .rst_n         (sys_rst_n      ),    //复位,低有效
  90. 90       .wr_en         (wr_en          ),    //写使能
  91. 91       .wr_data       (wr_data        ),    //写数据
  92. 92       .rd_en         (rd_en          ),    //读使能
  93. 93       .rd_data       (rd_data        ),    //读数据
  94. 94       .data_max      (DATA_MAX       ),    //读写ddr的最大数据量
  95. 95       .ddr3_init_done(ddr_init_done   ),    //ddr3初始化完成信号
  96. 96       .error_flag    (error_flag     )     //ddr3读写错误
  97. 97       );
  98. 98  
  99. 99  //利用LED灯指示ddr3读写测试的结果及ddr3是否初始化完成
  100. 100 led_disp u_led_disp(
  101. 101      .clk_50m            (sys_clk         ),
  102. 102      .rst_n              (sys_rst_n       ),
  103. 103      .ddr3_init_done     (ddr_init_done   ),
  104. 104      .error_flag         (error_flag      ),
  105. 105      .led_error          (led_error       ),
  106. 106      .led_ddr_init_done  (led_ddr_init_done)
  107. 107      );
  108. 108
  109. 109 endmodule
复制代码

在代码的第30~34行我们定义了四个参数,分别为ddr3读写起始地址(APP_ADDR_MIN)、ddr3读写结束地址(APP_ADDR_MAX)、ddr3读写突发长度(BURST_LENGTH)以及读写ddr3的最大数据量(DATA_MAX)。其中APP_ADDR_MIN、APP_ADDR_MAX和DATA_MAX是以一个16bit的数据为一个单位,BURST_LENGTH是以一个128bit的数据为一个单位。
APP_ADDR_MAX= APP_ADDR_MIN + BURST_LENGTH * 8 * n(n表示突发次数)
DATA_MAX= APP_ADDR_MAX - APP_ADDR_MIN
对于突发长度(BURST_LENGTH)的设置,根据配置,列地址是10位,列地址边界就是1023,一次突发结束地址不能超过1023(即1024个字节),超过就需要分两次,分两次的话实测是有可能会发生列地址回滚现象的,即列地址回滚到0(起始地址),覆盖了一部分以0为起始地址的数据,由于22G器件的DDR是硬核IP,无法规避,只能从应用层控制,所以这里建议设置为2的整数次幂且不要超过64(例如2、4、8、16、32、64),以此来规避可能出现的因为回滚造成数据覆盖而导致读写错误问题。
由于DDR3控制器被封装成FIFO接口,在使用时只需要像读/写FIFO那样给出读/写使能即可,如代码51~62行所示。同时定义了最大和最小读写地址,在调用时数据在该地址空间中连续读写。
程序的53行及56行指定DDR3控制器的数据突发长度,由于DDR3 IP核的突发长度位宽为8位,因此控制器的突发长度不能大于255。
ddr3控制模块代码如下:
  1. 1   module ddr3_top(
  2. 2        input             refclk_in        ,//外部参考时钟输入
  3. 3        input             rst_n            ,//外部复位输入
  4. 4   
  5. 5        input   [27:0    app_addr_rd_min  ,//读ddr3的起始地址
  6. 6        input   [27:0    app_addr_rd_max  ,//读ddr3的结束地址
  7. 7        input   [7:0     rd_bust_len      ,//从ddr3中读数据时的突发长度
  8. 8        input   [27:0    app_addr_wr_min  ,//读ddr3的起始地址
  9. 9        input   [27:0    app_addr_wr_max  ,//读ddr3的结束地址
  10. 10       input   [7:0     wr_bust_len      ,//从ddr3中读数据时的突发长度
  11. 11       //用户     
  12. 12       input             wr_clk           ,//wfifo写时钟  
  13. 13       input             rd_clk           ,//rfifo读时钟     
  14. 14       input             datain_valid     ,//数据有效使能信号
  15. 15       input   [15:0    datain           ,//有效数据
  16. 16       input              rdata_req        ,//请求数据输入
  17. 17       output  [15:0    dataout          ,//rfifo输出数据
  18. 18  
  19. 19       output            ddr_init_done    ,//DDR初始化完成
  20. 20       input             pad_loop_in      ,
  21. 21       input             pad_loop_in_h    ,
  22. 22       output            pad_rstn_ch0     ,
  23. 23       output            pad_ddr_clk_w    ,
  24. 24       output            pad_ddr_clkn_w   ,
  25. 25       output            pad_csn_ch0      ,
  26. 26       output [15:0      pad_addr_ch0     ,
  27. 27       inout  [16-1:0   pad_dq_ch0       ,
  28. 28       inout  [16/8-1:0 pad_dqs_ch0      ,
  29. 29       inout  [16/8-1:0 pad_dqsn_ch0     ,
  30. 30       output [16/8-1:0 pad_dm_rdqs_ch0  ,
  31. 31       output            pad_cke_ch0      ,
  32. 32       output            pad_odt_ch0      ,
  33. 33       output            pad_rasn_ch0     ,
  34. 34       output            pad_casn_ch0     ,
  35. 35       output            pad_wen_ch0      ,
  36. 36       output [2:0       pad_ba_ch0       ,
  37. 37       output            pad_loop_out     ,
  38. 38       output            pad_loop_out_h
  39. 39      );
  40. 40  
  41. 41  //wire define
  42. 42       wire  [32-1:0   axi_awaddr     ;
  43. 43       wire  [7:0      axi_awlen      ;
  44. 44       wire  [2:0      axi_awsize     ;
  45. 45       wire  [1:0      axi_awburst    ;
  46. 46       wire             axi_awlock     ;
  47. 47       wire             axi_awready    ;
  48. 48       wire             axi_awvalid    ;
  49. 49       wire             axi_awurgent   ;
  50. 50       wire             axi_awpoison   ;
  51. 51       wire  [128-1:0  axi_wdata      ;
  52. 52       wire  [16-1:0   axi_wstrb      ;
  53. 53       wire             axi_wvalid     ;
  54. 54      wire             axi_wready     ;
  55. 55       wire             axi_wlast      ;
  56. 56       wire             axi_bready     ;
  57. 57       wire  [32-1:0   axi_araddr     ;
  58. 58       wire  [7:0      axi_arlen      ;
  59. 59       wire  [2:0      axi_arsize     ;
  60. 60       wire  [1:0      axi_arburst    ;
  61. 61       wire             axi_arlock     ;
  62. 62       wire             axi_arpoison   ;
  63. 63       wire             axi_arurgent   ;
  64. 64       wire             axi_arready    ;
  65. 65       wire             axi_arvalid    ;
  66. 66       wire  [128-1:0  axi_rdata      ;
  67. 67       wire             axi_rlast      ;
  68. 68       wire             axi_rvalid     ;
  69. 69       wire             axi_rready     ;
  70. 70       wire             axi_clk        ;
  71. 71       wire [10:0      wfifo_rcount   ;//rfifo剩余数据计数
  72. 72       wire [10:0      rfifo_wcount   ;//wfifo写进数据计数
  73. 73       wire             wrfifo_en_ctrl ;//写FIFO数据读使能控制位
  74. 74       wire             wfifo_rden     ;//写FIFO数据读使能
  75. 75       wire             pre_wfifo_rden ;//写FIFO数据预读使能
  76. 76  
  77. 77  //*****************************************************
  78. 78  //**                    main code
  79. 79  //*****************************************************
  80. 80  //因为预读了一个数据所以读使能wfifo_rden要少一个周期通过wrfifo_en_ctrl控制
  81. 81  assign wfifo_rden = axi_wvalid && axi_wready && (~wrfifo_en_ctrl) ;
  82. 82  assign pre_wfifo_rden = axi_awvalid && axi_awready ;
  83. 83  
  84. 84  //ddr3读写控制器模块
  85. 85  rw_ctrl_128bit  u_rw_ctrl_128bit
  86. 86  (
  87. 87   .clk                 (axi_clk          ),
  88. 88   .rst_n               (rst_n            ),
  89. 89   .ddr_init_done       (ddr_init_done    ),
  90. 90   .axi_awaddr          (axi_awaddr       ),
  91. 91   .axi_awlen           (axi_awlen        ),
  92. 92   .axi_awsize          (axi_awsize       ),
  93. 93   .axi_awburst         (axi_awburst      ),
  94. 94   .axi_awlock          (axi_awlock       ),
  95. 95   .axi_awready         (axi_awready      ),
  96. 96   .axi_awvalid         (axi_awvalid      ),
  97. 97   .axi_awurgent        (axi_awurgent     ),
  98. 98   .axi_awpoison        (axi_awpoison     ),
  99. 99   .axi_wstrb           (axi_wstrb        ),
  100. 100  .axi_wvalid          (axi_wvalid       ),
  101. 101  .axi_wready          (axi_wready       ),
  102. 102  .axi_wlast           (axi_wlast        ),
  103. 103  .axi_bready          (axi_bready       ),
  104. 104  .wrfifo_en_ctrl      (wrfifo_en_ctrl   ),
  105. 105  .axi_araddr          (axi_araddr       ),
  106. 106  .axi_arlen           (axi_arlen        ),
  107. 107  .axi_arsize          (axi_arsize       ),
  108. 108  .axi_arburst         (axi_arburst      ),
  109. 109  .axi_arlock          (axi_arlock       ),
  110. 110  .axi_arpoison        (axi_arpoison     ),
  111. 111  .axi_arurgent        (axi_arurgent     ),
  112. 112  .axi_arready         (axi_arready      ),
  113. 113  .axi_arvalid         (axi_arvalid      ),
  114. 114  .axi_rlast           (axi_rlast        ),
  115. 115  .axi_rvalid          (axi_rvalid       ),
  116. 116  .axi_rready          (axi_rready       ),
  117. 117  .wfifo_rcount        (wfifo_rcount    ),
  118. 118  .rfifo_wcount        (rfifo_wcount     ),
  119. 119  .app_addr_rd_min     (app_addr_rd_min  ),
  120. 120  .app_addr_rd_max     (app_addr_rd_max  ),
  121. 121  .rd_bust_len         (rd_bust_len      ),
  122. 122  .app_addr_wr_min     (app_addr_wr_min  ),
  123. 123  .app_addr_wr_max     (app_addr_wr_max  ),
  124. 124  .wr_bust_len         (wr_bust_len      )
  125. 125  );
  126. 126
  127. 127  //ddr3IP核模块
  128. 128  ddr3_ip u_ddr3_ip (
  129. 129   .pll_refclk_in    (refclk_in      ), // input
  130. 130   .top_rst_n        (rst_n          ), // input
  131. 131   .ddrc_rst         (0              ), // input
  132. 132   .csysreq_ddrc     (1'b1           ), // input
  133. 133   .csysack_ddrc     (               ), //output
  134. 134   .cactive_ddrc     (               ), //output
  135. 135   .pll_lock         (               ), //output
  136. 136   .pll_aclk_0       (axi_clk        ), //output
  137. 137   .pll_aclk_1       (               ), //output
  138. 138   .pll_aclk_2       (               ), //output
  139. 139   .ddrphy_rst_done  (               ), // output
  140. 140   .ddrc_init_done   (ddr_init_done  ), // output
  141. 141   .pad_loop_in      (pad_loop_in    ), // input
  142. 142   .pad_loop_in_h    (pad_loop_in_h  ), // input
  143. 143   .pad_rstn_ch0     (pad_rstn_ch0   ), //output
  144. 144   .pad_ddr_clk_w    (pad_ddr_clk_w  ), //output
  145. 145   .pad_ddr_clkn_w   (pad_ddr_clkn_w), // output
  146. 146   .pad_csn_ch0      (pad_csn_ch0    ), //output
  147. 147   .pad_addr_ch0     (pad_addr_ch0   ), //output [15:0]
  148. 148   .pad_dq_ch0       (pad_dq_ch0     ), // inout[15:0]
  149. 149   .pad_dqs_ch0      (pad_dqs_ch0    ), // inout[1:0]
  150. 150   .pad_dqsn_ch0     (pad_dqsn_ch0   ), // inout[1:0]
  151. 151   .pad_dm_rdqs_ch0  (pad_dm_rdqs_ch0), //output [1:0]
  152. 152   .pad_cke_ch0      (pad_cke_ch0    ), //output
  153. 153   .pad_odt_ch0      (pad_odt_ch0    ), //output
  154. 154   .pad_rasn_ch0     (pad_rasn_ch0   ), //output
  155. 155   .pad_casn_ch0     (pad_casn_ch0   ), //output
  156. 156   .pad_wen_ch0      (pad_wen_ch0    ), //output
  157. 157   .pad_ba_ch0       (pad_ba_ch0     ), //output [2:0]
  158. 158   .pad_loop_out     (pad_loop_out   ), //output
  159. 159   .pad_loop_out_h   (pad_loop_out_h), // output
  160. 160   .areset_0         (0              ), // input
  161. 161   .aclk_0           (axi_clk        ), // input
  162. 162   .awid_0           (0              ), // input [7:0]
  163. 163   .awaddr_0         (axi_awaddr     ), // input[31:0]
  164. 164   .awlen_0          (axi_awlen      ), // input[7:0]
  165. 165   .awsize_0         (axi_awsize     ), // input[2:0]
  166. 166   .awburst_0        (axi_awburst    ), // input[1:0]
  167. 167   .awlock_0         (axi_awlock     ), // input
  168. 168   .awvalid_0        (axi_awvalid    ), // input
  169. 169   .awready_0        (axi_awready    ), // output
  170. 170   .awurgent_0       (axi_awurgent   ), // input
  171. 171   .awpoison_0       (axi_awpoison   ), // input
  172. 172   .wdata_0          (axi_wdata      ), // input[127:0]
  173. 173   .wstrb_0          (axi_wstrb      ), // input[15:0]
  174. 174   .wlast_0          (axi_wlast      ), // input
  175. 175   .wvalid_0         (axi_wvalid     ), // input
  176. 176   .wready_0         (axi_wready     ), //output
  177. 177   .bid_0            (               ), //output [7:0]
  178. 178   .bresp_0          (               ), //output [1:0]
  179. 179   .bvalid_0         (               ), //output
  180. 180   .bready_0         (axi_bready     ), // input
  181. 181   .arid_0           (0              ), // input [7:0]
  182. 182   .araddr_0         (axi_araddr     ), // input[31:0]
  183. 183   .arlen_0          (axi_arlen      ), // input[7:0]
  184. 184   .arsize_0         (axi_arsize     ), // input[2:0]
  185. 185   .arburst_0        (axi_arburst    ), // input[1:0]
  186. 186   .arlock_0         (axi_arlock     ), // input
  187. 187   .arvalid_0        (axi_arvalid    ), // input
  188. 188   .arready_0        (axi_arready    ), //output
  189. 189   .arpoison_0       (axi_arpoison   ), // input
  190. 190   .rid_0            (               ), //output [7:0]
  191. 191   .rdata_0          (axi_rdata      ), //output [127:0]
  192. 192   .rresp_0          (               ), //output [1:0]
  193. 193   .rlast_0          (axi_rlast      ), //output
  194. 194   .rvalid_0         (axi_rvalid     ), //output
  195. 195   .rready_0         (axi_rready     ), // input
  196. 196   .arurgent_0       (axi_arurgent   ), // input
  197. 197   .csysreq_0        (1'b1           ), // input
  198. 198   .csysack_0        (               ), //output
  199. 199   .cactive_0        (               )  // output
  200. 200 );
  201. 201
  202. 202 //ddr3控制器fifo控制模块
  203. 203  ddr3_fifo_ctrl u_ddr3_fifo_ctrl (
  204. 204      .rst_n               (rst_n && ddr_init_done    ) ,  //复位
  205. 205      //输入源接口
  206. 206      .wr_clk              (wr_clk                    ) ,  //写时钟
  207. 207      .rd_clk              (rd_clk                    ) ,  //读时钟
  208. 208      .clk_100             (axi_clk                   ) ,  //用户时钟
  209. 209      .datain_valid        (datain_valid              ) ,  //数据有效使能信号
  210. 210      .datain              (datain                    ) ,  //有效数据
  211. 211      .rfifo_din           (axi_rdata                 ) ,  //用户读数据
  212. 212      .rdata_req           (rdata_req                 ) ,  //请求像素点颜色数据输入
  213. 213      .rfifo_wren          (axi_rvalid                ) ,  //ddr3读出数据的有效使能
  214. 214      .wfifo_rden          (wfifo_rden||pre_wfifo_rden) ,  //ddr3 写使能
  215. 215      //用户接口
  216. 216      .wfifo_rcount        (wfifo_rcount              ) , //rfifo剩余数据计数
  217. 217      .rfifo_wcount        (rfifo_wcount              ) , //wfifo写进数据计数
  218. 218      .wfifo_dout          (axi_wdata                 ) , //用户写数据
  219. 219      .pic_data            (dataout                   )   //rfifo输出数据
  220. 220      );
  221. 221
  222. 222 endmodule
复制代码

ddr3控制器顶层模块主要完成ddr3读写控制器模块、FIFO控制模块和ddr3 IP核的例化。ddr3读写控制器模块负责与ddr3 IP核模块的命令和地址的交互,根据FIFO控制模块中fifo的剩余数据量来切换DDR3的读写命令和地址。ddr3 IP核模块一边与用户端进行交互,另一边对芯片进行操作,以实现数据的存储。FIFO控制模块负责对输入和输出的数据进行时钟域的切换和位宽的转换。
ddr3读写控制器模块代码如下:
  1. 1   module rw_ctrl_128bit
  2. 2     (
  3. 3       input                  clk             , //时钟
  4. 4       input                  rst_n           , //复位
  5. 5       input                  ddr_init_done   , //DDR初始化完成
  6. 6       output      [32-1:0   axi_awaddr      , //写地址
  7. 7       output reg  [7:0      axi_awlen       , //写突发长度
  8. 8       output wire [2:0       axi_awsize      , //写突发大小
  9. 9       output wire [1:0       axi_awburst     , //写突发类型
  10. 10      output                 axi_awlock      , //写锁定类型
  11. 11      input                  axi_awready     , //写地址准备信号
  12. 12      output reg              axi_awvalid     , //写地址有效信号
  13. 13      output                 axi_awurgent    , //写紧急信号,1:Write address指令优先执行
  14. 14      output                 axi_awpoison    , //写抑制信号,1:Write address指令无效
  15. 15      output wire [15:0      axi_wstrb       , //写选通
  16. 16      output reg              axi_wvalid      , //写数据有效信号
  17. 17      input                  axi_wready      , //写数据准备信号
  18. 18      output reg              axi_wlast       , //最后一次写信号
  19. 19      output wire             axi_bready      , //写回应准备信号
  20. 20      output reg              wrfifo_en_ctrl  , //写FIFO数据读使能控制位
  21. 21      output      [32-1:0   axi_araddr      , //读地址
  22. 22      output reg  [7:0      axi_arlen       , //读突发长度
  23. 23      output wire [2:0       axi_arsize      , //读突发大小
  24. 24      output wire [1:0       axi_arburst     , //读突发类型
  25. 25      output wire             axi_arlock      , //读锁定类型
  26. 26      output wire             axi_arpoison    , //读抑制信号,1:Read address指令无效
  27. 27      output wire             axi_arurgent    , //读紧急信号,1:Read address指令优先执行
  28. 28      input                  axi_arready     , //读地址准备信号
  29. 29      output reg              axi_arvalid     , //读地址有效信号
  30. 30      input                  axi_rlast       , //最后一次读信号
  31. 31      input                   axi_rvalid      , //读数据有效信号
  32. 32      output wire             axi_rready      , //读数据准备信号
  33. 33      input       [10:0      wfifo_rcount    , //写端口FIFO中的数据量
  34. 34      input       [10:0      rfifo_wcount    , //读端口FIFO中的数据量
  35. 35      input       [27:0      app_addr_rd_min , //读DDR3的起始地址
  36. 36      input       [27:0      app_addr_rd_max , //读DDR3的结束地址
  37. 37      input       [7:0       rd_bust_len     , //从DDR3中读数据时的突发长度
  38. 38      input       [27:0      app_addr_wr_min , //写DDR3的起始地址
  39. 39      input       [27:0      app_addr_wr_max , //写DDR3的结束地址
  40. 40      input       [7:0       wr_bust_len       //从DDR3中写数据时的突发长度
  41. 41  );
  42. 42  
  43. 43  //localparam define
  44. 44  localparam IDLE        = 4'd1 ; //空闲状态
  45. 45  localparam DDR3_DONE   =4'd2 ; //DDR3初始化完成状态
  46. 46  localparam WRITE_ADDR  = 4'd3 ; //写地址
  47. 47  localparam WRITE_DATA  = 4'd4 ; //写数据
  48. 48  localparam READ_ADDR   =4'd5 ; //读地址
  49. 49  localparam READ_DATA   =4'd6 ; //读数据
  50. 50  
  51. 51  //reg define
  52. 52  reg        init_start   ;//初始化完成信号
  53. 53  reg [31:0 init_addr    ; //突发长度计数器
  54. 54  reg [31:0 axi_araddr_n ; //读地址计数
  55. 55  reg [31:0 axi_awaddr_n ; //写地址计数
  56. 56  reg [3:0 state_cnt    ; //状态计数器
  57. 57  reg [9:0 lenth_cnt    ; //突发写次数计数器
  58. 58  
  59. 59  //wire define
  60. 60  wire [9:0 lenth_cnt_max; //最大突发次数
  61. 61  
  62. 62  //*****************************************************
  63. 63  //**                    main code
  64. 64  //*****************************************************
  65. 65  
  66. 66  assign  axi_awlock  = 1'b0      ;
  67. 67  assign  axi_awurgent = 1'b0      ;
  68. 68  assign  axi_awpoison = 1'b0      ;
  69. 69  assign  axi_bready  = 1'b1      ;
  70. 70  assign  axi_wstrb   = {16{1'b1}};
  71. 71  assign  axi_awsize  = 3'b100   ;
  72. 72  assign  axi_awburst = 2'd1      ;
  73. 73  assign  axi_arlock  = 1'b0      ;
  74. 74  assign  axi_arurgent = 1'b0      ;
  75. 75  assign  axi_arpoison = 1'b0      ;
  76. 76  assign  axi_arsize  = 3'b100   ;
  77. 77  assign  axi_arburst = 2'd1      ;
  78. 78  assign  axi_rready  = 1'b1      ;
  79. 79  
  80. 80  //计算最大突发次数
  81. 81  assign  lenth_cnt_max = app_addr_wr_max / (wr_bust_len * 4'd8);
  82. 82  
  83. 83  //读写地址,因为第0位无效,所以读写地址数据从第1位开始填入
  84. 84  assign  axi_araddr = {6'b0,axi_araddr_n[24:0],1'b0};
  85. 85  assign  axi_awaddr = {6'b0,axi_awaddr_n[24:0],1'b0};
  86. 86  
  87. 87  //稳定ddr3初始化信号
  88. 88  always @(posedge clk or negedge rst_n) begin
  89. 89       if (!rst_n)
  90. 90           init_start <= 1'b0;
  91. 91       else if (ddr_init_done)
  92. 92           init_start <= ddr_init_done;
  93. 93       else
  94. 94           init_start <= init_start;
  95. 95  end
复制代码

代码第81行代码计算了最大突发次数,由于是从0地址开始写入,所以lenth_cnt_max = app_addr_wr_max / (wr_bust_len * 8)。第84行和85行输出读写地址,由于第0位无效,所以第0位补0,读写地址数据从第1位开始填入。
第88~95行代码用于稳定ddr3初始化完成信号,因为ddr3 IP核对初始化完成信号存在信号校准,所以初始化完成后该信号并非一直保持为高,会有跳动,因此在这里做当检测到一次ddr3初始化完成信号后,就将该信号一直拉高,使后续模块运行时时序不受影响。
  1. 96  
  2. 97  //写地址模块
  3. 98  always @(posedge clk or negedge rst_n) begin
  4. 99       if (!rst_n) begin
  5. 100          axi_awaddr_n <= app_addr_wr_min;
  6. 101          axi_awlen    <= 8'b0;
  7. 102          axi_awvalid  <= 1'b0;
  8. 103      end
  9. 104      //DDR3初始化完成
  10. 105      else if (init_start) begin
  11. 106          axi_awlen <= wr_bust_len - 1'b1;
  12. 107          //当写地址计数小于最后一次写地址起始位时
  13. 108          if (axi_awaddr_n < {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
  14. 109              //写地址有效信号和写地址准备信号都为1时
  15. 110              if (axi_awvalid && axi_awready) begin
  16. 111                  axi_awvalid  <= 1'b0;         //拉低写地址有效信号
  17. 112                  //写地址计数加一个突发长度所需的地址
  18. 113                 axi_awaddr_n <= axi_awaddr_n + wr_bust_len * 5'd16;//wr_bust_len*128/8
  19. 114              end
  20. 115              //状态机处于写地址状态且写地址准备信号为1时
  21. 116              else if (state_cnt == WRITE_ADDR && axi_awready)
  22. 117                  axi_awvalid  <= 1'b1;    //拉高写地址有效信号
  23. 118          end
  24. 119          //当写地址计数等于最后一次写地址起始位时
  25. 120          else if (axi_awaddr_n == {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
  26. 121              if (axi_awvalid && axi_awready) begin
  27. 122                  axi_awvalid  <= 1'b0;
  28. 123                  axi_awaddr_n <= app_addr_wr_min; //写地址计数清零(回到写起始地址)
  29. 124              end
  30. 125              else if (state_cnt == WRITE_ADDR && axi_awready)
  31. 126                  axi_awvalid  <= 1'b1;
  32. 127          end
  33. 128          else
  34. 129              axi_awvalid <= 1'b0;
  35. 130      end
  36. 131      else begin
  37. 132              axi_awaddr_n <= axi_awaddr_n;
  38. 133              axi_awlen    <= 8'b0;
  39. 134              axi_awvalid  <= 1'b0;
  40. 135      end
  41. 136 end
  42. 137
复制代码

第98~136行代码执行写地址操作,ddr3初始化完成后,若写地址计数小于最后一次写地址起始位时,如果当前状态机处于写地址状态且写地址准备信号有效,拉高写地址有效信号;写地址有效信号和写地址准备信号同时为高时,写地址计数器(axi_awaddr_n)增加一个突发长度所需的地址并将写地址有效信号拉低,即写地址有效信号只拉高了一个时钟周期。若写地址计数小于最后一次写地址起始位时,当写地址有效信号和写地址准备信号同时为高时,将写地址计数器清零(即回到写起始地址),其他信号变化相同。
  1. 138 //写数据模块
  2. 139 always @(posedge clk or negedge rst_n) begin
  3. 140      if (!rst_n) begin
  4. 141          axi_wvalid <= 1'b0  ;
  5. 142          axi_wlast  <= 1'b0  ;
  6. 143          init_addr  <= 32'd0 ;
  7. 144          lenth_cnt  <= 8'd0  ;
  8. 145          wrfifo_en_ctrl <= 1'b0;
  9. 146      end
  10. 147      else begin
  11. 148          //DDR3初始化完成
  12. 149          if (init_start) begin
  13. 150              //当突发写次数计数器小于最大突发次数时
  14. 151              if (lenth_cnt < lenth_cnt_max) begin
  15. 152                  if (axi_wvalid && axi_wready && init_addr < wr_bust_len - 2'd2) begin
  16. 153                      init_addr      <= init_addr + 1'b1;
  17. 154                      wrfifo_en_ctrl <= 1'b0;
  18. 155                  end
  19. 156         //因为写DDR时已经提前让FIFO准备好第一个数据,所以使能在写结尾要减少一个使能周期
  20. 157                  else if(axi_wvalid && axi_wready && init_addr==wr_bust_len - 2'd2) begin
  21. 158                      axi_wlast      <= 1'b1;
  22. 159                      wrfifo_en_ctrl <= 1'b1;              //提前一个时钟周期拉高
  23. 160                      init_addr      <= init_addr + 1'b1;
  24. 161                  end
  25. 162                  //当突发长度计数器等于一次突发长度时
  26. 163                  else if(axi_wvalid && axi_wready && init_addr==wr_bust_len - 2'd1) begin
  27. 164                      axi_wvalid     <= 1'b0;
  28. 165                      axi_wlast      <= 1'b0;
  29. 166                      wrfifo_en_ctrl <= 1'b0;
  30. 167                      lenth_cnt      <= lenth_cnt + 1'b1;  //突发写次数计数器加1
  31. 168                      init_addr      <= 32'd0;
  32. 169                  end         
  33. 170                  else if (state_cnt == WRITE_DATA && axi_wready)
  34. 171                      axi_wvalid     <= 1'b1;
  35. 172                  else
  36. 173                      lenth_cnt      <= lenth_cnt;
  37. 174              end
  38. 175              else begin
  39. 176                  axi_wvalid <= 1'b0     ;
  40. 177                  axi_wlast  <= 1'b0     ;
  41. 178                  init_addr  <= init_addr;
  42. 179                  lenth_cnt  <= 8'd0     ;
  43. 180              end
  44. 181          end
  45. 182          else begin
  46. 183              axi_wvalid <= 1'b0 ;
  47. 184              axi_wlast  <= 1'b0 ;
  48. 185              init_addr  <= 32'd0;
  49. 186              lenth_cnt  <= 8'd0 ;
  50. 187          end
  51. 188      end
  52. 189 end
  53. 190
复制代码

第139~189行代码执行写数据操作,ddr3初始化完成后,若突发写次数计数器小于最大突发次数时,如果当前状态机处于写数据状态且写数据准备信号有效时,拉高写数据有效信号直至完成一次突发写操作后再将其拉低。因为写DDR时已经提前让FIFO准备好第一个数据,所以使能在写结尾要减少一个使能周期,因此在写数据有效信号和写数据准备信号同时为高时,若突发长度计数器(init_addr)小于突发长度-2时,突发长度计数器加1;若突发长度计数器(init_addr)等于突发长度-2(即写倒数第二个数)时,将wrfifo_en_ctrl信号拉高(即在写结尾减少一个使能周期);若突发长度计数器(init_addr)等于突发长度-1(即写最后一个数)时,将wrfifo_en_ctrl信号拉低,即wrfifo_en_ctrl信号只拉高一个时钟周期,为下一次写数据操作做准备。
  1. 191 //读地址模块
  2. 192 always @(posedge clk or negedge rst_n) begin
  3. 193      if (!rst_n) begin
  4. 194        axi_araddr_n <= app_addr_rd_min;
  5. 195        axi_arlen    <= 8'b0;
  6. 196        axi_arvalid  <= 1'b0;
  7. 197      end
  8. 198      //DDR3初始化完成
  9. 199      else if(init_start) begin
  10. 200          axi_arlen <= rd_bust_len - 1'b1;
  11. 201          //当读地址计数小于最后一次读地址起始位时
  12. 202          if (axi_araddr_n < {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
  13. 203              if (axi_arready && axi_arvalid) begin
  14. 204                  axi_arvalid  <= 1'b0;
  15. 205                  axi_araddr_n <= axi_araddr_n + rd_bust_len * 5'd16;
  16. 206              end
  17. 207              else if(axi_arready && state_cnt == READ_ADDR)
  18. 208                  axi_arvalid  <= 1'b1;
  19. 209          end
  20. 210          //当读地址计数等于最后一次读地址起始位时
  21. 211          else if (axi_araddr_n == {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
  22. 212              if (axi_arready && axi_arvalid) begin
  23. 213                  axi_arvalid  <= 1'b0;
  24. 214                  axi_araddr_n <= app_addr_rd_min;
  25. 215              end
  26. 216              else if(axi_arready && state_cnt==READ_ADDR)
  27. 217                  axi_arvalid  <= 1'b1;
  28. 218          end            
  29. 219          else
  30. 220              axi_arvalid <= 1'b0;
  31. 221      end
  32. 222      else begin  
  33. 223              axi_araddr_n   <=app_addr_rd_min;
  34. 224              axi_arlen      <= 8'b0;
  35. 225              axi_arvalid    <= 1'b0;
  36. 226      end     
  37. 227 end
  38. 228
复制代码

读地址操作的信号跳转与写地址操作时类似,这里不再赘述。
  1. 229 //DDR3读写逻辑实现模块
  2. 230 always @(posedge clk or negedge rst_n) begin
  3. 231      if(~rst_n) begin
  4. 232          state_cnt <= IDLE;
  5. 233      end
  6. 234      else begin
  7. 235          case(state_cnt)
  8. 236              IDLE:begin
  9. 237                  if(init_start)
  10. 238                      state_cnt <= DDR3_DONE ;
  11. 239                  else
  12. 240                      state_cnt <= IDLE;
  13. 241              end
  14. 242              DDR3_DONE:begin
  15. 243                  if(wfifo_rcount >= wr_bust_len)
  16. 244                      state_cnt <= WRITE_ADDR;         //跳到写地址操作
  17. 245                  else if(rfifo_wcount < rd_bust_len)
  18. 246                      state_cnt <= READ_ADDR;          //跳到读地址操作
  19. 247                  else
  20. 248                      state_cnt <= state_cnt;
  21. 249              end            
  22. 250              WRITE_ADDR:begin
  23. 251                  if(axi_awvalid && axi_awready)
  24. 252                      state_cnt <= WRITE_DATA;        //跳到写数据操作
  25. 253                  else
  26. 254                      state_cnt <= state_cnt;         //条件不满足,保持当前值
  27. 255              end
  28. 256              WRITE_DATA:begin
  29. 257                  if(axi_wvalid && axi_wready && init_addr == wr_bust_len - 1)
  30. 258                      state_cnt <= DDR3_DONE;        //写到设定的长度跳到等待状态
  31. 259                  else
  32. 260                      state_cnt <= state_cnt;        //写条件不满足,保持当前值
  33. 261              end         
  34. 262              READ_ADDR:begin
  35. 263                  if(axi_arvalid && axi_arready)
  36. 264                      state_cnt <= READ_DATA;        //跳到写数据操作
  37. 265                  else
  38. 266                      state_cnt <= state_cnt;        //条件不满足,保持当前值
  39. 267              end
  40. 268              READ_DATA:begin
  41. 269                  if(axi_rlast)                     //读到设定的地址长度
  42. 270                      state_cnt <= DDR3_DONE;        //则跳到空闲状态
  43. 271                  else
  44. 272                      state_cnt <= state_cnt;        //否则保持当前值
  45. 273              end
  46. 274              default:begin
  47. 275                  state_cnt <= IDLE;
  48. 276              end
  49. 277          endcase
  50. 278      end
  51. 279 end
  52. 280
  53. 281 endmodule
复制代码

第230~279行代码是DDR3读写逻辑的实现,状态跳转如下图所示,图中写状态包含写地址状态和写数据状态;读状态包含读地址状态和读数据状态。
image055.png
图 38.4.16 DDR3读写状态跳转图
在复位结束后,如果DDR3没有初始化完成,那么状态一直在空闲状态(IDLE),否则跳到DDR3空闲状态(DDR3_DONE)。
程序中第243~244行处理DDR3写请求,以免写FIFO溢出,造成写入DDR3的数据丢失。当写FIFO中的数据量大于一次突发写长度时,执行DDR3写地址操作(WRITE_ADDR)。
程序中第245~246行处理DDR3读请求,以免读FIFO读空,造成空读现象。当读FIFO中的数据量小于一次读突发长度时,执行DDR3读地址操作(READ_ADDR)。
程序中第250~261行处理DDR3写地址跳转到写数据状态的过程,当写地址有效信号和写地址准备信号同时为高时,状态机由写地址状态(WRITE_ADDR)跳转到写数据状态(WRITE_DATA);当执行完一次突发写长度后,状态机由写数据状态跳转到DDR3空闲状态(DDR3_DONE)。
程序中第262~273行处理DDR3读地址跳转到读数据状态的过程,跳转机制与写状态类似,有别处在于读数据状态(READ_DATA)跳转到DDR3空闲状态(DDR3_DONE)的条件是最后一次读信号(axi_rlast)为1时。
ddr3控制器fifo控制模块代码如下
  1. 1  module ddr3_fifo_ctrl(
  2. 2       input          rst_n            ,  //复位信号
  3. 3       input          wr_clk           ,  //wfifo时钟
  4. 4       input          rd_clk           ,  //rfifo时钟
  5. 5       input          clk_100          ,  //用户时钟
  6. 6       input          datain_valid     ,  //数据有效使能信号
  7. 7       input  [15:0   datain           ,  //有效数据
  8. 8      input  [127:0 rfifo_din        ,  //用户读数据
  9. 9       input          rdata_req        ,  //请求像素点颜色数据输入
  10. 10      input          rfifo_wren       ,  //从ddr3读出数据的有效使能
  11. 11      input          wfifo_rden       ,  //wfifo读使能
  12. 12      output [127:0 wfifo_dout       ,  //用户写数据
  13. 13      output [10:0  wfifo_rcount     ,  //rfifo剩余数据计数
  14. 14      output [10:0  rfifo_wcount     ,  //wfifo写进数据计数
  15. 15      output [15:0  pic_data            //有效数据
  16. 16      );
  17. 17
  18. 18 rd_fifo u_rd_fifo  (
  19. 19   .wr_clk         (clk_100     ),  // input
  20. 20   .wr_rst         (~rst_n      ),  // input
  21. 21   .wr_en          (rfifo_wren  ),  // input
  22. 22   .wr_data        (rfifo_din   ),  // input [127:0]
  23. 23   .wr_full        (            ),  // output
  24. 24   .wr_water_level (rfifo_wcount),  // output
  25. 25   .almost_full    (            ),  // output
  26. 26   .rd_clk         (rd_clk      ),  // input
  27. 27   .rd_rst         (~rst_n      ),  // input
  28. 28   .rd_en          (rdata_req   ),
  29. 29   .rd_data        (pic_data    ),  // output [15:0]
  30. 30   .rd_empty       (            ),  // output
  31. 31   .rd_water_level (            ),  // output
  32. 32   .almost_empty   (            )   // output
  33. 33 );
  34. 34
  35. 35 wr_fifo u_wr_fifo  (
  36. 36   .wr_clk         (wr_clk      ),    // input
  37. 37   .wr_rst         (~rst_n      ),    // input
  38. 38   .wr_en          (datain_valid),
  39. 39   .wr_data        (datain      ),    //input [15:0]
  40. 40   .wr_full        (            ),    // output
  41. 41   .wr_water_level (            ),    // output
  42. 42   .almost_full    (            ),    // output
  43. 43   .rd_clk         (clk_100     ),    // input
  44. 44   .rd_rst         (~rst_n      ),    // input
  45. 45   .rd_en          (wfifo_rden  ),    // input
  46. 46   .rd_data        (wfifo_dout  ),    // output [127:0]
  47. 47   .rd_empty       (            ),    // output
  48. 48   .rd_water_level (wfifo_rcount),    // output
  49. 49   .almost_empty   (            )     // output
  50. 50 );
  51. 51 endmodule
复制代码

该模块例化了两个FIFO IP核,分别为128位进16位出的读FIFO和16位进128位出的写FIFO。读FIFO是将DDR3输出的128位宽的数据转为16位宽的数据后输出给用户;写FIFO是将用户输入的16位宽的数据转为128位宽的数据后输出给DDR3。
ddr测试数据模块代码如下:
  1. 1   module ddr_test(
  2. 2        input            clk_50m       ,   //时钟
  3. 3        input            rst_n         ,   //复位,低有效
  4. 4                                            
  5. 5        output reg        wr_en         ,   //写使能
  6. 6        output reg [15:0 wr_data       ,   //写数据
  7. 7        output reg        rd_en         ,   //读使能
  8. 8        input      [15:0 rd_data       ,   //读数据
  9. 9        input      [27:0 data_max      ,   //写入ddr的最大数据量
  10. 10      
  11. 11       input            ddr3_init_done,   //ddr3初始化完成信号
  12. 12       output reg        error_flag        //ddr3读写错误
  13. 13      
  14. 14       );
  15. 15  
  16. 16  //reg define
  17. 17  reg        init_done_d0;
  18. 18  reg        init_done_d1;
  19. 19  reg [27:0 wr_cnt      ;   //写操作计数器
  20. 20  reg [27:0 rd_cnt      ;   //读操作计数器
  21. 21  reg        rd_valid    ;   //读数据有效标志
  22. 22  reg [27:0 rd_cnt_d0   ;
  23. 23     
  24. 24  //*****************************************************
  25. 25  //**                    main code
  26. 26  //*****************************************************
  27. 27  
  28. 28  //同步ddr3初始化完成信号
  29. 29  always @(posedge clk_50m or negedge rst_n) begin
  30. 30       if(!rst_n) begin
  31. 31           init_done_d0 <= 1'b0 ;
  32. 32           init_done_d1 <= 1'b0 ;
  33. 33       end
  34. 34       else begin
  35. 35           init_done_d0 <= ddr3_init_done;
  36. 36           init_done_d1 <= init_done_d0;
  37. 37       end
  38. 38  end
  39. 39  
  40. 40  //对读计数器做一拍延时使数据对齐
  41. 41  always @(posedge clk_50m or negedge rst_n) begin
  42. 42       if(!rst_n)
  43. 43           rd_cnt_d0    <= 28'd0;
  44. 44       else
  45. 45           rd_cnt_d0 <= rd_cnt;
  46. 46  end
  47. 47  
  48. 48  //ddr3初始化完成之后,写操作计数器开始计数
  49. 49  always @(posedge clk_50m or negedge rst_n) begin
  50. 50       if(!rst_n)
  51. 51           wr_cnt <= 28'd0;
  52. 52       else if(init_done_d1 && (wr_cnt < data_max ))
  53. 53           wr_cnt <= wr_cnt + 1'b1;
  54. 54       else
  55. 55           wr_cnt <= wr_cnt;
  56. 56  end   
  57. 57  
  58. 58  //ddr3写端口FIFO的写使能、写数据
  59. 59  always @(posedge clk_50m or negedge rst_n) begin
  60. 60       if(!rst_n) begin
  61. 61           wr_en   <=1'b0;
  62. 62           wr_data <= 16'd0;
  63. 63       end
  64. 64       else if(wr_cnt >= 11'd0 && (wr_cnt < data_max )&&init_done_d1) begin
  65. 65               wr_en   <=1'b1;            //写使能拉高
  66. 66               wr_data <= wr_cnt[15:0];    //写入数据
  67. 67       end   
  68. 68       else begin
  69. 69               wr_en   <=1'b0;
  70. 70               wr_data <= 16'd0;
  71. 71       end
  72. 72  end
  73. 73  
  74. 74  //写入数据完成后,开始读操作
  75. 75  always @(posedge clk_50m or negedge rst_n) begin
  76. 76       if(!rst_n)
  77. 77           rd_en <= 1'b0;
  78. 78       else if(wr_cnt >= data_max )         //写数据完成
  79. 79           rd_en <= 1'b1;                  //读使能
  80. 80       else
  81. 81           rd_en <= rd_en;
  82. 82  end
  83. 83  
  84. 84  //对读操作计数
  85. 85  always @(posedge clk_50m or negedge rst_n) begin
  86. 86       if(!rst_n)
  87. 87           rd_cnt <= 28'd0;
  88. 88       else if(rd_en) begin
  89. 89           if(rd_cnt < data_max - 1'd1)
  90. 90               rd_cnt <= rd_cnt + 1'd1;
  91. 91           else
  92. 92               rd_cnt <= 28'd0;
  93. 93       end
  94. 94  end
  95. 95  
  96. 96  //第一次读取的数据无效,后续读操作所读取的数据才有效
  97. 97  always @(posedge clk_50m or negedge rst_n) begin
  98. 98       if(!rst_n)
  99. 99           rd_valid <= 1'b0;
  100. 100      else if(rd_cnt >= data_max - 1'd1 )  //等待第一次读操作结束
  101. 101          rd_valid <= 1'b1;               //后续读取的数据有效
  102. 102      else
  103. 103          rd_valid <= rd_valid;
  104. 104 end
  105. 105
  106. 106 //读数据有效时,若读取数据错误,给出标志信号
  107. 107 always @(posedge clk_50m or negedge rst_n) begin
  108. 108      if(!rst_n)
  109. 109          error_flag <= 1'b0;
  110. 110      else if(wr_en)      
  111. 111          error_flag <= 1'b0;      
  112. 112      else if(rd_valid && ((rd_data[15:0 != rd_cnt_d0[15:0])) )
  113. 113          error_flag <= 1'b1;             //若读取的数据错误,将错误标志位拉高
  114. 114      else
  115. 115          error_flag <= error_flag;
  116. 116 end
  117. 117
  118. 118 endmodule
复制代码

ddr测试数据模块从起始地址开始,连续向5120个存储空间中写入数据0~5119。写完成后一直进行读操作,持续将该存储空间的数据读出。其中第45~50行代码对读计数器做了延时处理,使其与从ddr3中读出的数据对齐。
需要注意的的是程序中第116行通过变量rd_valid将第一次读出的5120个数据排除,并未参与读写测试。这是由于ddr3控制器为了保证读FIFO时刻有数据,在写数据尚未完成时,就将ddr3中的数据“预读”一部分(一次读长度)到读FIFO中,因此第一次从FIFO中读出的数据是无效的。读/写时序如下图所示:
image057.png
图 38.4.17 写数据时序1
image059.png
图 38.4.18 写数据时序2
image061.png
图 38.4.19 读数据时序
从上面几个时序图中可以看出读写数据是一致的,因此信号error_flag一直处于低电平。
LED显示模块代码如下:
  1. 1  module led_disp(
  2. 2       input     clk_50m          , //系统时钟
  3. 3       input     rst_n            , //系统复位
  4. 4                                    
  5. 5       input     ddr3_init_done   , //ddr3初始化完成信号
  6. 6       input     error_flag       , //错误标志信号
  7. 7       output reg led_error        , //读写错误led灯
  8. 8       output reg led_ddr_init_done  //ddr3初始化完成led灯            
  9. 9       );
  10. 10
  11. 11 //reg define
  12. 12 reg [24:0 led_cnt     ;   //控制LED闪烁周期的计数器
  13. 13 reg        init_done_d0;               
  14. 14 reg        init_done_d1;
  15. 15
  16. 16 //*****************************************************
  17. 17 //**                    main code
  18. 18 //*****************************************************
  19. 19
  20. 20 //同步ddr3初始化完成信号
  21. 21 always @(posedge clk_50m or negedge rst_n) begin
  22. 22      if(!rst_n) begin
  23. 23          init_done_d0 <= 1'b0 ;
  24. 24          init_done_d1 <= 1'b0 ;
  25. 25      end
  26. 26      else if (ddr3_init_done) begin
  27. 27          init_done_d0 <= ddr3_init_done;
  28. 28          init_done_d1 <= init_done_d0;   
  29. 29      end
  30. 30      else begin
  31. 31          init_done_d0 <= init_done_d0;
  32. 32          init_done_d1 <= init_done_d1;   
  33. 33      end
  34. 34 end   
  35. 35
  36. 36 //利用LED灯不同的显示状态指示DDR3初始化是否完成
  37. 37 always @(posedge clk_50m or negedge rst_n) begin
  38. 38      if(!rst_n)
  39. 39          led_ddr_init_done <= 1'd0;
  40. 40      else if(init_done_d1)
  41. 41          led_ddr_init_done <= 1'd1;
  42. 42      else
  43. 43          led_ddr_init_done <= led_ddr_init_done;
  44. 44 end
  45. 45
  46. 46 //计数器对50MHz时钟计数,计数周期为0.5s
  47. 47 always @(posedge clk_50m or negedge rst_n) begin
  48. 48      if(!rst_n)
  49. 49          led_cnt <= 25'd0;
  50. 50      else if(led_cnt < 25'd25000000)
  51. 51          led_cnt <= led_cnt +25'd1;
  52. 52      else
  53. 53          led_cnt <= 25'd0;
  54. 54 end
  55. 55
  56. 56 //利用LED灯不同的显示状态指示错误标志的高低
  57. 57 always @(posedge clk_50m or negedge rst_n) begin
  58. 58      if(rst_n == 1'b0)
  59. 59          led_error <= 1'b0;
  60. 60      else if(error_flag) begin
  61. 61          if(led_cnt == 25'd25000000)
  62. 62              led_error <= ~led_error;    //错误标志为高时,LED灯每隔0.5s闪烁一次
  63. 63          else
  64. 64              led_error <= led_error;
  65. 65      end   
  66. 66      else
  67. 67          led_error <= 1'b1;        //错误标志为低时,LED灯常亮
  68. 68 end
  69. 69
  70. 70 endmodule
复制代码

LED显示模块用LED不同的显示状态指示ddr3初始完成情况(LED0常亮表示ddr3初始化完成)和ddr3读写测试的结果:若读写测试正确无误,则LED1常亮;若出现错误(读出的数据与写入的数据不一致),则LED1以0.5s为周期闪烁。
这里我们讲解一下ddr3例程如何进行Modelsim仿真,工程编译完成后ddr IP会自动生成一个sim文件夹,文件夹路径及内容如下所示:
image063.png
图38.4.20 sim文件夹的路径及所含内容
这里我们要自己添加一个批量处理文件sim.bat,首先新建一个.bat文件(新建任意文本文档后将文件后缀改为.bat即可),这里我们将其命名为sim,然后用Notepad++软件打开,编写如下所示代码,代码很简单,只有一行,作用是执行ctrl_phy_sim.tcl文件。
  1. vsim -gui -do ctrl_phy_sim.tcl
复制代码

image064.png
图38.4.21 批量处理文件sim中的代码
因为该sim.bat文件会执行所在路径的TCL文件,所以我们要将其和ctrl_phy_sim.tcl放在同一个路径下,即sim.bat要放在sim文件夹下,如下图所示。
image065.png
图 38.4.22 sim.bat文件放置位置
接下来我们找到ddr_test_top_tb.v文件进行修改,ddr_test_top_tb.v文件是官方提供的ddr3仿真tb文件,是生成ddr3 IP核后自动生成的,但是它是用于仿真官方ddr例程的仿真文件,所以我们需要稍作修改,使其成为能够仿真我们自己的工程的仿真文件,该文件位置如下图所示:
image067.png
图 38.4.23 ddr_test_top_tb.v文件所在位置
用Notepad++软件打开ddr_test_top_tb.v文件进行修改,修改如下图所示:
image068.png
图 38.4.24 修改图1
将原代码中例化ipsl_hmic_h_top_test的部分(即57~86行)注释或删除掉,如图 38.4.24所示。
image070.png
图 38.4.25 修改图2
若不需要设置结束时间,可以将源代码中第244~549行注释或删除掉,如图 38.4.25所示。
然后将本次实验的顶层代码例化进ddr_test_top_tb文件中,例化代码如下所示:
  1. ddr3_rw_top  u_ddr3_rw_top(
  2.     .sys_clk                                (pll_refclk_in),
  3.     .sys_rst_n                              (bu_top_rst_n),
  4.     .led_error                              (),
  5.     .led_ddr_init_done                      (),
  6.     //DDR3接口         
  7.     .pad_loop_in                            (pad_loop_in  ),
  8.     .pad_loop_in_h                          (pad_loop_in_h  ),
  9.     .pad_rstn_ch0                           (pad_rstn_ch0  ),
  10.     .pad_ddr_clk_w                          (pad_ddr_clk_w  ),
  11.     .pad_ddr_clkn_w                         (pad_ddr_clkn_w ),
  12.     .pad_csn_ch0                            (pad_csn_ch0  ),
  13.     .pad_addr_ch0                           (pad_addr_ch0  ),
  14.     .pad_dq_ch0                             (pad_dq_ch0  ),
  15.     .pad_dqs_ch0                            (pad_dqs_ch0  ),
  16.     .pad_dqsn_ch0                           (pad_dqsn_ch0  ),
  17.     .pad_dm_rdqs_ch0                        (pad_dm_rdqs_ch0 ),
  18.     .pad_cke_ch0                            (pad_cke_ch0  ),
  19.     .pad_odt_ch0                            (pad_odt_ch0  ),
  20.     .pad_rasn_ch0                           (pad_rasn_ch0  ),
  21.     .pad_casn_ch0                           (pad_casn_ch0  ),
  22.     .pad_wen_ch0                            (pad_wen_ch0  ),
  23.     .pad_ba_ch0                             (pad_ba_ch0  ),
  24.     .pad_loop_out                           (pad_loop_in  ),
  25.     .pad_loop_out_h                         (pad_loop_in_h  )
  26. );
复制代码

接着我们找到sim_file_list.f文件,该文件是定位仿真中所需要添加的代码,即我们工程下的代码,其所在路径如下图所示。
image072.png
图 38.4.26 sim_file_list.f文件所在位置
用Notepad++软件打开sim_file_list.f文件并在其中添加我们例程的.v文件,其余部分无需改动,保留即可,添加代码如下所示,添加位置没有要求,可以添加在代码的开头部分。
  1. ./../../../../rtl/ddr3_rw_top.v
  2. ./../../../../rtl/ddr3_top.v
  3. ./../../../../rtl/ddr_test.v
  4. ./../../../../rtl/led_disp.v
  5. ./../../../../rtl/rw_ctrl_128bit.v
  6. ./../../../../rtl/ddr3_fifo_ctrl.v
  7. ./../../rd_fifo/rd_fifo.v
  8. ./../../rd_fifo/rtl/ipml_fifo_ctrl_v1_3.v
  9. ./../../rd_fifo/rtl/ipml_fifo_v1_6_rd_fifo.v
  10. ./../../rd_fifo/rtl/ipml_sdpram_v1_6_rd_fifo.v
  11. ./../../wr_fifo/wr_fifo.v
  12. ./../../wr_fifo/rtl/ipml_fifo_ctrl_v1_3.v
  13. ./../../wr_fifo/rtl/ipml_fifo_v1_6_wr_fifo.v
  14. ./../../wr_fifo/rtl/ipml_sdpram_v1_6_wr_fifo.v
复制代码

这里的文件位置是对于sim文件来说的相对路径,“./”表示sim文件的当前位置,“../”表示上一级,例如“./../../pll_clk/pll_clk.v”表示sim所在当前路径上上级的pll_clk文件夹下的pll_clk.v文件。
最后双击sim.bat (如下图所示)即可进行Modelsim仿真。
image073.png
图 38.4.27 双击sim进行仿真
注:如若仿真时,进入Modelsim界面后出现闪退现象,可以找到ctrl_phy_sim.tcl文件,文件所在路径如下图所示:
image074.png
图 38.4.28 ctrl_phy_sim.tcl文件所在位置
用Notepad++软件打开,将其中“vsim -suppress 3486,3680,3781 +nowarn1 -c -sva -lib work ddr_test_top_tb-l sim.log”注释或删除掉,改成“vsim -novopt -suppress 3486,3680,3781  -c ddr_test_top_tb -L work -l sim.log”具体修改如下图红框中所示。
image075.png
图 38.4.29 ctrl_phy_sim.tcl文件修改部分图示
其中run 800us表示仿真时长,因为我们并没有添加仿真信号,所以该条语句无用,可以注释掉,保存后,再次双击sim.bat即可进入仿真界面。等待Modelsim完成编译之后添加好我们所需观察的信息进行仿真即可。
image077.png
图 38.4.30 Modelsim仿真
这里增加一个知识点,添加完仿真所需观察的信号后,我们可以将其另存为一个.do文件,这样在下次需要仿真时就可以省去查找和添加信号的时间。选择File后点击Save Format,操作如下所示。
image079.png
图 38.4.31 保存成.do文件操作指示图1
存储路径保持默认即可(即工程仿真sim文件夹下),然后点击ok即可。
image081.png
图 38.4.32 保存成.do文件操作指示图2
我们将.do文件添加到ctrl_phy_sim.tcl,并取消对run 800us的注释,这样等下次再对本次工程进行仿真时,只需要双击sim.bat即可出现仿真波形。.do文件的添加语句为do wave.do(其中wave为do文件名),如下图所示。
image082.png
图 38.4.33 ctrl_phy_sim.tcl文件中添加执行.do文件的语句

1.5 下载验证
首先将下载器一端连接电脑,另一端与开发板上的JTAG下载口相连,最后连接电源线并打开电源开关。接下来我们下载程序,验证DDR3读写测试实验的结果。
程序下载完成后,LED0在短暂延时之后,开始处于常亮的状态表明DDR3初始化完成,若LED1保持常亮说明读数据正确,DDR3读写测试实验验证成功,如下图所示:
image084.png
图 38.5.1 实验现象
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-5-29 10:52

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表