逻辑门电路搭建
上一讲我们认识了四种基本逻辑门。这一讲,我们把它们连接起来,搭出能解决实际问题的电路。
单个逻辑门就像一颗螺丝钉——本身功能有限。但把它们组合起来,就能构建出任意复杂的功能。
生活化类比:乐高积木搭建城堡
想象你面前有四种颜色的乐高积木:
- 绿色积木 = AND 门:只有两端都连接时才能通电
- 橙色积木 = OR 门:任意一端连接就能通电
- 红色积木 = NOT 门:通电变断电,断电变通电
- 紫色积木 = XOR 门:两端状态不同时通电
用这几块积木,你可以搭出什么?
一个投票器:三个人投票,少数服从多数。这是一个典型的组合逻辑电路——把 AND/OR 门连起来就能实现。
一个密码锁:需要同时按下正确的几个键才能开锁。这本质上是 AND 门的级联——每个键对应一个输入,只有所有输入都为 1 时才输出 1。
一个报警器:任意一个传感器触发就报警。这本质上是 OR 门的应用——只要有一个传感器输出 1,报警器就响。
组合逻辑 vs 时序逻辑
在深入电路搭建之前,先区分两个重要概念:
| 类型 | 特点 | 输出取决于 | 有记忆吗? | 例子 |
|---|---|---|---|---|
| 组合逻辑 | 输出仅由当前输入决定 | 当前输入值 | 没有 | 加法器、多数表决器、译码器 |
| 时序逻辑 | 输出由当前输入 + 历史状态决定 | 当前输入 + 之前存的值 | 有 | 计数器、寄存器、状态机 |
本讲关注组合逻辑。时序逻辑将在第 9 讲详细介绍。
简单区分方法:组合逻辑是「你输入什么,我就输出什么」(像数学函数);时序逻辑是「我还记得你之前输入过什么」(像人的记忆)。
电路搭建方法论:三步法
用逻辑门搭建任意组合逻辑电路,有一个通用的三步流程:
- 列出真值表:把输入的所有组合列出来,写出每种情况下期望的输出。
- 写出逻辑表达式:对于输出为 1 的每一行,写出对应的「与项」(各输入的与组合),再用 OR 连接所有与项。这叫做「积之和」(Sum of Products)形式。
- 画电路图 / 写代码:把逻辑表达式翻译成门电路连接。
下面我们通过三个由浅入深的例子来实践这个方法。
案例一:两输入密码锁
需求:设计一个简单的密码检查电路。有两个输入 A 和 B,只有当 A=1 且 B=1 时输出 1(解锁),其他情况输出 0(锁定)。
这个需求其实就是一个 AND 门。但我们可以用它来演示三步法:
第一步:真值表
| A | B | 期望输出 Y |
|---|---|---|
| 0 | 0 | 0(锁) |
| 0 | 1 | 0(锁) |
| 1 | 0 | 0(锁) |
| 1 | 1 | 1(解锁) |
第二步:逻辑表达式
只有最后一行输出为 1。对应的与项是:A AND B。
所以:Y = A AND B
第三步:电路实现
只需要一个 AND 门,A 和 B 作为输入,输出就是 Y。
太简单了?下面来看一个有实际意义的例子。
案例二:多数表决器(核心案例)
需求:三个人(A、B、C)对提案进行投票表决(1 表示赞成,0 表示反对)。当至少两人赞成时,提案通过(输出 1)。
这是一个经典的三输入多数表决器,也是数字电路教材中的必学案例。
第一步:列出真值表
三输入共有 23 = 8 种组合:
| A | B | C | 赞成人数 | 是否通过 Y | 说明 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 无人赞成 |
| 0 | 0 | 1 | 1 | 0 | 仅 C 赞成 |
| 0 | 1 | 0 | 1 | 0 | 仅 B 赞成 |
| 0 | 1 | 1 | 2 | 1 | B 和 C 赞成 |
| 1 | 0 | 0 | 1 | 0 | 仅 A 赞成 |
| 1 | 0 | 1 | 2 | 1 | A 和 C 赞成 |
| 1 | 1 | 0 | 2 | 1 | A 和 B 赞成 |
| 1 | 1 | 1 | 3 | 1 | 全票通过 |
第二步:写出逻辑表达式
找出输出为 1 的行(第 4、6、7、8 行),每行写一个与项:
- 第 4 行:NOT(A) AND B AND C
- 第 6 行:A AND NOT(B) AND C
- 第 7 行:A AND B AND NOT(C)
- 第 8 行:A AND B AND C
用 OR 连接:Y = (NOT(A) AND B AND C) OR (A AND NOT(B) AND C) OR (A AND B AND NOT(C)) OR (A AND B AND C)
这个表达式虽然看起来有点长,但逻辑上非常清晰:Y 为 1 当且仅当任意两个或三个输入为 1。
第三步:用 SVG 画出电路图
三个 AND 门分别检测「AB」、「BC」、「AC」三对组合是否为 11,然后 OR 门汇总结果。
交互演示:多数表决器的 Python 实现
实例
# 需求:三个人投票,至少两人赞成(1)时才通过(输出 1)
# ----- 复用第 6 讲的基本逻辑门 -----
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def OR(a, b):
return 1 if a == 1 or b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
# ----- 方法一:积之和(SOP)标准形式 -----
def majority_voter_sop(a, b, c):
"""
多数表决器的「积之和」实现。
直接从真值表推导:找出所有输出为 1 的行,
每行写一个与项,再用 OR 连接。
"""
term1 = AND(AND(NOT(a), b), c) # ~A·B·C
term2 = AND(AND(a, NOT(b)), c) # A·~B·C
term3 = AND(AND(a, b), NOT(c)) # A·B·~C
term4 = AND(AND(a, b), c) # A·B·C
return OR(OR(term1, term2), OR(term3, term4))
# ----- 方法二:优化简化形式 -----
def majority_voter_optimized(a, b, c):
"""
多数表决器的优化实现:Y = AB + BC + AC。
这种形式需要的门更少,但逻辑等价于方法一。
为什么等价?
方法一的 SOP 形式展开后,利用布尔代数吸收律可以化简:
~A·B·C + A·~B·C + A·B·~C + A·B·C
= (~A·B·C + A·B·C) + (A·~B·C + A·B·C) + (A·B·~C + A·B·C)
= B·C·(~A+A) + A·C·(~B+B) + A·B·(~C+C)
= B·C + A·C + A·B
"""
return OR(OR(AND(a, b), AND(b, c)), AND(a, c))
# ============================================
# 打印完整真值表,对比两种方法
# ============================================
print("=" * 60)
print(" 三输入多数表决器真值表(RUNOOB 演示)")
print("=" * 60)
print()
print("┌───┬───┬───┬────────┬──────────┬──────────┐")
print("│ A │ B │ C │ 通过? │ SOP 方法 │ 优化方法 │")
print("├───┼───┼───┼────────┼──────────┼──────────┤")
for a in [0, 1]:
for b in [0, 1]:
for c in [0, 1]:
count = a + b + c
passed = "通过" if count >= 2 else "未通过"
sop = majority_voter_sop(a, b, c)
opt = majority_voter_optimized(a, b, c)
print(f"│ {a} │ {b} │ {c} │ {passed} │ {sop} │ {opt} │")
print("└───┴───┴───┴────────┴──────────┴──────────┘")
# ============================================
# 门电路使用统计
# ============================================
print("\n" + "=" * 60)
print(" 两种方法使用的门电路数量对比")
print("=" * 60)
print()
print("方法一(SOP 完整形式):")
print(" NOT 门: 3 个(~A, ~B, ~C)")
print(" AND 门: 4 个(四个与项,每个需要 2 个 2 输入 AND)")
print(" OR 门: 3 个(合并四个与项)")
print(" 总计: 约 14 个基本门调用")
print()
print("方法二(优化形式 Y = AB + BC + AC):")
print(" AND 门: 3 个(AB, BC, AC)")
print(" OR 门: 2 个(合并三个与项)")
print(" 总计: 5 个基本门调用")
print()
print("优化后减少了约 65% 的门电路使用量!")
print("这就是布尔代数化简的工程价值。")
注意真值表到逻辑表达式的转换过程。这是数字电路设计中最核心的技能之一:看到真值表,就能写出表达式;看到表达式,就能画出电路图。三者一一对应。
案例三:一位比较器
需求:比较两个 1 位二进制数 A 和 B。输出三个信号:A>B、A=B、A
这是一个非常重要的基础电路——多位比较器和 CPU 中的条件判断,都是从这里扩展出去的。
第一步:真值表
| A | B | A > B | A = B | A < B |
|---|---|---|---|---|
| 0 | 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 1 | 0 | 0 |
| 1 | 1 | 0 | 1 | 0 |
第二步:逻辑表达式
观察真值表:
- A > B 为 1 当且仅当:A=1 AND B=0。所以:Greater = A AND NOT(B)
- A = B 为 1 当且仅当:A 和 B 相同。所以:Equal = NOT(A XOR B),或者说 Equal = (A AND B) OR (NOT(A) AND NOT(B))
- A < B 为 1 当且仅当:A=0 AND B=1。所以:Less = NOT(A) AND B
注意 A = B 的检测,和 XOR 门关系密切。XOR 输出为 1 意味着「不同」,对其取 NOT 就得到了「相等」。
第三步:代码实现
实例
# 比较两个 1 位二进制数 A 和 B
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def OR(a, b):
return 1 if a == 1 or b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
def one_bit_comparator(a, b):
"""
一位比较器:比较 A 和 B。
返回三元组 (Greater, Equal, Less)
"""
greater = AND(a, NOT(b)) # A > B: A=1 且 B=0
less = AND(NOT(a), b) # A < B: A=0 且 B=1
# A = B: 既不是 greater 也不是 less
# 等价于 NOT(A XOR B),这里用另一种实现
equal = AND(NOT(greater), NOT(less))
return (greater, equal, less)
# 打印真值表
print("=" * 50)
print(" 一位比较器真值表(RUNOOB 演示)")
print("=" * 50)
print()
print("┌───┬───┬────────┬────────┬────────┐")
print("│ A │ B │ A > B │ A = B │ A < B │")
print("├───┼───┼────────┼────────┼────────┤")
for a in [0, 1]:
for b in [0, 1]:
gt, eq, lt = one_bit_comparator(a, b)
print(f"│ {a} │ {b} │ {gt} │ {eq} │ {lt} │")
print("└───┴───┴────────┴────────┴────────┘")
# 测试一些有用的场景
print("\n测试场景:")
a_test, b_test = 1, 0
gt, eq, lt = one_bit_comparator(a_test, b_test)
print(f"A={a_test}, B={b_test}: A>B={gt}, A=B={eq}, A<B={lt}")
print(f"解读:A 大于 B" if gt else ("A 等于 B" if eq else "A 小于 B"))
案例四:2-4 译码器
需求:输入 2 位二进制数(00、01、10、11),让对应的 4 条输出线中的一条为 1,其余为 0。
译码器是 CPU 中的核心部件——当 CPU 要选择某个寄存器时,实质上就是在用译码器。
真值表
| A1 | A0 | Y0 | Y1 | Y2 | Y3 |
|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 0 |
| 1 | 1 | 0 | 0 | 0 | 1 |
逻辑表达式
- Y0 = NOT(A1) AND NOT(A0)
- Y1 = NOT(A1) AND A0
- Y2 = A1 AND NOT(A0)
- Y3 = A1 AND A0
实例
# 输入:2 位二进制数 A1 A0
# 输出:4 条线中恰好一条为 1
def AND(a, b):
return 1 if a == 1 and b == 1 else 0
def NOT(a):
return 1 if a == 0 else 0
def decoder_2to4(a1, a0):
"""
2 线到 4 线译码器。
返回列表 [Y0, Y1, Y2, Y3],
其中恰好有一个为 1,其余为 0。
"""
Y0 = AND(NOT(a1), NOT(a0)) # 00 → Y0
Y1 = AND(NOT(a1), a0) # 01 → Y1
Y2 = AND(a1, NOT(a0)) # 10 → Y2
Y3 = AND(a1, a0) # 11 → Y3
return [Y0, Y1, Y2, Y3]
# 打印真值表
print("=" * 50)
print(" 2-4 译码器真值表(RUNOOB 演示)")
print("=" * 50)
print()
print("┌────┬────┬────┬────┬────┬────┐")
print("│ A1 │ A0 │ Y0 │ Y1 │ Y2 │ Y3 │")
print("├────┼────┼────┼────┼────┼────┤")
for a1 in [0, 1]:
for a0 in [0, 1]:
Y = decoder_2to4(a1, a0)
print(f"│ {a1} │ {a0} │ {Y[0]} │ {Y[1]} │ {Y[2]} │ {Y[3]} │")
print("└────┴────┴────┴────┴────┴────┘")
# 展示一个实际使用场景
print("\n实际应用场景:用译码器选择寄存器")
registers = ["R0", "R1", "R2", "R3"]
for a1 in [0, 1]:
for a0 in [0, 1]:
Y = decoder_2to4(a1, a0)
idx = Y.index(1) # 找到哪条输出线为 1
print(f" 地址 A1A0={a1}{a0} → 选择寄存器 {registers[idx]}")
译码器是「一选多」的经典电路。与之相对的是多路选择器(MUX),它是「多选一」。这两者在 CPU 的数据通路设计中非常常见。译码器用于「选中某个目标」(如选择写哪个寄存器),多路选择器用于「选中某个来源」(如选择 ALU 从哪个寄存器读数据)。
通用门:NAND 和 NOR 的特别之处
实际芯片制造中,NAND 和 NOR 门比 AND 和 OR 门更「底层」。
NAND 门 = AND + NOT,即先做 AND 再取反。NAND 和 NOR 被称为「通用门」(Universal Gates),因为它们各自单独就能实现所有布尔函数。
为什么芯片偏爱 NAND/NOR?因为在 CMOS 工艺中:
- NAND 门只需要 4 个晶体管,而 AND 门需要 6 个(AND = NAND + NOT)
- NAND 门的开关速度更快
- 因此实际芯片中的 AND 功能,底层都是用 NAND + NOT 实现的
下面验证 NAND 门的「通用性」:
实例
# NAND 门单独就可以实现 NOT、AND、OR 三种基本功能
def NAND(a, b):
"""NAND 门:AND 的结果取反。
Y = 0 当且仅当 a=1 且 b=1"""
return 0 if a == 1 and b == 1 else 1
# 用 NAND 实现 NOT:NOT(A) = NAND(A, A)
def NOT_from_NAND(a):
return NAND(a, a)
# 用 NAND 实现 AND:AND(A,B) = NOT(NAND(A,B)) = NAND(NAND(A,B), NAND(A,B))
def AND_from_NAND(a, b):
nand_ab = NAND(a, b)
return NAND(nand_ab, nand_ab)
# 用 NAND 实现 OR:OR(A,B) = NAND(NOT(A), NOT(B))
def OR_from_NAND(a, b):
return NAND(NOT_from_NAND(a), NOT_from_NAND(b))
# ============================================
# 验证
# ============================================
print("=" * 60)
print(" 证明 NAND 门的通用性(RUNOOB 演示)")
print("=" * 60)
print("\n[验证:NAND 实现的 NOT]")
for a in [0, 1]:
expected = 1 if a == 0 else 0
actual = NOT_from_NAND(a)
print(f" A={a}: NOT_from_NAND = {actual}, 期望 = {expected}, {'通过' if actual == expected else '失败'}")
print("\n[验证:NAND 实现的 AND]")
for a in [0, 1]:
for b in [0, 1]:
expected = 1 if a == 1 and b == 1 else 0
actual = AND_from_NAND(a, b)
print(f" A={a}, B={b}: AND_from_NAND = {actual}, 期望 = {expected}, {'通过' if actual == expected else '失败'}")
print("\n[验证:NAND 实现的 OR]")
for a in [0, 1]:
for b in [0, 1]:
expected = 1 if a == 1 or b == 1 else 0
actual = OR_from_NAND(a, b)
print(f" A={a}, B={b}: OR_from_NAND = {actual}, 期望 = {expected}, {'通过' if actual == expected else '失败'}")
print("\n结论:只需 NAND 一种门,就能构造出 ANY 布尔函数。")
print("这就是为什么 nand2tetris 课程从 NAND 门开始就能搭建整台计算机。")
nand2tetris(从与非门到俄罗斯方块)是一个著名的计算机课程。它挑战你只用 NAND 门这一种元件,逐步搭建出 CPU、内存、操作系统,最终运行一个俄罗斯方块游戏。如果你对计算机底层感兴趣,强烈推荐。
交互演示:多数表决器电路搭建器(runoob vis-network 演示)
下方使用 vis-network 可视化了一个三输入多数表决器电路(Y = AB + BC + AC)。点击输入节点 A、B、C 切换 0/1,电路会自动求值并更新所有连线颜色——红色表示信号为 1,灰色表示信号为 0。
