导言
关于Verilog阻塞与非阻塞的问题,非常多的教程给出了解释,但大多仅给出一些关于阻塞非阻塞的设计原则,没有更加详细的讲解,大多也没有给出可对比的示例。最近再次看了Clifford E. Cummings关于阻塞与非阻塞的经典论文,再结合一些书籍,亦安详细写一篇关于阻塞和非阻塞的文章,希望能对有困惑的朋友有一些帮助。
基本概念
关于阻塞赋值(=)和非阻塞赋值(<=), 阻塞赋值(=)是顺序敏感的,非阻塞赋值(<=)是顺序独立的。阻塞赋值按它们在程序块中列出的顺序顺序执行。当它们被执行时,它们会立即对抽象 reg 的内容产生影响,阻塞必须在执行下一个赋值之前执行。非阻塞赋值在对左侧抽象 reg 进行赋值之前,评估程序块中每个语句右侧的表达式,并同时执行。
顺序敏感和顺序独立示例
// Blocking assignment executes sequentially . initial begin a=#12 1; b=#3 0; c=#2 3;
// Non-blocking assignment executes in parallel.
initial begin
d <=#12 1;
e <=#3 0;
f <=#2 3;
end
如之前所讲,阻塞赋值是顺序敏感,而非阻塞赋值是顺序独立的。从两份代码所仿真的时序图所示,abc是顺序执行的,而非阻塞赋值def则是同时执行。这也意味着,如果改变阻塞赋值语句顺序,那么会得到不同的结果。如下面2段代码改变阻塞赋值顺序综合出结果不同,前面代码综合出1个DFF,而第二段代码综合出2个DFF。
always @(posedge clk) begin rega = data; regb = rega; end
always @(posedge clk) begin regb = rega; rega = data; end
而对于非阻塞赋值,仅改变语句的顺序并不会改变结果,如下面2段代码综合出的结果是一样的。
reg qa,qb,qc; always @(posedge clk) begin qa <= a; qb <= qa; qc <= qb; end
reg qa,qb,qc; always @(posedge clk) begin qc <= qb; qb <= qa; qa <= a; end
为什么always块组合逻辑使用阻塞赋值?
always @(a or b or c or d) begin t1 = a & b; t2 = c | d; out = t1 & t2; end
always @(a or b or c or d or t1 or t2) begin t1 <= a & b; t2 <= c | d; out <= t1 & t2; end
上面2段代码综合出的结果都是一致的,区别就是当使用非阻塞赋值时,敏感列表需要加上t1和t2。对于具有阻塞分配的 always 块,always 块的敏感列表包含组合电路的所有输入 a、b、c 和 d。每次输入改变时,总是阻塞,因此输出结果,必须重新评估。此时 always 块中的语句是按顺序执行的,输入的最新值用于确定 t1 和 t2,最后使用新的 t1 和 t2 计算出。
在具有非阻塞赋值的 always 块中,语句是同时执行的。因此,当敏感列表中信号改变触发always块执行时,out 将使用 t1 和 t2 的旧值,因为它们的新值尚不可用。为确保在组合电路中具有相同的行为,除了电路的输入信号之外,还应将电路的内部信号 t1 和 t2 放入敏感列表中。每次更新 t1 和 t2 的值时,这将重新触发(重新进入)always 块,使输出最终能够计算其新值。然而,这个模型相对复杂并且可能会造成混淆,所以always模块组合电路的描述应该使用阻塞赋值。
为什么always模块描述时序使用非阻塞?
always @(posedge clk) begin t1 = a & b; t2 = t1 & c; out = t1 & t2; end
always @(posedge clk) begin t1 <= a & b; t2 <= t1 & c; out <= t1 & t2; end
对于具有阻塞赋值的always块,在clk的每一个上升沿,三个赋值顺序执行。因此,t1 使用 clk 的上升沿处的 a 和 b 的值更新,然后 t2在clk上升沿使用 t1 的新值和c的值更新自己的值。最后,使用 t1 和 t2 的新值评估 out。可以看出,t1和t2只是用于临时存储,方便对复杂表达式进行分区;它们不代表真正的硬件寄存器输出,甚至可能被优化掉。值得注意的是,组合电路已经优化为 out = t1&t2 = (a&b)&(t1&c) = (a&b)&(a&b&c) = a&b&c = t1&c = t2。
对于具有非阻塞赋值的 always 块,在clk的每个上升沿处,同时执行三个赋值:(1)t1 在 clk 的上升沿处由 a 和 b 的值更新(2)同时t2使用旧t1的值(其新值此时不可用)和 c 在 clk 的上升沿更新值,以及(3)同时使用 t1的旧值和t2的旧值(它们的新值此时不可用)更新 out。
如图所示,阻塞和非阻塞分配描述了完全不同的时序电路。根据阻塞和非阻塞分配的行为,它们分别表示一个和三个触发器。也就是说,当 t1 和 t2 使用阻塞赋值来描述时,它们是组合输出而不是时序输出。因此,该模型可能会非常混乱,所以时序的always模块仅使用非阻塞赋值。
总结
本文主要介绍阻塞赋值和非阻塞赋值的基本概念和运行机理,以及分析不同always块应该使用阻塞还是非阻塞,在记住相关规则的情况下,能理解原因也是非常重要的。亦安以Clifford E. Cummings的论文中关于阻塞和非阻塞所描述的原则结束本篇文章:
-
在时序的模块中使用非阻塞赋值。 -
当使用always块来描述组合逻辑时,使用阻塞赋值。 -
当在同一个always块中描述时序和组合逻辑时,使用非阻塞赋值。 -
在同一个always块中不要混合使用阻塞和非阻塞赋值。
没有回复内容