Verilog

**Verilog** 是一種用於描述、設計電子系統（特別是數位電路）的硬體描述語言，主要用於在積體電路設計，特別是超大型積體電路的電腦輔助設計。Verilog 是電機電子工程師學會（IEEE）的 1364 號標準。

Verilog 能夠在多種抽象級別對數位邏輯系統進行描述：既可以在電晶體級、邏輯閘級進行描述，也可以在暫存器傳輸級對電路訊號在暫存器之間的傳輸情況進行描述。:18 除了對電路的邏輯功能進行描述，Verilog 代碼還能夠被用於邏輯仿真、邏輯綜合，:3-7 其中後者可以把暫存器傳輸級的 Verilog 代碼轉換為邏輯閘級的網表，從而方便在現場可程式化邏輯閘陣列上實現硬體電路，或者讓硬體廠商製造具體的特殊應用積體電路。設計人員還可以利用 Verilog 的擴充部分 Verilog-AMS 進行類比電路和混合訊號積體電路的設計。

## 發展歷史

1983 年末，Gateway 設計自動化公司的工程師創立了 Verilogs。當時 Gateway 設計自動化公司還叫做自動整合設計系統（Automated Integrated Design Systems），1985 年公司將名字改為 Gateway 設計自動化。該公司的菲爾・莫比（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 可以對整合的類比和混訊系統進行建模。:255 容易與電機電子工程師學會 1364-2005 標準混淆的是加強硬體驗證語言特性的 SystemVerilog（電機電子工程師學會 1800-2005 標準），它是 Verilog-2005 的一個超集，它是硬體描述語言、硬體驗證語言（針對驗證的需求，特別加強了物件導向特性）的一個整合。

2009 年，IEEE 1364-2005 和 IEEE 1800-2005 兩個部分合併為 IEEE 1800-2009，成為了一個新的、統一的 SystemVerilog 硬體描述驗證語言（hardware description and verification language, HDVL）。

## 以模組為基礎的設計

描述複雜的硬體電路，設計人員總是將複雜的功能劃分為簡單的功能，模組是提供每個簡單功能的基本結構。設計人員可以採取「自頂向下」的思路，將複雜的功能模組劃分為低層次的模組。:58 這一步通常是由系統級的總設計師完成，而低層次的模組則由下一級的設計人員完成。自頂向下的設計方式有利於系統級別層次劃分和管理，並提高了效率、降低了成本。「由下而上」方式是「自頂向下」方式的逆過程。

使用 Verilog 描述硬體的基本設計單元是模組（module）。構建複雜的電子電路，主要是通過模組的相互連接呼叫來實現的。模組被包含在關鍵字 `module`、`endmodule` 之內。:59 實際的電路元件。Verilog 中的模組類似 C 語言中的函式，它能夠提供輸入、輸出埠，可以實例呼叫其他模組，也可以被其他模組實例呼叫。模組中可以包括組合邏輯部分、過程時序部分。例如，四選一的多路選擇器，就可以用模組進行描述。它具有兩個位選輸入訊號、四個資料輸入，一個輸出端，在 Verilog 中可以表示為：

設計人員可以使用一個頂層模組，通過實例呼叫上面這個模組的方式來進行測試。這個頂層模組常被稱為「測試平台（Testbench）」。
為了最大程度地對電路的邏輯進行功能驗證，測試代碼需要儘可能多地覆蓋系統所涉及的語句、分支、條件、路徑、觸發、狀態機狀態，:141-144 驗證人員需要在測試平台里建立足夠多的輸入激勵，:8 並連接到被測模組的輸入端，然後檢測其輸出端的表現是否符合預期（諸如 SystemVerilog 的硬體驗證語言能夠提供針對驗證專門最佳化的資料結構，以隨機測試的方式進行驗證，這對於高度複雜的積體電路設計驗證可以起到關鍵作用）。實例呼叫模組時，需要將埠的連接情況按照這個模組聲明時的順序排列。這個頂層模組由於不需要再被外界呼叫，因此沒有輸入輸出埠：:4

在這個測試平台模組里，設計人員可以設定仿真時的輸入訊號以及訊號監視程式，然後觀察仿真時的輸出情況是否符合要求，這樣就可以了解設計是否達到了預期。

範例中的對模組進行實例參照時，按照原模組聲明時的順序羅列了輸入變數。除此之外，還可以使用或者採用命名埠連接的方式。使用這種方式，埠的排列順序可以與原模組聲明時不同，甚至可以不連接某些埠：

上面所述的情況是，測試平台頂層模組的測試變數直接連接了所設計的功能模組。測試平台還可以是另一種形式，即測試平台並不直接連接所設計的功能模組，而是在這個測試平台之下，將激勵模組和功能模組以相同的抽象級別，通過線網相互連接。這兩種形式的測試平台都可以完成對功能模組的測試。大型的電路系統，正是由各個層次不同模組之間的連接、呼叫，來實現複雜的功能的。:11

## 語言要素

