阻塞賦值與非阻塞賦值


轉:

http://hi.baidu.com/zhang_bi/blog/item/57edb701a9da6b00728b65db.html

在Verilog中有兩種類型的賦值語句:阻塞賦值語句(“=”)和非阻塞賦值語句(“<=”)。正確地使用這兩種賦值語句對於Verilog的設計和仿真非常重要。下面我們以例子說明阻塞和非阻塞賦值的區別。

  

        我們先來看幾段代碼及其對應的電路:

HDL源代碼
對應的RTL電路
module Shifter1(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 = D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
▲ 大家可以看到Q1、Q2被優化掉了
module Shifter2(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;
end
endmodule
 
module Shifter3(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q3 = Q2;
Q2 = Q1;
Q1 = D;
end
endmodule
 
module Shifter4(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 = Q2;
end
endmodule
 
module Shifter5(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;

always @(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 = Q2;
end
endmodule
 
module Shifter6(
Clk,
D,
Q3
);
input Clk;
input [7:0] D;
output [7:0] Q3;
reg [7:0] Q3, Q2, Q1;
always @(posedge Clk)
begin
Q1 <= D;
Q2 = Q1;
Q3 <= Q2;
end
endmodule
 

        從上面的例子中,我們可以看出,在阻塞賦值語句中,賦值的次序非常重要,而在非阻塞賦值語句中,賦值的次序並不重要。


 

  下面我們具體分析一下阻塞和非阻塞賦值的語義本質:

  阻塞:在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句;
  非阻塞:當前語句的執行不會阻塞下一語句的執行。


  先看阻塞賦值的情況:我們來看這段代碼:

    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語句的電路功能比較難理解,可以將這些語句展開,增強代碼的可讀性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM