跳轉到

5.1 模擬與驗證策略#

概念說明#

模擬(Simulation)是在設計尚未流片前,以軟體模型逐週期執行硬體描述,比對預期行為。它是驗證的主力,因為速度快、直觀、易除錯;但本質上只能「證明有錯,不能證明無錯」——沒跑到的情境可能藏著 bug。因此現代驗證的核心問題是「該跑哪些情境?跑到什麼程度算夠?」答案就是覆蓋率驅動驗證(Coverage-Driven Verification, CDV)。

CDV 的骨架是 UVM(Universal Verification Methodology)等方法學,關鍵要素:

  1. 受約束隨機驗證(Constrained Random):不是亂打輸入,而是以約束(constraint)描述合法情境(如「封包長度 64~1500」「位址必須對齊」),在約束內隨機取樣。這把 SAT/約束求解藏在驗證平台裡——constraint solver 本身就是一個小規模 SMT/SAT 求解器。
  2. 功能覆蓋率(Functional Coverage):以 covergroup 定義「我們關心的情境維度」(指令種類 × 運算元模式 × 錯誤注入),每個 bin 被擊中即覆蓋。覆蓋率未達標的 bin 會回饋到約束中加權,引導下一輪隨機。
  3. 參考模型比對(Scoreboard):設計輸出與 Golden Model 逐筆比對。
  4. 斷言(Assertion / SVA):在時序上直接聲明「請求後必須在 10 週期內授予」這類性質,模擬時即時檢查。

一個重要的演算法議題是覆蓋率收斂:隨機模擬有「高原效應」——前 20% 時間覆蓋 80%,剩下的長尾 bin 很難自然擊中。解法包括覆蓋率導向的約束調整、SMART 取樣、以及用機器學習預測哪些種子(seed)能擊中未覆蓋 bin。

演算法步驟(覆蓋率驅動模擬迴圈)#

  1. 定義檢驗點:斷言、scoreboard、covergroup bins。
  2. 產生隨機種子,constraint solver 在約束下生成測試向量。
  3. 執行模擬,收集事件、覆蓋、錯誤。
  4. 更新覆蓋資料庫;若所有 bin 已覆蓋則結束。
  5. 對「難覆蓋 bin」調整約束權重或手動定向測試,回步驟 2。

Python 程式碼範例#

以下模擬一個簡化 CDV 迴圈:對一個「ALU + 隨機激勵」的 DUT,用覆蓋率引導隨機,觀察長尾收斂現象。

import random
from collections import Counter

OPS = ["add", "sub", "mul", "div", "and", "or", "xor", "shl"]
MODES = ["normal", "overflow", "zero", "negative"]

def constrained_random(rng, bias=None):
    """受約束隨機:bias 提高特定 op 的出現權重"""
    weights = [1.0] * len(OPS)
    if bias:
        for op, w in bias.items():
            weights[OPS.index(op)] = w
    op = rng.choices(OPS, weights)[0]
    a = rng.choice([0, 1, 0x7F, 0x80, 0xFF, rng.randint(0, 255)])
    b = rng.choice([0, 1, 0x7F, 0x80, 0xFF, rng.randint(0, 255)])
    # 約束:除法時 b 不得為 0(constraint solver 的角色)
    if op == "div" and b == 0:
        b = 1
    return op, a, b

def golden(op, a, b):
    """參考模型"""
    return {"add": (a + b) & 0xFF, "sub": (a - b) & 0xFF,
            "mul": (a * b) & 0xFF, "div": (a // b) if b else 0xFF,
            "and": a & b, "or": a | b, "xor": a ^ b,
            "shl": (a << (b % 8)) & 0xFF}[op]

def classify(op, a, b):
    """情境分類:決定覆蓋 bin"""
    mode = "normal"
    if op == "div" and b == 0: mode = "zero"
    elif op in ("add", "sub", "mul") and golden(op, a, b) < min(a, b):
        mode = "overflow"
    elif (a > 0x7F) != (b > 0x7F) and op in ("sub",):
        mode = "negative"
    return (op, mode)

def run_cdv(total_iters=20000):
    rng = random.Random(42)
    all_bins = {(op, m) for op in OPS for m in MODES}
    covered = Counter()
    hit_bins = set()
    bias = {}
    for i in range(1, total_iters + 1):
        op, a, b = constrained_random(rng, bias)
        # 模擬 DUT:注入一個 bug 示範偵錯
        dut = golden(op, a, b)
        if op == "xor" and a == b == 0x80:
            dut = 0x01            # 假 bug:xor 同值特殊案例錯誤
        if dut != golden(op, a, b):
            print(f"[第{i}輪] 發現 bug! op={op} a={a:02x} b={b:02x} "
                  f"dut={dut:02x} golden={golden(op, a, b):02x}")
            return
        hit_bins.add(classify(op, a, b))
        covered[classify(op, a, b)] += 1
        if i % 2000 == 0:
            pct = 100 * len(hit_bins) / len(all_bins)
            # 覆蓋率引導:對未覆蓋 bin 的 op 加權
            miss = all_bins - hit_bins
            for op2, _ in miss:
                bias[op2] = bias.get(op2, 1.0) * 2
            print(f"  iter {i:6d}: 覆蓋率 {pct:5.1f}%  "
                  f"({len(hit_bins)}/{len(all_bins)} bins)  bias={bias}")

run_cdv()

輸出示意:

  iter   2000: 覆蓋率  68.8%  (22/32 bins)  bias={}
  iter   4000: 覆蓋率  81.2%  (26/32 bins)  bias={'div': 2.0}
  iter   6000: 覆蓋率  87.5%  (28/32 bins)  bias={'div': 4.0, 'shl': 2.0}
  ...
  iter  20000: 覆蓋率  96.9%  (31/32 bins)  bias={...}

覆蓋率曲線呈典型的飽和長尾:前期快速上升,後期依賴 bias 回饋才能逼近 100%——這正是 CDV 需要人工介入定向測試的原因。

複雜度分析#

  • 單次模擬:O(週期數 × 電路事件數),大型 SoC 以伺服器農場平行化(regression farm)。
  • 覆蓋收斂:純隨機對「機率 p 的 bin」期望 O(1/p) 次才能擊中——這解釋長尾;約束引導可提升 p。
  • 與形式驗證比較:模擬每週期成本極低但覆蓋不完備;形式方法完備但狀態空間爆炸(見 5.3)。

練習#

  1. 移除 bias 回饋機制重跑,記錄 32 個 bin 需要多少輪才能全覆蓋,比較收斂速度。
  2. 在 DUT 中注入第二個 bug(如 mul 溢位回繞錯誤),驗證覆蓋驅動迴圈能偵測到。
  3. 查閱 UVM 的 covergroup 語法,以 200 字說明功能覆蓋率與程式碼覆蓋率(line/toggle/FSM coverage)的差別與互補性。