Verilog阻塞与非阻塞赋值详解-FPGA常见问题社区-FPGA CPLD-ChipDebug

Verilog阻塞与非阻塞赋值详解

导言

关于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


图片[1]-Verilog阻塞与非阻塞赋值详解-FPGA常见问题社区-FPGA CPLD-ChipDebug
时序图

如之前所讲,阻塞赋值是顺序敏感,而非阻塞赋值是顺序独立的。从两份代码所仿真的时序图所示,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]-Verilog阻塞与非阻塞赋值详解-FPGA常见问题社区-FPGA CPLD-ChipDebug

上面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

图片[3]-Verilog阻塞与非阻塞赋值详解-FPGA常见问题社区-FPGA CPLD-ChipDebug图片[4]-Verilog阻塞与非阻塞赋值详解-FPGA常见问题社区-FPGA CPLD-ChipDebug

对于具有阻塞赋值的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的论文中关于阻塞和非阻塞所描述的原则结束本篇文章:

  1. 在时序的模块中使用非阻塞赋值。
  2. 当使用always块来描述组合逻辑时,使用阻塞赋值。
  3. 当在同一个always块中描述时序和组合逻辑时,使用非阻塞赋值。
  4. 在同一个always块中不要混合使用阻塞和非阻塞赋值。
请登录后发表评论

    没有回复内容