深入理解 Verilog 中的赋值间延迟与赋值内延迟
Verilog 延迟语句可以在赋值运算符的左侧或右侧指定延迟值。
赋值间延迟
// 延迟指定在左侧 #<delay> <LHS> = <RHS>
赋值间延迟语句的延迟值位于赋值运算符的左侧(LHS)。这表示该语句本身在延迟过期后执行,是最常用的延迟控制形式。
module tb; reg a, b, c, q; initial begin $monitor("[%0t] a=%0b b=%0b c=%0b q=%0b", $time, a, b, c, q); // 在时间 0 初始化所有信号为 0 a <= 0; b <= 0; c <= 0; q <= 0; // 赋值间延迟:等待 #5 时间单位 // 然后将 a 和 c 赋值为 1。注意 'a' 和 'c' // 在当前时间步结束时更新 #5 a <= 1; c <= 1; // 赋值间延迟:等待 #5 时间单位 // 然后将 'q' 赋值为 RHS 计算出的值 #5 q <= a & b | c; #20; end endmodule
请注意,q 在 10 个时间单位时变为 1,因为该语句在 10 时间单位时被求值,而 RHS(a、b、c 的组合)计算结果为 1。
仿真日志
xcelium> run [0] a=0 b=0 c=0 q=0 [5] a=1 b=0 c=1 q=0 [10] a=1 b=0 c=1 q=1 xmsim: *W,RNQUIE: Simulation is complete.
赋值内延迟
// 延迟指定在右侧 <LHS> = #<delay> <RHS>
赋值内延迟是指赋值运算符右侧(RHS)存在延迟。这表示语句首先被求值,捕获 RHS 上所有信号的值,然后仅在延迟过期后将结果赋给目标信号。
module tb; reg a, b, c, q; initial begin $monitor("[%0t] a=%0b b=%0b c=%0b q=%0b", $time, a, b, c, q); // 在时间 0 初始化所有信号为 0 a <= 0; b <= 0; c <= 0; q <= 0; // 赋值间延迟:等待 #5 时间单位 // 然后将 a 和 c 赋值为 1。注意 'a' 和 'c' // 在当前时间步结束时更新 #5 a <= 1; c <= 1; // 赋值内延迟:首先执行语句 // 然后等待 5 个时间单位,再将求值后的值赋给 q q <= #5 a & b | c; #20; end endmodule
注意仿真日志中 q 的赋值缺失!
仿真日志
xcelium> run [0] a=0 b=0 c=0 q=0 [5] a=1 b=0 c=1 q=0 xmsim: *W,RNQUIE: Simulation is complete.
这是因为在 5 个时间单位时,a 和 c 使用非阻塞赋值语句赋值。非阻塞赋值的行为是:先求值 RHS,但直到该时间步结束才真正赋值给变量。
因此 a 和 c 的值虽求值为 1,但尚未赋值,当下一个非阻塞语句(q 的赋值)执行时,a 和 c 仍保持旧值 0,导致 q 的 RHS 求值为 0,$monitor 未检测到变化而不显示。
为观察变化,将 a 和 c 的赋值语句从非阻塞改为阻塞赋值。
... // 非阻塞改为阻塞,其余代码不变 #5 a = 1; c = 1; q <= #5 a & b | c; ...
仿真日志
xcelium> run [0] a=0 b=0 c=0 q=0 [5] a=1 b=0 c=1 q=0 [10] a=1 b=0 c=1 q=1 xmsim: *W,RNQUIE: Simulation is complete.
Verilog