Module 宣告與 port:把 header 變短#
SV module header 的冗長問題#
一個典型的 SystemVerilog module header 長這樣:
module counter #(
parameter int WIDTH = 8
) (
input logic clk,
input logic rst,
input logic en,
output logic [WIDTH-1:0] count
);
#( parameter ... ) 與 ( input/output ... ) 兩層括號、每行重複的 input logic、[WIDTH-1:0] 這種範圍表達式——這些是每個硬體工程師每天要打幾十遍的儀式。冗長不只是美觀問題:[WIDTH-1:0] 與 WIDTH 的關聯是靠人眼維護的,改一個參數名就要改所有 port 行;方向與型別的重複也讓 grep 與重構工具難以可靠處理。
Veryl 的語法:一切都在括號內#
Veryl 把 param 與 port 統一放在同一層括號內,用 param/port 關鍵字區分,並以尾隨逗號收尾,讓 diff 乾淨:
module counter (
param WIDTH: u32 = 8,
port clk: input clock,
port rst: input reset,
port en: input logic,
port count: output logic<WIDTH>,
) {
var cnt: logic<WIDTH>;
always_ff (clk, rst) {
if_reset {
cnt = 0;
} else if en {
cnt = cnt + 1;
}
}
assign count = cnt;
}
注意幾個特點:
param WIDTH: u32 = 8:參數有明確型別,預設值寫在同一行。- port 型別用
logic<WIDTH>而非logic[WIDTH-1:0],位寬語意就是型別參數,不需要在每個 port 重寫範圍表達式。 clock與reset是獨立型別,編譯器能辨識時脈與重置訊號,always_ff (clk, rst)與if_reset都依賴這個型別資訊。- 函式體內
var(可變)、let(單一賦值)、const(常數)三種繫結各有語意,取代 SV 中logic/wire的模糊區分:var對應可被程序賦值的變數,let對應單一驅動的組合連線,const是編譯期常數。
完整範例:FIFO 頂層#
module sync_fifo (
param WIDTH: u32 = 32,
param DEPTH: u32 = 64,
port clk: input clock,
port rst: input reset,
port w_vld: input logic,
port w_rdy: output logic,
port w_data: input logic<WIDTH>,
port r_vld: output logic,
port r_rdy: input logic,
port r_data: output logic<WIDTH>,
) {
const AW: u32 = $clog2(DEPTH);
var w_ptr: logic<AW>;
var r_ptr: logic<AW>;
var mem: logic<WIDTH>;
always_ff (clk, rst) {
if_reset {
w_ptr = 0;
} else if w_vld && w_rdy {
w_ptr = w_ptr + 1;
}
}
always_ff (clk, rst) {
if_reset {
r_ptr = 0;
} else if r_vld && r_rdy {
r_ptr = r_ptr + 1;
}
}
assign w_rdy = w_ptr != r_ptr - 1;
assign r_vld = w_ptr != r_ptr;
}
(此例省略記憶體讀寫細節,專注展示 header 與內部宣告的風格。)const AW: u32 = $clog2(DEPTH) 展示了模組內部常數可以由參數推導,位寬型別 logic<AW> 直接引用它,整條依賴鏈都在型別系統內。
方向推導(direction inference)#
Veryl 允許 port 省略方向:
module adder (
port a: logic<8>,
port b: logic<8>,
port s: logic<8>,
) {
assign s = a + b;
}
編譯器根據訊號在模組內的使用方式推導方向:被讀取的是 input,被驅動的是 output,既讀又驅動的情形則需要顯式標註。這聽起來只是省幾個字,實際上改變了工作流程——方向是可以從行為推導的冗餘資訊,讓機器算比讓人寫更不容易出錯。需要與外部工具對接、或方向有特殊語意(如 inout)時,仍然可以顯式書寫。
歷史脈絡#
module header 的冗長有歷史根源。Verilog(1984)誕生時是Gateway Design Automation 內部的模擬器腳本語言,設計目標是「快速描述小型測試電路」,而不是大型專案的軟體工程。port 方向(input/output/inout)是 1995 年 IEEE 1364 標準化時為了支援綜合與層級連線而強化的,parameter 區塊則來自 VHDL 的 generic 影響。兩層括號的結構在當年是合理的,但三十年間設計規模成長了四個數量級,header 的重複資訊成本被急劇放大——這是典型的「語言演進落後於使用規模」現象。
教育層面也值得注意:因為 header 冗長,大學的數位設計課程普遍讓學生用「複製上一個 module 再改」的方式寫程式,方向與型別的正確性從來不是教學重點。新一代 HDL 把這些冗餘交給編譯器(型別推導、方向推導),教學重點得以回到電路行為本身——這是工具進步對教育內容的反饋。
flowchart LR
A[Verilog 1984<br/>模擬器腳本] --> B[IEEE 1364 標準化<br/>port/param 語法]
B --> C[SV 冗長 header<br/>維護成本高]
C --> D[Veryl<br/>param/port 同層括號 + 方向推導]小結#
Veryl 的 module 宣告把 param 與 port 放進同一層括號,port 方向可省略由編譯器推導,var/let/const 明確區分三種繫結語意。相較 SV 的兩層括號與重複的方向型別宣告,header 更短、依賴更顯式、錯誤更早被攔截。這些改動的共同邏輯是:能推導的資訊就不該讓人手寫——把工程師的時間還給電路本身。