阻塞赋值与非阻塞赋值&&延一拍
D触发器
时序逻辑电路的延一拍
同步D触发器
“同步”是和工作时钟同步的意思,当时钟的上升沿(也可以是下降沿)来到时检测到复位操作才有效,否则无效。
sys_rst_n 被拉低后 led_out 没有立刻变为 0,而是当 syc_clk 的上升沿到来的时候 led_out 才复位成功,在复位释放的时候也是相同原因。
异步D触发器
“异步”是和工作时钟不同步的意思,寄存器的复位不关心时钟的上升沿来不来,只要有检测到按键被按下,就立刻执行复位操作。
sys_rst_n 被拉低后 led_out 立刻变为0,而不是等待 syc_clk 的上升沿到来的时候 led_out 才复位,而在复位释放的时候 led_out不会立刻变为 key_in 的值,因为还要等待时钟上升沿到来到时才能检测到 key_in 的值,此时才将 key_in 的值赋值给 led_out。
异同比较
区别主要就是复位有效的条件是“立刻”执行还是等待“沿”再执行的区别。
而复位释放之后,都是等待时钟上升沿才有效。这与D触发器的特性一致。
同步:always@(posedge sys_clk)
异步:always@(posedge sys_clk or negedge sys_rst_n)
使用 Intel(Altera)芯片时最好使用异步复位(如果是Xilinx 的芯片则推荐使用同步复位,Xilinx 的 UltraFas 方法学则推荐使用局部复位或最好不使用复位),这样子就可以节约更多的逻辑资源,更利于时序的收敛,之所以会有这样的差异是由于 FPGA 内部结构决定的。
延一拍
上面两个图最左边的一组红色竖线。key_in 在复位后的第一个时钟的上升沿来到时拉高,我们可以发现此时 led_out 并没有在同一时刻也跟着拉高,而在之前的组合逻辑中输出是在输入变化的同一时刻立刻变化的。
当表达时序逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿前一时刻的值;当表达组合逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为在该时钟上升沿同一时刻的值。
理解延一拍:
是当时钟上升沿来临时,左边的值更新是以上升沿之前右边的值更新的。不管同步还是异步D触发器,时序逻辑电路都有延一拍的特点。
赋值语句
阻塞赋值 =
只有一个步骤,即计算赋值号右边的语句并更新赋值号左边的语句,此时不允许有来自任何其他 Verilog 语句的干扰,直到现行的赋值完成时刻,即把当前赋值号右边的值赋值给左边的时刻完成后,它才允许下一条的赋值语句的执行
//阻塞赋值
module blocking
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [1:0] in ,
output reg [1:0] out
);
reg [1:0] reg_in; //in_reg:给输入信号打一拍
//out:输出控制一个 LED 灯
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
begin
reg_in = 2'b0;
out = 2'b0;
end
else
begin
reg_in = in ;//延一拍,只有复位结束并且下一个时钟上升沿到来才会赋值
out = reg_in ;//没有延一拍,阻塞赋值,只要右侧表达式有变化,左侧跟着变化
end
end
endmodule
`timescale 1ns/1ns
module blocking_tb();
reg sys_clk ;
reg sys_rst_n ;
reg [1:0] in ;
wire [1:0] out ;
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
in <= 2'b0;
#20
sys_rst_n <= 1'b1;
end
//模拟系统时钟
always #10 sys_clk = ~sys_clk;
//in:产生输入随机数,模拟按键的输入情况
//取模求余数,产生非负随机数 0、1、2、3,每隔 20ns 产生一次随机数
always #20 in <= {$random} % 4;
//------------------------blocking_inst------------------------
blocking blocking_inst
(
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n ),
.in ( in ),
.out ( out )
);
endmodule
输入信号 in 和中间变量 in_reg、输出信号out 的关系就是延迟一拍的关系。
首先中间变量 in_reg 一定要等待复位被释放后且第一个时钟上升沿来到时才会被赋值为输入信号 in 的值,所以会比输入信号 in 延迟一拍,而中间变量 in_reg 和输出信号 out 却没有延迟一拍的关系了,而是在同一时刻同时变化的,因为我们使用的是阻塞赋值,也就是说只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也将立刻变化。
非阻塞赋值
非阻塞操作开始时计算非阻塞赋值符的赋值号右边的语句,赋值操作结束时刻才更新赋值号左边的语句,可以认为是两个步骤(赋值开始时刻和结束时刻)来完成非阻塞赋值。
//非阻塞赋值
module nonblocking
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [1:0] in ,
output reg [1:0] out
);
reg [1:0] reg_in; //in_reg:给输入信号打一拍
//out:输出控制一个 LED 灯
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
begin
reg_in <= 2'b0;
out <= 2'b0;
end
else
begin //非阻塞赋值不是顺序执行,阻塞赋值才是
reg_in <= in ;//延一拍
out <= reg_in ;//也有延一拍,虽然右边的值变化了,但要等到下一次时钟上升沿变化才会赋值给左边
end
end
endmodule
`timescale 1ns/1ns
module nonblocking_tb();
reg sys_clk ;
reg sys_rst_n ;
reg [1:0] in ;
wire [1:0] out ;
//初始化系统时钟、全局复位和输入信号
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
in <= 2'b0;
#20
sys_rst_n <= 1'b1;
end
//模拟系统时钟
always #10 sys_clk = ~sys_clk;
//in:产生输入随机数,模拟按键的输入情况
//取模求余数,产生非负随机数 0、1、2、3,每隔 20ns 产生一次随机数
always #20 in <= {$random} % 4;
//------------------------blocking_inst------------------------
nonblocking nonblocking_inst
(
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n ),
.in ( in ),
.out ( out )
);
endmodule
输入信号 in 和中间变量 in_reg 是延迟一拍的关系,而中间变量 in_reg 和输出信号 out 也是延迟一拍的关系,也就是输入信号 in 和输出信号 out 一共是延迟两拍的关系。
in_reg 一定要等待复位被释放后且第一个时钟上升沿来到时才会被
赋值为输入信号 in 的值,所以会比输入信号 in 延迟一拍,这是和阻塞赋值过程相同的,但是接下来,因为我们使用的是非阻塞赋值,也就是说只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也不会立刻变化,需要等待下一次时钟沿到来时一起变化,所以最终结果是输出信号 out 相对于输入信号是打了两拍的关系。
比较
- 阻塞赋值(=) :该语句结束时就完成赋值操作,前面的语句没有完成前,后面的语句是不能执行的。在一个过程块内多个阻塞赋值语句是顺序执行的。
- 非阻塞赋值(<=) :一条非阻塞赋值语句的执行是不会阻塞下一条语句的执行,也就是说在本条非阻塞赋值语句执行完毕前,下一条语句也可开始执行。非阻塞赋值语句在过程块结束时才完成赋值操作。在一个过程块内的多个非阻塞赋值语句是并行执行的。
- 一个 always 块只一个变量进行赋值
因为 always 块是并行的,执行的顺序是随机的,综合时会报多驱动的错误,所以严禁在多个 always 块中对同一个变量赋值;当然也不推荐一个 always 对多个变量进行赋值,虽然这种方式是允许的,但如果过多会导致代码的混乱且不易后期的维护和修改。
总结
- 时序逻辑电路都有延一拍(是赋值时取前一时刻的值,而不是延一个节拍赋值)
- 阻塞赋值= 不会有延一拍的效果(只要赋值号右边的表达式的值有变化,赋值号左边的表达式的值也将立刻变化)