Veloris.
返回索引
概念基础 2026-02-23

你的 FPGA 输出为什么会"抖"?——竞争与冒险的本质

6 分钟
2.0k words

你的 FPGA 输出为什么会”抖”?——竞争与冒险的本质

💡 你用一个与门和一个非门对同一个信号 A 做运算,然后把结果送进一个或门。逻辑上,输出应该恒为 1(A | ~A = 1)。但如果你用示波器去看实际输出,会发现在 A 跳变的瞬间,输出上出现了一个窄窄的低电平尖峰——一个”不该存在”的脉冲。

这个脉冲就是毛刺(Glitch),它的产生机制在数字电路中有个专门的名字:冒险(Hazard)。而导致冒险的根本原因,是信号经过不同路径到达同一个逻辑门时存在时间差——这种时间差叫做竞争(Race Condition)

更要命的是,如果这个毛刺恰好被下一级的触发器采样到了,你的状态机可能直接跳到一个非法状态。这篇文章带你彻底搞清楚:竞争和冒险是怎么产生的、有多危险、以及怎么消灭它们


1. 竞争和冒险:到底有什么区别?

很多人把竞争和冒险混为一谈,但它们其实是因果关系

概念定义角色
竞争(Race)信号经过不同延迟的路径到达同一逻辑门,到达时间不确定原因
冒险(Hazard)由于竞争导致输出端出现瞬时错误脉冲(毛刺)结果

一句话:竞争是”信号赛跑”,冒险是”赛跑导致的翻车”。

有竞争不一定有冒险(如果时间差不够大,毛刺可能不会出现),但有冒险一定有竞争。


2. 冒险的类型

冒险分为三种类型,理解它们有助于你在代码审查中快速定位风险:

静态冒险(Static Hazard)——最常见,也是 FPGA 设计中最需要关注的:

  • 静态-1 冒险:输出本应保持高电平,但瞬间出现了一个低电平尖峰(如 A | ~A 的例子)
  • 静态-0 冒险:输出本应保持低电平,但瞬间出现了一个高电平尖峰

动态冒险(Dynamic Hazard):输出在跳变过程中出现多次振荡(0→1→0→1),通常出现在多级逻辑中。

功能冒险(Functional Hazard):多个输入同时变化时,输出的过渡过程中出现不正确的瞬态值。这种冒险无法通过添加冗余逻辑消除,只能通过同步设计来规避。

💡 工程师手记在实际项目中,静态冒险是最常踩坑的。我曾经在一个状态机的输出解码逻辑中,用组合逻辑直接生成一个 enable 信号去控制计数器。仿真一切正常,但上板后计数器偶尔会多计一次。最后用 SignalTap 抓到了那个只有几纳秒宽的毛刺——它恰好被计数器的时钟沿采到了。


3. 为什么同步设计能”免疫”毛刺

这是全文最重要的部分。理解了这个原理,你就掌握了应对竞争和冒险的核心武器。

关键洞见:毛刺只存在于组合逻辑的输出”稳定之前”。只要你在信号稳定之后再去采样,毛刺就伤不到你。

而同步设计正好做到了这一点——所有数据都在时钟边沿被触发器采样。在两个时钟边沿之间,组合逻辑有充足的时间完成计算和稳定。即使中间产生了毛刺,只要在下一个时钟边沿到来之前毛刺已经消失,触发器就只会采到正确的稳定值。

时钟   ______|‾‾‾‾‾‾|______|‾‾‾‾‾‾|______
                ↑ 采样点           ↑ 采样点
组合输出 ──┐  ┌─┐  ┌──────────────────────
           └──┘ └──┘
           ↑毛刺↑    ← 毛刺在采样点之前已消失,触发器采到的是稳定值

这就是为什么 FPGA 设计的第一原则是”全同步设计”。

💬 你可能会问:如果我全部用同步设计,是不是就完全不用担心毛刺了?

几乎是的。但前提是你的组合逻辑路径延迟不能超过一个时钟周期(即满足时序约束)。如果组合逻辑太长,毛刺可能还没消失,下一个时钟边沿就来了——这就是时序违例。解决办法是插入流水线寄存器,把长路径拆短。


4. 实战:Verilog 中如何消灭竞争和冒险

4.1 关键输出必须寄存

组合逻辑的输出如果要驱动其他模块或作为控制信号,必须经过寄存器打一拍

// ❌ 危险:组合逻辑直接输出,可能有毛刺
wire ctrl = (state == IDLE) & start;  // 组合逻辑输出

// ✅ 安全:寄存器输出,毛刺被过滤
reg ctrl_r;
always @(posedge clk) begin
    if (rst)
        ctrl_r <= 1'b0;
    else
        ctrl_r <= (state == IDLE) & start;
end

