本文首發於微信公眾號「花螞蟻」,想要學習FPGA及Verilog的同學可以關注一下。

一、初步理解阻塞賦值與非阻塞賦值

在Verilog HDL語言中,信號有兩種賦值方式:

(1).非阻塞(Non_Blocking)賦值方式( 如 b <= a; )

  • 塊結束後才完成賦值操作。
  • b的值並不是立刻就改變的。
  • 這是一種比較常用的賦值方法。(特別在編寫可綜合模塊時)

(2).阻塞(Blocking)賦值方式( 如 b = a; )

  • 賦值語句執行完後,塊才結束。
  • b的值在賦值語句執行完後立刻就改變的。
  • 可能會產生意想不到的結果。

非阻塞賦值方式和阻塞賦值方式的區別常給設計人員帶來問題。問題主要是給"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]所示的非阻塞賦值方式就可以避免這種錯誤。

二、深入理解阻塞和非阻塞賦值的不同

  1. 在描述組合邏輯的always 塊中用阻塞賦值,則綜合成組合邏輯的電路結構。
  2. 在描述時序邏輯的always 塊中用非阻塞賦值,則綜合成時序邏輯的電路結構。

為什麼一定要這樣做呢?這是因為要使綜合前模擬和綜合後模擬一致的緣故。

首先了解兩個定義:

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

endmodule

在上面的模塊中,阻塞賦值的次序是經過仔細安排的,以使模擬的結果與移位寄存器相一致。雖然該模塊可被綜合成移位寄存器,但我們不建議使用這種風格的模塊來描述時序邏輯。

[例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;

endmodule

本例中,阻塞賦值分別被放在不同的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;

endmodule

[例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

endmodule

[例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種非阻塞賦值設計方式全部可以保證綜合正確

阻塞賦值和非阻塞賦值的原則歸納如下:

  • 原則1:時序電路建模時,用非阻塞賦值。
  • 原則2:鎖存器電路建模時,用非阻塞賦值。
  • 原則3:用always 塊寫組合邏輯時,採用阻塞賦值。
  • 原則4:在同一個always 塊中同時建立時序和組合邏輯電路時,用非阻塞賦值。
  • 原則5:在同一個always 塊中不要同時使用非阻塞賦值和阻塞賦值。
  • 原則6:不要在多個always 塊中為同一個變數賦值。

本文首發於微信公眾號「花螞蟻」,想要學習FPGA及Verilog的同學可以關注一下。


推薦閱讀:
相關文章