1. 从一次带宽瓶颈排查说起DDR性能的核心矛盾前阵子帮一个朋友看他们的一块Zynq 7020板子现象很典型PL端逻辑跑得挺欢但一到批量搬数据就掉速AXI读写DDR的实测带宽只有理论值的三成左右。他一开始怀疑是DDR PHY配置有问题又怀疑是地址线等长没做好折腾了好几天。我让他先把Vivado里的DDR仿真跑一遍把IBS模型下的时序波形拉出来看结果发现根因根本不在硬件而是他访问DDR的方式太碎——每次只读几个字节Burst长度设得很短Prefetch能力完全没被利用起来。这件事让我意识到很多人做DDR相关开发注意力都放在能不能跑起来上却忽略了DDR真正的性能命脉在于**Burst突发传输和Prefetch预取**这两个机制。你板子画得再漂亮、等长控制得再精准如果访问模式跟DDR的内部工作方式对不上带宽就是上不去。这篇内容我就围绕这两个核心机制把DDR从能跑到跑得快之间的那层窗户纸捅破顺带把DDR协议、IBS模型、AXI读写DDR、IDD测试这些周边知识点串起来讲清楚。不管你是刚接触DDR的新手还是已经在调Zynq、调FPGA DDR仿真的老手应该都能从里面找到对自己有用的东西。2. DDR为什么要Burst从一次传输的代价说起2.1 一次内存访问到底发生了什么要理解Burst的价值得先搞清楚DDR颗粒内部一次访问的完整流程。DDR的存储阵列是一个二维的电容矩阵行Row和列Column交叉定位一个存储单元。当你发出一个读命令时DDR内部大致要经历这么几步先激活对应的行ACT命令把整行数据搬到感应放大器里然后发出列地址CAS从感应放大器里选出目标数据最后通过数据总线送出去。这一套流程里ACT和CAS之间、CAS和数据输出之间都有固定的延迟也就是大家熟悉的tRCD、tCL这些时序参数。关键点在于激活一行的开销是固定的但一行里能读出的数据量是可变的。如果你每次只读一个字节那激活一行的代价就全摊在这一个字节上效率低得可怕。反过来如果你一次把这一行里连续的一大段数据都读出来激活的固定开销就被摊薄了单位数据的成本大幅下降。这就是Burst机制存在的根本理由——用一次命令开销换取多笔数据传输。2.2 Burst Length是怎么定义的DDR协议里Burst LengthBL指的是一次读或写命令连续传输的数据个数。以DDR3/DDR4为例常见的BL有BL8和BL4BC4Burst Chop 4。到了DDR5BL进一步扩展到BL16。这个数字不是随便定的它跟DDR的Prefetch架构强绑定。拿DDR3来说它的Prefetch是8n也就是内部一次预取8个数据单元每个单元位宽等于颗粒位宽。外部接口是双沿传输DDRDouble Data Rate所以一个时钟周期传2笔数据。8个数据单元除以每周期2笔正好需要4个时钟周期来完成一次完整的Burst传输。这就是为什么DDR3的BL8对应4个时钟周期。如果你用BC4那就是只传一半另外一半被砍掉了效率自然打折。这里有个容易混淆的点BL是数据个数不是时钟周期数。很多人看波形的时候会把BL8理解成8个周期结果算带宽的时候差了一倍。记住DDR是双沿的BL8 8笔数据 4个时钟周期。2.3 Burst对带宽的实际影响我们来算一笔账。假设DDR3-1600数据位宽16bit时钟频率800MHz因为DDR是双沿等效1600MT/s。理论峰值带宽 800MHz × 2 × 16bit 25600Mbit/s 3.2GB/s。但这是理想情况前提是数据总线一直在传数据中间没有空隙。实际中如果Burst之间有空隙带宽就会掉。假设每次BL8传输后要等2个周期才能发下一个命令那有效带宽就变成 8/(42) × 3.2GB/s ≈ 2.13GB/s直接掉了三分之一。如果BL更短、命令间隔更大掉得更狠。所以Burst的核心价值就一句话让数据总线尽可能处于忙的状态减少命令开销和总线空闲带来的浪费。这也是为什么在AXI读写DDR的场景里AXI的burst长度设置会直接影响最终带宽——AXI的burst最终会被内存控制器转换成DDR的Burst两者要匹配好。提示AXI的burst lengthAxLEN和DDR的Burst Length不是一回事。AXI的burst是总线层面的突发DDR的Burst是颗粒层面的。内存控制器负责在两者之间做转换和调度但如果AXI侧请求太碎控制器再怎么调度也救不回来。3. PrefetchDDR性能跃迁的真正引擎3.1 Prefetch的本质是提前把数据准备好如果说Burst解决的是一次命令传多少数据的问题那Prefetch解决的就是数据从哪来、怎么提前准备好的问题。Prefetch的字面意思是预取它的核心思想是在外部接口还没要数据之前内部就已经把数据从存储阵列里取出来放到一个更靠近接口的缓冲里。为什么需要这么做因为存储阵列的访问速度跟不上外部接口的速度。DDR的外部接口是双沿传输速度很快但内部阵列的访问相对慢。如果每次外部要数据都现去阵列里取接口就得等带宽就上不去。Prefetch相当于在阵列和接口之间加了一个蓄水池阵列按自己的节奏往池子里灌水接口按自己的节奏从池子里取水两边解耦接口就能持续满速输出。3.2 从DDR到DDR5Prefetch是怎么演进的Prefetch的位宽n是划分DDR世代的重要标志我们来看这张演进表世代Prefetch外部数据率内部阵列频率关系典型BLDDR2n双沿阵列频率 接口时钟/1BL2DDR24n双沿阵列频率 接口时钟/2BL4DDR38n双沿阵列频率 接口时钟/4BL8DDR48n双沿阵列频率 接口时钟/4BL8DDR516n双沿阵列频率 接口时钟/8BL16这张表信息量很大。以DDR3为例Prefetch 8n意味着内部一次从阵列取8个数据单元。外部接口双沿传输一个时钟周期传2笔所以8个数据单元需要4个时钟周期传完。换句话说内部阵列的工作频率只有外部接口时钟的1/4。这就是Prefetch的魔法让慢速的阵列通过批量预取来匹配快速的外部接口。到了DDR5Prefetch翻倍到16n内部阵列频率进一步降到接口时钟的1/8。这意味着DDR5的阵列可以做得更慢更省电但通过更大的预取批量外部依然能跑到很高的数据率。这就是为什么DDR5能在功耗和带宽之间取得更好的平衡。3.3 Prefetch和Burst的配合关系Prefetch和Burst是一对搭档不是一回事。Prefetch是内部行为决定了一次从阵列取多少数据到缓冲Burst是外部行为决定了一次命令在总线上传多少数据。两者通过BL这个参数对齐。理想情况下BL应该等于Prefetch位宽除以2因为双沿。DDR3 Prefetch 8nBL8正好对齐一次预取的数据刚好一次Burst传完不多不少。如果你把BL设成4BC4那预取来的8个数据只用了一半另一半浪费了效率直接砍半。这就是为什么在很多对带宽敏感的场景里我们强烈建议用BL8而不是BC4。注意BC4不是没用它在某些只需要读4个数据的场景下能减少不必要的传输降低延迟。但如果你追求的是吞吐量BL8几乎总是更优选择。4. 内存控制器如何调度Burst与Prefetch4.1 内存控制器的角色定位内存控制器Memory Controller是CPU/FPGA和DDR颗粒之间的翻译官加调度员。上游来的请求可能是AXI burst、可能是CPU的cache line填充、可能是DMA的搬运请求格式五花八门下游的DDR颗粒只认ACT、RD、WR、PRE这些命令。内存控制器要做的就是把这些上游请求翻译成DDR能听懂的命令序列并且尽可能高效地排列这些命令。这个高效排列就是调度。调度的目标很明确最大化数据总线的利用率同时保证公平性和实时性。听起来简单做起来极难因为DDR的时序约束非常复杂tRCD、tRP、tRAS、tRRD、tFAW一大堆参数互相牵制稍不注意就会违反时序或者效率暴跌。4.2 地址映射对Burst效率的影响一个经常被忽视但极其关键的点是地址映射Address Mapping。上游请求的物理地址怎么映射到DDR的Bank、Row、Column直接决定了Burst能不能连续、Row能不能复用。举个直观的例子。假设你的数据在物理内存里是连续存放的但地址映射把连续的地址打散到了不同的Bank甚至不同的Row那每次访问都要重新ACT一行Burst再长也救不了。反过来如果映射做得好连续访问落在同一行里一次ACT就能服务很多次Burst效率极高。在FPGA和Zynq的场景里地址映射通常由内存控制器IP配置决定。Xilinx的MIGMemory Interface Generator就提供了地址映射的配置选项。我的经验是如果你的访问模式是顺序大块读写优先选择能让连续地址落在同一Row的映射方式如果访问模式是随机的那就要在Bank交错上做文章用多Bank并行来掩盖单Bank的延迟。4.3 读写切换和刷新带来的开销即使Burst和Prefetch都配置得当还有两个隐形杀手会吃掉带宽读写切换Read-Write Turnaround和刷新Refresh。DDR的数据总线是双向的读和写不能同时进行。从读切换到写或者从写切换到读都需要一定的总线转向时间tWTR、tRTW。如果读写频繁交替这个转向开销会累积得很可观。所以内存控制器通常会做读写分组把一批读请求攒在一起发再把一批写请求攒在一起发减少切换次数。刷新就更不用说了DDR的电容会漏电必须定期刷新通常每64ms刷8192次。刷新期间不能访问这是硬性开销。DDR3的刷新开销大约占总时间的3%-5%DDR4/DDR5通过Fine Granularity Refresh等机制有所优化但依然存在。你在算实际可达带宽的时候一定要把这部分扣掉。5. 实操在FPGA/Zynq上验证Burst与Prefetch的效果5.1 用Vivado的DDR仿真看波形如果你手上有Xilinx的板子验证DDR行为最直接的方式就是跑Vivado自带的DDR仿真。MIG IP在生成的时候会附带一个example design里面包含了仿真testbench和DDR的IBS模型。IBSIntelligent Bus Simulator模型是Micron等厂商提供的行为级模型能比较真实地反映DDR颗粒的时序行为。跑仿真的步骤大致是这样生成MIG IP时勾选Example Design然后在Vivado里打开生成的工程直接跑Behavioral Simulation。仿真波形里你能看到DDR的命令总线CS、RAS、CAS、WE、地址总线、以及DQ数据总线。重点观察几个东西命令之间的间隔是否符合时序、Burst传输是否连续、数据总线上有没有大段空闲。我自己的习惯是先在testbench里构造一个顺序读的场景看看连续Burst的波形长什么样再构造一个随机读的场景对比一下带宽差异。这个对比做一次你对Burst和调度的理解会深刻很多。5.2 AXI读写DDR的带宽测试在Zynq平台上PL端访问DDR通常走AXI接口。要测实际带宽可以用Xilinx的AXI Traffic Generator或者自己写一个简单的AXI Master做批量读写。这里有个关键配置AXI的burst length。AXI3最大支持16拍burstAXI4扩展到256拍。如果你用的是AXI3单次burst最多16拍每拍如果传8字节64bit位宽一次burst就是128字节。这个粒度对DDR来说还算可以但如果你的请求间隔大效率还是会掉。AXI4的256拍burst能一次传2KB对DDR友好得多。实测下来在Zynq 7020上用AXI4、64bit位宽、256拍burst做顺序读DDR带宽能跑到理论值的70%-80%如果换成16拍burst可能只有50%左右。这个差距就是Burst长度带来的。5.3 地址线等长与DDR性能的关系热词里有个ad18 ddr地址线等长设置这其实是硬件层面的问题。地址线等长是为了保证地址信号同时到达各个DDR颗粒避免建立/保持时间违例。如果等长没做好DDR可能跑不到目标频率或者在高频下出现误码。但要注意等长解决的是能不能跑的问题不是跑得快不快的问题。等长做好了DDR能稳定工作在目标频率但带宽能不能上去取决于Burst、Prefetch、调度这些逻辑层面的东西。很多人把这两个问题混为一谈硬件调了半天结果发现是软件访问模式的问题。等长的经验值地址线和控制线通常要求误差在±50mil以内时钟线要求更严差分对内部误差要控制在±5mil。具体数值要看你的频率和板材频率越高要求越严。DDR3-1600一般±50mil够用DDR4-3200可能就要±25mil甚至更严。6. 常见问题与排查技巧实录6.1 DDR带宽上不去的排查思路带宽不达标是最常见的问题排查要按层次来别一上来就怀疑硬件。我整理了一个排查顺序表排查层次检查项常见问题解决方向应用层访问模式请求太碎、随机性太强改成顺序大块访问总线层AXI burst长度burst太短增大AxLEN控制器层地址映射连续地址被打散调整映射配置控制器层调度策略读写频繁切换调整调度优先级物理层时序参数tRCD/tCL配置过保守按颗粒手册优化物理层信号完整性等长/阻抗不达标检查PCB和端接这个顺序的逻辑是从软到硬从易到难。软件层面的问题改起来快先排除掉硬件层面的问题改起来慢放后面。我见过太多人一上来就怀疑硬件结果折腾一周发现是AXI burst设短了。6.2 DDR IDD测试是干什么的热词里的ddr idd测试指的是DDR的电流测试。IDD是JEDEC定义的一系列电流参数比如IDD0是激活-预充电电流IDD2N是待机电流IDD4R是读操作电流IDD4W是写操作电流。这些参数主要用来评估DDR的功耗特性。对普通开发者来说IDD测试的直接价值不大但间接价值在于通过IDD4R和IDD4W的对比能看出读和写的功耗差异。通常写的功耗比读高因为写要驱动DQ和DQS。如果你在做低功耗设计这个数据能帮你判断读写比例对整体功耗的影响。6.3 DRAM、DDR、PSRAM到底有什么区别这三个词经常被混用其实不是一回事。DRAM是动态随机存取存储器的总称DDR是DRAM的一种双沿传输的DRAMPSRAM是伪静态RAM内部是DRAM但接口像SRAM。区别的核心在于接口复杂度和成本。DDR接口复杂需要内存控制器、需要严格的时序和等长但带宽高、容量大、单位成本低。PSRAM接口简单像SRAM一样直接读写不需要复杂的控制器但带宽低、容量小、单位成本高。所以PSRAM常用在低端嵌入式设备上DDR用在需要高性能的场景。集成DDR的ARM SoC现在很普遍比如各种应用处理器。这类SoC把DDR控制器集成在片内外部只需要接DDR颗粒简化了设计。但集成不等于简单DDR的时序和信号完整性要求一点没降低。6.4 Zynq 7020用JTAG固化Flash时必须用DDR吗这个问题挺具体答案是不一定但通常会用。JTAG固化Flash的过程是通过JTAG把boot image写到QSPI Flash里。这个过程本身不需要DDR因为数据是从JTAG进来的直接写到Flash就行。但实际工程里boot image可能很大包含FSBL、bitstream、u-boot、Linux内核等如果不用DDR做缓冲直接往Flash写会很慢而且有些工具链默认会用DDR做中转。所以必须用DDR这个说法不准确更准确的说法是用DDR能加速固化过程。如果你的板子DDR还没调通理论上可以不用DDR固化但会比较慢而且要看具体工具的支持情况。6.5 如何查看DDR版本查看DDR版本有几个途径。最直接的是看颗粒上的丝印Micron、三星、海力士的颗粒都会印型号型号里包含了世代信息比如MT41K开头的是DDR3L。如果颗粒被散热片盖住了可以查板子的原理图或BOM。再不行可以在系统里读SPD信息如果板子有SPD EEPROMSPD里记录了内存的详细参数。在软件层面Linux下可以用dmidecodex86平台或者读设备树里的memory节点。FPGA平台上DDR的配置信息通常在MIG的配置文件中翻一下就能看到。7. 几个容易踩的坑和我的实操心得先说一个我踩过的坑过度追求理论带宽。有一次我花了很多时间优化DDR访问把带宽从理论值的60%提到了75%结果发现整个系统的瓶颈根本不在DDR而在上游的数据处理逻辑。DDR带宽提上去了系统性能没变。所以优化之前一定要先定位真正的瓶颈别对着一个不是瓶颈的地方使劲。第二个坑是忽略刷新开销。我在算带宽预算的时候一开始没扣刷新算出来能到80%实测只有72%。后来把刷新开销约4%和读写切换开销扣掉预算和实测就对上了。做带宽规划的时候一定要留出这些隐形开销的余量。第三个心得是善用IBS模型做前期验证。在板子还没回来之前用IBS模型跑仿真能提前发现很多问题比如时序配置错误、Burst长度不匹配等。等板子回来了再调成本高得多。Vivado的DDR仿真虽然慢但值得跑。最后一个技巧用AXI Performance Monitor做在线监测。Zynq的PS端有AXI Performance Monitor能实时统计AXI接口的读写带宽、事务数、延迟等。调优的时候开着它改一个参数看一眼数据比盲调高效得多。这个工具很多人不知道但真的很好用。关于DDR的后续扩展如果你已经把Burst和Prefetch调明白了下一步可以研究一下DDR的Training机制。DDR3以上都需要做Write Leveling、Read Gate Training、Read Leveling这些训练训练做得好不好直接影响高频下的稳定性。这块内容展开又是一大篇有机会再单独聊。