FPGA----非阻塞賦值與阻塞賦值
1.0簡介
編碼指南:
指南:在為生成組合邏輯[1]而編寫的always塊中使用阻塞賦值。
指南:在為生成順序邏輯[1]而編寫的always塊中使用非阻塞賦值。
競爭:
如果兩個或多個計划在同一模擬時間步驟中執行的語句在IEEE Verilog標准允許的情況下更改語句執行順序時會產生不同的結果,則會發生Verilog競爭。
在本文中,將使用以下縮寫:
RHS - 方程右側的表達式或變量將縮寫為RHS方程,RHS表達式或RHS變量。
LHS - 方程左側的表達式或變量將縮寫為LHS方程,LHS表達式或LHS變量。
2.0阻塞賦值&非阻塞賦值
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。
3.0編碼准則
Clifford在《Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!》
一文中有給出八條編碼准則:
1)時序電路建模時,用非阻塞賦值;
2)鎖存器電路建模時,用非阻塞賦值;
3)用always塊建立組合邏輯模型時,用阻塞賦值;
4)在同一個always塊中建立時序和組合邏輯電路時,用非阻塞賦值;
5)在同一個alway塊中,不要即用非阻塞又用阻塞賦值;
6)不要在一個以上的always塊中為同一個變量賦值;
7)用$strobe系統任務來顯示用非阻塞賦值的變量值;
8)在賦值時不要使用#0延遲。
4.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