
DDR带宽需求建模这个系列写到第四篇了。前三篇我们先把协议基础、bank结构与地址映射、读写效率损耗这些底层的坑都趟了一遍今天这篇回到整机视角解决一个每天都在发生的实际问题在一个真实的SoC或者FPGA原型系统里用户IO——也就是显示、网络、PCIe这些前台业务流量——和后台任务——比如刷新、ECC、内存搬运、日志这类看不见的隐性消耗——同时去抢DDR带宽这笔账到底怎么算带宽究竟怎么分才不让系统翻车。我见过太多次这样的场景功能仿真全部通过一到原型板或者芯片回来显示器出图开始撕裂万兆网偶发丢包PCIe吞吐死活上不去。查到最后十有八九不是逻辑功能错了而是DDR带宽分配出了问题。要么把理论带宽直接当成可用带宽要么忽略了刷新这类固定开销要么就是用户IO的峰值瞬间叠加把整个总线打穿。这篇文章就把分布式带宽的账从头到尾算一遍。1. 先把DDR带宽的总盘子算清楚1.1 理论带宽只是天花板不是可用带宽很多人一提DDR带宽第一反应就是拿总线位宽乘频率。以DDR4-2400、64bit数据位宽为例计算公式很简单理论带宽 数据速率(MT/s) × 数据位宽(Byte) 2400 MT/s × 8 Byte 19.2 GB/s如果把内存颗粒换成DDR4-3200那就是25.6GB/s。这套计算在PPT阶段没问题但它只是理论天花板工程上能稳定拿到的带宽往往只有它的60%到85%。剩下的部分去哪了被协议时序吃掉了。DDR的每一次读写操作并不是数据总线一直在传数据。命令与数据总线的切换需要时间读写方向转换需要惩罚周期连续访问不同bank时还要等预充电和行激活。我习惯把实际带宽损失拆成两块第一块是固定开销比如刷新、读写turnaround、tWR写恢复这些协议强制的等待第二块是随机性开销比如bank冲突、页面miss、地址映射没做好导致的频繁预充电。后者跟你的应用访问模式强相关前者是DDR规范里写死的谁都逃不掉。1.2 需求方分两类前台用户IO后台隐形任务把带宽蛋糕分成两块来建模是我自己的习惯做法也是本系列从这篇开始的核心思路。第一类是用户IO。这类流量特点非常明显有明确的实时性要求带宽需求可以算得很精确但burst特性差异很大。显示控制器每帧读取数据必须按时出图否则画面撕裂网络MAC收到包以后必须及时写到DDR否则FIFO溢出丢包PCIe DMA搬运的数据如果不能及时从DDR读走上游链路就会反压。用户IO是前台业务它的延迟劣化会直接体现在用户可感知的指标上。第二类是后台任务。这类流量平时不起眼但占的带宽一点都不少。最主要的包括DDR Refresh刷新、ECC错误检查、硬件加速器加解密、压缩的内部分组搬移、日志DMA、CPU或者NPU的cache line回写、内存碎片整理等。它们大部分没有硬实时要求但问题是它们会周期性插入打断前台业务的连续访问如果完全不管随时可能成为压垮带宽的最后一根稻草。1.3 为什么不能把两边简单相加做需求预算的时候最忌讳的事情就是把所有业务方的平均带宽直接加起来然后跟DDR可用带宽比一下发现才用了50%很安全就宣布带宽充足。这个结论在真实系统里基本是错的。原因有两个。第一带宽需求不是平均值的堆积而是峰值窗口的叠加。用户IO大多是突发的比如显示控制器的行消隐期间可能不读数据但一旦开始读就是一大片网络流量的突发更是无规律。如果两个burst在同一个时钟窗口撞在一起瞬时需求可能远超平均值。第二后台任务的插入是带伤害的。刷新命令发出后整块DDR在几百纳秒内都不能访问这段时间数据总线是空的但它占用的时间被拉长了。你算带宽的时候不能只算刷新命令占了多少数据周期还要算刷新前后调度器被打断后重新切换读写方向的那部分额外损耗。所以更合理的建模方式是把每个需求的有效带宽和峰值带宽分开记录再考虑它们是否能在时间上错开。这也是后面几节要重点做的事。2. 用户IO怎么建模三种典型流量一次说清2.1 显示/视频类周期均匀但行内突发不能忽视显示控制器是DDR带宽建模里最友好的一个客户因为它的访问模式几乎是可以精确预测的。以4K60、RGBA8888格式为例像素量 3840 × 2160 8,294,400 像素 单帧大小 8,294,400 × 4 Byte 33,177,600 Byte ≈ 31.6 MiB 每秒帧率 60 → 平均读取带宽 ≈ 1.99 GB/s如果是1080p60同样的算法下来只有约0.5GB/s。看起来都不高对吧但显示控制器有个特点它是按行读取的。一行数据会一次性突发读取过来中间还要插入行消隐和帧消隐。如果多路显示叠加比如四图层混合、UI和视频分别取两个DDR master那么两个master可能在同一行时间内同时发出很大的读请求。这时候虽然平均带宽不高但几微秒窗口内的瞬时需求可能是平均值的两三倍。还有一类被忽视的是显示写入路径。比如GPU渲染完成后把frame buffer写回DDR或者视频编码器把编码前的原始帧写入这些都是写操作。如果整个显示链路有读写交叉就会引入DDR的读转写惩罚额外的带宽浪费在总线切换上。建模显示链路时我一般会额外加10%到15%的效率损失系数专给这个读写切换买单。2.2 网络类小包是带宽杀手描述符也是带宽以太网流量的建模比视频棘手得多因为它的访问粒度和突发性完全取决于包的大小和到达模式。10GbE线速单向流量就是1.25GB/s只要是一个方向跑满DDR侧就必须持续提供这个节奏的写入能力而网络接收路径上的包是小包这个结论就完全变了。举一个很典型的场景64字节小包打到线速。以太网最小帧是64字节加上前导码和帧间隙10GbE理论包率大约是14.88Mpps百万包每秒。每个小包到达后DMA从DDR读一个描述符然后把包的payload写入DDR处理完以后再写回一个描述符。算下来每个包DDR侧的访问次数至少是三次有效数据只有64字节但描述符读32字节、描述符写32字节、可能还有buffer header的读写。也就是说DDR实际搬运的数据可能是payload的好几倍。所以小包场景下网络模块的DDR带宽需求不是payload带宽乘以一个系数而是直接按包率×每次收发包的内存访问量来估算。这也是为什么很多网卡芯片和SoC在宣称10GbE线速的同时一定要强调自己有足够大的描述符cache和高效的DMA聚合机制。如果你在做架构评估遇到小包线速场景切记把包率计算放进来仅仅用1.25GB/s做预算一定会不够。2.3 PCIe/DMA类读写双向都要消耗DDR带宽PCIe是另一个经常让人低估带宽需求的大户尤其是做处理器接口的。以PCIe Gen3 x8为例单方向有效带宽约7.88GB/s这个数字看起来很舒服。但注意PCIe自身带宽和DDR带宽是两个独立资源的账本。PCIe写方向的数据进来要落DDR消耗DDR带宽PCIe读方向的数据要从DDR读出来也消耗DDR带宽。如果双向同时跑DDR那边要同时吞下读和写两侧的流量。经常有人只算进来多少数据要写DDR却忘了同样大小的数据如果被读走DDR侧也要付出同等代价。假设PCIe双向各5GB/sDDR侧的实际压力不是5GB/s而是两个方向的5GB/s加起来约10GB/s这还没算DDR读写切换的额外惩罚。DMA类业务的另一个特点是访问地址常常很大块连续搬运时效率很高但一旦源和目的地址在不同bank或者buffer地址由于缓存行对齐产生大量不连续的页面bank冲突会显著拉低效率。在建模时最好给DMA场景单独做一次大块连续搬运和跨页面分散搬运的两组数据。2.4 用户IO带宽需求公式化汇总把前面几类情况归纳成一个可执行的口诀单模块DDR带宽需求 ≈ 有效数据带宽 ÷ 访问效率 描述符/元数据开销 缓存未命中回写开销其中访问效率受读写比、burst长度、地址连续性和bank冲突影响。读写比越接近1:1总线切换次数越多效率越低burst越短每次DDR命令的固定开销占比越高效率也越低。从实战角度视频流算完有效带宽后建议再乘1.2到1.3的系数网络小包场景则建议按包率重新推演而不是按payload带宽乘系数。3. 后台任务看不见的带宽吸血鬼3.1 Refresh刷新每隔一段时间就要还一次的信贷后台任务里最刚性的就是刷新。DRAM存储单元的电容会漏电必须周期性地读出再写回否则数据就丢了。DDR规范定义了一个刷新时间窗口正常温度下DDR4的tREFI大约是7.8微秒也就是每隔7.8微秒必须完成一次刷新操作而温度超过一定阈值后tREFI会缩短到3.9微秒刷新频率直接翻倍因为高温漏电更快。这就是热词里那个ddr twr trefi的来源tWR是写恢复时间tREFI是刷新间隔这两个参数直接决定后台刷新开销占比。刷新开销占比的粗略计算方式如下以DDR4-2400、tRFC350ns为例每7.8us发一次REF命令 每次REF占用tRFC 350ns 带宽损失 350ns ÷ 7800ns ≈ 4.5%注意这只是单rank的粗略估计。实际系统中如果接了多个rank刷新需要逐rank处理开销还会成倍上升。更麻烦的是刷新命令一旦发出整个bank甚至整颗die在tRFC期间都不可访问这会让上层精心安排的读写队列被硬生生打断。即使4.5%的数字看起来不大它带来的延迟抖动却可能是致命的。这也是为什么有些实时性要求极高的系统会考虑用LPDDR的Flexible Refresh它允许把刷新拆散到多个窗口换取延迟的可预测性。如果你的设计目标是不让任何一笔IO等待超过某个阈值刷新调度策略往往比带宽总量本身更重要。3.2 ECC/Inline ECC为可靠付出的带宽税ECC内存的带宽代价被很多人漏掉。传统上用带ECC的DDR数据位宽从64bit变成72bit等于额外增加12.5%的数据引脚。从带宽账本角度看72bit总线在同样频率下的总带宽提高了12.5%但你的有效数据还是按64bit计算也就是说为了让ECC位一起搬运DDR控制器必须更频繁地访问内存真实消耗随ECC位一起放大。比pin再多一层的代价出在Inline ECC上也就是SoC内部实现端到端保护。这种方案通常需要把原始数据和ECC校验信息一起写入如果写入粒度小于DDR的原子访问粒度控制器就必须先把背后的整块数据读出来、修改后再写回这就是read-modify-write。一读一写带宽直接乘二这是后台任务里最容易爆表的开销之一。我做过一个系统原本DDR带宽利用率只有40%开了细粒度ECC保护以后某些写放大严重的路径利用率直接涨到75%以上。所以做带宽预算的时候ECC机制带来的写放大一定要单独列一行绝不能并进其他里糊弄过去。3.3 搬运、日志、查表可以伸缩的软性消耗刷新和ECC是必须付出的代价后台任务里另一类是软性的比如内存搬运、日志、查表、加密引擎中间的暂存数据。它们的共同点是需求方不是持续运行但一旦运行就是大块读写。内存搬运尤其值得单独建模。一次memcpy在DDR侧会产生读和写两笔流量假如要搬10GB数据DDR实际承载的是20GB——读10GB、写10GB。很多系统做内存整理、休眠唤醒时的镜像恢复、或者异构核之间的buffer移交都要一次性搬很大的数据量。这类任务不能简单粗暴地打满带宽因为它在搬运期间几乎会占满整个DDR总线让所有用户IO同时断供。我见过一个方案是把搬运拆成小块和用户IO的burst交错调度虽然搬运总时间变长了但业务侧完全没有感知这是典型的用延迟换确定性。日志DMA的量通常不大但它的危害在于高频率的小次数访问。每写一行日志都要唤醒DDR控制器和AXI互联扩大调度开销。我的建议是这类后台任务要么用FIFO先聚合成较大粒度再落DDR要么降低它的带宽预算上限不要让它以最高优先级自由飞翔。3.4 把后台任务分成硬性和软性两类梳理这部分对实际设计非常重要。我做需求分解的时候会把后台任务分成两档。硬性后台任务不执行系统就出错或丢数据。主要指Refresh它是优先级最高、不可协商的。ECC的周期检查和必要时的纠错也属于这一类它是可靠性兜底。软性后台任务暂停一会儿不会出事只是时间上延后。包括日志、CRC校验、大块搬运、缓存预取等。区分这两类的好处是在做带宽分配策略时硬性部分必须在任何情况下优先保证软性部分可以作为动态可伸缩的弹性负担。比如刷新优先级最高用户IO次之日志和搬运再往后排。如果系统带宽紧张我们优先压缩的是软性后台任务的配额而不是反过来限制用户IO。4. 带宽怎么分分配策略与落地手法4.1 先做一张完整的带宽账本分配之前必须先有一张完整的表格。我一般会在项目的架构评审阶段就拉一个清单每个需求方占一行列清楚平均带宽长期运行下的均值单位GB/s峰值带宽单次最大burst对应的瞬时需求burst持续时长对调度器评估银行窗口有用访问周期周期性还是随机性延迟容忍度比如显示可以容忍100us的写延迟但读延迟不能超过一行时间优先级用户IO通常高后台任务通常低表格填完以后第一个结论往往不是够不够而是哪些需求根本不可能同时发生。比如64字节小包打满10GbE的同时又把PCIe双向打满很多系统根本同时做不到因为上游链路总数就摆在那里。把这些互斥场景排掉以后真正需要分析的峰值叠加窗口就会小很多。4.2 分配策略优先级、权重、预留三件套有了账本下一步才是选择分配策略。三种常见做法我都在项目里试过。第一种是严格优先级调度。简单直接但低优先级任务很容易被饿死后台任务可能永远得不到带宽。第二种是权重轮转WRR把带宽按比例切分每个需求方都有一个最小份额缺点是无法精细控制延迟高优先级请求还是要等权重轮转的空档才能插队。第三种是预留带宽加剩余共享这是我在工程中比较推荐的方式。先给硬性需求固定预留下来比如某个模块保证至少2GB/s再把剩余带宽按权重分给其他模块同时允许高优先级模块借用低优先级模块没跑满的份额。落实到DDR控制器和AXI互连上预留可以用仲裁权重、AXI QoS信号、或者专用的带宽管理器来做。市面上大多数多主DDR控制器都支持按channel配置最小带宽保证这一层用好远比在应用层做各种复杂的动态调整省事得多。4.3 AXI QoS怎么映射到实际系统AXI协议里的QoS信号是0到15共16个等级这是热词里axi读写ddr最常涉及的工程点。如何给用户IO和后台任务分配QoS不同公司有不同的经验表我比较常用的做法是0到3软性后台任务比如日志、批量搬运4到7中等实时性业务如网络收发、普通DMA8到11高实时性业务如显示读取、实时控制环路12到15最高优先级只给真正不能等的场景比如刷新的内部映射会用到最高等级有个需要注意的坑QoS信号在AXI互连里每过一级都可能被重新映射或者直接被忽略。如果你的总线上有多个AXI switch必须确认每个层级都正确传递了QoS字段否则应用层配了高优先级也没用。另外不要把所有业务都配成高优先级。全高等于全低所有master同时抢带宽的时候仲裁器只会在它们之间轮流放行比专门配置一段区间还要糟糕。4.4 设计余量预留多少才算稳余量预留是这个话题里最玄学也最讲究经验的部分。我一般遵循两个经验法则。第一DDR可用带宽按理论值的70%来估算最多不超过80%。如果系统里有大量小于64B的小型访问或者高度分散的随机访问这个系数还要往下调。第二在平均带宽需求之上再预留20%到30%的峰值余量。换句话说如果一个系统的平均带宽需求是10GB/s我会建议DDR理论带宽选到至少18GB/s左右10除以0.7再乘以1.25也就是实际留出一倍的余量看起来夸张但做过的项目里因为余量不够翻车的远比余量浪费多得多。补一句关于DDR类型的实用区分有些低功耗嵌入式场景会考虑PSRAM伪静态随机存储器它内部其实是DRAM但接口做成SRAM风格好处是免控制器、免刷新管理缺点是容量和带宽都上不去。只要带宽需求真正到了几十GB/s这个量级老老实实上DDR是唯一选择这时候刷新问题就躲不掉了。而如果只是几十MB到几百MB的临时缓冲PSRAM确实能省掉一大笔控制器复杂度。5. 一个综合案例4K显示 万兆网 后台任务5.1 需求侧逐项算一遍用一个混合场景来做全流程对账。假设一个中等规模SoCDDR4-2400 64bit接了一路4K60显示控制器一个双口10GbE控制器一个PCIe Gen3 x8接口外加CPU集群和各类后台任务。先列需求表需求方平均带宽备注4K60显示读1.99 GB/sRGBA8888纯读显示alpha混合辅助写0.2 GB/s底层buffer局部更新10GbE收发2.5 GB/s按payload线速双向PCIe Gen3 x8双向6.0 GB/s实际测试约3GB/s单方向CPUCache回写/外设0.8 GB/s平均loadDDR Refresh估算0.86 GB/s19.2 × 4.5%ECC/Inline ECC写放大0.5 GB/s细粒度写场景平均合计大约12.85GB/s。这个数字看起来离19.2GB/s还有段距离似乎没有问题但接下来的对账才是重点。5.2 供给侧对账实际可用带宽与效率曲线DDR4-2400理论19.2GB/s按70%可用效率估算稳定可提供约13.4GB/s。平均需求12.85GB/s几乎卡着这个线系统几乎没有余量。如果访问模式稍微差一点效率跌到60%可用带宽只有11.5GB/s连平均需求都喂不饱。这时候就要做场景拆分。如果PCIe跑不到双线满速平均需求可能在10GB/s左右系统反而有30%余量。所以不能只看一张平均表必须列出最悲观场景和最可能场景两个版本分别对账。最悲观场景是PCIe读写各3GB/s、10GbE小包、ECC写放大全开这时候DDR利用率逼近95%延迟会显著升高用户IO可能开始丢包。5.3 峰值窗口撞车怎么办峰值窗口分析在这个案例里最能说明问题。显示控制器读一行数据需要的时间大约是一行像素 3840字节RGBA就是15360字节 按DDR理论带宽19.2GB/s这一行最快可在约0.8微秒内读完 但显示行周期约16.3微秒所以读操作集中在行开始的短时间内假设显示器在行起始瞬间发出一个大burst与此同时10GbE刚好收到几个512字节的包要写入DDR而PCIe的DMA又正好在做一次跨页搬运。这三个burst撞在同一时间窗口的瞬时需求可能飙到8到10GB/s虽然只有几微秒但足以让DDR的读写队列全部打满。对于显示控制器来说只要读取延迟超过行消隐的余量画面就开始抖。解决办法主要有三条路径。第一通过QoS保证显示读取优先其它流量在它burst的时候被降权。第二让PCIe和网络的DMA burst长度可配把它们拆成更小粒度防止长时间占用总线。第三调整地址映射让显示缓冲区和网络buffer尽可能落在不同bank减少冲突提升实际效率。5.4 输出结论和调整建议对完账以后这个案例的结论是按平均值看带宽够按最坏峰值看偏紧。我的调整建议是三条。第一把DDR换到DDR4-2666或者DDR4-3200把理论带宽拉到21.3GB/s或25.6GB/s可用带宽自然上移这是最省事的方案如果成本允许的话。第二如果不换DDR就必须做严格的QoS规划和后台任务限速比如给10GbE接收路径加一个字节级别的节流门限给日志DMA设定最大带宽5%避免任何软性后台任务在峰值窗口添乱。第三把ECC策略从细粒度读改写改成整块buffer清零减少写放大。这三板斧下去这个系统的余量基本就能回到40%以上。6. 实操中的坑带宽监控、仿真与排查笔记6.1 用性能计数器实测DDR带宽架构评估再精细最终还是要回到实测。我强烈建议每个DDR控制器预留一套性能计数器至少统计读数据周期数、写数据周期数、总线空闲周期数、刷新命令次数、bank冲突计数、平均队列深度。这些数据能直接回答实际带宽用了多少和效率为什么这么低两个问题。性能计数器的口径要特别注意。我曾经踩过坑计数器的读数据周期只统计了数据总线上的有效数据没算每笔事务之间的空隙导致统计出来的带宽使用率比实际低了十几个百分点。后来统一口径有效数据带宽除以总线上发生读写的总周期数才得到真正的总线利用率。另外很多DDR控制器厂家提供的MRCMemory Reference Code或者MIG都带monitor接口FPGA原型阶段把这些信号引到系统ILA里看波形可以非常直观地看到用户IO和后台任务的时间交错关系。6.2 仿真验证Vivado MIG模型与Sigrity DDR Simulation做FPGA原型的时候最常用的是Xilinx VIVADO里的MIGMemory Interface Generator它可以生成DDR控制器的RTL自带仿真模型能跑功能仿真。如果你想验证带宽分配策略是否正确单纯跑MIG自带的example design是不够的需要自己搭一个多master的AXI环境把视频DMA、网络DMA、后台任务各自使用AXI VIP来驱动然后观测DDR控制器的带宽统计。这里热词里提到的S家DDR VIP就派上用场了——商用DDR VIP自带协议检查和时序违例上报比自己手写BFM省事得多尤其在做DDR协议层面的异常注入时几乎是必需品。如果关心信号完整性而不是带宽分配那就需要走另一条路把DDR的IBIS模型导出来放到Sigrity 2025 DDR Simulation这种SI仿真工具里做布线拓扑分析。这和带宽建模是两个层面的问题带宽建模回答的是系统能不能喂饱SI仿真回答的是物理层信号跑不跑得稳。我见过有人把这两个概念混在一起谈结果在评估带宽时纠结走线长度走了不少弯路。6.3 常见问题速查表结合我踩过和见过的问题整理一个针对用户IO和后台任务抢带宽场景的速查表。现象可能原因排查思路显示偶发撕裂/抖动行的起点burst与网络或后台任务撞车用性能计数器看行起始窗口的总线占用检查QoS是否生效网络丢包但DDR平均利用率不高包率太高导致描述符读写次数太多按包率重算带宽看接收FIFO深度考虑描述符预取PCIe吞吐线性下跌跨页搬运bank冲突严重检查地址映射增大burst到页边界对齐后台任务一跑前台延迟就飙升后台任务burst太猛、优先级没限制给后台任务加throttle限速设置最大带宽配额温度升高后带宽突然变紧张高温让tREFI从7.8us变成3.9us把制冷做上来或者按高温刷新率做带宽预算还有一个常被问到的如何查看DDR版本问题。硬件上看颗粒表面的编码或者内存条标签上的型号部分用SPD EEPROM读出来就行。Linux下直接跑dmidecode -t memory就能看到内存速度、时序参数和厂商信息。FPGA原型里MIG配置界面选择的Memory Part Number就决定了DDR版本和时序参数对不上就很容易出现稳定性和效率问题。6.4 最后几个提醒再分享几个零碎但很管用的经验。第一给后台任务加限速建议加上限再保证下限否则它会自己占满所有空闲带宽反而让前台业务无法借用。第二AXI QoS的优先级不要动不动全配15给真正紧急的路径留出几个最高等级的空位。第三做带宽预算是动态文档每次需求变更都要回来重新对一次表芯片项目尤其如此一次评估定终身的问题出得最多。最后再说一个容易忽略的点DDR带宽建模这件事最终产出不是那张Excel表而是对系统什么时候会崩、崩了以后谁背锅的清晰预期。模型不准没关系但要能给出现这些结论的理由。把用户IO和后台任务分开算、峰值叠加再做一遍再留足余量这个系统大概率就不会在带宽问题上半夜把你叫醒。