介紹
Verilog HDL是一種硬體描述語言,用於從算法級、門級到開關級的多種抽象設計層次的數字系統建模。被建模的數字系統對象的複雜性可以介於簡單的門和完整的電子數字系統之間。數字系統能夠按層次描述,並可在相同描述中顯式地進行時序建模。
Verilog HDL 語言具有下述描述能力:設計的行為特性、設計的數據流特性、設計的結構組成以及包含回響監控和設計驗證方面的時延和波形產生機制。所有這些都使用同一種建模語言。此外,Verilog HDL語言提供了程式語言接口,通過該接口可以在模擬、驗證期間從設計外部訪問設計,包括模擬的具體控制和運行。
Verilog HDL語言不僅定義了語法,而且對每個語法結構都定義了清晰的模擬、仿真語義。因此,用這種語言編寫的模型能夠使用Verilog仿真器進行驗證。語言從C程式語言中繼承了多種操作符和結構。Verilog HDL提供了擴展的建模能力,其中許多擴展最初很難理解。但是,Verilog HDL語言的核心子集非常易於學習和使用,這對大多數建模套用來說已經足夠。當然,完整的硬體描述語言足以對從最複雜的晶片到完整的電子系統進行描述。
發展歷史
Verilog是由Gateway設計自動化公司的工程師於1983年末創立的。當時Gateway設計自動化公司還叫做自動集成設計系統(Automated Integrated Design Systems),1985年公司將名字改成了前者。該公司的菲爾·莫比(Phil Moorby)完成了Verilog的主要設計工作。1990年,Gateway設計自動化被Cadence公司收購。
1990年代初,開放Verilog國際(Open Verilog International, OVI)組織(即現在的Accellera)成立,Verilog面向公有領域開放。1992年,該組織尋求將Verilog納入電氣電子工程師學會標準 。最終,Verilog成為了電氣電子工程師學會1364-1995標準,即通常所說的Verilog-95。
設計人員在使用這個版本的Verilog的過程中發現了一些可改進之處。為了解決用戶在使用此版本Verilog過程中反映的問題,Verilog進行了修正和擴展,這部分內容後來再次被提交給電氣電子工程師學會。這個擴展後的版本後來成為了電氣電子工程師學會1364-2001標準,即通常所說的Verilog-2001。Verilog-2001是對Verilog-95的一個重大改進版本,它具備一些新的實用功能,例如敏感列表、多維數組、生成語句塊、命名連線埠連線等。目前,Verilog-2001是Verilog的最主流版本,被大多數商業電子設計自動化軟體包支持。
2005年,Verilog再次進行了更新,即電氣電子工程師學會1364-2005標準。該版本只是對上一版本的細微修正。這個版本還包括了一個相對獨立的新部分,即Verilog-AMS。這個擴展使得傳統的Verilog可以對集成的模擬和混合信號系統進行建模。容易與電氣電子工程師學會1364-2005標準混淆的是加強硬體驗證語言特性的SystemVerilog(電氣電子工程師學會1800-2005標準),它是Verilog-2005的一個超集,它是硬體描述語言、硬體驗證語言(針對驗證的需求,特別加強了面向對象特性)的一個集成。
2009年,IEEE 1364-2005和IEEE 1800-2005兩個部分合併為IEEE 1800-2009,成為了一個新的、統一的SystemVerilog硬體描述驗證語言(hardware description and verification language, HDVL)。
設計
描述複雜的硬體電路,設計人員總是將複雜的功能劃分為簡單的功能,模組是提供每個簡單功能的基本結構。設計人員可以採取“自頂向下”的思路,將複雜的功能模組劃分為低層次的模組。這一步通常是由系統級的總設計師完成,而低層次的模組則由下一級的設計人員完成。自頂向下的設計方式有利於系統級別層次劃分和管理,並提高了效率、降低了成本。“自底向上”方式是“自頂向下”方式的逆過程。
使用Verilog描述硬體的基本設計單元是模組(module)。構建複雜的電子電路,主要是通過模組的相互連線調用來實現的。模組被包含在關鍵字module、endmodule之內。實際的電路元件。Verilog中的模組類似C語言中的函式,它能夠提供輸入、輸出連線埠,可以實例調用其他模組,也可以被其他模組實例調用。模組中可以包括組合邏輯部分、過程時序部分。例如,四選一的多路選擇器,就可以用模組進行描述。它具有兩個位選輸入信號、四個數據輸入,一個輸出端,在Verilog中可以表示為:
module mux (out, select, in0, in1, in2, in3);
output out;
input [1:0] select;
input in0, in1, in2, in3;
//具體的暫存器傳輸級代碼
endmodule
設計人員可以使用一個頂層模組,通過實例調用上面這個模組的方式來進行測試。這個頂層模組常被稱為“測試平台(Testbench)”。為了最大程度地對電路的邏輯進行功能驗證,測試代碼需要儘可能多地覆蓋系統所涉及的語句、分支、條件、路徑、觸發、狀態機狀態,驗證人員需要在測試平台里創建足夠多的輸入激勵,並連線到被測模組的輸入端,然後檢測其輸出端的表現是否符合預期(諸如SystemVerilog的硬體驗證語言能夠提供針對驗證專門最佳化的數據結構,以隨機測試的方式進行驗證,這對於高度複雜的積體電路設計驗證可以起到關鍵作用)。實例調用模組時,需要將連線埠的連線情況按照這個模組聲明時的順序排列。這個頂層模組由於不需要再被外界調用,因此沒有輸入輸出連線埠:
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
在這個測試平台模組里,設計人員可以設定仿真時的輸入信號以及信號監視程式,然後觀察仿真時的輸出情況是否符合要求,這樣就可以了解設計是否達到了預期。
示例中的對模組進行實例引用時,按照原模組聲明時的順序羅列了輸入變數。除此之外,還可以使用或者採用命名連線埠連線的方式。使用這種方式,連線埠的排列順序可以與原模組聲明時不同,甚至可以不連線某些連線埠:
mux my_mux (
.out(OUT),
.select(SELECT),
.in0(IN0),
.in1(IN1),
.in2(IN2),
.in3(IN3));//使用命名連線埠連線,括弧外面是模組聲明時的連線埠,括弧內是實際的連線埠連線//括弧外相當於C語言的形式參數,括弧內相當於實際參數
endmodule
上面所述的情況是,測試平台頂層模組的測試變數直接連線了所設計的功能模組。測試平台還可以是另一種形式,即測試平台並不直接連線所設計的功能模組,而是在這個測試平台之下,將激勵模組和功能模組以相同的抽象級別,通過線網相互連線。這兩種形式的測試平台都可以完成對功能模組的測試。大型的電路系統,正是由各個層次不同模組之間的連線、調用,來實現複雜的功能的。
語言要素
Verilog的設計初衷是成為一種基本語法與C語言相近的硬體描述語言。這是因為C語言在Verilog設計之初,已經在許多領域得到廣泛套用,C語言的許多語言要素已經被許多人習慣。一種與C語言相似的硬體描述語言,可以讓電路設計人員更容易學習和接受。不過,Verilog與C語言還是存在許多差別。另外,作為一種與普通計算機程式語言不同的硬體描述語言,它還具有一些獨特的語言要素,例如向量形式的線網和暫存器、過程中的非阻塞賦值等。總的來說,具備C語言的設計人員將能夠很快掌握Verilog硬體描述語言。
基本規範
空白符
空白符是指代碼中的空格(對應的轉義標識符為\b)、制表符(\t)和換行(\n)。如果這些空白符出現在字元串里,那么它們可忽略。除此之外,代碼中的其他空白符在編譯的時候都將會被視為分隔標識符,即使用2個空格或者1個空格並無影響。不過,在代碼中使用合適的空格,可以讓上下行代碼的外觀一致(例如使賦值運算符位於同一個豎直列),從而提高代碼的可讀性。
注釋
為了方便代碼的修改或其他人的閱讀,設計人員通常會在代碼中加入注釋。與C語言一樣,有兩種方式書寫注釋。第一種為多行注釋,即注釋從/*開始,直到*/才結束;另一種為單行注釋,注釋從//開始,從這裡到這一行末尾的內容會被系統識別為注釋。
某些電子設計自動化工具,會識別出代碼中以特殊格式書寫、含有某些預先約定關鍵字的注釋,並從這些注釋所提取有用的信息。這些注釋不是供人閱讀,而是向第三方工具提供有關設計項目的額外信息。例如,某些邏輯綜合工具可以從注釋中讀取綜合的約束信息。
大小寫敏感性
Verilog是一種大小寫敏感的硬體描述語言。其中,它的所有系統關鍵字都是小寫的。
標識符及保留字
Verilog代碼中用來定義語言結構名稱的字元稱為標識符,包括變數名、連線埠名、模組名等等。標識符可以由字母、數字、下劃線以及美元符($)來表示。但是標識符的第一個字元只能是字母、數字或者下劃線,不能為美元符,這是因為以美元符開始的標識符和系統任務的保留字衝突。
和其他許多程式語言類似,Verilog也有許多保留字(或稱為關鍵字),用戶定義的標識符不能夠和保留字相同。Verilog的保留字均為小寫。變數類型中的wire、reg、integer等、表示過程的initial、always等,以及所有其他的系統任務、編譯指令,都是關鍵字。可以查閱官方文獻以完整的關鍵字的列表。
轉義標識符
轉義標識符(又稱轉義字元),是由\開始,以空白符結束的一種特殊程式語言結構。這種結構可以用來表示那些容易與系統語言結構相同的內容(例如"在系統中被用來表示字元串,如果字元串本身的內容包含一個與之形式相同的雙引號,那么就必須使用轉義標識符)。下面列出了常用的幾種轉義標識符。除此之外,在反斜線之後也可以加上字元的ASCII,這種轉義標識符相當於一個字元。常用的轉義標識符有\n(換行)、\t(定位點)、\b(空格)、\\(反斜槓)和\"(英文的雙引號)等。
數據類型
四值邏輯
|
|
上面列出了Verilog採用的具有八種信號強度的四值邏輯(four-valued logic),數字電路中的信號可以用邏輯值、信號強度加以描述。當系統遇到信號之間的競爭時,需要考慮各組信號的狀態和強度。如果驅動統一線網的信號強度不同,則輸出結果是信號強度高的值;如果兩個強度相同的信號之間連線到同一個線網,將會發生競爭,結果為不確定值x。
線網與暫存器
Verilog所用到的所有變數都屬於兩個基本的類型:線網類型和暫存器類型。
線網與我們實際使用的電線類似,它的數值一般只能通過連續賦值(continuous assignment),由賦值符右側連線的驅動源決定。線網在初始化之前的值為x(trireg類型的線網是一個例外,它相當於能夠儲存電荷的電容器)。如果未連線驅動源,則該線網變數的當前數值為z,即高阻態。線網類型的變數有以下幾種:wire、tri、wor、trior、wand、triand、tri0、tri1、supply0、supply1、trireg,其中wire作為一般的電路連線使用最為普遍,而其他幾種用於構建匯流排,即多個驅動源連線到一條線網的情況,或搭建電源、接地等。當進行模組的連線埠聲明時,如果沒有明確指出其類型,那么這個連線埠會被隱含地聲明為wire類型。因此,在聲明輸出連線埠時應該注意是否有必要加上reg關鍵字。以下面的代碼片段為例:
module my_moule (out1, out2, in1, in2); //該模組具有兩個輸出連線埠
output reg out1; //out1連線埠被聲明為為reg類型,它可以保存當前值
output out2; //out2連線埠隱含地被聲明為為wire類型,它的數值必須依賴連續賦值語句維持
endmodule
暫存器與之不同,它可以保存當前的數值,直到另一個數值被賦值給它。在保持當前數值的過程中,不需要驅動源對它進行作用。如果未對暫存器變數賦值,它的初始值則為x。Verilog中所說的暫存器類型變數與真實的硬體暫存器是不同的,它是指一個儲存數值的變數。如果要在一個過程(initial過程或always過程)里對變數賦值,這個變數必須是暫存器類型的。暫存器類型的變數有以下幾種:reg(普通暫存器)、integer(整數)、time(時間)、real(實數),其中reg作為一般的暫存器使用最為普遍。利用暫存器變數的數組,還可以對ROM進行建模。
關於選擇線網類型還是暫存器類型,需要符合一定的規定。模組的輸入連線埠可以與外界的線網或暫存器類型的變數連線,但是這個模組輸出連線埠只能連線到外界的線網。再簡單點,就是在兩個模組的信號連線點,提供信號的一方可以是暫存器或者線網,但是接受信號的一方只能是線網。此外,在initial、always過程代碼塊中賦值的變數必須是暫存器類型的,而連續賦值的對象只能是線網類型的變數。
數字的表示
在Verilog里,當一個變數的類型確定,即已經知道它是暫存器類型或者是線網類型,當把具體的數值賦值給它時,需要利用下面所述的數字表示方法。數字表示的基本語法結構為<位寬>'<數制的符號><數值>。其中,位寬是與數據大小相等的對應二進制數的位數加上占位所用0的位數,這個位數需要使用十進制來表示。位寬是可選項,如果沒有指明位寬,則默認的數據位寬與仿真器有關(最小32位);數制需要用字母來表示,h對應十六進制,d對應十進制,o對應八進制,b對應二進制。如果沒有指明數制,則默認數據為十進制數。例如:
•12'h123:十六進制數123(使用12位)
•20'd44:十進制數44(使用20位,高位自動使用0填充)
•4'b1010:二進制數1010(使用4位)
•6'o77:八進制數77(使用6位)
如果某個數的最高位為x或z,那么系統會自動使用x或z來填充沒有占據的更高位。如果最高位為其他情況,系統會自動使用0來填充沒有占據的更高位。
另外,如果需要使用reg表示負數,可以在位寬之前添加一個負號,但是需要注意後面的數值為所需負數的二進制補碼。為了防止出錯,可以直接使用整數integer或實數real,二者都是帶符號數,再利用省略位寬和數制的十進制數來表示負數。
向量
向量形式的數據是Verilog相對C語言較為特殊的一種數據,但是這種數據在硬體描述語言中十分重要。在Verilog中,標量的意思是只具有一個二進制位的變數,而向量表示具有多個二進制位的變數。如果沒有特別指明位寬,系統默認它為標量。
在真實的數字電路,例如將兩個四位二進制數相加的進位加法器中,我們可以發現,其中一個數是通過四條電線(每條線表示四位中的某一位)連線到加法器上的。我們可以用一個向量來表示這個多位數,分別用這個向量的各個分量來表示“四條電線”,即四位中的某一位。這樣做的好處是,可以方便地在Verilog代碼的其他地方選擇其中的一位(位選)或多位(域選)。當然,如果沒有進行位選或域選,則這個多位數整體被選擇。
向量的表示需要使用方括弧,方括弧里的第一個數字為向量第一個分量的序號,第二個數字為向量最後一個分量的序號,中間用冒號隔開。向量分量的序號不像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中的幾種暫存器類型的數據,包括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個元素的高四位。
參數
可以通過parameter關鍵字聲明參數。參數與常數的意義類似,不能夠通過賦值運算改變它的數值。在模組進行實例化時,可以能夠通過defparam,即參數重載語句塊來改變模組實例的參數。另一種方法是在模組實例化時,使用#()將所需的實例參數覆蓋模組的默認參數。局部參數可以用localparam關鍵字聲明,它不能夠進行參數重載。
在設計中使用參數,可以使得模組代碼在不同條件下被重複利用,例如四位數全加器和十六位數全加器可以通過參數實例化同一個通用全加器模組。
字元串
Verilog中的字元串總體來說與C語言中的字元串較為類似,其中每個字元以ASCII表示,占8位。字元串存儲在位寬足夠的向量暫存器中。字元串中的空格、換行等特殊內容,以轉義標識符(參見前面提到過的轉義標識符)的形式表示。
流程控制
為了使設計人員方便地使用暫存器傳輸級描述,Verilog提供了多種流程控制結構,包括if、if...else、if...else if...else等形式的條件結構,case分支結構,for、while循環結構。這些流程控制結構與C語言有著相似的用法。不同的循環結構可能造成不同的邏輯綜合結果。
Verilog也提供了一些C語言中沒有的流程控制結構以適應硬體描述語言的需要,例如casex、casez兩種選擇結構,前者可以條件數值中的x、z均作為無關值,後者僅將z作為無關值;此外還提供了forever、repeat兩種循環結構,分別用於無限循環和指定次數循環。數字電路的邏輯功能描述常常使用到這些流程控制結構,例如,case結構可以清晰地描述一個數據選擇器。
運算符
Verilog的許多運算符和C語言類似,但是有一部分運算符是特有的,例如拼接運算符、縮減運算符、帶有無關位的相等運算符等。
Verilog的常見運算符隱藏▲
•按位
•按位取反(~):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或同不為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個運算元相減或取1個運算元的負數(二進制補碼錶示)
•乘(*):2個運算元相乘
•除(/):2個運算元相除
•求冪(**)}}:2個運算元求冪,前一個運算元為底數,後一個運算元為指數
•關係
•大於(>):比較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填充;有符號數則用其符號位填充
•算術左移(<<<):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
系統任務
系統任務可以被用來執行一些系統設計所需的輸入、輸出、時序檢查、仿真控制操作。所有的系統任務名稱前都帶有美元符號$使之與用戶定義的任務和函式相區分。
例如,$display用於顯示指定的字元串,然後自動換行(用法類似C語言中的printf函式);$monitor用於監視變數,一旦被監視的變數發生變化,會顯示指定的字元串;而$time可以提取當前的仿真時間。完整的列表請查閱參考工具、Verilog手冊或標準文檔。
編譯指令
Verilog具有一些編譯指令,它們的基本格式為`<keyword>,注意第一個符號不是單引號,而是鍵盤上數字1左邊那個鍵對應的撇號。常用的編譯指令有文本宏預定義`define、`include,它們的功能與C語言中類似,分別提供文本替換、檔案包含的功能。Verilog還提供了`ifdef、`ifndef等一系列條件編譯指令,設計人員可以使得代碼在滿足一定條件的情況下才進行編譯。此外,`timescale指令可以對時間單位進行定義。詳細的編譯指令清單請參閱相關參考書籍。
描述
兩種過程
在Verilog中,可以聲明兩種不同的過程:always過程和initial過程。過程可以是包含時序的過程描述,而不包含時序的過程還可以表達組合邏輯。always過程從關鍵字always開始,可以連續多次運行,當過程的最後一行代碼執行完成後,再次從第一行代碼開始執行。如果沒有使用系統任務$finish,always過程將不斷循環執行。initial過程從關鍵字initial開始,它只能執行一次。
一個模組中可以包含多個過程,各個過程相互之間是並發執行的。不過,過程不能夠嵌套使用。如果過程中有多個語句,則需要使用關鍵字begin、end或fork、join將它們組成一個代碼塊。這兩種關鍵字組合代表著順序代碼塊和並行代碼塊,後面的部分會講述這兩種結構。
例如,利用always過程循環執行的特點,可以為模組提供一個時間脈衝(注意第一個initial過程為時鐘的初始化,這個過程只需要進行一次):
initial a = 1'b0;
always #1 a=~a;
end
雖然,always代碼塊和while語句、forever語句都能提供循環功能,但是alway代碼塊的循環更側重過程的循環執行,而後二者更側重代碼的循環執行。因此,為了使代碼更具條理,過程的循環應當用always語句描述。當然,在實際使用過程中,強制使用其中的某一種在功能實現上都是可行的。
暫存器變數的過程賦值
在Verilog中,有兩種賦值運算,一種叫做阻塞賦值(blocking assignment),其運算符為=;另一種叫做非阻塞賦值(non-blocking assignment),其運算符為<=。在順序代碼塊中使用阻塞賦值語句,如果這一句沒有執行完成,那么後面的語句不會執行;如果在順序代碼塊中使用非阻塞賦值,則執行這一句的同時,並不會阻礙下一句代碼的執行。而且,如果後一個語句涉及前面一個非阻塞賦值語句中的變數,由於這兩個語句“同時”執行,因此後一個語句所用到的是前面一個語句執行前變化的數值。非阻塞賦值是Verilog作為硬體描述語言與普通程式語言的一個重大區別。
帶有兩個觸發器輸出端的簡單示例如下
always @ (posedge reset or posedge clock)
begin
a <= b;
b <= a;
end
endmodule
上面的例子如果沒有使用非阻塞賦值,而使用阻塞賦值,那么a和b的數值就不能被交換。a和b在執行完畢後的數值都與之前b的數值相同。在傳統的程式語言中,可能需要一個臨時的變數,或者使用指針,才能夠達到交換兩個變數的目的。這裡使用了非阻塞賦值,相當於引入了一個隱含的臨時變數。第二個非阻塞賦值右邊的a是第一句賦值之前的數值,變數交換的目的得以實現。信號邊緣敏感的過程語句塊內常使用非阻塞賦值,使語句塊的諸賦值語句同時進行,雖然功能上似乎可以用阻塞賦值實現,但是仿真時會產生不正常的結果。
通常的過程賦值語句往往只有在觸發或循環等情況,即賦值語句被執行到時候,才會使左邊的暫存器變數改變一次;而線網變數的連續賦值則一直“監視”右邊表達式的變化,一旦其結果發生變化,立即會左邊的線網變數更新為此結果。如果需要對暫存器變數進行過程連續賦值,則可以使用Verilog提供的assign或force關鍵字“強制地”將賦值運算符右邊表達式的結果連續不斷地施加在左邊的暫存器變數上。
線網變數的連續賦值
對線網類型變數的連續賦值是數字電路數據流建模的重要步驟,數字系統不含時的組合邏輯部分可以使用線網的連續賦值描述。線網不能夠像暫存器那樣儲存當前數值,它需要驅動源提供信號,這種驅動是連續不斷的,因此線網變數的賦值稱為連續賦值,這與暫存器變數在過程中的單次賦值不同,而且所用的運算符也有區別。在Verilog里,線網連續賦值的關鍵字為assign,下面為一個例子:
module and
wire out;
wire in1, in2;
assign out = in1 & in2;
在這個例子中,線網變數out在系統運行過程中總為兩個輸入線網變數in1和in2邏輯與的結果。
線網的連續賦值可以在關鍵字assgin附加延遲信息,例如上面的代碼可以改為:
assign #5 out = in1 & in2; //in1和in2邏輯與的結果在5個時間周期後才施加在out上
時序控制
Verilog能夠描述過程中的時序特性,這也是硬體描述語言與普通計算機程式語言的重要差別之一。過程的時序控制可以通過三種方式實現:延遲時序控制、事件時序控制以及電平敏感時序控制。
過程中的時序控制可以控制代碼的執行時間。在Verilog中,除了過程中的時序控制,還可以定義元件、路徑的延遲。這些延遲請參見本條目後面有關邏輯門級延遲的部分。
延遲時序控制
在代碼中使用關鍵字#和延遲的時間,就可以通過延遲來進行時序控制。延遲的時間可以是數字、變數或者表達式。延遲時序控制又分為兩種:常規延遲和內嵌延遲。
常規延遲在賦值語句的左邊,系統執行到這一行代碼時,系統先進行延遲,延遲完成後,再計算表達式,並將結果賦值給左邊的變數;而內嵌延遲在賦值語句的右邊,系統執行到這一行代碼時,系統先立即計算表達式,再進行延遲,最後把表達式的結果賦值給左邊的變數。在上述兩種延遲方式中,設計人員需要注意表達式的自變數在延遲過程中可能發生變化。常規延遲是先延遲再計算表達式,這時表達式的自變數可能已經發生了變化;而內嵌延遲在延遲前就已經進行了計算,表達式的自變數在延遲過程中發生的變化,對已經計算的表達式結果沒有影響,延遲只是指這個結果需要等待一段時間再賦值給左邊的變數。
下面的代碼片段分別展示了常規延遲和內嵌延遲:
parameter
latency = 8;
initialbegin 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的最終數值為3
在順序語句塊(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被觸發
endalways
@(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),以此類推。如果需要監視的變數很多,則可以使用@*或@(*),它表示對之後代碼塊中的所有輸入變數敏感。此外,敏感列表中除了變數,還可以是前面所提到過的常規事件、命名事件。
電平敏感時序控制
Verilog中還有一種電平敏感時序控制方式,即使用wait(a),當變數a為真,則執行後面的代碼塊。
順序塊與並行塊
begin、end組合代表了這個代碼塊的各行代碼是順序執行的,這種代碼塊稱為順序代碼塊;後面的fork、join代表了這個代碼塊的各行代碼是並發執行的,這種代碼塊稱為並行代碼塊。與模組、過程不同,兩種代碼塊是可以嵌套,即順序代碼塊中可以包含並行代碼塊。下面的例子展示了這兩種代碼塊嵌套使用的效果:
initial
fork
x = 1;
y = 2;
begin
z = 3;
w = 4;
end
join
由於這個initial過程使用了關鍵字fork、join,其中x、y的賦值同時於系統零時刻發生,而z和w由於位於一個順序代碼塊中,因此w的賦值在z的賦值後才進行。
在使用並行代碼塊的時候,有可能引起代碼的競爭,例如兩個語句對一個變數同時進行賦值。雖然理論上兩個語句同時執行,但是具體的情況是必然有一句先執行,但這與順序語句塊的“先後”有本質區別。實際的先後順序取決於所用的仿真系統。這並不是Verilog硬體描述語言本身的缺陷,並行語句塊是一種人為設定的功能,這可以讓設計人員更容易地描述某些過程,當然他們必須認真考慮競爭帶來的潛在問題。
任務和函式
如果某部分代碼需要在不同地方多次使用,可以在模組中定義任務或函式。
任務通過關鍵字task來聲明。任務可以有零個或者多個輸入變數,但是沒有輸出返回值。調用任務時,將按照任務內指定的方式處理這些變數。由於它相當於一個子過程,因此任務中賦值的變數只能是暫存器類型的,而且只能使用過程賦值語句。任務可以具有時序結構,例如延遲、非阻塞賦值等。任務中可以調用任務和函式。與模組的聲明不同,任務的聲明沒有類似模組連線埠列表的輸入變數列表。儘管如此,調用任務的時候,還是需要在括弧里按照任務聲明時的順序羅列輸入變數。在某種程度上,任務和C語言中沒有返回值的函式有些類似。
函式通過關鍵字function來聲明。任務不僅有輸入變數,還有一個返回值作為輸出變數,這個返回值的名稱與函式的名稱相同。函式與任務不同,它是一個只有邏輯功能的部分,不能包含時序結構。函式中只能調用函式。Verilog中的函式與C語言中有返回值的函式有些類似。通常將函式放在賦值運算符的右邊,它的返回值被賦值給左邊的變數。
如果任務或函式同時在多個地方被調用,則需要使用automatic關鍵字聲明,這樣系統可以為不同地方的調用分配獨立的記憶體空間。
邏輯門級描述
邏輯門級描述的抽象級別較低,僅次於電晶體級。實際的硬體電路往往都是以邏輯門級網表作為基礎構建的,而設計人員常常會在進行更高抽象級別的設計。儘管如此,邏輯門級的設計還是更接近真實電路形式。Verilog提供了一系列邏輯門原語(Primitive)供用戶使用。例如,非(not)、與門(and)、或門(or)、與非門(nand)、或非(nor)、異或(xor)、同或(xnor)。邏輯門原語和模組類似,可以通過實例引用的方式使用。
電晶體級描述
Verilog能夠在低抽象級別對電路進行描述,是它的一個重要特點。Verilog中提供了多種電晶體級(也稱開關級)元件類型,包括N型金屬氧化物半導體場效應管(關鍵字為nmos)、P型金屬氧化物半導體場效應管(關鍵字為pmos)、互補式金屬氧化物半導體(關鍵字為cmos)、帶阻抗的互補式金屬氧化物半導體(關鍵字為rcmos)、電源單元(關鍵字為supply1)、接地單元(關鍵字為supply0)等。所有的電晶體都可以設定延遲屬性。設計人員可以利用這些低抽象級元件構建所需要的邏輯門或直接構成其他高級組件。
延遲
邏輯門和電晶體的延遲
真實的硬體電路不可避免地都存在延遲現象。在Verilog中,可以對邏輯門、電晶體這些元件的延遲信息進行描述。可以為元件的延遲指定一個時間,則上升、下降、關斷的延遲都使用這個時間;也可以按照先後順序分別指定上升延遲、下降延遲,而關斷延遲取二者較小值;當然也可以為上升、下降、關斷各指定一個時間。例如,下面的代碼為與門實例添加了三個延遲時間,分別對應上升、下降、關斷:
and #(1, 2, 3) my_and (out, in1, in2);
邏輯門和電晶體的延遲屬於“慣性延遲”。它的意思是,邏輯門和電晶體獲得外部輸入之後,延遲指定的時間後,才會將結果呈現在輸出端上。在延遲期間,如果輸入改變,但是這個信號的持續時間小於指定延遲的時間,則不會影響邏輯門和電晶體的輸出;如果這個信號的持續時間大於指定延遲的時間,則之前的結果將不會呈現在輸出端,改變輸入信號後的結果將經過延遲後將呈現在輸出端
Verilog還允許設計人員為每個延遲時間設定最大值、典型值、最小值,在編譯階段可以通過編譯代碼選擇其中一個。
線網延遲
在聲明線網或對線網進行連續賦值的時候,可以為線網添加延遲信息。這樣,所有連續賦值給線網的表達式都會立即計算出結果,但是這個結果在延遲時間後才會賦值給線網。如果在這段延遲時間內,右側表達式的結果發生變化,則用於賦值的表達式結果取變化後的。另外,如果如果輸入變數變化的脈衝寬度小於延遲的時間,其變化不會對輸出造成影響。這種延遲被稱為“慣性延遲”,邏輯門和電晶體的延遲也是這種情況。
過程延遲
過程延遲在前面的延遲時序控制一部分講述過。過程賦值語句中的延遲主要分為常規延遲(又稱為外部延遲)和內嵌延遲(又稱為內部延遲)兩種,其中前者先延遲,再計算表達式、賦值給左邊的變數;而後者先立即計算表達式,經過延遲後再將結果賦值給左邊的變數。
路徑延遲
設計人員可以在模組中關鍵字specify、endspecify之間對路徑延遲進行描述。與元件的延遲不同,路徑延遲是指信號在某兩個暫存器類型或線網類型變數之間傳遞所需的延遲時間。在specify代碼塊中可以使用條件結構來根據情況選擇所需的延遲時間值。與元件延遲相同的是,延遲的時間值可以指定上升、下降、關斷的情況,同時也可以包含最大值、典型值、最小值。
邏輯綜合
概念
設計人員編寫的Verilog代碼通常是在較高抽象級別的,例如暫存器傳輸級。這一抽象級別包含了對電路信號在暫存器之間傳輸情況的描述。但是邏輯門級的網表,即邏輯門的相互連線形式,才最接近真實的硬體電路。這一形式與暫存器傳輸級的描述,在功能上是等效的。為了給後續硬體製造人員提供這種低抽象級別的描述,需要將高抽象級別的Verilog代碼轉換為低抽象級別的邏輯門級網表。這一過程稱為邏輯綜合(Logic Synthesis)。
在自動化邏輯綜合工具出現之前,儘管人們可以用硬體描述語言進行設計,但是還是需要人工進行邏輯綜合。例如,如果電路模組只有少數幾個輸入端,我們可以使用類似卡諾圖的方法來對邏輯函式進行化簡。隨著電路規模不斷增加,人工邏輯綜合的容易出錯、耗費大量時間的缺點逐漸凸顯。同時,在某種特殊器件工藝下最最佳化的綜合結果不一定在另一種工藝下還合適,如果需要採用另外的工藝,設計人員需要花費很長時間重新進行邏輯綜合。隨著自動化邏輯綜合工具的出現,硬體描述語言、所需器件工藝信息(工藝庫)可以直接被邏輯綜合工具讀取,通過其內部的自動綜合算法,輸出符合設計約束(通常包括時序、功耗、面積的約束)的邏輯門級網表。藉助自動綜合工具,設計人員可以將更多的精力放在高抽象級別的硬體描述語言設計。
可綜合代碼
邏輯綜合工具不能接受所有的Verilog代碼。設計人員需要確保硬體描述語言代碼是周期到周期的暫存器傳輸級描述。諸如while的循環結構必須通過信號邊緣的形式(如@(posedge clock))提供終止條件;initial結構可能也不能被轉換。如果不指明數字的位寬,那么系統可能默認它為一個較大的值(如32位),這就可能產生規模非常龐大的邏輯門級網表,其中一部分是不必要的,這將造成資源的浪費。與未知邏輯x、高阻態z有關的運算符不能被轉換,例如===、!==此外,條件結構如果只有if而沒有對else的情況進行設計,或者選擇結構缺少默認情況default,很可能產生預期之外的鎖存器。由於需要使用與工藝相關的邏輯門,因此用戶自定義的原語很可能不能被轉換。設計人員需要採取良好的代碼風格,以獲得更最佳化的邏輯綜合結果。為了適應符合可重用設計思想的系統晶片、IP核設計,設計人員還應該遵循更嚴格的編碼規範。
結構類型 | 注 |
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種邏輯門、電晶體原語,Verilog也提供用戶自定義原語(User Defined Primitive, UDP)。原語與模組的層次結構類似,但是原語的輸入輸出關係是完全通過查表實現的。組合邏輯的用戶自定義原語的核心是真值表,時序邏輯的用戶自定義原語的核心是激勵表。設計人員需要在狀態表中羅列可能出現的輸入和輸出情況。如果在實際使用過程中,遇到狀態表中沒有定義的情況,則輸出不確定值x。使用自定義原語很直觀,但是如果輸入變數較多,狀態表就會變得很複雜。在很多情況中,用戶自定義原語並不能被邏輯綜合工具轉換。
程式語言接口
程式語言接口(Program Language Interface, PLI)提供了通過C語言函式對Verilog數據結構進行存儲、讀取操作的途徑。
Verilog程式語言接口的發展先後經過了三代,其中第一代為任務或函式子程式,它可以在C程式和Verilog設計之間傳遞數據;第二代為存取子程式,它可以在用戶自定義C程式和Verilog的內部數據表示的接口上被使用;第三代為Verilog過程接口,它進一步擴展了前兩代程式語言接口的功能。
通過使用程式語言接口,設計人員可以自定義接口的功能,然後通過類似調用系統任務的方式調用這些自定義功能。這樣,設計人員可以很大程度地擴展他們能使用的功能,例如監視、激勵、調試功能,或者用它來提取設計信息、顯示輸出等。
相關工具
Verilog作為業界使用最廣泛的硬體描述語言之一,有大量的電子設計自動化工具對它予以支持。通過使用集成開發環境,設計人員可以在常見的Windows或其他圖形化系統中進行設計、仿真、驗證,例如Cadence和Synopsys等公司提供的積體電路計算機輔助設計系統。
與VHDL比較
VHDL——VHSIC(Very High Speed Integrated Circuit) HDL,由美國DOD支持開發的HDL,1987年成為IEEE 1076-1987 標準,後修訂為IEEE 1076-1993 標準。
Verilog來自C 語言,易學易用,編程風格靈活、簡潔,使用者眾多,特別在ASIC領域流行;
VHDL 來自ADA,語法嚴謹,比較難學,在歐洲和國內有較多使用者;
兩者描述的設計層次有所不同:
VHDL:系統級、行為級、RTL 級、門級
VerilogHDL:行為級、RTL 級、門級、開關級,不支持:電路級(spice)、版圖級(GDSII/CIF)
用途
Verilog HDL就是在用途最廣泛的C語言的基礎上發展起來的一種硬體描述語言,它是由GDA(Gateway Design Automation)公司的PhilMoorby在1983年末首創的,最初只設計了一個仿真與驗證工具,之後又陸續開發了相關的故障模擬與時序分析工具。1985年Moorby推出它的第三個商用仿真器Verilog-XL,獲得了巨大的成功,從而使得Verilog HDL迅速得到推廣套用。1989年CADENCE公司收購了GDA公司,使得VerilogHDL成為了該公司的獨家專利。1990年CADENCE公司公開發表了Verilog HDL,並成立LVI組織以促進Verilog HDL成為IEEE標準,即IEEE Standard 1364-1995.
Verilog HDL的最大特點就是易學易用,如果有C語言的編程經驗,可以在一個較短的時間內很快的學習和掌握,因而可以把Verilog HDL內容安排在與ASIC設計等相關課程內部進行講授,由於HDL語言本身是專門面向硬體與系統設計的,這樣的安排可以使學習者同時獲得設計實際電路的經驗。與之相比,VHDL的學習要困難一些。但Verilog HDL較自由的語法,也容易造成初學者犯一些錯誤,這一點要注意。