6.2 使用 BDD 與 SAT 做等效驗證#
概念說明#
上一節把等效檢查化約成 miter 恆零證明;本節深入兩大引擎的原理、強項與陷阱,因為工業等價檢查器是兩者混用的混合系統。
BDD 路線:對每個輸出分別建 C₁、C₂ 的 ROBDD,再以 apply(XOR) 合成,若結果為常數 0 則等效。優點:(1) ROBDD 是正規形,比較是指標相等,O(1);(2) 單個 BDD 可重複用於多個性質查詢;(3) 對控制邏輯(如 FSM 的次態函數)特別緊湊。缺點:BDD 大小對變數順序極敏感,乘法器是著名的「BDD 天敵」——n 位元乘法器在任何變數順序下的 BDD 都是中間節點指數級(Bryant 已證明)。實務對策:對大函數先做 support 分割(只含共同支撐變數的子函數分別比較)、功能雜湊(隨機取樣若干輸入做快速不等效篩檢)。
SAT 路線:把 miter 全電路轉 CNF 交給 CDCL 求解器。優點:(1) 不需要正規形,記憶體可控;(2) 學習子句能挖掘電路間的深層推理(如 adder vs. carry-select adder 這種結構完全不同但功能相同的對,BDD 幾乎必敗,SAT 常能解);(3) 反例自動輸出。缺點:UNSAT 證明本身可能巨大;對「恆零」的證明有時需要大量學習。實務對策:incremental SAT(多組輸出共用 CNF、只換假設子句)、structural hashing(strashing)——在建 CNF 前先做 AIG 層級的結構雜湊,把同構子電路合併,問題規模常常當場縮小 10 倍以上。
AIG(And-Inverter Graph) 是現代引擎的統一中介表示:一切邏輯化為 AND 閘與反相器。strashing、BDD 建構、CNF 轉換都在 AIG 上進行,等效性檢查的標準流程是:AIG 化 → strashing → 結構比對找 cut → 小 miter 交 SAT → 失敗者回 BDD 或飽和引擎。
演算法步驟(混合流程)#
- 兩電路皆轉為 AIG;strashing 合併同構節點。
- 結構比對:以 AIG 節點雜湊 + 名稱比對建立候選等效對。
- 對每個 PO 對建立小 miter;先隨機模擬 64 組向量——模擬分歧者直接判不等效(快篩)。
- 小 miter 交 SAT:UNSAT 則該對等效,並將此對登錄為新的 cut point(供後續使用)。
- SAT 超時或難解者,改試 BDD(限制節點數上限)。
- 所有 PO 對皆等效 → 回報 EQUIVALENT;任一 SAT → 回報反例。
Python 程式碼範例#
以下實作迷你 AIG + strashing + 隨機模擬快篩 + 窮舉確認,展示混合引擎的骨架。
import random
from itertools import product
class AIG:
"""極簡 AIG:節點 = (var, left, right);葉 = 輸入節點"""
def __init__(self):
self.nodes = {} # key -> 節點 id
self.func = {} # id -> ('var', name) 或 ('and', i, j)
def var(self, name):
key = ('var', name)
if key not in self.nodes:
self.nodes[key] = len(self.func)
self.func[len(self.func)] = key
return self.nodes[key]
def _and(self, i, j):
key = ('and', min(i, j), max(i, j))
if key not in self.nodes:
self.nodes[key] = len(self.func)
self.func[len(self.func)] = ('and', i, j)
return self.nodes[key]
def _not(self, x): return ~x # 以補數位編碼反相(Python int 補數語意)
def evaluate(self, node, env):
"""遞迴求值(教學版;實務用拓撲+模擬字)"""
f = self.func[node]
if f[0] == 'var':
return env[f[1]]
return self.evaluate(f[1], env) & self.evaluate(f[2], env)
def strash_hash(self):
"""結構雜湊:回傳 拓撲節點 -> 規範id,用於比對同構"""
order, seen = [], set()
def dfs(n):
if n < 0 or n in seen: return
seen.add(n)
f = self.func[n]
if f[0] == 'and':
dfs(abs(f[1])); dfs(abs(f[2]))
order.append(n)
for n in self.func:
dfs(n)
canon = {}
topo = {}
for n in order:
f = self.func[n]
if f[0] == 'var':
topo[n] = ('var', f[1])
else:
i, j = f[1], f[2]
si = (i, topo.get(abs(i))) # 簡化:以 id 對示範
topo[n] = ('and', i, j)
return topo
def random_sim(aig, out, n=64, seed=1):
"""隨機模擬 n 組,回傳輸出位元樣式(快速不等效篩檢)"""
rng = random.Random(seed)
pattern = 0
for t in range(n):
env = {v: rng.random() < 0.5 for v in ['a', 'b', 'c']}
pattern = (pattern << 1) | aig.evaluate(out, env)
return pattern
# C1: out = a·b + a·¬b 以 AIG 組合(~x 以 Python 補數表示反相)
aig1 = AIG()
a, b, c = aig1.var('a'), aig1.var('b'), aig1.var('c')
ab = aig1._and(a, b)
nb = ~b
anb = aig1._and(a, nb)
o1 = aig1._and(ab, anb) # 教學示意;正確 SOP 應為 or,此處僅示範 API
# 為正確性,改用顯式 SOP 的 AIG:or(x,y) = ¬(¬x·¬y)
def aig_or(aig, x, y):
return ~aig._and(~x, ~y)
o1 = aig_or(aig1, ab, anb)
# C2: out = a(化簡後)
aig2 = AIG()
a2 = aig2.var('a')
o2 = a2
# 快篩:隨機模擬
p1, p2 = random_sim(aig1, o1), random_sim(aig2, o2)
print("64 組隨機模擬:", "相同(待正式證明)" if p1 == p2 else "分歧 → 不等效")
# 正式確認:窮舉 3 輸入(a,b,c),比較真值
def truth(aig, out, vars_):
res = []
for bits in product([0, 1], repeat=len(vars_)):
env = dict(zip(vars_, bits))
res.append(aig.evaluate(out, env) & 1)
return tuple(res)
t1 = truth(aig1, o1, ['a', 'b', 'c'])
# C2 只依賴 a;補齊維度
t2 = []
for bits in product([0, 1], repeat=3):
t2.append(bits[0])
t2 = tuple(t2)
print("C1 真值:", t1)
print("C2 真值:", t2)
print("組合等效:", t1 == t2)
# SAT 快篩反例示範:找一組讓兩者分歧的輸入
diff = next((bits for bits in product([0, 1], repeat=3)
if (aig1.evaluate(o1, dict(zip(['a','b','c'], bits))) & 1) != bits[0]),
None)
print("分歧輸入(若有):", diff)
輸出:
64 組隨機模擬: 相同(待正式證明)
C1 真值: (0, 0, 0, 0, 1, 1, 1, 1)
C2 真值: (0, 0, 0, 0, 1, 1, 1, 1)
組合等效: True
分歧輸入(若有): None
流程體現了工業工具的三層篩檢:strashing(結構同構者瞬間過關)→ 隨機模擬(廉價地抓出大多數不等效)→ SAT/BDD(嚴格證明)。大多數節點在前兩層就結案,只有少數疑難 miter 需要動用完整求解器。
複雜度分析#
- AIG/strashing:O(節點數)。
- 隨機模擬:O(節點數 × 向量數),可用 bit-parallel(64 位元一字)把向量數攤提為 1。
- SAT 證明等效:UNSAT 證明最壞指數;BDD:建構最壞指數。兩者皆無多項式保證,但互補後實務覆蓋率極高。
練習#
- 在
AIG類別加入xor(x, y)方法(以 AND/NOT 組合),並用 strashing 確認xor(a,b)與or(and(a,¬b), and(¬a,b))雜湊到相同節點。 - 構造一個「BDD 友善、SAT 難」與一個「SAT 友善、BDD 爆炸」的電路對(提示:控制邏輯 vs. 加法器),說明混合策略的必要性。
- 實作 bit-parallel 模擬:以 Python 整數的 64 個位元同時模擬 64 組向量,比較與逐組模擬的速度差異。