過程賦值:用於對reg型變量賦值,改變寄存器的值或為以后排定改變。
語法
{阻塞性(blocking)賦值} RegisterLValue = [ TimingControl] Expression; {非阻塞性(non-blocking)賦值} RegisterLValue <= [ TimingControl] Expression; |
阻塞:在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句;
非阻塞:當前語句的執行不會阻塞下一語句的執行。
過程賦值右邊的表達式在賦值執行的時候算出。如果沒有內部賦值延時,左邊的寄存器由於阻塞性賦值將立即更新,而非阻塞性賦值則下一個仿真周期才更新左邊的寄存器。如果有內部賦值延時,左邊的寄存器只在發生內部賦值延時后更新。
例如對於阻塞性賦值:
- 當執行賦值語句時,算出右邊的表達式的值,但左邊的值不更新,直到產生定時控制事件或延時(稱為“內部賦值延時”)。
- 直到左邊被更新后(即經過內部賦值延時后)阻塞性賦值才完成。begin-end塊中的下一個語句直到此時才開始執行。
結合編程語句區分如下:
• 非阻塞(non-blocking) 賦值語句(b<= a):
- 塊內的賦值語句同時賦值;
- b的值被賦成新值a的操作, 是與塊內其他賦值語句同時完成的;
- 建議在可綜合風格的模塊中使用非阻塞賦值。
• 阻塞(blocking) 賦值語句(b = a):
- 完成該賦值語句后才能做下一句的操作;
- b的值立刻被賦成新值a;
- 硬件沒有對應的電路,因而綜合結果未知。
阻塞賦值和非阻塞賦值如果使用不當會存在冒險和競爭現象,必須按照下面兩條准則:
1)在描述組合邏輯的always塊中使用阻塞賦值,則綜合組合邏輯的電路結構;
2)在描述時序邏輯的always塊中使用非阻塞賦值,則綜合時序邏輯的電路結構。
在時鍾沿觸發的always塊中,如果用非阻塞賦值語句對reg型變量賦值;或者當reg型變量經過多次循環其值仍保持不變,則會在綜合中生成觸發器。若不想生成觸發器,而希望用reg型變量生成組合邏輯,則應使用電平觸發。在組合邏輯中,阻塞賦值只與電平有關,往往和觸發沿沒有關系,可以將其看成並行執行的;在時序邏輯中,非阻塞賦值是並行執行的;因此,優秀的HDL設計,其內部語句也是並行執行的。
非阻塞賦值與阻塞賦值示例:
1. 非阻塞賦值方式

1 module nonblocking(input clk, 2 input a, 3 output reg c); 4 reg b; 5 6 always @(posedge clk) begin 7 b <= a; 8 c <= b; 9 end 10 11 endmodule
編譯結果顯示使用了兩個寄存器。Clock "clk" Internal fmax is restricted to 420.17 MHz between source register "b" and destination register "c~reg0" (the specified clock operates at the specified fMAX between the specified source pin or register and the specified destination pin or register). 器件內部的fmax(Internal fmax)分析器件中同步元件(如寄存器)到同步元件之間的延時,然后計算出最高頻率。
RTL原理圖如下所示:
波形功能和時序仿真分別如下所示:
非阻塞賦值在塊結束時才完成賦值操作。c的值比b的值落后一個時鍾周期(功能仿真表現為b→c,時序仿真表現為b→c~reg0)。時序仿真中,第一個時鍾沿15.743ns后(時鍾周期 + tco = 10ns+5.743ns)輸出c。
2. 阻塞賦值方式

