Verilog初级教程(15)Verilog中的阻塞与非阻塞语句

前言

本文通过仿真的方式,形象的说明阻塞赋值以及非阻塞赋值的区别,希望和其他教程相辅相成,共同辅助理解。

正文

阻塞赋值

阻塞赋值语句使用=进行赋值,并在程序块中一个接一个地执行。但是,这不会阻止在并行块中运行的语句的执行。
通过仿真最容易理解,下面是仿真文件:

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a = 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    b = 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c = 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    d = 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	e = 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

请注意,当仿真开始时,有两个初始块是并行执行的。语句在每个块中依次执行,两个块在时间0ns处结束。更具体的说,变量a首先被分配,然后是显示语句,接着是所有其他语句。这在输出中可以看到,变量b和c在第一条显示语句中是8’hxx。这是因为当调用第一个$display时,变量b和c的赋值还没有被执行。

打印执行结果:

[0] a=0xda b=0xxx c=0xxx
[0] a=0xda b=0xf1 c=0xxx
[0] a=0xda b=0xf1 c=0x30
[0] d=0xaa e=0xxx
[0] d=0xaa e=0x55

在接下来的例子中,我们将在同一组语句中添加一些延迟,看看它的表现。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a = 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b = 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c = 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    #5 d = 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e = 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真记录:

Time resolution is 1 ps
[0] a=0xda b=0xxx c=0xxx
[5000] d=0xaa e=0xxx
[10000] a=0xda b=0xf1 c=0xxx
[10000] a=0xda b=0xf1 c=0x30
[10000] d=0xaa e=0x55

综上,顺序执行或者串行执行一览无余。

非阻塞赋值

非阻塞赋值允许在不阻塞下面语句执行的情况下安排赋值,并由<=符号指定。值得注意的是,同一个符号在表达式中被用作关系运算符,在非阻塞赋值的上下文中被用作赋值运算符。如果我们以上面的第一个例子为例,将all = symobls替换为非阻塞赋值操作符<=,我们会看到输出的结果有一些不同。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

先给出仿真结果:

Time resolution is 1 ps
[0] a=0xxx b=0xxx c=0xxx
[0] a=0xxx b=0xxx c=0xxx
[0] a=0xxx b=0xxx c=0xxx
[0] d=0xxx e=0xxx
[0] d=0xxx e=0xxx

看到所有的$display语句都打印了’h’x。这种行为的原因在于非阻塞赋值的执行方式。特定时间步长的每一条非阻塞语句的RHS都会被捕获,并转入下一条语句。被捕获的RHS值只有在时间步长结束时才会分配给LHS变量。

所以,如果我们把上面例子的执行流程分解一下,我们会得到如下图所示的东西。
注:从前几期可以知道RHS为右值。

|生成块1:初始化
| 时间 #0ns : a <= 8'DA, 为非阻塞,所以记下 RHS (8'hDA) 的值并执行下一步。
| 时间#0ns : $display()阻塞了,所以执行这条语句,但是a还没有收到新的值,所以a=8'hx。
| 时间 #0ns : b <= 8'F1, 为非阻塞,所以记下 RHS 的值 (8'hF1) 并执行下一步。
| 时间#0ns : $display()阻塞,所以执行此语句。但b还没有收到新的值,所以b=8'hx。
| 时间#0ns : c <= 8'30, 非阻塞,所以记下RHS值(8'h30)并执行下一步。
| 时间#0ns : $display()被阻塞,所以执行这条语句,但c还没有收到新的值,所以c=8'hx。
| 时间步骤和初始块结束,将捕获的值分配到变量a、b、c中。
|
|生成块2:初始化
| 时间#0ns : d <= 8'AA, 为非阻塞,所以记下RHS值(8'hAA)并执行下一步。
| 时间#0ns : $display()被阻塞,所以执行这条语句,但d还没有收到新的值,所以d=8'hx。
| 时间#0ns : e <= 8'55, 是非阻塞的,所以记下RHS值(8'h55)并执行下一步。
| 时间#0ns : $display()阻塞,所以执行这条语句,但e没有收到新的值,所以e=8'hx。
| 时间步骤和初始块结束,将捕获的值分配到变量d和e中。
|
|仿真结束在#0ns

接下来,我们用第二个例子,将所有阻塞语句替换成非阻塞语句。

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    #5 d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

恐怕这就有点让人头大了。

给出仿真结果,在进行分析:

Time resolution is 1 ps
[0] a=0xxx b=0xxx c=0xxx
[5000] d=0xxx e=0xxx
[10000] a=0xda b=0xxx c=0xxx
[10000] a=0xda b=0xxx c=0xxx
[10000] d=0xaa e=0xxx

