时序电路中阻塞赋值和非阻塞赋值
FPGA----非阻塞赋值与阻塞赋值
编码指南:
指南:在为生成组合逻辑[1]而编写的always块中使用阻塞赋值。
指南:在为生成顺序逻辑[1]而编写的always块中使用非阻塞赋值。
竞争:
如果两个或多个计划在同一模拟时间步骤中执行的语句在IEEE Verilog标准允许的情况下更改语句执行顺序时会产生不同的结果,则会发生Verilog竞争。
在本文中,将使用以下缩写:
RHS - 方程右侧的表达式或变量将缩写为RHS方程,RHS表达式或RHS变量。
LHS - 方程左侧的表达式或变量将缩写为LHS方程,LHS表达式或LHS变量。
2.1阻塞赋值
在Verilog HDL的概念中阻塞赋值操作符用等号(即=)表示。
在赋值时先计算等号右手方向(RHS)部分的值,这时赋值语句不允许任何别的Verilog语句的干扰,直到现行的赋值完成时刻,即把RHS赋值给LHS(等号左手方向)的时刻,它才允许别的赋值语句的执行。一般可综合的阻塞赋值操作在RHS不能设定有延迟(即使是零延迟也不允许)。从理论上讲,它与后面的赋值语句只有概念上的先后,而无实质上的延迟。若在RHS上加延迟,则在延迟期间会阻止赋值语句的执行,延迟后才执行赋值,这种赋值语句是不可综合的,在需要综合的模块设计中不可使用这种风格的代码。
问题
如果在一个过程块中阻塞赋值的RHS变量正好是另一个过程块中阻塞赋值的LHS变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的顺序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的顺序是无法确定的。
为了说明这一点,请查看示例1中的Verilog代码。
module fbosc1 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;
always @(posedge clk or posedge rst)
if (rst) y1 = 0; // reset
else y1 = y2;
always @(posedge clk or posedge rst)
if (rst) y2 = 1; // preset
else y2 = y1;
endmodule
Example 1 - Feedback oscillator with blocking assignments
根据IEEE Verilog标准,可以按任何顺序安排两个始终块。 如果在复位后执行第一个always块,则y1和y2都将取值1.如果第二个always块在复位后首先执行,则y1和y2都将取值0.这显然代表一个Verilog 竞争条件。
2.2非阻塞赋值
非阻塞赋值操作符用小于等于号(即<=)表示。
为在赋值操作时刻开始时计算非阻塞赋值符的RHS表达式,赋值操作时刻结束时更新LHS。在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog语句,包括其他的Verilog非阻塞赋值语句都能同时计算RHS表达式和更新LHS。非阻塞赋值允许其他的Verilog语句同时进行操作。
非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将begin-end之间的所有赋值语句同时赋值到赋值语句的左边,注意:是begin—end之间的所有语句,一起执行,且一个时钟只执行一次。
为了说明这一点,请查看示例2中的Verilog代码
module fbosc2 (y1, y2, clk, rst);
output y1, y2;
input clk, rst;
reg y1, y2;
always @(posedge clk or posedge rst)
if (rst) y1 <= 0; // reset
else y1 <= y2;
always @(posedge clk or posedge rst)
if (rst) y2 <= 1; // preset
else y2 <= y1;
endmodule
Example 2 - Feedback oscillator with nonblocking assignments
同样,根据IEEE Verilog标准,可以按任何顺序安排两个始终块。 无论在复位后哪个块始终首先启动,都将在时间步的开始时评估两个非阻塞RHS表达式,然后在同一时间步的末尾更新两个非阻塞LHS变量。 从用户的角度来看,这两个非阻塞语句的执行是并行发生的。
2.3区别
非阻塞赋值
always@(posedge clk)
begin
b<=a;
c<=b;
end
阻塞赋值
always@(posedge clk)
begin
b=a;
c=b;
end
两种不同的赋值方式结果是不同的,非阻塞赋值b<=a;c<=b;两条语句是同时执行的,而阻塞赋值b=a;c=b;两条语句先执行b=a后执行c=b。
Clifford在《Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!》
一文中有给出八条编码准则:
1)时序电路建模时,用非阻塞赋值;
2)锁存器电路建模时,用非阻塞赋值;
3)用always块建立组合逻辑模型时,用阻塞赋值;
4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值;
5)在同一个alway块中,不要即用非阻塞又用阻塞赋值;
6)不要在一个以上的always块中为同一个变量赋值;
7)用$strobe系统任务来显示用非阻塞赋值的变量值;
8)在赋值时不要使用#0延迟。
准则1)时序电路建模时,用非阻塞赋值;
图2显示了简单的顺序流水线寄存器(最简单的时序电路)的框图。 示例5 - 示例8显示了工程师可能选择使用阻塞赋值四的种不同编码方式。
在例5中,顺序排序的阻塞分配将使输入值d放在下一个posedge clk上每个寄存器的输出上。 在每个时钟边沿,输入值无延迟地直接传输到q3输出。 这显然不会对流水线寄存器进行建模,实际上会合成一个寄存器! (见图3)。
module pipeb1 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q1 = d;
q2 = q1;
q3 = q2;
end
endmodule
Example 5 - Bad blocking-assignment sequential coding style #1
在示例6中,已仔细排序阻塞分配,以使模拟正确地像流水线寄存器一样运行。 该模型合成了图2所示的流水线寄存器。
module pipeb2 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q3 = q2;
q2 = q1;
q1 = d;
end
endmodule
Example 6 - Bad blocking-assignment sequential coding style #2 - but it works!
在示例7中,阻塞分配已拆分为单独的always块。
允许Verilog以任何顺序模拟always块,这可能导致此管道模拟错误。 这是Verilog的竞争条件! 以不同顺序执行always块会产生不同的结果。 但是,这个Verilog代码将合成到正确的流水线寄存器。 这意味着预合成和后合成模拟之间可能存在不匹配。 pipeb4示例或同一个始终块语句的任何其他顺序也将合成到正确的管道逻辑,但可能无法正确模拟。
module pipeb3 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q1=d;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;
endmodule
Example 7 - Bad blocking-assignment sequential coding style #3
module pipeb4 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;
always @(posedge clk) q1=d;
endmodule
Example 8 - Bad blocking-assignment sequential coding style #4
如果四个阻塞分配示例中的每一个都用非阻塞赋值重写,则每个都将正确模拟并合成所需的流水线逻辑。
module pipen1 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
q3 <= q2;
end
endmodule
Example 9 - Good nonblocking-assignment sequential coding style #1
module pipen2 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) begin
q3 <= q2;
q2 <= q1;
q1 <= d;
end
endmodule
Example 10 - Good nonblocking-assignment sequential coding style #2
module pipen3 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q1<=d;
always @(posedge clk) q2<=q1;
always @(posedge clk) q3<=q2;
endmodule
Example 11 - Good nonblocking-assignment sequential coding style #3
module pipen4 (q3, d, clk);
output [7:0] q3;
input [7:0] d;
input clk;
reg [7:0] q3, q2, q1;
always @(posedge clk) q2<=q1;
always @(posedge clk) q3<=q2;
always @(posedge clk) q1<=d;
endmodule
Example 12 - Good nonblocking-assignment sequential coding style #4
由此可见,对流水线逻辑进行编码时 ,非阻塞赋值会比阻塞赋值好,虽然阻塞赋值也可以通过严谨的拍序实现。
准则2)锁存器电路建模时,用非阻塞赋值;
类似的分析表明,使用非阻塞分配来模拟锁存器也是最安全的。
准则3)用always块建立组合逻辑模型时,用阻塞赋值;
使用Verilog对组合逻辑进行编码的方法有很多种,但是当使用always块对组合逻辑进行编码时,应该使用阻塞分配。
例19中的代码构建了三个顺序执行语句的y输出。
由于非阻塞分配在更新LHS变量之前评估RHS表达式,因此tmp1和tmp2的值是在进入此始终块时这两个变量的原始值,而不是在模拟时间步骤结束时将更新的值。
y输出将反映tmp1和tmp2的旧值,而不是始终块的当前传递中计算的值。
module ao4 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
endmodule
Example 19 - Bad combinational logic coding style using nonblocking assignments
将temp1和temp2放入敏感信号列表,当非阻塞赋值更新更新事件队列中的LHS变量时,always块将自触发并用新计算的tmp1和tmp2值更新y输出。 在通过always块两次后,y输出值现在将是正确的。 通过始终块的多次传递等同于降低的模拟性能,并且如果存在合理的替代方案则应该避免。
module ao5 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d or tmp1 or tmp2) begin
tmp1 <= a & b;
tmp2 <= c & d;
y <= tmp1 | tmp2;
end
endmodule
Example 20 - Inefficient multi-pass combinational logic coding style with nonblocking assignments
一个更好的开发习惯,即不需要多次遍历always块的习惯,就是只使用写入模型组合逻辑的always块中的阻塞赋值。
module ao2 (y, a, b, c, d);
output y;
input a, b, c, d;
reg y, tmp1, tmp2;
always @(a or b or c or d) begin
tmp1 = a & b;
tmp2 = c & d;
y = tmp1 | tmp2;
end
endmodule
Example 21 - Efficient combinational logic coding style using blocking assignments
示例21中的代码与示例19中的代码相同,只是非阻塞分配已被阻塞分配替换,这将保证只有一次通过always块后,y将输出正确的值;
准则4)在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值
使用非阻塞分配有时可以方便地将简单组合逻辑与时序逻辑电路组合。 将组合和时序代码组合到单个always块中时,将always块编码为具有非阻塞赋值的顺序始终块,如例22所示。
module nbex2 (q, a, b, clk, rst_n);
output q;
input clk, rst_n;
input a, b;
reg q;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= a ^ b;
endmodule
Example 22 - Combinational and sequential logic in a single always block
在例22中实现的相同逻辑也可以实现为两个单独的始终块,一个用阻塞赋值编码的纯组合逻辑,一个具有用非阻塞赋值编码的纯时序逻辑,如例23所示。
module nbex1 (q, a, b, clk, rst_n);
output q;
input clk, rst_n;
input a, b;
reg q, y;
always @(a or b)
y = a ^ b;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= y;
endmodule
Example 23 - Combinational and sequential logic separated into two always blocks
准则5)在同一个alway块中,不要即用非阻塞又用阻塞赋值;
Verilog允许阻塞和非阻塞赋值在always块内自由混合。 通常,即使Verilog允许,在同一个总块中混合阻塞和非阻塞赋值也是一种糟糕的编码风格。
例24中的代码将正确模拟和综合,因为阻塞赋值不是与非阻塞赋值相同的变量。 虽然这可行,但clifford不鼓励这种编码风格。
module ba_nba2 (q, a, b, clk, rst_n);
output q;
input a, b, rst_n;
input clk;
reg q;
always @(posedge clk or negedge rst_n) begin: ff
reg tmp;
if (!rst_n) q <= 1'b0;
else begin
tmp = a & b;
q <= tmp;
end
end
endmodule
Example 24 - Blocking and nonblocking assignment in the same always block - generally a bad idea!
示例25中的代码很可能在大多数情况下正确模拟,但Synopsys工具将报告语法错误,因为阻塞赋值被赋值给与非阻塞赋值之一相同的变量。 必须修改此代码才能合成。
module ba_nba6 (q, a, b, clk, rst_n);
output q;
input a, b, rst_n;
input clk;
reg q, tmp;
always @(posedge clk or negedge rst_n)
if (!rst_n) q = 1'b0; // blocking assignment to "q"
else begin
tmp = a & b;
q <= tmp; // nonblocking assignment to "q"
end
endmodule
Example 25 - Synthesis syntax error - blocking and nonblocking assignment to the same variable
准则6)不要在一个以上的always块中为同一个变量赋值;
即使使用非阻塞赋值,从多个always快对同一变量进行多次赋值也是Verilog竞争条件。
在示例26中,两个始终块正在对q输出进行分配,两者都使用非阻塞分配。 由于这些始终块可以按任何顺序进行调度,因此模拟输出是竞争条件。
module badcode1 (q, d1, d2, clk, rst_n);
output q;
input d1, d2, clk, rst_n;
reg q;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d1;
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 1'b0;
else q <= d2;
endmodule
Example 26 - Race condition coding style using nonblocking assignments
综合工具运行这段代码的时候会报错:
Warning: In design ‘badcode1’, there is 1 multiple-driver net with unknown wired-logic type.
当忽略警告并编译示例26中的代码时,推断出两个触发器,其输出馈送2输入和门。 在该示例中,预合成模拟甚至不与后合成模拟紧密匹配。
准则7)用$strobe系统任务来显示用非阻塞赋值的变量值;
Myth:“使用带有非阻塞赋值的$ display命令不起作用”
Truth:在所有$ display命令之后更新非阻塞赋值
module display_cmds;
reg a;
initial $monitor("\$monitor: a = %b", a);
initial begin
$strobe ("\$strobe : a = %b", a);
a = 0;
a <= 1;
$display ("\$display: a = %b", a);
#1 $finish;
end
endmodule
上面模拟显示,在执行非阻塞赋值更新事件之前,$ display命令在活动事件队列中执行。
$display: a = 0
$monitor: a = 1
$strobe : a = 1
准则8)在赋值时不要使用#0延迟
Myth: “#0强制分配到时间步的末尾”
Truth: #0强制分配给“非活动事件队列
module nb_schedule1;
reg a, b;
initial begin
a = 0;
b = 1;
a <= b;
b <= a;
$monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
$display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
$strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
#0 $display ("%0dns: #0 : a=%b b=%b", $stime, a, b);
#1 $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
$display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
$strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
$display ("%0dns: #0 : a=%b b=%b", $stime, a, b);
#1 $finish;
end
endmodule
模拟显示,在执行非阻塞赋值更新事件之前,在非活动事件队列中执行了#0-delay命令。
0ns: $display: a=0 b=1
0ns: #0 : a=0 b=1
0ns: $monitor: a=1 b=0
0ns: $strobe : a=1 b=0
1ns: $display: a=1 b=0
1ns: #0 : a=1 b=0
1ns: $monitor: a=1 b=0
1ns: $strobe : a=1 b=0