Verilog 的設計初衷是成為一種基本語法與 C 語言相近的硬體描述語言。:18 這是因為在 Verilog 設計之初，C 語言已經在許多領域得到廣泛應用，C 語言的許多語言要素已經被許多人習慣。一種與 C 語言相似的硬體描述語言，可以讓電路設計人員更容易學習和接受。不過，Verilog 與 C 語言還是存在許多差別。另外，作為一種與普通電腦程式語言不同的硬體描述語言，它還具有一些獨特的語言要素，例如向量形式的線網和暫存器、過程中的非阻塞賦值等。總的來說，具備 C 語言能力的設計人員將能夠很快掌握 Verilog 硬體描述語言。

### 基本規範

#### 空白符

空白符是指代碼中的空格（對應的跳脫識別碼為 `\b`）、制表符（`\t`）和換行（`\n`）。:17 如果這些空白符出現在字串里，那麼它們不可忽略。除此之外，代碼中的其他空白符在編譯的時候都將會被視為分隔識別碼，即使用 2 個空格或者 1 個空格並無影響。不過，在代碼中使用合適的空格，可以讓上下行代碼的外觀一致（例如使設定運算子位於同一個豎直列），從而提高代碼的可讀性。

#### 注釋

為了方便代碼的修改或其他人的閱讀，設計人員通常會在代碼中加入注釋。與 C 語言一樣，有兩種方式書寫注釋。第一種為多行注釋，即注釋從 `/*` 開始，直到 `*/` 才結束；另一種為單行注釋，注釋從 `//` 開始，從這裡到這一行末尾的內容會被系統辨識為注釋。

某些電子設計自動化工具，會辨識出代碼中以特殊格式書寫、含有某些預先約定關鍵詞的注釋，並從這些注釋所提取有用的資訊。這些注釋不是供人閱讀，而是向第三方工具提供有關設計專案的額外資訊。例如，某些邏輯綜合工具可以從注釋中讀取綜合的約束資訊。:24

#### 大小寫敏感性

Verilog 是一種大小寫敏感的硬體描述語言。其中，它的所有系統關鍵字都是小寫的。:29

#### 識別碼及保留字

Verilog 代碼中用來定義語言結構名稱的字元稱為識別碼，包括變數名、埠名、模組名等等。識別碼可以由字母、數位、底線以及美元符（`$`）來表示。但是識別碼的第一個字元只能是字母、數位或者底線，不能為美元符，這是因為以美元符開始的識別碼和系統任務的保留字衝突。:19

和其他許多程式語言類似，Verilog 也有許多保留字（或稱為關鍵字），使用者定義的識別碼不能夠和保留字相同。Verilog 的保留字均為小寫。:19 變數類型中的 `wire`、`reg`、`integer` 等、表示過程的 `initial`、`always` 等，以及所有其他的系統任務、編譯指令，都是關鍵字。可以查閱官方文獻以完整的關鍵字的列表。

#### 跳脫識別碼

跳脫識別碼（又稱跳脫字元），是由 `\` 開始，以空白符結束的一種特殊程式語言結構。這種結構可以用來表示那些容易與系統語言結構相同的內容（例如 `"` 在系統中被用來表示字串，如果字串本身的內容包含一個與之形式相同的雙引號，那麼就必須使用跳脫識別碼）。下面列出了常用的幾種跳脫識別碼。除此之外，在反斜線之後也可以加上字元的 ASCII，這種跳脫識別碼相當於一個字元。常用的跳脫識別碼有 `\n`（換行）、`\t`（制表位）、`\b`（空格）、`\\`（反斜槓）和 `\"`（英文的雙引號）等。

### 資料類型

#### 四值邏輯

上面列出了 Verilog 採用的具有八種訊號強度的四值邏輯（four-valued logic），數位電路中的訊號可以用邏輯值、訊號強度加以描述。當系統遇到訊號之間的競爭時，需要考慮各組訊號的狀態和強度。如果驅動統一線網的訊號強度不同，則輸出結果是訊號強度高的值；如果兩個強度相同的訊號之間連接到同一個線網，將會發生競爭，結果為不確定值 `x`。:20

#### 線網與暫存器

Verilog 所用到的所有變數都屬於兩個基本的類型：線網類型和暫存器類型。:30

線網與我們實際使用的電線類似，它的數值一般只能通過連續賦值（continuous assignment），由賦值符右側連接的驅動源決定。線網在初始化之前的值為 x（`trireg` 類型的線網是一個例外，它相當於能夠儲存電荷的電容器:105）。如果未連接驅動源，則該線網變數的當前數值為 `z`，即高阻態。線網類型的變數有以下幾種：`wire`、`tri`、`wor`、`trior`、`wand`、`triand`、`tri0`、`tri1`、`supply0`、`supply1`、`trireg`，其中 `wire` 作為一般的電路連線使用最為普遍，而其他幾種用於構建匯流排，即多個驅動源連接到一條線網的情況，:79-80 或搭建電源、接地等。當進行模組的埠聲明時，如果沒有明確指出其類型，那麼這個埠會被隱含地聲明為 `wire` 類型。因此，在聲明輸出埠時應該注意是否有必要加上 `reg` 關鍵字。以下面的代碼片段為例：

暫存器與之不同，它可以儲存當前的數值，直到另一個數值被賦值給它。在保持當前數值的過程中，不需要驅動源對它進行作用。如果未對暫存器變數賦值，它的初始值則為 `x`。Verilog 中所說的暫存器類型變數與真實的硬體暫存器是不同的，它是指一個儲存數值的變數。如果要在一個過程（`initial` 過程或 `always` 過程）里對變數賦值，這個變數必須是暫存器類型的。暫存器類型的變數有以下幾種：`reg`（普通暫存器）、`integer`（整數）、`time`（時間）、`real`（實數），其中 `reg` 作為一般的暫存器使用最為普遍。利用暫存器變數的陣列，還可以對 ROM 進行建模。

關於選擇線網類型還是暫存器類型，需要符合一定的規定。模組的輸入埠可以與外界的線網或暫存器類型的變數連接，但是這個模組輸出埠只能連接到外界的線網。再簡單點，就是在兩個模組的訊號連接點，提供訊號的一方可以是暫存器或者線網，但是接受訊號的一方只能是線網。:35-36 此外，在 `initial`、`always` 過程代碼塊中賦值的變數必須是暫存器類型的，:58:30 而連續賦值的物件只能是線網類型的變數。:81:81

#### 數位的表示

在 Verilog 里，當一個變數的類型確定，即已經知道它是暫存器類型或者是線網類型，當把具體的數值賦值給它時，需要利用下面所述的數位表示方法。數位表示的基本語法結構為 `<位宽>'<数制的符号><数值>`。:31 其中，位寬是與資料大小相等的對應二進位數的位數加上占位所用 0 的位數，這個位數需要使用十進位來表示。位寬是可選項，如果沒有指明位寬，則預設的資料位寬與仿真器有關（最小 32 位元）；數制需要用字母來表示，`h` 對應十六進位，`d` 對應十進位，`o` 對應八進位，`b` 對應二進位。如果沒有指明數制，則預設資料為十進位數。: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 中，純量的意思是只具有一個位元的變數，而向量表示具有多個位元的變數。如果沒有特別指明位寬，系統預設它為純量。:21

在真實的數位電路，例如將兩個四位二進位數相加的進位加法器中，我們可以發現，其中一個數是通過四條電線（每條線表示四位中的某一位）連接到加法器上的。我們可以用一個向量來表示這個多位數，分別用這個向量的各個分量來表示「四條電線」，即四位中的某一位。這樣做的好處是，可以方便地在 Verilog 代碼的其他地方選擇其中的一位（位選）或多位（域選）。:22 當然，如果沒有進行位選或域選，則這個多位數整體被選擇。

向量的表示需要使用方括號，方括號里的第一個數位為向量第一個分量的序號，第二個數位為向量最後一個分量的序號，中間用冒號隔開。向量分量的序號不像 C 語言的陣列一樣必須從 0 開始，不過為了和數位電路里二進位數高低位的表示方法一致，我們常常讓最低位為 0（即對於四位二進位數，其最高位為第 3 位，次高位為第 2 位，次低位為第 1 位，最低位為第 0 位），當然這只是一種習慣。例如，上面提到的四位二進位數用向量表示為：

上面的向量聲明之後，我們就可以方便地選擇其中的某幾個分量進行操作。請注意用於域選的方括號的位置在向量名稱之後，方括號內的數位為所需的位數。例如我們可以進行以下操作：

當對向量進行賦值時，如果右邊的數值位寬大於左邊的變數，則多出來的位被丟棄；如果右邊的數值位寬小於左邊的變數，則不夠的位用 0 填補。

#### 陣列

Verilog 中的幾種暫存器類型的資料，:32 包括 `reg`、`integer`、`time`、`real`，以及由這幾種資料構成的向量，都可以構成陣列。聲明陣列時，方括號位於陣列名的後面，括號內的第一個數位為第一個元素的序號，第二個數位為最後一個元素的序號，中間用冒號隔開。如果陣列是由向量構成的，則陣列的其中某個元素是向量。同樣，出於習慣考慮，我們一般讓陣列第一個元素的序號為 0，後面元素的序號依次遞增。此外，和 C 語言類似，使用者可以聲明多維陣列。例如：

由於陣列和向量的表示都使用了方括號，因此使用時需要注意這個變數或向量的名稱在最初被聲明為何種類型的資料。上面第三行的例子是 65536 個 8 位元向量組成的向量陣列，它可以描述一個 64KB 的記憶體。

表示陣列某個元素時，允許使用變數來表示元素的索引（如 `number [i] = 1234;`），但是表示一個向量的一位或者幾位時，只允許使用數位來表示位的索引；此外，使用陣列時一次只能對一個元素進行操作，而不能向向量那樣同時對連續的幾個位進行操作，例如 `my_input [65535][7:4] = 4'b1010;` 將一個四位二進位數賦值給第 65536 個元素的高四位。:86

#### 參數

可以通過 `parameter` 關鍵字聲明參數。參數與常數的意義類似，不能夠通過賦值運算改變它的數值。在模組進行實例化時，可以能夠通過 `defparam`，即參數多載語句塊來改變模組實例的參數。另一種方法是在模組實例化時，使用 `#()` 將所需的實例參數覆蓋模組的預設參數。局部參數可以用 `localparam` 關鍵字聲明，它不能夠進行參數多載。:25

在設計中使用參數，可以使得模組代碼在不同條件下被重複利用，例如四位數全加器和十六位數全加器可以通過參數實例化同一個通用全加器模組。:29

#### 字串

Verilog 中的字串總體來說與 C 語言中的字串較為類似，其中每個字元以 ASCII 表示，占 8 位元。字串儲存在位寬足夠的向量暫存器中。字串中的空格、換行等特殊內容，以跳脫識別碼（參見前面提到過的跳脫識別碼）的形式表示。

### 流程控制

為了使設計人員方便地使用暫存器傳輸級描述，Verilog 提供了多種流程控制結構，包括 `if`、`if...else`、`if...else if...else` 等形式的條件結構，`case` 分支結構，`for`、`while` 迴圈結構。這些流程控制結構與 C 語言有著相似的用法。不同的迴圈結構可能造成不同的邏輯綜合結果。Verilog 也提供了一些 C 語言中沒有的流程控制結構以適應硬體描述語言的需要，例如 `casex`、`casez` 兩種選擇結構，前者可以條件數值中的 `x`、`z` 均作為無關值，後者僅將 `z` 作為無關值；:53:87-88 此外還提供了 `forever`、`repeat` 兩種迴圈結構，分別用於無限迴圈和指定次數迴圈。:56-65: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 個運算元相減或取 1 個運算元的負數（二進位二補數表示:31）
  * 乘（`*`）：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 填充；有符號數則用其符號位填充: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`

### 系統任務

系統任務可以被用來執行一些系統設計所需的輸入、輸出、時序檢查、仿真控制操作。:51-53 所有的系統任務名稱前都帶有美元符號 `$` 使之與使用者定義的任務和函式相區分。例如，`$display` 用於顯示指定的字串，然後自動換行（用法類似 C 語言中的 `printf` 函式）；`$monitor` 用於監視變數，一旦被監視的變數發生變化，會顯示指定的字串；而 `$time` 可以提取當前的仿真時間。完整的列表請查閱參考工具、Verilog 手冊或標準文件。

### 編譯指令

Verilog 具有一些編譯指令，它們的基本格式為 `` `<keyword> ``，注意第一個符號不是單引號，而是鍵盤上數位 1 左邊那個鍵對應的撇號。常用的編譯指令有文字宏預定義 `` `define ``、`` `include ``，它們的功能與 C 語言中類似，分別提供文字替換、檔案包含的功能。Verilog 還提供了 `` `ifdef ``、`` `ifndef `` 等一系列條件編譯指令，設計人員可以使得代碼在滿足一定條件的情況下才進行編譯。此外，`` `timescale `` 指令可以對時間單位進行定義。:132 詳細的編譯指令清單請參閱相關參考書籍。

## 暫存器傳輸級描述

### 兩種過程

在 Verilog 中，可以聲明兩種不同的過程：always 過程和 initial 過程。過程可以是包含時序的過程描述，而不包含時序的過程還可以表達組合邏輯。:170always 過程從關鍵字 `always` 開始，可以連續多次執行，當過程的最後一行代碼執行完成後，再次從第一行代碼開始執行。如果沒有使用系統任務 `$finish`，always 過程將不斷迴圈執行。initial 過程從關鍵字 `initial` 開始，它只能執行一次。:140-145

一個模組中可以包含多個過程，各個過程相互之間是並行執行的。不過，過程不能夠巢狀使用。如果過程中有多個語句，則需要使用關鍵字 `begin`、`end` 或 `fork`、`join` 將它們組成一個代碼塊。這兩種關鍵字組合代表著順序代碼塊和並列代碼塊，後面的部分會講述這兩種結構。

例如，利用 always 過程迴圈執行的特點，可以為模組提供一個時間脈衝（注意第一個 `initial` 過程為時鐘的初始化，這個過程只需要進行一次）：

雖然，always 代碼塊和 `while` 語句、`forever` 語句都能提供迴圈功能，但是 always 代碼塊的迴圈更側重過程的迴圈執行，而後二者更側重代碼的迴圈執行。因此，為了使代碼更具條理，過程的迴圈應當用 `always` 語句描述。當然，在實際使用過程中，強制使用其中的某一種在功能實現上都是可行的。

### 暫存器變數的過程賦值

在 Verilog 中，有兩種賦值運算，一種叫做阻塞賦值（blocking assignment），其運算子為 `=`；另一種叫做非阻塞賦值（non-blocking assignment），其運算子為 `<=`。在順序代碼塊中使用阻塞賦值語句，如果這一句沒有執行完成，那麼後面的語句不會執行；如果在順序代碼塊中使用非阻塞賦值，則執行這一句的同時，並不會阻礙下一句代碼的執行。:47 而且，如果後一個語句涉及前面一個非阻塞賦值語句中的變數，由於這兩個語句「同時」執行，因此後一個語句所用到的是前面一個語句執行前變數的數值。非阻塞賦值是 Verilog 作為硬體描述語言與普通程式語言的一個重大區別。

帶有兩個正反器輸出端的簡單範例如下

上面的例子如果沒有使用非阻塞賦值，而使用阻塞賦值，那麼 `flop1` 和 `flop2` 的數值就不能被交換。`flop1` 和 `flop2` 在執行完畢後的數值都與之前 `flop2` 的數值相同。在傳統的程式語言中，可能需要一個臨時的變數，或者使用指標，才能夠達到交換兩個變數的目的。這裡使用了非阻塞賦值，相當於引入了一個隱含的臨時變數。第二個非阻塞賦值右邊的 `a` 是第一句賦值之前的數值，變數交換的目的得以實現。:84 訊號邊緣敏感的過程語句塊內常使用非阻塞賦值，使語句塊的諸賦值語句同時進行，雖然功能上似乎可以用阻塞賦值實現，但是仿真時會產生不正常的結果。:180

通常的過程賦值語句往往只有在觸發或迴圈等情況，即賦值語句被執行到時候，才會使左邊的暫存器變數改變一次；而線網變數的連續賦值則一直「監視」右邊表達式的變化，一旦其結果發生變化，立即會左邊的線網變數更新為此結果。如果需要對暫存器變數進行過程連續賦值，則可以使用 Verilog 提供的 `assign` 或 `force` 關鍵字「強制地」將設定運算子右邊表達式的結果連續不斷地施加在左邊的暫存器變數上。

### 線網變數的連續賦值

對線網類型變數的連續賦值是數位電路資料流建模的重要步驟，數位系統不含時序的組合邏輯部分可以使用線網的連續賦值描述。線網不能夠像暫存器那樣儲存當前數值，它需要驅動源提供訊號，這種驅動是連續不斷的，因此線網變數的賦值稱為連續賦值，這與暫存器變數在過程中的單次賦值不同，而且所用的運算子也有區別。在 Verilog 里，線網連續賦值的關鍵字為 `assign`，下面為一個例子：

在這個例子中，線網變數 `out` 在系統執行過程中總為兩個輸入線網變數 `in1` 和 `in2` 邏輯與的結果。

線網的連續賦值可以在關鍵字 `assgin` 附加延遲資訊，:43 例如上面的代碼可以改為：

### 時序控制

Verilog 能夠描述過程中的時序特性，這也是硬體描述語言與普通電腦程式語言的重要差別之一。過程的時序控制可以通過三種方式實現：延遲時序控制、事件時序控制以及電平敏感時序控制。

過程中的時序控制可以控制代碼的執行時間。在 Verilog 中，除了過程中的時序控制，還可以定義元件、路徑的延遲。這些延遲請參見本條目後面有關邏輯閘級延遲的部分。

#### 延遲時序控制

在代碼中使用關鍵字 `#` 和延遲的時間，就可以通過延遲來進行時序控制。延遲的時間可以是數位、變數或者表達式。延遲時序控制又分為兩種：常規延遲和內嵌延遲。:89

常規延遲在賦值語句的左邊，系統執行到這一行代碼時，系統先進行延遲，延遲完成後，再計算表達式，並將結果賦值給左邊的變數；而內嵌延遲在賦值語句的右邊，系統執行到這一行代碼時，系統先立即計算表達式，再進行延遲，最後把表達式的結果賦值給左邊的變數。在上述兩種延遲方式中，設計人員需要注意表達式的自變數在延遲過程中可能發生變化。常規延遲是先延遲再計算表達式，這時表達式的自變數可能已經發生了變化；而內嵌延遲在延遲前就已經進行了計算，表達式的自變數在延遲過程中發生的變化，對已經計算的表達式結果沒有影響，延遲只是指這個結果需要等待一段時間再賦值給左邊的變數。:89-93

下面的代碼片段分別展示了常規延遲和內嵌延遲：

在順序語句塊（`begin...end`）中，由於語句是從上到下、一行一行地執行，而所有常規延遲時間都是實際執行時間相對於這一句本來應該開始執行的時間（也是上一句執行完成之時）的延遲值。因此，在上面的代碼範例中，對變數 `y` 的賦值時間相對於上一句結束延遲了 8 個系統周期，而上一句相對系統零時刻已經延遲了 5 個系統周期，因此對 `y` 的賦值發生在第 13 個系統周期。不過，如果順序語句塊中存在非阻塞賦值，由於這個結構有著類似並列語句塊的特點，因此需要特別考慮。

在並列語句塊（`fork...join`）中，由於所有語句都是並行執行的，而所有常規延遲時間都是實際執行時間相對於這一句本來應該開始執行的時間（也是系統零時刻）的延遲值，因此各個常規延遲所指的時間都是相對於系統零時刻。

#### 事件時序控制

事件時序控制的意思是，如果指定的事件發生，則代碼被觸發執行。它的關鍵字為 `@`，後面可以加變數或者事件名稱。參見下面的例子：

上面 `@` 後面括號里的是常規事件。Verilog 允許設計人員通過關鍵字 `event` 和觸發符號 `->` 定義自己所需要的命名事件觸發：

一種經典的用法結構如下，可以理解為「在整個仿真過程中，一旦某變數發生變化，就執行某操作」：

另一種用法稱為 OR 事件時序控制，其代碼結構為 `@(a or b)` 或 `@(a, b)`，即當 `a` 或 `b` 其中任意一個變數發生變化時，代碼或代碼塊才被觸發執行。監視的變數如果有 3 個，則其代碼結構變為 `@(a or b or c)` 或 `@(a, b, c)`，以此類推。如果需要監視的變數很多，則可以使用 `@*` 或 `@(*)`，它表示對之後代碼塊中的所有輸入變數敏感。此外，敏感列表中除了變數，還可以是前面所提到過的常規事件、命名事件。:87-89

#### 電平敏感時序控制

Verilog 中還有一種電平敏感時序控制方式，即使用 `wait(a)`，當變數 `a` 為真，則執行後面的代碼塊。:69

### 順序代碼塊與並列代碼塊

`begin`、`end` 組合代表了這個代碼塊的各行代碼是順序執行的，這種代碼塊稱為順序代碼塊；:66-67 後面的 `fork`、`join` 代表了這個代碼塊的各行代碼是並行執行的，這種代碼塊稱為並列代碼塊。:68-69 與模組、過程不同，兩種代碼塊是可以巢狀，即順序代碼塊中可以包含並列代碼塊。:69-72 下面的例子展示了這兩種代碼塊巢狀使用的效果：

由於這個 `initial` 過程使用了關鍵字 `fork`、`join`，其中 `x`、`y`、`z` 的賦值同時於系統零時刻發生，而 `z` 和 `w` 由於位於一個順序代碼塊中，因此 `w` 的賦值在 `z` 的賦值後才進行。

在使用並列代碼塊的時候，有可能引起代碼的競爭，例如兩個語句對一個變數同時進行賦值。雖然理論上兩個語句同時執行，但是具體的情況是必然有一句先執行，但這與順序語句塊的「先後」有本質區別。實際的先後順序取決於所用的仿真系統。:95 這並不是 Verilog 硬體描述語言本身的缺陷，並列語句塊是一種人為設定的功能，這可以讓設計人員更容易地描述某些過程，當然他們必須認真考慮競爭帶來的潛在問題。

### 任務和函式

如果某部分代碼需要在不同地方多次使用，可以在模組中定義任務或函式。

任務通過關鍵字 `task` 來聲明。任務可以有零個或者多個輸入變數，但是沒有輸出返回值。呼叫任務時，將按照任務內指定的方式處理這些變數。由於它相當於一個子過程，因此任務中賦值的變數只能是暫存器類型的，而且只能使用過程賦值語句。任務可以具有時序結構，例如延遲、非阻塞賦值等。任務中可以呼叫任務和函式。:79 與模組的聲明不同，任務的聲明沒有類似模組埠列表的輸入變數列表。儘管如此，呼叫任務的時候，還是需要在括號里按照任務聲明時的順序羅列輸入變數。:57 在某種程度上，任務和 C 語言中沒有返回值的函式有些類似。

函式通過關鍵字 `function` 來聲明。函式不僅有輸入變數，還有一個返回值作為輸出變數，這個返回值的名稱與函式的名稱相同。函式與任務不同，它是一個只有邏輯功能的部分，不能包含時序結構。函式中只能呼叫函式。:79Verilog 中的函式與 C 語言中有返回值的函式有些類似。通常將函式放在設定運算子的右邊，它的返回值被賦值給左邊的變數。

如果任務或函式同時在多個地方被呼叫，則需要使用 `automatic` 關鍵字聲明，這樣系統可以為不同地方的呼叫分配獨立的記憶體空間。:118:121-122

## 邏輯閘級描述

邏輯閘級描述的抽象級別較低，僅次於電晶體級。實際的硬體電路往往都是以邏輯閘級網表作為基礎構建的，而設計人員常常會在進行更高抽象級別的設計。儘管如此，邏輯閘級的設計還是更接近真實電路形式。Verilog 提供了一系列邏輯閘原語（Primitive）供使用者使用。例如，非（`not`）、及閘（`and`）、或閘（`or`）、反及閘（`nand`）、或非（`nor`）、互斥或（`xor`）、同或（`xnor`）。邏輯閘原語和模組類似，可以通過實例參照的方式使用。:156

## 電晶體級描述

Verilog 能夠在低抽象級別對電路進行描述，是它的一個重要特點。Verilog 中提供了多種電晶體級（也稱開關級）元件類型，包括 N 型金屬氧化物半導體場效電晶體（關鍵字為 `nmos`）、P 型金屬氧化物半導體場效電晶體（關鍵字為 `pmos`）、互補式金屬氧化物半導體（關鍵字為 `cmos`）、帶阻抗的互補式金屬氧化物半導體（關鍵字為 `rcmos`）、電源單元（關鍵字為 `supply1`）、接地單元（關鍵字為 `supply0`）等。所有的電晶體都可以設定延遲屬性。:9 設計人員可以利用這些低抽象級元件構建所需要的邏輯閘或直接構成其他進階組件。:158-167

## 延遲

### 邏輯閘和電晶體的延遲

真實的硬體電路不可避免地都存在延遲現象。在 Verilog 中，可以對邏輯閘、電晶體這些元件的延遲資訊進行描述。可以為元件的延遲指定一個時間，則上升、下降、關斷的延遲都使用這個時間；也可以按照先後順序分別指定上升延遲、下降延遲，而關斷延遲取二者較小值；當然也可以為上升、下降、關斷各指定一個時間。:50-51 例如，下面的代碼為及閘實例添加了三個延遲時間，分別對應上升、下降、關斷：

邏輯閘和電晶體的延遲屬於「慣性延遲」。它的意思是，邏輯閘和電晶體獲得外部輸入之後，延遲指定的時間後，才會將結果呈現在輸出端上。在延遲期間，如果輸入改變，但是這個訊號的持續時間小於指定延遲的時間，則不會影響邏輯閘和電晶體的輸出；如果這個訊號的持續時間大於指定延遲的時間，則之前的結果將不會呈現在輸出端，改變輸入訊號後的結果將經過延遲後將呈現在輸出端:143-144

Verilog 還允許設計人員為每個延遲時間設定最大值、典型值、最小值，在編譯階段可以通過編譯代碼選擇其中一個。:51-52

### 線網延遲

在聲明線網或對線網進行連續賦值的時候，可以為線網添加延遲資訊。這樣，所有連續賦值給線網的表達式都會立即計算出結果，但是這個結果在延遲時間後才會賦值給線網。:43 如果在這段延遲時間內，右側表達式的結果發生變化，則用於賦值的表達式結果取變化後的。另外，如果如果輸入變數變化的脈衝寬度小於延遲的時間，其變化不會對輸出造成影響。這種延遲被稱為「慣性延遲」，:59 邏輯閘和電晶體的延遲也是這種情況。

### 過程延遲

過程延遲在前面的延遲時序控制一部分講述過。過程賦值語句中的延遲主要分為常規延遲（又稱為外部延遲）和內嵌延遲（又稱為內部延遲）兩種，其中前者先延遲，再計算表達式、賦值給左邊的變數；而後者先立即計算表達式，經過延遲後再將結果賦值給左邊的變數。:89-93

### 路徑延遲

設計人員可以在模組中關鍵字 `specify`、`endspecify` 之間對路徑延遲進行描述。與元件的延遲不同，路徑延遲是指訊號在某兩個暫存器類型或線網類型變數之間傳遞所需的延遲時間。在 `specify` 代碼塊中可以使用條件結構來根據情況選擇所需的延遲時間值。與元件延遲相同的是，延遲的時間值可以指定上升、下降、關斷的情況，同時也可以包含最大值、典型值、最小值。:148-153

## 邏輯綜合

### 概念簡介

設計人員編寫的 Verilog 代碼通常是在較高抽象級別的，例如暫存器傳輸級。這一抽象級別包含了對電路訊號在暫存器之間傳輸情況的描述。但是邏輯閘級的網表，即邏輯閘的相互連接形式，才最接近真實的硬體電路。這一形式與暫存器傳輸級的描述，在功能上是等效的。為了給後續硬體製造人員提供這種低抽象級別的描述，需要將高抽象級別的 Verilog 代碼轉換為低抽象級別的邏輯閘級網表。這一過程稱為邏輯合成（Logic Synthesis）。:5:253

在自動化邏輯合成工具出現之前，儘管人們可以用硬體描述語言進行設計，但是還是需要人工進行邏輯合成。例如，如果電路模組只有少數幾個輸入端，我們可以使用類似卡諾圖的方法來對邏輯函式進行化簡。隨著電路規模不斷增加，人工邏輯合成的容易出錯、耗費大量時間的缺點逐漸凸顯。同時，在某種特殊元件製程下最佳化的綜合結果不一定在另一種製程下還合適，如果需要採用另外的製程，設計人員需要花費很長時間重新進行邏輯合成。隨著自動化邏輯合成工具的出現，硬體描述語言、所需元件製程資訊（製程庫）可以直接被邏輯合成工具讀取，通過其內部的自動合成演算法，輸出符合設計約束（通常包括時序、功耗、面積的約束）的邏輯閘級網表。藉助自動合成工具，設計人員可以將更多的精力放在高抽象級別的硬體描述語言設計。:203-204

### 可合成代碼

邏輯合成工具不能接受所有的 Verilog 代碼。設計人員需要確保硬體描述語言代碼是周期到周期的暫存器傳輸級描述。諸如 `while` 的迴圈結構必須通過訊號邊緣的形式（如 `@(posedge clock)`）提供終止條件；`initial` 結構可能也不能被轉換。如果不指明數位的位寬，那麼系統可能預設它為一個較大的值（如 32 位元），這就可能產生規模非常龐大的邏輯閘級網表，其中一部分是不必要的，這將造成資源的浪費。與未知邏輯 `x`、高阻態 `z` 有關的運算子不能被轉換，例如 `===`、`!==`。此外，條件結構如果只有 `if` 而沒有對 `else` 的情況進行設計，或者選擇結構缺少預設情況 `default`，很可能產生預期之外的閂鎖。:93 由於需要使用與製程相關的邏輯閘，因此使用者自訂的原語很可能不能被轉換。:91 設計人員需要採取良好的代碼風格，以獲得更最佳化的邏輯合成結果。:204-219 為了適應符合可重用設計思想的系統晶片、矽智財設計，設計人員還應該遵循更嚴格的編碼規範。

## 進階功能

### 使用者自訂原語

除了系統提供的 26 種邏輯閘、電晶體原語，:103-104Verilog 也提供使用者自訂原語（User Defined Primitive, UDP）。原語與模組的階層類似，但是原語的輸入輸出關係是完全通過查表實現的。組合邏輯的使用者自訂原語的核心是真值表，序向邏輯的使用者自訂原語的核心是激發表。設計人員需要在狀態表中羅列可能出現的輸入和輸出情況。如果在實際使用過程中，遇到狀態表中沒有定義的情況，則輸出不確定值 `x`。:62 使用自訂原語很直觀，但是如果輸入變數較多，狀態表就會變得很複雜。在很多情況中，使用者自訂原語並不能被邏輯綜合工具轉換。:91:200

### 程式語言介面

程式語言介面（Program Language Interface, PLI）提供了通過 C 語言函式對 Verilog 資料結構進行儲存、讀取操作的途徑。:9

Verilog 程式語言介面的發展先後經過了三代，其中第一代為任務或函式子程式，它可以在 C 程式和 Verilog 設計之間傳遞資料；第二代為存取子程式，它可以在使用者自訂 C 程式和 Verilog 的內部資料表示的介面上被使用；第三代為 Verilog 過程介面，它進一步擴充了前兩代程式語言介面的功能。:199-200

通過使用程式語言介面，設計人員可以自訂介面的功能，然後通過類似呼叫系統任務的方式呼叫這些自訂功能。這樣，設計人員可以很大程度地擴充他們能使用的功能，例如監視、激勵、除錯功能，或者用它來提取設計資訊、顯示輸出等。:184-186

## 相關電子設計自動化工具

Verilog 作為業界使用最廣泛的硬體描述語言之一，有大量的電子設計自動化工具對它予以支援。通過使用整合式開發環境，設計人員可以在常見的 Windows 或其他圖形化系統中進行設計、仿真、驗證，例如 Cadence 和 Synopsys 等公司提供的積體電路電腦輔助設計系統。

## 與 VHDL 的比較

Verilog 硬體描述語言（Verilog Hardware Description Language）的英語縮寫 Verilog HDL 和另一種類似的硬體描述語言 VHDL 在名稱上容易混淆，實際上二者是兩種不同的硬體描述語言。一些進階的電子設計自動化工具支援使用者在專案內同時使用 Verilog 和 VHDL 來進行硬體設計。:18

VHDL 是由美國國防部主持研發的硬體描述語言，成為了第一個成為電機電子工程師學會標準的硬體描述語言，美國政府相關的專案都是基於 VHDL；而 Verilog 由民間商業公司的私有產品發展為 IEEE 標準的，因此在商用領域的市場占有量更大，設計人員和支援資源比 VHDL 更廣。在美國大約有 10 萬設計人員、200 所大學教授採用 Verilog 硬體描述語言。

Verilog 和 VHDL 作為業界廣泛認可、同為電機電子工程師學會標準的硬體描述語言，有著各自的特點。VHDL 的設計之初就更加針對標準化進行設計，Verilog 則具有簡明、高效的代碼風格。:4 兩種語言都能夠在多個抽象層次對數位電路建模，並且可以與驗證、仿真、綜合工具協同工作。其中，Verilog 的邏輯閘級、電晶體級級電路描述能力更強，VHDL 不具備這樣低階的描述能力，但是另一方面，VHDL 的系統級抽象描述能力則比 Verilog 強。:255 另外，由於 Verilog 與 C 語言在語法上有相似之處，因此具有 C 語言基礎的設計人員更容易掌握它，:11 而 VHDL 設計人員需要具有 Ada 語言編程基礎，並且學習周期比 Verilog 更長。相關學術文獻顯示，在美國的進階數位系統設計領域，Verilog 和 VHDL 的使用比率大約分別為 80% 和 20%，這項比率在日本和台灣和美國相似。隨著 Verilog-A 被合併到 Verilog 標準之中，而該部分後來成為了 Verilog-AMS 的一部分，該語言增加了對類比電子系統的描述能力，因此它在混合訊號積體電路中有著更廣泛的應用。

## 參考文獻

## 延伸閱讀