文章目录
前言一、非阻塞赋值的仿真语义(关键点)二、通过具体例子演示(最直观)示例 1 — 非阻塞赋值(期望的“并行”更新)示例 2 — 阻塞赋值(会出现顺序依赖的问题)
三、为什么这跟真实硬件的 D 触发器匹配?四、 delta cycle / 仿真细节(进阶)五、 额外例子:多寄存器并行更新六、 实战建议(工程经验)七、 总结(一句话)
前言
我们把说明拆成几部分:语义(仿真规则)→ 例子与时序步骤 → 硬件映射 → 实战建议。过程尽量一步步推导,带具体数值与“波形”表格,便于理解。
一、非阻塞赋值的仿真语义(关键点)
在 always @(posedge clk) 中使用非阻塞赋值 <= 时,Verilog 的仿真采用两阶段行为:
计算阶段(sample): 在时钟沿到达时,所有非阻塞赋值的右侧表达式(RHS)都用当前的旧值求值并暂存(即“读取旧值”)。
更新阶段(update): 随后(在相同的仿真时间点的稍后delta cycle),把这些预先计算好的右侧结果写回到左侧信号(寄存器)。
关键: RHS 的读取发生在“同时”的瞬间,写回发生在“统一的后时刻”。因此多个寄存器之间不会互相看到已更新的新值 —— 它们都看到时钟到来前的旧值。
二、通过具体例子演示(最直观)
示例 1 — 非阻塞赋值(期望的“并行”更新)
reg a, b;
initial begin
a = 0;
b = 1;
end
always @(posedge clk) begin
a <= b;
b <= a;
end
假设在上升沿到来之前:a = 0, b = 1。
按非阻塞语义:
计算阶段(在 posedge 时刻)
计算 RHS_a = b → 取得旧值 1
计算 RHS_b = a → 取得旧值 0
将 (a ← 1) 和 (b ← 0) 放入“更新队列”
更新阶段(同一时刻稍后一个 delta)
把 a 更新为 1,b 更新为 0
**结果:**a = 1, b = 0 —— 完成交换(swap),符合硬件寄存器在同一个时钟边沿并行采样旧值然后并行更新的实际行为。
我们用时间表表示:
| 时刻 | 动作 | a | b |
|---|---|---|---|
| t0 (之前) | 初始 | 0 | 1 |
| posedge — sample | RHS_a = b(=1); RHS_b = a(=0) | 0 | 1 |
| posedge — update | 写回:a=1, b=0 | 1 | 0 |
示例 2 — 阻塞赋值(会出现顺序依赖的问题)
always @(posedge clk) begin
a = b;
b = a;
end
同样初始 a=0, b=1,阻塞赋值语义是逐条立即执行:
a = b; 立刻把 a 赋为 1(因为读取 b 的旧值 1)
接着执行 b = a; —— 现在 a 已是 1,所以 b 被赋为 1
结果:a = 1, b = 1 —— 没有完成交换。这就是“读写不一致/顺序依赖”问题的典型例子:因为阻塞赋值让后面的语句看到前面刚修改过的值。
阻塞的时间表:
| 时刻 | 动作 | a | b |
|---|---|---|---|
| t0 (之前) | 初始 | 0 | 1 |
| posedge | 执行 → a=1 |
1 | 1 |
| posedge | 执行 → b=1 (读取的是刚刚更新的a) |
1 | 1 |
三、为什么这跟真实硬件的 D 触发器匹配?
真实的同步电路中,每个 D 触发器在时钟上升沿同时把其 D 输入采样到内部并在稍后并行输出更新。也就是说,硬件上的行为本质是“看旧值并并行写回”。非阻塞赋值 (<=) 的仿真规则正是为匹配这种硬件行为而设计的:
每个寄存器的输入在时钟沿“同一刻”被采样(RHS 读取旧值);
然后所有寄存器同时更新(统一写回)。
阻塞赋值 (=) 在仿真中是顺序语义,容易引入仿真与综合后的实际硬件行为不一致的问题(尤其当你在一个时钟边沿内写多个寄存器并期望并行行为时)。
四、 delta cycle / 仿真细节(进阶)
在 Verilog 仿真器中,非阻塞赋值的写回通常被安排在同一时间点的下一个 delta cycle(仿真器内部的微小步骤)进行。这保证了:
同一时间点内所有非阻塞赋值的 RHS 都先评估(取样旧值);
然后在更新阶段统一写回,不会让同一时刻的其它语句看到已更新的新值。
这就是为什么 a <= b; b <= a; 能完成“并行交换”。
五、 额外例子:多寄存器并行更新
always @(posedge clk) begin
r0 <= r1 + 1;
r1 <= r2 + 1;
r2 <= r0 + 1;
end
在非阻塞语义下,每个 RHS 都使用 posedge 之前的 r0,r1,r2 的值来计算新值,然后再统一更新 —— 与硬件并行采样行为一致。阻塞赋值则会导致依赖链条上的寄存器看到“刚被更新的值”,改变计算结果。
六、 实战建议(工程经验)
时序逻辑(时钟事件的 always)用非阻塞赋值 <=。
这能确保寄存器并行采样、消除同一 always 内因为顺序导致的“假竞争”。
组合逻辑(always @*)用阻塞赋值 =。
容易编写“组合电路”且语义直观。
不要在同一模块/同一信号上同时用两个 always 块驱动同一信号。 这会造成多驱动(多个驱动器),不是非阻塞能解决的。
避免在时序逻辑中既使用阻塞又使用非阻塞赋值来读写同一寄存器(会引起困惑和潜在 bug)。
跨时钟域数据不要靠非阻塞把问题“掩盖”:非阻塞只处理同一时钟域的并行采样,不解决亚稳态或时钟域交互问题(需要同步器/握手)。
七、 总结(一句话)
非阻塞赋值通过先采样 RHS(读取旧值)再统一写回的两阶段语义,正确模拟(并匹配)硬件中寄存器在同一时钟沿并行采样并更新的行为,从而消除了因顺序赋值造成的寄存器间“读写不一致”或“伪竞争”。但它不解决组合多重驱动、跨时钟域同步等其他类型的竞争。
















暂无评论内容