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 n2”――>
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块为组合逻辑建模,使用“阻塞赋值” |