ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AXI总线死锁之AW-W依赖:成因、定位与RTL防护

AXI总线死锁之AW-W依赖:成因、定位与RTL防护 1. 先搞清楚死锁AXI总线不是数据库也会卡死干验证或者做SoC集成的朋友应该都经历过这种画面仿真跑到某个点时钟还在翻波形里的valid全部高悬ready全部纹丝不动整条总线像被人按了暂停键。抓下来一看AWVALID、WVALID、AWREADY、WREADY这几根信号排列组合出一个死局最常见的元凶之一就是AXI总线死锁里的AW-W依赖。这篇文章就把这个场景掰开揉碎讲清楚它怎么产生、怎么复现、怎么定位以及RTL里怎么防、验证里怎么查。AXI总线死锁听起来像是个“协议小概率事件”但只要系统里有多个Master、多个Outstanding事务、或者经过一层带内部FIFO的桥接逻辑它就会从理论变成现实。最容易踩到的坑是大家把“AXI允许outstanding”理解成“随便发多少个都不会卡”结果忽略了一个事实从设备和互连逻辑里的FIFO深度、地址槽数量、状态机处理能力都是有限的。当这些有限资源遇上AW通道和W通道之间的依赖死锁的四个条件就凑齐了。1.1 死锁四定律在AXI里长什么样死锁不是数据库和线程并发编程的专利。操作系统课上讲的那四个必要条件放在AXI总线上照样成立互斥、持有并等待、不可抢占、循环等待。在AXI场景里“资源”可以是一个地址槽、一条写数据FIFO、一个内部事务表项甚至是一段W通道的调度权。互斥的意思是这些资源同一时刻只能被一个事务占用持有并等待是一个Master已经占住了AW地址槽但还在等W通道让出不可抢占是AXI的valid/ready握手一旦没有完成谁也没法强行把数据塞进从设备循环等待就是A事务在等B事务占的资源B事务又在等A事务占的资源。只要这四个条件同时成立总线就必然变成一潭死水。很多工程师第一次接触死锁是在数据库或者多线程编程里觉得锁表、互斥量这些东西离硬件很远。其实从系统角度看去AXI总线上的死锁和数据库行锁死锁是同一套模型资源被多个请求方竞争每个请求方都攥着自己已经拿到的资源不肯放最后谁也推进不下去。理解了这层共性后面看波形和画等待图的时候就会顺手很多。1.2 写通道里的“AW-W依赖”到底指什么AXI写操作有三条通道AW发地址W发数据B回响应。从架构上看这三条通道是独立握手的所以Master可以先发AW再发W也可以在特定实现下把W数据和AW相对独立地推进。协议的灵活性给性能带来了好处也给死锁埋了引线。所谓AW-W依赖指的是写地址通道是否能够继续推进取决于写数据通道上某个事务的数据是否已经被接收。最常见的实现是从设备内部只有一个写事务地址槽或者一个事务表项AW进来之后从设备把它记录在案然后必须等对应的W数据全部到齐内部写状态机才能把这个事务清空空出来的地址槽才能接收下一个AW。换句话说AW通道能不能向前走本质上被W通道的完成情况锁住了。这种设计从单笔事务看没什么问题先有地址再有数据顺序合理。但一旦系统里有多个Master、多个Outstanding事务W通道又不是专门给同一笔事务服务的就可能出现“AW在等WW又在等另一个事务的AW”的闭环。这就是我们把这类死锁统称为AW-W依赖型死锁的原因。2. AW-W依赖型死锁的完整推演光说定义不够直观我直接搭一个最小复现模型。这个模型我至少在两个真实项目里见过等价版本每次根因都一样从设备把地址槽的释放条件和W数据的接收绑死而W通道又被另一个Master的突发数据长期占据。2.1 一个最小复现模型两个Master、一个从设备、一个地址槽假设系统里有两个MasterM0和M1它们都向同一个从设备S发写事务。从设备S的写通道内部资源如下只有一个写事务地址槽地址槽被占住后AWREADY不会再为新的AW拉高地址槽要等当前事务的W数据被接收、内部状态机清空后才释放W通道仲裁采用严格优先级M1优先级高于M0从设备内的写数据FIFO非常浅只能暂存少量数据且必须拿到对应AW才能决定数据往哪个目标去。第一眼看上去这不过是一个“outstanding能力为1”的从设备。问题是M0和M1并不知道这个限制它们都认为AXI协议允许自己同时发多笔写。于是总线就按照下面的节奏一步步走进死锁。2.2 一步一步进入死锁我用一个表格记录这个死锁过程的每一步方便你在调试时对着波形找步骤M0 行为M1 行为从设备/互连状态1发出AW0握手成功还没动作地址槽被AW0占用等待W02准备好W0等待W通道授权发出AW1但AWREADY拉低AW1停在总线上地址槽不释放AW1无法进入3W0一直等W通道W1突发被W通道仲裁选中开始传输W通道被W1占用从设备数据FIFO开始接收W14W0继续等W1继续传直到从设备数据FIFO写满数据FIFO满WREADY拉低W1也无法前进5等WREADY变高等从设备消费W1而消费W1需要AW1从设备等W0完成释放地址槽W0又被W通道挡住走到第5步总线实际上已经冻住了。从表面上看起来每一方都在等一个“很正常”的条件但所有条件互相咬死谁都不会先松手。2.3 循环等待的拆解把上面的等待关系画成一张图会更清楚M0.W0 -- 等待 W 通道 W 通道 -- 被 M1.W1 占用 M1.W1 -- 等待 从设备 WREADY 从设备 WREADY -- 等待 数据 FIFO 空位 数据 FIFO 空位 -- 等待 AW1 AW1 -- 等待 地址槽释放 地址槽释放 -- 等待 W0 完成这个依赖链正好是一个环W0等通道通道被W1占着W1又从设备数据FIFO满被卡住数据FIFO要等AW1来消费AW1要等地址槽释放地址槽要等W0完成W0又回到了等待通道。这就是教科书意义上的循环等待只不过资源从数据库锁变成了AXI的通道和FIFO。同样的道理也适用于另一个方向的依赖也就是W先到、从设备必须先等AW才能接收数据。如果AW通道因为别的事务的W卡住而W通道又在等AWREADY最后也会形成等价的死锁环。所以说AW-W依赖不是单指某一种状态机写法而是指地址通道和数据通道之间互相钳制的这一类问题。3. 实际调试中怎么把这种死锁揪出来伟哥说得好遇到死锁别慌先抓波形。AXI死锁的特征其实非常明显时钟还在跑所有通道都处于valid和ready僵持的状态而且这个状态能持续几百甚至几千个周期不动。只要看到这种情况十有八九是死锁而不是单纯性能问题。3.1 从波形里认出“僵硬”的握手拿到波形后先把关键的写通道信号拉出来AWVALID、AWREADY、WVALID、WREADY、BVALID、BREADY。一个典型的AW-W依赖死锁快照长这样一个Master的AWVALID为1AWREADY为0它的AW已经被从设备拒之门外很长时间另一个Master的WVALID为1WREADY为0它的W数据被从设备的WREADY卡住再往前翻能看到最早一个AW已经握手成功但对应的W数据迟迟没有完成所有Master的write outstanding计数都停在某个值既不变多也不变少。这种状态和普通拥塞最大的区别是“完全没有周期级的抖动”。正常拥塞下即使整体吞吐低总会有某个通道偶尔握手成功而死锁状态下所有相关握手信号像焊死了一样一个周期都不动。看到这种画面基本就可以进入根因定位了。3.2 画一张等待图别靠猜定位死锁最忌讳上来就改代码或者随便调仲裁优先级。正确做法是把当前系统里所有写事务的状态列出来画一张等待图。具体操作分四步第一步列出每个Master当前还没收到B响应的写事务记录每笔事务的AW是否已握手、W是否已开始、W是否已结束第二步去从设备侧看内部资源占用情况比如地址槽被哪个事务占着数据FIFO里存的是哪个事务的数据第三步看互连逻辑的仲裁状态W通道当前在服务哪个Master的哪笔事务第四步把这些信息整理成“谁在等谁”的依赖边。等你把依赖图画完如果发现它能绕成一个环死锁结论就成立了。这个方法看着笨但在复杂SoC里特别管用。很多死锁问题看起来像“某个Master卡住了”其实问题根本不在那个Master身上而在另一个Master的W突发把整个通道占死了。3.3 数据库死锁、线程死锁同样的模型不同的马甲AXI总线死锁、数据库死锁、线程死锁本质上都是资源分配不当导致的循环等待。我在调试AXI死锁的时候经常直接借用并发编程里的术语只不过把“锁”换成“地址槽”和“W通道授权”。类型典型资源持有者等待对象常见解法数据库死锁行锁、表锁事务另一个事务持有的锁锁顺序、超时回滚、死锁检测线程死锁互斥量、条件变量线程另一个线程持有的锁锁顺序、trylock、层级锁AXI总线死锁地址槽、数据FIFO、W通道授权Master/从设备状态机另一个事务占用的通道资源限制outstanding、解耦AW/W释放条件、公平仲裁这张表不是生搬硬套。数据库里两个事务互相等锁和AXI里两个Master互相等通道资源从检测思路上完全一致找到环打破环。数据库死锁有超时回滚硬件死锁没有“回滚”这个选项一旦总线冻住整个系统只能复位重启。所以硬件上更依赖在设计阶段就把死锁条件拆掉。4. 堵住AW-W依赖从协议、RTL到验证的完整打法知道了死锁怎么形成预防思路就清晰了要么让AW和W不互相等要么保证在资源有限的情况下不会形成循环等待。下面从不同层面讲我实际用过的做法。4.1 协议与架构层面的约束架构上最容易犯的错误是觉得AXI协议允许outstanding就无脑让Master往死里发。实际做系统集成的时候一定要先确认从设备和互连到底支持多少个outstanding写事务。如果某个从设备的地址槽必须等W数据完成才能释放那么这个从设备实际outstanding能力就是1所有OS看不到这一层的Master都会踩雷。我在项目里定过一个规矩凡是内部有“AW接收依赖W完成”的模块对外必须把这个限制暴露出来要么通过outstanding count配置限制主端要么在互连的QoS/调度层面保证前一笔W数据能优先占用W通道。不能一边只给一个地址槽一边要求多个Master并发写还指望不出事。另外还要注意W数据和AW的顺序约束。如果从设备不支持W先到Master就不能把W数据提前推到通道上反之如果从设备必须先收AW再等W那么Master在发出AW之后必须保证W数据能很快跟上不能发出AW就晾在那里去等别的通道。很多死锁其实是Master的调度器“只发AW不发W”造成的跟从设备关系不大。4.2 RTL设计里的关键取舍RTL设计上最直接的解耦办法是把“地址槽释放条件”从“W数据完全接收”改成“W数据已经进入一个足够深的数据缓冲”。比如AXI-to-AHB桥里AW进来后先把地址转换成AHB总线事务W数据进FIFO只要FIFO没满就允许下一个AW进来。这样AW通道的推进不再被W通道的即时完成状态绑死循环等待的一个关键边就被切断了。如果确实因为后端存储或数据通路限制必须等W数据到齐才能接受下一个AW那就要从资源数量下手。把地址槽深度从1加到2或者把写数据FIFO加深到能容纳一笔完整突发都能打破死锁环。注意这里不是随便加一个深度就完事而是要结合最长突发长度和outstanding事务数计算W数据缓冲深度至少应该能容纳可能被堵在中间的所有突发数据。仲裁器设计同样重要。W通道仲裁如果采用严格优先级高优先级Master一直有数据低优先级Master可能永远拿不到W通道就为循环等待创造了条件。至少要做到公平轮转最好还能配合“等待超时提升优先级”的机制。我在前面那个最小模型里特意设置了严格优先级就是因为实际项目里这种配置非常常见而且最容易出事。4.3 验证与断言让死锁在回归前现形验证层面除了跑随机压力用例一定要加liveness检查。协议检查通常只查handshake规则比如valid不能在没有ready时乱拉低但这类检查查不出“双方都合法但谁都不往前走”的死锁。我常用的一个简单断言是只要AW完成握手最终必须看到对应的WLAST和B响应。property p_write_txn_complete(slave_if s); (posedge s.aclk) disable iff (!s.aresetn) (s.awvalid s.awready) |- ##[1:$] (s.wvalid s.wready s.wlast); endproperty property p_write_response_after_wlast(slave_if s); (posedge s.aclk) disable iff (!s.aresetn) (s.wvalid s.wready s.wlast) |- ##[1:$] (s.bvalid s.bready); endproperty这种属性只会在死锁出现后超时触发定位时作用有限但能在回归阶段第一时间把问题炸出来。除了断言还可以在testbench里加一个deadlock watchdog设定一个最大等待周期数某个通道的握手如果超过该周期数还没有完成就dump当前所有Master和从设备的状态到日志并打印等待图。实测下来这种监视器比事后拿waveform翻几百周期要快得多。5. 复盘一次真实的AXI写通道死锁讲完方法论分享一个我印象很深的案例。这个案例不是虚构是在一个带CPU和DMA的子系统验证里遇到的现象、定位和修复都非常典型。5.1 现场现象与第一印象当时测的是一个SoC子系统CPU核和DMA会同时访问挂在AXI-to-APB桥后面的外设寄存器区。随机压力测试跑了几千个事务后仿真永远停在同一个地方不是报错也不是超时就是整条总线卡死。第一眼看到波形时CPU侧已经发出了AW正在等W通道DMA侧则在做一个大块数据搬运WVALID一直为高但WREADY长期为低。桥接模块内部的地址FIFO显示只有一项而且这项被CPU的AW占着。第一印象是“DMA把总线挤爆了”因为DMA的数据量确实很大。但如果只是拥塞总线至少会在某些周期让一个握手成功。实际情况是AWREADY和WREADY都纹丝不动说明这不是性能问题而是死锁。5.2 根因定位一个“看起来没问题”的状态机把等待图画出来后发现根因和模型里的场景几乎一样桥接模块的状态机为了省事设计成“收到AW帧后必须等当前写事务的W数据全部到达才允许接收下一个AW”。也就是说地址FIFO虽然只有一项但在这一项被占住后桥不会回收它除非W通道把数据送完。与此同时DMA的W突发被仲裁器判定为高优先级持续占据W通道CPU的W0根本无法进入。DMA的W数据不断地往桥的数据FIFO里灌但桥的消费逻辑又需要拿到DMA的AW1才能知道数据该写到哪个外设地址而AW1被地址FIFO挡住。依赖环就此形成CPU等W通道W通道被DMA占住DMA等AW1AW1等地址FIFO释放地址FIFO释放又等CPU的W0完成。这里最坑的地方在于每一个模块单独看都“符合协议”桥的状态机没有违反AXI握手规则DMA也只是在发正常的写突发仲裁器严格按优先级调度也不违反AXI规范。但把它们组合在一起就凑齐了死锁的全部条件。5.3 修复与回归一行代码的代价和两个验证点修复方案不是把DMA优先级调低也不是让CPU多等一会而是直接把地址FIFO深度从1改成2同时把地址槽的释放条件从“W数据全部接收”改成“W数据进入数据FIFO即可”。改完之后即使CPU的W0还堵在路上DMA的AW1也能进入地址FIFO桥的消费逻辑拿到AW1后就能消费DMA的W数据W通道一旦释放CPU的W0自然跟着走了。代码改动其实很小但回归验证需要补两个点第一加一条断言保证桥的地址FIFO只要还有空位AWREADY就必须在有限周期内拉高第二随机测试里把CPU和DMA的写优先级、突发长度、outstanding数量都设成不同组合连续跑了几百个种子。修改后这些用例全部通过之后类似的死锁没再复现过。这个案例给我最大的教训是协议上“合法”不代表系统里“安全”AXI死锁从来不是某一条通道自己的问题而是通道间依赖关系的问题。6. 常见问题速查表与最后几点心得调试AXI死锁是个熟练活很多问题看一眼现象就能猜到大概方向。这里整理了一张速查表也是我平时给人排查死锁时的第一份checklist。现象可能原因排查方向AWVALID为1且长时间不握手从设备地址槽满或地址槽被某笔未完成事务占住查从设备内部事务表项、地址FIFO占用WVALID为1且WREADY长时间为0从设备数据FIFO满或W通道被更高优先级突发占用查数据FIFO深度、W通道仲裁状态AW和W各卡在不同Master上基本就是AW-W依赖型循环等待画等待图找“AW等W、W等AW”的环改变一个Master优先级后死锁消失仲裁问题掩盖了资源不足不要只调优先级要查资源深度和释放条件断言只报超时不报位置Liveness检查缺少状态dump增加deadlock watchdog和等待图打印6.1 最后几点心得我在实际项目中处理完这类死锁后最深的体会是AXI死锁问题很难靠“看一眼代码”发现因为它往往发生在多个模块的交互边界上。习惯性做三件事能省很多时间一是所有写通道都加liveness断言不要只做handshake协议检查二是设计评审时重点审“地址槽释放条件”和“数据FIFO消费条件”凡是两者形成依赖的都要标黄三是出现卡死先画等待图不要急着改仲裁优先级优先级很可能只是压死骆驼的最后一根稻草。另一个小技巧是在testbench里专门做一个“事务超时监控器”按Master分别统计所有outstanding写事务的年龄。年龄超过阈值的Master把它当前AW、W、B三个通道的状态全部打印出来。这个监视器写起来不复杂但每次都能在死锁产生后立刻给出第一手的现场证据比事后翻波形高效太多。最后想强调的是AW-W依赖不是某一种从设备实现独有的bug它是通道独立性带来的固有风险。只要系统里存在多个Master、多笔outstanding写事务、以及有限深度的内部缓冲就有可能出现这种环。理解了模型之后再回去看那些“偶尔卡死但复现不出来”的老问题往往会有一种豁然开朗的感觉。
返回列表