如何解释Verilog中的阻塞和非阻塞赋值?

28

我对阻塞和非阻塞赋值在绘制硬件图时的解释有些困惑。我们是否需要推断出非阻塞赋值给我们提供了一个寄存器?那么根据这个语句 c <= a+b ,c会是一个寄存器,但a和b不是吗?

module add (input logic clock,  
output logic[7:0] f);   

logic[7:0] a, b, c;  

always_ff @(posedge clock)  
begin   
  a = b + c;   
  b = c + a;   
  c <= a + b;  
end   

assign f = c;  

endmodule

7
我推荐这个由EDA传奇人物所做的演示:http://www.sutherland-hdl.com/papers/1996-CUG-presentation_nonblocking_assigns.pdf - Ross Rogers
6个回答

44
传统的Verilog智慧是错的。对于一个本地变量使用阻塞赋值没有问题。然而,你永远不应该在同步通信中使用阻塞赋值,因为这是不确定性的。
在时钟始终块内部使用非阻塞赋值将总是推断出一个触发器,由语义所决定。
是否在时钟始终块内使用阻塞赋值推断出一个触发器完全取决于它的使用方式。如果有可能在分配之前读取变量,则会推断出一个触发器。否则,这就像一个临时变量,并且会导致一些组合逻辑。

35
起初,理解阻塞和非阻塞赋值之间的差异确实有点棘手。但不用担心——有一个方便的经验法则:
如果你想使用always块来推断组合逻辑,请使用阻塞赋值符号(=)。如果你想使用时钟控制的always块来推断时序逻辑,请使用非阻塞赋值符号(<=)。尽量不要混用两者。
上面的代码可能不是最好的例子。因为不知道你要构建什么样的加法器/触发器结构,存在出现组合反馈路径(这是不好的)的危险。而且由于没有输入总线,你本质上是在空中构建abc
但是回答你的问题,任何在时钟控制的always块内赋值的变量都会推断为触发器,除非使用阻塞操作符(=)进行赋值并将其作为一种本地变量使用。
module add
  (
   input clock,
   input [7:0] in1,
   input [7:0] in2,
   output logic [7:0] f1, f2, f3, f4, f5
   );   


   // f1 will be a flipflop
   always_ff @(posedge clock) begin
      f1 = in1 + in2;
   end


   // f2 will be a flipflop
   always_ff @(posedge clock) begin
      f2 <= in1 + in2;
   end


   // f3 will be a flipflop
   // c1 will be a flipflop
   logic [7:0] c1;
   always_ff @(posedge clock) begin
      c1 <= in1 + in2;
      f3 <= c1 + in1;
   end


   // f4 will be a flipflop
   // c2 is used only within the always block and so is treated
   // as a tmp variable and won't be inferred as a flipflop
   logic [7:0] c2;
   always_ff @(posedge clock) begin
      c2 = in1 + in2;
      f4 = c2 + in1;
   end


   // c3 will be a flipflop, as it's used outside the always block
   logic [7:0] c3;
   always_ff @(posedge clock) begin
      c3 = in1 + in2;
   end

   assign f5 = c3 + in1;

endmodule

在一个 always 块中不要混用阻塞和非阻塞赋值的一个很重要的原因是,混合使用这两种赋值可能会导致 RTL 仿真和门级仿真/实际硬件操作之间产生严重的仿真不匹配。Verilog 仿真器对 =<= 的处理方式有很大区别。阻塞赋值的意思是“立即将值分配给变量”。非阻塞赋值的意思是“找出要分配给此变量的值,并将其存储起来,在某个未来的时间进行分配”。想更好地理解这一点可以阅读这篇好的论文:也可参见:http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf

2

我也曾经为此感到困惑。

但首先,你应该明白非阻塞或阻塞实际上与是否创建锁存器/触发器无关!

你可以从以下角度简单地理解它们的区别(起初):i. 如果使用阻塞,则在赋值语句的左侧被赋值之前,其后的语句将无法执行,因为如果变量被使用,那么更改为它的左侧的内容会更新并被使用。但是对于非阻塞,它不会像下面的语句一样阻塞,而是与下面的语句并行执行(实际上RHS计算应该先完成,但当你感到困惑时可以忽略它)。这次执行周期中LHS不会更改/更新(在下一个总是块再次触发时更新)。下面的语句使用旧值,因为它在执行周期结束时更新。

a = 0; b= 0;
a = 1;
b = a;
--> output a = 1, b = 1;
a = 0; b= 0;
a <= 1;
b = a;
--> output a = 1, b = 0;

一个关键点是查找你的代码(始终块)中是否有任何情况变量未分配值但可能发生。如果不向它传递值并且出现该情况,则会创建拉奇/触发器来保持该值。

例如,

always @(*) begin
    if(in) out = 1;
    else out = 0;
end
--> this end without latch/ff
always @(*) begin
    if(in) out = 1;
end
--> this end with one latch/ff to keep value when in = 0, as it might happen and you didn't assign value to out as in=1 do. 

以下操作也会创建锁存器/触发器:
always @(*) begin
    if(in) a = 1;
    else b = 1;
end

--> 当in=1时,b没有赋值,当in=0时,a没有赋值,创建了latch/ffs。

另外,当你感应到clk的posedge时always @(posedge clk),它必须以latch/ff结尾。因为对于clk,必须存在负边缘,并且你没有做任何操作,所以创建了latch/ff来保留所有旧值!


2
只想补充一下Jan Decaluwe的回答。尽管他说得非常正确,但似乎实际上很少有代码使用他所描述的内容。现在混合阻塞和非阻塞语句已成为禁忌,这要感谢Cummings先生。
问题在于,大多数地方都避免使用阻塞语句作为局部变量,并且在Google的搜索范围内几乎找不到任何一个给出示例的代码。我唯一找到Jan提到的编码风格的地方是这篇文章中获胜的代码。而且,这是我偶然发现的。

0

你可以随时在数字领域解释Verilog,只要你理解如果你编写的相同代码将被转换为门级电路会发生什么,我个人不遵循使用非阻塞在序列中或在组合中使用阻塞的规则,这将限制你的思维。只需坚持代码的数字方面即可。 这是如果您的代码转换为门级电路将发生的情况,只需查看您想要的内容

  1. 首先制作完整加法器 - 输入a和b。
    1. 输出将进入触发器,创建具有clk同步的输出a。
    2. 现在,由于赋值是阻塞的,所以新a将被应用于下一个完整添加器,该完整添加器具有这个新a和c作为输入,并且它的输出将进入dffcsync到clk创建新b。
    3. 现在,因为b = c + a;存在于阻塞语句中,所以b会更新为这个新b。
    4. 现在它是c<=a+b。现在会创建一个完整加法器,其具有a和b作为输入,其进入dff sync到clk,现在是否还有其他条件,例如再次说a=c;
    5. 然后将创建一个dff,其中包含旧的c而不是非阻塞语句创建的新c,并且此dff sync到clk的输出进入a并更新a。

谢谢 问候 Rahul jain


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接