本文通过仿真的方式,形象的说明阻塞赋值以及非阻塞赋值的区别,希望和其他教程相辅相成,共同辅助理解。
阻塞赋值语句使用=
进行赋值,并在程序块中一个接一个地执行。但是,这不会阻止在并行块中运行的语句的执行。
通过仿真最容易理解,下面是仿真文件:
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