2.1 布林代數與真值表#
概念說明#
布林代數(Boolean Algebra)由英國數學家 George Boole 於 19 世紀提出,是數位電路設計的數學基礎。布林變數只有兩個值:0(假)與 1(真),正好對應電路中的低電位與高電位。三個基本運算是:
- AND(與,記號 · 或省略):全部為 1 結果才為 1
- OR(或,記號 +):任一為 1 結果為 1
- NOT(非,記號 ¬ 或 '):取反
在此之上可以組合出 NAND、NOR、XOR(互斥或)等複合運算。布林運算中最重要的兩條定律源自德摩根(De Morgan):
- ¬(A·B) = ¬A + ¬B
- ¬(A+B) = ¬A · ¬B
德摩根定律是 EDA 的重要工具:NAND 與 NOR 閘在實體上比 AND/OR 閘面積更小、速度更快,所以合成工具會利用德摩根定律大量做 NAND/NOR 重寫。
其他重要恆等式包括:
| 定律 | 形式 |
|---|---|
| 分配律 | A·(B+C) = A·B + A·C;A + B·C = (A+B)·(A+C) |
| 吸收律 | A + A·B = A;A·(A+B) = A |
| 合併律 | A·B + A·¬B = A |
| 補充律 | A + ¬A = 1;A · ¬A = 0 |
| 冪等律 | A + A = A;A · A = A |
真值表(Truth Table) 是布林函數最直接的表示法:對 n 個輸入的所有 2ⁿ 種組合,逐一列出輸出值。真值表的優點是「完備而無歧義」,任何函數對應唯一真值表;缺點是隨變數數呈指數成長——20 個輸入就有一百萬列,因此大規模設計必須改用其他表示法(如 BDD,見 3.2 節)。
兩個關鍵的標準形式:
- 最小項展開(Sum of Products, SOP):把函數寫成一列 AND 項的 OR,每個 AND 項(最小項,minterm)對應一個輸出為 1 的輸入組合。
- 最大項展開(Product of Sums, POS):一列 OR 項的 AND,對應輸出為 0 的組合。
SOP 或 POS 都是「兩層邏輯」:輸入經過一層 AND 閘、再一層 OR 閘。兩層邏輯的好處是延遲可預測,缺點是可能浪費閘數;多級優化將在 3.1 節討論。
演算法步驟:由真值表產生 SOP 並化簡#
- 列出函數 f 的真值表(0 ~ 2ⁿ−1 共 2ⁿ 列)。
- 找出所有 f = 1 的列,將每列轉成最小項:輸入為 1 用原變數、為 0 用取反變數,變數間做 AND。
- 把所有最小項用 OR 連接,得到標準 SOP。
- 反覆套用布定律(合併 A·B+A·¬B=A、吸收)化簡,直到不再縮短。
Python 程式碼範例#
下面的程式把一個函數轉成標準 SOP,並用「字面值集合」表示積項,實作合併與吸收化簡:
from itertools import product
def truth_table(nvars, func):
rows = []
for bits in product([0, 1], repeat=nvars):
rows.append(bits + (int(func(*bits)),))
return rows
def sop_from_truth_table(nvars, func):
names = [chr(ord('a') + i) for i in range(nvars)]
terms = []
for bits in product([0, 1], repeat=nvars):
if func(*bits):
terms.append(tuple(v if b else "¬" + v
for v, b in zip(names, bits)))
return terms
def shrink(term):
"""把 '¬a' 正規化為 ('a', 0),方便比較"""
return frozenset((l.lstrip("¬"), 0 if l.startswith("¬") else 1)
for l in term)
def simplify(nvars, minterms):
"""合併律 + 吸收律的兩層 SOP 化簡"""
cubes = [shrink(t) for t in minterms]
# 步驟1:合併——兩個積項只在同一變數上取值相反且其餘相同
changed = True
while changed:
changed = False
new, used = set(), [False] * len(cubes)
for i, c1 in enumerate(cubes):
for j, c2 in enumerate(cubes):
if i < j and len(c1 ^ c2) == 1 and len(c1) == len(c2):
used[i] = used[j] = True
# 將差一個「相反字面值」的對合併:直接丟掉那兩個對立方
merged_map = {}
for i, c1 in enumerate(cubes):
for j, c2 in enumerate(cubes):
if i < j and len(c1) == len(c2):
diffs = [(v, a, b) for (v, a) in c1
for (v2, b) in c2 if v == v2 and a != b]
if len(diffs) == 1 and c1 - {diffs[0][:2]} == c2 - {(diffs[0][0], diffs[0][2])}:
m = c1 - {diffs[0][:2], (diffs[0][0], diffs[0][2])}
new.add(frozenset(m))
used[i] = used[j] = True
for i, u in enumerate(used):
if not u:
new.add(cubes[i])
if new != set(cubes):
changed = True
cubes = list(new)
# 步驟2:吸收——若 A ⊇ B(A 的約束更少)則 A 是多餘的
final = [c for c in cubes if not any(
c != d and {v for v, _ in d} - {v for v, _ in c} == set()
and len(d) < len(c) for d in cubes)]
return final
def pretty_term(c):
return "·".join(lit + v for v, lit in sorted(c))
data = [("f(a,b) = ab + ab'(可合併為 a)", 2, lambda a, b: a and (b or not b)),
("g(a,b,c) = ab + bc", 3, lambda a, b, c: (a and b) or (b and c))]
for name, n, f in data:
print("==", name)
print("真值表:")
for row in truth_table(n, f):
print(" ", row)
mints = sop_from_truth_table(n, f)
print("標準SOP:", " + ".join("·".join(t) for t in mints))
print("化簡後:", " + ".join(sorted(pretty_term(c) for c in simplify(n, mints))),
"\n")
執行輸出:
== f(a,b) = ab + ab'(可合併為 a)
真值表:
(0, 0, 0)
(0, 1, 0)
(1, 0, 1)
(1, 1, 1)
標準SOP: a·b + a·¬b
化簡後: a
可見 ab + ab' 經合併律化為單一變數 a——電路上從兩個 AND 閘加一個 OR 閘,變成零閘(直接接線),這正是邏輯優化的威力。
複雜度分析#
- 真值表列數:2ⁿ,n 為輸入變數數,屬指數級。
- 標準 SOP 的最小項數最壞為 2ⁿ⁻¹(如奇rey函數)。
- 上面的合併-吸收迴圈最壞 O(3ⁿ·n)(中介積項可能很多),工業上用 Quine-McCluskey 或 Espresso 啟發式(見 2.2、2.3 節)。
練習#
- 將函數 h(a,b,c) = a + b·c 展開成標準 POS(最大項展開),並用程式驗證其真值表與原函數相同。
- 修改
simplify,在吸收步驟中改為「刪除被包含者」的寫法,並用 h(a,b,c) = ab + abd + ac 測試是否化簡為 a(b+c)。 - 用德摩根定律將 y = ¬(a·b) + ¬c 改寫成只用 NAND 閘表示的運算式,寫出每一步恆等變換。