阻塞赋值和非阻塞赋值的核心区别,简单说就是:阻塞赋值 = 在过程块内按顺序立即生效,非阻塞赋值 <= 先计算右值、等当前时刻结束再统一更新左值;在 FPGA 设计里,通常用 = 描述组合逻辑,用 <= 描述时序逻辑。
而锁存器 Latch 的本质是:组合逻辑代码没有覆盖所有赋值路径,综合器为了“记住上一次的值”,就推断出带存储功能的电路,这通常不是我们在同步设计里想要的结果。
核心区别
阻塞赋值 = 的特点是当前语句执行完成后,后面的语句才能继续执行,所以在同一个 always 块里更像“顺序执行”。
非阻塞赋值 <= 的特点是先计算右边表达式,再在当前时间步结束时统一更新左边,因此更符合寄存器在同一时钟边沿同时采样、同时更新的硬件行为。
你可以把它理解成:阻塞赋值像“改完前一行结果,再执行下一行”,非阻塞赋值像“先把所有下一拍要写入的值记下来,最后一起更新”。
所以工程上常见规范是:组合逻辑 always @(*) 里优先用阻塞赋值,时序逻辑 always @(posedge clk ...) 里优先用非阻塞赋值。
为什么要区分
如果在时序逻辑里乱用阻塞赋值,仿真中的语句执行顺序可能和你想表达的真实寄存器级硬件行为不一致,从而造成“仿真看起来对,结构理解却错了”的问题。
相反,非阻塞赋值天然更适合描述多个寄存器在同一个时钟沿同时更新的场景,比如流水线寄存器、状态寄存器和打拍同步器。
例如两个级联寄存器如果都写在同一个时钟 always 块中,使用非阻塞赋值时会表现为真正的“两级寄存器”;如果错误使用阻塞赋值,第二级可能在仿真语义上直接拿到第一级刚更新的值,造成理解偏差。
这也是为什么面试中经常会问:“为什么时序逻辑通常推荐用非阻塞赋值?”,标准回答就是它更符合边沿触发寄存器的并行更新特性。
常见写法
组合逻辑推荐写成下面这种风格,使用 always @(*) 和阻塞赋值,这样输出会随着输入变化即时在组合路径上传播。
always @(*) begin y = a & b; z = y | c; end
时序逻辑推荐写成下面这种风格,使用时钟边沿触发和非阻塞赋值,描述寄存器在时钟到来时更新。
always @(posedge clk or negedge rst_n) begin if (!rst_n) begin q1 <= 1'b0; q2 <= 1'b0; end else begin q1 <= d; q2 <= q1; end end
Latch 是什么
Latch 是一种电平敏感的存储单元,不是边沿触发器;它在使能有效时透明传输,在使能无效时保持原值。
而 FPGA/同步数字设计通常更偏好使用边沿触发寄存器,因为寄存器更符合统一时钟驱动的同步设计思想,也更利于时序分析和工程收敛。
在 FPGA 中,Latch 往往不是理想选择,因为它会让时序行为更复杂,容易引入毛刺、增加分析难度,很多资料也指出在 FPGA 里它通常不如 D 触发器那样自然高效。
所以面试里一旦问“如何避免 latch”,潜台词通常就是在考你是否具备规范的组合逻辑编码习惯。
Latch 产生原因
最常见原因是条件分支不完整,也就是某个输出信号在某些条件下没有被赋值,综合器只能让它“保持之前的值”,于是就推断出 latch。
典型例子是 if 只有 if 没有 else,或者 case 没有覆盖全部分支,也没有 default。
例如下面这个组合逻辑就会推断 latch,因为 en=0 时 y 没有新值可赋,只能保持上一次结果。
always @(*) begin if (en) y = a; end
另一个常见原因是 case 语句没有覆盖所有可能输入值,导致部分路径没有赋值。
还有资料提到,组合逻辑块里敏感列表不完整也可能带来仿真与预期不一致的问题;现代写法通常直接使用 always @(*) 来减少这类遗漏风险。
如何规避
第一条原则是:组合逻辑中,目标输出在所有可能路径上都必须被赋值。
最直接的方法就是给 if 配完整的 else,给 case 加 default,确保不存在“ 某种条件下什么都不做”的情况。
例如下面这种写法就是安全的,因为 y 无论 en 是 0 还是 1 都有确定结果。
always @(*) begin if (en) y = a; else y = b; end
第二条常用方法是:在组合逻辑块开头先给输出一个默认值,再在后续分支中按条件覆盖它。
这种写法在复杂 if-else 或 case 结构里特别实用,因为它能显著降低遗漏分支的风险。
always @(*) begin y = 8'h00; if (en) y = a; end
第三条原则是:需要存储行为时,用时钟触发寄存器明确表达,不要靠“漏赋值”去隐式实现保持功能。
也就是说,如果你本意是“这个值要保持到下一拍”,那就应该写成 always @(posedge clk) 的寄存器逻辑,而不是在组合逻辑里故意少写分支。
