ARTICLE DETAIL

资讯详情

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

AXI总线仲裁器设计全解析:从协议原理到RTL实现与工程调试

AXI总线仲裁器设计全解析:从协议原理到RTL实现与工程调试 做了快十年的SoC和FPGA设计我发现自己经常跟人解释同一个问题AXI总线仲裁器到底是什么它凭什么决定谁先用总线为什么有时候它会“饿死”某个主设备又为什么明明功能对了一上系统就死锁。这些问题的根源大多是没把AXI协议里的通道、握手机制和仲裁粒度想透。这篇文章是“AXI协议理解”系列的第一篇我打算把最常见的AXI总线仲裁器拆开讲清楚从为什么需要仲裁、协议层面有哪些约束到几种典型仲裁算法和可落地的设计细节最后附上我实际调试中踩过的坑。不管你是刚接触AMBA协议的FPGA开发还是在做自研总线互连的芯片工程师这篇文章应该都能帮你少走些弯路。总线仲裁这件事听起来像是“谁先来谁先用”的排队逻辑但在AXI的世界里远没那么简单。AXI本身是点对点的主从通道协议它并没有规定主设备之间怎么共用一个从设备。当芯片里有CPU、DMA、ISP、GPU这些主设备都要去读写同一个DDR控制器时必须有一个中间层来协调访问顺序这个中间层就是互连结构里的仲裁器。它解决的不仅是谁先用总线的问题还直接影响带宽分配、延迟上限、时序收敛和系统是否死锁。1. 为什么需要总线仲裁器——从一片“抢总线”的现场说起1.1 多主设备共享资源的必然冲突拿一颗典型的SoC来说CPU需要从DDR取指令和数据DMA要搬运图像帧ISP要写回处理结果硬件加速器也要频繁访问片外存储。它们对DDR和各类外设寄存器的访问需求几乎是同时持续存在的。而DDR控制器这类从设备接口通常只有一个总线在同一时刻只能让一个主设备占用地址通道发起事务这就产生了结构性冲突。这种冲突可以类比成一条单车道上的多辆车同时要进同一个车库门。车库里只有一扇门所有车都得排队这就需要一个调度员来决定谁先进入。AXI总线仲裁器就是这个调度员。它接收来自不同主设备的访问请求按照某种策略选择其中一个主设备把地址通道的“使用权”授予它等它完成一次事务后再调度下一个请求者。如果没有仲裁器多主设备同时驱动总线信号就会打架整个芯片会直接进入未定义状态。1.2 仲裁器在AXI互联结构中的位置AXI协议规定的是主从接口之间的握手关系但它不负责解决多个主设备对同一从设备的访问冲突所以仲裁逻辑必须放在互连结构中。常见的AXI互连结构有两种。一种是共享总线形式所有主设备和从设备挂在同一组总线上同一时刻只有一对主从能通信。这种结构节省面积但带宽有限适合主从数量都不多的场景。另一种是交叉开关Crossbar形式允许多个主设备同时访问不同的从设备比如CPU访问DDR的同时DMA可以访问另一个外设。Crossbar内部的每一个交叉点本质上都包含一个仲裁器用来决定哪个主设备能接通哪个从设备。所以仲裁器在AXI系统里的位置是在主设备侧的请求信号和从设备侧的地址通道接口之间。它负责从多个请求中选出一个然后把选中主设备的地址和控制信号送到从设备接口上。数据通道一般不需要仲裁因为一旦地址通道完成了事务配对读数据和写数据就沿着已经确定的通路传输。2. AXI仲裁必须懂的基础通道、握手与事务边界2.1 五个通道和它们的信号角色AXI4协议定义了五个独立通道分别是读地址通道AR、读数据通道R、写地址通道AW、写数据通道W和写响应通道B。仲裁器通常重点处理两个地址通道因为地址通道是事务的起点决定了哪个主设备的事务能进入从设备数据通道则跟着地址走。通道方向关键信号作用读地址AR主→从ARID, ARADDR, ARLEN, ARVALID, ARREADY发起读事务描述起始地址、突发长度、突发类型读数据R从→主RID, RDATA, RRESP, RVALID, RREADY返回读数据和读响应每个beat与RID对应写地址AW主→从AWID, AWADDR, AWLEN, AWVALID, AWREADY发起写事务描述写地址和突发属性写数据W主→从WIDAXI3, WDATA, WSTRB, WLAST, WVALID, WREADY传输写数据WLAST标记最后一个beat写响应B从→主BID, BRESP, BVALID, BREADY从设备返回一次写事务的完成状态一个关键点在ID信号上。AXI4允许主设备在不等待前一个事务完成的情况下连续发起多个outstanding事务每个事务用ID来区分。仲裁器必须正确管理这些ID否则从设备返回的读数据和写响应会对不上号。2.2 valid/ready握手与背压逻辑AXI所有通道的传输都建立在valid/ready握手之上。发起方拉高VALID表示本拍数据或地址有效接收方拉高READY表示本拍可以接收。只有VALID和READY同时为高这一拍才算真正完成传输。这里有几条铁律VALID信号一旦拉高必须保持到握手完成不能中途撤销READY可以在VALID之前拉高也可以在VALID之后拉高接收方不允许在VALID拉高之前等待READY但控制逻辑允许多数设计的接收端“预拉高READY”以降低延迟。如果从设备处理不过来就会拉低READY此时主设备的数据就停在总线上产生所谓的stall背压。背压信号直接从握手接口反向传递最终可能让一条数据通路上的FIFO不断积压这是AXI系统中最常见的性能瓶颈来源。在AXI流AXI-Stream场景下valid/ready握手更是核心命题。AXI-Stream取消了地址通道只保留数据通道广泛用于DMA、视频处理、数据采集等连续流场景。AXI-Stream FIFO的本质就是一个带背压管理的缓存满则拉低READY空则拉低VALID。很多做数字信号处理链路的工程师经常把大量时间花在排查valid/ready时序上就是因为握手打得不对数据流就会断断续续吞吐率直线下降。2.3 仲裁粒度事务级仲裁与outstanding事务AXI的一个事务可以是单拍传输也可以是突发传输突发长度由ARLEN/AWLEN决定AXI4的单次突发最长可以达到256拍。仲裁器做仲裁时通常以“整个事务”为粒度也就是说一旦选中某个主设备并接受了它的地址请求就需要让这个主设备的整个突发过程都占住对应的地址通道不能在两个beat之间把总线切换到另一个主设备。有人会问能不能做beat级仲裁让多个master的地址beat交错发出协议层面AXI4允许读数据交织但写通道在AXI4里已经取消了WID信号写数据交织必须依赖严格的顺序约束所以实际系统里写数据交织很少使用。读数据交织也需要额外的ID管理机制绝大多数互连结构还是选择简单可靠的事务级锁定一次突发完整结束后才释放仲裁权。还有一个必须理解的概念是outstanding事务。AXI主设备在发起一个事务后不必等待响应就能发起下一个事务。outstanding数量越多系统能够利用的指令级并行度就越高但同时给仲裁和响应排序带来的复杂度也越大。仲裁器必须结合ID信号识别哪些请求是同一主设备发出的新事务哪些是对已授权事务的重复请求否则就会出现重复授权或请求丢失的问题。3. 常见AXI仲裁算法从固定优先级到加权轮询3.1 固定优先级简单高效的“VIP通道”固定优先级仲裁的思路是给每个主设备分配一个固定的优先级编号所有请求同时到来时编号最大的请求者获胜。在RTL里实现一个固定优先级仲裁器非常直接通常就是一个比较器网络或查找表。这种方案的优点是延迟小、链路短、逻辑量少在时序紧张的模块里特别好用。缺点是低优先级主设备可能长时间得不到总线授权出现饥饿。实际芯片设计里固定优先级通常用在一些高确定性、高实时性要求的场合比如CPU的取指请求必须优先于普通DMA搬运或者某个实时控制器的访问不能被延迟。固定优先级仲裁器如果只有一个主设备连续发起请求高优先级主设备就会“插队”低优先级主设备会被无限延迟。我在项目里见过一种做法给低优先级主设备加一个超时机制一旦超过一定时钟数还没有获得授权就临时把它的优先级提到最高。这种“超时抬优先级”的方式在工程里很实用既保留了固定优先级低延迟的优点又避免了极端饥饿。3.2 轮询仲裁公平优先的“大家轮流”轮询仲裁Round-Robin的核心思想是让所有请求者轮流获得总线授权没有固定优先级。实现方式可以是一个循环计数器从最近授权的那个主设备的下一个开始扫描请求。如果1号主设备这次拿到了总线下次仲裁就从2号开始查询这样每个主设备都能等量获得访问机会公平性很好。轮询仲裁在AXI互连中非常常见尤其是多个同等重要的DMA引擎或加速器争抢同一块DDR带宽的场景。它保证每个主设备都能分到总线时间片避免了饥饿但代价是延迟不确定。一个高优先级请求可能刚好错过上一轮授权要等一整轮才能再次获得机会。对于时延敏感的接口轮询仲裁不一定是最佳选择。我实际写轮询仲裁器时喜欢用“指针式”状态机一个grant指针指向最近一次授权的master编号仲裁时从grant指针1开始查找第一个有效请求。如果请求是热编码的可以用比较器和移位搞定如果请求是二进制编码就转换成独热码再操作。实现上要注意一个细节如果轮询到最后一个请求者都没有命中要正确处理回绕到0号master的逻辑别把仲裁器做成“轮不到第一个”的bug形态。3.3 加权轮询与动态带宽分配纯轮询的问题在于它把带宽平均分配但真实系统里不同主设备对带宽的需求往往不一样。比如DMA搬运视频数据可能需要60%的总线带宽CPU取指只需要10%外设寄存器访问只需要5%。这时候加权轮询Weighted Round-Robin就能派上用场。加权轮询给每个主设备分配一个权重值表示它在一次完整轮询周期中可以连续获得授权的次数。实现时可以给每个master设置一个饱和度寄存器授权一次就减一减到零后跳过该master直到所有master的权重用完再开启新一轮。通过调整权重工程师可以精确控制不同主设备之间的带宽配比。加权轮询的进阶版是动态权重调整根据实际带宽利用率和缓冲水位动态改变权重值。比如DMA的FIFO快满了就临时提高它的权重防止数据溢出视频处理模块的输入缓冲空了就提高它的优先级避免画面卡顿。这种动态调度算法在大型SoC中应用较多但设计复杂度也明显上升需要仔细验证没有某种工作负载组合会让某个主设备长时间得不到授权。3.4 仲裁算法横向对比算法实现复杂度公平性延迟确定性带宽分配典型适用场景固定优先级低差低优先级可能饥饿高高优先级延迟稳定无法精确控制CPU实时取指、关键控制通路轮询低好所有master等概率低延迟随负载波动均分不适合差异化多个对等DMA、加速器加权轮询中中按权重分配中可量化配置视频/图像处理、多业务流量混合动态仲裁高取决于算法中低实时调整复杂SoC、多核处理器互连选择哪种算法没有标准答案关键看你的系统约束是带宽优先、延迟优先还是公平优先。我最常做的选择是集群内部有严格实时性要求的走固定优先级加超时保护多个对等的带宽型主设备走轮询或加权轮询具体的权重根据系统带宽模型计算后配置。4. 仲裁器的典型实现方式与关键细节4.1 请求-授权模型与AXI握手信号映射AXI仲裁器的内部模型可以抽象成“请求-授权”Request-Grant。每个主设备周期性地把ARVALID或AWVALID拉高表示有请求仲裁器根据当前授权状态和仲裁算法产生一个grant信号告诉某个主设备“你被选中了可以继续完成这一拍握手”。映射到AXI协议上仲裁器在从设备侧扮演主设备角色在主设备侧扮演从设备角色。对于主设备发来的ARVALID仲裁器要产生对应的ARREADY对于主设备发来的AWVALID仲裁器产生对应的AWREADY。ARREADY拉高的条件就是“该主设备的请求被仲裁选中且从设备侧接口可以接收地址”。所以grant信号和ARREADY/AWREADY信号在时序上是紧密绑定的。实现时的关键点是注册还是组合。纯组合仲裁的延迟很短但会产生比较长的组合路径。寄存器输出grant的仲裁器时序更干净但会引入一拍的仲裁延迟。AXI允许地址通道多等一拍代价只是发起方多等一个周期现代高频设计里更愿意用寄存器输出来换取时序收敛。4.2 传输期间锁定grant防止burst被拆散仲裁器授权一次通常对应一个完整AXI突发事务。地址通道ready拉高、地址被采样的那一刻仲裁器的grant就进入锁定状态不能再切换到另一个master。只有当本次突发完全结束也就是读数据通道收到最后一个RLAST或者写数据通道发出WLAST且收到写响应之后仲裁器才能释放grant开始新的仲裁。这里有设计上的取舍。一种做法是在地址通道授权后就锁死grant直到从设备侧返回事务完成信号比如读侧RLAST写侧BRESP。另一种做法是简化处理地址通道授权后延迟固定拍数再释放grant适用于单次突发长度固定的场景。固定突发长度的做法实现简单但灵活性差如果主设备发起的是长突发会有问题。我在实际项目中遇到过grant提前释放导致的严重bugAR通道先授权给master Amaster B紧跟着发起另一笔事务仲裁器错误地切换过去结果从设备依据ARID把读数据返回给AA收到的第一笔数据还没处理完第二笔数据又进来了整个读数据通道乱套。后来我们在设计规范里明确规定读事务必须锁定到RLAST返回后才释放grant写事务必须锁定到BRESP返回后才释放grant。4.3 一个简洁的轮询仲裁器RTL示例下面给出一个四路输入轮询仲裁器的SystemVerilog简化实现重点展示仲裁核心逻辑。接口设计省略了AXI全通道只保留请求、授权和轮询指针部分。module rr_arbiter #( parameter NUM_MASTERS 4 ) ( input logic clk, input logic rst_n, input logic [NUM_MASTERS-1:0] req, // 地址通道请求热编码 output logic [NUM_MASTERS-1:0] grant // 授权输出热编码 ); logic [$clog2(NUM_MASTERS)-1:0] ptr_q; // 轮询指针 logic [NUM_MASTERS-1:0] grant_c; // 组合授权结果 logic any_req; // 轮询仲裁从 ptr_q 下一个位置开始找第一个有效请求 always_comb begin grant_c 0; for (int i 0; i NUM_MASTERS; i) begin int idx; idx (int(ptr_q) i 1) % NUM_MASTERS; if (req[idx]) begin grant_c[idx] 1b1; break; end end end assign any_req |req; assign grant any_req ? grant_c : 0; // 轮询指针在每次仲裁完成后更新 always_ff (posedge clk or negedge rst_n) begin if (!rst_n) ptr_q 0; else if (any_req) ptr_q ptr_q 1; end endmodule这几行代码是轮询仲裁的核心组合逻辑从当前指针的下一个位置开始循环查找置位的请求位找到就输出对应grant。下面简单分析一下这个实现的关键点。第一指针更新的时机是每拍只要有请求就自增而不是等到整个burst结束再更新所以这个版本适合做单拍传输的仲裁如果要扩展成burst级仲裁需要配合事务完成信号来更新指针。第二组合路径包含req到grant的级联比较链master数量增多时延迟会变差8路以上建议分两级或者用查找表优化。第三grant信号必须与AXI地址通道的ready做联动真实使用时不会直接把grant当ARREADY输出还需要考虑下游从设备是否ready。4.4 工具选型自研RTL还是使用现成IP如果你是做FPGA开发大多数情况下不需要自己从零写AXI仲裁器。Xilinx的AXI Interconnect IP和Intel的AXI Bridge IP都内置了成熟的仲裁逻辑图形化界面里可以配置主从端口数量、仲裁策略、寄存器切片和FIFO深度。用现成IP的好处是经过充分验证协议兼容性好时序也优化过适合快速搭建系统原型。但自研仲裁逻辑也有不可替代的场景。第一你需要精确控制延迟和带宽分配现成IP的仲裁算法不透明权重配置有时无法达到预期。第二你在做ASIC后端实现必须拥有RTL。第三你只在一个小模块内部使用轻量级仲裁引入完整AXI Interconnect IP反而浪费面积和功耗。在这些情况下写一个简洁的仲裁器就是合理的。自研仲裁器最需要注意的是协议完备性和验证充分性。刚写完的仲裁器光靠波形调试很难覆盖所有握手组合我建议配合SystemVerilog断言做时序检查重点检查grant与valid/ready的关系、burst锁定期间的切换行为、以及ID匹配的正确性。5. 实际设计中的常见坑与排查5.1 死锁多主设备、多outstanding的循环等待AXI仲裁器引入后最常见的严重问题就是死锁。典型场景是这样的master A正在等待master B释放某个资源而master B想访问master A当前占用的总线通道两个master互相等待谁都无法前进系统挂死。有人会问AXI事务本身不等待其他master完成怎么会死锁问题往往出在系统层次。比如master A发出一个读请求后它的下一步计算需要依赖master B的数据master B发出一个写请求但写数据通道上还堵着master A之前发起的写数据未完成B的请求又排在A后面。一旦某些中间缓存设计得不合理就形成了环形等待。此外多通道联合仲裁器如果对读通道和写通道分别独立仲裁也可能出现读和写之间的依赖循环。排查死锁的常用手段是系统级仿真加断言监控把所有未完成事务打上时间戳超过阈值仍未完成就打印警告。更简单的方式是在设计阶段就遵守一条规则任何master在发起一笔事务后必须在等待响应的同时仍然能够释放自己占用的通道资源不要让一个master持有通道不放的同时还等待另一个master的动作。5.2 响应错配与ID丢失AXI支持outstanding事务后从设备的读数据返回顺序可以和发起顺序不一致。仲裁器如果把不同master的事务混在一起却没有正确管理ID就会出现数据错配。最典型的错误是master A和master B都用ID0发起读请求仲裁器没有给不同master的ID做扩展加扰从设备返回的数据就无法区分归属。解决办法有两种。一种是在仲裁器的输出侧给每个master的ID字段添加不同的前缀比如master0的事务ID保持原IDmaster1的事务ID在最高位加1这样即使在切片后所有master的原始ID相同全局ID也不会冲突。另一种是仲裁器内部实现一个ID映射表每次授权新事务时分配一个唯一ID响应返回时根据映射关系翻译回原ID。第二种做法更通用但需要额外逻辑和存储空间。5.3 时序收敛与仲裁延迟优化仲裁逻辑位于多个master请求汇聚的地方组合链路往往很长。尤其是超过16路输入的仲裁器单次比较链可能超过20级逻辑在高速时钟下很难收敛。我在一个400MHz的AXI互连设计中轮询仲裁器的request-to-ready路径成了关键路径怎么优化都不达标。后来用了两级仲裁解决第一级把16个master分成4组每组内部先用一个快速仲裁器选出组内胜出者第二级把4个组胜出者再仲裁一次得到最终grant。这样比较链路从16路缩短到44路逻辑深度明显下降。另一个常用手段是提前仲裁在当前事务的倒数第二个beat就启动下一次仲裁计算把仲裁延迟藏在数据传输过程中等到当前事务一结束就可以立刻输出新grant不产生气泡。这种技术在大型互连IP里很常见。5.4 常见问题速查表现象可能原因排查/解决建议某个master始终拿不到总线优先级配置错误请求信号未正确同步仲裁器回绕逻辑bug打印grant和req波形检查算法配置核对轮询指针范围偶发性数据错乱ID冲突grant切换过早数据通道选通错误检查全局ID唯一性确认burst结束信号与grant释放时序系统挂死总线长期busy多通道依赖死锁FIFO满且READY拉低无法恢复增加超时看门狗检查死锁环路修正通道释放条件实际带宽远低于理论峰值仲裁切换产生气泡背压连锁反应增加寄存器切片启用提前仲裁检查FIFO深度是否过小时序不收敛仲裁链路过长多路请求汇聚在同一条路径分组仲裁插入流水级调整仲裁算法降低比较链深度6. 写在最后仲裁器设计中的个人体会跟AXI仲裁器打交道这些年我最大的体会是仲裁算法本身并不复杂复杂的是跟协议、时序和系统行为“咬合”在一起的细节。纯轮询仲裁十几行RTL就能写完但要让它在真实的AXI系统里既不丢请求、不乱配ID、不死锁、还能收敛时序需要投入的验证和调试精力远超写代码本身。所以我建议刚入手的工程师一定要先吃透AXI的通道、握手机制和事务边界再设计仲裁器否则很容易在调试阶段被各种“看起来不可能”的问题折磨。最后再分享一个我常用的技巧调试仲裁器问题时不要只盯着grant信号看要同时对比主设备发出的请求信号、地址通道的valid/ready握手波形和从设备侧返回的响应。仲裁器只是整个互连环节的一环很多问题看起来出在仲裁实际上源头在相邻模块的FIFO深度、时钟域同步或者ID管理上。把波形时间轴拉大把相关通道都放出来对比问题往往一眼就能找到。这个系列的下一篇我打算接着讲AXI互连结构中读数据返回顺序的处理和ID管理实践到时候再继续聊。
返回列表