關於Verilog HDL的一些技巧、易錯、易忘點
·技巧篇:
組合邏輯輸出型別選擇;
語法上的變數交換;
·易忘篇:
case/casex/casez語句;
迴圈語句;
數制和操作符;
資料型別;
·易錯:
資料的截位與擴位
子模組例化中隱式線網賦值
技巧篇:
1、組合邏輯輸出:描述一個純組合邏輯電路時,儘量不要把輸出定義成輸出型別,例如描述下面的電路:
1 module mux #(parameterN=2)( 2 3 input [N-1:0] a, // sel=00時,選擇該輸入 4 5 input [N-1:0] b, // sel=01時,選擇該輸入 6 7 input [N-1:0] c, // sel=10時,選擇該輸入 8 9 input [N-1:0] d, // sel=11時,選擇該輸入 10 11 input [1:0] sel, //選擇器 12 13 output[N-1:0] mux_out);// 選擇器結果輸出 14 15 reg [N-1:0] mux_temp; // 臨時變數,用於防止其他呼叫者誤認為輸出鎖存 16 17 assign mux_out=mux_temp;18 19 //always_comb //該語句在systemverilog中可以替換下面的語句並檢查 20 21 always @ (a or b or c or d or sel) 22 23 case (sel) 24 25 0 : mux_temp = a; 26 27 1 : mux_temp = b; 28 29 2 : mux_temp = c; 30 31 3 : mux_temp = d; 32 33 default : $display("Error with sel signal"); 34 35 endcase 3637 endmodule
2、語法上的變數交換:在always 語句塊內部,任何一個語句塊(以begin 開始,end 結束)都是序列執行的,只是存在賦值立刻生效還是事後生效的差異,即後面將要重點論述的阻塞賦值和非阻塞賦值兩種區別(這兩種賦值語句綜合的區別請看我的另一篇博文,連結為:
http://www.cnblogs.com/IClearner/p/7188875.html)。
對於下面的程式碼,從純語法上講:
1 always@(*)begin 2 3 temp=b; 4 5 b=a; 6 7 a=temp; 8 9 end
上面的例子,就是一個序列執行的例子,能夠完成 a 與 b 的數值交換,如果不是序列執行,上述程式碼將很難完成類似各類程式控制。
易忘篇/陌生篇:
1、case語句的各種注意情況及對應綜合電路
(留坑,以後填)
2、迴圈語句:迴圈語句,主要包含 for、while、forever、repeat 四類語句,但只有 for 語句才有可能具備可綜合性,其餘均為測試驗證所準備。
迴圈語句 for 的語法為:
for(表示式 1;表示式 2;表示式 3) 語句
其實可以將 for 語句理解為:
for(迴圈變數賦初值;迴圈結束條件;迴圈變數增值)執行語句
·for 迴圈的例子如下,這是最原始的一個8bit 乘法器實現,其中<<表示左移,等效於乘以2 的移位次方:
module mac_8 #(parameter size = 8)( input wire [size-1:0] opa, opb, output reg [2*size-1:0] mult_out ); reg[2*size-1:0] result; integer bindex; always @(*)begin result = opb[0]?opa:0; for( bindex=1; bindex<=size-1; bindex=bindex+1 )begin//根據乘法特性,判斷後是否進行移位 if(opb[bindex]) result = result + (opa<<(bindex)); end mult_out = result; end endmodule
模擬波形如下所示:
(上述例子也可以當做技巧看,也就是使用位移實現乘法運算)
3、數制與操作符
這裡數制和操作符…其實我已經基本是滾瓜爛熟了,放在這裡是給初學者查詢的…
上面那個圖是我在word寫的,在Verilog中,一般一個整數我們稱呼為 xx位xx進位制數。數的位數和符號數的易忘點,圖已經說明了。
通常有時候,一些初學者往往不知道位數與值之間的關係,我當初也不知道,現在我來給自己備忘一下:
3’b101:3位的二進位制數101,(在我們的慣用的數制:10進位制)數值大小為5;
3’d6:3位的10進位制數6,等效於3’b110;數制大小為6;
3’d9:由於只有3位,十進位制數9的數值為4位:4‘b1001;所以3‘d9的數值高位被階段,3’d9表示真實的數值為3’d1或者3‘b001,數值大小為1
用例 |
說明 |
‘hAE |
8 位十六進位制數 |
10’b10 |
左邊添 0 佔位,實際為 10’b0000000010 |
10’bx1x0 |
左邊添 x 佔位,實際為 10’bxxxxxxx1x0 |
3’b1001_0011 |
3’b011 相等 |
運算類別 |
符號 |
運算子含義 |
||
算術運算子 |
+ |
加法(二元運算子) |
||
- |
減法(二元運算子) |
|||
* |
乘法(二元運算子) |
|||
/ |
除法(二元運算子) |
|||
% |
取模(二元運算子) |
|||
關係運算符 |
> |
大於 |
||
< |
小於 |
|||
>= |
不小於 |
|||
<= |
不大於 |
|||
== |
邏輯相等 |
|||
!= |
邏輯不等 |
|||
邏輯運算子 |
&& |
邏輯與 |
||
|| |
邏輯或 |
|||
! |
邏輯非 |
|||
按位邏輯運算子 |
~ |
一元非,相當於非門運算 |
||
& |
二元與,相當於與門運算 |
|||
| |
二元或,相當於或門運算 |
|||
^ |
二元異或,相當於異或門運算 |
|||
~^,^~ |
|
二元異或非即同或,相當於同或門運算 |
|
|
|
||||
移位運算子 |
>> |
右移 |
||
<< |
左移 |
|||
賦值運算子 |
= |
阻塞賦值,等效於立即生效 |
||
<= |
非阻塞賦值,等效於當前模組結束後賦值,或者下個時鐘
週期賦值生效 |
|||
縮減運算子 |
& |
一元與,相當於資料 bit 逐個進行與操作 |
||
| |
一元或,相當於資料 bit 逐個進行或操作 |
|||
^ |
一元異或,相當於資料 bit 逐個進行異或操作 |
|||
~^ |
一元同或,相當於資料 bit 逐個進行同或操作 |
單元運算子:可以帶一個運算元,運算元放在運算子的右邊。
二元運算子:可以帶二個運算元,運算元放在運算子的兩邊。
三元運算子:可以帶三個運算元,這三個數用三目運算子分隔開。
縮減運算子是對單個運算元進行或與非遞推運算,最後的運算結果是一位的二進位制數。縮減運算子目前支援或與非三種操作。具體運算過程如下:第一步先將運算元的第一位與第二位進行或與非運算,第二步將運算結果與第三位進行或與非運算,依次類推,直至最後一位。
拼接運算子則與縮減運算子相反,主要目的是將兩個或多個訊號的某些位拼接起來進行運算操作。拼接運算不消耗任何邏輯資源,只是一個單純的連線邏輯。其使用方法如下:
{訊號1的某幾位,訊號2的某幾位,..,..,訊號n的某幾位}
即把某些訊號的某些位詳細地列出來,中間用逗號分開,最後用大括號括起來表示一個整體訊號。例如:
{a,b[3:0],c,3’b101}
也可以寫成如下形式:
{a,b[3],b[2],b[1],b[0],c,1’b1,1’b0,1’b1}
在位拼接表示式中不允許存在沒有指明位數的訊號。這是因為在計算拼接訊號的位寬的大小時必需知道其中每個訊號的位寬。位拼接還可以用重複法來簡化表示式,例如:
{6{a}}//這等同於{a,a,a,a,a,a,a},a可為任意位元位寬
位拼接還可以用巢狀的方式來表達,例如:
{c,{3{a,b}}}//這等同於{c,a,b,a,b,a,b}
用於表示重複的表示式如上例中的6 和3,必須是常數表示式或者引數。
4、資料型別:
·當一個wire 型別的訊號沒有被驅動時,預設值為Z(高阻)。
·有一種專門針對儲存器模型(RAM)的定義方法,例如:
(* ramstyle =”MLAB”*)reg[31:0] RegFile1[15:0];
(* ramstyle =”MLAB”*)reg[31:0] RegFile2[15:0];
在ASIC 設計中,這種描述方式只會被識別為一系列的暫存器堆,並不會被識別為RAM;ASIC 中應當利用RAM 單元庫(IP)例化的方法描述RAM。而在FPGA 中,綜合器首先將這種描述識別為RAM 的宣告,並通過識別物件的行為確認描述物件是RAM 還是暫存器堆。如果後續的描述行為滿足RAM 的特徵,就自動替換為FPGA 內部內建的RAM 單元庫,否則將識別為暫存器堆。上例的RegFile1 與RegFile2 物件在Altera FPGA 中,將被識別為16 個32bit 位寬的RAM,而且指定為MLAB 型別。
易錯篇
1、資料的截位與擴位
(1)擴位操作
位寬擴充套件:如果所規定的位寬太小,那個將會截斷高的幾個位(如2’b1101,將變成2’b01),如果指定的位寬太大,則會用0或者x/z來向左擴充套件數值,但不會擴充套件符號位。
對於有符號數:如果位寬位寬小於數值規定,符號位可能被截斷(如數-4‘sd15,即1111_0001,將會被截斷,代表的值為+1,即0001);
如果位寬大於數值規定,都是用0來擴充,因此負數可能被擴充為正數。
①在定點計算中,經過加法和乘法運算後,輸出結果的位寬會增加。但如果繼續使用和輸入運算元同等位寬的數來表示結果,就會丟失有用的位元資訊,造成輸出結果錯誤。
②例如,在有限字長的情況下,若兩個M 位的數相加,其結果最高可能為M +1位;若兩個M 位的數相乘,其結果最多可為2M 位。
③4 位元加法運算中的擴位現象:
有符號數):
4’b0101 和4’b0111 分別對應著+5 和+7,二者相加後本應為+12,即5’b01100。但由於位寬限制,如不擴位,只能保留低4 位,即4’b1100,對應著-4,造成嚴重的計算錯誤。類似的錯誤還會造成負數相加變成正數。
無符號數):
4’b1111 + 4’b1111 = 5’11110 ,但是由於加數是4 位,在Verilog語言中只保留低4 位,就會得到4’b1111 + 4’b1111 = 4’1110 的結果,這樣就會造成計算錯誤。
注意:
①無論是有符號數還是無符號數,高位寬的變數(或者數)賦值給低位寬的變數(或者數),低位寬將只能接收到高位寬數的的低位數值。
②低位寬賦值給高位寬時,有:無符號數/變數 給 無符號數/變數,符號數/變數 給 符號數/變數,這兩個都不會出錯;
無符號數/變數 給 符號數/變數,符號數/變數 給 無符號數/變數 時將出現錯誤。
(2)截位操作
①在有限字長的情況下,若兩個M 位的數相加,其結果就是M +1位;若兩個M 位的數相乘,其結果就是2M 位。但在實際的操作過程中,考慮到資源的問題,不能任由相加、相乘操作來增加運算元的位寬,必須進行截斷。
②例如,兩個16 位數相乘後,其結果為32 位,如再和一個16 位數相乘,結果就變為48 位,這樣下去,用不了幾個乘法操作就會使運算元的位寬劇增,所佔用的硬體資源也會很多。因此,需要將乘積結果進行截位,寄存在M 位的暫存器中。
(3)截位與擴位規範(Verilog中)
①加法實現規範,擴充套件符號位後相加。
1 reg[12:0] Adder_Out; 2 reg[11:0] Adder_In1,Adder_In2; 3 Adder_Out <= {Adder_In1[11],Adder_In1} + {Adder_In2[11],Adder_In2};
②對於擷取乘法的結果,需要加溢位保護的擷取規範。例如要擷取12 位元輸出的第6 位到第2 位,其實現程式碼為:
1 if((addRakeOut[11:6] == 0) || (addRakeOut[11:6] == 63)) 2 tmptraffic <= addRakeOut[6:2]; 3 else 4 tmptraffic <= (addRakeOut[11] == 1) ? 16 : 15;
或者:
1 if((addRakeOut[11:6] == 6'b000000) || (addRakeOut[11:6] == 6'b111111)) 2 tmptraffic <= addRakeOut[6:2]; 3 else 4 tmptraffic <= (addRakeOut[11] == 1) ? 5’b10000 : 5’sb01111;
③移位操作規範,移位操作的截位和加法器的截位規則類似,如16 位元資料的左、右1 位元移位示例:
1 reg[15:0] Data; 2 if((Data[15:14] == 2'b00) || (Data[15:14] == 2'b11)) 3 Data <= {Data[14:0],1'b0};//左移一位 4 else 5 Data <= (Data[15]) ? 16'b1000_0000_0000_0000 : 16'b0111_1111_1111_1111; 6 //右移一位 7 Data <= {Data[15],Data[15:1]};
2、子模組例化中隱式線網賦值時
子模組例化時,要用線網型別的變數 連線 被呼叫的子模組埠訊號 和主模組的 訊號,當這個線網變數沒有在主模組中宣告時,該線網變數的位寬有以下情況:
①如果該線網變數是用於連線 主模組的埠訊號 和 被呼叫的子模組的埠訊號,那麼該線網變數的位寬 跟 主模組埠的位寬相同。
②如果該線網變數只在主模組裡起連線作用(如連線兩個 被呼叫的子模組),該線網位寬預設為1位。
③Verilog判斷隱式線網變數的位寬從頂層環境開始,也就是判斷隱式線網的位寬並不參看被呼叫的子模組埠的位寬。