ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

AXI总线仲裁器设计全解析:从算法策略到协议约束与调试实战

AXI总线仲裁器设计全解析:从算法策略到协议约束与调试实战 AXI总线仲裁器这个话题说实话是很多做SoC集成和FPGA开发的同事容易忽略又不得不面对的东西。刚开始接触AXI的时候可能大家都会把精力放在valid/ready握手、outstanding传输、乱序返回这些协议细节上但一旦系统里挂了多个master——比如CPU、DMA、以太网MAC、USB控制器——你会发现总线仲裁器的设计直接决定了整个系统的带宽分配和延迟表现。这篇是“AXI协议理解”系列的第一篇专门把仲裁器这块拆开讲清楚它到底解决什么问题、常用的仲裁策略有哪些、AXI协议给仲裁器设计埋了哪些坑、以及我自己在实际项目里踩过的雷。适合刚入门AXI的FPGA/IC工程师也适合做了几年集成但没仔细抠过仲裁细节的朋友。1. 为什么AXI总线需要仲裁器1.1 AXI总线的基本结构与仲裁器位置AXIAdvanced eXtensible Interface是ARM AMBA协议家族里的高性能总线协议它的核心特点是通道独立读地址通道AR、读数据通道R、写地址通道AW、写数据通道W、写响应通道B各自独立工作。这种设计让读和写可以同时进行也让outstanding未完成传输的数量可以做得很大从而隐藏内存访问延迟。但通道独立带来一个直接问题多个master同时发起访问时谁先谁后比如CPU要读DDR里的指令DMA要往DDR写一帧图像数据以太网控制器也要读取描述符这几个请求几乎同时到达。从设备比如DDR控制器只有一个地址端口同一时刻只能接收一个请求这时候就必须有一个仲裁器来决定放行哪个请求。仲裁器就位于master和slave之间在多master互联的场景下它承担着“交通警察”的角色。在典型SoC里仲裁器通常不是独立存在的而是集成在互联矩阵Interconnect或者NICNetwork Interconnect里。比如ARM的NIC-400、NIC-450或者Xilinx的AXI Interconnect IP内部都包含了仲裁逻辑。但理解仲裁器本身的设计思路比直接调IP更有价值——因为很多性能问题归根结底是仲裁策略选择不当造成的。1.2 没有仲裁器会发生什么总线冲突的现场要是没有仲裁器多个master同时驱动地址总线和数据总线结果就是总线冲突两个master的电平互相打架协议层面完全失效。可能有人会说那让每个master分时使用总线不就行了但问题在于AXI是通道独立的不同通道的仲裁需要分别做。比如master A的写地址可以仲裁通过master B的写数据也需要仲裁通过如果两者割裂处理数据通道和地址通道就可能对不上。还有一个容易被忽略的点AXI并没有规定仲裁应该放在哪个通道上。按照协议master发出请求后slave通过valid/ready握手来接收。仲裁器介入的时机可以是地址通道决定谁的命令先被slave接收也可以是数据通道决定谁的数据先传输。更常见的做法是在地址通道上做仲裁因为地址通道的请求头包含了完整的传输信息地址、突发长度、ID等仲裁器只需要决定这些请求头谁先通过数据通道就顺着走就行了。我之前见过一个实际的项目工程师把仲裁放在了数据通道上结果出现了两个master的写数据在总线上一半一半交错传输的诡异现象协议仿真能过但上板之后DDR的数据就是错乱的。后来追查下来问题就出在仲裁位置放错了写数据通道一旦被分片写响应和写数据之间的配对关系就乱了。这个案例后面会细说。1.3 仲裁器在SoC设计中的实际部署仲裁器的部署位置通常有三种。第一种是点对点互联一个master连一个slave一般不需要仲裁器除非这个master内部有多个请求源。第二种是共享总线式的多master互联仲裁器集中管理所有master对单个slave的访问这种结构简单但容易成为性能瓶颈适合对带宽要求不高的系统。第三种是交叉开关矩阵Crossbar每个slave端口都有一个独立的仲裁器多个master可以同时访问不同的slave这时仲裁器分布在交叉开关的每个输出端口上。在Xilinx的AXI Interconnect IP里打开配置界面就能看到每个slave端口对应的仲裁策略选项比如Round Robin或者Priority。默认的Round Robin通常不会出大问题但如果你有实时性要求高的master就必须按优先级来配否则延迟抖动会非常明显。这个选择需要结合具体业务来确定不能一张配置单打天下。2. 常见的AXI仲裁算法与选型分析2.1 固定优先级仲裁简单但饿死风险高固定优先级仲裁Fixed Priority是最直觉的做法给每个master分配一个优先级编号仲裁时直接选出编号最高的请求。这个方案逻辑最简单一个比较器加一个多路选择器就能搞定占用资源极少时序也容易收敛。但它的缺点和优点一样明显低优先级master可能永远得不到服务这就是所谓的饿死starvation问题。举个实际例子CPU和UART的DMA访问同一个DDR控制器如果CPU永远是最高优先级而CPU又持续有缓存未命中的读请求UART的DMA可能几毫秒都拿不到总线。对于UART这种需要实时从FIFO搬运数据的设备来说几毫秒的空窗就意味着FIFO溢出数据直接丢失。我之前做过一个带AXI UART16550核的项目默认配置里CPU是最高优先级低优先级里挂了一个UART DMA通道。调试串口经常出现乱码刚开始怀疑是波特率不匹配后来抓了总线的波形才发现UART的读请求经常被CPU的突发读插队DMA服务间隔不稳定FIFO发生了下溢。改成UART DMA的优先级高于CPU之后问题当场就消失了。所以固定优先级仲裁不是不能用而是要用对地方。如果高优先级master的请求频率有上限或者低优先级master本身容忍大延迟那固定优先级反而是最优选择因为它的延迟是可预测的资源耗费也最低。2.2 轮询仲裁保证公平性的基础方案轮询仲裁Round-Robin是解决饿死问题最直接的策略多个master的请求排成一个圈仲裁指针每拍移动到下一个master有请求就放行没请求就跳过。这个方案保证了所有master在长期统计下能获得大致相等的总线访问机会。设计上有两个变体需要注意。一种是固定顺序轮询指针永远按0→1→2→3的顺序移动无论当前master是否有请求。另一种是跳过式轮询指针会跳到有请求的master减少无效轮询周期。后者的效率更高但实现上需要额外的查找逻辑时序要稍微紧一点。不过轮询仲裁也有它的软肋它对延迟不敏感也没有优先级概念。如果某个master对实时性要求很高比如视频解码器需要每帧固定的带宽轮询给出的只是统计学意义上的公平延迟抖动依然存在。这时候就需要加权轮询或者QoS机制来调节。在实际工程里一个常见的折中方案是“轮询为主优先级为辅”默认所有master按轮询仲裁但每当某个高优先级master连续等待超过设定周期时临时把仲裁结果切给它。这种自适应方案兼顾了公平和实时不过状态机复杂度会明显上升。2.3 加权轮询按带宽需求分配总线时间加权轮询Weighted Round-Robin解决的是“公平但不够用”的问题。比如DMA需要70%的带宽CPU只需要30%简单的轮询会各分50%DMA的带宽就欠了。加权的思路是给每个master分配一个权重值权重大的master在每轮仲裁中获得更多的服务次数。实现上有两种常见做法。第一种是令牌式每个周期给所有master发放与权重成正比的令牌数请求消耗令牌令牌多的master自然获得更多服务。第二种是分槽式把仲裁周期切成与总权重等长的槽位按权重比例分配槽位给各个master。第一种方式更灵活第二种更容易做到严格周期。加权轮询的难点在于权重怎么定。理论上可以根据系统带宽模型来计算但在实际SoC里主master的带宽需求往往是动态的——比如CPU的DDR访问量随负载波动非常大。如果权重是固定的要么浪费带宽要么仍然不够用。所以更先进的仲裁器会引入动态权重调整这已经接近QoS的思路了。2.4 基于QoS的仲裁AXI4标准里的QoS接口AXI4协议在ARID和AWID之外增加了一个QOS信号宽度是4位表示每次传输的服务质量等级。从0到15数值越大优先级越高。这给仲裁器设计提供了一个标准化的优先级接口仲裁器不需要知道master是谁只需要看QOS值就能做仲裁决定。QOS信号的价值在于它把“谁的请求重要”这个决策从硬件架构层搬到了软件可配置层。系统软件可以在运行时动态调整某个master的QOS值从而改变总线服务的偏向。比如视频播放时提高显示控制器的QOS保证帧不撕裂后台跑基准测试时降低CPU的QOS给存储控制器的维护操作腾带宽。不过QOS只是一个提示信号AXI协议并不强制仲裁器必须响应它。我在设计仲裁器的时候通常会把QOS值映射为动态优先级再结合轮询机制做一个混合仲裁器优先级高的先服务相同优先级之间轮询。这种方式兼顾了灵活性和公平性是目前比较推荐的实现思路。3. AXI协议对仲裁器设计的特殊约束3.1 valid/ready握手与仲裁时机的选择AXI协议里每个通道的数据传输都通过valid/ready握手完成。发起方拉高valid表示数据有效接收方拉高ready表示可以接收两者同时为高时传输发生。仲裁器介入后实际上扮演了“接收方发起方”的双重角色对上游master来说是slave对下游slave来说是master。仲裁时机的选择直接关系到一个问题是等master的valid信号拉高之后再仲裁还是提前预判我的经验是对于地址通道通常等valid拉高后再仲裁就够了因为地址通道的请求不是每拍都有的仲裁器本身也要等请求到来才能做决定。但对于数据通道如果希望做到背靠背传输back-to-back预判是必要的——在最后一拍数据还没传完时就开始仲裁下一笔传输的数据通路。握手还有一个重要的特性一旦valid拉高发起方就不能撤销必须等到握手完成。这意味着仲裁器一旦向某个master给出了ready信号就必须完成这笔传输。如果这时发现仲裁错了——比如出现了更高优先级的请求——也不能反悔只能等当前传输结束。这在设计里叫“不可抢占”特性是AXI仲裁与普通CPU总线仲裁的重要区别。3.2 outstanding传输与ID管理的仲裁影响AXI协议允许master在未收到前一笔传输响应之前就发起后续传输这就是outstanding。这个特性极大提升了总线利用率但也给仲裁器带来了麻烦仲裁器不能只看当前一笔请求还要考虑master发出的请求队列深度。比如一个DMA控制器可以同时发出16笔读突发但如果仲裁器一笔一笔地处理每笔之间还要等DDR的响应返回DMA的outstanding能力就浪费了。正确的做法是仲裁器也维护一个与master对应的未完成传输计数只要计数没到上限就允许master连续发起新的请求。ID信号的管理更加微妙。AXI的ID信号用于标记传输的归属支持乱序返回。仲裁器在合并多个master的请求时必须为每个master的ID做映射避免两个master的ID冲突。比如master A和master B都用了ID0如果不做映射slave返回的数据就无法区分属于哪个master。我踩过一个ID映射的坑当时在做一个AXI to AXI Bridge两个master的ID都是4位宽度理论上直接级联就行。但其中一个master发起了乱序读另一个master的ID恰好和它重叠导致返回数据被Bridge分发到了错误的master。最后不得不给Bridge增加了一个ID remap机制把每个master的ID空间独立开才彻底解决。3.3 写通道仲裁的特殊性AW、W、B三个通道的配合很多刚开始接触AXI仲裁的人会犯一个错误只仲裁AW通道忽略了W通道。实际上写操作涉及三个通道AW写地址、W写数据、B写响应。如果AW和W的仲裁策略不一致就会出现地址通道选了master A数据通道却传着master B的数据结果这两个master的写操作互相串扰。为了保证写操作的正确性仲裁器通常需要把AW和W通道绑定在同一仲裁域里。也就是说当仲裁器决定为master A的写操作打开闸门时它的AW请求和W请求都必须被同时放行。这带来一个数据缓冲的问题如果下游slave暂时无法接收W数据仲裁器要把master A的写数据缓存下来这就涉及FIFO深度设计。AW和W还有一个同步问题AXI协议不要求W数据和AW请求严格同拍到达但要求所有W数据必须在对应的AW请求之后到达。仲裁器需要记录每个master的写请求状态确保不会出现AW还没放行W数据先到的情况。实际实现中最简单的策略是AW和W按同一个仲裁结果走仲裁为谁服务就把两个通道的闸门都打开。3.4 多master访问DDR时的死锁与屏障处理多master并发访问DDR时最头痛的问题是死锁。考虑一个场景master A正在对slave X做读操作master B正在对slave Y做写操作而这两个slave内部又相互依赖。仲裁器如果不加约束理论上就可能互相等待。AXI协议本身没有定义屏障Barrier机制但AXI4增加了屏障事务Barrier Transaction的概念当一个master发出屏障请求时它要求在此之前所有outstanding的传输都完成之后的新传输才能开始。仲裁器在收到屏障请求后需要暂时停止对这个master放行新请求直到它的所有未完成传输全部返回。系统级死锁的预防还需要考虑更宏观的因素——比如CPU核和DMA之间通过外设寄存器的握手逻辑。如果CPU在等待DMA完成中断而DMA又在等待CPU释放某个外设锁两边都卡在总线访问上系统就死锁了。这类问题通常靠超时计数器来兜底仲裁器里的每笔请求都应该维护一个超时标志超时后强制丢弃或上报。4. 实操从零搭建一个AXI仲裁器4.1 接口设计与仲裁状态机下面给出一个精简的AXI仲裁器设计思路重点展示地址通道的仲裁逻辑。这个设计假设有4个master端口每个端口都遵循AXI4协议仲裁器输出到一个slave端口。module axi_arbiter #( parameter ID_WIDTH 4, parameter ADDR_WIDTH 32, parameter DATA_WIDTH 64, parameter N_SLAVES 4 )( input clk, input rst_n, // 4个master端口的AR通道输入 input [N_SLAVES-1:0] ar_valid_i, output [N_SLAVES-1:0] ar_ready_o, input [N_SLAVES*ADDR_WIDTH-1:0] ar_addr_i, input [N_SLAVES*ID_WIDTH-1:0] ar_id_i, // 输出到slave的AR通道 output reg ar_valid_o, input ar_ready_i, output reg [ADDR_WIDTH-1:0] ar_addr_o, output reg [ID_WIDTH-1:0] ar_id_o );仲裁器的核心是一个状态机状态包括IDLE、ARB、WAIT_HANDSHAKE。IDLE状态检测是否有AR请求ARB状态根据仲裁算法选择winnerWAIT_HANDSHAKE状态等待slave的ar_ready握手完成后回到IDLE。如果采用轮询算法还要维护一个指针寄存器。// 轮询指针 reg [N_SLAVES-1:0] grant_ptr; wire [N_SLAVES-1:0] req_vec ar_valid_i; wire [N_SLAVES-1:0] masked_req req_vec ~(grant_ptr - 1); wire [N_SLAVES-1:0] grant masked_req ? (masked_req ~(masked_req - 1)) : (req_vec ~(req_vec - 1));上面这段是经典的总线仲裁轮询算法优先服务当前指针之后的最低有效位请求如果没有再从最低位开始找。这个写法简洁高效FPGA综合后只会占用很少的LUT。4.2 写数据通道的FIFO缓冲与背压处理地址通道仲裁只是第一步。为了让写数据W通道能够与AW通道解耦仲裁器内部通常需要为每个master设置一个写数据FIFO。master的数据先写入FIFO仲裁器按仲裁结果从对应FIFO读出数据送往slave。FIFO深度怎么选经验公式是FIFO深度 ≥ 最大突发长度 × 数据宽度 / 8以字节为单位如果max burst是16 beats数据宽度是64位8字节那FIFO深度至少要128字节。这里还要考虑burst类型和地址对齐的情况。实际项目中我一般会多留25%的余量因为AXI的W通道允许数据提前到达FIFO太浅会造成背压。背压Backpressure是AXI里一个重要的概念。当FIFO满时仲裁器必须拉低W通道的ready信号这相当于告诉master“我暂时收不了数据”。这个信号会一级一级向上游传递最终可能让DMA暂停搬运。如果背压处理不好就可能出现总线上某个master长时间占有数据通路、而其他master被饿死的现象。为了解决这个问题我给仲裁器的每个FIFO都加了一个almost_full信号当FIFO快满时就不再对该master进行仲裁。这个细节在实际调试中帮了大忙否则DMA在突发传输中途被插断恢复起来非常影响性能。4.3 读数据返回通道的仲裁与ID重映射读数据通道R的仲裁和写数据通道不同。R通道方向是从slave返回master仲裁器需要把从slave收到的读数据分发给对应的master。这里的关键是ID信号slave返回数据时带上ID仲裁器通过ID判断这笔数据属于哪个master。但如果多个master的ID空间重叠——比如都用了ID 0、1、2——仲裁器就必须做ID重映射。通常的做法是给每个master的ID增加高位前缀将master编号编入ID。比如master 0的ID保持原样master 1的ID最高位加1依此类推。slave端看到的ID就是唯一的返回时仲裁器根据高位前缀决定数据分发到哪个master再把前缀去掉。这种方案在Xilinx的AXI Interconnect里也采用了类似的机制叫做ID Width Adaptation。值得注意的是ID重映射会改变ID的总线宽度如果原设计里ID宽度是4位4个master映射后slave端ID可能需要6位。这会影响后续slave的设计务必提前规划。4.4 验证方法直接看时序图比什么都直观仲裁器设计的验证除了跑仿真外我还习惯抓取总线时序图来人工检查。重点看几个信号arb_grant当前仲裁胜利者、ar_ready地址通道握手、以及各master的ar_valid。抓时序图时最容易发现的问题是仲裁切换过快或过慢。仲裁切换过快一个master还没发完一笔突发请求就被切走会导致slave端收到碎片化的请求序列切换太慢则可能在其他master有高优先级请求时仍然占用总线。一个常见的时序图场景是master A的ar_valid持续拉高但ar_ready只在某些周期有效。如果仲裁器在ar_ready无效时切换了grant下一拍ar_ready有效时slave端看到的可能是master B的请求这时master A的请求就被“饿”了一拍。好的仲裁器设计应该在grant切换和握手完成之间插入一个同步逻辑确保grant只在握手完成后的空拍切换。这些细节都可以从波形上直观观察到。5. 线上调试中的常见问题与排查技巧5.1 问题速查表症状可能原因排查方法DMA传输数据错乱写数据通道仲裁与地址通道不一致抓W和AW通道波形对比仲裁结果UART/串口乱码低优先级master被饿死FIFO溢出检查仲裁策略提高UART优先级或加轮询总线延迟抖动大fixed priority下高优先级请求过于频繁改用RRQOS混合仲裁或增加权重读数据返回乱序错乱ID重映射错误多个master的ID没有隔离检查仲裁器的ID映射表系统卡死总线一直busyoutstanding计数溢出或死锁加超时计数器检查屏障处理带宽远低于预期仲裁周期太长FIFO背压频繁配置仲裁器预取模式增加FIFO深度5.2 实测案例AXI UART16550的DMA传输异常这个案例我印象特别深。项目里用了一个带AXI接口的UART16550核收发数据走DMA。DMA通过AXI总线访问UART的寄存器FIFO实际上就是AXI读写寄存器操作但请求频率不高——每次就4字节的读或写。系统里还有一个千兆以太网MAC其DMA会发起长突发读和写DDR。一开始UART在现代调试串口上偶尔丢字符没太当回事直到有一天从FPGA上采集到UART的DMA请求延迟超过了100微秒才意识到问题严重。排查过程是这样的先看仲裁器的grant信号发现UART的AXI读请求虽然一直有效但grant经常被以太网MAC抢走。仲裁策略是固定优先级以太网MAC的优先级高于UART。后来我改成了加权轮询给UART一个最低权重确保每个轮询周期至少服务一次。改完之后抓波形UART DMA请求的服务间隔稳定在2微秒以内丢字符问题彻底消失。这个案例告诉我们仲裁策略的设计不能只盯着高带宽设备低带宽但实时性要求高的设备同样需要照顾。从系统的角度看仲裁器应该是一个“按需分配”的机构而不是简单的“强者恒强”。5.3 AXI Quad SPI与DDR读写并发时的带宽分配另一个常见场景是AXI Quad SPIQSPI控制器和CPU同时访问DDR。QSPI需要周期性读写Flash速度不快但每次访问的延迟必须稳定因为Flash的时序窗口很窄——如果QSPI的读请求被DDR的长突发卡住太久Flash的读命令就可能超时。我在这类设计里通常的做法是给QSPI控制器分配一个独立的仲裁槽位即使它只有1/8的权重也能保证每个仲裁周期内至少服务一次。这样DDR的大块读写可以吃满剩余带宽而QSPI的访问延迟始终可控。另一种思路是让QSPI走专用的低延迟路径绕过主仲裁器。在SoC里这叫做侧信道sideband专门给对延迟敏感但带宽需求低的控制面通信使用。这个方案在复杂的SoC里经常看到不过会增加布线复杂度。选择哪种方案需要综合评估系统对延迟的容忍度和对布局面积的约束。5.4 验证过程中值得注意的边界情况仲裁器的验证最容易漏掉的是边界情况。我总结了几类必测的场景多个master同时发起请求且ID完全相同验证ID重映射的正确性。master在握手期间突然拉低valid这在协议里是违法的仲裁器应该能容忍并记录错误。outstanding计数达到上限后master继续发出请求仲裁器必须忽略多余的请求并反压。某个slave长时间不响应仲裁器应该能通过超时机制释放总线否则整个系统会被一个slave拖死。上电复位瞬间仲裁器指针初始值可能随机必须确保不会把grant输出到没有请求的master。这些边界场景如果只用随机约束仿真很难全部覆盖。我推荐的做法是在验证环境里直接定向写这些case每个case都配合波形来确认。对于超时机制还要额外验证超时时间参数是否可配置方便后期调试。6. 我对AXI仲裁器设计的一些心得做了几年AXI相关的IP集成和调试我对仲裁器最大的感触是它看似简单实则处处是权衡。你很难设计出一个在所有场景下都最优的仲裁器所以好的设计者一定是先搞清楚系统的真实需求再选择合适的仲裁策略。如果你正在做一个对带宽不敏感的小系统固定优先级仲裁足够用省资源、时序好、行为可预测。如果你面对的是多master、多业务的SoC那就要认真考虑轮询、加权和QOS的组合。没有万能的仲裁器只有适配具体场景的仲裁器。最后分享一个调试技巧遇到AXI总线相关的性能问题时不要一上来就看代码或者仿真先抓一段真实总线波形。波形能直观告诉你哪个master在占总线、哪个master在等待、仲裁器的grant切换频率是多少。很多时候波形的异常一眼就能定位问题比盲猜代码高效得多。下一篇文章里我打算聊聊AXI的outstanding和ID管理这套机制和仲裁器配合得好不好直接决定了多master系统的最终性能。到时候把乱序返回、ID宽度扩展、以及和DDR控制器交互的那些坑一起讲清楚。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表