Learning Objectives
- 說明硬體描述語言與一般程式的差別:每一行同時存在
- 讀懂 module、input、output、wire、閘實例與 assign
- 讀懂 always @(posedge clk)、非阻塞賦值與 reg,分辨它們變成正反器還是組合電路
- 認得計數器與有限狀態機的固定寫法,知道合成與模擬的差別
工程師不會一條一條接線
前面的課你一條一條接過線。十幾個閘還可以,一顆真實處理器的閘多到不可能用手接,也沒辦法檢查。工程師改成用文字描述電路,再交給工具轉成閘,這種語言叫硬體描述語言(hardware description language,HDL)。
Verilog 是其中之一,1983 到 1984 年間在 Gateway Design Automation 公司由 Prabhu Goel、Phil Moorby 與 Chi-Lai Huang 開發,後來成為 IEEE 1364 標準(1995、2001、2005 年版)。2009 年它併入 SystemVerilog 的標準 IEEE 1800,目前最新版是 IEEE 1800-2023。另一個常見的硬體描述語言是 VHDL。這一課只教讀,因為你之後看到別人的設計、或請 AI 助教產生 Verilog 時,要看得懂它描述的是什麼電路。
它看起來像程式,但有一個最重要的差別:
一般程式描述「依序做什麼」;Verilog 描述「有什麼東西、怎麼接」。每一行都同時存在。
一塊電路叫做 module。最短的例子是半加器,它只有兩個閘:
module half_adder(
input wire a,
input wire b,
output wire sum,
output wire carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
assign 不是「執行一次賦值」,而是「sum 這條線永遠接著 a ^ b 的結果」。把這兩行對調,電路完全一樣。
閘也可以直接放:and g2 (t0, ns, d0); 的意思是放一個 AND 閘,名字叫 g2,第一個是輸出,後面是輸入。元件之間的內部連線用 wire 宣告。下面的多工器兩種寫法都有。
always、reg 與非阻塞賦值
有記憶的電路要用 always:
always @(posedge clk) begin
q1 <= d;
q2 <= q1;
end
@(posedge clk) 是「每次 clk 上升緣」,所以裡面被指定的東西會變成 D 正反器。左邊被指定的變數要宣告成 reg。
這裡的 <= 是非阻塞賦值(nonblocking assignment):上升緣到的時候,所有右邊先讀舊值,這一拍結束才一起更新。這正好就是正反器的行為:大家同時取樣、同時改變。所以 q2 拿到的是舊的 q1,兩個正反器串起來,q2 比 q1 晚一拍。
如果寫成 =(阻塞賦值,blocking assignment),就是一行一行立刻改,下一行讀到的是新值:q1 = d; q2 = q1; 的 q2 拿到的是 d,等於少了一級,只是兩個正反器存同一個值。
慣用的規則是:描述正反器的 always 用 <=,描述組合電路的 always 用 =。
reg 這個字會騙人。它只表示「這個變數在 always 裡被指定」,不代表一定是暫存器。always @(*) 裡被指定的 reg 是組合電路的輸出。判斷方法是看 always 的條件有沒有 posedge。
用 Python 看兩種賦值的差別
這不是 Verilog,是把兩種賦值的語意用 Python 寫出來。看 q2 的值是怎麼來的。
計數器與有限狀態機的固定寫法
第 8 課接過計數器,2 位元的版本寫成 Verilog 只有幾行。[1:0] 是 2 個位元,2'b00 是 2 位元、二進位、值是 00,posedge rst 表示重設不用等時脈:
module counter2(
input wire clk,
input wire rst,
output reg [1:0] count
);
always @(posedge clk or posedge rst) begin
if (rst) count <= 2'b00;
else count <= count + 2'b01;
end
endmodule
count + 2'b01 這一行不用自己想 XOR 怎麼接,工具會轉成加法電路。這就是用 HDL 的理由:寫要做什麼,細節交給工具,所以你讀的時候要能反過來想「這大概是什麼電路」。
有限狀態機通常寫成兩段,正好對應第 9 課的三塊電路:always @(*) 裡用 case 寫次態邏輯(就是狀態表),always @(posedge clk ...) 寫狀態暫存器,assign 寫輸出邏輯。
模擬、合成與 SystemVerilog
同一段 Verilog 有兩種用法:
- 模擬(simulation):在電腦上算出每個訊號隨時間的變化,用來檢查設計。測試用的程式也用 Verilog 寫,裡面可以有
#10(等 10 個時間單位)、印訊息這些動作。 - 合成(synthesis):把描述轉成實際的閘與正反器。只有一部分的語法能合成:
#10這種延遲,合成工具會忽略,因為閘的延遲是製程決定的,不是你寫了就算。
合成的結果可以放到 FPGA(出廠之後還能重新設定的可程式晶片),或做成 ASIC(為特定用途製造、電路固定的晶片)。
一個常見的陷阱:模擬結果正確,不代表合成出來的電路一樣。例如阻塞賦值的順序、case 沒有寫全,都可能讓模擬與硬體不一致。所以上面那些慣用規則不是風格問題。
現況:Verilog 的標準 2009 年已經併入 SystemVerilog,SystemVerilog 廣泛用於晶片設計與驗證。它是 Verilog 的延伸,舊的寫法大多還能用,並補上比較不會騙人的語法:logic 取代 reg、always_ff 明說這是正反器、always_comb 明說這是組合電路:
always_ff @(posedge clk) begin
q1 <= d;
q2 <= q1;
end
這一課的 Verilog 範例用的是 IEEE 1364-2001 的標準語法,電路是依程式手動對照,不是工具編譯的結果。
確認一下
assign y = a & b; 和 assign z = y | c; 兩行對調,電路會怎樣?
nand g5 (x, a, b); 這一行,哪一個是輸出?
reg y; 在 always @(*) 裡被指定,always 的條件沒有 posedge。y 是什麼?
always @(posedge clk) 裡面寫 q2 <= q1; 再寫 q1 <= d;(和前面的順序相反)。電路會怎樣?
always @(posedge clk) 裡面寫 q1 = d; 再寫 q2 = q1;,d 的變化會怎麼傳到 q2?
程式裡寫了 #10 y = a;,模擬時 y 晚 10 個時間單位才變。合成之後的電路呢?
重點整理
- HDL 描述硬體,不是依序執行的程式:每一行同時存在,順序不影響電路。
module是一塊電路;input、output是接腳;wire是線;閘實例的第一個接腳是輸出;assign是永遠成立的接線。always @(posedge clk)產生正反器,裡面用<=;always @(*)產生組合電路,裡面用=。reg不等於暫存器。- 有限狀態機的固定寫法:
case寫次態、posedge寫暫存器、assign寫輸出,一定要有重設。 - 模擬檢查設計,合成產生閘;只有部分語法能合成。SystemVerilog 是 Verilog 的延伸,廣泛用於晶片設計與驗證。
這條軌道走到這裡,你從一個邏輯閘接到一顆簡單的 CPU,也看得懂工程師實際用的語言了。想繼續往下,可以學計算機組織(管線、快取),或是拿一塊 FPGA 開發板,把這些 Verilog 真的放進晶片。