Verilog if-else 語法完整教學:基礎語法、應用範例與最佳實踐

目次

1. 前言

1-1. 什麼是 Verilog 的 if-else 敘述?

Verilog 是一種硬體描述語言(HDL),廣泛用於設計 FPGA 與 ASIC 等數位電路。其中,if-else 敘述 是依據條件控制程式流程的關鍵語法。

Verilog 中 if-else 敘述的主要用途如下:

  • 組合電路 的條件分支
  • 時序電路(如觸發器) 的操作控制
  • 動態訊號控制(例如:選擇器或條件運算)

例如,透過 if-else 敘述,可以根據訊號狀態產生不同的輸出。這在電路設計中非常實用,但若使用不當,可能會意外產生鎖存器(記憶元件)

1-2. 不正確使用 if-else 敘述可能造成的問題

若未正確使用 Verilog 的 if-else 敘述,可能會產生以下問題:

  1. 意外生成鎖存器
  • 若條件分支未明確涵蓋所有情況,綜合工具可能會自動插入鎖存器(記憶元件)。
  • 這會造成非預期的保持行為,使電路無法如預期運作。
  1. 模擬結果與綜合結果不一致
  • 在模擬中可能表現正常,但實際實現在 FPGA 或 ASIC 時行為卻不同。
  • 這是因為 if-else 的寫法可能導致綜合工具進行錯誤的最佳化。
  1. 程式可讀性降低
  • 過度巢狀的 if-else 敘述會降低可讀性。
  • 必要時可改用 case 敘述來提升程式結構的清晰度。

1-3. 本文的目的

本文將深入介紹 Verilog 中 if-else 敘述的基礎語法、應用範例、最佳實踐,以及與 case 敘述的使用差異

透過閱讀本文,你將能學到:

  • if-else 敘述的正確用法
  • 如何避免產生鎖存器的 Verilog 程式寫法
  • if-else 與 case 敘述的正確區分與應用
  • Verilog 設計中的最佳實務

文章將搭配具體範例程式碼,讓初學者也能輕鬆理解,建議完整閱讀!

2. Verilog if-else 敘述的基本語法

2-1. if-else 敘述的寫法

Verilog 的 if-else 敘述與軟體語言(如 C、Python)的 if-else 相似,但必須考慮到硬體描述語言的特性

基本的 if-else 語法如下:

always_comb begin
    if (條件) 
        處理1;
    else 
        處理2;
end

另外,也可以使用 else if 來實現多重條件分支

always_comb begin
    if (條件1) 
        處理1;
    else if (條件2) 
        處理2;
    else 
        處理3;
end

此語法經常用於設計組合電路,讓電路依據不同條件輸出不同的行為。

2-2. if-else 敘述的基本範例程式

接下來我們用一個具體的例子,建立一個簡單的選擇器電路

範例:依據輸入 a 來決定輸出 y 的值

