轉:
http://hi.baidu.com/zhang_bi/blog/item/57edb701a9da6b00728b65db.html
在Verilog中有兩種類型的賦值語句:阻塞賦值語句(“=”)和非阻塞賦值語句(“<=”)。正確地使用這兩種賦值語句對於Verilog的設計和仿真非常重要。下面我們以例子說明阻塞和非阻塞賦值的區別。
我們先來看幾段代碼及其對應的電路:
|
|
module Shifter1( |
![]() |
module Shifter2( |
![]() |
module Shifter3( |
![]() |
module Shifter4( |
![]() |
module Shifter5( |
![]() |
module Shifter6( |
![]() |
從上面的例子中,我們可以看出,在阻塞賦值語句中,賦值的次序非常重要,而在非阻塞賦值語句中,賦值的次序並不重要。
下面我們具體分析一下阻塞和非阻塞賦值的語義本質:
阻塞:在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句;
非阻塞:當前語句的執行不會阻塞下一語句的執行。
先看阻塞賦值的情況:我們來看這段代碼:
always @(posedge Clk)
begin
Q1 = D;
Q2 = Q1;
Q3 = Q2;
end
always語句塊對Clk的上升沿敏感,當發生Clk 0~1的跳變時,執行該always語句。
在begin...end語句塊中所有語句是順序執行的,而且最關鍵的是,阻塞賦值是在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句的。
在本例中,D的值賦給Q1以后,再執行Q2 = Q1;同樣在Q2的值更新以后,才執行Q3 = Q2。這樣,最終的計算結果就是Q3 = D。
所有的語句執行完以后,該always語句等待Clk的上升沿,從而再一次觸發begin...end語句。
接下來,再看看非阻塞賦值的情況。
所謂非阻塞賦值,顧名思義,就是指當前語句的執行不會阻塞下一語句的執行。
always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end
首先執行Q1 <= D,產生一個更新事件,將D的當前值賦給Q1,但是這個賦值過程並沒有立刻執行,而是在事件隊列中處於等待狀態。
然后執行Q2 <= Q1,同樣產生一個更新事件,將Q1的當前值(注意上一語句中將D值賦給Q1的過程並沒有完成,Q1還是舊值)賦給Q2,這個賦值事件也將在事件隊列中處於等待狀態。
再執行Q3 <= Q2,產生一個更新事件,將Q2的當前值賦給Q3,這個賦值事件也將在事件隊列中等待執行。
這時always語句塊執行完成,開始對下一個Clk上升沿敏感。
那么什么時候才執行那3個在事件隊列中等待的事件呢?只有當當前仿真時間內的所有活躍事件和非活躍事件都執行完成后,才開始執行這些非阻塞賦值的更新事件。這樣就相當於將D、Q1和Q2的值同時賦給了Q1、Q2和Q3。
注:
*仿真器首先按照仿真時間對事件進行排序,然后再在當前仿真時間里按照事件的優先級順序進行排序。
*活躍事件是優先級最高的事件。在活躍事件之間,它們的執行順序是隨機的。阻塞賦值(=)、連續賦值(assign)以及非阻塞賦值的右式計算等都屬於活躍事件。
下面通過一個典型案例,進一步說明阻塞賦值和非阻塞賦值的區別。
這里有一個數組:Data[0]、Data[1]、Data[2]和Data[3],它們都是4比特的數據。我們需要在它們當中找到一個最小的數據,同時將該數據的索引輸出到LidMin中,這個算法有點類似於“冒泡排序”的過程,而且需要在一個時鍾周期內完成。例如,如果這4個數據中 Data[2]最小,那么LidMin的值則為2。
module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [3:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min <= 2'd0;
end
else
begin
if (Data[0] <= Data[Lid_Min]) //"<="表示小於等於
begin
Lid_Min <= 2'd0; //"<="表示非阻塞賦值
end
if (Data[1] <= Data[Lid_Min])
begin
Lid_Min <= 2'd1;
end
if (Data[2] <= Data[Lid_Min])
begin
Lid_Min <= 2'd2;
end
if (Data[3] <= Data[Lid_Min])
begin
Lid_Min <= 2'd3;
end
end
end
endmodule
我們的原意是首先將Lid_Min設置為一個初始值(任意值都可以),然后將Data[0]~Data[3]與Data[Lid_Min]進行比較,每比較一個數,就將較小的索引暫存在Lid_Min中,然后再進行下一次比較。當4組數據比較完成之后,最小的數據索引就會保留在Lid_Min 中。
我們在以上代碼中使用了非阻塞賦值,結果發現,仿真波形根本不是我們所需要的功能,如圖所示,圖中的Data[0]~Data[3]分別為 11、3、10和12,Lid_Min的初始值為0。按道理來說,Lid_Min的計算結果應該為1,因為Data[1]最小,但仿真波形卻為2。
為什么會得出這樣的結果呢?
在時鍾上升沿到來以后,且Rst_n信號無效時開始執行以下4個語句,假設這時候的Lid_Min是0,Data[0]~Data[3]分別為11、3、10和12:
if (Data[0] <= Data[Lid_Min]) //"<="表示小於等於
begin
Lid_Min <= 2'd0; //"<="表示非阻塞賦值
end
if (Data[1] <= Data[Lid_Min])
begin
Lid_Min <= 2'd1;
end
if (Data[2] <= Data[Lid_Min])
begin
Lid_Min <= 2'd2;
end
if (Data[3] <= Data[Lid_Min])
begin
Lid_Min <= 2'd3;
end
第一句的if為真,因此執行Lid_Min <= 2’d0,而這時候,Lid_Min並沒有立刻被賦值,而是調度到事件隊列中等待執行,這是非阻塞賦值的特點。
第二句的if為真,因此執行Lid_Min <= 2’d1,這是Lid_Min也沒有立刻被賦值為1,而是調度到事件隊列中等待執行。當前的Lid_Min還是0,沒有發生任何變化。
同樣,第三句的if也為真,因此執行Lid_Min <= 2’d2,將更新事件調度到事件隊列中等待執行。當前的Lid_Min還是0。
而第四句的if為假,因此直接跳過Lid_Min <= 2’d3,這時跳出always語句,等待下一個時鍾上升沿。
在以上的always語句執行完成以后,仿真時間沒有前進。這時存在於事件隊列中當前仿真時間上的3個被調度的非阻塞更新事件開始執行,它們分別將Lid_Min更新為0、1和2。
按照Verilog語言的規范,這3個更新事件屬於同一仿真時間內的事件,它們之間的執行順序隨機,這就產生了不確定性。一般的仿真器在實現的時候是根據它們被調度的先后順序執行的,事件隊列就像一個存放事件的FIFO,它是分層事件隊列的一部分,如圖所示:
這3個事件在同一仿真時間被一一執行,而真正起作用的時最后一個更新事件,因此在仿真的時候得到的最終結果時Lid_Min為2。
然后我們想要得到的結果是,在每個if語句判斷並執行完成以后,Lid_Min先暫存這個中間值,再進行下一次比較,也就是說在進行下一次比較之前,這個Lid_Min必須被更新,而這一點也正是阻塞賦值的特點,因此我們將代碼作如下更改:
module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [3:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min <= 2'd0;
end
else
begin
if (Data[0] <= Data[Lid_Min]) //"<="表示小於等於
begin
Lid_Min = 2'd0; //"<="表示非阻塞賦值
end
if (Data[1] <= Data[Lid_Min])
begin
Lid_Min = 2'd1;
end
if (Data[2] <= Data[Lid_Min])
begin
Lid_Min = 2'd2;
end
if (Data[3] <= Data[Lid_Min])
begin
Lid_Min = 2'd3;
end
end
end
endmodule
其仿真波形如圖所示:
在代碼仿真過程中,第二句的if為真,執行Lid_Min = 2'd1,根據阻塞賦值的特點,Lid_Min被立刻賦值為1。在執行第三句if的時候,if (Data[2] <= Data[Lid_Min])為假,直接跳過Lid_Min = 2'd2不執行,同樣也跳過Lid_Min = 2'd3不執行。Lid_Min被最終賦值為1,這正是我們想要的結果。
另外,為了使代碼看起來更簡潔,我們使用for語句改寫了代碼:
module Bubble_Up(
Rst_n,
Clk,
Data,
Lid_Min
);
input Rst_n;
input Clk;
input [5:0] Data [0:3];
output [1:0] Lid_Min;
reg [1:0] Lid_Min;
integer i;
always @(posedge Clk or negedge Rst_n)
begin
if (~Rst_n)
begin
Lid_Min = 2'd0;
end
else
begin
for (i = 2'd0; i <= 2'd3; i = i + 2'd1)
begin
if (Data[i] <= Data[Lid_Min])
begin
Lid_Min = i;
end
end
end
end
endmodule
這種寫法與前面展開的寫法完全等效,功能完全一致。今后大家在讀代碼時發現帶有for語句的電路功能比較難理解,可以將這些語句展開,增強代碼的可讀性。