# 4. Verilog 過程結構
[參考網址:Verilog 中文詳細教程](https://www.runoob.com/w3cnote/verilog-tutorial.html)
###### tags: `verilog` `IC 設計`
---
[toc]
---
**關鍵詞:initial, always**
過程結構語句有 2 種,initial 與 always 語句。它們是行為級建模的 2 種基本語句。 一個模塊中可以包含多個 initial 和 always 語句,但 2 種語句不能嵌套使用。 這些語句在模塊間並行執行,與其在模塊的前後順序沒有關係。 但是 initial 語句或 always 語句內部可以理解為是順序執行的(非阻塞賦值除外)。 每個 initial 語句或 always 語句都會產生一個獨立的控制流,執行時間都是從 0 時刻開始。
### :pushpin: initial語句
initial 語句從 0 時刻開始執行,只執行一次,多個 initial 塊之間是相互獨立的。 如果 initial 塊內包含多個語句,需要使用關鍵字 begin 和 end 組成一個塊語句。 如果 initial 塊內只要一條語句,關鍵字 begin 和 end 可使用也可不使用。 initial 理論上來講是不可綜合的,多用於初始化、信號檢測等。 對上一節代碼稍作修改,進行仿真,代碼如下。
```verilog=
`timescale 1ns/1ns
module test ;
reg ai, bi ;
initial begin
ai = 0 ;
#25 ; ai = 1 ;
#35 ; ai = 0 ; //absolute 60ns
#40 ; ai = 1 ; //absolute 100ns
#10 ; ai = 0 ; //absolute 110ns
end
initial begin
bi = 1 ;
#70 ; bi = 0 ; //absolute 70ns
#20 ; bi = 1 ; //absolute 90ns
end
//at proper time stop the simulation
initial begin
forever begin
#100;
//$display("---gyc---%d", $time);
if ($time >= 1000) begin
$finish ;
end
end
end
endmodule
```
仿真結果如下:
可以看出,2 個 initial 進程語句分別給信號 ai,bi 賦值時,相互間並沒有影響。 信號 ai,bi 的值按照賦值順序依次改變,所以 initial 內部語句也可以看做是順序執行。

### :pushpin: always 語句
與 initial 語句相反,always 語句是重複執行的。 always 語句塊從 0 時刻開始執行其中的行為語句;當執行完最後一條語句後,便再次執行語句塊中的第一條語句,如此循環反复。 由於循環執行的特點,always 語句多用於仿真時鐘的產生,信號行為的檢測等。 下面用 always 產生一個 100MHz 時鐘源,並在 1010ns 時停止仿真代碼如下。 代碼如下:
```verilog=
`timescale 1ns/1ns
module test ;
parameter CLK_FREQ = 100 ; //100MHz
parameter CLK_CYCLE = 1e9 / (CLK_FREQ * 1e6) ; //switch to ns
reg clk ;
initial clk = 1'b0 ; //clk is initialized to "0"
always # (CLK_CYCLE/2) clk = ~clk ; //generating a real clock by reversing
always begin
#10;
if ($time >= 1000) begin
$finish ;
end
end
endmodule
```
仿真結果如下: 可見,時鐘週期是我們想要得到的 100MHz。而且仿真在 1010ns 時停止。

整體verilog 源碼:
```verilog=
`timescale 1ns/1ns
module test ;
reg ai, bi ;
initial begin
ai = 0 ;
#25 ; ai = 1 ;
#35 ; ai = 0 ; //absolute 60ns
#40 ; ai = 1 ; //absolute 100ns
#10 ; ai = 0 ; //absolute 110ns
end
initial begin
bi = 1 ;
#70 ; bi = 0 ; //absolute 70ns
#20 ; bi = 1 ; //absolute 90ns
end
/*
initial begin
forever begin
#100;
//$display("---gyc---%d", $time);
if ($time >= 1000) begin
$finish ;
end
end
end
*/
parameter CLK_FREQ = 100 ; //100MHz
parameter CLK_CYCLE = 1e9 / (CLK_FREQ * 1e6) ; //switch to ns
reg clk ;
initial clk = 1'b0 ; //clk is initialized to "0"
always # (CLK_CYCLE/2) clk = ~clk ; //always reversing, and generating a real clock
always begin
#10;
if ($time >= 110) begin
$finish ;
end
end
endmodule
```
# 4.2 Verilog 過程賦值
**阻塞賦值,非阻塞賦值,並行**
過程性賦值是在 initial 或 always 語句塊裡的賦值,賦值對像是寄存器、整數、實數等類型。 這些變量在被賦值後,其值將保持不變,直到重新被賦予新值。 連續性賦值總是處於激活狀態,任何操作數的改變都會影響表達式的結果;過程賦值只有在語句執行的時候,才會起作用。這是連續性賦值與過程性賦值的區別。 Verilog 過程賦值包括 2 種語句:阻塞賦值與非阻塞賦值。
### :pushpin: 阻塞賦值
阻塞賦值屬於順序執行,即下一條語句執行前,當前語句一定會執行完畢。 阻塞賦值語句使用等號 **=** 作為賦值符。 前面的仿真中,initial 裡面的賦值語句都是用的阻塞賦值。
### :pushpin: 非阻塞賦值
非阻塞賦值屬於並行執行語句,即下一條語句的執行和當前語句的執行是同時進行的,它不會阻塞位於同一個語句塊中後面語句的執行。 非阻塞賦值語句使用小於等於號 **<=** 作為賦值符。 利用下面代碼,對阻塞、非阻塞賦值進行仿真,來說明 2 種過程賦值的區別。
```verilog=
`timescale 1ns/1ns
module test ;
reg [3:0] ai, bi ;
reg [3:0] ai2, bi2 ;
reg [3:0] value_blk ;
reg [3:0] value_non ;
reg [3:0] value_non2 ;
initial begin
ai = 4'd1 ; //(1)
bi = 4'd2 ; //(2)
ai2 = 4'd7 ; //(3)
bi2 = 4'd8 ; //(4)
#20 ; //(5)
//non-block-assigment with block-assignment
ai = 4'd3 ; //(6)
bi = 4'd4 ; //(7)
value_blk = ai + bi ; //(8)
value_non <= ai + bi ; //(9)
//non-block-assigment itself
ai2 <= 4'd5 ; //(10)
bi2 <= 4'd6 ; //(11)
value_non2 <= ai2 + bi2 ; //(12)
end
//stop the simulation
always begin
#10 ;
if ($time >= 1000) $finish ;
end
endmodule
```
仿真結果如下:
- 語句(1)-(8)都是阻塞賦值,按照順序執行。
- 20ns 之前,信號 ai,bi 值改變。由於過程賦值的特點,value_blk = ai + bi 並沒有執行到,所以 20ns 之前,value_blk 值為 X(不確定狀態)。
- 20ns 之後,信號 ai,bi 值再次改變。執行到 value_blk = ai + bi,信號 value_blk 利用信號 ai,bi 的新值得到計算結果 7。
- 語句(9)-(12)都是非阻塞賦值,並行執行。
- 首先,(9)-(12)雖然都是並發執行,但是執行順序也是在(8)之後,所以信號 value_non = ai + bi 計算是也會使用信號 ai,bi 的新值,結果為 7。
- 其次,(10)-(12)是並發執行,所以 value_non2 = ai2 + bi2 計算時,並不關心信號 ai2,bi2 的最新非阻塞賦值結果。
- 即 value_non2 計算時使用的是信號 ai2,bi2 的舊值,結果為 4'hF。

### :pushpin: 使用非阻塞賦值避免競爭冒險
上述仿真代碼只是為了讓讀者更好的理解阻塞賦值與非阻塞賦值的區別。實際 Verilog 代碼設計時,切記不要在一個過程結構中混合使用阻塞賦值與非阻塞賦值。兩種賦值方式混用時,時序不容易控制,很容易得到意外的結果。 更多時候,在設計電路時,always 時序邏輯塊中多用非阻塞賦值,always 組合邏輯塊中多用阻塞賦值;在仿真電路時,initial 塊中一般多用阻塞賦值。
如下所示,為實現在時鐘上升沿交換 2 個寄存器值的功能,在 2 個 always 塊中使用阻塞賦值。 因為 2 個 always 塊中的語句是同時進行的,但是 a=b 與 b=a 是無法判定執行順序的,這就造成了競爭的局面。 但不管哪個先執行(和編譯器等有關係),不考慮 timing 問題時,他們執行順序總有先後,最後 a 與 b 的值總是相等的。沒有達到交換 2 個寄存器值的效果。
```verilog=
always @(posedge clk) begin
a = b ;
end
always @(posedge clk) begin
b = a;
end
```
但是,如果在 always 塊中使用非阻塞賦值,則可以避免上述競爭冒險的情況。 如下所示,2 個 always 塊中語句並行執行,賦值操作右端操作數使用的是上一個時鐘週期的舊值,此時 a<=b 與 b<=a 就可以相互不干擾的執行,達到交換寄存器值的目的。
```verilog=
always @(posedge clk) begin
a <= b ;
end
always @(posedge clk) begin
b <= a;
end
```
當然,利用下面代碼也可以實現交換寄存器值的功能,但是顯然不如在 always 塊中直接用非阻塞賦值簡單直觀。
```verilog=
always @(posedge clk) begin
temp = a ;
a = b ;
b = temp ;
end
```
# 4.3 Verilog 時序控制
**關鍵詞**:時延控制,事件觸發,邊沿觸發,電平觸發
Verilog 提供了 2 大類時序控制方法:時延控制和事件控制。事件控制主要分為邊沿觸發事件控制與電平敏感事件控制。
### :pushpin: 時延控制
基於時延的時序控制出現在表達式中,它指定了語句從開始執行到執行完畢之間的時間間隔。 時延可以是數字、標識符或者表達式。 根據在表達式中的位置差異,時延控制又可以分為常規時延與內嵌時延。
### :pushpin: 常規時延
遇到常規延時時,該語句需要等待一定時間,然後將計算結果賦值給目標信號。 格式為:#delay procedural_statement,例如:
```verilog=
reg value_test ;
reg value_general ;
#10 value_general = value_test ;
```
該時延方式的另一種寫法是直接將井號 # 獨立成一個時延執行語句,例如:
```verilog=
#10 ;
value_ single = value_test ;
```
### :pushpin: 內嵌時延
遇到內嵌延時時,該語句先將計算結果保存,然後等待一定的時間後賦值給目標信號。 內嵌時延控制加在賦值號之後。例如:
```verilog=
reg value_test ;
reg value_embed ;
value_embed = #10 value_test ;
```
需要說明的是,這 2 種時延控制方式的效果是有所不同的。
當延時語句的賦值符號右端是常量時,2 種時延控制都能達到相同的延時賦值效果。
當延時語句的賦值符號右端是變量時,2 種時延控制可能會產生不同的延時賦值效果。 例如下面仿真代碼:
```verilog=
`timescale 1ns/1ns
module test ;
reg value_test ;
reg value_general, value_embed, value_single ;
//signal source
initial begin
value_test = 0 ;
#25 ; value_test = 1 ;
#35 ; value_test = 0 ; //absolute 60ns
#40 ; value_test = 1 ; //absolute 100ns
#10 ; value_test = 0 ; //absolute 110ns
end
//(1)general delay control
initial begin
value_general = 1;
#10 value_general = value_test ; //10ns, value_test=0
#45 value_general = value_test ; //55ns, value_test=1
#30 value_general = value_test ; //85ns, value_test=0
#20 value_general = value_test ; //105ns, value_test=1
end
//(2)embedded delay control
initial begin
value_embed = 1;
value_embed = #10 value_test ; //0ns, value_test=0
value_embed = #45 value_test ; //10ns, value_test=0
value_embed = #30 value_test ; //55ns, value_test=1
value_embed = #20 value_test ; //85ns, value_test=0
end
//(3)single delay control
initial begin
value_single = 1;
#10 ;
value_single = value_test ; //10ns, value_test=0
#45 ;
value_single = value_test ; //55ns, value_test=1
#30 ;
value_single = value_test ; //85ns, value_test=0
#20 ;
value_single = value_test ; //105ns, value_test=1
end
always begin
#10;
if ($time >= 150) begin
$finish ;
end
end
endmodule
```
仿真結果如下,由圖可知:
(1)一般延時的兩種表達方式執行的結果都是一致的。
(2)一般時延賦值方式:遇到延遲語句後先延遲一定的時間,然後將當前操作數賦值給目標信號,並沒有"慣性延遲"的特點,不會漏掉相對較窄的脈衝。
(3)內嵌時延賦值方式:遇到延遲語句後,先計算出表達式右端的結果,然後再延遲一定的時間,賦值給目標信號。

下面分析下內嵌延時的賦值過程:
```verilog=
value_embed = #10 value_test ; //0ns, value_test=0
```
0ns 時,執行此延時語句。 先將 0 賦值給信號 value_embed, 延遲 10ns 輸出為 0;
```verilog=
value_embed = #45 value_test ; //10ns, value_test=0
```
10ns 時,執行此延時語句。 由於此時 value_test 仍然為 0,所以 value_embed 值不變。 即到 55ns 時,value_embed 值仍然保持為 0。
```verilog=
value_embed = #30 value_test ; //55ns, value_test=1
```
同理,55ns 時,value_test 值為 1,將其賦值給 value_embed 並延遲 30ns 輸出。 所以 85ns 時,value_embed 輸出為 1。
```verilog=
value_embed = #20 value_test ; //85ns, value_test=0
```
同理,105ns 時,value_embed 輸出為 0。
### :pushpin: 邊沿觸發事件控制
在 Verilog 中,事件是指某一個 reg 或 wire 型變量發生了值的變化。 基於事件觸發的時序控制又主要分為以下幾種。
**一般事件控制**
事件控制用符號 @ 表示。 語句執行的條件是信號的值發生特定的變化。 關鍵字 posedge 指信號發生邊沿正向跳變,negedge 指信號發生負向邊沿跳變,未指明跳變方向時,則 2 種情況的邊沿變化都會觸發相關事件。例如:
```verilog=
//信號clk只要發生變化,就執行q<=d,雙邊沿D觸發器模型
always @(clk) q <= d ;
//在信號clk上升沿時刻,執行q<=d,正邊沿D觸發器模型
always @(posedge clk) q <= d ;
//在信號clk下降沿時刻,執行q<=d,負邊沿D觸發器模型
always @(negedge clk) q <= d ;
//立刻計算d的值,並在clk上升沿時刻賦值給q,不推薦這種寫法
q = @(posedge clk) d ;
```
### :pushpin: 命名事件控制
用戶可以聲明 event(事件)類型的變量,並觸發該變量來識別該事件是否發生。命名事件用關鍵字 event 來聲明,觸發信號用 -> 表示。例如:
```verilog=
event start_receiving ;
always @( posedge clk_samp) begin
-> start_receiving ; //採樣時鐘上升沿作為時間觸發時刻
end
always @(start_receiving) begin
data_buf = {data_if[0], data_if[1]} ; //觸發時刻,對多維數據整合
end
```
### :pushpin: 敏感列表
當多個信號或事件中任意一個發生變化都能夠觸發語句的執行時,Verilog 中使用"或"表達式來描述這種情況,用關鍵字 or 連接多個事件或信號。這些事件或信號組成的列表稱為"敏感列表"。當然,or 也可以用逗號 , 來代替。例如:
```verilog=
//帶有低有效復位端的D觸發器模型
always @(posedge clk or negedge rstn) begin
//always @(posedge clk , negedge rstn) begin
//也可以使用逗號陳列多個事件觸發
if(! rstn)begin
q <= 1'b ;
end
else begin
q <= d ;
end
end
```
當組合邏輯輸入變量很多時,那麼編寫敏感列表會很繁瑣。此時,更為簡潔的寫法是 @* 或 @(*),表示對語句塊中的所有輸入變量的變化都是敏感的。例如:
```verilog=
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin
//两种写法等价
assign s = a? b+c : d ? e+f : g ? h+i : j ? k+l : m ;
end
```
### :pushpin: 電平敏感事件控制
前面所討論的事件控制都是需要等待信號值的變化或事件的觸發,使用 @+敏感列表 的方式來表示的。 Verilog 中還支持使用電平作為敏感信號來控制時序,即後面語句的執行需要等待某個條件為真。 Verilog 中使用關鍵字 wait 來表示這種電平敏感情況。例如:
```verilog=
initial begin
wait (start_enable) ; //等待 start 信号
forever begin
//start信號使能後,在clk_samp上升沿,對數據進行整合
@(posedge clk_samp) ;
data_buf = {data_if[0], data_if[1]} ;
end
end
```
# 4.4 Verilog 語句塊
**關鍵詞**:順序塊,並行塊,嵌套塊,命名塊,disable
Verilog 語句塊提供了將兩條或更多條語句組成語法結構上相當於一條一句的機制。主要包括兩種類型:順序塊和並行塊。
### :pushpin: 順序塊
順序塊用關鍵字 begin 和 end 來表示。 順序塊中的語句是一條條執行的。當然,非阻塞賦值除外。 順序塊中每條語句的時延總是與其前面語句執行的時間相關。 在本節之前的仿真中,initial 塊中的阻塞賦值,都是順序塊的實例。
### :pushpin: 並行塊
並行塊有關鍵字 fork 和 join 來表示。 並行塊中的語句是並行執行的,即便是阻塞形式的賦值。 並行塊中每條語句的時延都是與塊語句開始執行的時間相關。 順序塊與並行塊的區別顯而易見,下面用仿真說明。 仿真代碼如下:
```verilog=
`timescale 1ns/1ns
module test ;
reg [3:0] ai_sequen, bi_sequen ;
reg [3:0] ai_paral, bi_paral ;
reg [3:0] ai_nonblk, bi_nonblk ;
//============================================================//
//(1)Sequence block
initial begin
#5 ai_sequen = 4'd5 ; //at 5ns
#5 bi_sequen = 4'd8 ; //at 10ns
end
//(2)fork block
initial fork
#5 ai_paral = 4'd5 ; //at 5ns
#5 bi_paral = 4'd8 ; //at 5ns
join
//(3)non-block block
initial fork
#5 ai_nonblk <= 4'd5 ; //at 5ns
#5 bi_nonblk <= 4'd8 ; //at 5ns
join
endmodule
```
仿真結果如下: 如圖所示,順序塊順序執行
第 10ns 時,信號 bi_sequen 才賦值為 8。 而並行塊,ai_paral 與 bi_paral 的賦值是同時執行的,所以均在 5ns 時被賦值。 而非阻塞賦值,也能達到和並行塊同等的賦值效果。

### :pushpin: 嵌套塊
順序塊和並行塊還可以嵌套使用。 仿真代碼如下:
```verilog=
`timescale 1ns/1ns
module test ;
reg [3:0] ai_sequen2, bi_sequen2 ;
reg [3:0] ai_paral2, bi_paral2 ;
initial begin
ai_sequen2 = 4'd5 ; //at 0ns
fork
#10 ai_paral2 = 4'd5 ; //at 10ns
#15 bi_paral2 = 4'd8 ; //at 15ns
join
#20 bi_sequen2 = 4'd8 ; //at 35ns
end
endmodule
```
仿真結果如下:
並行塊語句塊內是並行執行,所以信號 ai_paral2 和信號 bi_paral2 分別在 10ns, 15ns 時被賦值。而並行塊中最長的執行時間為 15ns,所以順序塊中的信號 bi_sequen2 在 35ns 時被賦值。

### :pushpin: 命名塊
我們可以給塊語句結構命名。 命名的塊中可以聲明局部變量,通過層次名引用的方法對變量進行訪問。 仿真代碼如下:
```verilog=
`timescale 1ns/1ns
module test;
initial begin: runoob //命名模塊名字為runoob,分號不能少
integer i ; //此變量可以通過test.runoob.i 被其他模塊使用
i = 0 ;
forever begin
#10 i = i + 10 ;
end
end
reg stop_flag ;
initial stop_flag = 1'b0 ;
always begin : detect_stop
if ( test.runoob.i == 100) begin //i累加10次,即100ns时停止仿真
$display("Now you can stop the simulation!!!");
stop_flag = 1'b1 ;
end
#10 ;
end
endmodule
```
仿真結果如下:

命名的塊也可以被禁用,用關鍵字 disable 來表示。 disable 可以終止命名塊的執行,可以用來從循環中退出、處理錯誤等。 與 C 語言中 break 類似,但是 break 只能退出當前所在循環,而 disable 可以禁用設計中任何一個命名的塊。 仿真代碼如下:
```verilog=
`timescale 1ns/1ns
module test;
initial begin: runoob_d //命名模塊名字為runoob_d
integer i_d ;
i_d = 0 ;
while(i_d<=100) begin: runoob_d2
# 10 ;
if (i_d >= 50) begin //累加5次停止累加
disable runoob_d3.clk_gen ;//stop 外部block: clk_gen
disable runoob_d2 ; //stop 当前block: runoob_d2
end
i_d = i_d + 10 ;
end
end
reg clk ;
initial begin: runoob_d3
while (1) begin: clk_gen //时钟产生模块
clk=1 ; #10 ;
clk=0 ; #10 ;
end
end
endmodule
```
仿真結果如下:
由圖可知,信號 i_d 累加到 50 以後,便不再累加,以後 clk 時鐘也不再產生。 可見,disable 退出了當前的 while 塊。

需要說明的是,disable 在 always 或 forever 塊中使用時只能退出當前回合,下一次語句還是會在 always 或 forever 中執行。因為 always 塊和 forever 塊是一直執行的,此時的 disable 有點類似 C 語言中的 continue 功能。
# 4.5 Verilog 條件語句
關鍵詞:if,選擇器
### :pushpin: 條件語句
條件(if)語句用於控制執行語句要根據條件判斷來確定是否執行。 條件語句用關鍵字 if 和 else 來聲明,條件表達式必須在圓括號中。 條件語句使用結構說明如下:
```verilog=
if (condition1) true_statement1 ;
else if (condition2) true_statement2 ;
else if (condition3) true_statement3 ;
else default_statement ;
```
if 語句執行時,如果 condition1 為真,則執行 true_statement1 ;如果 condition1 為假,condition2 為真,則執行 true_statement2;依次類推。
else if 與 else 結構可以省略,即可以只有一個 if 條件判斷和一組執行語句 ture_statement1 就可以構成一個執行過程。
else if 可以疊加多個,不僅限於 1 或 2 個。
ture_statement1 等執行語句可以是一條語句,也可以是多條。如果是多條執行語句,則需要用 begin 與 end 關鍵字進行說明。
下面代碼實現了一個 4 路選擇器的功能。
```verilog=
module mux4to1(
input [1:0] sel ,
input [1:0] p0 ,
input [1:0] p1 ,
input [1:0] p2 ,
input [1:0] p3 ,
output [1:0] sout);
reg [1:0] sout_t ;
always @(*) begin
if (sel == 2'b00)
sout_t = p0 ;
else if (sel == 2'b01)
sout_t = p1 ;
else if (sel == 2'b10)
sout_t = p2 ;
else
sout_t = p3 ;
end
assign sout = sout_t ;
endmodule
```
testbench 代碼如下:
```verilog=
`timescale 1ns/1ns
module test ;
reg [1:0] sel ;
wire [1:0] sout ;
initial begin
sel = 0 ;
#10 sel = 3 ;
#10 sel = 1 ;
#10 sel = 0 ;
#10 sel = 2 ;
end
mux4to1 u_mux4to1 (
.sel (sel),
.p0 (2'b00), //path0 are assigned to 0
.p1 (2'b01), //path1 are assigned to 1
.p2 (2'b10), //path2 are assigned to 2
.p3 (2'b11), //path3 are assigned to 3
.sout (sout));
//finish the simulation
always begin
#100;
if ($time >= 1000) $finish ;
end
endmodule
```
仿真結果如下。 由圖可知,輸出信號與選擇信號、輸入信號的狀態是相匹配的。

事例中 if 條件每次執行的語句只有一條,沒有使用 begin 與 end 關鍵字。但如果是 if-if-else 的形式,即便執行語句只有一條,不使用 begin 與 end 關鍵字也會引起歧義。 例如下面代碼,雖然格式上加以區分,但是 else 對應哪一個 if 條件,是有歧義的。
```verilog=
if(en)
if(sel == 2'b1)
sout = p1s ;
else
sout = p0 ;
```
當然,編譯器一般按照就近原則,使 else 與最近的一個 if(例子中第二個 if)相對應。 但顯然這樣的寫法是不規範且不安全的。 所以條件語句中加入 begin 與 and 關鍵字就是一個很好的習慣。 例如上述代碼稍作修改,就不會再有書寫上的歧義。
```verilog=
if(en) begin
if(sel == 2'b1) begin
sout = p1s ;
end
else begin
sout = p0 ;
end
end
```
# 4.6 Verilog 多路分支語句
**關鍵詞**:case,選擇器
case 語句是一種多路條件分支的形式,可以解決 if 語句中有多個條件選項時使用不方便的問題。
### :pushpin: case 語句
case 語句格式如下:
```verilog=
case(case_expr)
condition1 : true_statement1 ;
condition2 : true_statement2 ;
……
default : default_statement ;
endcase
```
case 語句執行時,如果 condition1 為真,則執行 true_statement1 ; 如果 condition1 為假,condition2 為真,則執行 true_statement2;依次類推。
如果各個 condition 都不為真,則執行 default_statement 語句。 default 語句是可選的,且在一個 case 語句中不能有多個 default 語句。
條件選項可以有多個,不僅限於 condition1、condition2 等,而且這些條件選項不要求互斥。雖然這些條件選項是並發比較的,但執行效果是誰在前且條件為真誰被執行。
ture_statement1 等執行語句可以是一條語句,也可以是多條。如果是多條執行語句,則需要用 begin 與 end 關鍵字進行說明。
**case 語句支持嵌套使用。**
下面用 case 語句代替 if 語句實現了一個 4 路選擇器的功能。仿真結果與 testbench 可參考條件語句一章,兩者完全一致。
```verilog=
module mux4to1(
input [1:0] sel ,
input [1:0] p0 ,
input [1:0] p1 ,
input [1:0] p2 ,
input [1:0] p3 ,
output [1:0] sout);
reg [1:0] sout_t ;
always @(*)
case(sel)
2'b00: begin
sout_t = p0 ;
end
2'b01: sout_t = p1 ;
2'b10: sout_t = p2 ;
default: sout_t = p3 ;
endcase
assign sout = sout_t ;
endmodule
```
case 語句中的條件選項表單式不必都是常量,也可以是 x 值或 z 值。 當多個條件選項下需要執行相同的語句時,多個條件選項可以用逗號分開,放在同一個語句塊的候選項中。 但是 case 語句中的 x 或 z 的比較邏輯是不可綜合的,所以一般不建議在 case 語句中使用 x 或 z 作為比較值。 例如,對 4 路選擇器的 case 語句進行擴展,舉例如下:
```verilog=
case(sel)
2'b00: sout_t = p0 ;
2'b01: sout_t = p1 ;
2'b10: sout_t = p2 ;
2'b11: sout_t = p3 ;
2'bx0, 2'bx1, 2'bxz, 2'bxx, 2'b0x, 2'b1x, 2'bzx :
sout_t = 2'bxx ;
2'bz0, 2'bz1, 2'bzz, 2'b0z, 2'b1z :
sout_t = 2'bzz ;
default: $display("Unexpected input control!!!");
endcase
```
### :pushpin: casex/casez 語句
casex、 casez 語句是 case 語句的變形,用來表示條件選項中的無關項。 casex 用 "x" 來表示無關值,casez 用問號 "?" 來表示無關值。 兩者的實現的功能是完全一致的,語法與 case 語句也完全一致。 但是 casex、casez 一般是不可綜合的,多用於仿真。 例如用 casez 語句來實現一個 4bit 控制端的 4 路選擇選擇器。
```verilog=
module mux4to1(
input [3:0] sel ,
input [1:0] p0 ,
input [1:0] p1 ,
input [1:0] p2 ,
input [1:0] p3 ,
output [1:0] sout);
reg [1:0] sout_t ;
always @(*)
casez(sel)
4'b???1: sout_t = p0 ;
4'b??1?: sout_t = p1 ;
4'b?1??: sout_t = p2 ;
4'b1???: sout_t = p3 ;
default: sout_t = 2'b0 ;
endcase
assign sout = sout_t ;
endmodule
```
# 4.7 Verilog 循環語句
關鍵詞:while, for, repeat, forever
Verilog 循環語句有 4 種類型,分別是 while,for,repeat,和 forever 循環。循環語句只能在 always 或 initial 塊中使用,但可以包含延遲表達式
### :pushpin: while 循環
while 循環語法格式如下:
```verilog=
while (condition) begin
…
end
```
while 循環中止條件為 condition 為假。 如果開始執行到 while 循環時 condition 已經為假,那麼循環語句一次也不會執行。 當然,執行語句只有一條時,關鍵字 begin 與 end 可以省略。 下面代碼執行時,counter 執行了 11 次。
```verilog=
`timescale 1ns/1ns
module test ;
reg [3:0] counter ;
initial begin
counter = 'b0 ;
while (counter<=10) begin
#10 ;
counter = counter + 1'b1 ;
end
end
//stop the simulation
always begin
#10 ; if ($time >= 1000) $finish ;
end
endmodule
```
仿真結果如下:

### :pushpin: for 循環
for 循環語法格式如下:
```verilog=
for(initial_assignment; condition ; step_assignment) begin
…
end
```
initial_assignment 為初始條件。 condition 為終止條件,condition 為假時,立即跳出循環。 step_assignment 為改變控制變量的過程賦值語句,通常為增加或減少循環變量計數。 一般來說,因為初始條件和自加操作等過程都已經包含在 for 循環中,所以 for 循環寫法比 while 更為緊湊,但也不是所有的情況下都能使用 for 循環來代替 while 循環。
下面 for 循環的例子,實現了與 while 循環中例子一樣的效果。需要注意的是,i = i + 1 不能像 C 語言那樣寫成 i++ 的形式,i = i -1 也不能寫成 i -- 的形式。
```verilog=
// for 循环语句
integer i ;
reg [3:0] counter2 ;
initial begin
counter2 = 'b0 ;
for (i=0; i<=10; i=i+1) begin
#10 ;
counter2 = counter2 + 1'b1 ;
end
end
```
### :pushpin: repeat 循環
repeat 循環語法格式如下:
```verilog=
repeat (loop_times) begin
…
end
```
repeat 的功能是執行固定次數的循環,它不能像 while 循環那樣用一個邏輯表達式來確定循環是否繼續執行。 repeat 循環的次數必須是一個常量、變量或信號。如果循環次數是變量信號,則循環次數是開始執行 repeat 循環時變量信號的值。即便執行期間,循環次數代表的變量信號值發生了變化,repeat 執行次數也不會改變。 下面 repeat 循環例子,實現了與 while 循環中的例子一樣的效果。
```verilog=
// repeat 循环语句
reg [3:0] counter3 ;
initial begin
counter3 = 'b0 ;
repeat (11) begin //重复11次
#10 ;
counter3 = counter3 + 1'b1 ;
end
end
```
下面 repeat 循環例子,實現了連續存儲 8 個數據的功能:
```verilog=
always @(posedge clk or negedge rstn) begin
j = 0 ;
if (!rstn) begin
repeat (8) begin
buffer[j] <= 'b0 ; //没有延迟的赋值,即同时赋值为0
j = j + 1 ;
end
end
else if (enable) begin
repeat (8) begin
@(posedge clk) buffer[j] <= counter3 ; //在下一个clk的上升沿赋值
j = j + 1 ;
end
end
end
```
仿真結果如下圖。 由圖可知,rstn 拉高時,buffer 的 8 個向量同時賦值為 0。 第二個時鐘週期後,buffer 依次被 counter3 賦值,實現了連續存儲 8 個數據的功能。

### :pushpin: forever 循環
forever 循環語法格式如下:
```verilog=
forever begin
…
end
```
forever 語句表示永久循環,不包含任何條件表達式,一旦執行便無限的執行下去,系統函數 $finish 可退出 forever。 forever 相當於 while(1) 。 通常,forever 循環是和時序控制結構配合使用的。 例如,使用 forever 語句產生一個時鐘:
```verilog=
reg clk ;
initial begin
clk = 0 ;
forever begin
clk = ~clk ;
#5 ;
end
end
```
例如,使用 forever 語句實現一個時鐘邊沿控制的寄存器間數據傳輸功能:
```verilog=
reg clk ;
reg data_in, data_temp ;
initial begin
forever @(posedge clk) data_temp = data_in ;
end
```
完整verilog source-code:
```verilog=
`timescale 1ns/1ns
module test ;
//(1) while-loop sentense
reg [3:0] counter ;
initial begin
counter = 'b0 ;
while (counter<=10) begin
#10 ;
counter = counter + 1'b1 ;
end
end
//(2) for-loop sentense
integer i ;
reg [3:0] counter2 ;
initial begin
counter2 = 'b0 ;
for (i=0; i<=10; i=i+1) begin
#10 ;
counter2 = counter2 + 1'b1 ;
end
end
//(3) repeat-loop sentense
reg [3:0] counter3 ;
initial begin
counter3 = 'b0 ;
repeat (11) begin
#10 ;
counter3 = counter3 + 1'b1 ;
end
end
//(3)
reg clk ;
reg rstn ;
reg enable ;
reg [3:0] buffer [7:0] ;
integer j ;
initial begin
clk = 0 ;
rstn = 1 ;
enable = 0 ;
#3;
rstn = 0 ;
#3;
rstn = 1 ;
enable = 1 ;
forever begin
clk = ~clk ;
#5 ;
end
end
always @(posedge clk or negedge rstn) begin
j = 0 ;
if (!rstn) begin
repeat (8) begin
buffer[j] <= 'b0 ;
j = j + 1 ;
end
end
else if (enable) begin
repeat (8) begin
@(posedge clk) buffer[j] <= counter3 ;
j = j + 1 ;
end
end
end
//stop the simulation
always begin
#10 ;
if ($time >= 1000) $finish ;
end
endmodule
```
# 4.8 Verilog 過程連續賦值
關鍵詞:deassign,force,release 過程連續賦值是過程賦值的一種。這種賦值語句能夠替換其他所有 wire 或 reg 的賦值,改寫了 wire 或 reg 型變量的當前值。 與過程賦值不同的是,過程連續賦值的表達式能被連續的驅動到 wire 或 reg 型變量中,即過程連續賦值發生作用時,右端表達式中任意操作數的變化都會引起過程連續賦值語句的重新執行。 過程連續性賦值主要有 2 種,assign-deassign 和 force-release。
### :pushpin: assign, deassign
assign(過程賦值操作)與 deassign (取消過程賦值操作)表示第一類過程連續賦值語句。賦值對像只能是寄存器或寄存器組,而不能是 wire 型變量。 賦值過程中對寄存器連續賦值,寄存器中的值被保留直到被重新賦值。 例如,一個帶復位端的 D 觸發器可以用下面代碼描述:
```verilog=
module dff_normal(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk or negedge rstn) begin
if(!rstn) begin //Q = 0 after reset effective
Q <= 1'b0 ;
end
else begin
Q <= D ; //Q = D at posedge of clock
end
end
endmodule
```
下面,用 assign 與 deassign 改寫,完成相同的功能。 即在復位信號為 0 時,Q 端被 assign 語句賦值,始終輸出為 0。 復位信號為 1 時,Q 端被 deassign 語句取消賦值,在時鐘上升沿被重新賦值。
```verilog=
module dff_assign(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk) begin
Q <= D ; //Q = D at posedge of clock
end
always @(negedge rstn) begin
if(!rstn) begin
assign Q = 1'b0 ; //change Q value when reset effective
end
else begin //cancel the Q value overlay,
deassign Q ; //and Q remains 0-value until the coming of clock posedge
end
end
endmodule
```
### :pushpin: force, release
force (強制賦值操作)與 release(取消強制賦值)表示第二類過程連續賦值語句。 使用方法和效果,和 assign 與 deassign 類似,但賦值對象可以是 reg 型變量,也可以是 wire 型變量。 因為是無條件強制賦值,一般多用於交互式調試過程,不要在設計模塊中使用。 當 force 作用在寄存器上時,寄存器當前值被覆蓋;release 時該寄存器值將繼續保留強制賦值時的值。之後,該寄存器的值可以被原有的過程賦值語句改變。
當 force 作用在線網上時,線網值也會被強制賦值。但是,一旦 release 該線網型變量,其值馬上變為原有的驅動值。 為直觀的觀察兩種類型變量強制賦值的區別,利用第一節中的計數器 counter10 作為設計模塊,testbench 設計如下。
```verilog=
`timescale 1ns/1ns
module test ;
reg rstn ;
reg clk ;
reg [3:0] cnt ;
wire cout ;
counter10 u_counter (
.rstn (rstn),
.clk (clk),
.cnt (cnt),
.cout (cout));
initial begin
clk = 0 ;
rstn = 0 ;
#10 ;
rstn = 1'b1 ;
wait (test.u_counter.cnt_temp == 4'd4) ;
@(negedge clk) ;
force test.u_counter.cnt_temp = 4'd6 ;
force test.u_counter.cout = 1'b1 ;
#40 ;
@(negedge clk) ;
release test.u_counter.cnt_temp ;
release test.u_counter.cout ;
end
initial begin
clk = 0 ;
forever #10 clk = ~ clk ;
end
//finish the simulation
always begin
#1000;
if ($time >= 1000) $finish ;
end
endmodule // test
```
仿真結果如下。 由圖可知,在 cnt_temp 等於 4 時(80ns), cnt_temp 被強制賦值為 6,cout 被強制賦值為 1。 release 時(120ns), cnt_temp 為寄存器類型,仍然保持原有值不變,直到時鐘上升沿對其進行加法賦值操作,值才變為 7 。 而 120ns 時,由於 cout 是線網型變量,其值不能保存。原碼 counter10 模型中存在驅動語句: assign cout = (cnt_temp==4'd9) ,所以 cout 值變為 0 。

counter10.v
```verilog=
module counter10(
input rstn,
input clk,
output [3:0] cnt,
output cout);
reg [3:0] cnt_temp ;
always@(posedge clk or negedge rstn) begin
if(! rstn)begin
cnt_temp <= 4'b0 ;
end
else if (cnt_temp==4'd9) begin
cnt_temp <=4'b000;
end
else begin
cnt_temp <= cnt_temp + 1'b1 ;
end
end
assign cout = (cnt_temp==4'd9) ;
assign cnt = cnt_temp ;
endmodule // counter10
module dff_normal(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk or negedge rstn) begin
if(!rstn) begin //Q = 0 after reset effective
Q <= 1'b0 ;
end
else begin
Q <= D ; //Q = D at posedge of clock
end
end
endmodule // dff_normal
module dff_assign(
input rstn,
input clk,
input D,
output reg Q
);
always @(posedge clk) begin
Q <= D ; //Q = D at posedge of clock
end
always @(negedge rstn) begin
if(!rstn) begin
assign Q = 1'b0 ; //change the Q value when reset effective
end
else begin //cancel the Q value overlay,
deassign Q ; //and Q remains 0-value until the coming of clock posedge
end
end
endmodule // dff_normal
```
test.v
```verilog=
`timescale 1ns/1ns
module test ;
reg rstn ;
reg clk ;
reg [3:0] cnt ;
wire cout ;
counter10 u_counter (
.rstn (rstn),
.clk (clk),
.cnt (cnt),
.cout (cout));
initial begin
clk = 0 ;
rstn = 0 ;
#10 ;
rstn = 1'b1 ;
wait (test.u_counter.cnt_temp == 4'd4) ;
@(negedge clk) ;
force test.u_counter.cnt_temp = 4'd6 ;
force test.u_counter.cout = 1'b1 ;
#40 ;
@(negedge clk) ;
release test.u_counter.cnt_temp ;
release test.u_counter.cout ;
end
initial begin
clk = 0 ;
forever #10 clk = ~ clk ;
end
//finish the simulation
always begin
#1000;
if ($time >= 1000) $finish ;
end
endmodule // test
```