Verilog是一種用於描述、設計電子系統(特別是數碼電路)的硬件描述語言,主要用於在集成電路設計,特別是超大型積體電路的電腦輔助設計。Verilog是電機電子工程師學會(IEEE)的1364號標準。[2]
Verilog能夠在多種抽象級別對數碼邏輯系統進行描述:既可以在電晶體級、邏輯門級進行描述,也可以在暫存器傳輸級對電路訊號在暫存器之間的傳輸情況進行描述。[3]:18除了對電路的邏輯功能進行描述,Verilog代碼還能夠被用於邏輯仿真、邏輯綜合,[4]:3-7其中後者可以把暫存器傳輸級的Verilog代碼轉換為邏輯門級的網表,從而方便在現場可程式化邏輯閘陣列上實現硬件電路[5],或者讓硬件廠商製造具體的特殊應用積體電路。設計人員還可以利用Verilog的擴充部分Verilog-AMS進行模擬電路和混合訊號積體電路的設計。[6][7]
發展歷史
1983年末,Gateway設計自動化公司的工程師創立了Verilogs。當時Gateway設計自動化公司還叫做自動整合設計系統(Automated Integrated Design Systems),1985年公司將名字改為Gateway設計自動化。該公司的菲爾·莫比(Phil Moorby)完成了Verilog的主要設計工作。1990年,Gateway設計自動化被Cadence公司收購。[8]
1990年代初,開放Verilog國際(Open Verilog International, OVI)組織(即現在的Accellera)成立,Verilog面向公有領域開放。[9]1992年,該組織尋求將Verilog納入電機電子工程師學會標準 。最終,Verilog成為了電機電子工程師學會1364-1995標準,即通常所說的Verilog-95。[10]
設計人員在使用這個版本的Verilog的過程中發現了一些可改進之處。為了解決用戶在使用此版本Verilog過程中反映的問題,Verilog進行了修正和擴充,這部分內容後來再次被提交給電機電子工程師學會。這個擴充後的版本後來成為了電機電子工程師學會1364-2001標準,即通常所說的Verilog-2001。Verilog-2001是對Verilog-95的一個重大改進版本,它具備一些新的實用功能,例如敏感列表、多維陣列、生成陳述式塊、命名埠連接等。[11]目前,Verilog-2001是Verilog的最主流版本,被大多數商業電子設計自動化軟件套件支援。
2005年,Verilog再次進行了更新,即電機電子工程師學會1364-2005標準。該版本只是對上一版本的細微修正。這個版本還包括了一個相對獨立的新部分,即Verilog-AMS。[12]這個擴充使得傳統的Verilog可以對整合的模擬和混訊系統進行建模。[13]:255容易與電機電子工程師學會1364-2005標準混淆的是加強硬件驗證語言特性的SystemVerilog(電機電子工程師學會1800-2005標準),它是Verilog-2005的一個超集,它是硬件描述語言、硬件驗證語言(針對驗證的需求,特別加強了物件導向特性)的一個整合。[14]
2009年,IEEE 1364-2005和IEEE 1800-2005兩個部分合併為IEEE 1800-2009,成為了一個新的、統一的SystemVerilog硬件描述驗證語言(hardware description and verification language, HDVL)。[15][16]
以模組為基礎的設計
描述複雜的硬件電路,設計人員總是將複雜的功能劃分為簡單的功能,模組是提供每個簡單功能的基本結構。設計人員可以採取「自頂向下」的思路,將複雜的功能模組劃分為低層次的模組。[17]:58這一步通常是由系統級的總設計師完成,而低層次的模組則由下一級的設計人員完成。自頂向下的設計方式有利於系統級別層次劃分和管理,並提高了效率、降低了成本。[18]「由下而上」方式是「自頂向下」方式的逆過程。
使用Verilog描述硬件的基本設計單元是模組(module)。[19]構建複雜的電子電路,主要是通過模組的相互連接呼叫來實現的。模組被包含在關鍵字module
、endmodule
之內。[17]:59實際的電路元件。Verilog中的模組類似C語言中的函數,它能夠提供輸入、輸出埠,可以實例呼叫其他模組,也可以被其他模組實例呼叫。模組中可以包括組合邏輯部分、過程時序部分。[19]例如,四選一的多路選擇器,就可以用模組進行描述。它具有兩個位選輸入訊號、四個數據輸入,一個輸出端,在Verilog中可以表示為:
module mux (out, select, in0, in1, in2, in3);
output out;
input [1:0] select;
input in0, in1, in2, in3;
//具體的寄存器传输级代碼
endmodule
設計人員可以使用一個頂層模組,通過實例呼叫上面這個模組的方式來進行測試。這個頂層模組常被稱為「測試平台(Testbench)」。 [20]為了最大程度地對電路的邏輯進行功能驗證,測試代碼需要儘可能多地覆蓋系統所涉及的陳述式、分支、條件、路徑、觸發、狀態機狀態,[21]:141-144驗證人員需要在測試平台里建立足夠多的輸入激勵,[22]:8並連接到被測模組的輸入端,然後檢測其輸出端的表現是否符合預期(諸如SystemVerilog的硬件驗證語言能夠提供針對驗證專門最佳化的數據結構,以隨機測試的方式進行驗證,這對於高度複雜的集成電路設計驗證可以起到關鍵作用)。實例呼叫模組時,需要將埠的連接情況按照這個模組聲明時的順序排列。這個頂層模組由於不需要再被外界呼叫,因此沒有輸入輸出埠:[4]:4
module tester;
reg [1:0] SELECT;
reg IN0, IN1, IN2, IN3;
wire OUT;
mux my_mux (OUT, SELECT, IN0, IN1, IN2, IN3); //实例调用mux模块,这个实例被命名为my_mux
initial //需要仿真的激励代码
begin
end
endmodule
在這個測試平台模組里,設計人員可以設定仿真時的輸入訊號以及訊號監視程式,然後觀察仿真時的輸出情況是否符合要求,這樣就可以了解設計是否達到了預期。[20]
範例中的對模組進行實例參照時,按照原模組聲明時的順序羅列了輸入變數。除此之外,還可以使用或者採用命名埠連接的方式。使用這種方式,埠的排列順序可以與原模組聲明時不同,甚至可以不連接某些埠:
mux my_mux (.out(OUT), .select(SELECT), .in0(IN0), .in1(IN1), .in2(IN2), .in3(IN3));
//使用命名端口连接,括号外面是模块声明时的端口,括号内是实际的端口连接
//括号外相当于C语言的形式参数,括号内相当于实际参数
endmodule
上面所述的情況是,測試平台頂層模組的測試變數直接連接了所設計的功能模組。測試平台還可以是另一種形式,即測試平台並不直接連接所設計的功能模組,而是在這個測試平台之下,將激勵模組和功能模組以相同的抽象級別,通過線網相互連接。[23]這兩種形式的測試平台都可以完成對功能模組的測試。大型的電路系統,正是由各個層次不同模組之間的連接、呼叫,來實現複雜的功能的。[4]:11
語言要素
Verilog的設計初衷是成為一種基本語法與C語言相近的硬件描述語言。[3]:18這是因為在Verilog設計之初,C語言已經在許多領域得到廣泛應用,C語言的許多語言要素已經被許多人習慣。一種與C語言相似的硬件描述語言,可以讓電路設計人員更容易學習和接受。不過,Verilog與C語言還是存在許多差別。另外,作為一種與普通電腦程式語言不同的硬件描述語言,它還具有一些獨特的語言要素,例如向量形式的線網和暫存器、過程中的非阻塞賦值等。總的來說,具備C語言能力的設計人員將能夠很快掌握Verilog硬件描述語言。[24]
空白符是指代碼中的空格(對應的跳脫識別碼為\b
)、制表符(\t
)和換行(\n
)。[17]:17如果這些空白符出現在字串里,那麼它們不可忽略。除此之外,代碼中的其他空白符在編譯的時候都將會被視為分隔識別碼,即使用2個空格或者1個空格並無影響。[25]不過,在代碼中使用合適的空格,可以讓上下行代碼的外觀一致(例如使設定運算子位於同一個豎直列),從而提高代碼的可讀性。
為了方便代碼的修改或其他人的閱讀,設計人員通常會在代碼中加入註釋。與C語言一樣,有兩種方式書寫註釋。第一種為多行註釋,即註釋從/*
開始,直到*/
才結束;另一種為單行註釋,註釋從//
開始,從這裏到這一行末尾的內容會被系統辨識為註釋。[26]
某些電子設計自動化工具,會辨識出代碼中以特殊格式書寫、含有某些預先約定關鍵詞的註釋,並從這些註釋所提取有用的資訊。這些註釋不是供人閱讀,而是向第三方工具提供有關設計專案的額外資訊。例如,某些邏輯綜合工具可以從註釋中讀取綜合的約束資訊。[27]:24
Verilog代碼中用來定義語言結構名稱的字元稱為識別碼,包括變數名、埠名、模組名等等。識別碼可以由字母、數碼、底線以及美元符($
)來表示。但是識別碼的第一個字元只能是字母、數碼或者底線,不能為美元符,這是因為以美元符開始的識別碼和系統任務的保留字衝突。[29]:19
和其他許多程式語言類似,Verilog也有許多保留字(或稱為關鍵字),用戶定義的識別碼不能夠和保留字相同。Verilog的保留字均為小寫。[29]:19變數類型中的wire
、reg
、integer
等、表示過程的initial
、always
等,以及所有其他的系統任務、編譯指令,都是關鍵字。[30]可以查閱官方文獻以完整的關鍵字的列表。
跳脫識別碼(又稱跳脫字元),是由\
開始,以空白符結束的一種特殊程式語言結構。[30]這種結構可以用來表示那些容易與系統語言結構相同的內容(例如"
在系統中被用來表示字串,如果字串本身的內容包含一個與之形式相同的雙引號,那麼就必須使用跳脫識別碼)。下面列出了常用的幾種跳脫識別碼。除此之外,在反斜線之後也可以加上字元的ASCII,這種跳脫識別碼相當於一個字元。常用的跳脫識別碼有\n
(換行)、\t
(制表位)、\b
(空格)、\\
(反斜槓)和\"
(英文的雙引號)等。
|
|
上面列出了Verilog採用的具有八種訊號強度的四值邏輯(four-valued logic),數碼電路中的訊號可以用邏輯值、訊號強度加以描述。當系統遇到訊號之間的競爭時,需要考慮各組訊號的狀態和強度。如果驅動統一線網的訊號強度不同,則輸出結果是訊號強度高的值;如果兩個強度相同的訊號之間連接到同一個線網,將會發生競爭,結果為不確定值x
。[29]:20
Verilog所用到的所有變數都屬於兩個基本的類型:線網類型和暫存器類型。[4]:30
線網與我們實際使用的電線類似,它的數值一般只能通過連續賦值(continuous assignment),由賦值符右側連接的驅動源決定。[31]線網在初始化之前的值為x(trireg
類型的線網是一個例外,它相當於能夠儲存電荷的電容器[23]:105)。如果未連接驅動源,則該線網變數的當前數值為z
,即高阻態。線網類型的變數有以下幾種:wire
、tri
、wor
、trior
、wand
、triand
、tri0
、tri1
、supply0
、supply1
、trireg
,其中wire
作為一般的電路連線使用最為普遍,[32]而其他幾種用於構建匯流排,即多個驅動源連接到一條線網的情況,[4]:79-80或搭建電源、接地等。當進行模組的埠聲明時,如果沒有明確指出其類型,那麼這個埠會被隱含地聲明為wire
類型。因此,在聲明輸出埠時應該注意是否有必要加上reg
關鍵字。以下面的代碼片段為例:
module my_moule (out1, out2, in1, in2); //该模块具有两个输出端口
output reg out1; //out1端口被声明为为reg类型,它可以保存当前值
output out2; //out2端口隐含地被声明为为wire类型,它的数值必须依赖连续赋值语句维持
endmodule
暫存器與之不同,它可以儲存當前的數值,直到另一個數值被賦值給它。在保持當前數值的過程中,不需要驅動源對它進行作用。[31]如果未對暫存器變數賦值,它的初始值則為x
。Verilog中所說的暫存器類型變數與真實的硬件暫存器是不同的,它是指一個儲存數值的變數。如果要在一個過程(initial
過程或always
過程)里對變數賦值,這個變數必須是暫存器類型的。暫存器類型的變數有以下幾種:reg
(普通暫存器)、integer
(整數)、time
(時間)、real
(實數),其中reg
作為一般的暫存器使用最為普遍。[33]利用暫存器變數的陣列,還可以對ROM進行建模。[31]
關於選擇線網類型還是暫存器類型,需要符合一定的規定。模組的輸入埠可以與外界的線網或暫存器類型的變數連接,但是這個模組輸出埠只能連接到外界的線網。再簡單點,就是在兩個模組的訊號連接點,提供訊號的一方可以是暫存器或者線網,但是接受訊號的一方只能是線網。[29]:35-36此外,在initial
、always
過程代碼塊中賦值的變數必須是暫存器類型的,[29]:58[4]:30而連續賦值的對象只能是線網類型的變數。[29]:81[4]:81
在Verilog里,當一個變數的類型確定,即已經知道它是暫存器類型或者是線網類型,當把具體的數值賦值給它時,需要利用下面所述的數碼表示方法。數碼表示的基本語法結構為<位宽>'<数制的符号><数值>
。[4]:31其中,位寬是與數據大小相等的對應二進制數的位數加上佔位所用0的位數,這個位數需要使用十進制來表示。位寬是可選項,如果沒有指明位寬,則預設的數據位寬與仿真器有關(最小32位元);數制需要用字母來表示,h
對應十六進制,d
對應十進制,o
對應八進制,b
對應二進制。如果沒有指明數制,則預設數據為十進制數。[29]:18例如:
- 233:十進制數233(未指明位寬)
- 12'h123:十六進制數123(使用12位元)
- 20'd44:十進制數44(使用20位,高位自動使用0填充)
- 4'b1010:二進制數1010(使用4位元)
- 6'o77:八進制數77(使用6位)
- -233:十進制數-233(未指明位寬)
- -32'd3: 十進制數-3 (使用32位元)
- 32'hfffffffd: 十六進制數fffffffd (使用32位元)
如果某個數的最高位為x
或z
,那麼系統會自動使用x
或z
來填充沒有佔據的更高位。如果最高位為其他情況,系統會自動使用0來填充沒有佔據的更高位。
向量形式的數據是Verilog相對C語言較為特殊的一種數據,但是這種數據在硬件描述語言中十分重要。在Verilog中,純量的意思是只具有一個位元的變數,而向量表示具有多個位元的變數。如果沒有特別指明位寬,系統預設它為純量。[29]:21
在真實的數碼電路,例如將兩個四位二進制數相加的進位加法器中,我們可以發現,其中一個數是通過四條電線(每條線表示四位中的某一位)連接到加法器上的。我們可以用一個向量來表示這個多位數,分別用這個向量的各個分量來表示「四條電線」,即四位中的某一位。這樣做的好處是,可以方便地在Verilog代碼的其他地方選擇其中的一位(位選)或多位(域選)。[29]:22當然,如果沒有進行位選或域選,則這個多位數整體被選擇。
向量的表示需要使用方括號,方括號里的第一個數碼為向量第一個分量的序號,第二個數碼為向量最後一個分量的序號,中間用冒號隔開。向量分量的序號不像C語言的陣列一樣必須從0開始,不過為了和數碼電路里二進制數高低位的表示方法一致,我們常常讓最低位為0(即對於四位二進制數,其最高位為第3位,次高位為第2位,次低位為第1位,最低位為第0位),當然這只是一種習慣。例如,上面提到的四位二進制數用向量表示為:
wire [3:0] input_add; //声明名为input_add的4位wire型向量
wire [4:1] input_add1; //也是4位wire型向量,但是分量序号从4到1
wire [0:3] input_add2; //也是4位wire型向量,但是分量序号从0到3
上面的向量聲明之後,我們就可以方便地選擇其中的某幾個分量進行操作。請注意用於域選的方括號的位置在向量名稱之後,方括號內的數碼為所需的位數。例如我們可以進行以下操作:
input_add [3] = 1'b1; //将1赋值给input_add向量的第三位(最高位)
input_add [1:0] = 2'b01; //将0和1分别赋值给input_add向量的第1、0位(最低两位)
當對向量進行賦值時,如果右邊的數值位寬大於左邊的變數,則多出來的位被丟棄;如果右邊的數值位寬小於左邊的變數,則不夠的位用0填補。
Verilog中的幾種暫存器類型的數據,[4]:32包括reg
、integer
、time
、real
,以及由這幾種數據構成的向量,都可以構成陣列。聲明陣列時,方括號位於陣列名的後面,括號內的第一個數碼為第一個元素的序號,第二個數碼為最後一個元素的序號,中間用冒號隔開。如果陣列是由向量構成的,則陣列的其中某個元素是向量。同樣,出於習慣考慮,我們一般讓陣列第一個元素的序號為0,後面元素的序號依次遞增。此外,和C語言類似,用戶可以聲明多維陣列。例如:
integer number [0:100]; //声明一个有101个元素的整数数组
number [25] = 1234; //将1234赋值给25号(第26个)元素
reg [7:0] my_input [65535:0]; //声明一个有65536个元素的8位向量寄存器
my_input [97] = 8'b10110101; //将10110101分别赋值给97号(第2个)元素的7至0位
reg my_reg [0:3][0:4]; //声明一个具有20个元素的二维寄存器数组
my_reg [1][2] = 1'b1; //将1赋值给上述二维数组的第2行、第3列元素
由於陣列和向量的表示都使用了方括號,因此使用時需要注意這個變數或向量的名稱在最初被聲明為何種類型的數據。上面第三行的例子是65536個8位元向量組成的向量陣列,它可以描述一個64KB的記憶體。
表示陣列某個元素時,允許使用變數來表示元素的索引(如number [i] = 1234;
),但是表示一個向量的一位或者幾位時,只允許使用數碼來表示位的索引;此外,使用陣列時一次只能對一個元素進行操作,而不能向向量那樣同時對連續的幾個位進行操作,例如my_input [65535][7:4] = 4'b1010;
將一個四位二進制數賦值給第65536個元素的高四位。[27]:86
可以通過parameter
關鍵字聲明參數。參數與常數的意義類似,不能夠通過賦值運算改變它的數值。在模組進行實例化時,可以能夠通過defparam
,即參數多載陳述式塊來改變模組實例的參數。另一種方法是在模組實例化時,使用#()
將所需的實例參數覆蓋模組的預設參數。局部參數可以用localparam
關鍵字聲明,它不能夠進行參數多載。[29]:25
在設計中使用參數,可以使得模組代碼在不同條件下被重複利用,例如四位數全加器和十六位數全加器可以通過參數實例化同一個通用全加器模組。[34]:29
Verilog中的字串總體來說與C語言中的字串較為類似,其中每個字元以ASCII表示,佔8位。[35]字串儲存在位寬足夠的向量暫存器中。字串中的空格、換行等特殊內容,以跳脫識別碼(參見前面提到過的跳脫識別碼)的形式表示。
為了使設計人員方便地使用暫存器傳輸級描述,Verilog提供了多種流程控制結構,包括if
、if...else
、if...else if...else
等形式的條件結構,case
分支結構,for
、while
迴圈結構。這些流程控制結構與C語言有着相似的用法。不同的迴圈結構可能造成不同的邏輯綜合結果。[36]Verilog也提供了一些C語言中沒有的流程控制結構以適應硬件描述語言的需要,例如casex
、casez
兩種選擇結構,前者可以條件數值中的x
、z
均作為無關值,後者僅將z
作為無關值;[23]:53[4]:87-88此外還提供了forever
、repeat
兩種迴圈結構,分別用於無限迴圈和指定次數迴圈。[3]:56-65[17]:109-126數碼電路的邏輯功能描述常常使用到這些流程控制結構,例如,case
結構可以清晰地描述一個數據多工器。
Verilog的許多運算子和C語言類似,但是有一部分運算子是特有的,例如拼接運算子、縮減運算子、帶有無關位的相等運算子等。
- 按位元
- 按位元取反(
~
):1個多位元運算數按位元取反。例如:a=4'b1011
,則~a
的結果為4'b0100 - 按位元與(
&
):2個多位元運算數按位元進行與運算,各位的結果按順序組成一個新的多位數。例如:a=2'b10
,b=2'b11
,則a&b
的結果為2'b10 - 按位元或(
|
):2個多位元運算數按位元進行或運算,各位的結果按順序組成一個新的多位數。例如:a=2'b10
,b=2'b11
,則a|b
的結果為2'b11 - 按位元異或(
^
):2個多位元運算數按位元進行異或運算,各位的結果按順序組成一個新的多位數。例如:a=2'b10
,b=2'b11
,則a^b
的結果為2'b01 - 按位元同或(
~^
或^~
):2個多位元運算數按位元進行同或運算,各位的結果按順序組成一個新的多位數。例如:a=2'b10
,b=2'b11
,則a~^b
的結果為2'b10
- 按位元取反(
- 邏輯
- 邏輯取反(
!
):對1個運算元進行邏輯取反,如果這個運算元為0,則結果為1;如果這個運算元不為0,則結果為0 - 邏輯與(
&&
):對2個運算元進行邏輯與,如果二者同不為0,則結果為1,否則為0。例如:3 && 0的結果為0。 - 邏輯或(
||
):對2個運算元進行邏輯或,如果二者其中至少有一個不為0,則結果為1,否則為0。例如:3||0
的結果為1。
- 邏輯取反(
- 縮減
- 縮減與(
&
):對一個多位元運算數進行縮減與操作,先將它最高位與次高位進行與操作,其結果再與第二次高位進行與操作,直到最低位。例如:&(4'b1011)
的結果為0 - 縮減與非(
~&
):對一個多位元運算數進行縮減與非操作,先將它最高位與次高位進行與非操作,其結果再與第二次高位進行與非操作,直到最低位。例如:~&(4'b1011)
的結果為1 - 縮減或(
|
):對一個多位元運算數進行縮減或操作,先將它最高位與次高位進行或操作,其結果再與第二次高位進行或操作,直到最低位。例如:|(4'b1011)
的結果為1 - 縮減或非(
~|
):對一個多位元運算數進行縮減或非操作,先將它最高位與次高位進行或非操作,其結果再與第二次高位進行或非操作,直到最低位。例如:~|(4'b1011)
的結果為0 - 縮減異或(
^
):對一個多位元運算數進行縮減異或操作,先將它最高位與次高位進行異或操作,其結果再與第二次高位進行異或操作,直到最低位。例如:^(4'b1011)
的結果為1 - 縮減同或(
~^or^~
):對一個多位元運算數進行縮減同或操作,先將它最高位與次高位進行同或操作,其結果再與第二次高位進行同或操作,直到最低位。例如:~^(4'b1011)
的結果為0
- 縮減與(
- 算術
- 關係
- 大於(
>
):比較2個運算元,如果前者大於後者,結果為真 - 小於(
<
):比較2個運算元,如果前者小於後者,結果為真 - 大於或等於(
>=
):比較2個運算元,如果前者大於或等於後者,結果為真 - 小於或等於(
<=
):比較2個運算元,如果前者小於或等於後者,結果為真 - 邏輯相等(
==
):2個運算元比較,如果各位均相等,結果為真。如果其中任何一個運算元中含有x或z,則結果為x - 邏輯不等(
!=
):2個運算元比較,如果各位不完全相等,結果為真。如果其中任何一個運算元中含有x或z,則結果為x - case相等(
===
):2個運算元比較,如果各位(包括x和z位)均相等,結果為真 - case不等(
!==
):2個運算元比較,如果各位(包括x和z位)不完全相等,結果為真
- 大於(
- 移位
- 邏輯右移(
>>
):1個運算元向右移位,產生的空位用0填充 - 邏輯左移(
<<
):1個運算元向左移位,產生的空位用0填充 - 算術右移(
>>>
):1個運算元向右移位。如果是無符號數,則產生的空位用0填充;有符號數則用其符號位填充[4]:36 - 算術左移(
<<<
):1個運算元向左移位,產生的空位用0填充
- 邏輯右移(
- 拼接(
{,}
):2個運算元分別作為高低位進行拼,例如:{2'b10,2'b11}
的結果是a'b1011 - 重複(
{n{m}}
):將運算元m重複n次,拼接成一個多位的數。例如:A=2'b01
,則{2{A}}
的結果是4'b0101 - 條件(
?:
):根據?前的表達式是否為真,選擇執行後面位於:左右兩個陳述式。例如:(a>b)?(a=a-1):(b=b-2)
,如果a
大於b
,則將a-1
的值賦給a
,否則將b-2
的值賦給b
系統任務可以被用來執行一些系統設計所需的輸入、輸出、時序檢查、仿真控制操作。[4]:51-53所有的系統任務名稱前都帶有美元符號$
使之與用戶定義的任務和函數相區分。[37]例如,$display
用於顯示指定的字串,然後自動換行(用法類似C語言中的printf
函數);$monitor
用於監視變數,一旦被監視的變數發生變化,會顯示指定的字串;而$time
可以提取當前的仿真時間。完整的列表請查閱參考工具、Verilog手冊或標準文件。[38]
Verilog具有一些編譯指令,它們的基本格式為`<keyword>
,注意第一個符號不是單引號,而是鍵盤上數碼1左邊那個鍵對應的撇號。常用的編譯指令有文字宏預定義`define
、`include
,它們的功能與C語言中類似,分別提供文字替換、檔案包含的功能。Verilog還提供了`ifdef
、`ifndef
等一系列條件編譯指令,設計人員可以使得代碼在滿足一定條件的情況下才進行編譯。此外,`timescale
指令可以對時間單位進行定義。[29]:132詳細的編譯指令清單請參閱相關參考書籍。
暫存器傳輸級描述
在Verilog中,可以聲明兩種不同的過程:always過程和initial過程。過程可以是包含時序的過程描述,而不包含時序的過程還可以表達組合邏輯。[23]:170always過程從關鍵字always
開始,可以連續多次執行,當過程的最後一行代碼執行完成後,再次從第一行代碼開始執行。如果沒有使用系統任務$finish
,always過程將不斷迴圈執行。initial過程從關鍵字initial
開始,它只能執行一次。[39]:140-145
一個模組中可以包含多個過程,各個過程相互之間是並行執行的。不過,過程不能夠巢狀使用。如果過程中有多個陳述式,則需要使用關鍵字begin
、end
或fork
、join
將它們組成一個代碼塊。這兩種關鍵字組合代表着順序代碼塊和並列代碼塊,後面的部分會講述這兩種結構。
例如,利用always過程迴圈執行的特點,可以為模組提供一個時間脈衝(注意第一個initial
過程為時鐘的初始化,這個過程只需要進行一次):
initial
a = 1'b0;
always
#1 a=~a;
end
雖然,always代碼塊和while
陳述式、forever
陳述式都能提供迴圈功能,但是always代碼塊的迴圈更側重過程的迴圈執行,而後二者更側重代碼的迴圈執行。因此,為了使代碼更具條理,過程的迴圈應當用always
陳述式描述。當然,在實際使用過程中,強制使用其中的某一種在功能實現上都是可行的。
在Verilog中,有兩種賦值運算,一種叫做阻塞賦值(blocking assignment),其運算子為=
;另一種叫做非阻塞賦值(non-blocking assignment),其運算子為<=
。在順序代碼塊中使用阻塞賦值陳述式,如果這一句沒有執行完成,那麼後面的陳述式不會執行;如果在順序代碼塊中使用非阻塞賦值,則執行這一句的同時,並不會阻礙下一句代碼的執行。[4]:47而且,如果後一個陳述式涉及前面一個非阻塞賦值陳述式中的變數,由於這兩個陳述式「同時」執行,因此後一個陳述式所用到的是前面一個陳述式執行前變數的數值。非阻塞賦值是Verilog作為硬件描述語言與普通程式語言的一個重大區別。
帶有兩個正反器輸出端的簡單範例如下
always @ (posedge reset or posedge clock)
begin
a <= b;
b <= a;
end
endmodule
上面的例子如果沒有使用非阻塞賦值,而使用阻塞賦值,那麼flop1
和flop2
的數值就不能被交換。flop1
和flop2
在執行完畢後的數值都與之前flop2
的數值相同。在傳統的程式語言中,可能需要一個臨時的變數,或者使用指標,才能夠達到交換兩個變數的目的。這裏使用了非阻塞賦值,相當於引入了一個隱含的臨時變數。第二個非阻塞賦值右邊的a
是第一句賦值之前的數值,變數交換的目的得以實現。[29]:84訊號邊緣敏感的過程陳述式塊內常使用非阻塞賦值,使陳述式塊的諸賦值陳述式同時進行,雖然功能上似乎可以用阻塞賦值實現,但是仿真時會產生不正常的結果。[23]:180
通常的過程賦值陳述式往往只有在觸發或迴圈等情況,即賦值陳述式被執行到時候,才會使左邊的暫存器變數改變一次;而線網變數的連續賦值則一直「監視」右邊表達式的變化,一旦其結果發生變化,立即會左邊的線網變數更新為此結果。[23]如果需要對暫存器變數進行過程連續賦值,則可以使用Verilog提供的assign
或force
關鍵字「強制地」將設定運算子右邊表達式的結果連續不斷地施加在左邊的暫存器變數上。
對線網類型變數的連續賦值是數碼電路數據流建模的重要步驟,數碼系統不含時序的組合邏輯部分可以使用線網的連續賦值描述。線網不能夠像暫存器那樣儲存當前數值,它需要驅動源提供訊號,這種驅動是連續不斷的,因此線網變數的賦值稱為連續賦值,這與暫存器變數在過程中的單次賦值不同,而且所用的運算子也有區別。在Verilog里,線網連續賦值的關鍵字為assign
,下面為一個例子:
module and
wire out;
wire in1, in2;
assign out = in1 & in2;
在這個例子中,線網變數out
在系統執行過程中總為兩個輸入線網變數in1
和in2
邏輯與的結果。
線網的連續賦值可以在關鍵字assgin
附加延遲資訊,[4]:43例如上面的代碼可以改為:
assign #5 out = in1 & in2; //in1和in2逻辑与的结果在5个时间周期后才施加在out上
Verilog能夠描述過程中的時序特性,這也是硬件描述語言與普通電腦程式語言的重要差別之一。過程的時序控制可以通過三種方式實現:延遲時序控制、事件時序控制以及電平敏感時序控制。
過程中的時序控制可以控制代碼的執行時間。在Verilog中,除了過程中的時序控制,還可以定義元件、路徑的延遲。這些延遲請參見本條目後面有關邏輯門級延遲的部分。
在代碼中使用關鍵字#
和延遲的時間,就可以通過延遲來進行時序控制。延遲的時間可以是數碼、變數或者表達式。延遲時序控制又分為兩種:常規延遲和內嵌延遲。[17]:89
常規延遲在賦值陳述式的左邊,系統執行到這一行代碼時,系統先進行延遲,延遲完成後,再計算表達式,並將結果賦值給左邊的變數;而內嵌延遲在賦值陳述式的右邊,系統執行到這一行代碼時,系統先立即計算表達式,再進行延遲,最後把表達式的結果賦值給左邊的變數。在上述兩種延遲方式中,設計人員需要注意表達式的自變數在延遲過程中可能發生變化。常規延遲是先延遲再計算表達式,這時表達式的自變數可能已經發生了變化;而內嵌延遲在延遲前就已經進行了計算,表達式的自變數在延遲過程中發生的變化,對已經計算的表達式結果沒有影響,延遲只是指這個結果需要等待一段時間再賦值給左邊的變數。[17]:89-93
下面的代碼片段分別展示了常規延遲和內嵌延遲:
parameter latency = 8;
initial
begin
x = 1;
y = 2;
#5 x = 3; //使用常规延迟:等待5个系统周期后对x赋值
#latency y = 4; //使用变量进行常规延迟,再等待8个系统周期后对y赋值
z = #10 (x+y); //使用内嵌延迟:先用当前时刻的x、y数值计算(x+y),再等待10个系统周期后对z赋值
end //z的最终数值为7
在順序陳述式塊(begin...end
)中,由於陳述式是從上到下、一行一行地執行,而所有常規延遲時間都是實際執行時間相對於這一句本來應該開始執行的時間(也是上一句執行完成之時)的延遲值。因此,在上面的代碼範例中,對變數y
的賦值時間相對於上一句結束延遲了8個系統周期,而上一句相對系統零時刻已經延遲了5個系統周期,因此對y
的賦值發生在第13個系統周期。不過,如果順序陳述式塊中存在非阻塞賦值,由於這個結構有着類似並列陳述式塊的特點,因此需要特別考慮。
在並列陳述式塊(fork...join
)中,由於所有陳述式都是並行執行的,而所有常規延遲時間都是實際執行時間相對於這一句本來應該開始執行的時間(也是系統零時刻)的延遲值,因此各個常規延遲所指的時間都是相對於系統零時刻。
事件時序控制的意思是,如果指定的事件發生,則代碼被觸發執行。它的關鍵字為@
,後面可以加變數或者事件名稱。參見下面的例子:
@(clk) x = 1; //当变量clk发生变化,则将1赋值给x
@(posedge clk) y = 2; //在变量clk的上升沿,将2赋值给y
z = @(negedge clk) (x+y); //先立即计算表达式(x+y),然后在变量clk下降沿,将表达式的结果赋值给z
上面@
後面括號里的是常規事件。Verilog允許設計人員通過關鍵字event
和觸發符號->
定義自己所需要的命名事件觸發:
event bigger_than_two;
always @(posedge clock)
begin
if(a > 2)
->bigger_than_two; //如果a大于2,则事件bigger_than_two被触发
end
always @(bigger_than_two) //当bigger_than_two被触发,执行下面的过程
begin
//过程的代码
end
一種經典的用法結構如下,可以理解為「在整個仿真過程中,一旦某變數發生變化,就執行某操作」:
always @(a)
begin
x = x+1;
end
另一種用法稱為OR事件時序控制,其代碼結構為@(a or b)
或@(a, b)
,即當a
或b
其中任意一個變數發生變化時,代碼或代碼塊才被觸發執行。監視的變數如果有3個,則其代碼結構變為@(a or b or c)
或@(a, b, c)
,以此類推。如果需要監視的變數很多,則可以使用@*
或@(*)
,它表示對之後代碼塊中的所有輸入變數敏感。此外,敏感列表中除了變數,還可以是前面所提到過的常規事件、命名事件。[29]:87-89
Verilog中還有一種電平敏感時序控制方式,即使用wait(a)
,當變數a
為真,則執行後面的代碼塊。[29]:69
begin
、end
組合代表了這個代碼塊的各行代碼是順序執行的,這種代碼塊稱為順序代碼塊;[17]:66-67後面的fork
、join
代表了這個代碼塊的各行代碼是並行執行的,這種代碼塊稱為並列代碼塊。[17]:68-69與模組、過程不同,兩種代碼塊是可以巢狀,即順序代碼塊中可以包含並列代碼塊。[17]:69-72下面的例子展示了這兩種代碼塊巢狀使用的效果:
initial
fork
x = 1;
y = 2;
begin
z = 3;
w = 4;
end
join
由於這個initial
過程使用了關鍵字fork
、join
,其中x
、y
、z
的賦值同時於系統零時刻發生,而z
和w
由於位於一個順序代碼塊中,因此w
的賦值在z
的賦值後才進行。
在使用並列代碼塊的時候,有可能引起代碼的競爭,例如兩個陳述式對一個變數同時進行賦值。雖然理論上兩個陳述式同時執行,但是具體的情況是必然有一句先執行,但這與順序陳述式塊的「先後」有本質區別。實際的先後順序取決於所用的仿真系統。[29]:95這並不是Verilog硬件描述語言本身的缺陷,並列陳述式塊是一種人為設定的功能,這可以讓設計人員更容易地描述某些過程,當然他們必須認真考慮競爭帶來的潛在問題。
如果某部分代碼需要在不同地方多次使用,可以在模組中定義任務或函數。
任務通過關鍵字task
來聲明。任務可以有零個或者多個輸入變數,但是沒有輸出返回值。呼叫任務時,將按照任務內指定的方式處理這些變數。由於它相當於一個子過程,因此任務中賦值的變數只能是暫存器類型的,而且只能使用過程賦值陳述式。任務可以具有時序結構,例如延遲、非阻塞賦值等。任務中可以呼叫任務和函數。[3]:79與模組的聲明不同,任務的聲明沒有類似模組埠列表的輸入變數列表。儘管如此,呼叫任務的時候,還是需要在括號里按照任務聲明時的順序羅列輸入變數。[23]:57在某種程度上,任務和C語言中沒有返回值的函數有些類似。
函數通過關鍵字function
來聲明。函數不僅有輸入變數,還有一個返回值作為輸出變數,這個返回值的名稱與函數的名稱相同。函數與任務不同,它是一個只有邏輯功能的部分,不能包含時序結構。函數中只能呼叫函數。[3]:79Verilog中的函數與C語言中有返回值的函數有些類似。通常將函數放在設定運算子的右邊,它的返回值被賦值給左邊的變數。[40]
如果任務或函數同時在多個地方被呼叫,則需要使用automatic
關鍵字聲明,這樣系統可以為不同地方的呼叫分配獨立的主記憶體空間。[29]:118[29]:121-122
邏輯門級描述
邏輯門級描述的抽象級別較低,僅次於電晶體級。實際的硬件電路往往都是以邏輯門級網表作為基礎構建的,而設計人員常常會在進行更高抽象級別的設計。儘管如此,邏輯門級的設計還是更接近真實電路形式。Verilog提供了一系列邏輯門原語(Primitive)供用戶使用。例如,非(not
)、與門(and
)、或門(or
)、與非門(nand
)、或非(nor
)、異或(xor
)、同或(xnor
)。邏輯門原語和模組類似,可以通過實例參照的方式使用。[17]:156
電晶體級描述
Verilog能夠在低抽象級別對電路進行描述,是它的一個重要特點。Verilog中提供了多種電晶體級(也稱開關級)元件類型,包括N型金屬氧化物半導體場效電晶體(關鍵字為nmos
)、P型金屬氧化物半導體場效電晶體(關鍵字為pmos
)、互補式金屬氧化物半導體(關鍵字為cmos
)、帶阻抗的互補式金屬氧化物半導體(關鍵字為rcmos
)、電源單元(關鍵字為supply1
)、接地單元(關鍵字為supply0
)等。所有的電晶體都可以設置延遲屬性。[4]:9設計人員可以利用這些低抽象級元件構建所需要的邏輯門或直接構成其他進階組件。[29]:158-167
延遲
真實的硬件電路不可避免地都存在延遲現象。在Verilog中,可以對邏輯門、電晶體這些元件的延遲資訊進行描述。可以為元件的延遲指定一個時間,則上升、下降、關斷的延遲都使用這個時間;也可以按照先後順序分別指定上升延遲、下降延遲,而關斷延遲取二者較小值;當然也可以為上升、下降、關斷各指定一個時間。[29]:50-51例如,下面的代碼為與門實例添加了三個延遲時間,分別對應上升、下降、關斷:
and #(1, 2, 3) my_and (out, in1, in2);
邏輯門和電晶體的延遲屬於「慣性延遲」。它的意思是,邏輯門和電晶體獲得外部輸入之後,延遲指定的時間後,才會將結果呈現在輸出端上。在延遲期間,如果輸入改變,但是這個訊號的持續時間小於指定延遲的時間,則不會影響邏輯門和電晶體的輸出;如果這個訊號的持續時間大於指定延遲的時間,則之前的結果將不會呈現在輸出端,改變輸入訊號後的結果將經過延遲後將呈現在輸出端[23]:143-144
Verilog還允許設計人員為每個延遲時間設置最大值、典型值、最小值,在編譯階段可以通過編譯代碼選擇其中一個。[29]:51-52
在聲明線網或對線網進行連續賦值的時候,可以為線網添加延遲資訊。這樣,所有連續賦值給線網的表達式都會立即計算出結果,但是這個結果在延遲時間後才會賦值給線網。[4]:43如果在這段延遲時間內,右側表達式的結果發生變化,則用於賦值的表達式結果取變化後的。另外,如果如果輸入變數變化的脈衝寬度小於延遲的時間,其變化不會對輸出造成影響。這種延遲被稱為「慣性延遲」,[29]:59邏輯門和電晶體的延遲也是這種情況。
過程延遲在前面的延遲時序控制一部分講述過。過程賦值陳述式中的延遲主要分為常規延遲(又稱為外部延遲)和內嵌延遲(又稱為內部延遲)兩種,其中前者先延遲,再計算表達式、賦值給左邊的變數;而後者先立即計算表達式,經過延遲後再將結果賦值給左邊的變數。[17]:89-93
設計人員可以在模組中關鍵字specify
、endspecify
之間對路徑延遲進行描述。與元件的延遲不同,路徑延遲是指訊號在某兩個暫存器類型或線網類型變數之間傳遞所需的延遲時間。在specify
代碼塊中可以使用條件結構來根據情況選擇所需的延遲時間值。與元件延遲相同的是,延遲的時間值可以指定上升、下降、關斷的情況,同時也可以包含最大值、典型值、最小值。[29]:148-153
邏輯綜合
設計人員編寫的Verilog代碼通常是在較高抽象級別的,例如暫存器傳輸級。這一抽象級別包含了對電路訊號在暫存器之間傳輸情況的描述。但是邏輯門級的網表,即邏輯門的相互連接形式,才最接近真實的硬件電路。這一形式與暫存器傳輸級的描述,在功能上是等效的。為了給後續硬件製造人員提供這種低抽象級別的描述,需要將高抽象級別的Verilog代碼轉換為低抽象級別的邏輯門級網表。這一過程稱為邏輯合成(Logic Synthesis)。[4]:5[13]:253
在自動化邏輯合成工具出現之前,儘管人們可以用硬件描述語言進行設計,但是還是需要人工進行邏輯合成。例如,如果電路模組只有少數幾個輸入端,我們可以使用類似卡諾圖的方法來對邏輯函數進行化簡。隨着電路規模不斷增加,人工邏輯合成的容易出錯、耗費大量時間的缺點逐漸凸顯。同時,在某種特殊元件製程下最佳化的綜合結果不一定在另一種製程下還合適,如果需要採用另外的製程,設計人員需要花費很長時間重新進行邏輯合成。隨着自動化邏輯合成工具的出現,硬件描述語言、所需元件製程資訊(製程庫)可以直接被邏輯合成工具讀取,通過其內部的自動合成演算法,輸出符合設計約束(通常包括時序、功耗、面積的約束)的邏輯門級網表。藉助自動合成工具,設計人員可以將更多的精力放在高抽象級別的硬件描述語言設計。[29]:203-204
邏輯合成工具不能接受所有的Verilog代碼。設計人員需要確保硬件描述語言代碼是周期到周期的暫存器傳輸級描述。諸如while
的迴圈結構必須通過訊號邊緣的形式(如@(posedge clock)
)提供終止條件;initial
結構可能也不能被轉換。如果不指明數碼的位寬,那麼系統可能預設它為一個較大的值(如32位元),這就可能產生規模非常龐大的邏輯門級網表,其中一部分是不必要的,這將造成資源的浪費。與未知邏輯x
、高阻態z
有關的運算子不能被轉換,例如===
、!==
。此外,條件結構如果只有if
而沒有對else
的情況進行設計,或者選擇結構缺少預設情況default
,很可能產生預期之外的鎖存器。[4]:93由於需要使用與製程相關的邏輯門,因此用戶自訂的原語很可能不能被轉換。[4]:91設計人員需要採取良好的代碼風格,以獲得更最佳化的邏輯合成結果。[29]:204-219為了適應符合可重用設計思想的系統晶片、矽智財設計,設計人員還應該遵循更嚴格的編碼規範。[41]
結構類型 | 注 |
---|---|
initial |
只用於仿真測試檔案(test bench) |
events |
Events 對於同步測試檔案的各個組件比較有意義
|
real |
Real 資料類型不可合成
|
time |
Time 資料類型不可合成
|
force 和release |
Force 和release 不可合成
|
assign 和deassign |
reg 類型的assign 和deassign 操作不可合成,但是wire 類型的assign 操作可以合成
|
fork join |
使用非阻塞賦值可以獲得同樣效果 |
primitive |
只有門級的原語(primitives)可合成 |
table |
用戶自訂原語(UDP)及table 不可合成
|
#1 |
延遲只用於仿真,綜合器一般直接忽略延遲 |
進階功能
除了系統提供的26種邏輯門、電晶體原語,[23]:103-104Verilog也提供用戶自訂原語(User Defined Primitive, UDP)。原語與模組的階層類似,但是原語的輸入輸出關係是完全通過查表實現的。組合邏輯的用戶自訂原語的核心是真值表,序向邏輯的用戶自訂原語的核心是激發表。設計人員需要在狀態表中羅列可能出現的輸入和輸出情況。如果在實際使用過程中,遇到狀態表中沒有定義的情況,則輸出不確定值x
。[4]:62使用自訂原語很直觀,但是如果輸入變數較多,狀態表就會變得很複雜。在很多情況中,用戶自訂原語並不能被邏輯綜合工具轉換。[4]:91[23]:200
程式語言介面(Program Language Interface, PLI)提供了通過C語言函數對Verilog數據結構進行儲存、讀取操作的途徑。[4]:9
Verilog程式語言介面的發展先後經過了三代,其中第一代為任務或函數子程式,它可以在C程式和Verilog設計之間傳遞數據;第二代為存取子程式,它可以在用戶自訂C程式和Verilog的內部數據表示的介面上被使用;第三代為Verilog過程介面,它進一步擴充了前兩代程式語言介面的功能。[29]:199-200
通過使用程式語言介面,設計人員可以自訂介面的功能,然後通過類似呼叫系統任務的方式呼叫這些自訂功能。這樣,設計人員可以很大程度地擴充他們能使用的功能,例如監視、激勵、除錯功能,或者用它來提取設計資訊、顯示輸出等。[29]:184-186
相關電子設計自動化工具
Verilog作為業界使用最廣泛的硬件描述語言之一,有大量的電子設計自動化工具對它予以支援。通過使用整合式開發環境,設計人員可以在常見的Windows或其他圖形化系統中進行設計、仿真、驗證,例如Cadence和Synopsys等公司提供的集成電路電腦輔助設計系統。[43]
與VHDL的比較
Verilog硬件描述語言(Verilog Hardware Description Language)的英語縮寫Verilog HDL和另一種類似的硬件描述語言VHDL在名稱上容易混淆,實際上二者是兩種不同的硬件描述語言。[44]一些進階的電子設計自動化工具支援用戶在專案內同時使用Verilog和VHDL來進行硬件設計。[45]:18
VHDL是由美國國防部主持研發的硬件描述語言,成為了第一個成為電機電子工程師學會標準的硬件描述語言,美國政府相關的專案都是基於VHDL;而Verilog由民間商業公司的私有產品發展為IEEE標準的,因此在商用領域的市場佔有量更大,設計人員和支援資源比VHDL更廣。在美國大約有10萬設計人員、200所大學教授採用Verilog硬件描述語言。[46]
Verilog和VHDL作為業界廣泛認可、同為電機電子工程師學會標準的硬件描述語言,有着各自的特點。VHDL的設計之初就更加針對標準化進行設計,Verilog則具有簡明、高效的代碼風格。[17]:4兩種語言都能夠在多個抽象層次對數碼電路建模,並且可以與驗證、仿真、綜合工具協同工作。其中,Verilog的邏輯門級、電晶體級級電路描述能力更強,VHDL不具備這樣低階的描述能力,但是另一方面,VHDL的系統級抽象描述能力則比Verilog強。[13]:255另外,由於Verilog與C語言在語法上有相似之處,因此具有C語言基礎的設計人員更容易掌握它,[39]:11而VHDL設計人員需要具有Ada語言編程基礎,並且學習周期比Verilog更長。相關學術文獻顯示,在美國的進階數碼系統設計領域,Verilog和VHDL的使用比率大約分別為80%和20%,這項比率在日本和台灣和美國相似。[46]隨着Verilog-A被合併到Verilog標準之中,而該部分後來成為了Verilog-AMS的一部分,該語言增加了對模擬電子系統的描述能力,因此它在混合訊號積體電路中有着更廣泛的應用。[6]
參考文獻
延伸閱讀
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.