1 module blocking(input clk, 2 input a, 3 output reg c); 4 reg b; 5 6 always @(posedge clk) begin 7 b = a; //兩句交換,則等效於非阻塞賦值方式 8 c = b; 9 end 10 11 endmodule
編譯結果顯示使用了1個寄存器,所以時序列表中沒有Clock Setup: 'clk'一項。
RTL原理圖如下所示:
波形功能和時序仿真分別如下所示:
阻塞賦值在該語句結束時就完成賦值操作。在一個塊語句中,如果有多條阻塞賦值語句,在前面的賦值語句沒有完成之前,后面的語句就不能被執行,就像被阻塞了一樣,因此稱為阻塞賦值方式。可以看到,b被優化掉了,因為這里c的值與b的值一樣!
在阻塞賦值語句中,賦值次序非常重要,而在非阻塞賦值語句中,賦值的次序並不重要。
仿真器首先按照仿真時間對事件進行排序,然后再在當前仿真時間里按照事件的優先級順序進行排序。活躍事件是優先級最高的事件。在活躍事件之間,它們的執行順序是隨機的。阻塞賦值(=)、連續賦值(assign)以及非阻塞賦值的右式計算等都屬於活躍事件。
下面再通過一個典型案例,進一步說明阻塞賦值和非阻塞賦值的區別。
【例】數組Data[0]、Data[1]、Data[2]和Data[3]都是4bit的數據。找到它們當中最小的數據,並將該數據的索引輸出到LidMin中(類似 “冒泡排序”),且在一個時鍾周期內完成。首先將Lid_Min設置一個任意初始值,然后將Data[0]~Data[3]與Data[Lid_Min]進行比較,每比較一個數,就將較小的索引暫存在Lid_Min中,然后再進行下一次比較。當4組數據比較完成之后,最小的數據索引就會保留在Lid_Min中。例如,若4個數據中Data[2]最小,則LidMin的值為2。

1 module Bubble_Up(input Rst_n, 2 input Clk, 3 input [5:0] Data [0:3], //需要SystemVerilog extensions(通常端口不可用數組型) 4 output reg [1:0] Lid_Min ); 5 always @(posedge Clk or negedge Rst_n) begin 6 if (~Rst_n) begin 7 Lid_Min <= 2'd0; 8 end 9 else 10 begin //begin…end中為非阻塞賦值 11 if (Data[0] <= Data[Lid_Min]) begin 12 Lid_Min <= 2'd0; 13 end 14 if (Data[1] <= Data[Lid_Min]) begin 15 Lid_Min <= 2'd1; 16 end 17 if (Data[2] <= Data[Lid_Min]) begin 18 Lid_Min <= 2'd2; 19 end 20 if (Data[3] <= Data[Lid_Min]) begin 21 Lid_Min <= 2'd3; 22 end 23 end 24 end 25 26 endmodule
【注】SystemVerilog extensions設置:Settings --> Analysis & Synthesis Settings-->Verilog HDL Input --> Verilog Version--> SystemVerilog-2005。
以上代碼中使用了非阻塞賦值,但仿真波形結果並不正確,如下圖所示。圖中的Data[0]~Data[3]分別為11、3、10和12,Lid_Min的初始值為0。Lid_Min結果應為1,因為Data[1]最小。
原因如下:
在時鍾上升沿到來后,且Rst_n信號無效時開始執行后續4個語句,假設此時Lid_Min為0,Data[0]~Data[3]分別為11、3、10和12:
第一句的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必須被更新,而這點正是阻塞賦值的特點。將代碼段”else”內部改為阻塞賦值(<=→=),仿真波形如圖所示:
在仿真過程中,”else”段第二句的if為真,執行Lid_Min = 2'd1,根據阻塞賦值的特點,Lid_Min被立刻賦值為1。執行第三句if時if為假,直接跳過Lid_Min = 2'd2不執行,同樣也跳過Lid_Min = 2'd3不執行。Lid_Min被最終賦值為1,這正是我們想要的結果。
仿真采用Cyclone II系列EP2C5F256C6器件。編譯結果顯示,非阻塞賦值占用39個LE(logic elements),阻塞賦值占用80個LE,兩者均占用2個寄存器(registers)。
為了使代碼看起來更簡潔,也可使用for語句改寫”else”段代碼如下:

1 begin 2 for (i = 2'd0; i <= 2'd3; i = i + 2'd1) begin 3 if (Data[i] <= Data[Lid_Min]) begin 4 Lid_Min = i; 5 end 6 end 7 end
需要注意的是,for語句的電路功能比較難理解,其展開形式往往更具可讀性。