这种仿真结果的意思说明了阻塞赋值的特性,即在当前时刻不立即赋值,只有在一定时间步长结束时才赋值。
上面的例子,在0时刻,执行了如下语句:

    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);

由于是非阻塞赋值,所以当前a并不能立即得到右值8’hDA,拿第一个initial为例,在10ns,就得到了这个值8’hda,但是其他值(b和c)仍然为x。
下一个非10ns时刻,我们就可以得到a、b和c的具体值,为了验证,我们添加一条语句:

#1
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);

整体程序为:

module assign_tb();
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #1
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);    
  end

  initial begin
    #5 d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule

仿真结果:

Time resolution is 1 ps
[0] a=0xxx b=0xxx c=0xxx
[5000] d=0xxx e=0xxx
[10000] a=0xda b=0xxx c=0xxx
[10000] a=0xda b=0xxx c=0xxx
[10000] d=0xaa e=0xxx
[11000] a=0xda b=0xf1 c=0x30

我们主要关注:

[11000] a=0xda b=0xf1 c=0x30

可见,得到了验证。

最后,给出这段非阻塞赋值仿真程序的执行过程:

module tb;
  reg [7:0] a, b, c, d, e;

  initial begin
    a <= 8'hDA;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    #10 b <= 8'hF1;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
    c <= 8'h30;
    $display ("[%0t] a=0x%0h b=0x%0h c=0x%0h", $time, a, b, c);
  end

  initial begin
    #5 d <= 8'hAA;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
 	#5 e <= 8'h55;
    $display ("[%0t] d=0x%0h e=0x%0h", $time, d, e);
  end
endmodule
|在#0ns处生成Block1:初始化。
| 时间 #0ns : a <= 8'DA, 为非阻塞,所以记下 RHS (8'hDA) 的值并执行下一步。
| 时间#0ns : $display()阻塞了,所以执行这条语句,但是a还没有收到新的值,所以a=8'hx。
| 时间步骤结束:将捕获的值分配给变量a,现在a是8'hDA。
| 等到时间前进10个时间单位到#10ns。
|	
| 时间 #10ns : b <= 8'F1, 非阻塞,所以记下 RHS 的值 (8'hF1) 并执行下一步。
|时间#10ns : $display()阻塞,所以执行这条语句。但b还没有收到新的值,所以b=8'hx。
| 时间#10ns : c <= 8'30, 是非阻塞的,所以记下RHS值(8'h30)并执行下一步。
| 时间#10ns : $display()阻塞,所以执行此语句。但是c没有收到新的值,所以c=8'hx。
| 时间步骤和初始块结束,将捕获的值分配到变量b、c中。
|	
|在#0ns处生成Block2:初始化。
| 等到时间前进5个时间单位到#5ns。
|	
| 时间 #5ns : d <= 8'AA, 为非阻塞,所以记下 RHS 的值 (8'hAA) 并执行下一步。
| 时间#5ns : $display()阻塞,所以执行此语句。但d没有收到新的值,所以d=8'hx。
| 时间步骤结束:将捕获的值分配给变量d,现在d是8'hAA。
| 等到时间前进5个时间单位到#5ns。
|	
| 时间#10ns : e <= 8'55, 非阻塞,所以记下RHS值(8'h55)并执行下一步。
| 时间#10ns : $display()阻塞,所以执行这条语句,但e还没有收到新的值,所以e=8'hx。
| 时间步骤和初始块结束,将捕获的值分配给变量e,现在e是8'h55。
|
|仿真结束在#10ns

往期回顾

Verilog初级教程(14)Verilog中的赋值语句

Verilog初级教程(13)Verilog中的块语句

Verilog初级教程(12)Verilog中的generate块

Verilog初级教程(11)Verilog中的initial块

Verilog初级教程(10)Verilog的always块

Verilog初级教程(9)Verilog的运算符

Verilog初级教程(8)Verilog中的assign语句

Verilog初级教程(7)Verilog模块例化以及悬空端口的处理

Verilog初级教程(6)Verilog模块与端口

Verilog初级教程(5)Verilog中的多维数组和存储器

Verilog初级教程(4)Verilog中的标量与向量

Verilog初级教程(3)Verilog 数据类型

Verilog初级教程(2)Verilog HDL的初级语法

Verilog初级教程(1)认识 Verilog HDL

芯片设计抽象层及其设计风格

Verilog以及VHDL所倡导的的代码准则

FPGA/ASIC初学者应该学习Verilog还是VHDL?

参考资料以及推荐关注

Verilog Blocking & Non-Blocking

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/107436015