前言
对于阻塞赋值和非阻塞赋值,我在学校电工电子课上和之前的学习中有过粗浅的了解,在这之前的理解只是:组合逻辑只能用阻塞赋值,时序逻辑只能用非阻塞赋值。但经过学习,我对其有了更深的理解,并且我认为,深入了解其中的概念和用法是学习中必不可少的一步。
一、概念
- 阻塞赋值:阻塞赋值的赋值号用“=”表示。对应的电路结构往往与触发沿没有关系,只与输入电平的变化有关系。阻塞赋值的操作可以认为是只有一个步骤的操作,即计算赋值号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他 Verilog 语句的干扰,直到现行的赋值完成时刻,即把当前赋值号右边的值赋值给左边的时刻完成后,它才允许下一条的赋值语句的执行。串行块(begin-end)中的各条阻塞型过程赋值语句将以它们在顺序块后的排列次序依次执行。阻塞型过程赋值语句的执行过程是:首先计算赋值号右边的值,然后立即将计算结果赋值给左边,赋值语句结束,变量值立即发生改变。阻塞的概念是指在同一个 always 块中,其后面的赋值语句从概念上是在前一句赋值语句结束后再开始下面的赋值。
- 非阻塞赋值:非阻塞赋值的赋值号用“<=”表示。这是因为对应的电路结构往往与触发沿有关系,只有在触发沿的时刻才能进行非阻塞赋值。非阻塞操作开始时计算非阻塞赋值符的赋值号右边的语句,赋值操作结束时刻才更新赋值号左边的语句,可以认为是两个步骤(赋值开始时刻和结束时刻)来完成非阻塞赋值。在计算非阻塞语句赋值号右边的语句和更新赋值号左边的语句期间,其他的 Verilog 语句包括其他的 Verilog 非阻塞赋值语句都能同时计算赋值号右边的语句和更新赋值号左边的语句,允许其他的 Verilog 语句同时进行操作。非阻塞赋值的操作可以看作为两个步骤的过程:在赋值开始时刻,计算赋值号右边的语句。在赋值结束时刻,更新赋值号左边的语句。注意:非阻塞操作只能用于对寄存器类型变量进行赋值,因此只能用于“initial”和“always”块中,不允许用于连续赋值“assign”。
二、区别分析
对于概念可能初次接触会觉得云里雾里不知所云,上面(一、概念)部份可以不理解,但是加粗部分应该通透。下面进行实例分析,便可以更好的理解了。
1.简单变量解释
①阻塞赋值:
a=1;
b=2;
c=3;
begin
a=b;
c=a;
end
结果为:
a=2;b=2;c=2;
可以理解为是顺序执行。
②非阻塞赋值:
a=1;
b=2;
c=3;
begin
a<=b;
c<=a;
end
结果为:
a=2;b=2;c=1;
可以理解为是并行执行。
2.always块中的区别
①阻塞赋值:
- 代码:
module block_nonblock(
input wire clk,
input wire rst_n,
input wire [1:0]in,
output reg [1:0]out
);
reg [1:0]in_reg;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
in_reg=0;
out=0;
end
else begin
in_reg=in;
out=in_reg;
end
endmodule
- RTL视图为:
可以看出使用阻塞赋值后综合出来的是一个寄存器。
- 仿真测试模块:
`timescale 1ns/1ns
`define clk_peiod 20
module block_nonblock_tb;
reg clk;
reg rst_n;
reg [1:0]in;
wire [1:0]out;
block_nonblock block_nonblock(
.clk(clk),
.rst_n(rst_n),
.in(in),
.out(out)
);
initial clk=1;
always #(`clk_peiod/2) clk=~clk;
initial begin
rst_n=0;
in=0;
#(`clk_peiod*2+1);
rst_n=1;
end
always #41 in={$random}%4;
endmodule
(小提示:仿真设计输入时,在和上升沿对齐的地方输入变换时,可能会因软件和版本不同产生不同的延时效果,我使用的ModelSim ALTERA 10.3d,进行输入in时如果延时40ns(和时钟上升沿对齐),那么不会看到寄存器延一拍的现象,当设置为盐水41ns(和时钟上升沿不对齐)时,才可以看到真实的情况。)
分析:中间变量in_reg在in变化后的下一个时钟上升沿才会发生变化,所以会比in信号慢一拍,而out和in_reg信号间没有延迟现象,是在同一时间进行的,只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也将立刻变化,所以我们最终看到的结果是中间变量in_reg 和输出信号 out 是同时变化的。
②非阻塞赋值:
- 代码
module block_nonblock(
input wire clk,
input wire rst_n,
input wire [1:0]in,
output reg [1:0]out
);
reg [1:0]in_reg;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
in_reg=0;
out=0;
end
else begin
in_reg<=in;
out<=in_reg;
end
endmodule
- RTL视图为:
可以看出非阻塞赋值综合出来的是两个寄存器,这就会导致和阻塞赋值之间出现明显的延迟区别
- 仿真测试模块:
代码和阻塞赋值的代码相同。
分析:中间变量in_reg在in改变之后的下一个时钟上升沿才发生改变,因此in_reg与in之间会延迟一拍。对于out与in_reg之间,只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也不会立刻变化,需要等待下一次时钟沿到来时一起变化,所以我们最终看到的结果是输出信号 out 相对于输入信号是打了两拍的关系。
三、在编写程序时如何使用两种方式呢
两种方式并没有对错之分,但是我们在编写Verilog代码时有着官方推荐的标准:在组合电路中使用阻塞赋值,在时序电路中使用非阻塞赋值。并且,在always块中,尽可能只对一个信号进行赋值改变,这样可以避免出现我们不想要的更多的延迟时间。
总结
讲解了阻塞赋值和非阻塞赋值的概念、使用实例进行区别分析、总结两者用法区别。