基本概念
关于阻塞赋值(=
)和非阻塞赋值(<=
), 阻塞赋值(=
)是顺序敏感的,非阻塞赋值(<=
)是顺序独立的。阻塞赋值按它们在程序块中列出的顺序顺序执行。当它们被执行时,它们会立即对抽象 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
块中不要混合使用阻塞和非阻塞赋值。
摘自“亦安的数字小站”