ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zynq-7020 AXI-HP接口调优:从200MB/s到1.5GB/s的缓存一致性实战

Zynq-7020 AXI-HP接口调优:从200MB/s到1.5GB/s的缓存一致性实战 去年调一块Zynq-7020的板子PL侧接了个高速ADC数据要经AXI-HP口灌进DDR再由PS侧双核A9去预处理。刚开始跑通时画面一切正常我以为已经绕开了Zynq开发里最麻烦的那条路。等到把数据量加大、跑性能测试问题一个接一个冒出来先是PS读到的图像偶尔是上一帧再是吞吐卡在200MB/s上下死活上不去最后甚至出现整个总线被拖死、DDR仲裁失衡导致CPU执行被卡顿的情况。这篇文章就是那次调优的完整记录围绕缓存一致性、AXI-HP接口的隐藏特性以及实际性能瓶颈梳理出可以直接复用的排查思路和优化手段适合正在用Zynq-7020做高速数据采集、视频传输、软件无线电这类方向的工程师参考。很多人觉得AXI-HP口不过就是个把PL的AXI Master接到DDR上的接口配完地址映射、写好DMA逻辑就能跑。实际用下来HP口只是看起来简单里面牵扯到缓存一致性、AXI3协议限制、互联仲裁策略、DDR控制器的效率行为等一整套链路任何一个环节没对齐性能都会掉一个数量级。下文从问题现象讲起逐步把架构细节、踩坑清单、调优前后的实测数据一次说清楚。1. 为什么数据会差一拍从缓存一致性问题开场1.1 花屏问题的定位读到的是PL写入之前的旧数据第一次遇到缓存一致性问题是在一个图像采集工程里。PL侧的DMA把ADC采样到的图像数据连续写入DDR中的缓冲区CPU在下一帧同步信号到来时去读同一块地址。现象很典型图像不是全错而是每隔几帧会出现上一次帧的内容像是画面卡在上一帧。起初怀疑DMA时序有问题在Vivado里挂了ILA集成逻辑分析仪抓AXI波形DMA确实是在完整写完一帧后才拉高完成中断地址也没错。那问题就不在PL侧而在PS侧。根本原因是Zynq的Cortex-A9有L1和L2两级CacheCPU读内存时优先命中Cache。PL通过HP口把数据写进DDR但CPU地址空间里那块区域之前被读取过对应的Cache line是旧的CPU根本没意识到DDR里的数据已经变了直接返回了缓存里的旧值。这个现象在高吞吐、连续帧传输时几乎必然出现因为同样的缓冲区被反复复用Cache里一直保留着旧副本。解决办法分两个层面。最直接的办法是在每次DMA传输完成后做Cache维护操作更新前调用写回Clean让CPU写入的数据落到DDR读取前调用失效Invalidate丢掉可能过期的缓存内容。在Xilinx SDK裸机环境下就是Xil_DCacheFlush()和Xil_DCacheInvalidate()但要注意调用时机Flush必须在DMA启动读之前做Invalidate必须在DMA写完成后、CPU读之前做顺序反了等于白做。1.2 HP口与ACP口一致性机制的取舍理解了Cache问题后自然会问Zynq不是有SCUSnoop Control Unit吗用ACP口不就能自动维护一致性确实Zynq的PS侧有4个HP口和1个ACP口实际是1个S_AXI_ACP从口。ACP口经过SCUPL访问DDR时会去监听CPU的Cache状态硬件上实现了某种程度的一致性和原子操作CPU无需手工维护。而HP口则完全不同它走的是独立的路径直接面向DDR控制器不经过SCU所以PL写的数据对CPU Cache是不可见的CPU读到的内容完全取决于Cache状态这正是上一节花屏现象的根源。那为什么不都用ACP因为一致性是有代价的。SCU snoop操作要遍历CPU的Cache并做匹配这会增加访问延时且ACP口的带宽和事务处理能力本质上不如HP口。HP口路径短、直达DDR带宽潜力更大。在Xilinx Zynq-7020的数据手册和TRM里HP口的典型用途就是大带宽数据搬运比如DMA写采集数据、Display Controller读显存这类流量。而ACP口适合那些需要和CPU频繁共享小数据块、对一致性敏感的场合。因此正确思路是把HP口定位成高带宽的非一致传输通道由软件显式管理Cache生命周期把ACP口定位成小数据共享通道。两者各管一摊不要指望用一个口解决所有问题。实际项目中我把图像帧缓冲放在HP口路径上CPU处理前做Invalidate处理完要更新元数据时再统一Flush一次DMA对应两次Cache操作稳定可靠。2. AXI-HP接口的关键机制与隐藏陷阱清单2.1 地址映射偏移0x00100000这个坑Zynq-7020有一个非常容易忽略的地址映射问题PS侧的DDR物理地址从0x00100000开始而不是0x00000000。0x00000000-0x000FFFFF这1MB空间被分配给了内部SRAMOCM和其它保留外设。这个问题在PS用CPU搬运数据时不容易暴露因为SDK的链接脚本和Linux内核都会自己处理但一旦PL侧要直接访问DDR就必须在AXI地址里显式加上这个偏移。举个例子在Vivado的Address Editor里给某个AXI Master分配DDR区域默认基地址往往跟着PS的地址视图走可能就是0x00100000。但如果自己写DMA逻辑在寄存器里配目标地址时用了0x00008000这种看起来像DDR地址的值数据实际会被写到OCM里而不是DDR。这种问题在仿真里看不出来因为仿真模型的地址空间通常是理想化的只有上板后才会发现数据落点不对而且表现非常诡异——有时候能读到数据、断电重启后数据消失因为OCM是易失存储器且容量只有256KB。建议从一开始就建立一张地址表DDR起始0x00100000、OCM起始0x00000000、线性地址还是物理地址都要统一。所有PL侧DMA描述符里的地址一律写物理地址0x00100000偏移后的值在代码注释里写清楚避免后续换人维护时踩同一个坑。2.2 窄传输与读改写小于32字节的写为什么会拖垮带宽AXI-HP口的数据总线宽度是64位也就是8字节。如果PL侧的Master发起的写事务每次长度小于一个完整的数据位宽互联逻辑会把窄传输转换成读-修改-写操作先从DDR里读出整个64位甚至128位的旧数据把要写的那几个字节替换进去再整块写回。这样做在协议上是合法的代价是总线事务数膨胀DDR的有效带宽被严重浪费。我当时踩的具体问题来自一个自定义DMA模块描述符里有个状态字段每次只写4字节。在没优化时这个4字节的写操作插入到每帧数据之间看似不痛不痒实际上它把连续burst的流水打断了单单这一项就导致吞吐下降了约30%。更隐蔽的是如果PL同时从多个Master去写同一块DDR区域读改写操作还会引入数据竞争窗口——两个Master同时读回同一地址的旧值、各自修改后写回后写的会覆盖先写的改动。处理原则很简单所有PL侧写入尽量做到32字节对齐。32字节是怎么来的AXI3的burst长度最大16数据总线64位16次8字节的突发传输正好是128字节而DDR控制器的内部访问粒度往往以32或64字节为单位32字节对齐是最低要求。如果一定要细粒度更新就把这类状态字段集中到一个独立的、不与大数据流混用的缓冲区里用一个完整的32字节突发写完成所有状态刷新。2.3 跨4KB边界的burstAXI协议自带的传输限制AXI协议规定一个burst不能跨过4KB地址边界。Zynq HP口的互联逻辑同样遵守这个规则。设计DMA时如果目标地址是0x001FF000传输长度为4096字节恰好跨越边界协议层面要求拆成两个burst第一个burst传输到0x001FFFFF为止第二个burst从0x00200000开始继续。Master状态的地址指针、剩余长度计数都要能处理这种拆分不然波形仿真时会看到AXI协议违例或者在实际总线上出现不可预期的行为。这个限制在AXI3下尤其容易忽略因为AXI3的burst最大16拍每拍8字节就是128字节。很多人写DMA状态机时burst定的是常量16遇到非对齐首地址或跨边界时并不会主动去算剩余到边界的字节数而是照着满burst发结果要么地址越过了4KB边界要么提前结束导致数据错位。我在实测中最稳妥的做法是在设计DMA描述符时让软件侧预先做地址对齐化和段拆分。主机侧把大块传输拆成若干个不超过4KB的段每个段单独一个描述符硬件DMA只负责顺序执行描述符队列不需要在内部判断边界。这样PL侧的地址生成逻辑可以写成纯线性递增逻辑简单、时序收敛容易性能也完全不受影响。唯一的代价是描述符数量变多一些但对DDR中的环形描述符表来说几千个描述符完全不占空间。2.4 ARCACHE/AWCACHE与outstanding接口配置里的两个隐性变量PL侧自定义Master在接HP口时AXI总线上的ARCACHE、AWCACHE这两个信号大多数情况下被忽略很多教程直接给你写死成0b1111。这个值表示可分配、可写分配但问题是PL侧的Master本身并没有任何Cache把这个值传给PS互联后不同版本的互联逻辑和DDR控制器对其的解释可能不同轻则影响效率重则产生意想不到的额外延迟周期。踩过几次后我推荐使用0b0011Normal Non-cacheable Bufferable这个配置和PL侧无缓存、直接访问DDR的场景是匹配的。另一个关键变量是outstanding事务数。AXI Master每发起一个读事务就要占用一个读地址通道的槽位直到对应读数据返回后才释放。如果Master一次只发起一笔事务、等它返回后再发下一笔那么读延时完全暴露在关键路径上DDR的读写延迟通常在几十纳秒量级乘以事务数量有效吞吐会低得离谱。提升性能的核心思想是让总线满起来在一个事务还没返回时就发出另外几笔待处理事务这样DDR在服务完第一笔后紧接着就能返回第二笔的数据流水线不空转。在HP口方向上PS侧的S_AXI_HP从口有足够深的FIFO来容纳多个未完成事务PL侧Master只要维护一个简单的outstanding计数器就能实现多事务并行。我实测的差别很直观outstanding1时单向写入吞吐约250MB/s提升到8后同一逻辑直接跳到接近700MB/s配合burst对齐和DDR效率优化最后稳定在1.2GB/s以上。这个参数不需要PS侧额外配置完全是PL侧Master的设计问题。3. 实战调优全过程从200MB/s到1500MB/s3.1 基线测试与瓶颈定位动优化之前必须先建立一套可重复的性能基线。我在PL侧放了一个简单的AXI写Master把一块固定数据连续写入DDR中的指定缓冲区写完触发中断PS侧通过计时器和软件计数器计算吞吐。这个方案排除了应用层干扰测出来的是纯总线性能。最初的基线测出来只有200MB/s左右坦白说这个数字连理论带宽的一半都不到正常应该能到1GB/s以上所以问题一定出在Master侧或地址配置上。我首先排除了DDR频率因素在SDK里读取DDR控制器配置确认DDR3工作在1066Mbps的设定下。接着用ILA抓波形观察AXI通道上的burst长度和事务间隔发现Master每完成一笔burst后要等整整60多个周期才发下一笔地址。原因找到了状态机里等待当前burst的写响应BVALID返回后才生成下一笔AWADDR这个握手逻辑把流水线完全串行化了。这个瓶颈几乎每个自己写AXI Master的人都会遇到。正确的做法是让地址生成、数据发送、写响应回收三个子流程解耦用FIFO或寄存器堆做异步缓冲。地址通道和写数据通道实际上是独立的Master完全可以提前把多笔地址和对应的数据提交给总线写响应回来的时机不需要阻塞下一笔事务的发起。修改后的状态机在发出一笔burst地址后立刻生成下一笔地址只有当outstanding计数器达到最大允许值时才暂停发出新的地址事务。这一段修改带来的提升最大单单解决事务串行问题吞吐就从200MB/s升到了约500MB/s。后面所有的优化都是在总线已经能保持流水的前提下展开的如果流水不保持其它技巧全都会大打折扣。3.2 对齐、burst长度与批量搬运基线测试之后我仔细检查了每笔事务的地址对齐和burst长度。因为图像缓冲区的分配是通过SDK的memalign完成的起始地址本身按32字节对齐过所以首地址对齐是好的但在每帧数据中间有一段元数据区导致的地址偏移使得后续数据的burst地址变成了非对齐状态这直接限制了burst的有效数据宽度。对齐问题的实际影响在AXI波形上非常清晰同样的数据量对齐时每拍都能传满8字节不对齐时首末两拍分别只有部分字节有效有效带宽平白损失掉一部分。解决办法是数据结构设计时把所有负载段都按32字节边界重新划分元数据单独放不让它打断数据块的线性对齐。burst长度方面Zynq HP口遵循AXI3协议最大burst长度是16拍每拍64位就是128字节。有的工程师习惯从AXI4的IP迁移过来直接配burst长度256这在HP口上是不能正确执行的。我自己写的Master把所有burst固定为16拍地址步进128字节这样既满足协议上限也正好对应DDR控制器比较友好的访问粒度。批量搬运靠的不是加长单个burst而是让多个128字节的burst连续不断地发出去这样DDR的bank调度才能高效运作。3.3 双缓冲与中断合并吞吐上来之后下一个瓶颈转移到中断和软件开销上。初始设计里每完成一块数据的DMA传输PL都会向PS发起一次中断PS在中断处理里启动下一轮传输。低频时这个模型没问题但带宽一高中断频率跟着暴增GIC的中断分发、上下文切换、Cache操作全部挤在关键路径上CPU负载轻松超过50%而且吞吐也不再线性增长因为每次中断处理期间总线是空闲的。我用的优化方案由两层组成。第一层是双缓冲PL侧维护两个DDR缓冲区CPU在DMA写缓冲区A的同时处理缓冲区B里的数据DMA完成中断触发后软件只交换指针不等待数据处理完就立刻启动下一次传输。这样总线上永远有活干CPU处理和DMA搬运并行起来。第二层是中断合并PL侧逻辑里做事件计数每完成8次DMA传输才向PS发出一次合并中断PS在中断里循环处理这8个缓冲区的数据。合并比例可以根据实时性需求调整图像类应用对几十微秒的延迟不敏感合并8次毫无压力。优化后实测非常明显中断频率降到了原来的1/8CPU占用率从约45%降到不足10%整体吞吐又往上推了一步。这个经验说明当总线带宽和CPU处理能力在同一量级竞争时减少同步点比单纯加总线频率更有效。3.4 DDR仲裁与端口优先级调整还有一个容易被忽略的全局因素DDR控制器的仲裁策略。Zynq-7020的PS侧有多个总线主设备可以访问DDR双核CPU、两个DMA控制器、以及PL侧的4个HP口。当PL侧的数据流量变大后DDR控制器的QoS仲裁需要在多个请求源之间分配优先级。如果PL侧HP口的优先级设成了默认的低优先级在CPU或其它主设备同时有较多访问时HP口的带宽会被显著压缩。Zynq-7020的DDR控制器有一组QoS配置寄存器可以调整每个AXI端口的优先级和带宽分配。我在做纯带宽测试时发现同样一段写入逻辑在CPU空载时能跑到1.2GB/s一旦CPU同时在做浮点运算和SD卡读写吞吐立刻掉到800MB/s以下。通过调整DDRC的QoS寄存器把HP0口的优先级权重往上调PL流量被压缩的现象明显缓解最坏情况下的吞吐保底到了1.1GB/s左右。值得注意的是优先级调太高也有可能引发副作用。如果PL占用了绝大部分DDR带宽CPU的取指和数据访问会被拖慢最极端时Linux的调度周期都受影响。我的建议是把PL优先级调到中等偏上并配合中断合并降低CPU和PL的并发访问频率让两者错峰而不是在DDR仲裁器里互相争抢。4. 正确性验证与性能测量的反直觉经验4.1 为什么不能用读回来比对当性能指标很多初做DMA验证的人喜欢在PL写完DDR后再用CPU去读同一块区域和原始数据比对同时统计吞吐。但这条看似合理的路径有两个坑。第一CPU读数据时会经过Cache如果命中缓存测出来的读吞吐根本不是DDR或总线的真实性能而是Cache的性能数值会偏高得离谱。第二CPU通过L2 Cache批量读大数组时硬件预取器会提前拉数据测出来的带宽带有很大预取成分。正确验证吞吐的方法是用DMA引擎回读或者用另一个独立Master去读DDR并直接在PL侧做数据比对例如在PL里写一个Loopback逻辑把DMA写出去的同一块数据读回PL用CRC/哈希模块在线比对。这样全程不经过CPU的Cache测到的才是数据通路上的真实性能。如果做纯写性能测试则让Master只写DDR软件侧只通过DMA完成中断做计时不要在写入路径上插入CPU读操作。4.2 CPU侧Cache维护的正确姿势与缓存行对齐Cache维护操作的颗粒度会影响性能。Cortex-A9架构下L1数据Cache的cache line是32字节L2 Cache的line是64字节。Xil_DCacheFlush和Xil_DCacheInvalidate支持按地址和长度操作但实际执行时是按Cache line对齐刷新的如果传入的地址没对齐到64字节刷新的范围会被扩展到相邻的cache line可能把别的数据也冲刷掉或者多刷了不必要的内容白白浪费时间。我在工程里把所有共享缓冲区都按64字节对齐分配长度也按64字节取整。这样一来每次Cache维护操作刷新的都是恰好覆盖整个缓冲区的整数个Cache line没有额外的越界开销。刚开始图省事直接传ADDR_START和LENGTH后来发现Cache操作耗时波动大才意识到是对齐问题。对齐后Cache Flush Invalidate的总耗时稳定在微秒级对于每帧传输几毫秒的应用来说完全可以忽略。同时如果是Linux环境不建议在用户态频繁调/dev/mem加Cache维护函数很容易绕过内核的DMA API导致一致性管理混乱。更规范的做法是写一个字符设备驱动使用dma_alloc_coherent分配一致性缓冲区或者dma_map_single配合dma_unmap_single来做流式映射。这些内核API会在底层自动插桩Cache维护操作远比用户态手动操作可靠。4.3 用AXI Monitor / 集成逻辑分析仪量化时序光靠吞吐数字优化方向容易走弯路因为吞吐是一个综合结果分不清瓶颈在地址生成、数据通道还是DDR本身。我在调优过程中反复使用了两类工具Vivado的ILA和AXI Performance Monitor IP。ILA适合抓短时波形观察burst长度、地址连续性、VALID/READY握手间隔这些微观行为。比如前文说的每笔burst间停顿60周期就是靠ILA抓出来的。但ILA有个致命弱点存储深度有限抓不了长时间、大批量的传输统计。AXI Performance Monitor则适合做长时统计它能统计每个端口的读/写传输数、总字节数、平均延迟、outstanding数等指标。把它接到HP口上跑几秒钟的测试就能看到平均outstanding是多少、总线上有多少空闲周期、DDR的刷新间隔是不是造成了周期性的吞吐下跌。两个工具结合微观定位行为和宏观量化瓶颈一次做完后面优化才有的放矢。5. 把自己踩过的坑固化成设计检查清单调完这个项目后我整理了一份AXI-HP接口的设计检查清单给团队新人和后续项目复用。最核心的几条是第一所有PL侧到DDR的地址必须带0x00100000偏移且全局统一第二数据块和状态字段分离放置所有总线事务按32字节对齐第三burst长度固定为16拍地址步进128字节DMA描述符在软件侧按4KB边界切分第四Master必须支持多outstanding建议至少8用FIFO解耦地址、数据、响应三个子通道第五写ARCACHE/AWCACHE设为0b0011不要盲目写0b1111第六每次DMA传输结束后在正确时机执行Cache Clean和Invalidate第七性能测试用AXI Monitor和回读DMA不要用CPU读数据来验证。这些条目看起来琐碎但每一条背后都是实际项目中真实耗费过几天时间的问题。尤其是缓存一致性这条它在架构图上看不见、在波形仿真里也测不出来只有跑到真实系统、长时间连续运行才会暴露属于最隐蔽、也最影响可靠性的坑。如果有条件建议从项目一开始就在硬件验证平台里加入一个长时间压力测试用例让PL以满带宽连续搬运数据几个小时同时让CPU持续读写其它DDR区域这种组合测试能很快把一致性问题和仲裁问题逼出来比等到系统联调时再补救要高效得多。我在做完这次调优后把同一套Master逻辑迁移到了另一个使用ACP口的小数据量场景也顺带验证了两类接口在行为上的差异。整体来看Zynq-7020的AXI-HP口在正确的配置下可以非常稳定地跑到单口1.2GB/s以上的实际吞吐完全能满足大多数高速数据采集和图像处理应用。如果项目里还有余量可以考虑把PL时钟从200MHz进一步提高并配合DDR3的高频模式但那需要重新压时序和功耗收益不一定成比例。目前这个方案在量产板上已经稳定运行了大半年没有再出现缓存一致性和带宽相关的故障这也是我把整个排查过程写出来的原因——希望下一代工程师能少走这些弯路。
返回列表