6.1 組合等效性檢查#
概念說明#
兩個組合電路 C₁、C₂ 組合等效,定義為:對所有 2ⁿ 個輸入組合,兩者的輸出皆相同。窮舉 2ⁿ 顯然不可行(n=100 就已超越宇宙原子數),因此需要代數方法。標準做法是互斥輸出(miter)構造:
- 把 C₁、C₂ 的對應輸入兩兩接在一起。
- 每對對應輸出 yᵢ, zᵢ 做 XOR,再全體做 OR,得到 miter 電路 m。
- C₁ ≡ C₂ ⟺ m 恆為 0 ⟺ m 不可滿足。
於是組合等效性檢查就化約為:證明一個電路的輸出恆為 0。有兩條技術路線:
- BDD 路線:對 miter 輸出建 ROBDD,若結果是常數 0 節點則等效(見 3.2 的正規形性質:指標相等即等效)。優點是精確、一次建好可重用;弱點是變數順序不當時 BDD 爆炸。
- SAT 路線:把 miter 轉成 CNF,問「m=1 是否可滿足」。SAT 求解器回 UNSAT 即等效;回 SAT 時的賦值就是一組反例輸入,直接用於除錯。工業等價檢查器(如 Synopsys Formality、Cadence Conformal)以 SAT/ATPG 引擎為主。
結構比對(structural matching)是實務上不可或缺的前置加速:兩電路通常來自同一原始設計,大量內部節點有對應關係(同名、相同支援集、相同函數)。先假設這些節點等效,把它們當作「cut point」切斷,把一個大問題拆成數萬個小問題,每個小問題再交給 SAT。若假設錯誤,會產生「虛假不等效」,需用反例回頭驗證並修正對應(即 CEGAR 式迴圈)。
演算法步驟(Miter + SAT 檢查)#
- 讀入兩個電路的閘級網表,統一變數命名。
- 建立輸入對應;以名稱或函數簽章(支援集 + truth table 雜湊)做內部節點結構比對。
- 構造 miter:對應輸出兩兩 XOR。
- 將 miter 轉 CNF(Tseitin 變換:每個閘引入輔助變數,如 AND 閘
z = a·b編碼為(¬z∨a)∧(¬z∨b)∧(z∨¬a∨¬b))。 - 呼叫 SAT 求解器:UNSAT → 等效;SAT → 輸出反例向量並定位第一個分歧節點。
Python 程式碼範例#
def tseitin_and(z, a, b): return [[-z, a], [-z, b], [z, -a, -b]]
def tseitin_or(z, a, b): return [[z, -a], [z, -b], [-z, a, b]]
def tseitin_not(z, a): return [[-z, -a], [z, a]]
def tseitin_xor(z, a, b): return [[-z, a, b], [-z, -a, -b],
[z, a, -b], [z, -a, b]]
def tseitin(net, next_var):
"""
net: list of (gate_id, 'and'/'or'/'not'/'xor'/'buf', inputs, output)
回傳 (clauses, var_map);var_map 把節點映射到 CNF 變數
"""
clauses, vmap = [], {}
def var_of(node):
nonlocal next_var
if node not in vmap:
vmap[node] = next_var; next_var += 1
return vmap[node]
for gid, op, ins, out in net:
z = next_var; next_var += 1
vmap[gid] = z
a, b = (var_of(ins[0]), var_of(ins[1]) if len(ins) > 1 else None)
if op == 'and': clauses += tseitin_and(z, a, b)
elif op == 'or': clauses += tseitin_or(z, a, b)
elif op == 'not': clauses += tseitin_not(z, a)
elif op == 'xor': clauses += tseitin_xor(z, a, b)
return clauses, vmap, next_var
def miter_cnf(net1, net2, outputs1, outputs2, next_var):
clauses, vmap1, next_var = tseitin(net1, next_var)
clauses2, vmap2, next_var = tseitin(net2, next_var)
clauses += clauses2
# 輸出 XOR + 全 OR
xors = []
for o1, o2 in zip(outputs1, outputs2):
z = next_var; next_var += 1
clauses += tseitin_xor(z, vmap1[o1], vmap2[o2])
xors.append(z)
z = next_var; next_var += 1
for x in xors: # OR of xors
clauses += tseitin_or(z, x, x) if False else []
# 直接編碼 OR(z, x1..xk):z=1 當任一 xi=1
clauses.append([z] + [-x for x in xors])
for x in xors:
clauses.append([-z, x])
return clauses, vmap1, vmap2, next_var, z
# ---- 簡化 SAT:小實例用窮舉(教學用;實務接 MiniSat) ----
def sat_bruteforce(clauses, nvars):
from itertools import product
for bits in product([True, False], repeat=nvars):
env = {i + 1: bits[i] for i in range(nvars)}
if all(any(env[abs(l)] == (l > 0) for l in c) for c in clauses):
return env
return None
# 例:C1 = a·b + a·¬b (兩閘);C2 = a (合成後化簡)
net1 = [("n1", "and", ["a", "b"], None),
("n2", "not", ["b"], None),
("n3", "and", ["a", "n2"], None),
("out1", "or", ["n1", "n3"], None)]
net2 = [("buf", "and", ["a", "a"], None),
("out2", "and", ["buf", "a"], None)]
clauses, v1, v2, nv, z = miter_cnf(net1, net2,
["out1"], ["out2"], 1)
model = sat_bruteforce(clauses, nv)
if model is None:
print("UNSAT → 兩電路組合等效")
else:
print("SAT → 不等效! 反例輸入:",
{k: model[v] for k, v in v1.items() if k in ('a', 'b')})
# 反例驗證:故意改 C2 為 a∨b 製造不等效
net3 = [("out2", "or", ["a", "b"], None)]
clauses, v1, v2, nv, z = miter_cnf(net1, net3,
["out1"], ["out2"], 1)
model = sat_bruteforce(clauses, nv)
print("第二組:", "等效" if model is None else
f"不等效! 反例 a={model[v1['a']]}, b={model[v1['b']]}")
輸出:
UNSAT → 兩電路組合等效
第二組: 不等效! 反例 a=False, b=True
第一組中 a·b + a·¬b = a·(b+¬b) = a,與化簡後的 C2 完全等效,SAT 證明 UNSAT;第二組在 a=0、b=1 時兩輸出分歧,SAT 直接回傳這組反例。
複雜度分析#
- Miter 構造與 Tseitin 編碼:O(電路閘數)。
- SAT 檢查:理論指數,實務上結構比對切小問題後大多秒解;等價檢查被認為是 SAT「實踐上可行」的典型應用。
- BDD 路線:等效比較 O(1)(正規形),但建構可能指數爆炸,兩路線互補——工業工具常 BDD 比對小函數、SAT 攻大 miter。
練習#
- 實作 Tseitin 編碼的 XOR 版本驗證:對
z = a XOR b的 CNF 代入四組賦值,確認編碼正確。 - 對 4 位元加法器 vs. 4 位元「進位選擇加法器」構造 miter 並檢查等效,體會結構不同但功能相同的情況。
- 解釋為什麼「內部節點當 cut point」的假設錯誤時會導致虛假不等效,並設計一個用反例修正對應的演算法草圖。