4.2 输入信号打拍同步

来自外部或其他时钟域的信号,在使用前先用寄存器”打拍”:

// ✅ 输入打拍:消除输入信号变化带来的竞争
reg a_r, b_r;

always @(posedge clk) begin
    if (rst) begin
        a_r <= 1'b0;
        b_r <= 1'b0;
    end else begin
        a_r <= a;
        b_r <= b;
    end
end

// 后续逻辑使用打拍后的信号
always @(posedge clk) begin
    if (rst)
        out <= 1'b0;
    else
        out <= (a_r & b_r) | (~a_r & ~b_r);
end

4.3 添加冗余逻辑消除静态冒险

在纯组合逻辑中,如果无法用寄存器消除毛刺,可以通过卡诺图分析添加冗余项:

// ❌ 可能有静态冒险(b 从 1 变 0 时,两项同时为 0 的瞬间)
assign y = (a & ~b) | (b & c);

// ✅ 添加冗余项 (a & c) 覆盖过渡区间,消除冒险
assign y = (a & ~b) | (b & c) | (a & c);

4.4 长组合路径插入流水线

当组合逻辑路径延迟接近时钟周期时,插入寄存器分解路径:

// ❌ 长组合逻辑链,可能时序违例
assign result = func_a(func_b(func_c(input)));

// ✅ 流水线:拆成多个时钟周期完成
reg stage1, stage2;
always @(posedge clk) begin
    stage1 <= func_c(input);   // 第1拍
    stage2 <= func_b(stage1);  // 第2拍
    result <= func_a(stage2);  // 第3拍
end

💡 工程师手记我在做一个 FIR 滤波器的时候,一开始把所有乘加运算放在一个组合逻辑里完成,结果 Vivado 报时序违例——关键路径延迟超过了时钟周期。后来改成 4 级流水线,每级只做一次乘累加,时序轻松 meet,而且吞吐量还是每时钟一个样本。


5. 设计原则速查

以下是消灭竞争和冒险的核心原则,按重要性排序:

优先级原则说明
★★★全同步设计所有逻辑在时钟边沿触发,这是最根本的解决方案
★★★输出寄存关键信号通过寄存器输出,过滤毛刺
★★☆输入打拍外部异步信号先打拍再使用
★★☆赋值规则组合逻辑用 =,时序逻辑用 <=,绝不混用
★☆☆分支完整always @(*) 中所有分支都赋值,避免 latch
★☆☆门级仿真综合后加延迟模型仿真,检查实际毛刺

6. 总结

核心认知内容
竞争 vs 冒险竞争是原因(信号赛跑),冒险是结果(毛刺)
毛刺的本质组合逻辑输出在”稳定之前”的过渡态
终极解药同步设计——用时钟边沿采样,避开毛刺窗口
实操三板斧输出寄存、输入打拍、长路径加流水线

下一步:

  • 想了解时钟信号本身的问题?→ 阅读下一篇《同步时序电路和异步时序电路》
  • 想深入理解跨时钟域的亚稳态?→ 阅读《时序逻辑电路的亚稳态》
  • 动手练习:写一个带毛刺的组合逻辑电路,然后用 SignalTap / ILA 在板子上抓毛刺波形

常见问题

💬 毛刺在仿真中能看到吗?

RTL 功能仿真(零延迟仿真)通常看不到毛刺,因为仿真器假设所有门延迟为零。要看到毛刺,你需要做门级仿真(post-synthesis simulation),在综合后的网表中加入实际延迟模型。Vivado 中可以通过 write_verilog -mode timesim 导出带延迟的网表。

💬 竞争和冒险只发生在组合逻辑中吗?

组合逻辑中的毛刺属于”冒险”。时序逻辑中也有”竞争”的概念——比如在 always @(posedge clk) 中使用阻塞赋值,多个信号的更新顺序依赖于代码书写顺序,这也是一种竞争。解决方法就是时序逻辑中始终使用非阻塞赋值 <=

💬 实际项目中,毛刺最容易出问题的场景是什么?

三个高危场景:①组合逻辑直接作为时钟或复位信号(门控时钟);②组合逻辑输出作为其他模块的 enable/clear 控制信号;③跨时钟域的组合逻辑信号。这三种情况都必须加寄存器打拍。


参考资料

  1. Clifford E. Cummings, Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!, SNUG 2000
  2. Xilinx/AMD, UG906: Vivado Design Suite User Guide — Design Analysis and Closure Techniques
  3. IEEE Std 1364-2005: IEEE Standard for Verilog Hardware Description Language

系列导航:本文是「FPGA 入门系列」第 13 篇。

如果这篇文章对你有帮助,欢迎点赞、收藏,也欢迎在评论区分享你在实际项目中遇到的毛刺问题和解决方案。

End of file.