本文首發於微信公眾號「花螞蟻」,想要學習FPGA及Verilog的同學可以關注一下。
在Verilog HDL語言中,信號有兩種賦值方式:
(1).非阻塞(Non_Blocking)賦值方式( 如 b <= a; )
(2).阻塞(Blocking)賦值方式( 如 b = a; )
非阻塞賦值方式和阻塞賦值方式的區別常給設計人員帶來問題。問題主要是給"always"塊內的reg型信號的賦值方式不易把握。
到目前為止,前面所舉的例子中的"always"模塊內的reg型信號都是採用下面的這種賦值方式:
b <= a;
這種方式的賦值並不是馬上執行的,也就是說"always"塊內的下一條語句執行後,b並不等於a,而是保持原來的值。"always"塊結束後,才進行賦值。而另一種賦值方式阻塞賦值方式,如下所示:
b = a;
這種賦值方式是馬上執行的。也就是說執行下一條語句時,b已等於a。儘管這種方式看起來很直觀,但是可能引起麻煩。下面舉例說明:
[例1]:
always @( posedge clk ) begin b<=a; c<=b; end
[例1] 中的"always"塊中用了非阻塞賦值方式,定義了兩個reg型信號b和c,clk信號的上升沿到來時,b就等於a,c就等於b,這裡應該用到了兩個觸發器。請注意:賦值是在"always"塊結束後執行的,c應為原來b的值。這個"always"塊實際描述的電路功能如下圖所示:
[例2]:
always @(posedge clk) begin b=a; c=b; end
[例2]中的 "always"塊用了阻塞賦值方式。clk信號的上升沿到來時,將發生如下的變化:b馬上取a的值,c馬上取b的值(即等於a),生成的電路圖如下所示只用了一個觸發器來寄存器a的值,又輸出給b和c。這大概不是設計者的初衷,如果採用[例1]所示的非阻塞賦值方式就可以避免這種錯誤。
為什麼一定要這樣做呢?這是因為要使綜合前模擬和綜合後模擬一致的緣故。
首先了解兩個定義:
RHS – 方程式右手方向的表達式或變數可分別縮寫為: RHS 表達式或 RHS 變數。
LHS – 方程式左手方向的表達式或變數可分別縮寫為: LHS 表達式或 LHS 變數。
阻塞賦值的執行可以認為是隻有一個步驟的操作:
計算RHS 並更新LHS,此時不能允許有來自任何其他Verilog 語句的幹擾。 所謂阻塞的概念是指在同一個always 塊中,其後面的賦值語句從概念上(即使不設定延遲)是在前一句賦值語句結束後再開始賦值的。
[例1]用阻塞賦值的反饋振蕩器(不好的例子)
module fbosc1 (y1, y2, clk, rst); output y1, y2; input clk, rst; reg y1, y2;
always @(posedge clk or posedge rst) begin if (rst) y1 = 0; // reset else y1 = y2; end
always @(posedge clk or posedge rst) begin if (rst) y2 = 1; // preset else y2 = y1; end endmodule
例1中,如果前一個always塊的複位信號先到0 時刻,則y1 和y2 都會取1,而如果後一個always 塊的複位信號先到0 時刻,則y1 和y2 都會取0。這清楚地說明這個Verilog 模塊是不穩定的會產生冒險和競爭的情況。
如果在一個過程塊中阻塞賦值的RHS 變數正好是另一個過程塊中阻塞賦值的LHS 變數,這兩個過程塊又用同一個時鐘沿觸發,如果阻塞賦值的次序安排不好,就會出現競爭。若這兩個阻塞賦值操作用同一個時鐘沿觸發,則執行的次序是無法確定的。
非阻塞賦值的操作可以看作為兩個步驟的過程:
1) 在賦值時刻開始時,計算非阻塞賦值RHS 表達式。
2) 在賦值時刻結束時,更新非阻塞賦值LHS 表達式。
[例2] 用非阻塞賦值的反饋振蕩器(正確示範)
module fbosc2 (y1, y2, clk, rst); output y1, y2; input clk, rst; reg y1, y2;
always @(posedge clk or posedge rst) begin if (rst) y1 <= 0; // reset else y1 <= y2; end
always @(posedge clk or posedge rst) begin if (rst) y2 <= 1; // preset else y2 <= y1; end endmodule
例2中,無論哪一個always 塊的複位信號先到, 兩個always 塊中的非阻塞賦值都在賦值開始時刻計算RHS 表達式,而在結束時刻才更新LHS 表達式。所以這兩個always 塊在複位信號到來後,在always 塊結束時 y1 為0 而y2為1 是確定的。從用戶的角度看這兩個非阻塞賦值正好是並行執行的。
非阻塞賦值操作只能用於對寄存器類型變數進行賦值,因此只能用在"initial"塊和"always"塊等過程塊中。非阻塞賦值不允許用於連續賦值。
下圖表示是一個簡單的移位寄存器方框圖:
[例3] 不正確地使用的阻塞賦值來描述移位寄存器。(方式 #1)
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
在上面的模塊中,按順序進行的阻塞賦值將使得在下一個時鐘上升沿時刻,所有的寄存器輸出值都等於輸入值d。在每個時鐘上升沿,輸入值d 將無延時地直接輸出到q3。
[例4] 用阻塞賦值來描述移位寄存器也是可行的,但這種風格並不好。(方式 #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
在上面的模塊中,阻塞賦值的次序是經過仔細安排的,以使模擬的結果與移位寄存器相一致。雖然該模塊可被綜合成移位寄存器,但我們不建議使用這種風格的模塊來描述時序邏輯。
[例5] 不好的用阻塞賦值來描述移位時序邏輯的風格(方式 #3)
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;
本例中,阻塞賦值分別被放在不同的always 塊裏。模擬時,這些塊的先後順序是隨機的,因此可能會出現錯誤的結果。這是Verilog 中的競爭冒險。按不同的順序執行這些塊將導致不同的結果。但是, 這些代碼的綜合結果卻是正確的流水線寄存器。也就是說,前模擬和後模擬結果可能會不一致。
[例6] 不好的用阻塞賦值來描述移位時序邏輯的風格(方式 #4)
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;
[例7] 正確使用非阻塞賦值來描述時序邏輯的設計風格 (方式 #1)
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
[例8] 正確使用非阻塞賦值來描述時序邏輯的設計風格 (方式 #2)
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
[例9] 正確使用非阻塞賦值來描述時序邏輯的設計風格 (方式 #3)
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
[例10] 正確使用非阻塞賦值來描述時序邏輯的設計風格 (方式 #4)
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
以上移位寄存器時序邏輯電路設計的例子表明:
?4種阻塞賦值設計方式中有1種可以保證模擬正確
?4種阻塞賦值設計方式中有3種可以保證綜合正確
?4種非阻塞賦值設計方式全部可以保證模擬正確
?4種非阻塞賦值設計方式全部可以保證綜合正確
阻塞賦值和非阻塞賦值的原則歸納如下: