FPGA 学习笔记(五):阻塞赋值和非阻塞赋值的比较
在 Verilog 时序逻辑设计中,正确理解 =(阻塞赋值)和 <=(非阻塞赋值)的区别是避免仿真与综合不一致、杜绝竞争冒险的关键。
1. 阻塞赋值(Blocking Assignment, =)
阻塞赋值的行为类似于 C 语言:语句按顺序执行。在同一个 always 块中,后面的语句必须等待前面的语句执行完毕后才能执行。
1.1 设计目标
通过两个对比模块 block_0 和 block_1,观察改变赋值顺序对最终输出逻辑的影响。
1.2 RTL 代码实现
1 | |
知识点解析:
- 逻辑行为:在
block_0中,out在同一个时钟上升沿就拿到了a+b+c的结果;而在block_1中,out拿到的是“旧的 d”与c相加的结果。 - 顺序依赖:在阻塞赋值中,语句的书写顺序直接决定了电路的逻辑功能。
2. 非阻塞赋值(Non-blocking Assignment, <=)
非阻塞赋值的特点是“并行更新”:在时钟有效沿到来时,所有等号右边的表达式先计算,在时序步结束时统一更新到左边的变量中。
2.1 设计目标
通过 noblock_0 和 noblock_1 验证非阻塞赋值的并行特性和顺序无关性。
2.2 RTL 代码实现
1 | |
知识点解析:
- 并行更新:在非阻塞赋值中,所有的赋值动作被视为同时发生。
out拿到的永远是时钟沿到来前d的旧值。 - 顺序无关性:对比
noblock_0和noblock_1可以发现,改变语句顺序并不会改变逻辑结果,这对于描述硬件的并行特性至关重要。
3. 仿真验证与波形对比
为了直观观察四者的区别,我们可以编写一个统一的 Testbench。
3.1 测试脚本 (sim/block_noblock_tb.v)
1 | |
3.2 仿真结果分析
通过波形观察

当输入信号 {a, b, c} 从 000 变为 111(发生于第 201ns,第一个生效时钟沿在 210ns),再变为 010 时,各模块的输出呈现出显著的序列差异:
out2(block_0):变化序列为0 -> 3 -> 1。out0, out1, out3(noblock & block_1):变化序列为0 -> 1 -> 3 -> 2 -> 1。
深度解析:
- 为什么
block_1表现得像非阻塞赋值?
在block_1中,代码先执行out = d + c后执行d = a + b。在时钟上升沿到来时,out率先读取了d的旧值(上一个周期的结果)进行计算,随后d才被更新。这种由于“书写顺序”导致的逻辑延迟,在功能上模拟了非阻塞赋值(及触发器级联)的行为。 - 为什么所有模块的
d数值都一样?
因为无论采用哪种赋值方式,在每个时钟沿处理结束后的“稳定状态”下,d最终都被赋予了当前时刻a+b的计算结果。差别不在于d本身,而在于out计算时使用的是d的“更新前”还是“更新后”的值。 - 流水线效应(Pipeline):
- 在
block_0中,out在同一个时钟沿就拿到了a+b+c的完整结果。 - 在非阻塞赋值模块中,输入的变化需要经过两个时钟沿的“接力”才能完全传递到
out(第一次沿d变,第二次沿out变)。这在硬件上对应了两个级联的触发器,即流水线结构。
- 在
4. 总结
- 时序逻辑(
always @(posedge Clk)):一律使用 非阻塞赋值<=。 - 组合逻辑(
always @(*)):一律使用 阻塞赋值=。 - 禁止混用:在同一个
always块中,严禁混用=和<=。 - 单一赋值:同一个变量不应在多个
always块中被赋值。
FPGA 学习笔记(五):阻塞赋值和非阻塞赋值的比较
http://blog.556756.xyz/2026/02/26/FPGA/Block_Unblock/