1. 前言
Verilog 中 always 敘述的角色是什麼?
在數位電路設計中被廣泛使用的硬體描述語言「Verilog HDL」中,always 敘述扮演著非常重要的角色。Verilog 並不是像軟體一樣描述演算法,而是以「在什麼條件下,訊號如何變化」的方式來表現電路。在這之中,always 敘述是用來描述特定條件發生時要執行的動作的基本語法。
為什麼需要 always 敘述?
在 Verilog 中,主要有兩種方式來描述電路的行為:
- 組合邏輯電路:輸入改變時,輸出立即跟著改變的電路
- 時序邏輯電路:依照時脈訊號等時間條件改變輸出的電路
僅靠 assign 敘述無法描述複雜的條件分支或狀態記憶。在這種情況下,就需要 always 敘述。
例如,要描述具有多重條件分支的邏輯,或是透過觸發器(flip-flop)進行的記憶功能,就必須在 always 中使用控制結構(例如 if 敘述或 case 敘述)。
常見的 always 使用模式
always 敘述有幾種常見用法,會依照欲設計的電路種類來區分:
always @(*)
→ 用於組合邏輯電路always @(posedge clk)
→ 用於時脈上升沿觸發的時序電路always @(posedge clk or negedge rst)
→ 用於帶有非同步重置信號的時序電路等較複雜的控制結構
因此,理解Verilog 的核心語法 always,可以說是成為硬體設計工程師的第一步。
本文的目的
本篇文章將針對 Verilog 中的 always 敘述,從基礎語法、進階用法、常見陷阱到 SystemVerilog 的擴充功能,進行全面解說。
- 想了解
always的正確寫法 - 無法判斷邏輯合成錯誤的原因
- 分不清楚
=與<=的使用時機 - 想避免初學者常犯的錯誤
這篇文章將成為解答這些疑問與困惑的實用指南,幫助讀者快速掌握 always 的設計技巧。
2. always 敘述的基本語法與種類
always 敘述的基本語法
Verilog 的 always 敘述是一種根據特定條件(敏感度列表)反覆執行處理的描述方式。基本語法如下:
always @(敏感度列表)
begin
// 要執行的處理
end在這個語法中,最重要的部分是「敏感度列表」。它用來定義當哪些訊號發生變化時,該區塊需要被執行。
always @(*) 的用法(組合邏輯電路)
在組合邏輯電路中,輸入每次變化時,輸出都需要立即更新。在這種情況下,敏感度列表會使用 @(*)。
always @(*) begin
if (a == 1'b1)
y = b;
else
y = c;
end這樣寫的話,當 a、b、c 中任一個訊號發生變化時,always 區塊就會被執行,並重新計算輸出 y。
使用 @(*) 的好處
- 會自動將所有輸入訊號包含在敏感度列表中
- 避免因敏感度列表遺漏而導致邏輯模擬與合成結果不一致
always @(posedge clk) 的用法(時序邏輯電路)
在時序邏輯電路中,狀態會依照時脈訊號同步改變。這時候,敏感度列表需要指定 posedge clk。
always @(posedge clk) begin
q <= d;
end此例中,當時脈的上升沿(posedge)到來時,d 的值會被鎖存到 q。此處使用 <= 非阻塞賦值,這是在時序邏輯中最常用的方式。
posedge 與 negedge
posedge:在上升沿觸發negedge:在下降沿觸發
需要根據設計需求選擇正確的邊緣觸發方式。
always @(posedge clk or negedge rst)(含非同步重置)
在複雜電路設計中,經常需要具備重置功能。以下是帶有非同步重置的寫法:
always @(posedge clk or negedge rst) begin
if (!rst)
q <= 1'b0;
else
q <= d;
end這樣的描述方式,當重置信號為「0」時,q 會立即被清零;其餘情況下則會隨時脈同步鎖存 d。
組合邏輯電路與時序邏輯電路的區別
| 電路類型 | 使用的 always | 特徵 |
|---|---|---|
| 組合邏輯電路 | always @(*) | 輸入改變時輸出立即更新 |
| 時序邏輯電路 | always @(posedge clk) | 依時脈同步觸發動作 |
3. always 敘述中的賦值種類
Verilog 中有兩種賦值方式
在 Verilog 的 always 區塊內,可以使用兩種不同的賦值運算子:
=:阻塞賦值(blocking assignment)<=:非阻塞賦值(non-blocking assignment)
如果在不了解差異的情況下隨意使用,容易造成非預期的行為或模擬結果與合成結果不一致,因此這是非常重要的知識點。
阻塞賦值(=)是什麼?
阻塞賦值表示一個敘述執行完後,才會執行下一個敘述,有點像軟體中的順序流程控制。
always @(*) begin
a = b;
c = a;
end在這個例子中,a = b 會先執行,然後 c = a 使用的是更新後的 a。也就是說,變數的賦值順序會直接影響邏輯結果,因此必須小心。
主要用途
- 組合邏輯電路中的控制結構(
if、case) - 不需要保留狀態的處理
非阻塞賦值(<=)是什麼?
非阻塞賦值表示所有敘述同時被評估,並在時脈邊緣後一起更新,這能表現硬體的並行特性。
always @(posedge clk) begin
a <= b;
c <= a;
end在這裡,a <= b 與 c <= a 是同時評估,並在時脈觸發後一併更新。因此,c 會接收到前一個時脈週期的 a 值。
主要用途
- 時序邏輯電路(暫存器、觸發器)
- 需要準確保存與傳遞多個狀態時
阻塞與非阻塞的差異比較
| 特徵 | 阻塞賦值 (=) | 非阻塞賦值 (<=) |
|---|---|---|
| 執行順序 | 由上而下依序執行 | 同時評估,統一更新 |
| 主要使用場合 | 組合邏輯電路 | 時序邏輯電路 |
| 反映時機 | 立即更新 | 時脈邊緣後更新 |
| 常見錯誤 | 意外產生鎖存器(Latch) | 數值未更新或傳遞錯誤 |
混合使用會怎樣?
原則上,= 與 <= 不應在同一個區塊或同一個訊號上混合使用。例如:
always @(posedge clk) begin
a = b;
a <= c;
end在這個例子中,a 被賦值兩次,而且使用了不同的方式,會導致最終數值不確定,模擬與實際電路行為可能不同。
使用準則
- 組合邏輯電路 → 使用
=(阻塞賦值,寫在always @(*)區塊內) - 時序邏輯電路(時脈同步) → 使用
<=(非阻塞賦值,寫在always @(posedge clk)區塊內)
只要遵守這個規則,就能避免大部分常見的錯誤。
4. 使用 always 敘述時的注意事項與常見錯誤
敏感度列表(sensitivity list)的描述錯誤
若未正確指定應感知的訊號,將成為錯誤的來源
在 Verilog 中,always 的敏感度列表(@(...))必須明確寫出哪些訊號變化時要觸發。以下是僅列出部分訊號的錯誤範例:
always @(a) begin
if (b)
y = 1'b1;
else
y = 1'b0;
end在這段程式中,b 的變化不會觸發 always 區塊,因此即使 b 改變,輸出 y 也不會更新,造成錯誤。
解決方案:使用 @(*)
為了避免遺漏或忘記敏感度列表中的訊號,建議使用 @(*):
always @(*) begin
if (b)
y = 1'b1;
else
y = 1'b0;
end@(*) 會自動將程式中所有被參考的訊號納入敏感度列表,提升程式的可維護性與安全性。
意外產生鎖存器(Latch)
if 或 case 未涵蓋所有情況,會導致 Latch
若在條件分支中並未對所有情況賦值,合成工具會認為「需要保存先前的數值」,進而生成鎖存器。例如:
always @(*) begin
if (enable)
y = d; // 當 enable=0 時,y 未被更新
end這段程式看似正常,但當 enable=0 時,y 不會更新,導致自動插入 Latch 保存舊值。
解決方案:確保所有情況皆有賦值
always @(*) begin
if (enable)
y = d;
else
y = 1'b0; // 所有情況都有賦值
end這樣就能避免不必要的 Latch 生成。
條件分支過於複雜
如果 if 或 case 條件過於複雜,且未涵蓋所有可能輸入,可能導致未定義的行為或邏輯漏洞。
常見錯誤:缺少 case 的 default
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
// 2'b11 的情況未定義
endcase
end因為沒有處理 2'b11,所以可能產生未定義輸出。
解決方案:加上 default
always @(*) begin
case(sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
default: y = 1'b0; // 安全保護
endcase
end透過 default 節,無論輸入為何,輸出都會有定義,提升設計可靠性。
同時控制多個訊號時的注意事項
當一個 always 區塊同時控制多個訊號時,賦值順序或遺漏可能會導致非預期的依賴關係。在複雜電路中,建議將控制拆分為多個 always 區塊,以提高可讀性與安全性。
常見陷阱整理
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 輸出未更新 | 敏感度列表缺少必要訊號 | 使用 @(*) 自動感知 |
| 意外產生 Latch | 部分條件未進行賦值 | 使用 else 或 default 確保完整覆蓋 |
| 未定義行為 | case 未涵蓋所有條件 | 務必加入 default |
| 控制過於複雜 | 同一區塊同時操作多個訊號 | 拆分多個 always 區塊 |
5. SystemVerilog 中 always 敘述的擴充
always_comb:組合邏輯專用
概要
always_comb 與傳統的 always @(*) 幾乎相同,但它明確表示該區塊是組合邏輯。
always_comb begin
y = a & b;
end主要優點
- 自動生成敏感度列表
- 當設計中不小心產生 Latch 時,工具會發出警告
- 避免與先前定義的同名變數互相干擾
使用範例(與 Verilog 比較)
// Verilog
always @(*) begin
y = a | b;
end
// SystemVerilog
always_comb begin
y = a | b;
endalways_ff:時序邏輯專用(觸發器)
概要
always_ff 用來描述時脈驅動的時序邏輯,必須指定觸發條件,例如 posedge clk 或 negedge rst。
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 1'b0;
else
q <= d;
end主要優點
- 僅允許使用
<=(非阻塞賦值),若使用=會報錯 - 工具會檢查敏感度列表是否正確
- 一眼就能辨識該區塊為「時序邏輯」,可讀性與維護性更高
always_latch:鎖存器專用
概要
always_latch 用於有意識地描述鎖存器(level-sensitive latch)。不過,為了避免誤用,建議僅在必要時使用。
always_latch begin
if (enable)
q = d;
end注意事項
- 若條件分支允許跳過賦值,會明確生成 Latch
- 除非設計需求必須,否則應盡量避免使用
SystemVerilog 語法的使用區分
| 語法 | 用途 | 對應 Verilog | 特徵 |
|---|---|---|---|
always_comb | 組合邏輯 | always @(*) | 自動生成敏感度列表,可檢測 Latch |
always_ff | 觸發器(時序邏輯) | always @(posedge clk) | 時脈同步,避免錯誤賦值 |
always_latch | 鎖存器 | always @(*)(條件分支不完整) | 僅用於有意圖的 Latch,工具可檢測誤用 |
未來設計趨勢:SystemVerilog 為主流
近年來,開發現場越來越多推薦使用 SystemVerilog 語法,因為其可讀性與安全性更佳。EDA 工具也不斷進化,透過 always_ff 與 always_comb 可以降低「看似正確卻無法運作」的錯誤。
特別是在團隊合作與大型專案中,語法能讓設計意圖更加清晰,提升程式審查與後續維護的效率。
6. FAQ:關於 always 敘述的常見問題與解答
本章將針對 Verilog 與 SystemVerilog 中 always 敘述的使用,解答設計實務上常遇到的疑問與搜尋熱門問題,幫助初學者到中階工程師快速釐清觀念。
Q1. 在 always 區塊中,該使用 if 還是 case?
A. 基本原則是依條件數量與邏輯複雜度來選擇:
- 條件只有 2~3 個 → 使用
if較簡潔 - 多種明確的狀態切換 → 使用
case更清晰且可讀性高
此外,case 通常要求涵蓋所有情況,能降低設計遺漏的風險。
Q2. 如果省略敏感度列表會發生什麼?
A. 若敏感度列表未完整指定,部分訊號變化不會觸發 always 區塊,導致輸出無法更新。
這會造成模擬結果與實際電路行為不一致。最佳解法是使用 @(*) 或 SystemVerilog 的 always_comb。
Q3. 為什麼 always 會產生非預期的 Latch?
A. 當條件分支(if、case)中沒有涵蓋所有輸入情況時,合成工具會推斷需要保存先前值,因而生成 Latch。
錯誤範例:
always @(*) begin
if (en)
y = d; // 當 en==0 時,y 保持不變
end正確寫法:
always @(*) begin
if (en)
y = d;
else
y = 1'b0; // 確保所有情況皆有賦值
endQ4. 可以在同一個區塊混用 = 與 <= 嗎?
A. 原則上不建議。常見規則如下:
- 組合邏輯 → 使用
=(阻塞賦值) - 時序邏輯 → 使用
<=(非阻塞賦值)
若同一個訊號同時被 = 與 <= 賦值,可能模擬正常但實際電路錯誤。
原則:
同一訊號應保持一致的賦值方式。
Q5. always_ff 與傳統 always @(posedge clk) 有什麼差別?
A. 功能上相近,但 always_ff 在安全性與可讀性上更佳。
| 比較項目 | always @(posedge clk) | always_ff |
|---|---|---|
| 敏感度列表 | 需自行指定 | 工具會自動檢查 |
| 賦值方式 | 允許 = 或 <=(可能出錯) | 僅允許 <=,錯誤立即檢出 |
| 可讀性 | 無法明確區分用途 | 一眼就能辨識為「時序邏輯」 |
Q6. 在 always 中同時控制多個訊號可以嗎?
A. 可以,但越多訊號會降低可維護性。建議在以下情況下拆分為多個 always 區塊:
- 不同輸出彼此獨立
- 同步控制與非同步控制混雜
Q7. 如果在組合邏輯中使用 <=,會怎樣?
A. 雖然模擬可能正常,但在邏輯合成時可能產生預期外的電路。因此,組合邏輯應使用 =(阻塞賦值)。
7. 總結
always 敘述是 Verilog 設計的基礎與核心
在 Verilog 的硬體設計中,always 敘述是一個能同時描述組合邏輯與時序邏輯的強大工具。它不僅能擴展設計的表達能力,還能清楚描述控制流程與時序,因此對從初學者到專業工程師而言,都是必須掌握的核心知識。
本文重點解說了以下內容:
本文重點回顧
always @(*)與always @(posedge clk)的差異與使用場景=(阻塞賦值)與<=(非阻塞賦值)的差異與正確用法- 敏感度列表的寫法、避免產生 Latch 等常見錯誤的解決方法
- SystemVerilog 新增的
always_comb、always_ff、always_latch語法與其優點 - 實務開發中常見問題的 FAQ 整理與解答
程式描述的精確度決定設計品質
硬體描述語言有一個特點:「電路會依程式描述直接生成」。因此,任何小錯誤都可能直接導致電路不良。特別是 always 區塊,因為負責描述主要邏輯行為,必須特別注意:
- 語法是否精確
- 賦值方式是否正確
- 條件分支是否完整涵蓋
下一步:邁向更高階的設計
當能正確使用 always 後,接下來可以挑戰以下進階主題:
- 有限狀態機(FSM:Finite State Machine) 的設計
- 管線化(Pipeline)與串流處理 的實作
- IP Core 的開發 以及FPGA 實現
同時,若能進一步學習 SystemVerilog 或 VHDL 等語言,就能在更多專案與設計環境中發揮專業能力。
最後的提醒:設計者的心態
硬體設計並非「能動就好」,而是要正確運作、並能適應未來擴充與修改。
希望透過本文,讀者不僅能掌握 always 敘述的語法,也能培養穩健、安全的設計思維。



