波形的陷阱,以及逃生口
想像第一階那顆 FIFO——一個小小的硬體佇列,一側寫入、另一側讀出。為了說服自己它能運作,你寫了一個小小的[[testbench|測試平台]]:一具不可合成的測試外殼,負責驅動時脈、塞一個值進去、再彈一個值出來,並讓你打開波形檢視器。你塞了 7,彈出 7;塞了 3,彈出 3。你點點頭,繼續往下走。這就是「指定式肉眼檢查」,而它之所以讓人覺得有生產力,正是因為它太輕鬆了。
現在把規模放大。一顆量產等級的 FIFO,會用*數百萬*次隨機化的塞/彈序列來驗證——在反壓之下、在接近空與接近滿時、還會在中途丟入重置。沒有人讀得完一百萬週期的波形——更糟的是,眼睛會說謊。它會滑過那唯一一個輸出差了一位元的週期、那唯一一個「滿」旗號晚了一拍才拉起的角落。最終流出去的臭蟲,幾乎從來不是你盯著看的那些,而是你*略過*的那些。解方就是徹底不再信任眼睛,讓測試平台自己當裁判。
三件絕不能糾纏在一起的工作
驗證裡最重要的架構觀念,就是*關注點分離*。一個好的測試平台會把三件工作放進三個分開的盒子,彼此只透過定義清楚的介面相連。一旦把它們糾纏在一起——每位初學者的第一個測試平台都會這樣——你得到的就是一坨 `initial` 區塊,沒有任何人(包括未來的你)能再去擴充或重用。
- 激勵產生(stimulus generation)——*決定要做什麼*的部分:塞哪些值、何時彈出、何時施加反壓或重置。它懂協定,卻不懂設計的內部。在指定式測試裡它是一段固定腳本;在隨機測試裡,它在合法的約束內擲骰子。
- 受測設計(DUT)——你真正的那顆 FIFO,以可合成的 [[rtl|RTL]] 寫成。測試平台把它包起來,卻絕不伸手進它的內部。DUT 不該知道自己正被測試;它只看得到自己真實的接腳。
- 檢查(checking)——*判定結果是否正確*的部分。它握有一套獨立的「正確性」概念(一個參考模型),並拿它去和 DUT 的輸出相比對。正是這一塊,把「跑一次」變成「通過或失敗」。
為什麼非得分成三個盒子?因為這樣每一個都能在不驚動其他人的情況下改變。把指定式腳本換成隨機產生器,檢查器照樣原封不動地運作——它從來不在乎激勵*從哪裡來*。修好一個 RTL 臭蟲再重跑,同一個檢查器立刻重新判決。這正是你在後面幾階會遇到的整套 UVM 方法論的種子:UVM 本質上就是把這三件工作專業化成可重用的類別,並冠上標準名稱——一個驅動器(driver)、一個監測器(monitor)、一個記分板(scoreboard)。
在戳接腳之上:交易
接下來是讓上面這一切都變得自然的那一次思維躍遷。初學者的測試平台是用*接腳與時脈邊緣*在思考:「把 `wr_en` 拉高、在 `wr_data` 上放 0x2A、等一個時脈、再把 `wr_en` 拉低。」這是訊號線的語言。它累人、易錯,還把你的測試焊死在某個確切的匯流排協定上。專業的人則往上想一層,用交易(transaction)思考:「塞入值 0x2A。」一個動詞、一份酬載。這個塞入*如何*化為接腳的抖動,是別人的問題。
// A transaction is just a bundle of "what happened," not "how the wires moved."
class fifo_txn;
rand bit is_push; // push or pop?
rand bit [7:0] data; // payload for a push
bit [7:0] got; // data observed on a pop (filled by monitor)
endclass
// The driver translates ONE transaction into pin wiggles + clock edges:
// is_push=1, data=0x2A --> wr_en=1; wr_data=0x2A; @(posedge clk); wr_en=0;
// The monitor does the reverse: it watches the pins and rebuilds a transaction.這一層拉高的抽象,一口氣換來三樣東西。重用:就算匯流排協定被重新設計,同一個 `fifo_txn` 與同一套測試也能原封不動地跑——只有驅動器和監測器要改。隨機化:把欄位標上 `rand`,模擬器就能替你生成成千上萬筆合法交易(第三階「約束隨機」的世界)。可讀性:失敗的紀錄會說「塞入 0x2A,預期彈出 0x2A,實際彈出 0x2B」——一句話,而不是一段接腳軌跡。把抽象拉高到訊號線之上,正是「一段腳本」與「一個*驗證環境*」之間的分野。
黃金模型:來自軟體的第二意見
檢查器*怎麼知道*正確答案?它自己保有一份對「設計理應做什麼」的獨立實作——也就是參考模型,或稱*黃金模型*。關鍵在於,它寫在一個截然不同的抽象層級上:不是在閘級精確的 [[rtl|RTL]] 裡,而是寫成樸素的行為式軟體。對我們的 FIFO 而言,黃金模型是計算機科學裡最無聊的那種資料結構:一個佇列。你叫 DUT 塞什麼,你就同步塞進一個軟體佇列;DUT 彈出什麼,你就拿它和佇列彈出的東西相比對。
// The reference model: a behavioral queue, oblivious to gates, timing, RTL.
bit [7:0] golden[$]; // SystemVerilog dynamic queue ($ = unbounded)
// On every PUSH transaction the DUT accepts, mirror it into the model:
golden.push_back(txn.data);
// On every POP, ask the model what SHOULD come out, then compare:
expected = golden.pop_front();
if (txn.got !== expected) begin
$error("FIFO MISMATCH @%0t: expected 0x%0h, got 0x%0h",
$time, expected, txn.got);
fail_count++;
end else
match_count++;把它接起來:記分板迴路
現在把這幾層組裝成一個封閉迴路。激勵把交易送給驅動器,驅動器去抖動 DUT 的接腳。監測器盯著那些接腳,重建出它實際觀察到的交易。激勵的*意圖*與監測器的*觀察*,雙雙流進[[ic-uvm-scoreboard|記分板(scoreboard)]]——那個握著黃金模型、給出判決的盒子。記分板把每一次塞入鏡像進它的佇列,並拿每一次彈出去對照。這個迴路一週期接一週期地跑,全程由邏輯模擬驅動,無須任何眼睛。
STIMULUS DRIVER DUT (RTL) MONITOR SCOREBOARD
(decides what) (txn -> pins) (the FIFO) (pins -> txn) (golden model)
┌──────────┐ ┌────────┐ ┌─────────┐ ┌────────┐ ┌────────────┐
│ push 0x2A│─txn─>│ wiggle │─pins>│ your │pins>│ rebuild│─txn>│ queue + │
│ pop │ │ wires │<pins─│ design │<────│ txns │ │ COMPARE │
└──────────┘ └────────┘ └─────────┘ └────────┘ └─────┬──────┘
│ │
└────────────────── expected pushes ───────────────────────────>┘
v
PASS, or $error @ time T
Three boxes, one interface language (transactions), one automatic verdict.留意這個架構悄悄替你強制了什麼。驅動器與監測器是*刻意*分開的:驅動器知道自己*叫* DUT 做了什麼,但監測器回報的是 DUT 在訊號線上*實際*做了什麼。若兩者不一致——驅動器要求了一次彈出,監測器卻從沒看見它完成——這道落差本身就是一個臭蟲,而這個結構會把它浮上檯面。這種驅動器/監測器的拆分,再加上一個記分板,正是一個 UVM agent 的骨架;你剛剛用樸素的 [[hdl|HDL]] 親手搭出了後面幾階會以可重用函式庫形式交給你的東西。
「完成」是什麼意思,以及接下來往哪走
自我檢查的測試平台,把問題從「波形看起來對嗎?」換成了「全部 N 筆交易都吻合嗎?而且 N 夠大嗎?」前半段如今是自動的。後半段——*N 夠大嗎?我們有沒有操到那些嚇人的角落?*——是後面某一階「覆蓋率」的主題,並且在一開始就由一份[[ic-verification-plan|驗證計畫]]所統御:那是一張寫下來的清單,列出設計必須被證明能處理的每一種行為與角落情況,好讓「完成」成為一張檢查表,而不是一種感覺。自我檢查給你一個可信的「是/否」;計畫則告訴你,你還欠*多少個*「是」。
你現在握住了現代驗證的承重觀念:一個說交易語言的分層測試平台,透過分離的路徑去驅動並監測 DUT,並拿每一個週期去和一個獨立的黃金模型相比對、做出判決。接下來的一切——約束隨機產生、功能覆蓋率、[[verilog|SystemVerilog]] 斷言,乃至整套 UVM 類別函式庫——都是疊*在這同一副骨架上*的精巧化。把這個形狀搭對,這條路徑的其餘部分,就只是把它一格一格填滿而已。