9.2 竞争、毛刺与亚稳态:三大疑案侦破 本节摘要:竞争、毛刺、亚稳态是硬件调试的三大疑案,症状相似(偶发、难复现、换个工具就变脸),病根各异:竞争在调度语义、毛刺在结构路径、亚稳态在物理窗口。三案各有固定的分流判据与侦破路径,掌握指纹比对,排查就不靠撞运气。 防线讲完,练刑侦。这一节的三个对手是 RTL 世界最高发的疑难杂症,它们的共同特征是「偶发且难复现」——这也正是它们值得单独立案的原因:能按指纹分流,就不必每次都全盘重查。 案件一:竞争——病根在调度语义 竞争(race)指代码行为依赖事件执行的先后顺序,而顺序在标准里恰恰未定义。症状指纹:换仿真器结果不同、改无关代码结果翻转、同一份代码两次仿真结果不一致。
本节摘要:竞争、毛刺、亚稳态是硬件调试的三大疑案,症状相似(偶发、难复现、换个工具就变脸),病根各异:竞争在调度语义、毛刺在结构路径、亚稳态在物理窗口。三案各有固定的分流判据与侦破路径,掌握指纹比对,排查就不靠撞运气。
防线讲完,练刑侦。这一节的三个对手是 RTL 世界最高发的疑难杂症,它们的共同特征是「偶发且难复现」——这也正是它们值得单独立案的原因:能按指纹分流,就不必每次都全盘重查。
竞争(race)指代码行为依赖事件执行的先后顺序,而顺序在标准里恰恰未定义。症状指纹:换仿真器结果不同、改无关代码结果翻转、同一份代码两次仿真结果不一致。第 4 章给过判据,这里给出完整的侦破路径:
// 竞争现场:两个 always 块用阻塞赋值互相传递信号 always @(posedge clk) flag = ready; // 阻塞赋值:立即生效 always @(posedge clk) ready = ~flag; // 读到的 flag 是本拍新值还是旧值?标准不答
两个块同时被时钟沿触发,执行顺序由工具的实现决定:先执行哪个块,flag 与 ready 的最终值就不同。侦破三步:第一步,检索所有阻塞赋值,确认它们只出现在组合块内;第二步,检索同一信号的驱动者,确认唯一的 always 块(多驱动是竞争的近亲);第三步,检查跨块信号传递是否全部走非阻塞——非阻塞的「沿末统一生效」天然消除同刻顺序依赖。根治手法因此统一:时序块一律非阻塞,块间不传递同刻敏感的语义。4.1 节的「禁同刻依赖」在这里兑现成排查动作。
毛刺(glitch)是组合逻辑输出的短暂错误翻转,病根在汇聚路径的延迟差。它的症状指纹:下游偶发多计一次数、多触发一次动作、错误总在输入变化后瞬间出现。4.2 节讲过成因,这里的侦破重点是「从下游症状反推上游结构」:
// 高发结构:比较器组合输出直连下游使能 assign tick = (cnt == 8'd99); // cnt 多位同时翻转瞬间,输出可能毛刺 always @(posedge clk) begin if (tick) // 毛刺被采样就是一个假使能 pulse_cnt <= pulse_cnt + 1'b1; end
多位计数器从九十八翻到九十九的瞬间,低位已变高位未稳,比较器输出闪过一个假脉冲。侦破路径:症状出现时,在波形上把时间窗缩到输入翻转沿附近,观察可疑组合信号是否存在亚时钟级的窄脉冲;RTL 仿真看不到毛刺时,按结构审查预判——列出所有「多位信号汇入比较器或宽选择器」的组合输出,全部按高嫌疑对待。根治手法两条:组合输出寄存器化(打一拍再给下游),或改用同步使能表达(4.2 节的样板代码)。结构审查的口诀:组合信号只进数据端,不进时钟端、复位端、异步使能端。
亚稳态(metastability)的病根不在代码而在物理:建立保持窗口被跨域信号随机击穿。症状指纹:错误与两个时钟域的相位关系相关、发生频率随跨域信号量与频率上升、寄存器重启后换个位置再犯。第 6 章讲过机理与同步器,这里的侦破重点是「审计而非复现」——亚稳态在 RTL 仿真里根本不会发生(仿真器没有物理窗口的概念),等仿真抓它等于等一个不会来的证人。

侦破路径是全库审计三问:每个跨域信号是否都过了两级同步器?同步器两级之间是否干净(无组合逻辑、无分支扇出)?多比特数据是否走了握手或异步 FIFO 而非逐位打拍?三问的答案画在一张跨域信号清单上,逐行核对。补一句工具视角:现代时序工具能对同步器做专门的约束检查(标记同步链、豁免跨域伪路径),工具审计与人工清单互为备份。
案例展开:换工具才现形的竞争案。 背景:某老项目换仿真平台后,回归突然多出一批失败用例,设计代码一行未动。操作:失败用例的波形比对显示某状态标志与数据错开一拍,检查代码发现该标志在时序块用阻塞赋值驱动、下游在另一个时序块同刻读取——原平台调度恰好「先写后读」,新平台反过来。结果:标志与相关传递全部改非阻塞,两平台回归一致。解读:竞争案的狡猾在于「当前工具下永远复现不了」——它依赖的实现顺序恰好稳定时,代码怎么测都对;换工具、换版本甚至换编译选项都可能翻脸。这也解释了为什么竞争的根治靠代码纪律而非测试:测不出「未定义」本身,只能不写未定义。变式:接手陌生代码库时,先跑一遍「阻塞赋值在时序块内」的全库检索,命中率就是竞争风险指数——比通读代码快得多。
拿到一个偶发错误,按这张卡分流:错误是否随工具或版本变化——是,优先查竞争(调度语义);错误是否总在输入变化沿附近、且 RTL 仿真复现不出——是,优先查毛刺(结构路径);错误是否与两个时钟域相关、仿真永远正常——是,直接上亚稳态审计(同步器清单)。三问问完,排查方向基本锁定,剩下的就是按对应案卷的手法走完侦破。偶发错误的排查最忌「哪里可疑改哪里」,分流判据的价值就是把瞎撞变成定向。
三案的手法都指向同一个预防思路:把正确性建立在结构上,而不是建立在对时序、调度、物理现象的假设上。竞争的预防是赋值纪律,毛刺的预防是输出寄存器化,亚稳态的预防是同步器清单——三者都可以固化成 Lint 规则与评审条款。工程团队之间调试能力的差距,多数时候不是刑侦技术的差距,而是这些预防条款是否真的在执行。
还有一层是心态上的:偶发错误最消耗人的不是技术难度,而是「改了好像好了」的错觉。没有根因分析的修复都是赌博——症状消失可能只是运气。三案各自的「根治标志」很明确:竞争案的标志是所有赋值纪律合规且换工具验证一致;毛刺案的标志是嫌疑输出全部寄存器化;亚稳态案的标志是跨域清单全绿。达不到标志的「修复」,都该在缺陷单上挂着重审。
三案手法练完,最后一站翻开通用错案清单:高频错误的结案汇编。