
1. AXI总线中的AW-W依赖死锁场景的完整复盘做总线验证的人应该都遇到过这种场景仿真跑到一半整个testbench卡死不动了时钟还在跳但总线事务就是不往前走。波形拉出来一看AWREADY一直拉不高WREADY也一直等在那里两边就这么僵持着。这种卡死十有八九就是AXI总线上的死锁。而在所有死锁场景里AW-W依赖是我在实际项目中见过最多、也最容易踩的一种。先解释一下什么是AW-W依赖。AXI协议里写通道由三个独立的子通道组成写地址通道AW、写数据通道W、写响应通道B。协议规范上AW和W是相互独立的通道理论上谁先谁后都可以。但实际设计中很多slave会把AW通道的握手信号和W通道的数据接收逻辑耦合在一起比如AW FIFO满的时候必须等W通道写入一笔数据才能腾出空间而master侧又恰好持“必须先完成AW握手才能发送W数据”的逻辑。这时候master在等slave的AWREADYslave又在等master的WVALID一个完美的循环等待就形成了。这个问题的麻烦之处在于它不是必现的。同样的设计跑简单读写测试可能几十个case都过但一旦跑随机激励或者长时间压力测试就可能在某一个特定的时序窗口下触发死锁。更糟的是死锁一旦发生仿真时间会一直跑但功能逻辑完全停摆如果没在验证环境里做超时检测你可能要等好几个小时才发现case已经卡死了。所以这篇文章我想把AW-W依赖这个死锁场景从头到尾拆一遍协议层面的根因是什么、怎么在验证环境里主动构造这种场景、死锁发生之后怎么定位和排查、以及设计上怎么从源头规避。内容全部来自我实际做AXI验证项目时的真实经历希望能给你一些参考。2. AXI写通道的握手规则与死锁的四个必要条件2.1 AW、W、B三个通道的协议约束要把死锁讲清楚得先把AXI写通道的握手规则摆出来。AXI协议里每个通道都是基于VALID和READY两个信号完成一次握手二者拉高的那个时钟上升沿就是一次数据传输完成。写地址通道master拉高AWVALIDslave在能接收地址时拉高AWREADY两者同时为高的拍地址被采样。写数据通道master拉高WVALID同时在WDATA上放数据拉高WLAST表示最后一笔slave拉高WREADY表示可以接收两者同时为高的拍数据被采样。写响应通道slave在处理完写事务后拉高BVALID返回响应同时给出BRESPmaster拉高BREADY表示可以接收响应两者同时为高事务完成。这里有一个很多人容易忽略的约束那就是协议规定了BVALID必须在最后一个写数据WLAST对应的那笔被采样之后才能拉高这条依赖关系是协议强制的本身合理。但AW和W之间协议并没有强制谁必须先握手。说白了从协议规范的角度讲master完全可以先发写数据、再发写地址或者两个同时发。AXI协议甚至支持写数据通道比写地址通道提前一拍甚至几拍到达。所以如果你在处理AW通道和W通道的握手关系时擅自加了一条“W必须等到AW握手成功后才能发”这就等于给设计埋了一颗雷。2.2 死锁的四个必要条件在总线场景中的映射经典的操作系统课程里死锁有四个必要条件互斥、持有并等待、不可剥夺、循环等待。把这套理论映射到AXI总线场景里你会发现AW-W依赖死锁刚好满足这四个条件互斥总线通道上的握手信号和FIFO资源是排他性占用的。AW FIFO里某个entry被占用时其他地址请求无法使用这个entry。持有并等待slave持有AW FIFO的占用权因为AW FIFO满了同时在等待W通道的数据进来希望腾出空间而master持有W数据已经在W通道上valid等待WREADY同时在等待AW通道握手完成。不可剥夺握手中的信号不能被强行打断。master不能强行把已经拉高的WVALID收回去slave也不能强行让AW FIFO里的entry无效掉重新分配。循环等待master等slave的AWREADYslave等master的WVALID形成一个环。这个映射关系很关键因为这意味着只要你在设计中看到了上述任何一个条件被打破的可能死锁就有解。但反过来如果四个条件全部成立死锁就是必然的只是触发时机早晚的问题。2.3 AW-W依赖死锁的典型触发链路结合我遇到过的实际场景AW-W依赖死锁的典型触发链路是这样的第一步master发起一笔写突发burst假设是4拍数据。master侧的设计是先发AW等AW握手成功之后再开始发W数据。这一步本身是很多人的默认习惯协议不禁止但如果slave侧恰好没处理好这里就会出问题。第二步slave侧AW通道的接收FIFO设计成了容量只有1个entry当AW FIFO被占用时AWREADY不会拉高。同时slave的逻辑在AW FIFO满的时候要求W通道先写入一笔数据让FIFO里的AW被消费掉才会释放AWREADY。第三步master发AWAWVALID拉高等待AWREADY。slave这边AW FIFO已经满了AWREADY不拉高同时slave在等WVALID但master的WVALID此时还没拉起来——因为master在等AW握手成功。逻辑上这已经是一个死锁了。但从波形上看AWVALID和W数据都卡在那里两个信号都是低电平非常“安静”如果不是有经验很容易误判成“总线空闲可能master还没开始发请求”。在实际项目中这种死锁不会第一拍就暴露因为AW FIFO刚开始是空的前几笔事务能正常完成。但只要master侧再发出第二笔、第三个AW请求且slave还没来得及消费完第一笔AW FIFO就满了死锁的时机就到了。3. 验证环境里如何主动构造AW-W依赖死锁场景3.1 为什么随机激励很难覆盖到死锁说句实话在真正遇到一次死锁之前我一直觉得随机约束激励已经完全够用了。AXI VIP本身支持随机化地址对齐、突发长度、突发类型、数据间隔看起来覆盖面已经很大了。但随机激励很难精确命中AW-W依赖死锁原因在于大多数AXI VIP的master模型在发送写事务时默认会在AW握手完成后才发W数据或者AW和W数据之间有较大的间隔。而slave侧如果对AWREADY和WREADY的依赖关系设计得不合理这种“默认顺序”恰好就被绕过去了——aw和w不会发生重叠等待。要复现死锁就得让master在AW未握手成功的情况下持续拉高WVALID或者至少让W通道提前到AW握手之前的窗口期。这就需要你在验证环境里主动控制写通道的握手时序而不是依赖VIP默认的行为。3.2 用AXI VIP的配置接口强制AW-W交错目前主流的AXI VIP比如Synopsys的VC VIP、Cadence的AXI VIP或者开源的AXI验证IP基本都支持通过配置来控制写通道之间的时序关系。以SYNOPSYS VC VIP为例你可以在sequence里通过约束aw_ready和w_ready的依赖关系来构造交错// 关键在于让AW和W通道可以并行拉高VALID class axi_write_deadlock_seq extends axi_master_base_seq; uvm_object_utils(axi_write_deadlock_seq) constraint c_aw_w_overlap { // 允许W通道在AW未完成时就开始发数据 aw_valid_delay 0; w_valid_delay inside {[0:2]}; // 让AW通道的握手被延迟 aw_ready_delay inside {[10:20]}; } task body(); axi_write_transaction wr_trans; uvm_do_with(wr_trans, { burst_length 4; // 其他约束... }) endtask endclass核心思路就是一句话让W通道的发送不受AW握手的影响但同时让AW的握手被slave侧拉长。这样AWVALID和WVALID会在某个时间段内同时为高形成前文说的僵持条件。如果你的VIP不支持这种细粒度的时序控制那就需要自己在driver层做处理在发送AW之后不等AWREADY拉高就立刻发起W通道的驱动力。这种写法虽然麻烦但对场景的控制精度更高。3.3 用SystemVerilog断言实时检测死锁发生在验证环境里主动构造死锁很重要但更重要的是你要能第一时间发现死锁。仿真卡死本身就是一个“信号”但在大型testbench里你可能同时跑了多个master和多个slave单纯靠肉眼盯波形效率太低。我建议在总线monitor里加上死锁检测断言一旦AW和W之间出现循环等待的迹象立刻报错。下面这段断言是我在一个项目里实际用过的思路很简单如果AWVALID拉高后超过N个周期AWREADY没有响应同时WVALID也拉高且WREADY没有响应就判定为死锁。property aw_w_deadlock_checker; (posedge aclk) disable iff (!arst_n) // 检测条件AWVALID和WVALID同时为高但AWREADY和WREADY都持续为低 (awvalid wvalid !awready !wready) |- ##[1:MAX_WAIT_CYCLES] 1; // 这里MAX_WAIT_CYCLES要根据性能需求设置通常设置成10~20拍 endproperty assert property (aw_w_deadlock_checker) else $fatal(1, AXI deadlock detected: AW-W dependency loop!);这个断言的原理是正常情况下AXI总线上即使有拥塞AWREADY或WREADY其中一个总会在有限拍数内拉高两个同时死等的概率极低。如果持续N拍两个ready都是低说明大概率是依赖造成了死锁。不过要注意一点MAX_WAIT_CYCLES的取值是有讲究的。设太短总线flush时master不发新事务断言误报设太长死锁发生后仿真还要空跑很久。我做过最稳妥的做法是这个参数可以配置正常回归测试用比较小的值比如32拍跑性能测试时再调大。4. 死锁发生后的定位与排查从波形到根因4.1 死锁波形的典型特征构造死锁容易但如果你是在一个既有的项目里偶然遇到死锁第一步一定是看波形。死锁波形的典型特征是AWVALID和WVALID都拉高但AWREADY和WREADY都长期为低。B通道上没有任何活动BVALID一直为低。RESET已经释放时钟一直正常运行。这几个条件全部满足基本可以确定总线上发生了死锁。但这里有一个特别容易误判的点如果master还没发起新的写事务AWVALID本身是低的WVALID也是低的。这种情况下波形看起来一切正常你甚至可能以为master还在等待什么外部条件。所以排查死锁时不要只盯当前一拍要把时间窗口往回拉找到AWVALID和WVALID从低拉高的那一个时刻然后看从那以后发生了什么。4.2 用波形定位循环等待的两个关键检查点拿到波形后我一般按两个关键检查点来定位第一个检查点是AWREADY为什么一直为低。顺着AWREADY的生成逻辑往前追看它依赖了什么信号。常见的情况是AWREADY拉低是因为AW FIFO满而FIFO的释放逻辑又依赖W通道写入新数据来消费。这时候你就找到了“slave在等W”这一环。第二个检查点是WVALID为什么一直为高但WREADY不响应。这里要分两种情况如果slave侧WREADY本身能拉高但master侧WVALID一直没有拉高说明是master在等AW握手这是“master等slave”这一环如果WREADY也拉低那要去看为什么WREADY拉低——是不是slave内部数据buffer满而buffer的释放又依赖AW先完成。把两个检查点拼接起来如果能形成“A等B、B等A”的闭环根因就找到了。实际项目里闭环往往不止两个节点有时会形成A等B、B等C、C等A的三方循环排查起来更费时间但方法是一样的顺着ready信号逐级向上追。4.3 一个真实的死锁排查记录我之前在一款AI加速芯片的验证项目里遇到过一次很隐蔽的死锁。表面上看master和slave之间协议交互正常单笔读写测试全部通过但跑多核并发访问的场景时仿真跑到第37个case才卡死。排查过程很有意思。最初我怀疑是内存一致性协议的问题因为多个master在访问同一个slave的同一块地址区域。后来拉波形发现死锁的两个参与者分别是master_0和slave_2与数据一致性完全无关。再往下追slave_2的AW通道FIFO是4深度的理论容量并不小但问题是这个FIFO的读指针释放条件写错了——它依赖的是W通道的写指针推进而W通道的写指针又依赖AXI协议里的WLAST。当master_0发了一个奇数长度的burst比如长度3时WLAST的时序与正常情况下不同导致FIFO读指针没能及时释放FIFO被占满了。这个时机刚好和master_0在等AWREADY的窗口重叠死锁就形成了。这个case最终修了三行代码把FIFO的读指针释放条件从“W指针推进”改成“WLAST拉高后的下一拍”问题彻底解决。但它给验证环境提了一个很强的要求AXI总线的死锁测试不能只跑常规的偶数长度突发奇数长度、非对齐地址、乱序ID这些边界情况才是最容易踩雷的地方。5. 从设计源头规避AW-W依赖死锁5.1 slave侧握手信号的独立性原则验证环境能发现问题但真正解决问题还是要靠设计层面的规则约束。我做AXI slave设计时有一个很重要的原则AW、W、B三个通道的握手信号在逻辑上必须尽量保持独立。AWREADY的产生只应该依赖AW FIFO的状态也就是说AW FIFO有空间就拉高AW FIFO满就拉低。不应该把W通道的数据接收情况作为AWREADY的释放条件。WREADY的产生只应该依赖W数据FIFO的状态有空间就拉高满就拉低。不应该把AW通道的地址接收情况作为WREADY的释放条件。这个原则听起来很简单但实际设计里特别容易被绕进去。因为很多设计会把地址和数据打包成同一个描述符descriptor存储地址FIFO满了数据FIFO就必须暂停接收反过来数据FIFO满了又没法推出新的描述符。这种耦合设计在资源上确实省了一点但代价是死锁风险。我的建议是如果资源允许AW地址FIFO和W数据FIFO一定要分开设计、独立管理。两个FIFO的满状态互不依赖AW和W通道的握手才能做到真正的独立。如果资源紧张必须共用存储那么在逻辑上要保证接收AW时不检查W状态接收W时不检查AW状态只在FIFO内部做地址和数据的绑定关系管理。5.2 master侧不要“等待AW握手成功再发W”master侧同样重要。很多工程师在写master驱动时习惯性地把写流程写成先发AWVALID等待AWREADY握手成功后再发WVALID和WDATA。这个写法在功能上完全正确大多数时候也不会出问题。但前面说了如果你这么写而slave恰好又设计了AW和W的依赖逻辑死锁就是必然结果。从协议允许的范围看master完全可以在AWVALID拉高的同时就把WVALID拉高数据跟着放上去。AXI协议的写数据通道并不要求W必须等在AW之后。采用这种“AW和W并行发起”的方式即使slave侧存在一定的依赖也不会形成环master已经发出了W数据slave只要有能力接收W就能推进事务如果slave没有能力接收W那说明W FIFO满了这个状态和AW无关死锁的条件不成立。当然有些设计为了做写合并write merging会把写数据暂存在master侧的buffer里等收到来自slave的buffer地址再发送。这种情况下必须仔细分析buffer分配和释放的依赖关系不能在buffer分配上形成AW和W之间的互相等待。5.3 用超时计数器做最后防线无论设计怎么做验证环节都应该放一道最后防线。我强烈建议在每个slave的接口上加上busy timeout检测当AWVALID或WVALID拉高之后如果对应的READY信号在指定周期内一直没有拉高就自动报错或触发中断。// 简化示例AWREADY超时监控 always (posedge aclk or negedge arst_n) begin if (!arst_n) begin aw_timeout_cnt 0; aw_timeout_flag 0; end else if (awvalid !awready) begin if (aw_timeout_cnt TIMEOUT_THRESHOLD) aw_timeout_flag 1; // 上报超时 else aw_timeout_cnt aw_timeout_cnt 1; end else begin aw_timeout_cnt 0; end end这个逻辑不需要太复杂核心目的是如果握手超过了预设的阈值就说明协议层出现了非正常的状态。超时计数器既能用来在验证阶段暴露死锁也能在芯片实际运行阶段作为硬件自检手段上报异常道理是通的。6. 更多AXI死锁场景与通用排查方法论6.1 从AW-W依赖延伸开来的其他场景AW-W依赖只是AXI死锁的一个起点。我在项目里还遇到过不少其他类型的死锁简单列举一下W-B依赖死锁。slave在处理完W数据后产生BVALID响应但产生BVALID需要master先释放某个资源同时master又在等BVALID才能继续发W数据。这种死锁不能完全依靠总线协议来分析需要结合slave内部功能逻辑。R通道依赖死锁。读数据通道RVALID和RREADY之间的循环等待在乱序返回数据的master中更容易出现。比如slave同时处理两个读请求返回顺序与master ID管理方式不匹配时可能把一个ID的R数据堵住导致另一个ID的R数据也卡住。响应顺序依赖死锁。AXI协议允许不同ID的写事务乱序完成但有些设计会强制要求ID顺序返回。如果master侧管理多个outstanding事务而slave内部先完成了后发的ID却在返回B时被强制等前一个ID先返回就可能在B通道上形成阻塞。这些场景的共性和AW-W依赖是一样的多个通道的握手信号之间形成了不该有的依赖关系最终导致循环等待。所以排查思路也完全通用。6.2 死锁检测工具与调试方法除了自己写断言现在很多验证工具也提供了死锁检测功能。Cadence的XCHECK可以在仿真时自动检测X态传播和死锁循环Synopsys的VCS也有类似的formal/assertion检查选项。用这些工具的好处是可以在RTL仿真阶段自动标记出可能存在死锁的代码路径。但工具的局限在于它只能抓“已经发生”的死锁不能预测“可能发生”的死锁。所以我还是建议在验证环境中配套使用以下手段在总线monitor中维护每个master当前outstanding的事务数量如果某个master的outstanding数量长时间不为0且不减少触发告警。在powerful场景测试中随机化AW和W的延迟、随机化B和R的响应延迟增加死锁暴露概率。对slave的AW FIFO、W FIFO分别做full状态监测一旦出现两个FIFO同时full且时间超过阈值就要重点检查是否有依赖问题。6.3 常见AXI总线死锁排查速查表异常现象可能原因排查方向AWVALID为高、AWREADY长时间为低AW FIFO满AWREADY释放逻辑依赖W通道检查slave的AWREADY生成逻辑WVALID为高、WREADY长时间为低W FIFO满或WREADY释放逻辑依赖AW通道检查slave的WREADY生成逻辑AWVALID和WVALID同时为高两个ready都不拉高AW与W之间形成循环等待按AWREADY和WREADY两个方向逐级追查BVALID一直为低B通道无响应写事务未完成可能与WLAST相关检查BVALID的生成条件是否依赖W通道RVALID一直为低读数据无响应读请求未处理或slave状态机卡死检查读地址通道AR的握手和读状态机总线上所有事务都暂停burst不能推进可能是地址和数据路径的死锁重点检查master侧outstanding管理和slave侧ID管理这个表的价值在于它不只是一个问题清单更是一个“先看什么、再看什么”的排查路径。总线死锁不会凭空产生每个信号长期不变化背后一定有一个释放条件没有被满足的依赖链条顺着链条找到底答案就在那里。7. 最后分享一点实操感受做AXI总线验证这几年我最大的体会是死锁问题不怕难怕的是没有意识。很多死锁在设计的第一版就已经埋下了但常规的功能测试根本不会触发等到芯片流片回来或者系统集成阶段才暴露代价就是几何级数上升。所以在验证计划阶段把死锁场景作为一类独立的测试项来设计是性价比极高的投入。构造AW-W依赖死锁这个场景我个人的建议路径很简单先把写通道的三个子通道之间的依赖关系在excel里列成一张矩阵标明哪些信号可以独立握手、哪些信号存在强制依赖、哪些信号被设计加了不该有的依赖然后针对每一处“非协议强制的依赖”设计一个专门的定向测试。这个方法费不了多少时间但能帮你把死锁问题消灭在验证阶段。另外波形分析时不要只盯着握手信号AXI的ID和outstanding信息同样重要。很多死锁和事务完成顺序强相关ID配错了或者outstanding深度设小了都可能造成你意想不到的循环等待。把这些因素纳入考虑你的排查效率会高很多。