本帖最后由 正点原子运营 于 2023-12-18 17:06 编辑
第三十八章 DDR3读写测试实验
1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
6)FPGA技术交流QQ群:435699340
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系统框图如下图所示: 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文件夹中,路径如下图所示。 图 38.1.2 《Logos 系列产品 HMIC_H IP用户指南》所在路径 本设计AXI4接口为标准的AXI4协议接口,接口时序可参考AXI4协议,下表为部分关键信号的接口说明。 表 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所示)。图中箭头处信息传输发生。 图 38.1.3 VALID先变高READY后变高时序图 图 38.1.4 READY先变高VALID后变高时序图 图 38.1.5 VALID和READY信号同时变高时序图 地址、读、写和写响应通道之间的关系是灵活的。例如,写数据可以出现在接口上早于与其相关联的写地址。也有可能写数据与写地址在一个周期中出现。但是有两种关系必须被保持:一是读数据必须总是跟在与其数据相关联的地址之后;二是写响应必须总是跟在与其相关联的写事务的最后出现。 通道握手信号之间是有依赖性的,读事务握手依赖关系如图 38.1.6所示,读事务握手时,设备可以在ARVALID出现的时候再给出ARREADY信号,也可以先给出ARREADY信号,再等待ARVALID信号;但是设备必须等待ARVALID和ARREADY信号都有效才能给出RVALID信号,开始数据传输。 写事务握手依赖关系如图 38.1.7所示,写事务握手时,主机不能等设备给出AWREADY或WREADY信号后再给出信号AWVALID或WVALID;设备可以等待信号AWVALID或WVALID信号有效或者两个都有效之后再给出AWREADY信号。 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拉高时,表示在告诉用户当前为此次读操作的最后一个数据。 AXI4接口连续读操作的时序如图 38.1.9所示,主设备在从设备接收第一次读操作的地址后发送下一次读操作的地址。这样可以保证一个从设备在完成第一次读操作的同时可以开始处理第二次读操作的数据。下图中进行了两次连续读操作,可以看出也相应的拉高了两次rlast_0信号,对应第一次读操作的最后一个数据和第二次读操作的最后一个数据。 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信号时,表示当前为此次写数据操作的最后一个数据。 1.2 实验任务本节的实验任务是先向DDR3的存储器中写入5120个数据,写完之后再从存储器中读取相同地址的数据。若初始化成功,则LED0常亮,否则LED0不亮;若读取的值全部正确则LED1常亮,否则LED1闪烁。
1.3 硬件设计ATK-DFPGL22G开发板上使用了一片南亚的DDR3颗粒NT5CC256M16,硬件原理图如下图所示。在PCB的设计上,完全遵照紫光的DDR3硬件设计规范,严格保证等长设计和阻抗控制,从而保证高速信号的数据传输的可靠性。 本次实验用到的所有管脚的管脚分配如下表所示。
相关的管脚约束如下所示: - <font face="Tahoma">create_clock-name {sys_clk} [get_ports {sys_clk}] -period {20.000} -waveform {0.000 10.000}
- define_attribute{p:sys_clk} {PAP_IO_DIRECTION} {INPUT}
- define_attribute{p:sys_clk} {PAP_IO_LOC} {B5}
- define_attribute{p:sys_clk} {PAP_IO_VCCIO} {3.3}
- define_attribute{p:sys_clk} {PAP_IO_STANDARD} {LVCMOS33}
- define_attribute{p:sys_clk} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:sys_rst_n} {PAP_IO_DIRECTION} {INPUT}
- define_attribute{p:sys_rst_n} {PAP_IO_LOC} {G5}
- define_attribute{p:sys_rst_n} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:sys_rst_n} {PAP_IO_STANDARD} {SSTL15_I}
- define_attribute{p:sys_rst_n} {PAP_IO_PULLUP} {TRUE}
- define_attribute{p:led_error} {PAP_IO_DIRECTION} {OUTPUT}
- define_attribute{p:led_error} {PAP_IO_LOC} {J6}
- define_attribute{p:led_error} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:led_error} {PAP_IO_STANDARD} {SSTL15_I}
- define_attribute{p:led_error} {PAP_IO_DRIVE} {7.5}
- define_attribute{p:led_error} {PAP_IO_NONE} {TRUE}
- define_attribute{p:led_error} {PAP_IO_SLEW} {SLOW}
- define_attribute{p:led_ddr_init_done} {PAP_IO_DIRECTION} {OUTPUT}
- define_attribute{p:led_ddr_init_done} {PAP_IO_LOC} {F3}
- define_attribute{p:led_ddr_init_done} {PAP_IO_VCCIO} {1.5}
- define_attribute{p:led_ddr_init_done} {PAP_IO_STANDARD} {SSTL15_I}
- define_attribute{p:led_ddr_init_done} {PAP_IO_DRIVE} {7.5}
- define_attribute{p:led_ddr_init_done} {PAP_IO_NONE} {TRUE}
- define_attribute{p:led_ddr_init_done} {PAP_IO_SLEW} {SLOW}</font>
复制代码由于约束文件过长,为减少篇幅这里只展示了部分,需要注意的是ddr3相关的引脚约束不需要我们进行手动约束,可以将官方生成的约束文件中ddr3相关的约束代码复制进我们的约束文件即可。官方约束文件所在路径如下图所示: 因为我们的主控芯片是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中查找。方法如下。 图 38.3.3 打开Physical Constraint Editor界面 打开Physical Constraint Editor界面后点击TCL图标。 下图中六个红框分别对应六个PLL编号(由上到下分别表示PLL0~PLL5),点击我们需要约束的PLL,即可在下方看到其对应编号。 如何确定需要对哪个PLL进行约束可参照下图 因为我们这里是对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灯闪烁。由此画出系统的功能框图如下图所示: 由系统总体框图可知,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如下图所示。 因为PDS软件默认安装是不带有DDR3 IP核的,所以这里需要我们手动将ddr3的IP核添加进来,添加方法如下所述: 首先在IP Compiler界面中选择菜单栏中的File下的Update,如下图所示。 点击Update IP界面中的Add Packages,然后添加DDR3 IP核如下图所示。 图 38.4.4 点击Add Packages添加IP核 点击后找到本次实验所需的DDR3IP核(1_ipsl_hmic_h_v1_1.iar),该文件存放在工程文件夹下的prj文件夹内的ipcore文件夹中,如图 38.4.5所示,单击选中后点击open,操作如图 38.4.6所示。 图 38.4.5 1_ipsl_hmic_h_v1_1.iar所在路径 然后点击Install安装DDR3 IP核即可完成添加。 此时可以看到IP Compiler界面已经添加了新的IP“Logos HMIC_H(1.1)”,选择Logos HMIC_H(1.1)并在右侧命名为ddr3_ip,然后点击Customize进行配置,如下图所示。 HMIC_HIP配置分为四个页面,分别为Step1: Basic Options,Step2: Memory Options,Step3:Interface Options,Step4:Summary,请务必按照该页面顺序配置。 Step 1:Basic Options,是IP的基本配置页面,页面如下图所示: 图 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参数的配置页面,页面如下图所示: 图 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,是接口参数的配置页面,页面如下图所示: 图 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页面,用于打印当前的配置信息,不需要配置参数,页面如下图所示: 图 38.4.12 Step 4: Summary页面 配置完成后,点击保存(上图1号标签),保存后再点击Generate(上图2号标签)生成IP,即可生成相应于用户特定设置的HMIC_H IP代码。生成IP的信息报告界面如下所示。 至此ddr3 IP核创建完成,点击关闭窗口即可。 DDR3 IP核的接口信号很多,使用时如果每次都要进行新的配置会很繁琐,所以我们将DDR3控制器封装成类似于FIFO的接口,再使用时只需要像读写FIFO那样给出读/写使能即可,这么做就方便了我们在以后的其他工程中对其进行调用。 rd_fifoIP核参数配置如下图所示: 图 38.4.14 rd_fifo IP核参数配置界面 wr_fifoIP核参数配置如下图所示: 图 38.4.15 wr_fifo IP核参数配置界面 系统的顶层模块代码如下: - 1 module ddr3_rw_top(
- 2 input sys_clk , //系统时钟50M
- 3 input sys_rst_n , //系统复位
- 4 output led_error , //读写错误led灯
- 5 output led_ddr_init_done, //ddr3初始化完成led灯
- 6
- 7 //DDR3接口
- 8 input pad_loop_in , //低位温度补偿输入
- 9 input pad_loop_in_h , //高位温度补偿输入
- 10 output pad_rstn_ch0 , //Memory复位
- 11 output pad_ddr_clk_w , //Memory差分时钟正端
- 12 output pad_ddr_clkn_w , //Memory差分时钟负端
- 13 output pad_csn_ch0 , //Memory片选
- 14 output [15:0 pad_addr_ch0 , //Memory地址总线
- 15 inout [16-1:0 pad_dq_ch0 , //数据总线
- 16 inout [16/8-1:0 pad_dqs_ch0 , //数据时钟正端
- 17 inout [16/8-1:0 pad_dqsn_ch0 , //数据时钟负端
- 18 output [16/8-1:0 pad_dm_rdqs_ch0 , //数据Mask
- 19 output pad_cke_ch0 , //Memory差分时钟使能
- 20 output pad_odt_ch0 , //On DieTermination
- 21 output pad_rasn_ch0 , //行地址strobe
- 22 output pad_casn_ch0 , //列地址strobe
- 23 output pad_wen_ch0 , //写使能
- 24 output [2:0 pad_ba_ch0 , //Bank地址总线
- 25 output pad_loop_out , //低位温度补偿输出
- 26 output pad_loop_out_h //高位温度补偿输出
- 27 );
- 28
- 29 //parameter define
- 30 parameter APP_ADDR_MIN = 28'd0 ; //ddr3读写起始地址,以一个16bit的数据为一个单位
- 31 //APP_ADDR_MAX =BURST_LENGTH * 8 * (n+1)(n表示突发次数)
- 32 parameter APP_ADDR_MAX = 28'd5120 ; //ddr3读写结束地址,以一个16bit的数据为一个单位
- 33 parameter BURST_LENGTH = 8'd64 ; //ddr3读写突发长度,64个128bit的数据
- 34 parameter DATA_MAX = APP_ADDR_MAX - APP_ADDR_MIN; //读写ddr3的最大数据量
- 35
- 36 //wire define
- 37 wire [15:0 wr_data ; //DDR3控制器模块写数据
- 38 wire [15:0 rd_data ; //DDR3控制器模块读数据
- 39 wire wr_en ; //DDR3控制器模块写使能
- 40 wire rd_en ; //DDR3控制器模块读使能
- 41 wire ddr_init_done ; //ddr3初始化完成信号
- 42 wire error_flag ; //ddr3读写错误标志
- 43
- 44 ////*****************************************************
- 45 ////** main code
- 46 ////*****************************************************
- 47 //ddr3控制器顶层模块
- 48 ddr3_top u_ddr3_top(
- 49 .refclk_in (sys_clk ),
- 50 .rst_n (sys_rst_n ),
- 51 .app_addr_rd_min (APP_ADDR_MIN ),
- 52 .app_addr_rd_max (APP_ADDR_MAX ),
- 53 .rd_bust_len (BURST_LENGTH ),
- 54 .app_addr_wr_min (APP_ADDR_MIN ),
- 55 .app_addr_wr_max (APP_ADDR_MAX ),
- 56 .wr_bust_len (BURST_LENGTH ),
- 57 .wr_clk (sys_clk ),
- 58 .rd_clk (sys_clk ),
- 59 .datain_valid (wr_en ),
- 60 .datain (wr_data ),
- 61 .rdata_req (rd_en ),
- 62 .dataout (rd_data ),
- 63 .ddr_init_done (ddr_init_done ),
- 64 //DDR3接口
- 65 .pad_loop_in (pad_loop_in ),
- 66 .pad_loop_in_h (pad_loop_in_h ),
- 67 .pad_rstn_ch0 (pad_rstn_ch0 ),
- 68 .pad_ddr_clk_w (pad_ddr_clk_w ),
- 69 .pad_ddr_clkn_w (pad_ddr_clkn_w ),
- 70 .pad_csn_ch0 (pad_csn_ch0 ),
- 71 .pad_addr_ch0 (pad_addr_ch0 ),
- 72 .pad_dq_ch0 (pad_dq_ch0 ),
- 73 .pad_dqs_ch0 (pad_dqs_ch0 ),
- 74 .pad_dqsn_ch0 (pad_dqsn_ch0 ),
- 75 .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
- 76 .pad_cke_ch0 (pad_cke_ch0 ),
- 77 .pad_odt_ch0 (pad_odt_ch0 ),
- 78 .pad_rasn_ch0 (pad_rasn_ch0 ),
- 79 .pad_casn_ch0 (pad_casn_ch0 ),
- 80 .pad_wen_ch0 (pad_wen_ch0 ),
- 81 .pad_ba_ch0 (pad_ba_ch0 ),
- 82 .pad_loop_out (pad_loop_out ),
- 83 .pad_loop_out_h (pad_loop_out_h )
- 84 );
- 85
- 86 //ddr3测试数据模块
- 87 ddr_test u_ddr_test(
- 88 .clk_50m (sys_clk ), //时钟
- 89 .rst_n (sys_rst_n ), //复位,低有效
- 90 .wr_en (wr_en ), //写使能
- 91 .wr_data (wr_data ), //写数据
- 92 .rd_en (rd_en ), //读使能
- 93 .rd_data (rd_data ), //读数据
- 94 .data_max (DATA_MAX ), //读写ddr的最大数据量
- 95 .ddr3_init_done(ddr_init_done ), //ddr3初始化完成信号
- 96 .error_flag (error_flag ) //ddr3读写错误
- 97 );
- 98
- 99 //利用LED灯指示ddr3读写测试的结果及ddr3是否初始化完成
- 100 led_disp u_led_disp(
- 101 .clk_50m (sys_clk ),
- 102 .rst_n (sys_rst_n ),
- 103 .ddr3_init_done (ddr_init_done ),
- 104 .error_flag (error_flag ),
- 105 .led_error (led_error ),
- 106 .led_ddr_init_done (led_ddr_init_done)
- 107 );
- 108
- 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 module ddr3_top(
- 2 input refclk_in ,//外部参考时钟输入
- 3 input rst_n ,//外部复位输入
- 4
- 5 input [27:0 app_addr_rd_min ,//读ddr3的起始地址
- 6 input [27:0 app_addr_rd_max ,//读ddr3的结束地址
- 7 input [7:0 rd_bust_len ,//从ddr3中读数据时的突发长度
- 8 input [27:0 app_addr_wr_min ,//读ddr3的起始地址
- 9 input [27:0 app_addr_wr_max ,//读ddr3的结束地址
- 10 input [7:0 wr_bust_len ,//从ddr3中读数据时的突发长度
- 11 //用户
- 12 input wr_clk ,//wfifo写时钟
- 13 input rd_clk ,//rfifo读时钟
- 14 input datain_valid ,//数据有效使能信号
- 15 input [15:0 datain ,//有效数据
- 16 input rdata_req ,//请求数据输入
- 17 output [15:0 dataout ,//rfifo输出数据
- 18
- 19 output ddr_init_done ,//DDR初始化完成
- 20 input pad_loop_in ,
- 21 input pad_loop_in_h ,
- 22 output pad_rstn_ch0 ,
- 23 output pad_ddr_clk_w ,
- 24 output pad_ddr_clkn_w ,
- 25 output pad_csn_ch0 ,
- 26 output [15:0 pad_addr_ch0 ,
- 27 inout [16-1:0 pad_dq_ch0 ,
- 28 inout [16/8-1:0 pad_dqs_ch0 ,
- 29 inout [16/8-1:0 pad_dqsn_ch0 ,
- 30 output [16/8-1:0 pad_dm_rdqs_ch0 ,
- 31 output pad_cke_ch0 ,
- 32 output pad_odt_ch0 ,
- 33 output pad_rasn_ch0 ,
- 34 output pad_casn_ch0 ,
- 35 output pad_wen_ch0 ,
- 36 output [2:0 pad_ba_ch0 ,
- 37 output pad_loop_out ,
- 38 output pad_loop_out_h
- 39 );
- 40
- 41 //wire define
- 42 wire [32-1:0 axi_awaddr ;
- 43 wire [7:0 axi_awlen ;
- 44 wire [2:0 axi_awsize ;
- 45 wire [1:0 axi_awburst ;
- 46 wire axi_awlock ;
- 47 wire axi_awready ;
- 48 wire axi_awvalid ;
- 49 wire axi_awurgent ;
- 50 wire axi_awpoison ;
- 51 wire [128-1:0 axi_wdata ;
- 52 wire [16-1:0 axi_wstrb ;
- 53 wire axi_wvalid ;
- 54 wire axi_wready ;
- 55 wire axi_wlast ;
- 56 wire axi_bready ;
- 57 wire [32-1:0 axi_araddr ;
- 58 wire [7:0 axi_arlen ;
- 59 wire [2:0 axi_arsize ;
- 60 wire [1:0 axi_arburst ;
- 61 wire axi_arlock ;
- 62 wire axi_arpoison ;
- 63 wire axi_arurgent ;
- 64 wire axi_arready ;
- 65 wire axi_arvalid ;
- 66 wire [128-1:0 axi_rdata ;
- 67 wire axi_rlast ;
- 68 wire axi_rvalid ;
- 69 wire axi_rready ;
- 70 wire axi_clk ;
- 71 wire [10:0 wfifo_rcount ;//rfifo剩余数据计数
- 72 wire [10:0 rfifo_wcount ;//wfifo写进数据计数
- 73 wire wrfifo_en_ctrl ;//写FIFO数据读使能控制位
- 74 wire wfifo_rden ;//写FIFO数据读使能
- 75 wire pre_wfifo_rden ;//写FIFO数据预读使能
- 76
- 77 //*****************************************************
- 78 //** main code
- 79 //*****************************************************
- 80 //因为预读了一个数据所以读使能wfifo_rden要少一个周期通过wrfifo_en_ctrl控制
- 81 assign wfifo_rden = axi_wvalid && axi_wready && (~wrfifo_en_ctrl) ;
- 82 assign pre_wfifo_rden = axi_awvalid && axi_awready ;
- 83
- 84 //ddr3读写控制器模块
- 85 rw_ctrl_128bit u_rw_ctrl_128bit
- 86 (
- 87 .clk (axi_clk ),
- 88 .rst_n (rst_n ),
- 89 .ddr_init_done (ddr_init_done ),
- 90 .axi_awaddr (axi_awaddr ),
- 91 .axi_awlen (axi_awlen ),
- 92 .axi_awsize (axi_awsize ),
- 93 .axi_awburst (axi_awburst ),
- 94 .axi_awlock (axi_awlock ),
- 95 .axi_awready (axi_awready ),
- 96 .axi_awvalid (axi_awvalid ),
- 97 .axi_awurgent (axi_awurgent ),
- 98 .axi_awpoison (axi_awpoison ),
- 99 .axi_wstrb (axi_wstrb ),
- 100 .axi_wvalid (axi_wvalid ),
- 101 .axi_wready (axi_wready ),
- 102 .axi_wlast (axi_wlast ),
- 103 .axi_bready (axi_bready ),
- 104 .wrfifo_en_ctrl (wrfifo_en_ctrl ),
- 105 .axi_araddr (axi_araddr ),
- 106 .axi_arlen (axi_arlen ),
- 107 .axi_arsize (axi_arsize ),
- 108 .axi_arburst (axi_arburst ),
- 109 .axi_arlock (axi_arlock ),
- 110 .axi_arpoison (axi_arpoison ),
- 111 .axi_arurgent (axi_arurgent ),
- 112 .axi_arready (axi_arready ),
- 113 .axi_arvalid (axi_arvalid ),
- 114 .axi_rlast (axi_rlast ),
- 115 .axi_rvalid (axi_rvalid ),
- 116 .axi_rready (axi_rready ),
- 117 .wfifo_rcount (wfifo_rcount ),
- 118 .rfifo_wcount (rfifo_wcount ),
- 119 .app_addr_rd_min (app_addr_rd_min ),
- 120 .app_addr_rd_max (app_addr_rd_max ),
- 121 .rd_bust_len (rd_bust_len ),
- 122 .app_addr_wr_min (app_addr_wr_min ),
- 123 .app_addr_wr_max (app_addr_wr_max ),
- 124 .wr_bust_len (wr_bust_len )
- 125 );
- 126
- 127 //ddr3IP核模块
- 128 ddr3_ip u_ddr3_ip (
- 129 .pll_refclk_in (refclk_in ), // input
- 130 .top_rst_n (rst_n ), // input
- 131 .ddrc_rst (0 ), // input
- 132 .csysreq_ddrc (1'b1 ), // input
- 133 .csysack_ddrc ( ), //output
- 134 .cactive_ddrc ( ), //output
- 135 .pll_lock ( ), //output
- 136 .pll_aclk_0 (axi_clk ), //output
- 137 .pll_aclk_1 ( ), //output
- 138 .pll_aclk_2 ( ), //output
- 139 .ddrphy_rst_done ( ), // output
- 140 .ddrc_init_done (ddr_init_done ), // output
- 141 .pad_loop_in (pad_loop_in ), // input
- 142 .pad_loop_in_h (pad_loop_in_h ), // input
- 143 .pad_rstn_ch0 (pad_rstn_ch0 ), //output
- 144 .pad_ddr_clk_w (pad_ddr_clk_w ), //output
- 145 .pad_ddr_clkn_w (pad_ddr_clkn_w), // output
- 146 .pad_csn_ch0 (pad_csn_ch0 ), //output
- 147 .pad_addr_ch0 (pad_addr_ch0 ), //output [15:0]
- 148 .pad_dq_ch0 (pad_dq_ch0 ), // inout[15:0]
- 149 .pad_dqs_ch0 (pad_dqs_ch0 ), // inout[1:0]
- 150 .pad_dqsn_ch0 (pad_dqsn_ch0 ), // inout[1:0]
- 151 .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0), //output [1:0]
- 152 .pad_cke_ch0 (pad_cke_ch0 ), //output
- 153 .pad_odt_ch0 (pad_odt_ch0 ), //output
- 154 .pad_rasn_ch0 (pad_rasn_ch0 ), //output
- 155 .pad_casn_ch0 (pad_casn_ch0 ), //output
- 156 .pad_wen_ch0 (pad_wen_ch0 ), //output
- 157 .pad_ba_ch0 (pad_ba_ch0 ), //output [2:0]
- 158 .pad_loop_out (pad_loop_out ), //output
- 159 .pad_loop_out_h (pad_loop_out_h), // output
- 160 .areset_0 (0 ), // input
- 161 .aclk_0 (axi_clk ), // input
- 162 .awid_0 (0 ), // input [7:0]
- 163 .awaddr_0 (axi_awaddr ), // input[31:0]
- 164 .awlen_0 (axi_awlen ), // input[7:0]
- 165 .awsize_0 (axi_awsize ), // input[2:0]
- 166 .awburst_0 (axi_awburst ), // input[1:0]
- 167 .awlock_0 (axi_awlock ), // input
- 168 .awvalid_0 (axi_awvalid ), // input
- 169 .awready_0 (axi_awready ), // output
- 170 .awurgent_0 (axi_awurgent ), // input
- 171 .awpoison_0 (axi_awpoison ), // input
- 172 .wdata_0 (axi_wdata ), // input[127:0]
- 173 .wstrb_0 (axi_wstrb ), // input[15:0]
- 174 .wlast_0 (axi_wlast ), // input
- 175 .wvalid_0 (axi_wvalid ), // input
- 176 .wready_0 (axi_wready ), //output
- 177 .bid_0 ( ), //output [7:0]
- 178 .bresp_0 ( ), //output [1:0]
- 179 .bvalid_0 ( ), //output
- 180 .bready_0 (axi_bready ), // input
- 181 .arid_0 (0 ), // input [7:0]
- 182 .araddr_0 (axi_araddr ), // input[31:0]
- 183 .arlen_0 (axi_arlen ), // input[7:0]
- 184 .arsize_0 (axi_arsize ), // input[2:0]
- 185 .arburst_0 (axi_arburst ), // input[1:0]
- 186 .arlock_0 (axi_arlock ), // input
- 187 .arvalid_0 (axi_arvalid ), // input
- 188 .arready_0 (axi_arready ), //output
- 189 .arpoison_0 (axi_arpoison ), // input
- 190 .rid_0 ( ), //output [7:0]
- 191 .rdata_0 (axi_rdata ), //output [127:0]
- 192 .rresp_0 ( ), //output [1:0]
- 193 .rlast_0 (axi_rlast ), //output
- 194 .rvalid_0 (axi_rvalid ), //output
- 195 .rready_0 (axi_rready ), // input
- 196 .arurgent_0 (axi_arurgent ), // input
- 197 .csysreq_0 (1'b1 ), // input
- 198 .csysack_0 ( ), //output
- 199 .cactive_0 ( ) // output
- 200 );
- 201
- 202 //ddr3控制器fifo控制模块
- 203 ddr3_fifo_ctrl u_ddr3_fifo_ctrl (
- 204 .rst_n (rst_n && ddr_init_done ) , //复位
- 205 //输入源接口
- 206 .wr_clk (wr_clk ) , //写时钟
- 207 .rd_clk (rd_clk ) , //读时钟
- 208 .clk_100 (axi_clk ) , //用户时钟
- 209 .datain_valid (datain_valid ) , //数据有效使能信号
- 210 .datain (datain ) , //有效数据
- 211 .rfifo_din (axi_rdata ) , //用户读数据
- 212 .rdata_req (rdata_req ) , //请求像素点颜色数据输入
- 213 .rfifo_wren (axi_rvalid ) , //ddr3读出数据的有效使能
- 214 .wfifo_rden (wfifo_rden||pre_wfifo_rden) , //ddr3 写使能
- 215 //用户接口
- 216 .wfifo_rcount (wfifo_rcount ) , //rfifo剩余数据计数
- 217 .rfifo_wcount (rfifo_wcount ) , //wfifo写进数据计数
- 218 .wfifo_dout (axi_wdata ) , //用户写数据
- 219 .pic_data (dataout ) //rfifo输出数据
- 220 );
- 221
- 222 endmodule
复制代码
ddr3控制器顶层模块主要完成ddr3读写控制器模块、FIFO控制模块和ddr3 IP核的例化。ddr3读写控制器模块负责与ddr3 IP核模块的命令和地址的交互,根据FIFO控制模块中fifo的剩余数据量来切换DDR3的读写命令和地址。ddr3 IP核模块一边与用户端进行交互,另一边对芯片进行操作,以实现数据的存储。FIFO控制模块负责对输入和输出的数据进行时钟域的切换和位宽的转换。 ddr3读写控制器模块代码如下: - 1 module rw_ctrl_128bit
- 2 (
- 3 input clk , //时钟
- 4 input rst_n , //复位
- 5 input ddr_init_done , //DDR初始化完成
- 6 output [32-1:0 axi_awaddr , //写地址
- 7 output reg [7:0 axi_awlen , //写突发长度
- 8 output wire [2:0 axi_awsize , //写突发大小
- 9 output wire [1:0 axi_awburst , //写突发类型
- 10 output axi_awlock , //写锁定类型
- 11 input axi_awready , //写地址准备信号
- 12 output reg axi_awvalid , //写地址有效信号
- 13 output axi_awurgent , //写紧急信号,1:Write address指令优先执行
- 14 output axi_awpoison , //写抑制信号,1:Write address指令无效
- 15 output wire [15:0 axi_wstrb , //写选通
- 16 output reg axi_wvalid , //写数据有效信号
- 17 input axi_wready , //写数据准备信号
- 18 output reg axi_wlast , //最后一次写信号
- 19 output wire axi_bready , //写回应准备信号
- 20 output reg wrfifo_en_ctrl , //写FIFO数据读使能控制位
- 21 output [32-1:0 axi_araddr , //读地址
- 22 output reg [7:0 axi_arlen , //读突发长度
- 23 output wire [2:0 axi_arsize , //读突发大小
- 24 output wire [1:0 axi_arburst , //读突发类型
- 25 output wire axi_arlock , //读锁定类型
- 26 output wire axi_arpoison , //读抑制信号,1:Read address指令无效
- 27 output wire axi_arurgent , //读紧急信号,1:Read address指令优先执行
- 28 input axi_arready , //读地址准备信号
- 29 output reg axi_arvalid , //读地址有效信号
- 30 input axi_rlast , //最后一次读信号
- 31 input axi_rvalid , //读数据有效信号
- 32 output wire axi_rready , //读数据准备信号
- 33 input [10:0 wfifo_rcount , //写端口FIFO中的数据量
- 34 input [10:0 rfifo_wcount , //读端口FIFO中的数据量
- 35 input [27:0 app_addr_rd_min , //读DDR3的起始地址
- 36 input [27:0 app_addr_rd_max , //读DDR3的结束地址
- 37 input [7:0 rd_bust_len , //从DDR3中读数据时的突发长度
- 38 input [27:0 app_addr_wr_min , //写DDR3的起始地址
- 39 input [27:0 app_addr_wr_max , //写DDR3的结束地址
- 40 input [7:0 wr_bust_len //从DDR3中写数据时的突发长度
- 41 );
- 42
- 43 //localparam define
- 44 localparam IDLE = 4'd1 ; //空闲状态
- 45 localparam DDR3_DONE =4'd2 ; //DDR3初始化完成状态
- 46 localparam WRITE_ADDR = 4'd3 ; //写地址
- 47 localparam WRITE_DATA = 4'd4 ; //写数据
- 48 localparam READ_ADDR =4'd5 ; //读地址
- 49 localparam READ_DATA =4'd6 ; //读数据
- 50
- 51 //reg define
- 52 reg init_start ;//初始化完成信号
- 53 reg [31:0 init_addr ; //突发长度计数器
- 54 reg [31:0 axi_araddr_n ; //读地址计数
- 55 reg [31:0 axi_awaddr_n ; //写地址计数
- 56 reg [3:0 state_cnt ; //状态计数器
- 57 reg [9:0 lenth_cnt ; //突发写次数计数器
- 58
- 59 //wire define
- 60 wire [9:0 lenth_cnt_max; //最大突发次数
- 61
- 62 //*****************************************************
- 63 //** main code
- 64 //*****************************************************
- 65
- 66 assign axi_awlock = 1'b0 ;
- 67 assign axi_awurgent = 1'b0 ;
- 68 assign axi_awpoison = 1'b0 ;
- 69 assign axi_bready = 1'b1 ;
- 70 assign axi_wstrb = {16{1'b1}};
- 71 assign axi_awsize = 3'b100 ;
- 72 assign axi_awburst = 2'd1 ;
- 73 assign axi_arlock = 1'b0 ;
- 74 assign axi_arurgent = 1'b0 ;
- 75 assign axi_arpoison = 1'b0 ;
- 76 assign axi_arsize = 3'b100 ;
- 77 assign axi_arburst = 2'd1 ;
- 78 assign axi_rready = 1'b1 ;
- 79
- 80 //计算最大突发次数
- 81 assign lenth_cnt_max = app_addr_wr_max / (wr_bust_len * 4'd8);
- 82
- 83 //读写地址,因为第0位无效,所以读写地址数据从第1位开始填入
- 84 assign axi_araddr = {6'b0,axi_araddr_n[24:0],1'b0};
- 85 assign axi_awaddr = {6'b0,axi_awaddr_n[24:0],1'b0};
- 86
- 87 //稳定ddr3初始化信号
- 88 always @(posedge clk or negedge rst_n) begin
- 89 if (!rst_n)
- 90 init_start <= 1'b0;
- 91 else if (ddr_init_done)
- 92 init_start <= ddr_init_done;
- 93 else
- 94 init_start <= init_start;
- 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初始化完成信号后,就将该信号一直拉高,使后续模块运行时时序不受影响。 - 96
- 97 //写地址模块
- 98 always @(posedge clk or negedge rst_n) begin
- 99 if (!rst_n) begin
- 100 axi_awaddr_n <= app_addr_wr_min;
- 101 axi_awlen <= 8'b0;
- 102 axi_awvalid <= 1'b0;
- 103 end
- 104 //DDR3初始化完成
- 105 else if (init_start) begin
- 106 axi_awlen <= wr_bust_len - 1'b1;
- 107 //当写地址计数小于最后一次写地址起始位时
- 108 if (axi_awaddr_n < {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
- 109 //写地址有效信号和写地址准备信号都为1时
- 110 if (axi_awvalid && axi_awready) begin
- 111 axi_awvalid <= 1'b0; //拉低写地址有效信号
- 112 //写地址计数加一个突发长度所需的地址
- 113 axi_awaddr_n <= axi_awaddr_n + wr_bust_len * 5'd16;//wr_bust_len*128/8
- 114 end
- 115 //状态机处于写地址状态且写地址准备信号为1时
- 116 else if (state_cnt == WRITE_ADDR && axi_awready)
- 117 axi_awvalid <= 1'b1; //拉高写地址有效信号
- 118 end
- 119 //当写地址计数等于最后一次写地址起始位时
- 120 else if (axi_awaddr_n == {app_addr_wr_max , 1'b0} - wr_bust_len * 5'd16) begin
- 121 if (axi_awvalid && axi_awready) begin
- 122 axi_awvalid <= 1'b0;
- 123 axi_awaddr_n <= app_addr_wr_min; //写地址计数清零(回到写起始地址)
- 124 end
- 125 else if (state_cnt == WRITE_ADDR && axi_awready)
- 126 axi_awvalid <= 1'b1;
- 127 end
- 128 else
- 129 axi_awvalid <= 1'b0;
- 130 end
- 131 else begin
- 132 axi_awaddr_n <= axi_awaddr_n;
- 133 axi_awlen <= 8'b0;
- 134 axi_awvalid <= 1'b0;
- 135 end
- 136 end
- 137
复制代码
第98~136行代码执行写地址操作,ddr3初始化完成后,若写地址计数小于最后一次写地址起始位时,如果当前状态机处于写地址状态且写地址准备信号有效,拉高写地址有效信号;写地址有效信号和写地址准备信号同时为高时,写地址计数器(axi_awaddr_n)增加一个突发长度所需的地址并将写地址有效信号拉低,即写地址有效信号只拉高了一个时钟周期。若写地址计数小于最后一次写地址起始位时,当写地址有效信号和写地址准备信号同时为高时,将写地址计数器清零(即回到写起始地址),其他信号变化相同。 - 138 //写数据模块
- 139 always @(posedge clk or negedge rst_n) begin
- 140 if (!rst_n) begin
- 141 axi_wvalid <= 1'b0 ;
- 142 axi_wlast <= 1'b0 ;
- 143 init_addr <= 32'd0 ;
- 144 lenth_cnt <= 8'd0 ;
- 145 wrfifo_en_ctrl <= 1'b0;
- 146 end
- 147 else begin
- 148 //DDR3初始化完成
- 149 if (init_start) begin
- 150 //当突发写次数计数器小于最大突发次数时
- 151 if (lenth_cnt < lenth_cnt_max) begin
- 152 if (axi_wvalid && axi_wready && init_addr < wr_bust_len - 2'd2) begin
- 153 init_addr <= init_addr + 1'b1;
- 154 wrfifo_en_ctrl <= 1'b0;
- 155 end
- 156 //因为写DDR时已经提前让FIFO准备好第一个数据,所以使能在写结尾要减少一个使能周期
- 157 else if(axi_wvalid && axi_wready && init_addr==wr_bust_len - 2'd2) begin
- 158 axi_wlast <= 1'b1;
- 159 wrfifo_en_ctrl <= 1'b1; //提前一个时钟周期拉高
- 160 init_addr <= init_addr + 1'b1;
- 161 end
- 162 //当突发长度计数器等于一次突发长度时
- 163 else if(axi_wvalid && axi_wready && init_addr==wr_bust_len - 2'd1) begin
- 164 axi_wvalid <= 1'b0;
- 165 axi_wlast <= 1'b0;
- 166 wrfifo_en_ctrl <= 1'b0;
- 167 lenth_cnt <= lenth_cnt + 1'b1; //突发写次数计数器加1
- 168 init_addr <= 32'd0;
- 169 end
- 170 else if (state_cnt == WRITE_DATA && axi_wready)
- 171 axi_wvalid <= 1'b1;
- 172 else
- 173 lenth_cnt <= lenth_cnt;
- 174 end
- 175 else begin
- 176 axi_wvalid <= 1'b0 ;
- 177 axi_wlast <= 1'b0 ;
- 178 init_addr <= init_addr;
- 179 lenth_cnt <= 8'd0 ;
- 180 end
- 181 end
- 182 else begin
- 183 axi_wvalid <= 1'b0 ;
- 184 axi_wlast <= 1'b0 ;
- 185 init_addr <= 32'd0;
- 186 lenth_cnt <= 8'd0 ;
- 187 end
- 188 end
- 189 end
- 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信号只拉高一个时钟周期,为下一次写数据操作做准备。 - 191 //读地址模块
- 192 always @(posedge clk or negedge rst_n) begin
- 193 if (!rst_n) begin
- 194 axi_araddr_n <= app_addr_rd_min;
- 195 axi_arlen <= 8'b0;
- 196 axi_arvalid <= 1'b0;
- 197 end
- 198 //DDR3初始化完成
- 199 else if(init_start) begin
- 200 axi_arlen <= rd_bust_len - 1'b1;
- 201 //当读地址计数小于最后一次读地址起始位时
- 202 if (axi_araddr_n < {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
- 203 if (axi_arready && axi_arvalid) begin
- 204 axi_arvalid <= 1'b0;
- 205 axi_araddr_n <= axi_araddr_n + rd_bust_len * 5'd16;
- 206 end
- 207 else if(axi_arready && state_cnt == READ_ADDR)
- 208 axi_arvalid <= 1'b1;
- 209 end
- 210 //当读地址计数等于最后一次读地址起始位时
- 211 else if (axi_araddr_n == {app_addr_rd_max , 1'b0} - rd_bust_len * 5'd16) begin
- 212 if (axi_arready && axi_arvalid) begin
- 213 axi_arvalid <= 1'b0;
- 214 axi_araddr_n <= app_addr_rd_min;
- 215 end
- 216 else if(axi_arready && state_cnt==READ_ADDR)
- 217 axi_arvalid <= 1'b1;
- 218 end
- 219 else
- 220 axi_arvalid <= 1'b0;
- 221 end
- 222 else begin
- 223 axi_araddr_n <=app_addr_rd_min;
- 224 axi_arlen <= 8'b0;
- 225 axi_arvalid <= 1'b0;
- 226 end
- 227 end
- 228
复制代码
读地址操作的信号跳转与写地址操作时类似,这里不再赘述。 - 229 //DDR3读写逻辑实现模块
- 230 always @(posedge clk or negedge rst_n) begin
- 231 if(~rst_n) begin
- 232 state_cnt <= IDLE;
- 233 end
- 234 else begin
- 235 case(state_cnt)
- 236 IDLE:begin
- 237 if(init_start)
- 238 state_cnt <= DDR3_DONE ;
- 239 else
- 240 state_cnt <= IDLE;
- 241 end
- 242 DDR3_DONE:begin
- 243 if(wfifo_rcount >= wr_bust_len)
- 244 state_cnt <= WRITE_ADDR; //跳到写地址操作
- 245 else if(rfifo_wcount < rd_bust_len)
- 246 state_cnt <= READ_ADDR; //跳到读地址操作
- 247 else
- 248 state_cnt <= state_cnt;
- 249 end
- 250 WRITE_ADDR:begin
- 251 if(axi_awvalid && axi_awready)
- 252 state_cnt <= WRITE_DATA; //跳到写数据操作
- 253 else
- 254 state_cnt <= state_cnt; //条件不满足,保持当前值
- 255 end
- 256 WRITE_DATA:begin
- 257 if(axi_wvalid && axi_wready && init_addr == wr_bust_len - 1)
- 258 state_cnt <= DDR3_DONE; //写到设定的长度跳到等待状态
- 259 else
- 260 state_cnt <= state_cnt; //写条件不满足,保持当前值
- 261 end
- 262 READ_ADDR:begin
- 263 if(axi_arvalid && axi_arready)
- 264 state_cnt <= READ_DATA; //跳到写数据操作
- 265 else
- 266 state_cnt <= state_cnt; //条件不满足,保持当前值
- 267 end
- 268 READ_DATA:begin
- 269 if(axi_rlast) //读到设定的地址长度
- 270 state_cnt <= DDR3_DONE; //则跳到空闲状态
- 271 else
- 272 state_cnt <= state_cnt; //否则保持当前值
- 273 end
- 274 default:begin
- 275 state_cnt <= IDLE;
- 276 end
- 277 endcase
- 278 end
- 279 end
- 280
- 281 endmodule
复制代码
第230~279行代码是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 module ddr3_fifo_ctrl(
- 2 input rst_n , //复位信号
- 3 input wr_clk , //wfifo时钟
- 4 input rd_clk , //rfifo时钟
- 5 input clk_100 , //用户时钟
- 6 input datain_valid , //数据有效使能信号
- 7 input [15:0 datain , //有效数据
- 8 input [127:0 rfifo_din , //用户读数据
- 9 input rdata_req , //请求像素点颜色数据输入
- 10 input rfifo_wren , //从ddr3读出数据的有效使能
- 11 input wfifo_rden , //wfifo读使能
- 12 output [127:0 wfifo_dout , //用户写数据
- 13 output [10:0 wfifo_rcount , //rfifo剩余数据计数
- 14 output [10:0 rfifo_wcount , //wfifo写进数据计数
- 15 output [15:0 pic_data //有效数据
- 16 );
- 17
- 18 rd_fifo u_rd_fifo (
- 19 .wr_clk (clk_100 ), // input
- 20 .wr_rst (~rst_n ), // input
- 21 .wr_en (rfifo_wren ), // input
- 22 .wr_data (rfifo_din ), // input [127:0]
- 23 .wr_full ( ), // output
- 24 .wr_water_level (rfifo_wcount), // output
- 25 .almost_full ( ), // output
- 26 .rd_clk (rd_clk ), // input
- 27 .rd_rst (~rst_n ), // input
- 28 .rd_en (rdata_req ),
- 29 .rd_data (pic_data ), // output [15:0]
- 30 .rd_empty ( ), // output
- 31 .rd_water_level ( ), // output
- 32 .almost_empty ( ) // output
- 33 );
- 34
- 35 wr_fifo u_wr_fifo (
- 36 .wr_clk (wr_clk ), // input
- 37 .wr_rst (~rst_n ), // input
- 38 .wr_en (datain_valid),
- 39 .wr_data (datain ), //input [15:0]
- 40 .wr_full ( ), // output
- 41 .wr_water_level ( ), // output
- 42 .almost_full ( ), // output
- 43 .rd_clk (clk_100 ), // input
- 44 .rd_rst (~rst_n ), // input
- 45 .rd_en (wfifo_rden ), // input
- 46 .rd_data (wfifo_dout ), // output [127:0]
- 47 .rd_empty ( ), // output
- 48 .rd_water_level (wfifo_rcount), // output
- 49 .almost_empty ( ) // output
- 50 );
- 51 endmodule
复制代码
该模块例化了两个FIFO IP核,分别为128位进16位出的读FIFO和16位进128位出的写FIFO。读FIFO是将DDR3输出的128位宽的数据转为16位宽的数据后输出给用户;写FIFO是将用户输入的16位宽的数据转为128位宽的数据后输出给DDR3。 ddr测试数据模块代码如下: - 1 module ddr_test(
- 2 input clk_50m , //时钟
- 3 input rst_n , //复位,低有效
- 4
- 5 output reg wr_en , //写使能
- 6 output reg [15:0 wr_data , //写数据
- 7 output reg rd_en , //读使能
- 8 input [15:0 rd_data , //读数据
- 9 input [27:0 data_max , //写入ddr的最大数据量
- 10
- 11 input ddr3_init_done, //ddr3初始化完成信号
- 12 output reg error_flag //ddr3读写错误
- 13
- 14 );
- 15
- 16 //reg define
- 17 reg init_done_d0;
- 18 reg init_done_d1;
- 19 reg [27:0 wr_cnt ; //写操作计数器
- 20 reg [27:0 rd_cnt ; //读操作计数器
- 21 reg rd_valid ; //读数据有效标志
- 22 reg [27:0 rd_cnt_d0 ;
- 23
- 24 //*****************************************************
- 25 //** main code
- 26 //*****************************************************
- 27
- 28 //同步ddr3初始化完成信号
- 29 always @(posedge clk_50m or negedge rst_n) begin
- 30 if(!rst_n) begin
- 31 init_done_d0 <= 1'b0 ;
- 32 init_done_d1 <= 1'b0 ;
- 33 end
- 34 else begin
- 35 init_done_d0 <= ddr3_init_done;
- 36 init_done_d1 <= init_done_d0;
- 37 end
- 38 end
- 39
- 40 //对读计数器做一拍延时使数据对齐
- 41 always @(posedge clk_50m or negedge rst_n) begin
- 42 if(!rst_n)
- 43 rd_cnt_d0 <= 28'd0;
- 44 else
- 45 rd_cnt_d0 <= rd_cnt;
- 46 end
- 47
- 48 //ddr3初始化完成之后,写操作计数器开始计数
- 49 always @(posedge clk_50m or negedge rst_n) begin
- 50 if(!rst_n)
- 51 wr_cnt <= 28'd0;
- 52 else if(init_done_d1 && (wr_cnt < data_max ))
- 53 wr_cnt <= wr_cnt + 1'b1;
- 54 else
- 55 wr_cnt <= wr_cnt;
- 56 end
- 57
- 58 //ddr3写端口FIFO的写使能、写数据
- 59 always @(posedge clk_50m or negedge rst_n) begin
- 60 if(!rst_n) begin
- 61 wr_en <=1'b0;
- 62 wr_data <= 16'd0;
- 63 end
- 64 else if(wr_cnt >= 11'd0 && (wr_cnt < data_max )&&init_done_d1) begin
- 65 wr_en <=1'b1; //写使能拉高
- 66 wr_data <= wr_cnt[15:0]; //写入数据
- 67 end
- 68 else begin
- 69 wr_en <=1'b0;
- 70 wr_data <= 16'd0;
- 71 end
- 72 end
- 73
- 74 //写入数据完成后,开始读操作
- 75 always @(posedge clk_50m or negedge rst_n) begin
- 76 if(!rst_n)
- 77 rd_en <= 1'b0;
- 78 else if(wr_cnt >= data_max ) //写数据完成
- 79 rd_en <= 1'b1; //读使能
- 80 else
- 81 rd_en <= rd_en;
- 82 end
- 83
- 84 //对读操作计数
- 85 always @(posedge clk_50m or negedge rst_n) begin
- 86 if(!rst_n)
- 87 rd_cnt <= 28'd0;
- 88 else if(rd_en) begin
- 89 if(rd_cnt < data_max - 1'd1)
- 90 rd_cnt <= rd_cnt + 1'd1;
- 91 else
- 92 rd_cnt <= 28'd0;
- 93 end
- 94 end
- 95
- 96 //第一次读取的数据无效,后续读操作所读取的数据才有效
- 97 always @(posedge clk_50m or negedge rst_n) begin
- 98 if(!rst_n)
- 99 rd_valid <= 1'b0;
- 100 else if(rd_cnt >= data_max - 1'd1 ) //等待第一次读操作结束
- 101 rd_valid <= 1'b1; //后续读取的数据有效
- 102 else
- 103 rd_valid <= rd_valid;
- 104 end
- 105
- 106 //读数据有效时,若读取数据错误,给出标志信号
- 107 always @(posedge clk_50m or negedge rst_n) begin
- 108 if(!rst_n)
- 109 error_flag <= 1'b0;
- 110 else if(wr_en)
- 111 error_flag <= 1'b0;
- 112 else if(rd_valid && ((rd_data[15:0 != rd_cnt_d0[15:0])) )
- 113 error_flag <= 1'b1; //若读取的数据错误,将错误标志位拉高
- 114 else
- 115 error_flag <= error_flag;
- 116 end
- 117
- 118 endmodule
复制代码
ddr测试数据模块从起始地址开始,连续向5120个存储空间中写入数据0~5119。写完成后一直进行读操作,持续将该存储空间的数据读出。其中第45~50行代码对读计数器做了延时处理,使其与从ddr3中读出的数据对齐。 需要注意的的是程序中第116行通过变量rd_valid将第一次读出的5120个数据排除,并未参与读写测试。这是由于ddr3控制器为了保证读FIFO时刻有数据,在写数据尚未完成时,就将ddr3中的数据“预读”一部分(一次读长度)到读FIFO中,因此第一次从FIFO中读出的数据是无效的。读/写时序如下图所示: 从上面几个时序图中可以看出读写数据是一致的,因此信号error_flag一直处于低电平。 LED显示模块代码如下: - 1 module led_disp(
- 2 input clk_50m , //系统时钟
- 3 input rst_n , //系统复位
- 4
- 5 input ddr3_init_done , //ddr3初始化完成信号
- 6 input error_flag , //错误标志信号
- 7 output reg led_error , //读写错误led灯
- 8 output reg led_ddr_init_done //ddr3初始化完成led灯
- 9 );
- 10
- 11 //reg define
- 12 reg [24:0 led_cnt ; //控制LED闪烁周期的计数器
- 13 reg init_done_d0;
- 14 reg init_done_d1;
- 15
- 16 //*****************************************************
- 17 //** main code
- 18 //*****************************************************
- 19
- 20 //同步ddr3初始化完成信号
- 21 always @(posedge clk_50m or negedge rst_n) begin
- 22 if(!rst_n) begin
- 23 init_done_d0 <= 1'b0 ;
- 24 init_done_d1 <= 1'b0 ;
- 25 end
- 26 else if (ddr3_init_done) begin
- 27 init_done_d0 <= ddr3_init_done;
- 28 init_done_d1 <= init_done_d0;
- 29 end
- 30 else begin
- 31 init_done_d0 <= init_done_d0;
- 32 init_done_d1 <= init_done_d1;
- 33 end
- 34 end
- 35
- 36 //利用LED灯不同的显示状态指示DDR3初始化是否完成
- 37 always @(posedge clk_50m or negedge rst_n) begin
- 38 if(!rst_n)
- 39 led_ddr_init_done <= 1'd0;
- 40 else if(init_done_d1)
- 41 led_ddr_init_done <= 1'd1;
- 42 else
- 43 led_ddr_init_done <= led_ddr_init_done;
- 44 end
- 45
- 46 //计数器对50MHz时钟计数,计数周期为0.5s
- 47 always @(posedge clk_50m or negedge rst_n) begin
- 48 if(!rst_n)
- 49 led_cnt <= 25'd0;
- 50 else if(led_cnt < 25'd25000000)
- 51 led_cnt <= led_cnt +25'd1;
- 52 else
- 53 led_cnt <= 25'd0;
- 54 end
- 55
- 56 //利用LED灯不同的显示状态指示错误标志的高低
- 57 always @(posedge clk_50m or negedge rst_n) begin
- 58 if(rst_n == 1'b0)
- 59 led_error <= 1'b0;
- 60 else if(error_flag) begin
- 61 if(led_cnt == 25'd25000000)
- 62 led_error <= ~led_error; //错误标志为高时,LED灯每隔0.5s闪烁一次
- 63 else
- 64 led_error <= led_error;
- 65 end
- 66 else
- 67 led_error <= 1'b1; //错误标志为低时,LED灯常亮
- 68 end
- 69
- 70 endmodule
复制代码
LED显示模块用LED不同的显示状态指示ddr3初始完成情况(LED0常亮表示ddr3初始化完成)和ddr3读写测试的结果:若读写测试正确无误,则LED1常亮;若出现错误(读出的数据与写入的数据不一致),则LED1以0.5s为周期闪烁。 这里我们讲解一下ddr3例程如何进行Modelsim仿真,工程编译完成后ddr IP会自动生成一个sim文件夹,文件夹路径及内容如下所示: 这里我们要自己添加一个批量处理文件sim.bat,首先新建一个.bat文件(新建任意文本文档后将文件后缀改为.bat即可),这里我们将其命名为sim,然后用Notepad++软件打开,编写如下所示代码,代码很简单,只有一行,作用是执行ctrl_phy_sim.tcl文件。 - vsim -gui -do ctrl_phy_sim.tcl
复制代码
因为该sim.bat文件会执行所在路径的TCL文件,所以我们要将其和ctrl_phy_sim.tcl放在同一个路径下,即sim.bat要放在sim文件夹下,如下图所示。 接下来我们找到ddr_test_top_tb.v文件进行修改,ddr_test_top_tb.v文件是官方提供的ddr3仿真tb文件,是生成ddr3 IP核后自动生成的,但是它是用于仿真官方ddr例程的仿真文件,所以我们需要稍作修改,使其成为能够仿真我们自己的工程的仿真文件,该文件位置如下图所示: 图 38.4.23 ddr_test_top_tb.v文件所在位置 用Notepad++软件打开ddr_test_top_tb.v文件进行修改,修改如下图所示: 将原代码中例化ipsl_hmic_h_top_test的部分(即57~86行)注释或删除掉,如图 38.4.24所示。 若不需要设置结束时间,可以将源代码中第244~549行注释或删除掉,如图 38.4.25所示。 然后将本次实验的顶层代码例化进ddr_test_top_tb文件中,例化代码如下所示: - ddr3_rw_top u_ddr3_rw_top(
- .sys_clk (pll_refclk_in),
- .sys_rst_n (bu_top_rst_n),
- .led_error (),
- .led_ddr_init_done (),
- //DDR3接口
- .pad_loop_in (pad_loop_in ),
- .pad_loop_in_h (pad_loop_in_h ),
- .pad_rstn_ch0 (pad_rstn_ch0 ),
- .pad_ddr_clk_w (pad_ddr_clk_w ),
- .pad_ddr_clkn_w (pad_ddr_clkn_w ),
- .pad_csn_ch0 (pad_csn_ch0 ),
- .pad_addr_ch0 (pad_addr_ch0 ),
- .pad_dq_ch0 (pad_dq_ch0 ),
- .pad_dqs_ch0 (pad_dqs_ch0 ),
- .pad_dqsn_ch0 (pad_dqsn_ch0 ),
- .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
- .pad_cke_ch0 (pad_cke_ch0 ),
- .pad_odt_ch0 (pad_odt_ch0 ),
- .pad_rasn_ch0 (pad_rasn_ch0 ),
- .pad_casn_ch0 (pad_casn_ch0 ),
- .pad_wen_ch0 (pad_wen_ch0 ),
- .pad_ba_ch0 (pad_ba_ch0 ),
- .pad_loop_out (pad_loop_in ),
- .pad_loop_out_h (pad_loop_in_h )
- );
复制代码
接着我们找到sim_file_list.f文件,该文件是定位仿真中所需要添加的代码,即我们工程下的代码,其所在路径如下图所示。 图 38.4.26 sim_file_list.f文件所在位置 用Notepad++软件打开sim_file_list.f文件并在其中添加我们例程的.v文件,其余部分无需改动,保留即可,添加代码如下所示,添加位置没有要求,可以添加在代码的开头部分。 - ./../../../../rtl/ddr3_rw_top.v
- ./../../../../rtl/ddr3_top.v
- ./../../../../rtl/ddr_test.v
- ./../../../../rtl/led_disp.v
- ./../../../../rtl/rw_ctrl_128bit.v
- ./../../../../rtl/ddr3_fifo_ctrl.v
- ./../../rd_fifo/rd_fifo.v
- ./../../rd_fifo/rtl/ipml_fifo_ctrl_v1_3.v
- ./../../rd_fifo/rtl/ipml_fifo_v1_6_rd_fifo.v
- ./../../rd_fifo/rtl/ipml_sdpram_v1_6_rd_fifo.v
- ./../../wr_fifo/wr_fifo.v
- ./../../wr_fifo/rtl/ipml_fifo_ctrl_v1_3.v
- ./../../wr_fifo/rtl/ipml_fifo_v1_6_wr_fifo.v
- ./../../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仿真。 注:如若仿真时,进入Modelsim界面后出现闪退现象,可以找到ctrl_phy_sim.tcl文件,文件所在路径如下图所示: 图 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”具体修改如下图红框中所示。 图 38.4.29 ctrl_phy_sim.tcl文件修改部分图示 其中run 800us表示仿真时长,因为我们并没有添加仿真信号,所以该条语句无用,可以注释掉,保存后,再次双击sim.bat即可进入仿真界面。等待Modelsim完成编译之后添加好我们所需观察的信息进行仿真即可。 这里增加一个知识点,添加完仿真所需观察的信号后,我们可以将其另存为一个.do文件,这样在下次需要仿真时就可以省去查找和添加信号的时间。选择File后点击Save Format,操作如下所示。 存储路径保持默认即可(即工程仿真sim文件夹下),然后点击ok即可。 我们将.do文件添加到ctrl_phy_sim.tcl,并取消对run 800us的注释,这样等下次再对本次工程进行仿真时,只需要双击sim.bat即可出现仿真波形。.do文件的添加语句为do wave.do(其中wave为do文件名),如下图所示。 图 38.4.33 ctrl_phy_sim.tcl文件中添加执行.do文件的语句
1.5 下载验证首先将下载器一端连接电脑,另一端与开发板上的JTAG下载口相连,最后连接电源线并打开电源开关。接下来我们下载程序,验证DDR3读写测试实验的结果。 程序下载完成后,LED0在短暂延时之后,开始处于常亮的状态表明DDR3初始化完成,若LED1保持常亮说明读数据正确,DDR3读写测试实验验证成功,如下图所示: |