ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FPGA实战:DDR3 IP核配置全解析与调试经验

FPGA实战:DDR3 IP核配置全解析与调试经验 DDR3 IP核配置这件事做FPGA的人迟早都要碰上一次。不管是图像采集、高速数据缓冲还是SoC缓存DDR3几乎是中高性能系统绕不开的一环。我最早接触DDR3是在7系列的Artix-7平台上当时对着MIGMemory Interface GeneratorIP核一脸懵配置界面里密密麻麻的选项每一个都认识合在一起不知道该怎么选。折腾了几天把官方文档翻了个底朝天又对照着芯片手册一个个查才算是真正理顺了这整套流程。这篇文章我就把DDR3 IP核配置的完整思路、关键选项和实际调试中容易踩的坑整理出来给正在调DDR3的朋友做个参考。我以Xilinx Vivado环境下的MIG 7 Series为主来写其他厂商的IP核比如Intel的EMIF、安路/紫光的DDR控制器逻辑上大同小异核心概念和技术要点是通用的。1. DDR3为什么非得用IP核先搞懂硬件底层的麻烦很多人第一次接触DDR3 IP核内心都会有个疑问不就是读写个内存嘛自己写个状态机控制不就行了等你真去翻DDR3的芯片手册就会发现事情远没有想象中那么简单。1.1 DDR3不是你想的那样简单读写DDR3内部是一个三维结构Bank存储体、Row行、Column列。一次完整的读操作要先激活ACT对应Bank的某一行然后等tRCD时间过去再发送读命令RD数据经过CLCAS Latency列地址选通延迟个时钟周期后才会出现在DQ总线上。写操作更麻烦还需要考虑写均衡Write Leveling、DQS数据选通信号的相位对齐、片上终端ODT的导通时序等等。这还只是单一操作更别提还有刷新Refresh、预充电Precharge、模式寄存器配置MR0/MR1/MR2/MR3这些周期性任务。刷新是DDR3最要命的一个特性。DDR3的电容器存储电荷会随时间泄漏所以必须每隔一段时间刷新一次典型值是在85℃以内每64ms刷新全部8192行。如果你自己用状态机做控制器刷新逻辑一旦和读写请求冲突要么丢数据要么卡住主流程调试起来极其痛苦。这里面还有个更隐蔽的难点DDR3的时序参数非常多而且不是固定值。CL、tRCD、tRP、tRAS、tRC、tRFC、tFAW、tRRD……这些参数在芯片手册里有表格在不同频率、不同温度条件下取值不一样还分normal和extended范围。软件模型算起来尚且要小心手写状态机基本就是给自己挖坑。1.2 MIG IP核帮你干了哪些脏活累活MIG IP核说白了就是一个完整的DDR3内存控制器它内部包含了三个主要部分初始化模块上电后按照JEDEC标准时序依次完成复位、时钟训练、模式寄存器写入、ZQ校准、写均衡和读训练最终拉高init_calib_complete信号表示DDR3已经可以正常使用了。命令调度器接收用户接口发来的读/写请求按照Bank状态、Bank冲突、刷新时间窗口等因素进行重新排序和调度把用户请求翻译成DDR3芯片需要的ACT/RD/WR/PRE等底层命令序列。物理层PHY负责真正的DDR3高速接口包括DQ/DQS的双向IO、写入时的数据对齐、读取时的数据选通和眼图校准、地址命令信号的输出时序调整、ODT和驱动强度控制。用户接口层就是IP核对外呈现的那一组信号我们主要跟它打交道。MIG的Native接口看起来不算复杂一组命令地址通道一组写数据通道一组读数据通道加上几个握手信号。但如果不理解内部机制光这组接口的时序契约就够喝一壶的。提示MIG有AXI4接口和Native接口两种可选。如果只是简单做数据缓存Native接口更直白少一层AXI协议转换资源也更省。如果要做SoC或者将来想接CPU总线选AXI4接口更合适。理解了DDR3硬件的麻烦你就明白IP核的价值所在了。接下来的重点就是怎么把MIG配置对以及配置完之后怎么让它稳定跑起来。2. MIG IP核配置面板里的关键选项逐项拆解Vivado里创建MIG IP核后会弹出一大堆配置页面。看着吓人其实每个页面的核心决策点就那几个。我按实际配置顺序把关键选项过一遍顺便说清楚每个选项背后的逻辑。2.1 基本信息页不用纠结先认清两个概念第一页一般就是给IP核起名字、选版本、设定兼容模式什么的。这个页面没什么大坑但有两个概念务必先搞清楚控制器Controller和物理层PHY的比例关系。一个MIG IP核里可以配置一个或多个控制器。常见的配置是1个控制器带1个PHY这种情况下所有内存接口引脚都归这个控制器管理。也可以配置多个控制器共用一个PHY这叫做多端口内存控制器Multi-Port Memory Controller适用于多个主设备共享一块DDR3的场景。组件Component与DIMM的区别。如果你用的是板子上直接贴的DDR3颗粒选Component模式。如果你用的是内存条DIMM选SODIMM或UDIMM模式。实际工程里90%以上都是Component模式DIMM用得少而且DIMM时序更复杂IP核需要额外处理卡槽带来的走线延迟。选错了会导致时序训练始终无法收敛初始化卡死在某一步。2.2 内存类型与频率选项的背后逻辑进入Memory Selection页面后要选择具体的DDR3型号和频率。MIG里集成了一大堆常见DDR3型号的时序库选型号的好处是IP核会自动套用对应的时序参数不需要自己手填。但这里有个坑Vivado版本不同内置的型号列表也不同有时候板子上用的型号列表里没有怎么办我的经验是选一个同厂商、同类密度、同接口位宽、速度等级不低于实际芯片的替代型号。比如实际用的是MT41K256M16HA-125镁光8Gb x16列表里没有就找同厂商同一系列的其他x16型号。IP核关心的是电气特性和时序参数只要这些都是兼容的基本能正常工作。选好型号后记得手动核对一下tCK、CL这些关键参数与实际芯片手册是否一致不一致就改Frequency和CAS Latency。频率选项要注意区分三个概念Memory Clock PeriodDDR3颗粒的工作时钟周期。比如输入1.25ns对应的DDR3数据速率就是1600MT/sDDR上升沿和下降沿都传数据乘2。Input Clock PeriodFPGA侧给MIG的参考时钟周期。内部PLL/MMCM靠它产生几百MHz的高频时钟。REF ClockMIG内部PHY校准用的参考时钟通常选200MHz。如果这三者之间的关系没搞清楚配置界面里的warning就会提示一堆时序违例。我实测下来最省心的组合是DDR3跑1600MT/s内部时钟800MHz参考时钟用200MHzFPGA输入时钟根据板载晶振选常见的是50MHz、100MHz、125MHz。2.3 控制器选项里最容易被忽略的那几项Controller Options页面里有一堆下拉菜单大部分保持默认就行但下面这几个必须逐项确认Burst Length突发长度。用户接口每次读写最小粒度为8个连续的16-bit或32-bit数据。DDR3的突发长度固定为8BL8这个不用改。如果DDR3颗粒支持BC4突发长度4MIG也可以配置为4但实际带宽利用率会降低一般不建议。Output Driver Impedance输出驱动阻抗和On-Die TerminationODT阻抗。这两个值是DDR3的物理层配置要参考具体的PCB走线阻抗来选常见的是RZQ/6约40Ω和RZQ/7约34Ω。如果板子是按标准DDR3布线规则做的用默认值就行。如果初始化一直失败且怀疑信号完整性问题可以试着调整这两个参数比改PCB快得多。Data位宽。选8/16/32/64位。位宽越大同样的时钟频率下带宽越高但占用的FPGA IO引脚也越多。对图像处理这类大数据量应用至少选16位起步。选32位通常是最稳妥的带宽/引脚折中方案。有一个参数极度容易被忽略叫Write Pipeline Stages。这是用户写入数据到控制器内部FIFO的流水线级数取值范围2-8。系统时钟频率较高时如果这个值设得太小会导致写数据跟不上命令通道出现app_wdf_rdy长期拉低的情况。我做200MHz以上系统时钟时习惯直接设成4或6可以省去后面很多时序上的麻烦。还是回到那句话理解不了每个参数都没关系但你知道这个参数是干什么的、影响什么——这两个问题必须心中有数。3. 从example design起步跑通官方模板再动手改MIG IP核配置完成Generate Output Products之后接下来最关键的一步不是马上写自己的逻辑而是先打开Example Design把官方模板跑通。这步能帮你确认硬件环境、引脚约束、时钟复位链路都没问题。3.1 生成和添加example design在Vivado的Sources窗口里右键点击已经生成的MIG IP核选择Open IP Example Design。Vivado会创建一个新的工程这个工程里包含了MIG IP核的例化代码一个简单的UI测试逻辑ui_read_write_test模块会周期性地往DDR3里写数据读回来对比一段仿真测试程序sim_tb_top.v或.svPHY相关的原语例化比如IDELAYCTRL、IODELAY等打开工程后先跑仿真确认IP核在仿真环境里能完成初始化和读写测试再去跑综合实现。仿真通过了说明逻辑层面的配置没问题。如果仿真都过不了先解决仿真问题再上板。3.2 引脚约束的两种路径固定方案与自定义方案Example Design里自带了一套引脚约束文件XDC但这是针对Xilinx官方开发板的。不同板卡的DDR3引脚位置千差万别所以拿到一个实际板卡时引脚约束通常有两种处理方式自动生成Pin Out如果在配置MIG时勾选了“固定引脚”相关的选项并且板卡的DDR3引脚在FPGA封装里是固定的DDR3硬件工程师布线时引脚位置是确定的MIG可以自动生成匹配的XDC约束。这种方式最可靠因为DDR3引脚在FPGA芯片内部的位置是固定的你只需要确认对应的Bank电压正确即可。手动约束从板卡的原理图里查DDR3的每一根信号连到FPGA的哪个引脚手工编辑XDC。这种方式极其繁琐一个32位的DDR3接口地址、命令、数据、DQS加起来有上百根线手写容易出错。我强烈推荐第一种方式。如果板卡是自己画的高速电路板原理图设计时DDR3引脚已经有成熟的参考设计直接用参考设计里的XDC就好别自己重新分配。DDR3引脚一旦动了位置时序完全不可控大概率跑不起来。3.3 引脚分配那些麻烦事Bank电压与EMC约束配置引脚XDC时有三个细节值得专门提醒第一确认Bank电压。DDR3的IO电压标准是1.5VDDR3L是1.35VFPGA的HR Bank必须接到对应的VCCO。如果Bank电压搞错轻则无法通信重则烧毁引脚。XDC里通常会有set_property INTERNAL_VREF和set_property DCI之类的约束这些是和MIG配置匹配的复制过来用即可。第二DQS引脚必须是差分对。DQS、DQS#在FPGA封装里是专用的差分管脚对必须保证XDC里定义的引脚是这个Bank的合法差分位置。把DQS分到普通IO上是常见新手错误实现阶段就会报错。第三数据线要带Data Group约束。MIG生成的XDC里有set_property DCI_CASCADE和data group相关的约束保证一个字节通道8位数据1对DQS内部的走线延时可控。不要随便拆散它们否则上板后眼图质量会急剧恶化。Example Design实现完成、生成比特流后可以直接下载到板子里跑起来。运行正常的话板载LED会显示测试通过不同模板逻辑不同观察error信号即可。这一步成功了对DDR3 IP核的配置才算真正有了信心。4. 上板之前时钟复位与初始化状态的逐个确认Example Design跑通了接下来就是把MIG集成到自己的逻辑里。这时候最需要注意的是时钟、复位和初始化时序的关系。很多人在这一步把IP核的例化代码拷贝过去却发现上板后读出来的数据乱七八糟——大概率是时序问题。4.1 时钟域与复位逻辑MIG IP核对外输出几个时钟信号ui_clk用户接口主时钟所有Native接口的信号app_*都以它为时钟同步。ui_clk_sync_rst用户接口的同步复位信号高电平有效。mb_ddr4_clk0和mb_ddr4_clk1不同版本名字可能不同给用户逻辑用的其他时钟输出。务必记住一点你的读写逻辑必须在ui_clk时钟域里跑且必须用ui_clk_sync_rst做复位不能用自己随便生成的复位信号。因为DDR3控制器内部的状态机和FIFO都是在ui_clk时钟域下工作的用户逻辑必须跟它严格同频同相。有些同学图省事直接把系统复位信号接到用户接口上结果初始化永远过不去。ui_clk_sync_rst在初始化完成后会拉低之后你的逻辑才能开始发命令。正确做法是把init_calib_complete和ui_clk_sync_rst这两个信号同时纳入复位逻辑。4.2 用ILA抓初始化信号上板调试时我习惯首先抓这几个信号init_calib_complete只有拉高DDR3才算真正就绪。app_rdy控制器是否准备好接收命令。app_wdf_rdy控制器是否准备好接收写数据。app_rd_data_valid读数据是否有效。ui_clk_sync_rst复位是否已经释放。如果init_calib_complete一直不拉高常见原因有三个参考时钟没给对。MIG的sys_clk_i引脚必须接入有效的时钟输入频率和配置页面里写的Input Clock Period一致。这个信号本身可以用ILA在内部探测MIG里有时序校准的逻辑会锁存参考时钟的好/坏状态。复位信号时间太短。DDR3的初始化复位需要保持一定时间MIG IP核的sys_rst输入信号需要外部给一段足够的低电平脉冲至少要几百微秒的稳定低电平。DDR3颗粒的供电时序不对。如果板子上的DDR3还没上稳电就开始了初始化训练必然失败。上电顺序要参考颗粒厂商的datasheet先VDD后VDDQ这些是硬件问题软件救不回来。如果初始化一直卡死把ILA的触发条件设为init_calib_complete0且app_rdy1看看信号停在哪一步。MIG内部的状态机信号在example design里有时会引出来没有的话就去改一下IP核设置勾选相关调试接口。4.3 用户接口读写时的时序契约初始化完成后用户接口的读写操作有严格的时序契约这里面的门道不少。写操作的完整流程是拉高app_en同时把app_cmd0表示写1表示读和app_addr地址放到总线上。同时或稍晚但需满足一定窗口把写数据app_wdf_data、写使能app_wdf_wren和app_wdf_end准备好。握手靠app_rdy和app_wdf_rdy只有当两个ready都拉高时数据才算真正被接收。这里有个特别需要注意的点命令通道的握手和数据通道的握手是分开的。命令和写数据不是必须在同一个周期提交MIG内部有FIFO缓冲它们但两者之间的时间差有一个上限对应内部FIFO的深度。如果只发命令不写数据FIFO迟早溢出控制器就会卡死。读操作相对简单发一个读命令等若干个周期要满足读延迟app_rd_data_valid拉高时app_rd_data总线上的数据才是有效的。注意读数据不会连续每次都有效中间可能隔几个周期。我建议每个从零写DDR3读写逻辑的朋友先把MIG的ui_read_write_test模板吃透那段非常好用的参考代码把写平衡、读比较、握手等待都写清楚了。不要一上来就自己写复杂的DMA控制器先跑通简单读写再逐步增加复杂度。5. 实测中踩过的坑从数据错乱到带宽瓶颈配置和基本读写做通了DDR3的挑战其实才刚开始。这一节聊聊我实际调试过程中遇到的几类经典问题都是例化完MIG后最容易踩的雷。5.1 跨时钟域导致的偶发错位跨时钟域问题大概是DDR3调试中最难定位的一类故障。症状很典型功能偶尔出错复现概率不高重启后又恢复正常。这就是典型的跨时钟域信号未同步导致的亚稳态问题。MIG Native接口的所有信号都在ui_clk时钟域里。如果你的用户逻辑是在另一个时钟域比如图像处理里的pixel_clk下产生地址和数据必须先把这些信号同步到ui_clk时钟域再送入MIG接口。正确做法是在用户逻辑和MIG之间加一级FIFO。写入方向用用户时钟写FIFO、ui_clk读FIFO读取方向反过来。甚至可以用MIG自带的例化模板里那对app_*信号配合一个简单的异步FIFO IP核实现。不要去手动打两拍同步地址总线——地址不只是一个bit同步多位信号必然会出现中间态直接导致访问错地址。我当初就是在图像采集模块和MIG之间省了一个FIFO结果图像每隔几帧就会出现一条横向条纹排查了一整天才想到是跨时钟域问题。那以后凡是DDR3接口我必加FIFO宁可多耗点BRAM绝不再赌亚稳态。5.2 地址映射和DDR3存储结构的关系DDR3的地址不是简单的线性排列。MIG把用户地址app_addr按照配置页面里指定的Address Mapping方式映射到DDR3内部的Bank、Row、Column。默认映射方式通常选BANK_ROW_COLUMN也就是说地址的低位对应Column中间位对应Row高位对应Bank。这个映射方式直接影响读写效率。连续访问时尽量让地址落在同一个Bank的同一个Row里避免频繁的Bank切换和Row切换。如果访问模式是随机小粒度跳跃那映射方式怎么选都差别不大。但如果是顺序大块读写比如图像帧缓冲以行为单位进行突发的效率远高于逐列跳跃。还有一个容易忽略的因素用户地址要按突发长度对齐。在BL8模式下一个突发访问写入8个连续数据字。如果用户的起始地址没有按8对齐控制器内部会自动拼接数据带来额外的性能损失。比如每次只写1个32-bit数据但地址是随机的实际控制器每次都要做一个8字突发的一部分浪费了大半带宽且逻辑复杂度极高。设计DDR3访问方案时最好定义好最小传输粒度。比如图像的一行正好是DDR3某一行的一个整数倍就整行写入让突发长度最大化。实测下来同样带宽的DDR3顺序突发访问比随机跳跃访问的效率能差3-5倍。5.3 性能上不去的时候先查什么DDR3理论带宽很好算数据速率 × 位宽 ÷ 8。比如1600MT/s × 16bit ÷ 8 3.2GB/s。但实际能达到的带宽通常远低于理论值。影响实际带宽最大的几个因素按影响程度排序刷新开销DDR3每64ms要刷新8192行刷新期间控制器暂停正常访问。占比不大约2-3%频率越高占比越小。Bank/Row切换这是最大的性能杀手。访问模式频繁跨越Row边界控制器被迫插入PRE和ACT命令每次切换要损失tRPtRCD约20-30ns。读写切换DDR3的DQ总线是半双工的读转写或写转读之间需要插入总线周转周期tWTR、tRTW频繁切换读写方向会极大拉低效率。命令与数据FIFO的深度MIG内部FIFO如果满了app_rdy或app_wdf_rdy拉低用户逻辑就需要等待。如果你的DDR3实测带宽只有理论值的30%左右不要急着怀疑IP核配置先看一下访问模式。我踩过的一个典型例子是CPU侧发了大量小粒度读请求每次只读16字节间隔又短结果DDR3几乎把所有时间都花在了Row切换上。后来在CPU和DDR3之间加了一层缓存把常用的Row驻留在一个小SRAM里带宽瞬间翻倍。关于写数据通道还有一个细节app_wdf_wren必须和app_wdf_end配合使用。每个突发写命令需要正好对应8个数据字的写入。如果app_wdf_wren持续拉高的周期数不是8的倍数控制器行为就会异常。有些版本里app_wdf_end表示最后一个有效数据字必须精确对齐这个信号写错的话数据会错位得很隐蔽。写在最后的几点体会DDR3 IP核配置这事说难也难说简单也简单。难在概念多、时序多、还跟硬件强相关简单在于官方的Example Design其实已经把90%的坑帮你填好了。我自己的经验是不要急着写自己的逻辑先把Example Design在板子上跑起来观察LED和ILA信号确认初始化通过再一步步替换成自己的读写控制逻辑。另外调试DDR3时一定要有耐心这个问题不是写代码不是编译过了就能跑。物理层的信号完整性、PCB布线、电源质量任何一个环节有问题表现出来都是init_calib_complete不拉高或者数据偶发错误。这时候不要反复改软件先回头查硬件。最后分享一个小技巧如果板上DDR3初始化不通过试着把频率降一档。比如配置1600MT/s失败改成1333MT/s或1066MT/s再试。如果降频后能正常初始化那说明问题多半出在PCB信号完整性上而不是IP核配置错。这个降频测试法基本能排除掉80%的配置层面问题剩下再针对性地调ODT、驱动强度或检查硬件设计排查路径会清晰很多。
返回列表