搞FPGA的人应该都有过这种经历:板子回来后,DDR4颗粒就在那儿焊着,功能逻辑也写得差不多了,结果卡在内存接口上——翻手册翻到头秃,MIG IP核配了一遍又一遍,不是综合报错就是上板后训练失败。我最早接触DDR4接口的时候,也踩过这个坑,对着UG586硬啃了好几天,最后发现其实把MIG的配置逻辑理顺之后,DDR4接口远没有想象中那么玄乎。这篇东西就是给你的,手把手把MIG IP核的配置流程、背后的原理、以及我踩过的那些坑全部讲清楚。
1. 先搞明白MIG IP核到底替你扛了多少活
很多刚接触DDR4的兄弟容易陷入一个误区:总觉得DDR4接口的难点是“怎么把数据线连对”。其实不是。真正把项目拖垮的,是那些看不见摸不着的时序参数和训练流程。
1.1 DDR4接口到底难在哪
DDR4 SDRAM是一个遵循JEDEC标准的同步动态随机存储器,它的读写操作完全靠一堆时序参数约束。tRCD(行地址到列地址延迟)、tRP(预充电时间)、tRFC(刷新周期)、tCCD(列到列延迟)——这些参数每个都像一个脾气古怪的合伙人,你得在恰好的时间点发出对应的命令,差一个时钟周期都不行。
更要命的是,DDR4的速率动不动就是2400MT/s、2666MT/s。在这个速率下,数据线和DQS选通信号之间的skew必须控制在皮秒级别,走线上的反射、串扰、电源噪声都会直接导致读写数据出错。
如果这些全部靠FPGA逻辑手写,那相当于你自己要去徒手实现一个符合JEDEC标准的完整内存控制器加物理层训练引擎。别说不现实,就算真给你三个月时间,写出来的东西大概率也跑不到2400MT/s。
1.2 MIG的出现就是为了把这件事“傻瓜化”
MIG(Memory Interface Generator)是Xilinx/AMD提供的一个IP核,它的定位非常明确:把DDR4接口的控制器(Controller)、物理层(PHY)和训练逻辑(Training Engine)全部封装好,对外提供一个相对简单的用户接口。
你不需要关心什么时候发激活命令、什么时候预充电、什么时候该刷新,也不需要手工调节读写延迟(Read/Write Leveling),这些全是MIG内部默默干掉的脏活。
用户拿到的是一个同步的读写接口,有简单的握手信号,像操作一块SRAM一样去读写DDR4就行。
所以,MIG IP核的意义不是“提供一个配置工具”,而是“帮你把DDR4最复杂的部分全部隔离掉”。理解了这一点,你才会明白为什么配置MIG的时候,很多选项的设置会直接影响后面的成败。
2. 动手配置之前,先把这五件事想清楚
打开Vivado开始点MIG之前,我强烈建议你先花半天时间把下面五件事搞清楚。这些信息全部来自你的原理图和PCB设计,如果哪一项没确认清楚就开配,大概率后面会返工。
2.1 记住你的FPGA具体型号和速度等级
这是最基础但也最容易被忽略的一步。MIG IP核会根据你的FPGA器件型号和速度等级,自动决定能支持的最大DDR4速率。
举例来说,同样是Kintex-7系列,速度等级-1和-2能跑到的DDR4频率是有差别的。如果你的板子上DDR4选的是2400MT/s,但FPGA速度等级不够,MIG虽然能生成IP,上板训练却可能一直失败。这一点在UltraScale+系列上尤其明显,不同速度等级支持的DDR4速率上限差别很大。
在Vivado里新建IP时,先把Part选成你板子上那颗FPGA的具体型号,再去看MIG支持的速率范围,别用通用型号糊弄过去。
2.2 DDR4被接到了哪个Bank,Bank电压是什么
DDR4的I/O电压是1.2V,对应Vivado里的SSTL12或POD12电平标准。你的原理图设计上,DDR4必须连接到支持1.2V IO的HP Bank(High Performance Bank),而且Bank的VCCO必须正好是1.2V。
这个信息直接决定了你在MIG配置里选哪个Bank、怎么设置I/O Standard。如果板子上DDR4接的Bank是1.5V供电,那你用什么配置都白搭,电气特性完全不对。
2.3 DDR4的数据位宽是几位
DDR4颗粒常见的有x8、x16,一颗或几颗拼成一个rank。整条DDR4接口的数据位宽可能是16位、32位、64位等。
位宽越大,MIG需要的FPGA引脚越多,内部数据总线宽度也越大。在MIG里选择正确数据位宽是必须的,同时要注意DQS/DM等信号的组对关系。
有一个经验:位宽越大,训练流程越容易受PCB走线等长误差影响,调试难度也越高。如果是自己画的板子,首版建议把位宽控制在32位以内,不要一上来就挑战64位。
2.4 参考时钟从哪里来,频率是多少
DDR4接口需要一对差分参考时钟(REF_CLK),这个时钟通常由板上的晶振或FPGA内部时钟管理单元产生。MIG配置时会问你这个参考时钟的频率,常见的是100MHz、125MHz或200MHz。
你要确认实际送到MIG参考时钟引脚的频率到底是多少,不要想当然。一个很常见的坑是:原理图上标的“REF_CLK_200M”,实际给的是100MHz,配置的时候也填了100MHz,但PCB走线或时钟芯片配置有问题,导致上板后参考时钟频率和配置不一致,训练就一直卡在第一步。
2.5 PCB上的Address/Control走线延迟是否做了等长处理
这个虽然不是MIG配置项,但直接决定了你的DDR4接口能不能跑在标称速率。DDR4对address/command信号的建立保持时间要求很严格,PCB上需要保证DQS/DQ组内走线等长,以及Address/Control信号相对CK的等长关系。
如果你的板卡DDR4走线比较随意,那么别急着怪MIG配置不对,先量一量走线延迟。这也是为什么我建议在MIG里把“System Clock”和“Reference Clock”相关的选项都先设成最保守的值,等板子验证没问题了再往高速率拉。
3. 逐页走完MIG配置向导:每个选项背后的逻辑
这部分是整个指南的核心。我会按照Vivado里MIG IP核的配置向导顺序,一页一页地过,把每个选项为什么要这么选说清楚。以Vivado 2022.2里UltraScale+ MIG为例,7 Series的MIG(UG586)选项略有不同,但思路完全一致。
3.1 第一页:控制器数量与共享逻辑
打开MIG IP核配置界面,第一页会让你选择“Number of Controllers”和“AXI4 Support”等。
关于控制器数量,除非你确实需要同时驱动多颗独立DDR4颗粒,否则选1就行。每个控制器会有自己独立的一组用户接口信号,会占用大量FPGA逻辑资源。
关于“AXI4 Support”这个选项,建议刚开始做DDR4项目时先不勾选。为什么?因为AXI4接口虽然标准化,但它的突发协议、outstanding传输、地址通道和数据通道分离这些机制,会给调试增加一层复杂度。MIG默认的Native接口(也叫User Interface)已经足够简单直接,适合先打通链路再用AXI。
在“Shared Logic”这个选项上,7 Series和UltraScale的做法不同。7系列会问你“Shared Logic in IP”还是“Shared Logic in Example Design”,这里的含义是:MIG内部的一些公共逻辑(PLL/MMCM、时钟树、校准逻辑)是放在IP核内部还是放在例化文件中。
经验之谈:在7系列上选“Shared Logic in Example Design”可以让你在仿真时更容易修改时钟方案,但实际集成到工程里时容易漏接信号。UltraScale+上MIG核相对自动化一些,不用过度纠结这个选项,按默认即可。
3.2 第二页:内存类型与速率等级
这一页选择Memory Type为DDR4 SDRAM,并设置DDR4的速率。
这里的关键是“Memory Speed Grade”和“FPGA Speed Grade”要匹配。比如DDR4-2400的颗粒,MIG要求FPGA至少是-1速度等级(根据不同系别的定义),如果你的FPGA只能支持DDR4-1866,MIG会把最大可选速率限制住。
还有个细节是“Input Clock Period”选项,它会根据你选的速率自动计算用户接口时钟(UI Clock)的值。以DDR4-2400为例,UI时钟是300MHz(总速率的1/8),DDR4-2666对应333MHz。
如果你想降低用户逻辑的工作频率、降低时序收敛难度,也可以在MIG配置里选择2:1模式或4:1模式(即用户接口时钟更低、数据总线更宽)。代价是FPGA内部布线资源和信号延迟会增加。我的建议:首版项目用默认的4:1模式,把用户时钟压到最低,后面跑通了再优化。
3.3 第三页:位宽、Bank和引脚分配方式
这一页选择数据位宽和FPGA Bank。你需要根据原理图把DDR4连接的Bank填进去,并告诉MIG你用的是哪个Bank。
这里有几个关键点:
第一,“Pin/Bank Selection”如果选“Fixed Pin Out”,MIG会基于你选的Bank自动分配引脚,但你需要手工校验它分配的引脚和你的原理图是否一致。如果选“Mixed Pin Out”,可以手动改每个引脚,但操作极其繁琐,且容易出错。
第二,如果你的DDR4接口只占用Bank的一部分引脚,另外一部分引脚有其他用途,这时候MIG的自动分配可能会冲突。需要提前在“N/A Pins”或“Unused Pins”里标明哪些引脚不用。
第三,不同Bank对DDR4支持的DQ/DQS数量和排列有物理限制,这取决于FPGA的die结构。比如某些Bank只能支持到36 bit的DQ组,你硬分配64 bit的DQ到同一个Bank是不行的。MIG会在配置界面里直接报错。
3.4 第四页:DDR4选项——On-Die Termination与内部电压
这一页开始涉及DDR4颗粒端的配置。Rtt_Nom(片上终端电阻标称值)、Rtt_WR(写终端电阻)、Driver Impedance(驱动阻抗)这些参数,必须和你原理图上DDR4芯片的配置引脚电平匹配。
如果你的原理图设计里Rtt_Nom选的是RZQ/6(约40欧姆),MIG里也要选对应值;如果原理图上DDR4芯片的VREFCA电压设置和MIG填的不一致,也会导致信号质量下降,甚至训练失败。
对于首版调试,如果不太确定该怎么选,建议用MIG默认值,同时对照DDR4芯片的数据手册里的推荐配置检查一遍。这些参数不影响链路功能,但影响信号完整性,出问题时优先排查。
3.5 第五页:系统时钟、参考时钟与高级选项
系统时钟(System Clock)是FPGA逻辑里给MIG控制器的时钟;参考时钟(Reference Clock)是DDR4物理层的采样时钟。两者都从板级输入进来,但用途不同。
在“System Clock”设置里,如果选择“Differential”且由外部晶振直接提供,需要保证时钟信号质量;如果你是从FPGA内部PLL分出200MHz给MIG,也可以选“Single-Ended”并指定来源引脚。
“Advanced Clocking”选项推荐使用默认的“Use Custom”或自动模式。有些老版本教程会建议你手工指定PLL或MMCM,但新版本MIG的时钟管理单元已经足够智能,手工改动反而容易引入额外的不确定性。
还要注意“Memory Voltage”选项,DDR4必须选1.2V,如果选成1.35V则按DDR3L的电气标准配置,结果可想而知。
3.6 最后一页:例化与输出选项
配置向导最后会询问是否生成Example Design,强烈建议一定要勾选生成Example Design,并且不要关闭仿真模型选项。
Example Design里面包含了一个完整的、可综合可仿真的DDR4读写测试模块,还有MIG IP核的例化模板、引脚约束XDC文件。很多人在这一步省略了Example Design,结果后面全靠手写,效率低且容易出错。
生成IP后,Vivado会自带一份针对该DDR4配置的参考XDC,这份XDC里已经写好了所有MIG相关引脚的LOC约束和时序约束。千万不要自作聪明去删里面的任何一行,后面板级调试验证通过之前,这些约束一个都不能少。
4. 生成IP之后那些容易翻车的地方:引脚约束与时钟复位
配置向导走完,IP也生成了,很多新手会觉得万事大吉,但真正的考验才刚开始。MIG IP核只是个“毛坯房”,你要做的集成工作有一堆,而且有几个位置只要出错,板子必然点不亮。
4.1 引脚约束XDC:看起来很长,实际一字都不能多改
MIG生成的XDC里,除了引脚位置约束(LOC),还包括了延迟约束(Set Input Delay/Output Delay)、时钟约束(Create Clock)、以及一组非常重要的相位约束(Set Clock Groups)。
这些约束是MIG在内部训练逻辑跑通后才生成的,它们的目标是保证FPGA内部时序收敛到物理层训练可接受的范围内。
有次我图省事,觉得DDR4的走线仿真结果很好,就把XDC里某个Input Delay改小了一点,结果上板后训练偶发失败,调了一整天才发现是约束被改了。所以请记住:MIG生成的XDC不要动,至少在你的PCB改版之前不要动。
4.2 c0_sys_clk和c0_ui_clk的区别与连接
MIG对外输出几个时钟信号,其中最重要的两个是sys_clk(也叫sys_rst相关时钟)和ui_clk(用户接口时钟)。ui_clk就是你要拿去做读写FIFO握手用的时钟,所有用户逻辑时序都要以它为基准。
在UltraScale+ MIG里,ui_clk的频率直接由DDR4速率和总线位宽决定:DDR4-2400、数据位宽64位时,ui_clk是300MHz;如果数据位宽是16位,ui_clk会是1200MHz或600MHz,具体取决于MIG是否启用了2:1模式。
这个频率决定了你的用户逻辑必须跑到多快。如果用户逻辑时序收敛不了,优先考虑改MIG配置里的位宽和时钟模式,把ui_clk压下来,而不是去优化用户逻辑。
4.3 init_calib_complete不是可选项,是必等信号
MIG定义了一个名为init_calib_complete的输出信号。它拉高之前,DDR4物理层训练可能还在进行中,此时任何用户读写命令都不应该发出。
这个信号的重要性再怎么强调都不为过。我在好几个项目里都遇到过,因为用户逻辑没等init_calib_complete就发命令,导致校验和复位状态下的读写错误。
具体设计里,你应该把init_calib_complete接到用户逻辑的状态机里,作为从“复位态”到“运行态”的切换条件。如果它一直不拉高,说明MIG训练失败,需要去查硬件。
4.4 复位时序:sys_rst不是随便拉一下就行
MIG的系统复位输入是一个异步复位信号,低电平有效。它对释放时机有要求:必须在系统时钟稳定之后释放,且至少要维持有效状态一段足够长的时间。
遇到复位相关的坑不少,常见的有两种:一是外部复位电路释放太早,在时钟还没稳定时就把MIG放出来,结果是训练进程随机卡死;二是复位信号毛刺太多,导致MIG内部状态机锁死在未知状态。
稳妥做法是把FPGA的全局复位经过一个同步器和延迟链,确保复位释放时时钟已经稳定,再用一个“延迟20ms后用系统时钟同步的复位”给到MIG。如果你不想自己写,也可以直接用MIG Example Design里的复位逻辑,那是经过验证的。
5. 用户接口时序:怎么把数据写进去再读出来
MIG对外的用户接口,在7系列叫Design-Wise Native Interface,在UltraScale+上依然保持类似协议,整体上就是一个简单的读写握手协议。你要是能把这个握手捋顺,DDR4读写基本就通了一半。
5.1 写通道:命令和数据的握手不是同一拍
观察MIG用户接口的信号列表你会发现,写命令和写数据是分开的:app_cmd[2:0]携带具体命令(000代表写,001代表读),app_addr携带地址,app_en是命令有效信号,app_rdy是MIG返回的“就绪”信号。而数据侧有app_wdf_data、app_wdf_wren、app_wdf_rdy。
这里的关键点是:写命令和写数据可以不在同一拍有效,MIG内部有缓冲。你用app_en拉高并等待app_rdy拉高来表示一个命令被接受;数据侧的握手是app_wdf_wren拉高并等待app_wdf_rdy。
不规范的做法是:很多人想当然地认为“命令和数据同时有效就行”,结果在app_rdy等于0的情况下强发命令,丢失数据而不自知。
正确做法是:用状态机检查两个握手信号都满足再推进地址。对于写操作,推荐命令和数据的握手分开处理,不要合并成一个组合逻辑判断。
5.2 读通道:延迟是可变的,不能按固定周期取数
读操作的响应时间和读命令发起的时间之间有一个不确定间隔,MIG内部命中了哪一行、是否发生预充电、刷新是否穿插进来都会影响这个延迟。
所以你绝对不能用“发读命令后固定等N拍然后取数据”的思路。应该用app_rd_data_valid信号来捕获数据有效的时机。
具体的读状态机设计一般是:发读命令(同写握手),然后进入等待态,直到app_rd_data_valid拉高的那一拍把数据写入FIFO。注意app_rd_data_valid和app_rd_data是同步的,直接采样即可。
5.3 地址映射:不要奢望能“直接对应”DDR4物理地址
DDR4内部有Bank、Row、Column三级结构,MIG的用户接口地址是把这三者打包成一个线性地址。线性地址到物理地址的映射关系在MIG配置里是可选的(ROW/COLUMN/BANK的顺序可以调整)。
这一点对性能影响很大。如果你的读写模式需要频繁切换Bank,导致行激活和预充电开销变大,那性能会大打折扣。这时候可以通过调整MIG配置里的地址映射顺序或让用户逻辑按顺序突发访问来优化。
对于功能调试阶段,记住一个原则:只要地址不越界,DDR4就能正常工作,不用太纠结性能。先把链路跑通,再考虑通过调整突发长度和映射提升效率。
6. 上板之前先仿真,上板之后用ILA排障
很多人喜欢直接综合下载,出了问题再一头扎进Vivado里找原因。这个习惯在DDR4项目上特别吃亏。因为DDR4接口一旦上板出错,错误现象千奇百怪,可能是数据错位、可能训练失败、可能是偶发死机,光靠猜效率极低。
6.1 用Example Design跑一次仿真,验证配置本身没问题
MIG生成的Example Design自带一个DDR4仿真模型(根据你在IP里选的内存颗粒型号生成),可以直接用Vivado自带的仿真器跑。
第一次跑仿真时,重点观察三个东西:
- init_calib_complete是否能拉高
- 写数据、读回数据是否一致
- 例化设计里的比较器打印的结果是否为PASS
如果Example Design仿真通过,说明你的MIG配置、时钟方案、地址映射都是自洽的。这给后续上板调试树立了一个“配置没问题”的基准线。
如果Example Design仿真都报错,优先回头检查配置,尤其是时钟频率和内存型号,别急着改代码。
6.2 首板上电:第一步看init_calib_complete
板卡首次上电,不要直接跑完整业务逻辑。最小化验证步骤:
第一步,把Example Design的顶层直接烧进去,用ILA抓c0_init_calib_complete信号。如果这个信号在复位释放后几十毫秒内拉高,恭喜你,物理层训练通过了。
第二步,用ILA同时抓app_rdy、app_wdf_rdy、用户逻辑的写命令和数据,观察app_rdy在发命令前是否一直为高。如果app_rdy频繁拉低,说明MIG内部FIFO快满了,用户逻辑发数据太快,需要检查写数据的速率控制。
6.3 从训练失败的错误码反推硬件问题
如果init_calib_complete一直不拉高,MIG内部会有一个状态寄存器记录当前卡在哪个训练步骤。在7系列上由iopi_cal_clk等相关信号状态体现,有些版本的MIG会输出cal_debug_vector。
常见卡住阶段和原因:
- 卡在“Write Leveling”:DQS和CK的相位调不齐,大概率是PCB上DQS和CK走线长度差太多。
- 卡在“Read Calibration”:DDR4返回数据无法被正确采样,检查VREF设置、DQ/DQS等长和PCB信号质量。
- 卡在“Gateway阶段”:数据通路的bit错误太多,说明有布线顺序或bank分配错误。
以上这些错误码的具体定义可以查对应系列的Memory Interface Solution用户指南附录,但我给你的排查方向是通用的,因为训练逻辑的核心原理在7系列和UltraScale系列上一脉相承。
7. 跑一个能通的读写测试状态机:从零到整的代码思路
有了前面的基础,最后我们来写一个最简单的DDR4读写测试状态机。这个状态机的目的是把你刚才通过仿真和ILA验证的MIG用户接口“用起来”,为后续业务逻辑打底。
7.1 测试状态机的设计目标
这个测试状态机完成的事情很简单:等init_calib_complete拉高后,向DDR4的前128个地址写入0~127递增数据,然后回读并比较,最后把比较结果和错误地址通过ILA输出。
“简单但能跑通”是当前唯一目标,不追求性能,不搞复杂FIFO。
7.2 核心代码骨架
以下代码用Verilog描述,适用于UltraScale+ MIG,7系列需要把时钟和复位信号名做对应调整:
module ddr4_simple_test #( parameter DATA_WIDTH = 64, parameter ADDR_WIDTH = 28 )( input wire ui_clk, input wire ui_rst_n, input wire init_calib_complete, // 命令通道 output wire [2:0] app_cmd, output wire [ADDR_WIDTH-1:0] app_addr, output wire app_en, input wire app_rdy, // 写数据通道 output wire [DATA_WIDTH-1:0] app_wdf_data, output wire app_wdf_wren, output wire [DATA_WIDTH/8-1:0] app_wdf_mask, input wire app_wdf_rdy, // 读数据通道 input wire [DATA_WIDTH-1:0] app_rd_data, input wire app_rd_data_valid, input wire app_rd_data_end, // 测试结果 output reg test_pass, output reg test_error, output reg [ADDR_WIDTH-1:0] error_addr ); localparam IDLE = 3'd0; localparam WRITE_CMD = 3'd1; localparam WRITE_WAIT = 3'd2; localparam READ_CMD = 3'd3; localparam READ_WAIT = 3'd4; localparam CHECK = 3'd5; localparam PASS = 3'd6; localparam FAIL = 3'd7; reg [2:0] state; reg [ADDR_WIDTH-1:0] test_addr; reg [DATA_WIDTH-1:0] wdata; reg [DATA_WIDTH-1:0] rdata; reg [7:0] byte_cnt; assign app_cmd = (state == WRITE_CMD) ? 3'b000 : 3'b001; // 000=WRITE,001=READ assign app_addr = test_addr; assign app_en = (state == WRITE_CMD || state == READ_CMD) && app_rdy; assign app_wdf_data = wdata; assign app_wdf_mask = 8'd0; // 全字节有效 assign app_wdf_wren = (state == WRITE_CMD) && app_rdy; always @(posedge ui_clk or negedge ui_rst_n) begin if (!ui_rst_n) begin state <= IDLE; test_addr <= 0; wdata <= 0; test_pass <= 0; test_error <= 0; end else if (!init_calib_complete) begin state <= IDLE; test_addr <= 0; test_pass <= 0; test_error <= 0; end else begin case (state) IDLE: begin test_addr <= 0; wdata <= 0; test_pass <= 0; test_error <= 0; state <= WRITE_CMD; end WRITE_CMD: begin // 写数据与写命令同时握手成功,则进入下一地址 if (app_rdy && app_wdf_rdy) begin if (test_addr == {ADDR_WIDTH{1'b1}}) state <= READ_CMD; else begin test_addr <= test_addr + 1'b1; wdata <= wdata + 1'b1; end end end READ_CMD: begin if (app_rdy) begin test_addr <= 0; state <= READ_WAIT; end end READ_WAIT: begin if (app_rd_data_valid) begin rdata <= app_rd_data; state <= CHECK; end end CHECK: begin if (rdata == {wdata}) begin // 实际此处应使用期望数据,简化处理:比较 wdata 累加值 test_addr <= test_addr + 1'b1; wdata <= wdata + 1'b1; if (test_addr >= 8'd127) state <= PASS; else state <= READ_CMD; end else begin error_addr <= test_addr; test_error <= 1; state <= FAIL; end end PASS: begin test_pass <= 1; end FAIL: begin test_error <= 1; end endcase end end endmodule这里我故意简化了读回数据的期望值比较逻辑。实际工程中建议把写入的数据序列按地址存一个镜像,比如用BRAM保存期望值,回读时逐字节比对,这样错误检测覆盖率更高。
7.3 为什么这个状态机不建议直接用于性能验证
上面这个状态机是典型的“时序正确但效率低下”的写法:一次只写一个地址,每次写完等握手,每次读一个地址。它的价值在于功能验证,不在于性能评估。
如果你要测带宽、测最长突发、测跨Bank切换性能,建议改用“先写一整段连续地址,再顺序读回”的模式,并且把MIG的burst length设置和用户接口的连续传输利用起来。这是另一个话题,但在架构上你已经有测试基础了。
8. 说说我自己踩过的那些真实教训
写到这里,真心觉得DDR4+MIG这条路,最大的障碍不是资料少,而是资料太散。官方手册虽然是权威,但信息密度不高,容易让人晕。我最后想聊几个自己在项目中踩过的坑,算是给后来人避避雷。
第一个教训是:千万别在DDR4速率上拍脑袋。我有个项目,为了跑分好看把MIG配置到2666MT/s,结果板卡走线等长只做到了±5mil,上板后Read Calibration一直失败。后来老老实实降到2400MT/s,一次就过了。跑分再好看,不稳定等于零。
第二个教训是:MIG生成的Vivado工程和你的业务工程最好是分开。调试DDR4时用Example Design工程,验证通过后再把MIG IP核导入到业务工程里。这样做的好处是,DDR4接口的问题不会和你业务逻辑的问题混在一起。
第三个教训是:复位信号真的值得多花心思。我有一块板子,DDR4偶尔上电后训练失败,后来发现是外部复位芯片的复位释放时间和DDR4电源轨的稳定时间有竞争关系。给复位加了一个定时器延迟后才彻底解决。
最后一个建议:多花一点时间学习MIG例化代码的层次结构。Vivado生成的example design里那个mig_7series_0_mig.v或ddr4_mig顶层文件,包含了完整的时钟管理、复位同步和物理层例化。把它的层次结构读明白,以后遇到任何DDR4相关的问题,你至少知道该去哪个文件里翻代码。
DDR4接口这东西,说难也难,说简单也简单。难的是DDR4本身的协议复杂性,简单的是有了MIG这个“封装好的黑盒”后,普通开发者只需要把配置逻辑理清,把握手时序搞明白,就能顺畅地在这个黑盒外面开发自己的业务逻辑。希望这篇配置指南能帮你把这条路走得顺一点。