Vivado DDR4读写调试经验分享
实验目的
调用Vivado DDR4 SDRAM (MIG) IP核,基于官方example design,通过操作User Interface (UI)端接口,实现简单的DDR4读写。
实验设计
采用状态机控制,分为6个状态,顺次切换:
| IDLE | 空/初始状态 |
|---|---|
| WR0 | 向DDR4的20个地址(间隔8)写入20个数(0,8,...,152) |
| RD0 | 读出前10个数(0,8,...,72) |
| RD1 | 读出后10个数(80,...,152),在此状态,同时完成前10个数和后10个数的两两相加 |
| WR1 | 写回10个加法结果 |
| RD2 | 读出10个加法结果,检验正确性 |
时序说明
1. DDR写通道
DDR写通道分“写命令通道”和“写数据通道”,两者相互分离。笔者调试过程中很长时间未注意到该点,吃了很大的亏。
“写命令通道”主要有app_en, app_rdy, app_addr三个信号。当app_en & app_rdy为高时,命令被接收 (此时的app_addr成为有效的写地址)。
“写数据通道”主要有app_wdf_wren, app_wdf_data, app_wdf_rdy, app_wdf_mask, app_wdf_end五个信号。当app_wdf_wren & app_wdf_rdy为高时,数据被接收(此时app_wdf_data被写进Write Data FIFO (wdf)中,随后由DDR控制写入DDR中)。
典型的写命令时序可参见UG586第167页的这张图。

典型的写数据时序可参见UG586第168页的这张图。

上图很好地说明了“写命令通道”和“写数据通道”相互的时序要求。虚线框中1,2,3三种情况都是被准许的。Xilinx官方对此解释如下:

即:写数据可以在命令前、命令时、命令后放置,且命令后至多延迟两个周期就要放上数据。但官方配图容易给人造成误解,即:数据不得超前命令超过1个周期。调试过程中发现,数据实际可以超前命令很多个周期的!这一点需要注意。(如下图,在WR0状态中,第20个app_en & app_rdy在4355.486ns出现,但第20个app_wdf_wren & app_wdf_rdy在4325.482ns就出现了,后续测试表明,数据依然成功写入DDR)

2. DDR读通道
DDR读通道同样分"读命令通道"和"读数据通道"。
“读命令通道”主要有app_en, app_rdy, app_addr三个信号(与"写命令通道"的区分由app_cmd完成,app_cmd = 3'b000时为写,app_cmd = 3'b001时为读)。当app_en & app_rdy为高时,命令被接收 (此时的app_addr成为有效的读地址)。
“读数据通道”主要有app_rd_data, app_rd_data_valid两个信号。当app_rd_data_valid拉高时,相应周期的app_rd_data为有效的读数据。
典型的DDR读时序参见UG586第171页的这张图:

3. 主要控制信号的产生
DDR读写时序已知,那么我们如何coding实现该时序?
假设读通道的命令计数信号为rd_cmd_cnt,数据计数信号为rd_cnt;写通道的命令计数信号为wr_cnt,数据计数信号为wr_data_cnt。关键问题1:这些计数信号当何时自增?
例程表明,可以先通过app_rdy打一拍得到cmd_en(近似),app_wdf_rdy打一拍得到wr_en(近似);而后,以app_en = cmd_en & app_rdy控制命令通道的rd_cmd_cnt和wr_cnt自增,以app_wdf_wren = wr_en & app_wdf_rdy (近似)控制数据通道的wr_data_cnt自增,以app_rd_data_valid控制数据通道的rd_cnt自增。实践表明,此种控制方式十分稳定可靠,但对于其深层原理,笔者尚无说法。
关键问题2:何时进行状态间的切换?主要矛盾在于WR0 --> RD0 和 WR1 --> RD2这两处。由于写通道分命令通道和数据通道,谁最末完成,谁就应承担状态切换的任务。仿真发现,例程及本实验中,写数据通道总是不晚于写命令通道完成。由此采用写命令通道作为状态切换的标志通道。
关键问题3:app_wdf_end信号如何处置?该信号涉及到MC (Memory Controller)和UI (User Interface)时钟的差异。DDR端的MC Clock:用户端的UI Clock = 4:1,即用户端的时钟更慢。DDR端的MC Clock在上升沿和下降沿都能传输一个数据。所以对于8 bit的memory (ddr_dq[7:0]),每个UI Clock能传输8*2*4 = 64 bits,刚好能传完一个app_wdf_data[63:0],所以app_wdf_end拉高的周期与app_wdf_wren一致。如PHY RATIO = 2:1 (即MC Clock: UI Clock = 2:1),那么每个app_addr就要对应两个周期的数据,每个周期32 bits,此时app_wdf_end就要在每第二个周期拉高。
关键问题4:app_wdf_wren的及时拉低。由于写命令通道和写数据通道的分离,编写代码时应尤其注意app_wdf_wren信号的及时拉低。否则,即使在app_cmd反复切换数次之后,仍可能通过后续app_en的拉高写入大量垃圾数据,影响调试。(典型垃圾数据为0或原有效数据的继续自增)
例化&测试
例化需要明确模块间的相互关系。笔者编写的example_tb_x.sv模块用于产生app_前缀的信号。IP Catalog中调用的ddr4_0 IP核是DDR控制器,用于接收app_前缀的用户端信号,产生ddr_前缀的DDR端信号。ddr4_model.sv是软件模拟的DDR仿真模型,用于与上述ddr_前缀的DDR端信号交互。由此可得连接关系。值得注意的是,为了启动DDR仿真模型,需要在顶层tb文件中加入以下initial块,并产生时钟。
initial begin
sys_rst = 1'b0;
#200
sys_rst = 1'b1;
en_model = 1'b0;
#5 en_model = 1'b1;
#200;
sys_rst = 1'b0;
#100;
end
测试结果如下,可见在RD2状态成功读出10个加法结果且结果正确。

相关代码详见https://github.com/GeorgeLin200100/DDR4-Naive-WR-RD/tree/main
如有任何错漏,欢迎指正。
参考
- Zynq-7000 SoC and 7 Series Devices Memory Interface Solutions v4.2 User Guide (UG586)

浙公网安备 33010602011771号