Verilog HDL基礎知識4之阻塞賦值 & 非阻塞賦值
阻塞賦值語句
串行塊語句中的阻塞賦值語句按順序執(zhí)行,它不會阻塞其后并行塊中語句的執(zhí)行。阻塞賦值語句使用“=”作為賦值符。
例子 阻塞賦值語句 reg x, y, z;
reg [15:0] reg_a, reg_b;
integer count; // 所有行為語句必須放在 initial 或 always 塊內部
initial
begin
x = 0; y = 1; z = 1; // 標量賦值
count = 0; // 整形變量賦值
reg_a = 16'b0; reg_b = reg_a; // 向量的初始化
#15 reg_a[2] = 1'b1; // 帶延遲的位選賦值
#10 reg_b[15:13] = {x, y, z} // 把拼接操作的結果賦值給向量的部分位(域)
count = count + 1; // 給整形變量賦值(遞增)
end
在例子中,只有在語句x=0執(zhí)行完成后,才會執(zhí)行y=1,而語句count=count+1按順序在最后執(zhí)行。由于阻塞賦值語句是按順序執(zhí)行的,因此如果在一個begin-end塊中使用了阻塞賦值語句,那么這個塊語句表現的是串行行為。例子中,begin-end塊中各條語句執(zhí)行的仿真時間為:
1.x=0到regb = rega之間的語句在仿真0時刻執(zhí)行;
2.語句rega[2]=0在仿真時刻15執(zhí)行; * 語句regb[15:13]={x,y,z}在仿真時刻25執(zhí)行;
3.語句count=count+1在仿真時刻25執(zhí)行;
4.由于前面的語句分別包含了15和10個時間單位的延遲,因此語句count=count+1將在第25個單位時刻執(zhí)行。
注意,在對寄存器類型變量進行過程賦值時,如果賦值符兩側的位寬不相等,則采用以下原則:
1.如果右側表達式的位寬較寬,則將保留從最低位開始的右側值,把超過左側位寬的高位丟棄;
2.如果左側位寬大于右側位寬,則不足的高位補0;
非阻塞賦值語句
非阻塞賦值語句允許賦值調度,但它不會阻塞位于同一個順序塊中其后語句的執(zhí)行。非阻塞賦值使用“?”作為賦值符。讀者會注意到,它與“小于等于”關系操作符是同一個符號,但在表達式中它被解釋為關系操作符,而在非阻塞賦值的環(huán)境下被解釋成非阻塞賦值。為了說明非阻塞賦值的意義以及阻塞賦值的區(qū)別,讓我們來考慮將阻塞賦值例子中的部分阻塞賦值改為非阻塞賦值后的結果,修改后語句如下:
例 非阻塞賦值語句 reg x, y, z;
reg [15:0] reg_a, reg_b;
integer count; // 所有的行為語句必須寫在initial 和 always塊內
initial
begin
x = 0; y = 1; z = 1; // 標量賦值;
count = 0; // 整形變量賦值
reg_a = 16'b0; reg_b = reg_a; // 向量的初始化
reg_a[2] <= #15 1'b1; // 帶延遲的位選賦值
reg_b[15:13] <= #10 {x, y, z} // 把拼接操作的結果賦值給向量的部分位(域)
count <= count + 1; // 給整形變量賦值(遞增)
end
在這個例子中,從x=0到regb=rega之間的語句是在仿真0時刻順序執(zhí)行的,之后的三條非阻塞賦值語句在regb=rega執(zhí)行完成后并發(fā)執(zhí)行。
1.rega[2]=0被調度到15個時間單位之后執(zhí)行,即仿真時刻為15; * regb[15:13]={x,y,z}被調度到10個時間單位之后執(zhí)行,即仿真時刻為10;
2.count=count+1被調度到無任何延遲執(zhí)行,即仿真時刻為0。
從上面的分析中可以看到,仿真器將非阻塞賦值調度到相應的仿真時刻,然后繼續(xù)執(zhí)行后面的語句,而不是停下來等待賦值的完成。一般情況下,非阻塞賦值是在當前仿真時刻的最后一個時間步,即阻塞賦值完成之后才執(zhí)行的。
在上面的例子中,我們把阻塞和非阻塞賦值語句混合在一起使用,目的是想更清楚地比較和說明它們的行為。需要提醒大家注意的是,不要在同一個always塊中混合使用阻塞和非阻塞賦值語句。
非阻塞賦值語句的應用
描述了非阻塞賦值的行為之后,理解究竟為什么要在硬件設計中使用非阻塞賦值是很重要的。非阻塞賦值可以被用來為常見的硬件電路行為建立模型,例如當某一事件發(fā)生后,多個數據并發(fā)傳輸的行為。在下面的例子中,當時鐘信號的上升沿到來之后,執(zhí)行三個數據的并發(fā)傳輸:
always @(posedge clock)
begin
reg1 <= #1 in1;
reg2 <= @(negedge clock) in2 ^ in3;
reg3 <= #1 reg1; // reg1 的“舊值”
end
每當一個時鐘上升沿到來時,其中的非阻塞賦值語句按下面的順序執(zhí)行:
1.在每個時鐘上升沿到來時讀取操作數變量in1, in2, in3和reg1,計算右側表達式的值,該值由仿真器臨時保存;
2.對左值的賦值由仿真器調度到相應的仿真時刻,延遲時間由語句中內嵌的延遲值確定。在本例中,對reg1的賦值需要等一個時間單位,對reg2的賦值需要等到時鐘信號下降沿到來的時刻,對reg3的賦值需要等待一個時間單位;
3.每個賦值操作在被調度的仿真時刻完成。注意,對左側變量的賦值使用的是由仿真器保存的表達式“舊值”,因此賦值完成的實際順序并不重要。在本例中,對reg3賦值使用的是reg1 的“舊值”,而不是在此之前對reg1賦予的新值,reg1的“舊值”是在賦值事件調度時由仿真器保存的。
由上面的分析可見,reg1,reg2和reg3的最終值與賦值完成的順序無關,體現了非阻塞賦值并行的特點。
為了進一步理解阻塞和非阻塞賦值,讓我們來看以下例子。這個例子的目的是在每個時鐘上升延處交換a和b這兩個寄存器變量的值,其中使用了兩個always語句。
例 使用非阻塞賦值來避免競爭 // 說明1:使用阻塞語句的兩個并行的always塊
always @(posedge clock)
a = b; always @(posedge clock)
b = a; // 說明2:使用非阻塞語句的兩個并行的always塊
always @(posedge clock)
a <= b; always @(posedge clock)
b <= a;
在第一種描述中我們使用了阻塞賦值,這樣就產生了競爭的情況:a=b和b=a,具體執(zhí)行順序的先后取決于所使用的仿真器。因此這段代碼達不到交換a和b值的目的,而是使得兩者具有相同的值(在時鐘上升沿到來之前a或b的值),具體是哪一個值與使用的仿真器有關。
在第二種描述中,我們通過使用非阻塞賦值語句來避免競爭:在每個時鐘上升沿到來的時候,仿真器讀取每個操作數的值,進而計算表達式的值并保存在臨時變量中;當賦值的時候,仿真器將保存的值賦予非阻塞賦值語句的左側變量。這樣就將讀和寫分開來了,達到了交換數據的目的,并且不受語句執(zhí)行順序的影響。以下例子介紹了如何使用阻塞賦值實現說明2中使用非阻塞語句才能實現的數值交換。
例 使用阻塞賦值來達到非阻塞賦值的目的 // 使用臨時變量和阻塞賦值來模仿非阻塞賦值的行為
always @(posedge clock)
begin
// 讀操作
// 把右側表達式的值放在臨時變量中
temp_a = a;
temp_b = b;
// 寫操作
// 把臨時變量的值放到左側變量中
a = temp_b;
b = temp_a;
end
在數字電路設計中,如果某事件發(fā)生后將產生多個數據的并發(fā)傳輸,我們強烈建議讀者使用非阻塞賦值來描述這種情形。如果我們使用阻塞賦值來描述這種情形,由于最終結果依賴于語句的具體執(zhí)行順序,有可能引起競爭風險;而非阻塞賦值語句的執(zhí)行結果是與執(zhí)行順序無關的,因此它能夠準確地描述這種情況。非阻塞賦值的典型應用包括流水線建模和多個互斥(mutually exclusive)數據傳輸的建模。使用非阻塞賦值所帶來的問題是,它會引起仿真速度的下降以及內存使用量的增加。
評論