Verilog HDL 基础
Verilog HDL 基礎
一.Verilog 的基本概念
1. 硬件描述語言HDL
1.1 特點:
描述電路的連接、描述電路的功能、在不同抽象級上描述電路、
描述電路的時序、表達具有并行性
1.2 形式
Verilog 和 VHDL
2、自頂向下設計的基本概念
-
模塊(module)是Verilog的基本描述單位,用于描述某個設計的功能或結構及與其他模塊通信的外部端口。
-
模塊內容是嵌在module和endmodule兩個語句之間。每個模塊實現特定的功能,模塊可進行層次的嵌套,因此可以將大型的數字電路設計分割成大小不一的小模塊來實現特定的功能,最后通過由頂層模塊調用子模塊來實現整體功能,這就是Top-Down的設計思想。
3.抽象級別
系統級
算法級
RTL級 :描述數據在寄存器之間的流動和如何處理、控制這些數據流動的模型。
以上三種都屬于行為描述,只有RTL級才與邏輯電路有明確的對應關系。
門級 :描述邏輯門以及邏輯門之間的連接的模型。
開關級
二.Verilog 的基本格式
1.示例1
多路選擇器
module ?mux (out ,int 0,int 1,sel);parametet N=8;output ?[N:1] ? out;input [N:1] ? ?in0,in1;input ? sel;assign out=sel?in1:in0; //描述組合電路 ? endmodule2.示例2
module count4(out,reset,clk);output[3:0] out;input reset,clk; ?//默認為wire型,描述組合邏輯reg[3:0] out; ?//數據類型定義:寄存器型(有保持功能) //描述時序邏輯:always @(posedge clk) //時鐘上升沿執行下面語句塊:描述時序beginif (reset) out<=0; //同步復位else out<=out+1'b1; //計數end endmodule三.數據類型及常量、變量
1.常用詞法
-
Verilog HDL區分大小寫
-
Verilog HDL的關鍵字(如:always、and、input等) 都采用小寫
2.常量之數字
1.整數:
-
語法:<位寬> '<進制> <數值>
-
形式:
-
二進制整數(b或B)
-
十進制整數(d或D)
-
十六進制整數(h或H)
-
八進制整數(o或O)
-
表達方式有以下三種:
<位寬><進制><數字>這是一種全面的描述方式。
<進制><數字>在這種描述方式中,數字的位寬采用缺省位寬(這由具體的機器系統決定,但至少32位)。
<數字>在這種描述方式中,采用缺省進制十進制。
在表達式中,位寬指明了數字的精確位數。例如:一個4位二進制數的數字的位寬為4,一個4位十六進制數的數字的位寬為16(因為每單個十六進制數就要用4位二進制數來表示)。見下例:
8'b10101100 //位寬為8的數的二進制表示, 'b表示二進制 8'ha2 //位寬為8的數的十六進制,'h表示十六進制。注意:
4’h1111實際表示4’b0001
2.x和z值:
在數字電路中,x代表不定值,z代表高阻值。
一個x可以用來定義十六進制數的四位二進制數的狀態,八進制數的三位,二進制數的一位。
z的表示方式同x類似。z還有一種表達方式是可以寫作?。在使用case表達式時建議使用這種寫法,以提高程序的可讀性。見下例:
4'b10x0 //位寬為4的二進制數從低位數起第二位為不定值 4'b101z //位寬為4的二進制數從低位數起第一位為高阻值 12'dz //位寬為12的十進制數其值為高阻值(第一種表達方式) 12'd? //位寬為12的十進制數其值為高阻值(第二種表達方式) 8'h4x //位寬為8的十六進制數其低四位值為不定值3.負數:
一個數字可以被定義為負數,只需在位寬表達式前加一個減號,減號必須寫在數字定義表達式的最前面。注意減號不可以放在位寬和進制之間也不可以放在進制和具體的數之間。見下例:
-8'd5 //這個表達式代表5的補數(用八位二進制數表示) 8'd-5 //非法格式4.下劃線(underscore_):
下劃線可以用來分隔開數的表達以提高程序可讀性。但不可以用在位寬和進制處,只能用在具體的數字之間。見下例:
16'b1010_1011_1111_1010 ?//合法格式 8'b_0011_1010 ?//非法格式當常量不說明位數時,默認值是32位,每個字母用8位的ASCII值表示。
例:
10=32'd10=32'b1010 1=32'd1=32'b1 -1=-32'd1=32'hFFFFFFFF ‘BX=32'BX=32'BXXXXXXX…X “AB”=16'B01000001_010000103.常量之參數
在Verilog HDL中用parameter來定義常量,即用parameter來定義一個標識符代表一個常量,稱為符號常量,即標識符形式的常量,采用標識符代表一個常量可提高程序的可讀性和可維護性。parameter型數據是一種常數型的數據,其說明格式如下:
parameter 參數名1=表達式,參數名2=表達式, …, 參數名n=表達式;parameter是參數型數據的確認符,確認符后跟著一個用逗號分隔開的賦值語句表。在每一個賦值語句的右邊必須是一個常數表達式。
也就是說,該表達式只能包含數字或先前已定義過的參數。見下列:
parameter msb=7; ?//定義參數msb為常量7 parameter e=25, f=29; //定義二個常數參數 parameter r=5.7; ?//聲明r為一個實型參數 parameter byte_size=8, byte_msb=byte_size-1; //用常數表達式賦值 parameter average_delay = (r+f)/2; ?//用常數表達式賦值4.變量
一. wire型
wire型數據常用來表示用于以assign關鍵字指定的組合邏輯信號。Verilog程序模塊中輸入輸出信號類型缺省時自動定義為wire型。wire型信號可以用作任何方程式的輸入,也可以用作“assign”語句或實例元件的輸出。
wire型信號的格式同reg型信號的很類似。其格式如下:
wire [n-1:0] 數據名1,數據名2,…數據名i; //共有i條總線,每條總線內有n條線路或
wire [n:1] 數據名1,數據名2,…數據名i;wire是wire型數據的確認符,[n-1:0]和[n:1]代表該數據的位寬,即該數據有幾位。最后跟著的是數據的名字。如果一次定義多個數據,數據名之間用逗號隔開。聲明語句的最后要用分號表示語句結束。看下面的幾個例子。
wire a; //定義了一個一位的wire型數據 wire [7:0] b; //定義了一個八位的wire型數據 wire [4:1] c, d; //定義了二個四位的wire型數據二. reg型
寄存器是數據儲存單元的抽象。寄存器數據類型的關鍵字是reg。通過賦值語句可以改變寄存器儲存的值,其作用與改變觸發器儲存的值相當。
Verilog HDL語言提供了功能強大的結構語句使設計者能有效地控制是否執行這些賦值語句。這些控制結構用來描述硬件觸發條件,例如時鐘的上升沿和多路器的選通信號。reg類型數據的缺省初始值為不定值,x。
reg型數據常用來表示用于“always”模塊內的指定信號,常代表觸發器。通常,在設計中要由“always”塊通過使用行為描述語句來表達邏輯關系。在“always”塊內被賦值的每一個信號都必須定義成reg型。
reg型數據的格式如下:
reg [n-1:0] 數據名1,數據名2,… 數據名i;或
reg [n:1] 數據名1,數據名2,… 數據名i;reg是reg型數據的確認標識符,[n-1:0]和[n:1]代表該數據的位寬,即該數據有幾位(bit)。最后跟著的是數據的名字。如果一次定義多個數據,數據名之間用逗號隔開。聲明語句的最后要用分號表示語句結束。看下面的幾個例子:
reg rega; //定義了一個一位的名為rega的reg型數據 reg [3:0] ?regb; //定義了一個四位的名為regb的reg型數據 reg [4:1] regc, regd; //定義了兩個四位的名為regc和regd的reg型數據 reg [7:0] mymem[1023:0] //定義1k字節的存儲器對于reg型數據,其賦值語句的作用就象改變一組觸發器的存儲單元的值。
在Verilog中有許多構造(construct)用來控制何時或是否執行這些賦值語句。這些控制構造可用來描述硬件觸發器的各種具體情況,如觸發條件用時鐘的上升沿等,或用來描述具體判斷邏輯的細節,如各種多路選擇器。
reg型數據的缺省初始值是不定值x。reg型數據可以賦正值,也可以賦負值。但當一個reg型數據是一個表達式中的操作數時,它的值被當作是無符號值,即正值。例如:當一個四位的寄存器用作表達式中的操作數時,如果開始寄存器被賦以值-1,則在表達式中進行運算時,其值被認為是+15。
注意:
reg型只表示被定義的信號將用在“always”塊內,理解這一點很重要。并不是說reg型信號一定是寄存器或觸發器的輸出。雖然reg型信號常常是寄存器或觸發器的輸出,但并不一定總是這樣。
初學者往往會對wire和reg的用法混淆,下面是對wire和reg用法的總結:
wire用法總結
1.wire可以在Verilog中表示任意寬度的單線/總線
2.wire可以用于模塊的輸入和輸出端口以及一些其他元素并在實際模塊聲明中
3.wire不能存儲值(無狀態),并且不能在always @塊內賦值(=或<=)左側使用。
4.wire是assign語句左側唯一的合法類型
5.wire只能用于組合邏輯
reg用法總結
類似于電線,但可以存儲信息(有內存,有狀態)允許連接到模塊的輸入端口,但不能連接到實例化的輸出
在模塊聲明中,reg可以用作輸出,但不能用作輸入
在always@(......)語句塊內,= 或者 <= 賦值語句的左邊必須是是reg變量
在initial語句塊內,= 賦值語句的左邊必須是是reg變量
Reg不能用于assign賦值語句的左側
當與@(posedge clock)塊一起使用時,reg可用于創建寄存器
reg可用于組合邏輯和時序邏輯
構建一個模塊module時,
input必須是wire
output可以是wire也可以是reg
inout必須是wire
例化模塊時,
外部連接input端口的可以是wire也可以是reg
外部連接output端口的必須是wire
外部連接inout端口的必須是wire
四.運算符
Verilog HDL語言的運算符范圍很廣,其運算符按其功能可分為以下幾類:
算術運算符(+,-,×,/,%)
賦值運算符(=,<=)
關系運算符(>,<,>=,<=)
邏輯運算符(&&,||,!)
條件運算符(?:)
位運算符(,|,^,&,^)
移位運算符(<<,>>)
拼接運算符({ })
其它
在Verilog HDL語言中運算符所帶的操作數是不同的,按其所帶操作數的個數運算符可分為三種:
單目運算符(unary operator):可以帶一個操作數,操作數放在運算符的右邊。
二目運算符(binary operator):可以帶二個操作數,操作數放在運算符的兩邊。
三目運算符(ternary operator):可以帶三個操作,這三個操作數用三目運算符分隔開。
見下例:
clock = ~clock; ?// ~是一個單目取反運算符, clock是操作數。 c = a | b; ?// 是一個二目按位或運算符, a 和 b是操作數。 r = s ? t : u; // ?: 是一個三目條件運算符, s,t,u是操作數。下面對常用的幾種運算符進行介紹。
1.算術運算符
在Verilog HDL語言中,算術運算符又稱為二進制運算符,共有下面幾種:
+(加法運算符,或正值運算符,如 rega+regb,+3)
- (減法運算符,或負值運算符,如 rega-3,-3)
× (乘法運算符,如rega*3)
/ (除法運算符,如5/3)
% (模運算符,或稱為求余運算符,要求%兩側均為整型數據。如7%3的值為1)
在進行整數除法運算時,結果值要略去小數部分,只取整數部分。而進行取模運算時,結果值的符號位采用模運算式里第一個操作數的符號位。見下例。
模運算表達式 ? ? ? 結果 ? ? 說明 10%3 ? ? ? ? ? ? ? 1 ? ? ? 余數為1 11%3 ? ? ? ? ? ? ? 2 ? ? ? 余數為2 12%3 ? ? ? ? ? ? ? 0 ? ? ? 余數為0即無余數 -10%3 ? ? ? ? ? ? -1 ? ? ? 結果取第一個操作數的符號位,所以余數為-1 11%3 ? ? ? ? ? ? ? 2 ? ? ? 結果取第一個操作數的符號位,所以余數為2.注意: 在進行算術運算操作時,如果某一個操作數有不確定的值x,則整個結果也為不定值x。
2.位運算符
Verilog HDL作為一種硬件描述語言,是針對硬件電路而言的。在硬件電路中信號有四種狀態值1,0,x,z.在電路中信號進行與或非時,反映在Verilog HDL中則是相應的操作數的位運算。Verilog HDL提供了以下五種位運算符:
~ //取反
& //按位與
| //按位或
^ //按位異或
^~ //按位同或(異或非)
說明:
-
位運算符中除了~是單目運算符以外,均為二目運算符,即要求運算符兩側各有一個操作數.
-
位運算符中的二目運算符要求對兩個操作數的相應位進行運算操作。
下面對各運算符分別進行介紹:
1) "取反"運算符~
~是一個單目運算符,用來對一個操作數進行按位取反運算。
其運算規則見下表:
舉例說明:
rega='b1010;//rega的初值為'b1010 rega=~rega;//rega的值進行取反運算后變為'b01012) "按位與"運算符&
按位與運算就是將兩個操作數的相應位進行與運算,
其運算規則見下表:
3) "按位或"運算符|
按位或運算就是將兩個操作數的相應位進行或運算。
其運算規則見下表:
4) "按位異或"運算符^(也稱之為XOR運算符)
按位異或運算就是將兩個操作數的相應位進行異或運算。
其運算規則見下表:
5) "按位同或"運算符^~
按位同或運算就是將兩個操作數的相應位先進行異或運算再進行非運算.
其運算規則見下表:
6) 不同長度的數據進行位運算
兩個長度不同的數據進行位運算時,*系統會自動的將兩者按右端對齊.位數少的操作數會在相應的高位用0填滿,以使兩個操作數按位進行操作.*
3 邏輯運算符
在Verilog HDL語言中存在三種邏輯運算符:
&& 邏輯與
|| 邏輯或
! 邏輯非
"&&"和"||"是二目運算符,它要求有兩個操作數,如(a>b)&&(b>c),(a<b)||(b<c)。
"!"是單目運算符,只要求一個操作數,如!(a>b)。
下表為邏輯運算的真值表。它表示當a和b的值為不同的組合時,各種邏輯運算所得到的值。
邏輯運算符中"&&"和"||"的優先級別低于關系運算符,"!" 高于算術運算符。見下例:
-
(a>b)&&(x>y) 可寫成: a>b && x>y
-
(ab)||(xy) 可寫成:ab || xy
-
(!a)||(a>b) 可寫成: !a || a>b
為了提高程序的可讀性,明確表達各運算符間的優先關系,建議使用括號.
4.關系運算符
關系運算符共有以下四種:
a < b a小于b
a > b a大于b
a <= b a小于或等于b
a >= b a大于或等于b
在進行關系運算時,如果聲明的關系是假的(flase),則返回值是0,如果聲明的關系是真的(true),則返回值是1,如果某個操作數的值不定,則關系是模糊的,返回值是不定值。
所有的關系運算符有著相同的優先級別。關系運算符的優先級別低于算術運算符的優先級別。見下例:
a < size-1 //這種表達方式等同于下面 a < (size-1) //這種表達方式。 size - ( 1 < a ) //這種表達方式不等同于下面 size - 1 < a //這種表達方式。從上面的例子可以看出這兩種不同運算符的優先級別。當表達式size-(1<a)進行運算時,關系表達式先被運算,然后返回結果值0或1被size減去。而當表達式 size-1<a 進行運算時,size先被減去1,然后再同a相比。
5.等式運算符
在Verilog HDL語言中存在四種等式運算符:
== (等于)
!= (不等于)
=== (等于)
!== (不等于)
這四個運算符都是二目運算符,它要求有兩個操作數。"=="和"!="又稱為邏輯等式運算符。其結果由兩個操作數的值決定。由于操作數中某些位可能是不定值x和高阻值z,結果可能為不定值x。
而"="和"!"運算符則不同,它在對操作數進行比較時對某些位的不定值x和高阻值z也進行比較,兩個操作數必需完全一致,其結果才是1,否則為0*。*"="和"!"運算符常用于case表達式的判別,所以又稱為"case等式運算符"。這四個等式運算符的優先級別是相同的。下面畫出==與===的真值表,幫助理解兩者間的區別。
下面舉一個例子說明“==”和“===”的區別。
例:
if(A==1’bx) $display(“AisX”); (當A等于X時,這個語句不執行) if(A===1’bx) $display(“AisX”); (當A等于X時,這個語句執行)6.移位運算符
在Verilog HDL中有兩種移位運算符:
<< (左移位運算符) 和 >>(右移位運算符)。
其使用方法如下:
a >> n;//a右移n位a << n;//a左移n位a代表要進行移位的操作數,n代表要移幾位。這兩種移位運算都用0來填補移出的空位。下面舉例說明:
module shift;reg [3:0] start, result;initialbeginstart = 1; //start在初始時刻設為值0001result = (start<<2);//移位后,start的值0100,然后賦給result。end endmodule從上面的例子可以看出,start在移過兩位以后,用0來填補空出的位。
進行移位運算時應注意移位前后變量的位數,下面將給出一例。
例:
4’b1001<<1 = 5’b10010; 4’b1001<<2 = 6’b100100; 1<<6 = 32’b1000000; 4’b1001>>1 = 4’b0100; 4’b1001>>4 = 4’b0000;7.位拼接運算符(Concatation)
在Verilog HDL語言有一個特殊的運算符:位拼接運算符{}。用這個運算符可以把兩個或多個信號的某些位拼接起來進行運算操作。其使用方法如下:
{信號1的某幾位,信號2的某幾位,..,..,信號n的某幾位}即把某些信號的某些位詳細地列出來,中間用逗號分開,最后用大括號括起來表示一個整體信號。見下例:
{a,b[3:0],w,3’b101}也可以寫成為
{a,b[3],b[2],b[1],b[0],w,1’b1,1’b0,1’b1}在位拼接表達式中不允許存在沒有指明位數的信號。這是因為在計算拼接信號的位寬的大小時必需知道其中每個信號的位寬。
位拼接還可以用重復法來簡化表達式。見下例:
{4{w}} //這等同于{w,w,w,w}位拼接還可以用嵌套的方式來表達。見下例:
{b,{3{a,b}}} //這等同于{b,a,b,a,b,a,b}用于表示重復的表達式如上例中的4和3,必須是常數表達式。
補:
1.截取數據的不同位數進行拼接:
例:data1 = 0100_1101
data2 = 1011_0011
data = {data1[3:0],data2[7:4]}
則可得:data = 1101_1011
2.作為移位運算符使用,即在一個數據中對不同的位進行拼接,可以實現移位的作用:
這里需要補充一下移位運算符的用法:
左移:<< 右移:>>
在使用移位運算符時,無論是有符號還是無符號類型,移位出現的空缺一律用0填補,但是當進行的操作數和結果數位數不一致時,二者有明顯的差異:
無符號類型:當操作數和結果數不一致時,需要在高位填0后,再進行移位;
有符號類型:當操作數和結果數不一致時,需要在高位填補符號位后,再進行移位。
對于移位運算符,要根據具體的電路去截取不同的位數,這里舉一個例子:
data = 0011_0101
data1 = {1’b0,data[7:1]},則data1 = 0001_1010
data2 = (data >> 1),則data2 = 0001_1010
二者實現的效果是一致的。
8.縮減運算符(reduction operator)
縮減運算符是單目運算符,也有與或非運算。
其與或非運算規則類似于位運算符的與或非運算規則,但其運算過程不同。位運算是對操作數的相應位進行與或非運算,操作數是幾位數則運算結果也是幾位數。
而縮減運算則不同,縮減運算是對單個操作數進行或與非遞推運算,最后的運算結果是一位的二進制數。
縮減運算的具體運算過程是這樣的:第一步先將操作數的第一位與第二位進行或與非運算,第二步將運算結果與第三位進行或與非運算,依次類推,直至最后一位。
例如:
reg [3:0] B; reg C; C = &B;相當于:
C =( (B[0]&B[1]) & B[2] ) & B[3];由于縮減運算的與、或、非運算規則類似于位運算符與、或、非運算規則,這里不再詳細講述,請參照位運算符的運算規則介紹。
五.語句
| 過程賦值語句 | |
| 條件語句 | if-else 語句 |
| case 語句 | |
| 循環語句 | forever 語句 |
| repeat 語句 | |
| while 語句 | |
| for 語句 | |
| 結構說明語句 | initial 語句 |
| always 語句 | |
| task 語句 | |
| function 語句 | |
| 編譯預處理語句 | define 語句 |
| include 語句 | |
| timescale 語句 |
1.賦值語句
1.1 連續賦值語句(assign) :常用于對wire型變量賦值。
input a,b;output c;assign c=a&b;1.2 過程賦值語句:常用于對reg型變量進行賦值
(1).非阻塞(Non_Blocking)賦值方式( 如 b <= a;)
-
塊結束后才完成賦值操作。
-
b的值并不是立刻就改變的。
-
這是一種比較常用的賦值方法。(特別在編寫可綜合模塊時)
-
一條非阻塞賦值語句的執行是不會阻塞下一條語句的執行,并行執行。
module non_block(c,a,b,clk);output c,b;input a,clk;reg c,b;always @(posedge clk)begin b<=a; ? //b的值變為ac<=b; ? //c的值為b的舊值end endmodule
(2).阻塞(Blocking)賦值方式( 如 b = a; )
-
賦值語句執行完后,塊才結束。
-
b的值在賦值語句執行完后立刻就改變的。
-
可能會產生意想不到的結果。
-
該語句結束時就完成賦值操作,前面的語句沒有完成前,后面的語句不能執行,多個阻塞賦值語句是順序執行的。
2.條件語句
1.1 if_else語句
if語句是用來判定所給定的條件是否滿足,根據判定的結果(真或假)決定執行給出的兩種操作之一。Verilog HDL語言提供了三種形式的if語句。
(1). if(表達式)語句
例如:
if ( a > b ) out1 <= int1;(2).if(表達式) 語句1
else 語句2
例如:
if(a>b) out1<=int1;else ?out1<=int2;(3).if(表達式1) 語句1;
else if(表達式2) 語句2;
else if(表達式3) 語句3;
........
else if(表達式m) 語句m;
else 語句n;
例如:
if(a>b) out1<=int1; else if(a==b) out1<=int2; else out1<=int3;六點說明:
(1).三種形式的if語句中在if后面都有“表達式”,一般為邏輯表達式或關系表達式。系統對表達式的值進行判斷,若為0,x,z,按“假”處理,若為1,按“真”處理,執行指定的語句。
(2) .第二、第三種形式的if語句中,在每個else前面有一分號,整個語句結束處有一分號。
例如:
這是由于分號是Verilog HDL語句中不可缺少的部分,這個分號是if語句中的內嵌套語句所要求的。如果無此分號,則出現語法錯誤。但應注意,不要誤認為上面是兩個語句(if語句和else語句)。它們都屬于同一個if語句。else子句不能作為語句單獨使用,它必須是if語句的一部分,與if配對使用。
(3).在if和else后面可以包含一個內嵌的操作語句(如上例),也可以有多個操作語句,此時用begin和end這兩個關鍵詞將幾個語句包含起來成為一個復合塊語句。如:
if(a>b)beginout1<=int1;out2<=int2;end ? elsebeginout1<=int2;out2<=int1;end注意在end后不需要再加分號。因為begin_end內是一個完整的復合語句,不需再附加分號。
(4).允許一定形式的表達式簡寫方式。如下面的例子:
if(expression) 等同與 if( expression == 1 ) if(!expression) 等同與 if( expression != 1 )(5).if語句的嵌套
在if語句中又包含一個或多個if語句稱為if語句的嵌套。一般形式如下:
if(expression1)if(expression2) 語句1 (內嵌if)else 語句2 elseif(expression3) 語句3 (內嵌if)else 語句4應當注意if與else的配對關系,else總是與它上面的最近的if配對。如果if與else的數目不一樣,為了實現程序設計者的企圖,可以用begin_end塊語句來確定配對關系。例如:
if( )begin if( ) 語句1 (內嵌if)end else語句2這時begin_end塊語句限定了內嵌if語句的范圍,因此else與第一個if配對。注意begin_end塊語句在if_else語句中的使用。因為有時begin_end塊語句的不慎使用會改變邏輯行為。見下例:
if(index>0)for(scani=0;scani<index;scani=scani+1)if(memory[scani]>0)begin$display("...");memory[scani]=0;end else /*WRONG*/ $display("error-indexiszero");盡管程序設計者把else寫在與第一個if(外層if)同一列上,希望與第一個if對應,但實際上else是與第二個if對應,因為它們相距最近。正確的寫法應當是這樣的:
if(index>0)beginfor(scani=0;scani<index;scani=scani+1)if(memory[scani]>0)begin$display("...");memory[scani]=0;endendelse /*WRONG*/$display("error-indexiszero");(6).if_else例子
下面的例子是取自某程序中的一部分。這部分程序用if_else語句來檢測變量index以決定三個寄存器modify_segn中哪一個的值應當與index相加作為memory的尋址地址。并且將相加值存入寄存器index以備下次檢測使用。程序的前十行定義寄存器和參數。
//定義寄存器和參數。 reg [31:0] instruction, segment_area[255:0]; reg [7:0] index; reg [5:0] modify_seg1, modify_seg2, modify_seg3; parametersegment1=0, inc_seg1=1,segment2=20, inc_seg2=2,segment3=64, inc_seg3=4,data=128; //檢測寄存器index的值 if(index<segment2)begininstruction = segment_area[index + modify_seg1];index = index + inc_seg1;end else if(index<segment3)begininstruction = segment_area[index + modify_seg2];index = index + inc_seg2;end else if (index<data)begininstruction = segment_area[index + modify_seg3]; index = index + inc_seg3;end else instruction = segment_area[index];1.2 case語句
case語句是一種多分支選擇語句,if語句只有兩個分支可供選擇,而實際問題中常常需要用到多分支選擇,Verilog語言提供的case語句直接處理多分支選擇。它的一般形式如下:
case(表達式) <case分支項> endcase
casez(表達式) <case分支項> endcase
casex(表達式) <case分支項> endcase
case分支項的一般格式如下:
分支表達式: ? ? ? ? 語句 缺省項(default項): 語句說明:
a) case括弧內的表達式稱為控制表達式,case分支項中的表達式稱為分支表達式。控制表達式通常表示為控制信號的某些位,分支表達式則用這些控制信號的具體狀態值來表示,因此分支表達式又可以稱為常量表達式。
b) 當控制表達式的值與分支表達式的值相等時,就執行分支表達式后面的語句。如果所有的分支表達式的值都沒有與控制表達式的值相匹配的,就執行default后面的語句。
c) default項可有可無,一個case語句里只準有一個default項。
下面是一個簡單的使用case語句的例子。該例子中對寄存器rega譯碼以確定result的值。
reg [15:0] rega; reg [9:0] result; case(rega) 16 'd0: result = 10 'b0111111111; 16 'd1: result = 10 'b1011111111; 16 'd2: result = 10 'b1101111111; 16 'd3: result = 10 'b1110111111; 16 'd4: result = 10 'b1111011111; 16 'd5: result = 10 'b1111101111; 16 'd6: result = 10 'b1111110111; 16 'd7: result = 10 'b1111111011; 16 'd8: result = 10 'b1111111101; 16 'd9: result = 10 'b1111111110; default: result = 'bx; endcased) 每一個case分項的分支表達式的值必須互不相同,否則就會出現矛盾現象(對表達式的同一個值,有多種執行方案)。
e) 執行完case分項后的語句,則跳出該case語句結構,終止case語句的執行。
f) 在用case語句表達式進行比較的過程中,只有當信號的對應位的值能明確進行比較時,比較才能成功。因此要注意詳細說明case分項的分支表達式的值。
g) case語句的所有表達式的值的位寬必須相等,只有這樣控制表達式和分支表達式才能進行對應位的比較。*一個經常犯的錯誤是用'bx, 'bz 來替代 n'bx, n'bz,這樣寫是不對的,因為信號x, z的缺省寬度是機器的字節寬度,通常是32位(此處 n 是case控制表達式的位寬)。*
下面將給出 case, casez, casex 的真值表:
case語句與if_else_if語句的區別主要有兩點:
與case語句中的控制表達式和多分支表達式這種比較結構相比,if_else_if結構中的條件表達式更為直觀一些。
對于那些分支表達式中存在不定值x和高阻值z位時,case語句提供了處理這種情況的手段。下面的兩個例子介紹了處理x,z值位的case語句。
[例1]:
case ( select[1:2] ) 2 'b00: result = 0; 2 'b01: result = flaga; 2 'b0x, 2 'b0z: result = flaga? 'bx : 0; 2 'b10: result = flagb; 2 'bx0, 2 'bz0: result = flagb? 'bx : 0; default: result = 'bx; endcase[例2]:
case(sig) 1 'bz: $display("signal is floating"); 1 'bx: $display("signal is unknown"); default: ?$display("signal is %b", sig); endcaseVerilog HDL針對電路的特性提供了case語句的其它兩種形式用來處理case語句比較過程中的不必考慮的情況( don't care condition )。其中casez語句用來處理不考慮高阻值z的比較過程,casex語句則將高阻值z和不定值都視為不必關心的情況。所謂不必關心的情況,即在表達式進行比較時,不將該位的狀態考慮在內。這樣在case語句表達式進行比較時,就可以靈活地設置以對信號的某些位進行比較。見下面的兩個例子:
[例3]:
reg[7:0] ir; casez(ir)8 'b1???????: instruction1(ir);8 'b01??????: instruction2(ir);8 'b00010???: instruction3(ir);8 'b000001??: instruction4(ir); endcase[例4]:
reg[7:0] r, mask; mask = 8'bx0x0x0x0; casex(r^mask)8 'b001100xx: stat1;8 'b1100xx00: stat2;8 'b00xx0011: stat3;8 'bxx001100: stat4; endcase注:由于使用條件語句不當在設計中生成了原本沒想到有的鎖存器
Verilog HDL設計中容易犯的一個通病是由于不正確使用語言,生成了并不想要的鎖存器。下面我們給出了一個在“always"塊中不正確使用if語句,造成這種錯誤的例子。
檢查一下左邊的"always"塊,if語句保證了只有當al=1時,q才取d的值。這段程序沒有寫出 al = 0 時的結果, 那么當al=0時會怎么樣呢?
在"always"塊內,如果在給定的條件下變量沒有賦值,這個變量將保持原值,也就是說會生成一個鎖存器!
如果設計人員希望當 al = 0 時q的值為0,else項就必不可少了,請注意看右邊的"always"塊,整個Verilog程序模塊綜合出來后,"always"塊對應的部分不會生成鎖存器。
Verilog HDL程序另一種偶然生成鎖存器是在使用case語句時缺少default項的情況下發生的。
case語句的功能是:在某個信號(本例中的sel)取不同的值時,給另一個信號(本例中的q)賦不同的值。
注意看下圖左邊的例子,如果sel=00,q取a值,而sel=11,q取b的值。這個例子中不清楚的是:如果sel取00和11以外的值時q將被賦予什么值?在下面左邊的這個例子中,程序是用Verilog HDL寫的,即默認為q保持原值,這就會自動生成鎖存器。
右邊的例子很明確,程序中的case語句有default項,指明了如果sel不取00或11時,編譯器或仿真器應賦給q的值。程序所示情況下,q賦為0,因此不需要鎖存器。
以上就是怎樣來避免偶然生成鎖存器的錯誤。
-
如果用到if語句,最好寫上else項。
-
如果用case語句,最好寫上default項。
遵循上面兩條原則,就可以避免發生這種錯誤,使設計者更加明確設計目標,同時也增強了Verilog程序的可讀性。
3.循環語句
在Verilog HDL中存在著四種類型的循環語句,用來控制執行語句的執行次數。
forever 連續的執行語句。
repeat 連續執行一條語句 n 次。
while 執行一條語句直到某個條件不滿足。如果一開始條件即不滿足(為假),則語句一次也不能被執行。
for通過以下三個步驟來決定語句的循環執行。
-
a) 先給控制循環次數的變量賦初值。
-
b) 判定控制循環的表達式的值,如為假則跳出循環語句,如為真則執行指定的語句后,轉到第三步。
-
c) 執行一條賦值語句來修正控制循環變量次數的變量的值,然后返回第二步。
下面對各種循環語句詳細的進行介紹。
1.1forever語句
forever語句的格式如下:
forever 語句; ? 或 ? forever begin 多條語句 endforever循環語句常用于產生周期性的波形,用來作為仿真測試信號。它與always語句不同處在于不能獨立寫在程序中,而必須寫在initial塊中。
1.2 repeat語句
repeat語句的格式如下:
repeat(表達式) 語句;或 ? repeat(表達式) begin 多條語句 end在repeat語句中,其表達式通常為常量表達式。
下面的例子中使用repeat循環語句及加法和移位操作來實現一個乘法器。
parameter size=8,longsize=16; reg [size:1] opa, opb; reg [longsize:1] result;begin: mult reg [longsize:1] shift_opa, shift_opb; shift_opa = opa; shift_opb = opb; result = 0; ? repeat(size)beginif(shift_opb[1])result = result + shift_opa; shift_opa = shift_opa <<1;shift_opb = shift_opb >>1;end end1.3 while語句
while語句的格式如下:
while(表達式) 語句或用如下格式:
while(表達式) begin 多條語句 end下面舉一個while語句的例子,該例子用while循環語句對rega這個八位二進制數中值為1的位進行計數。
begin: count1s reg[7:0] tempreg; count=0; tempreg = rega; ? while(tempreg)beginif(tempreg[0]) count = count + 1;tempreg = tempreg>>1;end end1.4 for語句
for語句的一般形式為:
for(表達式1;表達式2;表達式3) 語句它的執行過程如下:
先求解表達式1;
求解表達式2,若其值為真(非0),則執行for語句中指定的內嵌語句,然后執行下面的第3步。若為假(0),則結束循環,轉到第5步。
若表達式為真,在執行指定的語句后,求解表達式3。
轉回上面的第2步驟繼續執行。
執行for語句下面的語句。
for語句最簡單的應用形式是很易理解的,其形式如下:
for(循環變量賦初值;循環結束條件;循環變量增值)執行語句for循環語句實際上相當于采用while循環語句建立以下的循環結構:
begin循環變量賦初值;while(循環結束條件)begin執行語句循環變量增值;end end這樣對于需要8條語句才能完成的一個循環控制,for循環語句只需兩條即可。
下面分別舉兩個使用for循環語句的例子。例1用for語句來初始化memory。例2則用for循環語句來實現前面用repeat語句實現的乘法器。
[例1]:
begin: init_mem reg[7:0] tempi; ? for(tempi=0;tempi<memsize;tempi=tempi+1) memory[tempi]=0; end[例2]:
parameter size = 8, longsize = 16; reg[size:1] opa, opb; reg[longsize:1] result;begin:mult integer bindex; result=0; for( bindex=1; bindex<=size; bindex=bindex+1 )if(opb[bindex])result = result + (opa<<(bindex-1)); end在for語句中,循環變量增值表達式可以不必是一般的常規加法或減法表達式。下面是對rega這個八位二進制數中值為1的位進行計數的另一種方法。見下例:
begin: count1sreg[7:0] tempreg;count=0;for( tempreg=rega; tempreg; tempreg=tempreg>>1 )if(tempreg[0]) count=count+1; end4.結構說明語句
過程塊是行為模型的基礎。
過程塊有兩種:
-
initial塊,只能執行一次
-
always塊,循環執行
過程塊中有下列部件:
-
過程賦值語句:在描述過程塊中的數據流
-
高級結構(循環,條件語句):描述塊的功能
-
時序控制:控制塊的執行及塊中的語句。
initial語句與always語句和begin_end與fork_join是一種高頻搭配:
1.1 initial語句
initial語句的格式如下:
initialbegin語句1;語句2;......語句n; end舉例說明:
[例1]:
initialbeginareg=0; //初始化寄存器aregfor(index=0;index<size;index=index+1)memory[index]=0; //初始化一個memory end在這個例子中用initial語句在仿真開始時對各變量進行初始化。
[例2]:
initialbegininputs = 'b000000; //初始時刻為0#10 inputs = 'b011001; #10 inputs = 'b011011; #10 inputs = 'b011000; #10 inputs = 'b001000; end從這個例子中,我們可以看到initial語句的另一用途,即用initial語句來生成激勵波形作為電路的測試仿真信號。一個模塊中可以有多個initial塊,它們都是并行運行的。
initial塊常用于測試文件和虛擬模塊的編寫,用來產生仿真測試信號和設置信號記錄等仿真環境。
1.2 always語句
always語句在仿真過程中是不斷重復執行的。
其聲明格式如下:
always <時序控制> <語句>always語句由于其不斷重復執行的特性,只有和一定的時序控制結合在一起才有用。如果一個always語句沒有時序控制,則這個always語句將會發成一個仿真死鎖。見下例:
[例1]:
always areg = ~areg;這個always語句將會生成一個0延遲的無限循環跳變過程,這時會發生仿真死鎖。如果加上時序控制,則這個always語句將變為一條非常有用的描述語句。見下例:
[例2]:
always #10 ?areg = ~areg;這個例子生成了一個周期為20 的無限延續的信號波形,常用這種方法來描述時鐘信號,作為激勵信號來測試所設計的電路。
[例3]:
reg[7:0] counter; reg tick; ? always @(posedge areg) begintick = ~tick;counter = counter + 1;end這個例子中,每當areg信號的上升沿出現時把tick信號反相,并且把counter增加1。這種時間控制是always語句最常用的。
always 的時間控制可以是沿觸發也可以是電平觸發的,可以單個信號也可以多個信號,中間需要用關鍵字 or 連接,如:
always @(posedge clock or posedge reset) //由兩個沿觸發的always塊begin……endalways @( a or b or c ) //由多個電平觸發的always塊begin……end沿觸發的always塊常常描述時序邏輯,如果符合可綜合風格要求可用綜合工具自動轉換為表示時序邏輯的寄存器組和門級邏輯,而電平觸發的always塊常常用來描述組合邏輯和帶鎖存器的組合邏輯,如果符合可綜合風格要求可轉換為表示組合邏輯的門級邏輯或帶鎖存器的組合邏輯。一個模塊中可以有多個always塊,它們都是并行運行的。
always是一個極高頻的語法,always@()用法總結如下
① always@(信號名)
? 信號名有變化就觸發事件
例: always@( clock) a=b;② always@( posedge信號名)
? 信號名有上升沿就觸發事件
例: always@( posedge clock) a=b;③ always@(negedge信號名)
? 信號名有下降沿就觸發事件
例: always@( negedge clock) a=b;④ always@(敏感事件1or敏感事件2or…)
? 敏感事件之一觸發事件
? 沒有其它組合觸發
例: always@(posedge reset or posedge clear) reg_out=0;⑤ always@(*)
? 無敏感列表,描述組合邏輯,和assign語句是有區別的
例: always@(*) b= 1'b0;assign賦值語句和always@(*)語句。兩者之間的差別有:
1.被assign賦值的信號定義為wire型,被always@(*)結構塊下的信號定義為reg型,值得注意的是,這里的reg并不是一個真正的觸發器,只有敏感列表為上升沿觸發的寫法才會綜合為觸發器,在仿真時才具有觸發器的特性。
2.另外一個區別則是更細微的差別:舉個例子,
wire a; reg b; ? assign a = 1'b0; ? always@(*) b= 1'b0;在這種情況下,做仿真時a將會正常為0,但是b卻是不定態。這是為什么?verilog規定,always@()中的是指該always塊內的所有輸入信號的變化為敏感列表,也就是仿真時只有當always@()塊內的輸入信號產生變化,該塊內描述的信號才會產生變化,而像always@() b = 1'b0,這種寫法由于1'b0一直沒有變化,所以b的信號狀態一直沒有改變,由于b是組合邏輯輸出,所以復位時沒有明確的值(不定態),而又因為always@(*)塊內沒有敏感信號變化,因此b的信號狀態一直保持為不定態。事實上該語句的綜合結果有可能跟assign一樣但是在功能仿真時就差之千里了。
5.編譯預處理語句
Verilog HDL語言和C語言一樣也提供了編譯預處理的功能。“編譯預處理”是Verilog HDL編譯系統的一個組成部分。
Verilog HDL語言允許在程序中使用幾種特殊的命令(它們不是一般的語句)。Verilog HDL編譯系統通常先對這些特殊的命令進行“預處理”,然后將預處理的結果和源程序一起在進行通常的編譯處理。
在Verilog HDL語言中,為了和一般的語句相區別,這些預處理命令以符號“ `”開頭(注意這個符號是不同于單引號“ '”的)。這些預處理命令的有效作用范圍為定義命令之后到本文件結束或到其它命令定義替代該命令之處。Verilog HDL提供了以下預編譯命令:
accelerate,autoexpand_vectornets,celldefine,default_nettype,define,else,endcelldefine,endif,endprotect,endprotected,expand_vectornets,ifdef,include,noaccelerate,noexpand_vectornets,noremove_gatenames,noremove_netnames,nounconnected_drive,protect,protecte,remove_gatenames,remove_netnames,reset,timescale,`unconnected_drive
在這一小節里只對常用的define、include、`timescale進行介紹,其余的請查閱參考書。
1.1 宏定義 `define
用一個指定的標識符(即名字)來代表一個字符串,它的一般形式為:
`define 標識符(宏名) 字符串(宏內容)如:
`define signal string它的作用是指定用標識符signal來代替string這個字符串,在編譯預處理時,把程序中在該命令以后所有的signal都替換成string。
這種方法使用戶能以一個簡單的名字代替一個長的字符串,也可以用一個有含義的名字來代替沒有含義的數字和符號,因此把這個標識符(名字)稱為“宏名”,在編譯預處理時將宏名替換成字符串的過程稱為“宏展開”。`define是宏定義命令。
[例1]:
`define WORDSIZE 8 module reg[1:`WORDSIZE] data; //這相當于定義 reg[1:8] data;關于宏定義的八點說明:
宏名可以用大寫字母表示,也可以用小寫字母表示。建議使用大寫字母,以與變量名相區別。
define命令可以出現在模塊定義里面,也可以出現在模塊定義外面。宏名的有效范圍為定義命令之后到原文件結束。通常,define命令寫在模塊定義的外面,作為程序的一部分,在此程序內有效。
3. *在引用已定義的宏名時,必須在宏名的前面加上符號“`”,表示該名字是一個經過宏定義的名字。**
使用宏名代替一個字符串,可以減少程序中重復書寫某些字符串的工作量。而且記住一個宏名要比記住一個無規律的字符串容易,這樣在讀程序時能立即知道它的含義,當需要改變某一個變量時,可以只改變 define命令行,一改全改。如例1中,先定義WORDSIZE代表常量8,這時寄存器data是一個8位的寄存器。如果需要改變寄存器的大小,只需把該命令行改為:define WORDSIZE 16。這樣寄存器data則變為一個16位的寄存器。由此可見使用宏定義,可以提高程序的可移植性和可讀性。
宏定義是用宏名代替一個字符串,也就是作簡單的置換,不作語法檢查。預處理時照樣代入,不管含義是否正確。只有在編譯已被宏展開后的源程序時才報錯。
宏定義不是Verilog HDL語句,不必在行末加分號。如果加了分號會連分號一起進行置換。如:
[例2]:
module test; reg a, b, c, d, e, out; `define expression a+b+c+d; assign out = `expression + e;...endmodule經過宏展開以后,該語句為:
assign out = a+b+c+d;+e;顯然出現語法錯誤。
\7) 在進行宏定義時,可以引用已定義的宏名,可以層層置換。如:
[例3]:
module test; reg a, b, c; wire out; `define aa a + b `define cc c + `aa assign out = `cc; endmodule這樣經過宏展開以后,assign語句為
assign out = c + a + b;宏名和宏內容必須在同一行中進行聲明。如果在宏內容中包含有注釋行,注釋行不會作為被置換的內容。如:
[例4]:
module`define typ_nand nand #5 //define a nand with typical delay`typ_nand g121(q21,n10,n11);………endmodule經過宏展開以后,該語句為:
nand #5 g121(q21,n10,n11);宏內容可以是空格,在這種情況下,宏內容被定義為空的。當引用這個宏名時,不會有內容被置換。
注意:組成宏內容的字符串不能夠被以下的語句記號分隔開的。
-
· 注釋行
-
· 數字
-
· 字符串
-
· 確認符
-
· 關鍵詞
-
· 雙目和三目字符運算符
如下面的宏定義聲明和引用是非法的。
`define first_half "start of string $display(`first_half end of string");注意在使用宏定義時要注意以下情況:
對于某些 EDA軟件,在編寫源程序時,如使用和預處理命令名相同的宏名會發生沖突,因此建議不要使用和預處理命令名相同的宏名。
宏名可以是普通的標識符(變量名)。例如signal_name 和 'signal_name的意義是不同的。但是這樣容易引起混淆,建議不要這樣使用。
1.2 “文件包含”處理`include
所謂“文件包含”處理是一個源文件可以將另外一個源文件的全部內容包含進來,即將另外的文件包含到本文件之中。Verilog HDL語言提供了`include命令用來實現“文件包含”的操作。其一般形式為:
`include “文件名”上圖表示“文件包含”的含意。圖(a)為文件File1.v,它有一個include "File2.v"命令,然后還有其它的內容(以A表示)。圖(b)為另一個文件File2.v,文件的內容以B表示。在編譯預處理時,要對include命令進行“文件包含”預處理:將File2.v的全部內容復制插入到 `include "File2.v"命令出現的地方,即File2.v 被包含到File1.v中,得到圖(c)所示的結果。
在接著往下進行的編譯中,將“包含”以后的File1.v作為一個源文件單位進行編譯。
“文件包含”命令是很有用的,它可以節省程序設計人員的重復勞動。可以將一些常用的宏定義命令或任務(task)組成一個文件,然后用include命令將這些宏定義包含到自己所寫的源文件中,相當于工業上的標準元件拿來使用。另外在編寫Verilog HDL源文件時,一個源文件可能經常要用到另外幾個源文件中的模塊,遇到這種情況即可用include命令將所需模塊的源文件包含進來。
[例1]:
(1)文件aaa.v
module aaa(a,b,out); input a, b; output out; wire out; assign out = a^b; endmodule(2)文件 bbb.v
`include "aaa.v" module bbb(c,d,e,out); input c,d,e; output out; wire out_a; wire out;aaa aaa(.a(c),.b(d),.out(out_a)); assign out=e&out_a; endmodule在上面的例子中,文件bbb.v用到了文件aaa.v中的模塊aaa的實例器件,通過“文件包含”處理來調用。模塊aaa實際上是作為模塊bbb的子模塊來被調用的。在經過編譯預處理后,文件bbb.v實際相當于下面的程序文件bbb.v:
module aaa(a,b,out);input a, b;output out;wire out;assign out = a ^ b; endmodulemodule bbb( c, d, e, out);input c, d, e;output out;wire out_a;wire out;aaa aaa(.a(c),.b(d),.out(out_a));assign out= e & out_a; endmodule關于“文件包含”處理的四點說明:
一個include命令只能指定一個被包含的文件,如果要包含n個文件,要用n個include命令。
`include命令可以出現在Verilog HDL源程序的任何地方,被包含文件名可以是相對路徑名,也可以是絕對路徑名。例如:'include"parts/count.v"
可以將多個include命令寫在一行,在include命令行,只可以出空格和注釋行。例如下面的寫法是合法的。
如果文件1包含文件2,而文件2要用到文件3的內容,則可以在文件1用兩個`include命令分別包含文件2和文件3,而且文件3應出現在文件2之前。例如在下面的例子中,即在file1.v中定義:
file2.v的內容為:
`define size2 `size1+1 . . .file3.v的內容為:
`define size1 4 . . .這樣,file1.v和file2.v都可以用到file3.v的內容。在file2.v中不必再用 `include "file3.v"了。
在一個被包含文件中又可以包含另一個被包含文件,即文件包含是可以嵌套的。例如上面的問題也可以這樣處理,見下圖,
它的作用和下圖的作用是相同的。
1.3時間尺度 `timescale
timescale命令用來說明跟在該命令后的模塊的時間單位和時間精度。使用`timescale命令可以在同一個設計里包含采用了不同的時間單位的模塊。
例如,一個設計中包含了兩個模塊,其中一個模塊的時間延遲單位為ns,另一個模塊的時間延遲單位為ps。EDA工具仍然可以對這個設計進行仿真測試。
`timescale 命令的格式如下:
`timescale<時間單位>/<時間精度>在這條命令中,時間單位參量是用來定義模塊中仿真時間和延遲時間的基準單位的。時間精度參量是用來聲明該模塊的仿真時間的精確程度的,該參量被用來對延遲時間值進行取整操作(仿真前),因此該參量又可以被稱為取整精度。
如果在同一個程序設計里,存在多個`timescale命令,則用最小的時間精度值來決定仿真的時間單位。另外時間精度至少要和時間單位一樣精確,時間精度值不能大于時間單位值。
在`timescale命令中,用于說明時間單位和時間精度參量值的數字必須是整數,其有效數字為1、10、100,單位為秒(s)、毫秒(ms)、微秒(us)、納秒(ns)、皮秒(ps)、毫皮秒(fs)。這幾種單位的意義說明見下表。
下面舉例說明`timescale命令的用法。
[例1]:
`timescale 1ns/1ps在這個命令之后,模塊中所有的時間值都表示是1ns的整數倍。這是因為在timescale命令中,定義了時間單位是1ns。模塊中的延遲時間可表達為帶三位小數的實型數,因為timescale命令定義時間精度為1ps.
[例2]:
`timescale 10us/100ns在這個例子中,timescale命令定義后,模塊中時間值均為10us的整數倍。因為timesacle 命令定義的時間單位是10us。延遲時間的最小分辨度為十分之一微秒(100ns),即延遲時間可表達為帶一位小數的實型數。
例3:
`timescale 10ns/1ns module test; reg set; parameter d=1.55; initial begin #d set=0; #d set=1; end endmodule在這個例子中,`timescale命令定義了模塊test的時間單位為10ns、時間精度為1ns。因此在模塊test中,所有的時間值應為10ns的整數倍,且以1ns為時間精度。這樣經過取整操作,存在參數d中的延遲時間實際是16ns(即1.6×10ns),這意味著在仿真時刻為16ns時寄存器set被賦值0,在仿真時刻為32ns時寄存器set被賦值1。仿真時刻值是按照以下的步驟來計算的。
根據時間精度,參數d值被從1.55取整為1.6。
因為時間單位是10ns,時間精度是1ns,所以延遲時間#d作為時間單位的整數倍為16ns。
EDA工具預定在仿真時刻為16ns的時候給寄存器set賦值0(即語句 #d set=0;執行時刻),在仿真時刻為32ns的時候給寄存器set賦值1(即語句 #d set=1;執行時刻),
注意:如果在同一個設計里,多個模塊中用到的時間單位不同,需要用到以下的時間結構。
用`timescale命令來聲明本模塊中所用到的時間單位和時間精度。
用系統任務$printtimescale來輸出顯示一個模塊的時間單位和時間精度。
用系統函數$time和$realtime及%t格式聲明來輸出顯示EDA工具記錄的時間信息。
補充:在verilog中#的用法
#是延遲的意思,井號后面數字是延遲的數量,延遲的單位由`timescale控制
比如有:`timescale 1ns/1ps 意思就是時間單位為1ns,精度是1ps ? 那么,#10.5 就是延遲10.5ns的意思 ? 在同步時序數字邏輯電路的verilog代碼中,不能加入“#”進行延遲,這不是代碼編寫階段能決定的
1.4 條件編譯命令ifdef、else、`endif
一般情況下,Verilog HDL源程序中所有的行都將參加編譯。但是有時希望對其中的一部分內容只有在滿足條件才進行編譯,也就是對一部分內容指定編譯的條件,這就是“條件編譯”。有時,希望當滿足條件時對一組語句進行編譯,而當條件不滿足是則編譯另一部分。
條件編譯命令有以下幾種形式:
1)
`ifdef 宏名 (標識符) 程序段1 `else 程序段2 `endif它的作用是當宏名已經被定義過(用define命令定義),則對程序段1進行編譯,程序段2將被忽略;否則編譯程序段2,程序段1被忽略。其中else部分可以沒有,即:
2)
`ifdef 宏名 (標識符) 程序段1 `endif這里的 “宏名” 是一個Verilog HDL的標識符,“程序段”可以是Verilog HDL語句組,也可以是命令行。這些命令可以出現在源程序的任何地方。
注意:被忽略掉不進行編譯的程序段部分也要符合Verilog HDL程序的語法規則。
通常在Verilog HDL程序中用到ifdef、else、`endif編譯命令的情況有以下幾種:
-
· 選擇一個模塊的不同代表部分。
-
· 選擇不同的時序或結構信息。
-
· 對不同的EDA工具,選擇不同的激勵。
六. Verilog測試(仿真)文件TestBench如何編寫
一、哪些步驟需要進行仿真
下圖是FPGA開發的整個流程,先看一下仿真都出現在哪里。 流程圖中綠色的步驟是要進行測試仿真的,即有三個步驟是要進行仿真操作的(有時會省略“綜合后仿真”這一步)
1.RTL仿真 ? 也稱為綜合前仿真、前仿真和功能仿真。 這一步只驗證在頂層模塊和功能子模塊的設計輸入完成后,其電路的邏輯功能是否符合設計要求,不考慮門延時和線延時。
2.綜合后仿真 ? 綜合后仿真加入了門延時。
3.時序仿真 ? 也稱為后仿真,在門延時的基礎上又加入了線延時。
二、如何編寫仿真測試文件
下面以功能仿真為例子,說明測試仿真文件如何編寫。
1.首先準備好需要被測模塊的Verilog代碼
module led_twinkle( input sys_clk , ?//系統時鐘input sys_rst_n, //系統復位,低電平有效 ?output [1:0] led //LED 燈 ); ?...... ? ? ? ? ?//省略功能部分endmodule ?2.編寫TestBench測試仿真文件
在Vivado軟件中,在左側的 Flow Navigator 窗口點擊 Add Source, 選擇 Add or create simulation sources,點擊 Next,點擊 Create File, 給測試文件命名,通常格式為 “tb被測試模塊名”,點擊OK,點擊Finish。
*在 Source 欄中的 Simulation Sources 中雙擊生成的 “tb_被測試模塊名” 文件,進行編寫。* ~timescale 1ns / 1ps ?//測試時間基本單位為1ns,精度為1psmodule tb_led_twinkle(); //通常起名格式為 tb_被測試模塊名 ? //輸入reg sys_clk; reg sys_rst_n; //輸出wire [1:0] led; ? //信號初始化,必須有這一步,容易被忽略 initial beginsys_clk = 1'b0; sys_rst_n = 1'b0;#200 ?//延時200nssys_rst_n = 1'b1; end ? //生成時鐘,模擬晶振實際的周期時序 always #10 sys_clk = ~sys_clk; //每10ns,sys_clk進行翻轉,達到模擬晶振周期為20ns//例化待測模塊(模塊例化可點擊文章最后附上的網址) led_twinkle u_led_twinkle( ? .sys_clk (sys_clk), ? ? ?//注意語句后面為逗號.sys_rst_n (sys_rst_n),.led (led) ? ? ? ? ? ? ?//最后一步無逗號 ); ? endmodule ?總結
以上是生活随笔為你收集整理的Verilog HDL 基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多维度了解一对多远程培训系统
- 下一篇: SQL学习笔记(01)_LIKE、IN、