本文章采用阻塞赋值和非阻塞赋值对比的方式来讨论二者的区别,利用简单的例程+波形图直观查看细小差别。
1.模块文件:
module block_nonblock(clk50M,rst_n,a,b,c,out);
input clk50M;
input rst_n;
input a; //实现a+b+c=out,拆分成两部。一步是定义d且d=a+b,另一布是out=c+d
input b;
input c;
output reg [1:0]out; //out最大是3,所以两位足够
reg [1:0] d; //只要用到这个d,就增加了一步寄存a+b的过程(肯定意味着延时)
always@(posedge clk50M or negedge rst_n) begin
if(!rst_n)
out=2'd0;
else begin
d=a+b; //情况1;情况2(out=d+c;d=a+b;);情况3(d<=a+b;out<=d+c;);情况4(out<=d+c;d<=a+b;)
out=d+c;
end
end
endmodule
注: 情况1:阻塞赋值顺序;
情况2:阻塞赋值逆序;
情况3:非阻塞赋值顺序;
情况4:非阻塞赋值逆序;
2.testbench文件:
`timescale 1ns/1ps
`define clock_period 20
module block_nonblock_tb;
reg clk;
reg rst_n;
reg a;
reg b;
reg c;
wire [1:0] out;
block_nonblock block_nonblock0(
.clk50M(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.c(c),
.out(out)
);
initial clk=1;
always #(`clock_period/2) clk=~clk;
initial begin
rst_n=0;
a=0;
b=0;
c=0;
#201;
rst_n=1;
#(`clock_period*200);
a=0;b=0;c=0;
#(`clock_period*200);
a=0;b=0;c=1;
#(`clock_period*200);
a=0;b=1;c=0;
#(`clock_period*200);
a=0;b=1;c=1;
#(`clock_period*200);
a=1;b=0;c=0;
#(`clock_period*200);
a=1;b=0;c=1;
#(`clock_period*200);
a=1;b=1;c=0;
#(`clock_period*200);
a=1;b=1;c=1;
#(`clock_period*200);
#(`clock_period*200);
$stop;
end
endmodule
3.RTL电路图:
前两幅图可以看出,阻塞赋值时,语句顺序不同电路也会不同。后两幅图非阻塞赋值时,语句顺序改变时,电路稳定不变化。
4.波形图:
这是非阻塞赋值顺序情况3的波形图。可以看到由于d寄存器的缘故,如果d变化那么out将在下一拍发生变化(由于那个寄存器的缘故,使得out运算只与前一时刻的运算结果有关,而与当前无关,所以虽然顺序为out<=d+c;d<=a+b;,但是out只认上一个always@(posedge clk)中发生的事也就是只取决于上一个d和c。)。而且d和out所有的变化都不是在检测到时钟上升沿的一瞬间,是由于后仿真接近实际情况,会有电路延时。
5.非阻塞赋值的优化手段(从算法入手):
为什么out延时一拍才输出?就是因为多加了一句d<=a+b(从电路图上看,就是多加了一个寄存器),如果想要去掉这个延时,只需out<=a+b+c(即去掉这个寄存器)。这样a+b+c就完全是一个组合逻辑,不存在延迟。
6.总结:
在设计电路时应该避免设计阻塞赋值。因为阻塞赋值时,“语句顺序”的不同会生成不同的电路,这就造成了电路是一种不太确定的状态,顺序不同电路就会不同。
推荐采用非阻塞赋值,语句是并行的,而且不论“语句顺序”怎么样,电路结构都是唯一确定的。如果想优化设计,只需从算法上修改。