尧图精选

FPGA工程中的AXI总线实战:握手、突发与接口选型

🕒 发布时间:2026/9/18 17:56:26 📁 来源:尧图网络
1. 为什么到了工程阶段AXI 这道坎必须自己走过去这个系列写到第 11 篇前面十篇基本都在跟能不能跑起来较劲时钟约束过了没有、复位有没有亚稳态、数码管刷不刷得动。到这里问题开始变了——不再是灯亮不亮而是数据怎么从 A 搬到 B而且搬得快、搬得稳。只要你的 FPGA 工程里出现 DDR、出现处理器软核、出现以太网或者视频通路AXI 总线协议就绕不过去。很多人第一次接触 AXI 是被 IP 核逼的调一个 MIG、挂一个 Zynq 的 HP 口、或者从 Vivado 里拖一个 AXI DMA 出来结果 Address Editor 里一片红色编译能过但一上板就挂死波形上 VALID 拉得老高READY 死活不动。这时候你想绕开 AXI写个自定义总线打通两边——短期确实能通但一旦要接官方 IP或者要跑 Linux、要接 DMA就彻底走不下去了。所以这一篇我不打算按手册的顺序讲 AXI4 有多少个通道、多少个信号。手册你自己会翻。我更想讲清楚三件事握手到底卡在哪、突发该怎么算、工程里到底该选 AXI4 还是 AXI4-Lite 还是 AXI-Stream。这三件事搞明白了你在仿真里看到僵死的波形能自己定位在 Address Editor 里看到红块能自己改带宽不够的时候知道该从哪里下手。适合谁看如果你 Verilog 能写状态机、跑过几个小工程但一看到AWVALID、ARLEN、TLAST就头大这篇就是给你写的。不需要你懂 AXI 的前置知识需要的是你愿意打开仿真器跟着波形看一遍。1.1 从能点亮灯到能把数据搬进 DDR的落差数码管动态显示那类工程本质是时序逻辑 组合译码信号方向单一谁驱动谁很清楚。AXI 不一样它是一套带流控的双向协议每一拍都在两边协商我这拍能不能给你、你这拍能不能收。这个协商机制叫握手是 AXI 里唯一真正需要吃透的东西比通道数量、信号名字重要得多。举个我刚上手时的真实感受把 AXI4-Lite 接一个自定义寄存器模块代码写完之后仿真里读寄存器永远读回 0。我当时以为是地址译码写错了查了两小时地址最后发现是RREADY我一直拉低——因为我想等数据来了再拉高 READY 去收而实际上从设备的RVALID是看到我RREADY拉高之后才拉起来的。两边互相等仿真器里就那么僵着波形上看两条线都是平的谁也看不出问题。这就是落差在单一方向的逻辑里你从来不需要考虑我这条线拉高之后对方会怎么反应在 AXI 里这是每一拍都要想的事。1.2 AXI 到底解决了什么问题五组独立通道的价值AXI4 把一次传输拆成五组通道写方向三组、读方向两组通道方向作用关键信号AW主 → 从写地址与写控制AWADDR / AWLEN / AWSIZE / AWBURST / AWVALID / AWREADYW主 → 从写数据WDATA / WSTRB / WLAST / WVALID / WREADYB从 → 主写响应BRESP / BVALID / BREADYAR主 → 从读地址与控制ARADDR / ARLEN / ARSIZE / ARVALID / ARREADYR从 → 主读数据RDATA / RRESP / RLAST / RVALID / RREADY为什么要拆成这样因为拆开之后每一组通道都能独立流水。主设备发完一个写地址不用等数据搬完可以接着发下一个写地址从设备可以一边收数据一边准备响应。这就是发命令和传数据解耦也是 AXI 比早年的 AHB 效率高的根本原因。你在实际工程里会明显感受到这一点一个 DDR 控制器挂了三四个主设备视频读、视频写、处理器取指、DMA如果每个主设备都必须等上一次传输彻底完成才能发下一次DDR 的带宽利用率会低得没法看。1.3 先记住五条通道的名字别急着背时序我的建议是第一遍不要看时序图先记住五条通道的名字和方向然后记住一条铁律——每个通道上VALID由发送方驱动READY由接收方驱动两者同时为高的那一拍才算一次成功传输。地址通道传一次数据通道可能传几十次突发响应通道传一次。把这条记住剩下的都是细节。细节可以查手册方向搞反了才是灾难性的——我见过不止一个人把BVALID当成主设备驱动结果写响应永远收不到。2. VALID/READY 握手AXI 里唯一真正需要吃透的机制握手规则本身只有三条但违反其中任何一条都会导致僵死或者数据错乱而且症状往往很隐蔽。2.1 双向握手的三条铁律第一条VALID一旦拉高在本次握手完成VALID READY同时为高之前绝对不允许撤销。这条是为了防止接收方判断错误——如果发送方拉了一下又缩回去接收方可能已经在内部记了一笔。第二条VALID的拉高不能依赖READY。也就是说发送方不能写成assign m_valid data_avail m_ready;这种形式。这一条最反直觉但它是死锁的根源如果两边都等对方先动就永远不动了。第三条READY可以依赖VALID也可以不依赖。接收方可以在任何时候提前把READY拉高表示我随时能收这完全合法而且能省一拍延迟。第三条是给优化留的口子。如果你的接收端缓冲区一直有空位直接把READY拉高握手就在VALID拉起的那一拍立刻完成零等待。反之如果缓冲区会满就用READY做反压。一条发送侧的代码大致长这样// 发送侧VALID 只跟本地数据有关绝不看 READY always_ff (posedge clk) begin if (!rst_n) begin m_valid 1b0; end else if (m_valid m_ready) begin m_valid fifo_empty ? 1b0 : 1b1; // 握手成功才能推进 end else if (!m_valid !fifo_empty) begin m_valid 1b1; // 有数据就抬不等 m_ready end end assign m_data fifo_dout; // data 与 valid 同步有效注意m_data的稳定性。如果m_data直接来自 FIFO 的dout而 FIFO 的读指针在握手成功那一拍才推进那就能自然满足VALID !READY期间数据不变的要求。如果你中间插了一级组合逻辑去改数据很容易破坏这条性质。2.2 一个仿真波形复盘为什么我的 READY 拉晚了三个周期这是我印象最深的一次排查。场景是自定义 AXI4-Lite 从设备主设备是软核处理器。上板后读寄存器偶尔返回全 0概率大概十分之一。我在仿真里抓了波形看到的现象是ARVALID拉起后我的ARREADY在第 4 拍才拉高中间隔了 3 个周期。当时我的从设备逻辑是这样的收到ARVALID后先做地址译码译码结果打到一级寄存器再用寄存器输出驱动ARREADY。多拍本身不违规但问题是——我在译码阶段做了一次读数据预取那个预取逻辑用了ARADDR却没考虑ARVALID还没握手完成时地址是不保证稳定的。换句话说我把ARADDR有效这件事默认成了常量忘了它只在ARVALID ARREADY那一拍才是已确认的。修复思路很简单地址译码和预取都改成在ar_handshake ARVALID ARREADY那一拍才采样或者干脆给地址加一个采样寄存器在握手时锁存。这次之后我养成一个习惯在任何 AXI 逻辑里只要用到地址、长度、ID 这些控制信号先问一句我采样它的那一拍握手成功了吗。这个习惯帮我避开了后面至少三四个类似的坑。2.3 STALL 与背压从 FIFO 水位看反压是怎么产生的工程里 90% 的READY拉低都是因为下游 FIFO 快满了。反压back pressure这个词听起来玄实际就是下游存不下了让上游停一停。一个典型的 AXI-Stream 通路是这样的上游是数据产生模块中间一级 FIFO下游是处理模块。当下游处理慢FIFO 水位上升到某个阈值比如almost_full时FIFO 的almost_full直接接到TREADY的反相上游就被顶住了。关键点是上游被顶住的时候不许丢数据也不许改数据。这一拍没握手成功TVALID保持高TDATA保持原值下一拍接着等。如果你的上游是个状态机在等READY期间跳到了别的状态数据就丢了。反压会一层层往上传递下游 FIFO 满 → 中间处理模块被顶住 → 它的输入 FIFO 也快满 → 再往上顶 → 最终顶到 AXI 主设备让AWVALID拉不起来写地址发不出去。整个链路就自动降速了。这是好事说明流控生效了。麻烦的是反压传递链上任何一级处理不当就会变成僵死而不是降速。一个实用的经验值FIFO 的almost_full阈值不要设得太贴近满。留出至少能容纳一个最大突发的余量——比如你的 AXI 突发最长 256 拍、位宽 64 位那就留够 256 个位置的余量否则最后几拍数据进来之前 FIFO 就满了会额外多出很多无效等待。2.4 一份可以贴在显示器边上的握手检查表我把自己踩过的坑整理成了一张表写新模块的时候对着过一遍能省下大量调试时间检查项违规后果正确做法VALID是否依赖READY死锁VALID只跟本地数据/状态有关VALID !READY时数据是否变化数据错乱用寄存器或 FIFO 输出直接驱动VALID是否中途撤销接收方误判只在握手成功那一拍降VALID地址是否在握手时才采样采样到无效地址用handshake信号做采样使能READY是否组合依赖过长的逻辑链时序违例READY打拍或用 FIFO 标志直出复位是否同步、低有效上电随机挂死ARESETN同步释放保持多拍最后一行特别容易被忽略。AXI 规范的复位是ARESETN低有效、同步。我见过直接用异步复位打天下结果某个 IP 上电偶发握手异常查了一整天才发现是复位释放时不同模块的时序差。提示很多厂商 IP 对复位保持时间有要求通常建议ARESETN至少保持 16 个时钟周期并且要在时钟稳定之后再释放。3. 突发传输与地址计算AXI4 的 LEN、SIZE、BURST 三个参数怎么算突发是 AXI 效率的来源也是最容易算错的地方。三个参数配合起来决定了这次搬多少、每拍多少、地址怎么走。3.1 AW/W/B 通道的配合关系与 WLAST 的强制要求先理清一次写传输的完整时序。主设备在 AW 通道发地址和控制信息在 W 通道发数据从设备在收完全部数据后在 B 通道回一个BRESP表示写完了、结果如何。三组通道的握手是独立的。这里有个经典的认知误区很多人以为必须先把 AW 发完才能发 W。规范并没有这个要求AW 和 W 可以同时发、可以先 W 后 AW、也可以交错。从设备可以选择等 AW 和 W 都到齐了再开始处理也可以边收边写。这个自由度带来一个非常容易踩的坑我在一个项目里真的中招了我的主设备逻辑写成先等AWREADY握手成功后再抬WVALID。而对接的那个从设备内部是等 W 通道有数据了才拉AWREADY。两边互相等波形上AWVALID高、AWREADY低、WVALID低三条线一动不动一挂就是永远。排查的时候我做的第一件事是把所有通道的 VALID/READY 组合状态列成一张表逐拍观察哪一组是两边都在等很快就定位了。修复方式是把主设备的 AW 和 W 改成并行驱动地址先压进一个小 FIFO数据直接从数据 FIFO 出两组VALID各自独立生成。还有WLAST。这一位标记突发的最后一拍。如果你的突发长度是 8 拍那第 8 拍的WVALID必须同时带WLAST1。从设备靠它判断这次写完了。我见过WLAST少拉一次的代码结果从设备一直等最后一拍主设备已经跑去发下一个突发了数据全错位。注意WLAST的拍数必须和AWLEN 1严格对应。调 IP 之前先在仿真里打印一下实际拍的数和AWLEN是否一致这一步能挡掉大部分数据看起来对了但写进去错位的问题。3.2 用一次 4KB 边界踩坑说明 burst 长度为何不能乱给AXI 有一条硬性规则一次突发不允许跨越 4KB 地址边界。原因是从设备通常拿地址的高位做译码一次突发如果跨过 4KB就可能一半落在设备 A、一半落在设备 B从设备根本没法处理。很多人写 AXI4 主设备的时候喜欢抄模板把LEN直接写成最大值AXI4 里 INCR 突发最大 256 拍结果地址一变就违约。我举个例子算一遍你就明白为什么这个坑这么容易踩。假设数据总线 64 位8 字节一次突发 256 拍总长度 2048 字节。起始地址是0x0000_0FF8结束地址 0x0FF8 2048 - 1 0x0FF8 0x7FF 0x17F7起始地址所在 4KB 页 0x0FF8 12 0x0结束地址所在 4KB 页 0x17F7 12 0x1跨页了这次突发非法。正确做法是拆成两段第一段从0x0FF8开始0x0FF8到0x0FFF只剩 8 字节也就是只能发 1 拍第二段从0x1000重新开始后面的部分按剩余长度发。你会发现第一段只有 1 拍效率极低。这就是为什么实际工程里的 DMA 描述符一定要按 4KB 对齐切分而不是简单地把一段大内存按固定长度切开。写驱动的朋友经常忽略这一点从上层传下来的 buffer 地址千奇百怪到了 AXI 层就得重新切。判断是否跨界的检查逻辑其实很简单// 判断本次突发是否跨越 4KB 边界 wire [31:0] start_addr AWADDR; wire [31:0] end_addr AWADDR ((AWLEN 1) AWSIZE) - 1; wire cross_4k (start_addr[31:12] ! end_addr[31:12]);如果你在写一个 AXI 主设备这个检查建议直接放进仿真断言里一跨界就报错比上板后对着 DDR 数据发愁强得多。3.3 地址对齐、窄传输与非对齐的实际处理思路AWSIZE决定了每一拍传输多少字节取值是2^SIZE。SIZE3 表示 8 字节一拍SIZE2 表示 4 字节一拍。窄传输narrow transfer是指每拍传输的字节数小于数据总线宽度。比如总线 64 位但你要按 4 字节一拍去传那就是窄传输。这时候WSTRB就很关键它逐位标记数据总线上哪些字节是有效的一个WSTRB位对应一个字节。举个具体例子数据总线 64 位共 8 个字节地址从0x1000开始SIZE24 字节只往0x1004写 4 个字节。那这一拍的WSTRB应该是8b1111_0000——高 4 字节有效对应0x1004到0x1007。那如果地址本身就是非对齐的呢比如你要从0x1002开始写数据。AXI 规范要求主设备自己把非对齐的起始地址处理掉先把地址向下对齐到 SIZE 边界再用WSTRB屏蔽掉不需要的字节。也就是说主设备把0x1002对齐成0x1000第一拍的WSTRB设为8b1100_0000把0x1000、0x1001两个字节屏蔽掉。这套处理逻辑必须放在主设备侧。很多人以为从设备会帮你处理非对齐实际不会——从设备只管按WSTRB往下写。这也是为什么自己写 AXI 主设备最麻烦的从来不是握手而是这些边界和字节选通。场景起始地址SIZEWSTRB说明对齐写 8 字节0x100038hFF全字节有效高 4 字节写0x100428hF0窄传输非对齐起 6 字节0x10023第一拍 8hFC主设备先对齐再屏蔽单字节写0x100708h01SIZE0只动一个字节4. 从 AXI4 到 AXI-Stream什么时候该用哪套接口同一个工程里经常三种接口同时存在选错了要么浪费逻辑要么根本实现不了。4.1 AXI4 / AXI4-Lite / AXI4-Stream 的选型对照接口有无地址是否支持突发典型用途逻辑开销AXI4有支持最大 256 拍DDR 搬数、大块内存访问大AXI4-Lite有不支持固定 1 拍寄存器配置、状态读取小AXI4-Stream无无长度限制靠 TLAST 定包视频流、数据流、滤波通路最小我的决策习惯很直接要访问内存地址就用 AXI4 或 Lite纯数据流水就用 Stream。配置寄存器一律用 AXI4-Lite。它没有突发一次就传一拍逻辑简单到你手写一个从设备只要一百多行代码。没必要为了省事把它升级成完整 AXI4那纯粹是给自己加负担。真正的大块数据搬运比如视频帧往 DDR 里写、DDR 里的数据往处理模块里读用完整 AXI4。因为突发能把它效率拉起来一次发 256 拍地址只传一次带宽利用率能到很高的水平。而处理链路上的中间环节比如从 FIFO 出来的像素流做一次 3x3 卷积用 AXI4-Stream。这里的场景是数据源源不断来、源源不断走根本不存在地址这个概念硬套 AXI4 反而是给自己找麻烦。4.2 Stream 没有地址TLAST 和 TKEEP 在实际项目里的用法AXI-Stream 只有五个关键信号TDATA、TVALID、TREADY、TLAST、TKEEP还有TSTRB、TUSER、TID、TDEST这些可选。TLAST是包边界标记。没有地址的接口下游怎么知道一段数据从哪结束就靠它。比如视频的一行像素你可以把TLAST放在一行结束时如果处理的是一帧也可以放在整帧结束。这里有个设计取舍值得说一下TLAST粒度设成行还是帧直接影响中间缓冲的设计。设成行每行结束就有一个明确的边界可以做行缓存、行级的流水处理比较灵活。设成帧那整帧数据就是一大包中间缓冲要么做得很大要么就得做帧内的分块处理。我在做一个 1080p 视频通路的时候最初把TLAST设在帧尾结果中间的行缓存模块完全没有边界信息只能靠自己数像素。后来改成行尾打TLAST行缓存模块拿到信号就能直接复位状态代码一下子干净了很多也更好调。TKEEP是字节有效标记逐字节对应TDATA。它比 AXI4 的WSTRB特殊的地方在于TKEEP可以与TLAST配合表示最后一拍里有几个字节是有效的。比如一帧的宽度不是位宽整数倍时最后一拍必定有填充字节靠TKEEP把它们标出来。提示如果下游模块对TKEEP处理不当很容易出现图像最后几个像素偶尔变成随机值的现象。这类问题在仿真里如果数据量不够大往往复现不出来建议仿真时主动构造非整数倍的帧宽。4.3 工程里的常见搭配DMA 搬数据 Stream 做流水处理真实工程里最省事的架构通常是这样的AXI4 主设备通常是个 DMA 引擎负责把 DDR 里的数据搬进片上 FIFOFIFO 的输出接成 AXI-Stream后面串上一串处理模块处理完再接回另一条 AXI-Stream最后交给另一个 DMA 引擎写回 DDR。这套结构的好处是职责分离会算地址、会发突发的只有 DMA 那一边中间所有处理模块完全不用管地址、不用管突发、不用管响应只要关心TVALID、TREADY、TDATA就行。调试的时候也简单——哪一级卡住了看它上游TVALID和TREADY是不是一直一高一低就能定位。不过要注意一点DMA 引擎和 Stream 之间的 FIFO深度不能拍脑袋定。太浅反压频繁往回顶DMA 的有效带宽上不去太深占用 BRAM 资源还可能让延迟变大导致反馈环路出问题。我一般会先估算一级处理模块的最大处理延迟FIFO 深度取在这个延迟内上游可能灌进来的数据量再多留一点余量。5. 互联结构里的仲裁多主设备抢 DDR 时发生了什么工程一旦变大DDR 前面一定会接好几个主设备这时候就需要互联结构Interconnect和仲裁器。5.1 轮询、固定优先级、QoS 三种仲裁策略的取舍策略行为适用场景风险轮询Round-Robin各主设备依次获得授权各主设备重要性相当高优先级任务可能被平均掉固定优先级高优先级永远先服务有明确实时性要求低优先级可能被饿死QoS 加权按配置的权重分配带宽混合负载、需精细调控配置复杂调参成本高选哪种取决于你的业务。视频通路一般有硬实时要求一帧数据必须在下一帧到来前搬完那视频读通道通常给高优先级处理器取指虽然单次数据量小但延迟敏感如果被顶住太久软件跑起来会莫名其妙变卡。这里有个反直觉的点固定优先级不是越极端越好。如果你的最高优先级设备是突发很长、一次占很久的类型那它在服务期间会把其他主设备完全堵死低优先级设备的延迟会非常难看。比较稳妥的做法是把高优先级设备的突发长度限制一下让它频繁但短而不是偶尔但长。5.2 用一个仲裁器仿真说明饿死是怎么发生的我做过一个简化实验来验证饿死的条件两个主设备固定优先级A 的优先级高于 BA 连续发长突发。波形上看到的现象很有代表性B 的ARVALID拉高之后ARREADY一直是低。A 的突发接连不断中间几乎不留空隙。由于仲裁器是固定的A 每次有请求就给授权B 的请求就那么一直挂着。实测下来 B 在仿真里几十万拍都没拿到一次授权。这个实验说明固定优先级下只要高优先级设备的请求密度足够高、低优先级设备的请求率也足够高饿死是必然发生的不是概率问题。工程上的应对方法有两个一是给低优先级设备设置一个最大等待时间超时后强制给它一次授权二是把高优先级设备的突发长度调短、中间插入空闲周期给仲裁器留出轮换的机会。前者更精确后者实现更简单看你的项目复杂度选。顺便说一句很多厂商的互联 IP 里已经内置了这类防饿死机制你在配置界面里能看到仲裁模式和权重的选项。如果发现自己手写的仲裁器有这个问题有时候换成 IP 反而更省事。5.3 读写通道的乱序返回与 ID 的作用AXI 允许多个读请求同时在途outstanding从设备可以不按请求顺序返回数据。那主设备怎么知道收到的数据对应哪个请求靠ARID和RID。规则很简单相同 ID 的响应必须保持顺序不同 ID 的响应可以乱序。这是主设备设计里的一个关键决策点。如果你给所有请求都分配同一个 ID那就退化成严格按序返回虽然逻辑简单但会浪费乱序带来的效率。如果给每个请求分配不同 ID理论上最灵活但主设备需要有足够的能力去区分和处理乱序返回的数据否则就乱了。实际工程里的折中做法是给每个主设备一个固定的 ID 空间同一主设备内部再按功能比如读通道的描述符读和数据读分几个 ID。我一般会用两到四个 ID既能利用一定程度的乱序主设备侧的重排序逻辑也不会太复杂。还有一个容易忽略的参数是 outstanding 深度。它表示允许多少个请求同时在途。深度越大DDR 的带宽利用率通常越高因为控制器手里有更多请求可以调度。但深度受限于你内部的 buffer 容量。所以看到带宽上不去的时候先看看 outstanding 深度是不是设得太保守往往比调仲裁策略立竿见影。6. 实际工程调试从波形、断言到性能计数理论讲完最后说说怎么把这些东西真正调通。6.1 握手僵死类问题的排查顺序遇到上板挂死或者仿真跑到某一拍不动了我固定按这个顺序看第一步看哪一组通道的VALID高而READY一直低。这能立刻告诉你是哪一边在等。第二步看这个READY是被什么信号拉低的。追到源头通常是某个 FIFO 的full或者某个状态机的条件。如果源头是 FIFO 满说明下游消费能力不足问题在下游如果源头是状态机去看状态机为什么没往前走往往它也在等另一组通道。第三步看是不是循环等待。把所有在途请求列出来如果 A 等 B、B 等 C、C 等 A那就是死锁。最常见的组合就是我前面提的主设备把WVALID挂在AWREADY之后而从设备要等 W 才拉AWREADY。第四步看握手的最后一拍。有些挂死不是一直不动而是跑了很久之后停在某一拍。这种情况十有八九是某次握手成功之后指针没推进或者状态没跳转。对着波形数一下握手的次数和指针变化是否一致很快能定位。6.2 用 SystemVerilog 断言把握手规则写进仿真手工看波形效率低我现在的习惯是把握手规则写成断言仿真一跑就自动报错。// 规则一VALID 在握手完成前不允许撤销 property p_valid_stable; (posedge clk) disable iff (!rst_n) (m_valid !m_ready) | m_valid; endproperty assert property (p_valid_stable) else $error(VALID dropped before handshake!); // 规则二握手未完成期间数据必须保持不变 property p_data_stable; (posedge clk) disable iff (!rst_n) (m_valid !m_ready) | $stable(m_data); endproperty assert property (p_data_stable) else $error(Payload changed while stalled!); // 规则三WLAST 必须恰好出现 AWLEN1 次中的最后一拍 property p_wlast_count; (posedge clk) disable iff (!rst_n) (m_valid m_ready) |- (wlast (beat_cnt awlen)); endproperty这三条断言基本覆盖了我 80% 的握手类 bug。特别是第二条$stable那个断言帮我抓过好几次反压期间数据被组合逻辑改掉的问题这种问题在波形上极难看出来因为变化只在个别拍上。注意断言里用disable iff (!rst_n)时要确认你的复位是同步释放的否则复位期间可能出现误报。6.3 带宽估算从理论峰值到实测效率的差距在哪带宽估算这件事很多人只会算理论值然后发现实测差一大截就以为是 IP 有问题。其实差距都出在效率上。先算理论值。假设 AXI 时钟 150 MHz数据位宽 128 bit16 字节单向理论带宽150e6 × 16 2.4e9 字节/秒 2.4 GB/s实际能跑到多少取决于几个因素读写切换的开销、DDR 的行切换与刷新、突发之间的空隙、反压造成的等待。我实测过的经验值单向连续大块传输能到理论值的 70% 到 85% 就算不错了如果读写混合因为 DDR 需要频繁切换总线方向效率会掉到 50% 到 65%小规模随机访问最惨可能只有 20% 以下。所以当你发现带宽不够先判断是哪一类负载。如果是大块连续搬运还达不到 70%那去看是不是突发长度太短、outstanding 深度太小、或者中间 FIFO 深度不够导致频繁反压。如果是随机小访问那就得从架构上想办法——比如把访问合并成更大的块或者改成片上缓存。6.4 我在这些项目里踩过的几个坑第一个坑以为READY越快拉越好。我早期写从设备的时候喜欢让READY无条件为高觉得这样最快。结果遇到下游处理模块还没来得及准备好数据就被握手走了又丢掉。后来改成由下游的ready统一驱动正确性优先。第二个坑在握手成功的同一拍去改环境。比如m_valid m_ready那一拍我同时去复位了某个计数器。后来发现在某些从设备上那一拍数据还在被采样我提前动了状态导致后续判断错位。稳妥做法是握手成功只做把状态往前推这件事别在同一拍做无关的清理。第三个坑忽略复位对握手的清理。有一次仿真能过、上板偶发挂死原因是复位释放的时候我某个模块的VALID寄存器被清成了 0但对应的 FIFO 指针没清两边状态不一致。后来我把整个 AXI 相关模块的复位做成统一的全局同步复位问题消失。第四个坑突发长度和 FIFO 深度不匹配。我设了 256 拍的突发中间 FIFO 只有 64 深理论上 FIFO 会在突发进行到一半时满反压必须能把上游顶住。但我的上游当时没做反压处理结果数据直接被丢弃。这类问题在波形上表现为某些位置的数据和预期不符如果不去对齐数据流很难发现。说到底AXI 的难点不在协议本身有多少条规则而在于这些规则组合起来之后产生的状态空间很大异常路径比正常路径多得多。我的体会是与其在出问题之后拼命看波形不如在最开始写代码的时候就把那几条铁律和检查表贴在旁边一条一条对照着写。握手写对了后面的突发、仲裁、带宽这些才有意义——握手不对其他全是白搭。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →