---
###### tags : `Books`
---
# Verilog HDL
## 使用 Verilog 的基本概念
### 接線 (Nets)
接線是連接硬體元素之點。接線之最主要的關鍵字為 wire ,接線預設為一個位元,並且預設值是 z (除了 trireg 接線,其預設值為 x) ,接線的值由它的驅動信號而來。若接線沒有驅動信號,則其值為 x 。
:::info
在提及接線的時候,我們可以使用 Net 與 wire ,但是真正在 Verilog 只有 wire 的關鍵字。
在 Verilog 中,高阻抗 z 可以使用 ? 替代。
:::
### 暫存器 (Registers)
暫存器用來表示資料儲存的元素,除非給定新的數值,否則暫存器內的數值會一直維持。 Verilog 中的暫存器與硬體的暫存器不同,就是一個可以持留 (Hold) 數值的變數。暫存器不用向接線一樣需要驅動,其在模擬過程中可以再任意時間被賦值。
### 時間
Verilog 的時間是以關鍵字 time 作宣告,其功用是儲存模擬時間 (Simulation Time) ,最少要為 64 bits 的資料。 `$time` 是系統函數,其功能是取得目前的模擬時間,以下為範例 :
```verilog=
time save_sim_time; // 定義時間變數
initial
save_sim_time = $time; // 儲存目前模擬時間
```
:::info
在宣告 `reg` 與 `wire` 時, vector 的宣告方式為 :
```verilog
type [upper:lower] vector_name;
```
其中 upper 比 lower 大, lower 也有可能比 upper 大。其最主要以 Endianess 有關。舉例來說,當較低的位元是 LSB 時 (`[3:0]`) 為 little-endian,當較高的位元是 MSB 時 (`[0:3]`) 為 big-endian 。 隨意將 little-endian 賦值給 big-endian 或是反之都是不符合規定的。
:::
:::info
在使用 assign 與 always 中,兩者合成出的電路是相同的,唯獨不一樣的是 assign 只能輸出至 wire ,而 always 則只能輸出至 reg 。
:::
### 系統任務 (System Tasks)
* 資訊顯示 (Displaying information)
`$display` 是最有用的系統任務,主要用來顯示變數值或是字串內容。很類似 C 語言中的 `printf` 的功能,不過每個 `$display` 命令後會自動換行,如果 `$display` 後面沒有接任何參數,就會輸出一個空行。以下為一些範例 :
```verilog=
$display("Hello Verilog World");
$display($time);
reg [0:40] virtual;
$display("At time %d virtual address is %h", $time, virtual);
```
* 資訊監視 (Monitoring information)
Verilog 有提供一個監控訊號變化的系統任務,只要監控訊號有變化,便會輸出新的值,這一個系統任務便是 `$monitor` 。 `$monitor` 與 `$display` 參數與格式相同,但是 `$monitor` 不僅僅是顯示一次而已,而是每一次信號變化都會顯示一次。
每一次僅允許有一個 `$monitor` 可以執行,如果有多個 `monitor` 的使用,只有最後一個有效,前面的 `monitor` 將不會作用。
:::info
不同的 `$monitor` 工作可以利用 `$monitoron` 與 `$monitoroff` 來做切換。
:::
* 中止 (Stopping) 和完成 (Finishing) 模擬
`$stop` 可以暫時中止模擬的進行,可以用在設計者任何一個想要停的時刻來做觀察除錯的工作。 `finish` 則是結束模擬運算。
:::info
Verilog 中也有與 C 語言類似的 define 與 include ,以下為使用的例子 :
```verilog=
`include "header.v" // 引入 header.v
`define WORD_REG reg [31:0]
`WORD_REG reg32; // 透過巨集定義一個 32-bit 暫存器變數
```
:::
## 模組與輸出入埠
### 模組 (Modules)
在 Verilog 中一個檔案可以用有許多的模組,且每個模組在檔案中,並沒有向 C 語言一樣要先有副程式,才有主程式的順序規定,模組宣告的順序可以是任意的,不受限於引用層次的高低。
### 埠的宣告 (Port Declaration)
在 Verilog 中內定的輸出入埠的宣告種類為 wire ,因此假若在埠的宣告中只有宣告 `output` 、 `input` 或是 `inout` ,則皆將其資料型態視為接線型態 (wire) ,假如需要將訊號的值儲存起來,就要將輸出入埠的種類宣告為 reg 。
注意 `input` 與 `inout` 型態的埠不能被宣告為 reg ,因為 reg 的作用主要在儲存訊號值,然而輸入訊號只是代表外來訊號的情況,所以不應該使用 reg 的型態。
### 輸出入埠的連接規定 (Port Connection Rules)
下圖為輸出入埠的連接規定 :
![](https://i.imgur.com/Z2xUJos.png)
* 輸入
在模組的內部,輸入埠永遠只是一個接線 (net) 。由外部來看,輸入埠的訊號可以接到暫存器 (reg) ,或是一個接線的訊號。
* 輸出
在模組的內部輸出訊號,可以宣告為暫存器或是接線的型態。由外部來看,必須接到一個接線,不可以連接到暫存器型態的訊號。
* 雙向
書向埠不管是在模組內,或外都必須連接到接線的型態。
* 輸出入埠的寬度
在 Verilog 中允許輸出入埠的內外連接寬度不同的訊號,但模擬器應該發出警告的訊號。
* 浮接的輸出入埠
Verilog 允許輸出入埠可以浮接,例如應用來除錯的時候,不需要接到任何的訊號,就可以將它浮接。
## 邏輯閘層次模型
### 閘的種類 (Gate Type)
Verilog 提供了兩種事先定義好的邏輯模型類別 : And/Or 閘與 Buf/Not 閘。
* And/Or 閘 : 只有一個純量輸出與多個純量輸入,而且閘的第一項為輸出,其餘為輸入。
```verilog=
wire OUT, IN1, IN2;
// 基本的取名
and a1(OUT, IN1, IN2);
// 輸入多於兩個的及閘
and a2(OUT, IN1, IN2, IN3);
// 基本邏輯閘不一定要取別名
and (OUT, IN1, IN2);
```
* Buf/Not 閘 : 只有一個純量輸入與多個純量輸出,且閘的最末項為輸入,其他項皆為輸出。
```verilog=
// 基本的取別名
buf b1(OUT1, IN);
// 輸出多於二
buf b1_2out(OUT1, OUT2, IN);
// 閘的別名不一定要取
not (OUT1, IN);
```
* Bufif/Notif 閘 : 使用 Buf/Not 閘加上一個控制信號。
```verilog=
// 取別名 bufif 閘, 0 與 1 的差別在於 ctrl 信號在進入前是否會先經過 not 閘
bufif1 b1 (out, in, ctrl);
bufif0 b0 (out, in, ctrl);
// 取別名 notif 閘
notif1 n1 (out, in, ctrl);
notif0 n0 (out, in, ctrl);
```
### 別名陣列 (Array of Instances)
在新的 Verilog 2001 的標準裡,可以利用別名陣列來做宣告。
```verilog=
wire [7:0] OUT, IN1, IN2;
// basic gate instantiations.
nand n_gate[7:0](OUT, IN1, IN2);
```
### 閘的延遲 (Gate Delays)
* 上升延遲 : 輸出由其他值上升至 1 所經過的延遲。
* 下降延遲 : 輸出由其他值下降至 0 所經過的延遲。
* 關閉延遲 : 輸出由其他值轉換至 z 的延遲。
```verilog=
// 所有轉換延遲 delay_time 時間
and #(delay_time) a1(out, i1, i2);
// 上升和下降延遲的設定
and #(rise_val, fall_val) a2(out, i1, i2);
// 上升,下降和關閉延遲的設定
bufif0 #(rise_val, fall_val, turnoff_val) b1(out, in, control);
and #(5) a1(out, i1, i2); // 所有轉換延遲 5 單位時間
and #(4, 6) a1(out, i1, i2); // 上升 = 4 、 下降 = 6
bufif0 #(3, 4, 5) b1(out, in, control); // 上升 = 3 、下降 = 4 、關閉 = 5
```
## 資料處理模型
### 持續指定的描述 (Continuous Assignments)
持續指定是在資料處理的層次中,對一條接線指定其邏輯值的最基本用法。持續指定的方式在 Verilog 中使用關鍵字 assign 來描述。其中驅動的強度在 Verilog 的預設值為 strong1 與 strong0 。持續指定的特性有以下幾點 :
1. 在指定敘述的左邊,必須是純量接線 (scalar net) 的單一接線,或是一個向量接線 (vector net) ,也可以是兩者的集合,但不能是一個純量暫存器或是向量暫存器。
2. 持續指定永遠處於活動的狀態,一旦敘述符號右邊的運算元的值發生變化時,其左邊指定那條線的值也會相對隨即改變。
3. 在敘述右邊的運算元,可以是暫存器、接線或是一個函數呼叫。
4. 延遲 (Delay) 是用來控制當指定敘述,需要更動左邊的接線值時,所需經過的延遲時間。
持續指定最主要三種方法,以下一一列出 :
* 正規的持續指定
```verilog=
wire out;
assign out = in1 & in2;
```
* 隱含式的持續指定 (Implicit Continuous Assignment)
除了前面用關鍵字 assign 的方式外,我們也可以在宣告接線的時候,一併加上持續指定的敘述,效果是一樣的。
```verilog=
// 正規的持續指定
wire out;
assign out = in1 & in2;
// 隱含式的持續指定,有相同效果
wire out = in1 & in2;
```
* 隱含式的接線宣告 (Implicit Net Declaration)
在對一條未經宣告的訊號使用隱藏式的持續指定時,該訊號會自動被宣告為接線。
```verilog=
// 持續指定
wire i1, i2;
assign out = i1 & i2;
// 訊號 out 並未被宣告為接線。不過因為隱含持續指定的緣故,模擬器會自動完成這項宣告。
```
:::info
在撰寫 Verilog 時,是非常不推薦使用 implicit nets 的。以以下為例 :
```verilog
wire [2:0] a, c; // Two vectors
assign a = 3'b101; // a = 101
assign b = a; // b = 1 implicitly-created wire
assign c = b; // c = 001 <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
// This could be a bug if the port was intended to be a vector.
```
implicit nets 是不能為 vector 的,並且只能用已宣告過的 net assign 給未宣告的 net 。因為 c 與 b 皆為未宣告的 net ,因此此種寫法是錯誤的。若想要避免 Verilog 自動幫使用者宣告 implicit net 產生不易除錯的錯誤的話,可以在開頭加上
```verilog
`default_nettype none
```
避免產生 implicit net 。
:::
### 延遲 (Delay)
這裡延遲的意思,是指一個 assign 右邊的值發生變化到相對應左邊的值,發生變化所需的時間。最主要有三種方法 :
* 正規指定延遲 (Regular Assignment Delay)
```verilog=
wire out;
assign #10 out = in1 & in2; // 在一個持續指定中描述延遲的方法
```
上例中,當 `in1` 或是 `in2` 的值發生變化時,相對應到 `out` 值發生變化所需的時間為十個時間單位。
:::info
需要注意的是,當輸入突波 (Pulse) 的時間寬度小於延遲的時候,則輸入所引發的變化將不會傳到輸出。
:::
* 隱含式連續指定延遲 (Implicit Continuous Assignment Delay)
配合前面所提的隱含式的指定敘述。
```verilog=
// 隱含式持續指定描述延遲的方式
wire #10 out = in1 & in2;
// 相同於
wire out;
assign #10 out = in1 & in2;
```
* 接線宣告延遲 (Net Declaration Delay)
在宣告一條接線時,指定這條接線的延遲,這條接線的敘述則用關鍵字 assign 來指定。
```verilog=
// 宣告一個 net 變數 out 的延遲時間為 10 個時間單位
wire #10 out;
assign out = in1 & in2;
// 上面的寫法與下面的寫法有相同的效果
wire out;
assign #10 out = in1 & in2;
```
### 運算子的種類 (Operator Types)
#### 算數運算子 (Arithmetic Operators)
* 二元運算子
如果任一個運算元,有不確定值 (x) 的位元時,則其結果為不確定值 (x) 。另外在取餘數時,其結果的正負符號需要與第一個運算元相同。
```
13 % 3 // 結果為 1
16 % 4 // 結果為 0
-7 % 2 // 結果為 -1 ,正負符號要跟第一個運算元相同
7 % -2 // 結果為 +1 ,正負符號要跟第一個運算元相同
```
* 一元運算子
要注意的是在 Verilog 中負數皆是以二的補數來表示,因此在 Verilog 中負數的型態,最好是整數或是實數,避免使用 \<sss\>'\<base\>\<nnn\> 型態的負數。
#### 相等運算子 (Equality Operators)
相等運算有兩種 : 邏輯上的相等運算與事件上的相等運算。在比較的時候,是將兩個運算元逐一位元的相互比較,假使兩個相比較的運算元長度不一的時候,較短的那個運算元就填 0 。以下為兩種相等運算子的比較 :
| 運算式 | 說明 | 可能傳回值 |
|:-------:|:---------------------------------------------------------:|:----------:|
| `a==b` | a 跟 b 相等,若是 a 、 b 中有 x 或是 z 的值的話則傳回 x | 0, 1, x |
| `a!=b` | a 跟 b 不相等,若是 a 、 b 中有 x 或是 z 的值的話則傳回 x | 0, 1, x |
| `a===b` | a 跟 b 相等,包括 x 或是 z 的比較 | 0, 1 |
| `a!==b` | a 跟 b 不相等,包括 x 或是 z 的比較 | 0, 1 |
#### 位元運算子 (Bitwise Operators)
其運算是將兩個運算元做相對應逐一位元的邏輯運算,假若兩個運算元的長度不一,則較短的那個運算元所欠缺的相對位元部分就補 0 。
#### 化簡運算子 (Reduction Operators)
化簡運算子與位元運算子相同,但是只有一個運算元,其運算是針對單一運算元的所有位元做邏輯上的運算,並輸出一個位元的結果。不同的是他是針對單一運算元從右到左逐一位元的做運算,其中反及、反或、反互斥或是由及、或與互斥或的運算結果取邏輯反運算得到的。
#### 條件運算子 (Conditional Operators)
條件運算子在資料處理模型中,經常用來描述條件指定 (Conditional Assignments) ,其中條件運算式就像是一個開關的控制。
條件運算子也可以用在巢狀的架構。
```verilog=
assign out = (A == 3) ? (control ? x : y) : (control ? m : n);
```
## 行為模型
### 結構化程序 (Structured Procedures)
在 Verilog 中有兩個結構化程序 : always 和 initial 這兩個敘述 (Statement) ,在行為模型朱是最基本的敘述,其他行為模式敘述皆必須在這兩個敘述之中。 Verilog 是一個並行程式語言 (Concurrent Programming Language) 而不像 C 語言的本質是循序的,在 Verilog 中,執行流 (Activity Flows) 是並行執行的,而每一個 always 和 initial 敘述代表一個執行流,其皆起始於模擬時間零,且不能有巢狀結構。
#### initial 敘述
一個 initial 區塊啟動於模擬時間零,且執行一次。如果有多個 initial 區塊,將同時啟動於模擬時間零,但各自結束執行。
initial 區塊通常是用在初始化,監控邏輯值的變化,顯示波型與其他需要在整個模擬中,只執行一次的動作。
#### always 敘述
always 區塊起始於模擬時間零,然後以迴圈的形式持續重複執行。若 always 敘述是用來描述一個持續重複工作的數位邏輯電路,如時脈產生器。
### 程序指定 (Procedural Assignment)
程序指定可用來更新暫存器 (reg) 、整數 (integer) 、實數 (real) 或時間 (time) 變數的值,變數上被指定的值,將被保持到被新的程序指定更新為止,這與持續指定 (Continuous Assignment) 是大不相同的。
#### 阻礙指定 (Blocking Assignments)
阻礙指定敘述會依照其在循環區塊中的位置,依序執行。平行區塊中的阻礙指定,並不會互相限制。阻礙指定的運算符號是 `=` 。
在指定敘述裡,如果等號右邊的位元數,較等號左邊的暫存變數的位元數要多時,較低位元的部分會被存取,多出的較高位元則被捨棄。反之若暫存變數位元數較多,則多出的較高位元數補零。
#### 無阻礙指定 (Nonblocking Assignment)
在循序區塊裡一個無阻礙指定,能安排執行順序不受敘述位置前後的影響,其運算符號為 `<=` 。以以下為例 :
```verilog=
reg x, y, z;
reg [15:0] reg_a, reg_b;
integer count;
// 所有行為模型敘述,必須在 initial 或 always 區塊。
initial
begin
x = 0; y = 1; z = 1; // 純量指定
count = 0; // 指定至整數變數
reg_a = 16'b0; reg_b = reg_a; // 設定向量初始
reg_a[2] <= #15 1'b1; // 在延遲後選擇位元指定
reg_b[15:13] <= #10 {x, y, z}; // 指定連結的結果,至向量部分指定。
count <= count + 1; // 指定至一個整數 (遞增)
end
```
上面的範例中從 `x = 0` 到 `reg_b = reg_a` 是在模擬時間零執行。接著 :
1. `reg_a[2] = 1` 是在第 15 個模擬時間單位執行。
2. `reg_b[15:13] = {x, y, z}` 是在第 10 個模擬時間單位執行。
3. `count = count + 1` 是在第 0 個模擬時間單位執行。
由上述範例可知,模擬器對於數個無阻礙指定敘述的執行時間排程,並不受同一區塊的其他阻礙指定敘述的影響。
:::warning
在實際電路設計的時候,同一區塊混用了阻礙指定與無阻礙指定是相當不好的做法。
:::
* 無阻礙指定的運用
無阻礙指定敘述在描述數位電路設計的行為相當重要,無阻礙指定是運用在共同事件驅動下,數個同時資料轉換發生的情形。以以下為例 :
```verilog=
always @(posedge clock)
begin
reg1 <= #1 in1;
reg2 <= @(negedge clock) in2 ^ in3;
reg3 <= #1 reg1; // reg1 舊的值
end
```
上述的例子中,在 clock 正緣觸發後便會讀取等號右邊變數 in1 、 in2 、 in3 與 reg1 之值,並將結果存在模擬器內部。接著依照排定的時間延遲寫入等號左邊變數。因此,即使 reg1 被指定新值的敘述先被執行, reg3 仍會被指定為 reg1 的舊值。
因此 reg1 、 reg2 、 reg3 最後的值,與各個敘述的先後次序無關。
:::info
在數位設計中,在一個共同的事件下,有多個資料同時轉換時,大多使用無阻礙指定,因阻礙指定可能執行的先後次序造成競爭情況。
:::
### 時序控制 (Timing Controls)
在 Verilog 中有數種時序控制的結構可供使用,時序控制主要是設定某一程序敘述在特定時間被執行。共有三種時序控制的方法可使用 : 延遲基礎時序控制、事件基礎時序控制和位準感測時序控制。
#### 延遲基礎時序控制 (Delay-Based Timing Contorl)
* 正規延遲控制 (Regular Delay Control)
在延遲時間非零的時候,使用在程序指定敘述的左邊。
```verilog=
// 定義參數
parameter latency = 20;
parameter delta = 2;
// 定義暫存器變數
reg x, y, z, p, q;
initial
begin
x = 0; // 無延遲
#10 y = 1; // 使用數值控制延遲,延遲執行 y = 1 10 個時間單位
#latency z = 0; // 使用識別字控制延遲,延遲 20 個時間單位
# (latency + delta) p = 1; // 使用一個運算式控制延遲
#y x = x + 1; // 使用識別字控制延遲,從變數 y 取得延遲值
# (4:5:6) q = 0; // 最大、典型、最小延遲值
end
```
* 指定內部延遲控制 (Intra-Assignment Delay Control)
不同於正規延遲控制,指定內部延遲控制是用在指定程序中,指定運算子 (Assignment Operator) 右邊的延遲時間。這種延遲敘述改變了執行流程 (Flow of Activity) 。
```verilog=
// 定義暫存器變數
reg x, y, z;
// 指定內部延遲
initial
begin
x = 0; z = 0;
y = #5 x + z; // 在 time = 0 讀取 x 和 z 的值並執行 x + z ,直到時間單位 5 在指定至 y 。
end
// 使用暫時變數與正規延遲得到相同的結果
initial
begin
x = 0; z = 0;
temp_xz = x + z;
#5 y = temp_xz; // 在目前的時間讀取 x + z 的值,並儲存至暫時變數。即使時間 0 至 5 間 x 和 z 的值改變了,也不影響單位時間 5 被指定至 y 的值。
end
```
:::info
這兩種延遲控制的不同,在於正規延遲控制延緩整個指定執行,而指定內部延遲控制為先算好指定之等號右邊的運算,延緩把這運算結果特定的時間量,再放至左邊變數。
:::
* 零延遲控制 (Zero delay control)
不同的 always-initial 區塊程序敘述,可能會排在同一個時間點執行,這些在不同區塊程序敘述的實際執行順序是不可確定的。零延遲控制可以確定程序敘述在其他敘述執行完之後才執行。這是為了要消除競爭情況,但是如果有多個零延遲控制敘述,這些敘述的順序仍然是不可確定的。
```verilog=
initial
begin
x = 0;
y = 0;
end
initial
begin
#0 x = 1; // 零延遲控制
#0 y = 1;
end
```
### 事件基礎時序控制 (Event-Based Timing Control)
#### 正規事件控制
事件控制的符號是 @ ,當信號產生正緣、負緣的轉換,或著數值的改變時,敘述會被執行。以下為範例 :
```verilog=
@(clock) q = d; // 當 clock 的值改變, q = d 被執行。
@(posedge clock) q = d; // 當 clock 正緣觸發, q = d 被執行。
@(negedge clock) q = d; // 當 clock 負緣觸發, q = d 被執行。
q = @(posedge clock) d; // d 立刻被執行,等到 clock 正緣觸發在指定至 q 。
```
:::info
在 Verilog 中,所謂的正緣觸發為訊號從 0 到 (x, z, 1) 或是從 (x, z) 到 1。
所謂的負緣觸發為訊號從 1 到 (0, x, z) 或是從 (x, z) 到 0 。
:::
#### 命名事件控制
Verilog 可以宣告一個事件,然後觸發和識別這個事件,事件並不會包含任何資料。一個被命名的事件是用關鍵字 event 來宣告,並使用符號 `->` 來觸發,一個觸發的事件是用 @ 來識別。
```verilog=
event received_data; // 定義一個事件 recieved_data
always @(posedge clock) // 在正緣觸發時作檢查
begin
if (last_data_packet)
-> received_data; // 觸發事件 recieved_data
end
always @(received_data) // 直到 received_data 事件被觸發
data_buf = {data_pkt[0], data_pkt[01], data_pkt[2], data_pkt[3]};
```
#### 事件或控制
當有多個訊號或事件中,任一個都能觸發某個敘述或多敘述區塊執行,就好像這些訊號或事件做或 (OR) 的邏輯運算。
```verilog=
always @(reset or clock or d) // 等待 reset, clock 或 d 的數值改變
begin
if (reset)
q = 1'b0;
else if (clock)
q = d;
end
```
除了可以使用 or 這個運算子,也可以利用逗號 `,` 運算子來達到這功能。
:::info
由於觸發的訊號很多時,很容易造成遺漏。因此 Verilog 提供了兩組萬用字元的描述方式 : `@*` 或是 `@(*)` ,來代表所有可能的觸發訊號。
:::
### 位準感測時序控制 (Level-Sensitive Timing Control)
Verilog 也允許位準感測時序控制,亦即等到某一個條件變成真 (true) 值,才執行某個敘述或區塊。
```verilog=
always
wait (count_enable) #20 count = count + 1;
```
以上述為例,若 count_enable 為 0 ,這個敘述將不會被執行。如果是 1 ,敘述將在 20 個單位時間後執行。若 count_enable 停在 1 的位準,則每隔 20 個時間, count 都會加 1 。
### 多路徑分支 (Multiway Branching)
#### case 敘述 (case Statements)
關鍵字為 case 、 endcase 、 default ,其中 default 的敘述是非必要的 (Optional) ,並且 case 敘述 是一個位元一個位元去比較 0 , 1 , x 和 z 。
```verilog=
module demultiplexer1_to_4 (out0, out1, out2, out3, in, s1, s0);
// 宣告輸出入埠
output out0, out1, out2, out3;
reg out0, out1, out2, out3;
input in;
input s1, s0;
always @(s1 or s0 or in)
case ({s1, s0}) // 依控制訊號選擇開關位置。
2'b00: begin out0 = in; out1 = 1'bz; out2 = 1'bz; out3 = 1'bz;
end
2'b01: begin out0 = 1'bz; out1 = in; out2 = 1'bz; out3 = 1'bz;
end
2'b10: begin out0 = 1'bz; out1 = 1'bz; out2 = in; out3 = 1'bz;
end
2'b11: begin out0 = 1'bz; out1 = 1'bz; out2 = 1'bz; out3 = in;
end
// 如果選擇訊號有 x 則輸出 x ,
// 如果選擇訊號有 z 則輸出 z ,
// 如果僅有一個 x ,其他皆為 z ,則 x 有較高的優先順序。
2'bx0, 2'bx1, 2'bxz, 2'bxx, 2'b0x, 2'b1x, 2'bzx:
begin
out0 = 1'bx; out1 = 1'bx; out2 = 1'bx; out3 = 1'bx;
end
2'bz0, 2'bz1, 2'bzz, 2'b0z, 2'b1z:
begin
out0 = 1'bz; out1 = 1'bz; out2 = 1'bz; out3 = 1'bz;
end
default: $display("Unspecified control signals");
endcase
endmodule
```
在上述的例子中,有多個輸入的組合如 2'bz0 、 2'bz1 、 2'bzz 、 2'b0z 和 2'b1z 都對應到同一個敘述或區塊,則只需要用逗點 `,` 來分隔。
#### 關鍵字 casex, casez
在 casez 中,不管是在條件運算式或選擇中,所有的 z 值就像隨意值一樣,可用 ? 替代之。在 casex 中,對所有 x 與 z 皆視為隨意值。
因此在 casex 和 casez 中,僅比較非 x 或 z 位置的值。
```verilog=
reg [3:0] encoding;
integer state;
casex (encoding) // x 表示隨意 (don't care) 值
4'b1xxx : next_state = 3;
4'bx1xx : next_state = 2;
4'bxx1x : next_state = 1;
4'bxxx1 : next_state = 0;
default : next_state = 0;
endcase
```
在上述的例子中,若 encoding = 4'b10xz 將使 next_state = 3 被執行。
### 迴圈 (Loops)
在 Verilog 中有四種迴圈敘述 : while 、 for 、 repeat 和 forever ,並且所有的迴圈皆僅能在 initial 或 always 的區塊中,其能包含延遲的敘述。
#### for 迴圈
for 迴圈可以用來初始化一個陣列或記憶體。並且 for 迴圈一般使用在有固定起始和結束的迴圈,如果迴圈式在某條件下的簡單迴圈,最好使用 while 迴圈。
#### repeat 迴圈
其關鍵字為 repeat ,其功用為指定一個固定次數的迴圈。 repeat 迴圈必須帶一個數值,可以是一個常數、變數或訊號值,而不能是一個邏輯表示式。若其為變數或訊號值時,僅在迴圈剛開始時才被讀取,在迴圈執行期間並不會再去讀取。
#### forever 迴圈
其關鍵字為 forever , forever 迴圈並未包含任何條件運算式 (Expression) ,
會一直執行到遇到 $finish 。
### 循序和平行區塊 (Sequential and Parallel Blocks)
#### 區塊型態
有兩種型態的區塊 : 循序區塊和平行區塊。
* 循序區塊
其關鍵字為 begin 和 end ,具有以下特性 :
1. 每一個敘述會依照其順序執行,且要等到前一個敘述執行完才能開始執行 (除了包含指定內部延遲控制的無阻礙指定) 。
2. 每一個相對延遲控制或事件控制的敘述,其需要等到前一個敘述執行完成,再依相對延遲時間或事件執行。
* 平行區塊
其關鍵字為 fork 和 join ,具以下特性 :
1. 在平行區塊中的所有敘述會同時執行。
2. 在平行區塊中,敘述的執行順序是依照延遲控制與事件控制。
3. 所有時序控制與事件驅動的時間,都為相對於進入區塊的模擬時間。
:::info
所有在平行區塊中的敘述皆執行於區塊啟動時,因此敘述在區塊中的順序是不重要的。
:::
以下為範例 :
```verilog=
// 範例 1 : 包含延遲的平行區塊
reg x, y;
reg [1:0] z, w;
initial
fork
x = 1'b0; // 完成執行在模擬時間 0
#5 y = 1'b1; // 完成執行在模擬時間 5
#10 z = {x, y}; // 完成執行在模擬時間 10
#20 w = {y, x}; // 完成執行在模擬時間 20
join
```
在平行區塊中,若在同一個時間有兩個敘述影響到同一個變數,會有產生競爭情況的可能。其中以下方的例子,因為 x 與 y 的賦值不一定會優先於 z 與 w 的賦值,因此 z 與 w 的值也有可能皆為 `2'bxx` ,一切皆已模擬器的設計為主。
#### 區塊的特殊特性 (Special Features of Blocks)
區塊敘述的特殊功能主要有三種 : 巢狀區塊 、 命名區塊與禁能命名區塊。
* 巢狀區塊 (Nested blocks)
區塊可以巢狀使用。循序區塊和並行區塊是可以合在一起的。
```verilog=
// 巢狀區塊
initial
begin
x = 1'b0;
fork
#5 y = 1'b1;
#10 z = {x, y};
join
#20 w = {y, x};
end
```
* 命名區塊 (Named blocks)
區塊是可以命名的 :
1. 命名區塊中可以宣告區域變數。
2. 命名區塊是設計階層的一部分,在命名區塊中的變數可用階層化命名參照存取 (Hierarchical Naming Reference) 。
3. 可以禁能一個命名區塊,也就是停止這區塊的執行。
```verilog=
// 命名區塊
module top;
initial
begin: block1 // 循序區塊命名為 block1
integer i; // 整數 i 是區塊中 block1 的靜態區塊區域變數
...
end
initial
fork: block2 // 平行區塊命名為 block2
reg i; // 暫存器 i 是 block2 的靜態區域變數
...
join
endmodule
```
* 禁能命名區塊 (Disabling Named Blocks)
關鍵字 disable 提供一個方法來結束一個命名區塊的執行。其功能與 C 語言中的 break 敘述類似,不同的是 break 僅能中斷正在執行的迴圈,而 disable 可以停止命名區塊的執行。
```verilog=
// 說明 : 尋找 flag (向量變數) 中第一個 1 的位元
reg [15:0] flag;
integer i; // 整數持續計數
initial
begin
flag = 16'b 0010_0000_0000_0000;
i = 0;
begin : block1 // 將 while 迴圈內的主要區塊命名為 block1
while (i < 16)
begin
if (flag[i])
begin
$display("Encountered a TRUE bit at element number %d", i);
disable block1; // 因發現 "1" 位元所以禁能 block1
end
i = i + 1;
end
end
end
```
#### 產生區塊 (Generate Blocks)
這一節中,我們將介紹如何利用產生區塊的方式,在模擬開始之前動態產生所需的區塊。這樣的產生方式,很適合開發參數化的設計模型,針對多位元的訊號所需的重複動作,可以產生出對應的模組。產生描述由關鍵字 generate 開始,到 endgenerate 結束。
* 迴圈化產生 (Generate Loop)
一個迴圈化產生可以在一個迴圈內初始化數個底下列出的資料型態 :
1. 變數定義
2. 模組
3. 使用者自訂原生邏輯閘,或是基本邏輯閘
4. 連續指定
5. initial 與 always 區塊
以下為範例 :
```verilog=
// 將兩個 N 位元的匯流排資料做互斥運算
module bitwise_xor(out, i0, i1);
// 參數定義,可以隨後修改
parameter N = 32; // 32 位元匯流排
// 宣告輸出入埠
output [N-1:0] out;
input [N-1:0] i0, i1;
// 宣告一個暫存的變數,用來做產生的指標,並不會在模擬過程中真正被使用到
genvar j;
// 產生逐位元的 xor 運算
generate for (j = 0; j < N; j = j + 1) begin: xor_loop
xor g1 (out[j], i0[j], i1[j]);
end // 結束迴圈化產生
endgenerate
endmodule
```
* 條件化產生 (Generate Conditional)
```verilog=
// 參數化乘法器
module multiplier (product, a0, a1);
// 宣告輸入向量寬度,可以被重新定義
parameter a0_width = 8; // 八位元
parameter a1_width = 8; // 八位元
// 區域參數宣告
// 這部分的參數,不可以被 defparam 來重新定義
localparam product_width = a0_width + a1_width;
// 宣告輸出入埠
output [product_width-1:0] product;
input [a0_width-1:0] a0;
input [a1_width-1:0] a1;
// 依照位元寬度來決定使用哪一種乘法器
generate
if (a0_width < 8) || (a1_width < 8)
cla_multiplier #(a0_width, a1_width) m0(product, a0, a1);
else
tree_multiplier #(a0_width, a1_width) m0(product, a0, a1);
endgenerate
endmodule
```
* 組合條件化產生 (Generate Case)
```verilog=
// N 位元加法器
module adder(co, sum, a0, a1, ci);
// 參數宣告,可以重新定義
parameter N = 4; // 四位元
// 宣告輸出入埠
output [N-1:0] sum;
output co;
input [N-1:0] a0, a1;
input ci;
// 依照輸入向量的位元寬度來決定該使用哪一種加法器
generate
case (N)
// Special cases for 1 and 2 bit adders
1: adder_1bit adder1(c0, sum, a0, a1, ci); // 一位元的加法器
2: adder_2bit adder2(c0, sum, a0, a1, ci); // 二位元的加法器
// 如果都不是,就採用 N 位元進位預算式加法器
default: adder_cla #(N) adder3(c0, sum, a0, a1, ci);
endcase
endgenerate
endmodule
```
## 任務與函數
### 任務與函數的不同之處
| 函數 | 任務 |
|:-------------------------------------------------------:|:-----------------------------------------------------------:|
| 一個函數可以引用其他的函數,但不能引用其他的任務 | 一個任務可以引用其他的任務與函數 |
| 函數永遠在時間等於零的時候開始執行 | 任務可以不從時間等於零的時候開始執行 |
| 函數不能包含有延遲、事件或是控制時間的任何陳述 | 任務可以包含有延遲、事件與時間控制的陳述 |
| 函數至少要有一個 input 的宣告 | 任務可以擁有零個或是更多個 input 、 output 或是 inout |
| 函數永遠回傳單一個值,並且不能有 output 與 inout 的宣告 | 任務並沒有傳回的值,但可以藉由 output 、 inout 將值輸出出來 |
### 任務
以關鍵字 task 與 endtask 宣告,適用於以下情況 :
1. 需要用到延遲、時間或是事件控制指令的時候。
2. 需要有零個或是多於一個輸出的時候。
3. 沒有輸入的時候。
* 以下為範例 :
```verilog
module operation;
...
always @(A or B)
begin
bitwise(AB_AND, AB_OR, AB_XOR, A, B);
end
...
task bitwise_oper;
output [15:0] ab_and, ab_or, ab_xor;
input [15:0] a, b;
begin
#delay ab_and = a & b;
ab_or = a | b;
ab_xor = a ^ b;
end
endtask
endmodule
```
* 以下為沒有輸入的範例 :
```verilog=
module sequence;
...
regclock;
...
initial
init_sequence; // 呼叫任務 init_sequence
...
always
begin
asymmetric_sequence; // 呼叫任務 asymmetric_sequence
end
...
...
task init_sequence;
begin
clock = 1'b0;
end
endtask
task asymmetric_sequence;
begin
#12 clock = 1'b0;
#5 clock = 1'b1;
#3 clock = 1'b0;
#10 clock = 1'b1;
end
endtask
endmodule
```
### 函數 (Functions)
使用關鍵字 function 與 endfunction,適用於以下情況 :
1. 程序傳回一個值時。
2. 至少有一個傳入參數的時候。
3. 沒有輸出或是輸出入埠。
4. 沒有阻礙程序
在 Verilog 中,當一個函數宣告的時候, Verilog 同時也內定的宣告了一個以函數的名稱為名的暫存器,當函數執行完畢,函數的輸出則經由這個暫存器傳回到我們呼叫函數的地方。需要注意的是,函數至少要有一個輸入,且沒有輸出,因為回傳值是透過內定宣告的暫存器來傳回,還有在函數中不可以呼叫其他的任務,但是可以呼叫其他的函數來使用。
* 以下為範例 :
```verilog=
module parity;
...
reg [31:0] addr;
reg parity;
always @(addr)
begin
parity = calc_parity(addr);
$display("Parity calculated = %b", calc_parity(addr));
end
...
function calc_parity;
input [31:0] address;
begin
calc_parity = ^address;
end
endfunction
...
endmodule
```
## 有用的程式技巧
### 程序持續指定 (Prodedural Continuous Assignments)
程序持續指定可以使用有限週期的指定方式,指定數值到暫存器或著線路,程序持續指定複寫 (override) 任一存在之指定。
#### assign 和 deassign
使用關鍵字 assign 與 deassign ,一般運用在週期控制。
```verilog=
// 非同步設定負緣觸發 D 型正反器
module edge_dff(q, qbar, d, clk, reset);
// 輸出輸入
output q, qbar;
input d, clk, reset;
reg q, qbar; // 宣告 q 和 qbar 是暫存器變數
always @(negedge clk) // 在 clock 負緣指定 q 和 qbar 的值
begin
q = d;
qbar = ~d;
end
always @(reset) // 當 reset 為高電位使用程序持續指定複寫 q 和 qbar
if (reset)
begin // 如果 reset 為高電位,使用程序持續指定複寫 q 和 qbar
assign q = 1'b0;
assign qbar = 1'b1;
end
else
begin // 如果 reset 為低電位,移除複寫
// 在移除複寫後,q = d and qbar = ~d 將要等到下一個 clock 負緣才能更改暫存器變數的值
deassign q;
deassign qbar;
end
endmodule
```
在上述的範例中,當 reset 信號為 high 時,q 和 qbar 的指定被複寫 (override) 一指定新值。且這暫存器變數在 deassign 後,直到再一次程序指定才會變更內值。
#### force 和 release
使用關鍵字 force 和 release ,可以複寫在暫存器和接點。建議 force 和 release 不要使用在設計區塊中,最好僅使用在模擬和除錯中。
* 暫存器的 force 和 release
force 一個暫存器,則不管任何一種的程序指定或程序持續指定,這一個暫存器將被複寫,直到 release 為止。以下為範例 :
```verilog=
module stimulus
...
...
// 取別名 D 型正反器
edge_dff dff(Q, Qbar, D, CLK, RESET);
...
...
initial
begin
// 在模擬時間 50 至 100 強制 dff.q 的值
#50 force dff.q = 1'b1; // 強制 q 的值為 1 在模擬時間 50
#50 release dff.q; // 解強制 q 的值在模擬時間 100
end
...
...
endmodule
```
* 接點的 force 和 release
force 一個接點,則任何程序持續指定將被複寫,直到 release 為止,其可為表示式或數值。當接點被 release 時,馬上還原為原來的驅動值。以下為範例 :
```verilog=
module top;
...
.,..
assign out = a & b & c; // 持續指定 out
...
initial
#50 force out = a | b & c;
#50 release out;
end
...
...
endmodule
```
在上述的例子中,當這個 force 敘述被啟動時,不管是 a 、 b 或 c 有改變,這一個表示式皆會重算。
### 複寫參數 (Overriding Parameters)
#### 模組別名參數指定
若在一個模組中有多個參數,只要依照參數的順序指定,就可以在此模組中給定這些參數新的數值。若未指明覆蓋的數值,則使用預設的參數值。以下為範例 :
```verilog=
// 定義包含延遲的模組
module bus_master;
parameter var1 = 2;
parameter var2 = 3;
parameter var3 = 7;
...
// <模組內部>
...
endmodule
// 最高層次模組,取兩個 bus_master 模組的別名
module top;
// 取具有新的延遲數值的模組別名
bus_master #(4, 5, 6) b1(); // b1: var1 = 4, var2 = 5, var3 = 6
bus_master #(9, 4) b2(); // b2: var1 = 9, var2 = 4, var3 = 7 (default)
// 依照列表順序指定參數值
bus_master #(.var2(4), var3(7)) b3(); // b3: var2 = 4, var3 = 7, var1 = 2 (default)
endmodule
```
在上述的例子中,建議使用指定名稱的方式設定參數值,如此可以將錯誤的機會減到最少,而且增加或減少參數的時候,可以不需擔心改變他們的順序。
### 有條件的編譯與執行 (Conditional Compilation and Execution)
#### 有條件的編譯
有條件的編譯是由編譯指令 (compiler directives) : `` `ifdef`` 、 `` `ifndef`` 、 `` `else`` 、 `` `elsif`` 、 `` `endif`` 來完成。
在 Verilog 檔案中可以使用 `` `define`` 敘述,來設定條件編譯的旗標。需要注意的是, `` `ifdef`` 敘述中不支援布林運算式,例如 `TEST && ADD_B2` 。
#### 有條件的執行
在系統任務中用來處理條件執行的關鍵字為 `$test$plusargs` ,以下為範例 :
```verilog=
// 條件執行
module test;
...
...
initial
begin
if($test$plusargs("DISPLAY_VAR"))
$display("Display = %b", {a, b, c}); // 如果 flag 被設定則顯示
else
$display("No Display"); // 反之,則不顯示
end
endmodule
```
如果執行的時候設定了 DISPLAY_VAR 旗標,則這些變數將會被顯示出來。旗標設定的方法,是在執行的時候,指定執行選項 `+DISPLAY_VAR` 。條件執行可以由系統任務 `$value$plusargs` ,做更進一步的控制。這個系統任務允許測試旗標的設定,是否帶數值。如果找不到吻合的旗標,`$value$plusargs` 傳回 0 ,若找到吻合的旗標則傳回非零的值。以下說明 `$value$plusargs` 的用法 :
```verilog=
// 使用 $value$plusargs 的條件執行
module test;
reg [8*128-1:0] test_string;
integer clk_period;
...
...
initial
begin
if($value$plusargs("testname=%s", test_string))
$readmemh(test_string, vectors); // 讀取測試向量
else
// 否則顯示錯誤訊息
$display("Test name option not specified");
if($value$plusargs("clk_t=%d", clk_period))
forever #(clk_period/2) clk = ~clk; // 設定時脈
else
// 否則顯示錯誤訊息
$display("Clock period option name not specified");
end
// 在這個例子中,若要利用上述的選項,可以在執行模擬器的時候加上下列選項。
// +testname=test1.vec +clk_t=10
// 如此測試檔名 = "test1.vec" 而且 clk+period = 10
endmodule
```
### 時間刻度 (Time Scales)
語法 : `` `timescale<reference_time_unit>/<time_precision>``
其中 <reference_time_unit> 是時間與延遲的單位大小, <time_precision> 則指定其精確度。只有 1 、 10 、 100 是其合法的整數值。
### 檔案輸出 (File Output)
系統任務 `$fopen` 可開啟一個檔案。
任務 `$fopen` 將傳回 32 位元的值叫做多通道描述符號 (multichannel descriptor) ,且在多通道描述符號中僅有一個位元會被設定。標準輸出的多通道描述符號的最末一個位元被設定 (bit 0) ,所以,標準輸出叫做通道 0 ,$fopen 所新開的通道,依其傳回多通道描述符號,依序位元 1 、 位元 2 ... 至位元 31 被設定,分別為通道 1 、 通道 2 ... 、通道 31 。
```verilog=
// 多通道描述符號
integer handle1, handle2, handle3; // integers 是 32-bit 值
// 標準輸出開啟時, descriptor = 32'h0000_0001 (bit 0 set)。
initial
begin
handle1 = $fopen("file1.out"); // handle1 = 32'h0000_0002 (bit 1 set)
handle2 = $fopen("file2.out"); // handle2 = 32'h0000_0004 (bit 2 set)
handle3 = $fopen("file3.out"); // handle3 = 32'h0000_0008 (bit 3 set)
end
```
#### 寫至檔案 (Writing to file)
系統任務 `$fdisplay` 、 `$monitor` 是用來寫至檔案的。
語法 : `$fdisplay(<file_descriptor>, p1, p2..., pn);`
`$fmonitor(<file_descriptor>, p1, p2..., pn);`
p1 、 p2 、 ... 、 pn 可以是變數、信號名,或引號內的字串。file_descriptor 是一個多通道描述符號, Verilog 將輸出寫至那些相對被設置為 1 的檔案。
```verilog=
// 寫至檔案
integer desc1, desc2, desc3; // 三個多通道描述符號
initial
begin
desc1 = handle1 | 1; // desc1 = 32'h0000_0003;
$fdisplay(desc1, "Display 1"); // 寫至檔案 file1.out 和標準輸出
desc2 = handle2 | handle1; // desc2 = 32'h0000_0006;
$fdisplay(desc2, "Display 2"); // 寫至檔案 file1.out 和 file2.out
desc3 = handle3; // desc3 = 32'h0000_0008;
$fdisplay(desc3, "Display 3"); // 寫至檔案 file3.out
```
#### 關閉檔案 (Closing files)
語法 : `$fclose(handle1);`