9.0 阻塞赋值 & 简单例子
有许多将Verilog和Verilog综合的书,它们举了很多成功地利用“阻塞赋值”为一些简单的时序电路建模的小例子。例13是一个在大多数Verilog书本里用来为一个触发器(flip-flop)建模的例子(这是简单而有缺陷的阻塞赋值建模,但是它确实可以工作):
module dffb (q, d, clk, rst);
output q;
input d, clk, rst;
reg q;
always @(posedge clk)
if (rst) q = 1'b0;
else q = d;
endmodule
Example 13 - Simple flawed blocking-assignment D-flipflop model - but it works!
如果工程师们想把所有的模块(module)都集中到一个always里面描述,“阻塞赋值”可以用来正确地为所需要的逻辑建模、仿真和综合。但是不幸的是这个原因导致了喜欢在其它情况下也使用“阻塞赋值”的习惯,并且更复杂的时序always块将会产生竞争条件------在前面已经详细阐述过。
module dffx (q, d, clk, rst);
output q;
input d, clk, rst;
reg q;
always @(posedge clk)
if (rst) q <= 1'b0;
else q <= d;
endmodule
Example 14 - Preferred D-flipflop coding style with nonblocking assignments
应该努力养成使用“非阻塞赋值”为 所有的 时序逻辑建模的习惯------象上面的例14一样------即使是为了对付任何一个简单的模块。
下面考虑一下一个稍微复杂的时序逻辑,一个线性反馈移位寄存器(Linear Feedback shift-Register)或称之为LFSR。
10.0 为时序反馈建模 (Sequential feedback modeling)
一个LFSR是一种带反馈环路(feedback loop)的时序逻辑。反馈环路(feedback loop)为工程师们带来了一个难题使得他们试图使用细心组织次序的“阻塞赋值”来为它正确建模,如下面的例子:
module lfsrb1 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1;
assign n1 = q1 ^ q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 = 1'b1;
q2 = 1'b1;
q1 = 1'b1;
end
else begin
q3 = q2;
q2 = n1;
q1 = q3;
end
endmodule
Example 15 - Non-functional LFSR with blocking assignments
(外注:综合报告―――>
Register <q1> equivalent to <q3> has been removed
Found 1-bit register for signal <q3>.
Found 1-bit xor2 for signal <n1>.
Found 1-bit register for signal <q2>.)
Summary:
inferred 2 D-type flip-flop(s).
没有办法通过调整描述次序的方法来正确建模除非引入一个临时的变量(
外注:例如引入“wire n<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />2”――>
module xxxxx (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1,n2;
assign n1 = q1 ^ q3;
assign n2 = q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 = 1'b1;
q2 = 1'b1;
q1 = 1'b1;
end
else begin
q3 = q2;
q2 = n1;
q1 = n2;
end
endmodule
这样可以得到正确的综合结果:
Found 1-bit register for signal <q3>.
Found 1-bit xor2 for signal <n1>.
Found 1-bit register for signal <q1>.
Found 1-bit register for signal <q2>.
Summary:
inferred 3 D-type flip-flop(s).)。
可以通过把所有赋值弄到一个等式的方式(one-line equations)来避免使用临时变量,例如下面的例16所示。但是现在编码显得更难于理解尤其当涉及的表达式更大更长时,编写代码和调试都变得比较困难,因此不鼓励使用这种风格。
module lfsrb2 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
always @(posedge clk or negedge pre_n)
if (!pre_n) {q3,q2,q1} = 3'b111;
else {q3,q2,q1} = {q2,(q1^q3),q3};
endmodule
Example 16 - Functional but cryptic LFSR with blocking assignments
如果把例15和例16的阻塞赋值(blocking assignment)都替换为非阻塞赋值(nonblocking assignment),如下面例17和18所示,那么所有的仿真都将如我们对一个LFSR所期望的那样。
module lfsrn1 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
wire n1;
assign n1 = q1 ^ q3;
always @(posedge clk or negedge pre_n)
if (!pre_n) begin
q3 <= 1'b1;
q2 <= 1'b1;
q1 <= 1'b1;
end
else begin
q3 <= q2;
q2 <= n1;
q1 <= q3;
end
endmodule
Example 17 - Functional LFSR with nonblocking assignments
module lfsrn2 (q3, clk, pre_n);
output q3;
input clk, pre_n;
reg q3, q2, q1;
always @(posedge clk or negedge pre_n)
if (!pre_n) {q3,q2,q1} <= 3'b111;
else {q3,q2,q1} <= {q2,(q1^q3),q3};
endmodule
Example 18 - Functional but cryptic LFSR with nonblocking assignments
根据8.0段例子pipeline和10.0段例子LFSR,我们推荐对所有时序逻辑建模时使用非阻塞赋值(nonblocking assignment)。相似的分析也将显示出对latch建模时使用非阻塞赋值(nonblocking assignment)是最安全的。
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
11.0 组合逻辑―使用阻塞赋值(blocking assignment)
用Verilog可以有很多种方法为组合逻辑建模,但是当使用always块来为组合逻辑建模时,应该使用阻塞赋值(blocking assignment)。
如果在某个always块里面只有一个赋值(表达),那么使用阻塞或者非阻塞赋值都可以正确工作。但是如果您对养成好的编码习惯有兴趣的话,还是要“总是用阻塞赋值对组合逻辑建模”。
一些设计师建议非阻塞赋值不应该只为编写时序逻辑,它也可以用来编写组合逻辑。当然对于简单的组合逻辑always块这是可以的,但是对于在一个always块里面含有多个赋值陈述,例如例19含有and-or的陈述,使用了不含延迟(delay)的非阻塞赋值会造成仿真不正确,或者要使仿真正确您需要另外的添加敏感事件列表(sensitivity list entries),和“多登入路径”(multiple passes)来贯穿always 块以使得仿真正确。接下来的问题是从仿真需要多长时间来看,这是低效率的(外注:即降低仿真的performance)。
例19的y输出建立在3个依次执行的陈述上(外注:tmp1 <= a & b; tmp2 <= c & d; y <= tmp1 | tmp2;)。由于非阻塞赋值的LHS变量值更新是在对RHS表达式估值之后,所以tmp1和tmp2的值仍然是该always块上一个登入口的值而不是在这一个仿真时间步(simulation time step)结束时被更新的值。因此y的值将受旧的tmp1和tmp2影响,而不是这次扫描过的always块内被更新的值。
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
例20与例19是一样的,不同之处在于tmp1和tmp2被添加到事件列表中去了。如第7段(section 7.0)中所述,在“非阻塞赋值更新事件队列”中当非阻塞赋值更新LHS变量时,always块将会“自触发”并使用最新的tmp1和tmp2来更新y输出。现在y输出值正确了因为增加使用了两条“登入路径”(two passes)贯穿整个always块。使用更多的“登入路径”来贯穿always块等于降低仿真器的性能,因此如果可以有合理的一些代码变化可以取代这种用法的话,就尽量避免这种用法。
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
0/0发展一个好的习惯可以避免使用“多登入路径”(multiple passes)贯穿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输出的正确(guarantee that the y-output assumes the correct value after only one pass through the always block?)。因此有下面的编码方针:
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
12.0 时序-组合混合逻辑建模:使用非阻塞赋值
很多时候为了方便我们把时序和一些简单的组合逻辑放在一起。当我们把时序和组合编码放在一个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相同的逻辑也可以使用两个分立的always块------一个是纯粹的时序逻辑(使用非阻塞赋值),另一个是纯粹的组合逻辑(使用阻塞赋值)------建模,例如下面的例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
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
13.0 其它混合“阻塞”与“非阻塞”赋值建模方针
Verilog允许在一个always块里面自由混合“阻塞”与“非阻塞”赋值。一般情况下在同一个always块里面混合“阻塞”与“非阻塞”赋值是“衰婆”风格(poor coding style,呵呵,借用电影《钢琴教师》里的翻译“衰婆”,刚好poor发音与“婆”有些相近。不过可能引
起大家一阵反胃,女士们一阵痛恨------向导演,可不要向我!),尽管Verilog允许这样做。
下面的例24的仿真和综合都将是正确的,因为“阻塞”与“非阻塞”赋值不是针对同一个变量来的。尽管这可以“正常工作”,但是作者不推荐这种风格。
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)工具会报告语法错误因为针对同一个既进行了“阻塞赋值”又进行了“非阻塞赋值”。这样的编码必须进行修改才可以综合。(Error:Cannot mix blocking and non blocking assignments on signal <q>.)
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
为了养成好的编写习惯,作者推荐始终坚持:
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
14.0 对同一变量多处赋值(Multiple assignments to the same variable)
对同一变量在二个以上(包括二个)always块里面进行赋值就是一种Verilog竞争生成环境------即使使用非阻塞赋值。
在下面例26里,二个always块对q进行赋值,同时使用非阻塞赋值。因为这些always块可以以同一次序安排执行,仿真输出呈竞争条件。
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
当新思(Synopsys)工具读这段编码时,会产生如下警告:
Warning: In design 'badcode1', there is 1 multiple-driver net with unknown wired-logic type.
当忽略这个警告并编译上面的例子时,推断结果是二个触发器的输出将作为一个and门的输入。在这个例子里综合前(pre-synthesis)仿真结果与综合后(post-synthesis)仿真结果不匹配。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
15.0 常见的“非阻塞”神话(外注:指与事实不符或严格说不正确的见解、想法)
15.1 非阻塞赋值和$display
神话:“对‘非阻塞赋值’使用$display命令不起作用。”
事实:非阻塞赋值在$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命令在“激活事件列”(the active event queue)里被执行的情形:在“非阻塞赋值更新”这个事件被执行之前。
$display: a = 0
$monitor: a = 1
$strobe : a = 1
15.2 赋“零延迟”
神话:“‘零延迟’#0 使得该赋值事件在时间步结束时发生”
事实:零延迟’#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
下面的仿真输出结果显示出$display命令在“非激活事件列”(the inactive event queue)里被执行的情形:在“非阻塞赋值更新”这个事件被执行之前。
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"
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
15.3 对同一变量多处进行“非阻塞赋值”
神话:“‘在同一个always块里对同一变量多处进行非阻塞赋值’ 没有被明确定义。”
事实:Verilog标准定义了以上操作。最后一个非阻塞赋值操作将赢得最后结果。引用IEEE1364-1995 Verilog Standard [2], pg. 47, section 5.4.1 – Determinism 如下:
“非阻塞赋值由它们被陈述的次序决定被执行的情况,考虑下面的例子:
initial begin
a <= 0;
a <= 1;
end
When this block is executed, there will be two events added to the nonblocking assign update queue. The previous rule requires that they be entered on the queue in source order; this rule requires that they be taken from the queue and performed in source order as well. Hence, at the end of time-step 1, the variable a will be assigned 0 and then 1."
换句话说:“最后一个非阻塞赋值操作将赢得优先权。”
指导方针和结论(概要):
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
#8: 不要使用#0延迟的赋值。
谨遵这些方针可以帮助Verilog设计者减少所遇到的90-100%的Verilog竞争。
16.0 最后注意:“nonblocking”的拼写
“nonblocking”经常被拼错为“non-blocking”。作者认为这是“微软化”的拼写方式。工程师们在“non”和“blocking”之间插入一个“-”是为了满足微软的拼写检查不致报错。在IEEE 1364-1995里正确的拼写应该是:nonblocking。(外注:呵呵,你已经看到了,在这
个文档里面所有的nonblocking都被下划了红色波浪线。)
参考文献:
[1] IEEE P1364.1 Draft Standard For Verilog Register Transfer Level Synthesis
[2] IEEE Standard Hardware Description Language Based on the Verilog Hardware
Description Language, IEEE Computer Society, IEEE Std 1364-1995
[3] Clifford Cummings, "Correct Methods For Adding Delays To Verilog Behavioral
Models," International HDL Conference 1999 Proceedings, pp. 23-29, April 1999.
(外注:以上参考文献原文的。译文中第4页“事件轴”引自中科院计算所张亮编著、人民邮电出版社2000年10月出版的《数字电路设计与Verilog HDL》;一些综合报告来自Xilinx公司的综合软件XST。)
作者和联系方式 (外注:偷懒省略。总之“Mr. Cummings, a member of the IEEE 1364 Verilog Standards Group (VSG) since 1994”,虽然文章所涉很使我们感到“离”众书“叛道”,但其实是足够可信的【并且使我疑团大释,所以拿出来让使用Verilog的家伙一齐“开怀”,译文中太多错误和缺点请大家不吝指正!】。
本文英文原文可以从下面的 web site下载: www.fpga.com.cn 或 www.sunburst-design.com/papers 。)
转载自:Verilog 非阻塞赋值的仿真/综合问题-电子开发网
文章评论(0条评论)
登录后参与讨论