阻塞赋值的赋值号用“=”表示。为什么称这种赋值方式为阻塞赋值呢?
答:因为对应的电路结构往往与触发沿没有关系,只与输入电平的变化有关系。阻塞赋值的操作可以认为是只有一个步骤的操作,即计算赋值号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他 Verilog语句的干扰,直到现行的赋值完成时刻,即把当前赋值号右边的值赋值给左边的时刻完成后,它才允许下一条的赋值语句的执行。串行块(begin-end)中的各条阻塞型过程赋值语句将以它们在顺序块后的排列次序依次执行。阻塞型过程赋值语句的执行过程是:首先计算赋值号右边的值,然后立即将计算结果赋值给左边,赋值语句结束,变量值立即发生改变。阻塞的概念是指在同一个 always 块中,其后面的赋值语句从概念上是在前一句赋值语句结束后再开始下面的赋值。
非阻塞赋值的赋值号用“<=”表示。为什么称这种赋值方式为非阻塞赋值呢?
答:这是因为对应的电路结构往往与触发沿有关系,只有在触发沿的时刻才能进行非阻塞赋值。非阻塞操作开始时计算非阻塞赋值符的赋值号右边的语句,赋值操作结束时刻才更新赋值号左边的语句,可以认为是两个步骤(赋值开始时刻和结束时刻)来完成非阻塞赋值。在计算非阻塞语句赋值号右边的语句和更新赋值号左边的语句期间,其他的 Verilog语句包括其他的 Verilog 非阻塞赋值语句都能同时计算赋值号右边的语句和更新赋值号左边的语句,允许其他的 Verilog 语句同时进行操作。非阻塞赋值的操作可以看作为两个步骤的过程:在赋值开始时刻,计算赋值号右边的语句。在赋值结束时刻,更新赋值号左边的语句。注意:非阻塞操作只能用于对寄存器类型变量进行赋值,因此只能用于“initial”和“always”块中,不允许用于连续赋值“assign”。
阻塞赋值(=)”和“非阻塞赋值(<=)”符号其实我们早就见过,大家可能存在疑问,有时候用阻塞赋值而有时候用非阻塞赋值,所以阻塞赋值和非阻塞赋值的概念一直是初学者较为头疼的一件事情,因为大多数人往往因为文字概念的描述理解不透彻,说白了就是看那些文字的解释反而让我们更难理解,更容易被绕进去,产生懵的感觉。甚至有些很有经验的 逻辑设计工程师也不能完全正确地理解何时使用非阻塞赋值何时使用阻塞赋值才能设计出符合要求的电路,不明白在电路结构的设计中,即可综合风格的 Verilog模块的设计中,究竟为什么还要用非阻塞赋值,以及符合IEEE标准的Verilog 仿真器究竟如何来处理非阻塞赋值的仿真。
本章的目的是尽可能地把阻塞和非阻塞赋值的含义和用法详细地解释清楚,并通过实例给出正确的用法,使之能够设计出符合我们意愿的的代码及功能。
一、阻塞赋值
1、RTL代码的编写
开始 RTL 代码的编写,RTL 代码编写出的模块叫 RTL 模块(后文中也称功能模块、可综合模块)。之所以叫 RTL 代码是因为用 Verilog HDL 在 Resistances Transistors Logic(寄存器传输级逻辑)来描述硬件电路,RTL 代码能够综合出真实的电路以实现我们设计的功能,区别于不可综合的仿真代码。
`timescale 1ns/1ns
module blocking
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire [1:0] in , //输入信号
output reg [1:0] out //输出信号
);
reg [1:0] in_reg;
//in_reg:给输入信号打一拍
//out:输出控制一个LED灯
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
in_reg = 2'b0;
out = 2'b0;
end
else
begin
in_reg = in;
out = in_reg;
end
endmodule
2、代码的分析和综合
3、 查看RTL 视图
4、Testbench代码的编写
`timescale 1ns/1ns
module tb_blocking();
wire [1:0] out ;
reg sys_clk ;
reg sys_rst_n ;
reg [1:0] in ;
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
in <= 2'b0;
#20;
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk;
//in:产生输入随机数,模拟按键的输入情况
always #20 in <= {$random} % 4; //取模求余数,产生非负随机数0、1,每隔20ns产生一次随机数
//------------------------ blocking_inst ------------------------
blocking blocking_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.in (in ), //input [1:0] in
.out (out ) //output [1:0] out
);
endmodule
5、ModelSim仿真波形
打开 ModelSim 执行仿真,仿真出来的波形如图所示,我们让仿真运行了 500ns即可得到较好的观察效果。
我们知道一个寄存器就是“延一拍”,所以该仿真波形和前面的 RTL视图刚好对应,发现输入信号 in
和中间变量 in_reg
、输出信号out
的关系就是延迟一拍的关系,但是为什么只是延迟一拍呢?
首先中间变量in_reg
一定要等待复位被释放后且第一个时钟上升沿来到时才会被赋值为输入信号in
的值,所以会比输入信号in
延迟一拍,而中间变量 in_reg
和输出信号 out
却没有延迟一拍的关系了,而是在同一时刻同时变化的,因为我们使用的是阻塞赋值,也就是说只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也将立刻变化,所以我们最终看到的结果是中间变量in_reg 和输出信号 out是同时变化的。
二、非阻塞赋值
1、RTL代码的编写
开始RTL代码的编写,RTL代码编写出的模块叫RTL模块(后文中也称功能模块、可综合模块)。之所以叫RTL代码是因为用Verilog HDL在Resistances Transistors Logic(寄存器传输级逻辑)来描述硬件电路,RTL代码能够综合出真实的电路以实现我们设计的功能,区别于不可综合的仿真代码。
`timescale 1ns/1ns
module non_blocking
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire [1:0] in , //输入按键
output reg [1:0] out //输出控制led灯
);
reg [1:0] in_reg;
//in_reg:给输入信号打一拍
//out:输出控制一个LED灯
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
in_reg <= 2'b0;
out <= 2'b0;
end
else
begin
in_reg <= in;
out <= in_reg;
end
endmodule
2、代码的分析和综合
3、 查看RTL视图
4、Testbench代码的编写
`timescale 1ns/1ns
module tb_non_blocking();
wire [1:0] out;
reg sys_clk;
reg sys_rst_n;
reg [1:0] in;
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
in <= 2'b0;
#20;
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
always #10 sys_clk = ~sys_clk;
//key_in:产生输入随机数,模拟按键的输入情况
always #20 in <= {$random} % 4; //取模求余数,产生非负随机数0、1,每隔20ns产生一次随机数
//------------------------non_blocking_inst------------------------
non_blocking non_blocking_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.in (in ), //input [1:0] in
.out (out ) //output [1:0] out
);
endmodule
5、ModelSim仿真波形
打开ModelSim执行仿真,仿真出来的波形如图所示,我们让仿真运行了 500ns即可得到较好的观察效果。
同样该仿真波形和其对应的 RTL视图也是刚好对应的,我们发现输入信号 in
和中间变量 in_reg
是延迟一拍的关系,而中间变量 in_reg
和输出信号 out
也是延迟一拍的关系,也就是输入信号 in
和输出信号 out
一共是延迟两拍的关系,为什么会这样呢?
首先中间变量 in_reg
一定要等待复位被释放后且第一个时钟上升沿来到时才会被赋值为输入信号 in
的值,所以会比输入信号 in延迟一拍,这是和阻塞赋值过程相同的,
但是接下来就不一样了,因为我们使用的是非阻塞赋值,也就是说只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也不会立刻变化,需要等待下一次时钟沿到来时一起变化,所以我们最终看到的结果是输出信号out
相对于输入信号
是打了两拍的关系。