module if_else_example(input logic a, b, output logic y);
    always_comb begin
        if (a == 1'b1) 
            y = b;
        else 
            y = ~b;
    end
endmodule

解說

  • a1 時,y 會輸出與 b 相同的值。
  • a0 時,y 會輸出 b 的反相值。

由此可見,if-else 敘述能輕鬆描述依據條件改變訊號的控制

2-3. if-else 敘述的運作原理

Verilog 的 if-else 敘述可用於以下兩種不同的電路設計:

  1. 組合電路(使用 always_comb)
  • 輸出會依輸入的變化即時更新。
  • 不會生成鎖存器,因此可避免非預期行為。
  • 建議使用 always_comb,而非 always @(*)
  1. 時序電路(使用 always_ff)
  • 輸出會依據時脈訊號更新。
  • 常用於D 觸發器等電路。

接下來我們分別介紹這兩種情境下 if-else 的實際用法。

2-4. 組合電路中的 if-else 敘述

在組合電路中,輸出會立即反映輸入的變化。
因此必須使用 always_comb,避免生成不必要的鎖存器。

module combination_logic(input logic a, b, output logic y);
    always_comb begin
        if (a == 1'b1) 
            y = b;
        else 
            y = ~b;
    end
endmodule

此程式會依據輸入 a 來改變輸出 y

  • a == 1 時:y = b
  • a == 0 時:y = ~b

注意事項

  • 務必使用 always_comb 來避免產生鎖存器。
  • 必須在所有條件下對輸出變數指定值(若省略 else,可能會產生鎖存器)。

2-5. 時序電路中的 if-else 敘述

在時序電路中,必須使用 always_ff,讓輸出隨時脈訊號更新。

範例:D 觸發器

module d_flipflop(input logic clk, reset, d, output logic q);
    always_ff @(posedge clk or posedge reset) begin
        if (reset) 
            q <= 1'b0;
        else 
            q <= d;
    end
endmodule

這個程式代表一個 D 觸發器:

  • reset1 時,q 被重設為 0
  • reset0 且時脈 clk 上升沿觸發時,d 的值會被存入 q

注意事項

  • 時序電路建議使用 always_ff,而非 always @(*)
  • 應使用 <=(非阻塞指派)來避免競爭與衝突。

2-6. if-else 敘述的實際應用範例

Verilog 的 if-else 敘述在以下情境中常被使用:

  1. LED 控制
  • 依據開關的狀態來控制 LED 的開啟與關閉。
  1. ALU(算術邏輯單元)
  • 用於加法、減法與邏輯運算的控制。
  1. 狀態轉移
  • 有限狀態機(FSM)的設計(將於後續章節詳細介紹)。

總結

  • if-else 敘述用於 Verilog 的條件分支控制。
  • 組合電路(always_comb)與時序電路(always_ff)必須正確區分使用。
  • 若未在所有條件下指定輸出值,可能會產生鎖存器。
  • 在實際電路設計中,if-else 敘述常用於狀態控制。

3. if-else 敘述的應用

if-else 敘述是 Verilog 條件分支的基礎,不僅能處理簡單控制,還能應用於組合電路與時序電路的設計。本章將示範 if-else 敘述的應用,例如4 位元加法器與有限狀態機(FSM)

3-1. 組合電路的設計

組合電路的輸出會隨輸入即時變化。
設計組合電路時,應使用 always_comb避免生成不必要的鎖存器

範例1: 4 位元加法器

此電路將兩個 4 位元輸入 (ab) 相加,並輸出含進位 (cout) 的結果 (sum)。

module adder(
    input logic [3:0] a, b,
    input logic cin,
    output logic [3:0] sum,
    output logic cout
);
    always_comb begin
        if (cin == 1'b0)
            {cout, sum} = a + b; // 無進位
        else
            {cout, sum} = a + b + 1; // 有進位
    end
endmodule

解說

  • cin = 0 時,計算 a + b
  • cin = 1 時,計算 a + b + 1(包含進位)。
  • 使用 always_comb 確保其為組合電路,並避免生成鎖存器。

3-2. 在時序電路(暫存器)中的應用

時序電路會依據時脈訊號 (clk) 更新資料
if-else 敘述可用於狀態轉移或暫存器控制。

範例2: D 觸發器

D 觸發器會在時脈訊號的上升沿(posedge clk)將輸入 d 儲存到輸出 q

module d_flipflop(
    input logic clk, reset, d,
    output logic q
);
    always_ff @(posedge clk or posedge reset) begin
        if (reset)
            q <= 1'b0; // reset 時歸零
        else
            q <= d; // 在 clk 上升沿更新 q
    end
endmodule

解說

  • reset = 1 時,q 被重設為 0
  • clk 上升沿時,d 的值被存入 q
  • 使用 always_ff 讓其正確作為暫存器(觸發器)運作。

3-3. 在狀態轉移(FSM)中的 if-else 應用

if-else 敘述也可用於有限狀態機(FSM: Finite State Machine) 的設計。
FSM 是一種具有多個狀態並依據條件進行轉換的電路

範例3: 簡單的狀態轉移電路

依據按鈕輸入 (btn),切換 LED 狀態 (led_state) 的 FSM。

module fsm_toggle(
    input logic clk, reset, btn,
    output logic led_state
);
    typedef enum logic {OFF, ON} state_t;
    state_t state, next_state;

    always_ff @(posedge clk or posedge reset) begin
        if (reset)
            state <= OFF; // 初始狀態
        else
            state <= next_state;
    end

    always_comb begin
        case (state)
            OFF: if (btn) next_state = ON;
                 else next_state = OFF;
            ON:  if (btn) next_state = OFF;
                 else next_state = ON;
            default: next_state = OFF;
        endcase
    end

    assign led_state = (state == ON);
endmodule

解說

  • state 用來記錄 LED 狀態(ON 或 OFF)。
  • reset = 1 時,LED 回到 OFF 狀態。
  • 每次 btn 被按下時,LED 狀態會在 ON 與 OFF 之間切換
  • 使用 case 敘述來描述狀態轉換,提升程式可讀性。

3-4. if-else 敘述的進階技巧

① 避免過度巢狀的 if-else

過度巢狀的 if-else 敘述會降低可讀性,並容易導致錯誤。

不良範例(巢狀過深)

always_comb begin
    if (a == 1) begin
        if (b == 1) begin
            if (c == 1) begin
                y = 1;
            end else begin
                y = 0;
            end
        end else begin
            y = 0;
        end
    end else begin
        y = 0;
    end
end

改進範例(使用 case 敘述)

always_comb begin
    case ({a, b, c})
        3'b111: y = 1;
        default: y = 0;
    endcase
end
  • 透過將條件組合成位元向量並使用 case 敘述,可以減少巢狀結構並提升可讀性

總結

  • if-else 敘述可用於組合電路與時序電路。
  • 組合電路建議使用 always_comb,時序電路則建議使用 always_ff
  • FSM 狀態機可透過 if-else 或 case 敘述實現。
  • 當 if-else 巢狀過深時,應使用 case 敘述或位元向量化條件改善程式結構。

4. if-else 敘述與 case 敘述的差異

在 Verilog 中,用於條件分支的有 if-else 敘述case 敘述
這兩者都是常見的控制結構,但適用的場合不同,正確區分使用非常重要。

4-1. 什麼是 case 敘述?

case 敘述的基本語法

case 敘述用於針對多個不同的值執行對應的處理。
當條件為特定值時,使用 case 會更合適。

always_comb begin
    case (條件變數)
        值1: 處理1;
        值2: 處理2;
        值3: 處理3;
        default: 處理4; // 其他情況
    endcase
end

case 敘述範例程式

以下範例依據輸入 sel 的值切換輸出 y

module case_example(input logic [1:0] sel, input logic a, b, c, d, output logic y);
    always_comb begin
        case (sel)
            2'b00: y = a;
            2'b01: y = b;
            2'b10: y = c;
            2'b11: y = d;
            default: y = 0; // 預設值,避免未定義
        endcase
    end
endmodule

解說

  • 根據 sel 的值,y 將輸出 a, b, c, d 其中之一。
  • 當條件為多個固定值時,使用 case 會讓程式更簡潔。
  • 設定 default 可以避免未定義的輸入造成誤動作。

4-2. if-else 與 case 的差異

if-else 與 case 都能做條件分支,但有以下關鍵差異

比較項目if-else 敘述case 敘述
適用情境適合範圍條件或連續判斷適合針對特定固定值判斷
可讀性巢狀過深會降低可讀性條件清晰,結構簡單
綜合結果if-else 由工具最佳化case 常被轉換為多工器(Multiplexer)
鎖存器發生可能性若條件未完全指定,可能產生鎖存器若未加 default,可能導致未定義行為

4-3. if-else 與 case 的使用時機

① 適合使用 if-else 的情境

條件為範圍判斷

always_comb begin
    if (value >= 10 && value <= 20)
        output_signal = 1;
    else
        output_signal = 0;
end
  • 若條件涉及範圍(例如 10~20),使用 if-else 更適合。
  • case 無法直接描述範圍條件。

條件具有優先順序

always_comb begin
    if (x == 1)
        y = 10;
    else if (x == 2)
        y = 20;
    else if (x == 3)
        y = 30;
    else
        y = 40;
end
  • 當條件具優先性時,使用 if-else 更直觀。
  • 上層條件成立後,後續條件將不再檢查。

② 適合使用 case 的情境

針對特定值進行分支

always_comb begin
    case (state)
        2'b00: next_state = 2'b01;
        2'b01: next_state = 2'b10;
        2'b10: next_state = 2'b00;
        default: next_state = 2'b00;
    endcase
end
  • 依據 state 的值決定下一狀態。
  • FSM 狀態轉移設計中,case 是常用寫法。

條件種類較多

always_comb begin
    case (opcode)
        4'b0000: instruction = ADD;
        4'b0001: instruction = SUB;
        4'b0010: instruction = AND;
        4'b0011: instruction = OR;
        default: instruction = NOP;
    endcase
end
  • 在指令解碼器等情境下,當條件數量眾多時,case 的可讀性更佳

總結

if-else 適合範圍條件或具有優先順序的處理
case 適合固定值判斷與狀態轉移(FSM)
當條件數量龐大時,建議使用 case 提升可讀性
判斷使用何者時,應依「條件型態」與「是否需要優先順序」來決定

5. Verilog if-else 敘述的最佳實踐

if-else 敘述雖然是 Verilog 中常見的條件分支方法,但若未正確撰寫,可能導致鎖存器生成或非預期行為。本章將說明撰寫 if-else 的最佳實踐

5-1. 避免生成鎖存器的寫法

在 Verilog 的組合電路設計中,若 if-else 區塊未涵蓋所有條件,工具會推斷需要保留先前的值,進而自動產生鎖存器

① 鎖存器產生的錯誤範例

always_comb begin
    if (a == 1'b1)
        y = b; // 當 a == 0 時,y 沒有被指定 → 保持前一個值
end

為什麼會生成鎖存器?

  • a == 1 時,y 被指定為 b
  • 但當 a == 0 時,y 未被指定 → 工具認為需要保留先前的值。
  • 結果:隱含生成一個鎖存器,導致非預期的電路行為。

② 正確的寫法(使用 else)

應該在所有條件下明確指定輸出

always_comb begin
    if (a == 1'b1)
        y = b;
    else
        y = 1'b0; // 明確指定輸出
end

③ 使用 default 值

在 if 區塊前,先給輸出一個初始值。

always_comb begin
    y = 1'b0; // 預設值
    if (a == 1'b1)
        y = b;
end

重點:只要在所有情況下都有給定值,就不會產生鎖存器!

5-2. 善用 always_combalways_ff

自 Verilog 2001 起,建議區分組合電路與時序電路:

① 組合電路(always_comb)

always_comb begin
    if (a == 1'b1)
        y = b;
    else
        y = 1'b0;
end
  • always_comb 會自動建立感測清單((*)),無需手動維護。
  • 設計意圖更清楚,且利於工具最佳化。

② 時序電路(always_ff)

always_ff @(posedge clk or posedge reset) begin
    if (reset)
        q <= 1'b0;
    else
        q <= d;
end
  • always_ff 明確表示此區塊為時脈驅動的暫存器
  • 比傳統的 always @(posedge clk ...) 可讀性更高,且能減少設計錯誤。

5-3. 提升 if-else 可讀性的技巧

if-else 敘述雖然直觀,但過多的巢狀結構會讓程式難以閱讀。以下方法可改善:

① 減少巢狀層級

不良範例(巢狀過深)

always_comb begin
    if (mode == 2'b00) begin
        if (enable) begin
            y = a;
        end else begin
            y = b;
        end
    end else begin
        y = c;
    end
end

改良範例(使用 case 或三元運算子)

always_comb begin
    case (mode)
        2'b00: y = enable ? a : b;
        default: y = c;
    endcase
end
  • 使用 case 敘述能讓條件更清楚。
  • 使用 ?: 三元運算子,可進一步簡化程式。

總結

if-else 必須涵蓋所有條件,否則會生成鎖存器。
組合電路應使用 always_comb,時序電路應使用 always_ff
巢狀過深時應改用 case 或三元運算子,提升可讀性。
變數命名應具體明確,以增加程式可讀性。

6. 常見問題(FAQ)

Verilog 的 if-else 敘述是基礎的條件分支結構,無論是初學者或進階工程師,都常會遇到一些常見疑問
本章將以 Q&A 形式回答,包括 鎖存器產生問題、與 case 敘述的差異、對速度的影響 等。

Q1: 為什麼使用 if-else 會生成鎖存器?如何避免?

A1: 鎖存器生成的原因

在 Verilog 中,若 if-else 敘述未對所有條件明確指定輸出值,綜合工具會推斷需保留舊值,因此自動生成鎖存器(Latch)

錯誤範例(會生成鎖存器)

always_comb begin
    if (a == 1'b1)
        y = b;  // 當 a == 0 時,y 沒有被指定 → 保持舊值
end

解決方法

① 使用 else

always_comb begin
    if (a == 1'b1)
        y = b;
    else
        y = 1'b0; // 明確指定
end

② 設定預設值

always_comb begin
    y = 1'b0; // 預設值
    if (a == 1'b1)
        y = b;
end

重點:在所有情況下給定輸出值,就不會產生鎖存器!

Q2: if-else 與 case 有什麼不同?何時應該使用?

A2: 使用時機

條件特性適合使用的語法
範圍判斷(例: 10 <= x <= 20if-else
針對特定值分支case
有優先順序if-else
分支數量多case

Q3: if-else 會影響電路速度嗎?

A3: 取決於綜合後的硬體結構

  • Verilog 是硬體描述語言,速度取決於綜合後的電路,而非語法本身。
  • 若 if-else 巢狀過深,可能增加邏輯延遲。
  • 但綜合工具通常會最佳化,讓等效電路的延遲差異不大。

最佳化建議
盡量減少巢狀,改用 case 或簡單條件運算。

Q4: 在 if-else 中應該用 = 還是 <=

A4: 差異在於「阻塞」與「非阻塞」指派

指派方式適用情境
= (阻塞指派)組合電路(always_comb)
<= (非阻塞指派)時序電路(always_ff)

組合電路 → 使用 =

always_comb begin
    if (a == 1)
        y = b; // 阻塞指派
end

時序電路 → 使用 <=

always_ff @(posedge clk) begin
    if (reset)
        y <= 0; // 非阻塞指派
    else
        y <= d;
end

Q5: 如何減少 if-else 的巢狀結構?

A5: 使用 case 或條件運算子

不良範例(巢狀過深)

always_comb begin
    if (mode == 2'b00) begin
        if (enable) begin
            y = a;
        end else begin
            y = b;
        end
    end else begin
        y = c;
    end
end

改良範例(使用 case 與三元運算子)

always_comb begin
    case (mode)
        2'b00: y = enable ? a : b;
        default: y = c;
    endcase
end

重點:使用 ? : 條件運算子可簡化 if-else

總結

if-else 未涵蓋所有條件時會產生鎖存器 → 解法是加 else 或預設值。
範圍或優先順序 → 用 if-else;多個固定值 → 用 case。
組合電路用 =,時序電路用 <=。
巢狀過深時,應改用 case 或條件運算子,提升可讀性。

7. 總結

Verilog 的 if-else 敘述是數位電路設計中非常重要的條件分支方法。本文已經從基礎語法、應用範例、最佳實踐到常見疑問做了詳細解說。

以下整理出在使用 if-else 時需要注意的重點

7-1. Verilog if-else 的基本要點

✅ 基本語法

  • if-else條件分支的基本結構
  • 組合電路中應使用 always_comb,並確保所有條件皆有指定值
always_comb begin
    if (a == 1'b1)
        y = b;
    else
        y = 1'b0; // 避免產生鎖存器
end
  • 時序電路(時脈驅動)中應使用 always_ff,並採用非阻塞指派 <=
always_ff @(posedge clk or posedge reset) begin
    if (reset)
        q <= 1'b0;
    else
        q <= d;
end

重點:組合電路用 =,時序電路用 <=

7-2. if-else 的正確使用方式

在組合電路中

  • 使用 always_comb
  • 確保所有條件下都有輸出值,避免鎖存器。
  • 可設定預設值以防未定義情況。

在時序電路中

  • 使用 always_ff
  • 利用 <= 更新暫存器狀態。
  • 適合設計觸發器、狀態機等。

if-else 適用場景

條件特性建議使用
範圍判斷(例: 10 <= x <= 20if-else
具有優先順序(例: if (x==1)else if (x==2)if-else
條件較少(2~3 個)if-else

7-3. 與 case 的區分

if-else 適合連續範圍與優先判斷
case 適合多個固定值或狀態轉移

case 更適合以下情況:

條件特性建議使用
針對特定值分支(例: 狀態 = IDLE, RUN, STOP)case
分支數量眾多(例: 8 種以上)case
有限狀態機(FSM)case

7-4. if-else 的最佳實踐

所有條件皆需指定輸出,避免鎖存器

always_comb begin
    if (a == 1'b1)
        y = b;
    else
        y = 1'b0; // 必須處理所有情況
end

正確區分 always_comb 與 always_ff

always_comb begin // 組合電路
    if (a)
        y = b;
    else
        y = 0;
end
always_ff @(posedge clk) begin // 時序電路
    if (reset)
        y <= 0;
    else
        y <= d;
end

巢狀過深時,應改用 case

always_comb begin
    case (sel)
        2'b00: y = a;
        2'b01: y = b;
        2'b10: y = c;
        default: y = d;
    endcase
end

7-5. 常見錯誤與修正

錯誤正確寫法
省略 else → 產生鎖存器必須使用 else 或指定預設值
在時序電路使用 =應使用 <=(非阻塞指派)
if-else 巢狀過深改用 case 提升可讀性

7-6. 總結重點

if-else 可用於組合與時序電路,但必須正確撰寫。
若未覆蓋所有條件,將導致鎖存器。
多個固定值或狀態轉移 → 使用 case。
時序電路用 <=,組合電路用 =。
避免巢狀過深,使用 case 或三元運算子提升可讀性。

7-7. 下一步

本文完整介紹了 Verilog if-else 敘述的基礎、應用、最佳實踐與常見問題。接下來建議學習以下進階主題:

FSM(有限狀態機)的設計技巧
使用 case 進行高效控制設計
在流水線設計中的 if-else 應用
時脈同步設計的最佳化方法

透過深入學習,將能提升對 Verilog 的掌握度,並設計出更高效與可靠的數位電路!🚀