關於Verilog HDL的一些技巧、易錯、易忘點(不定期更新)


   本文記錄一些關於Verilog HDL的一些技巧、易錯、易忘點等(主要是語法上),一方面是方便自己忘記語法時進行查閱翻看,另一方面是分享給大家,如果有錯的話,希望大家能夠評論指出。

關鍵詞:

  ·技巧篇:

      組合邏輯輸出類型選擇;

      語法上的變量交換;

 

  ·易忘篇:

      case/casex/casez語句;

      循環語句;

      數制和操作符;

      數據類型;

 

  ·易錯:

      數據的截位與擴位

      子模塊例化中隱式線網賦值

技巧篇:

  1、組合邏輯輸出:描述一個純組合邏輯電路時,盡量不要把輸出定義成輸出類型,例如描述下面的電路:

 

 1 module mux #(parameter N=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
36 
37 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 單元庫,否則將識別為寄存器堆。上例的RegFile1RegFile2 對象在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判斷隱式線網變量的位寬從頂層環境開始,也就是判斷隱式線網的位寬並不參看被調用的子模塊端口的位寬。

 


免責聲明!

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



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