现在位置: 首页 > 计算机组成原理 > 正文

从逻辑门到加法器

计算机不会「算数」——它只会用逻辑门一层层搭积木。这一讲我们亲手用 XOR、AND、OR 门搭建出一个能算加法的电路,从半加器到全加器再到 4 位加法器,逐级构建。


生活中的加法——列竖式的启示

回想你在纸上做加法的过程。计算 37 + 58

先算个位:7 + 8 = 15,写 5进 1

再算十位:3 + 5 + 进位1 = 9,写 9,结果是 95

这个过程揭示了三个关键点:

1. 逐位计算:每一位只关心自己的两个加数和来自低位的进位。

2. 进位传播:低位算完可能产生进位,这个进位要传给高位。

3. 最低位特殊:最低位没有来自更低位的进位,只需要处理两个加数。

计算机做加法也是这个思路:把一个大问题(多位加法)拆成许多小问题(1位加法),然后逐个击破。

半加器——最低位的 1 位加法

最低位没有进位输入,只需要把两个 1 位二进制数相加。能处理这种情况的电路叫半加器(Half Adder)

半加器有两个输入 A 和 B,两个输出:和位 Sum进位 Carry

半加器真值表:

ABSum(和)Carry(进位)
0000
0110
1010
1101

观察真值表,一个惊人的巧合出现了:

Sum = A XOR B。当 A 和 B 不同时 Sum=1,相同时 Sum=0——这正是异或门的真值表。

Carry = A AND B。只有两个输入都是 1 时才产生进位——这正是与门的真值表。

半加器只需要 1 个 XOR 门 + 1 个 AND 门就能实现。乔治·布尔恐怕自己都没想到,他发明的代数系统有一天会成为加法器的数学基础。

全加器——处理进位的完整加法

半加器有一个致命缺陷:它不能接收来自低位的进位。除最低位外,每一位都要处理三个数:A、B、来自低位的进位 Cin。

能同时处理两个加数和一个进位输入的电路叫全加器(Full Adder)

全加器真值表:

ABCinSumCout
00000
00110
01010
01101
10010
10101
11001
11111

一个巧妙的设计:用两个半加器就能搭出全加器

第一步:半加器 1 处理 A 和 B,得到中间和 S1 和进位 C1。

第二步:半加器 2 处理 S1 和 Cin,得到最终和 Sum 和进位 C2。

第三步:最终进位 Cout = C1 OR C2(任意一个半加器产生进位,最终就输出进位)。

全加器的电路结构:2 个半加器 + 1 个 OR 门。层层叠加,每个半加器内部又是 XOR + AND。这种分层构建的思想贯穿整个计算机体系结构。

级联全加器——4 位行波进位加法器

有了全加器,多位加法就简单了:把多个全加器串起来,低位的进位输出连到高位的进位输入

一个 4 位加法器的级联结构:

第 0 位(最低位):全加器处理 A0、B0,进位入 = 0,产生和 S0 和进位 C0。

第 1 位:全加器处理 A1、B1,进位入 = C0,产生和 S1 和进位 C1。

第 2 位:全加器处理 A2、B2,进位入 = C1,产生和 S2 和进位 C2。

第 3 位(最高位):全加器处理 A3、B3,进位入 = C2,产生和 S3 和进位 C3(最终进位)。

进位像水波一样从低位逐级「荡漾」到高位,因此这种结构叫行波进位加法器(Ripple Carry Adder)

行波进位加法器简单直观,但有一个性能瓶颈:最坏情况下,进位需要从第 0 位一直传到最高位,经过 N 级全加器的延迟。现代 CPU 使用超前进位(Carry Lookahead)等技术优化,但理解行波进位是理解一切加法器优化的前提。

Python 代码演示

实例

# ============================================
# 第8讲:从逻辑门到加法器 - Python 演示
# runoob 教程系列
# ============================================

def half_adder(a, b):
    """
    半加器:两个 1 位二进制数相加,不考虑进位输入。

    参数:
        a, b: 各为 0 或 1

    返回:
        (sum_bit, carry_out): 和位与进位

    电路实现:
        sum_bit = a XOR b   (异或门)
        carry_out = a AND b (与门)
    """

    sum_bit = a ^ b      # XOR:相同为 0,不同为 1
    carry_out = a & b    # AND:两者都为 1 时才进位
    return sum_bit, carry_out


def full_adder(a, b, carry_in):
    """
    全加器:两个 1 位二进制数相加,考虑进位输入。

    参数:
        a, b, carry_in: 各为 0 或 1

    返回:
        (sum_bit, carry_out): 和位与进位

    实现技巧——用两个半加器级联:
        第一步:half_adder(a, b) → (s1, c1)
        第二步:half_adder(s1, carry_in) → (sum_bit, c2)
        最终进位:carry_out = c1 OR c2
    """

    s1, c1 = half_adder(a, b)
    sum_bit, c2 = half_adder(s1, carry_in)
    carry_out = c1 | c2   # OR 门:任意一个有进位,最终就有进位
    return sum_bit, carry_out


def adder_4bit(a_bits, b_bits):
    """
    4 位行波进位加法器:4 个全加器级联。

    参数:
        a_bits: 4 位二进制列表,a_bits[0] 为最低位(LSB)
        b_bits: 4 位二进制列表,b_bits[0] 为最低位(LSB)

    返回:
        (sum_bits, final_carry): 4 位和(低位在前)与最终进位

    打印进位传播的完整过程,展示行波进位的工作方式。
    """

    carry_in = 0
    sum_bits = []

    print("=" * 55)
    print("  4 位行波进位加法器 —— 进位传播过程")
    print("=" * 55)
    # 高位在前显示,方便人阅读
    print(f"  加数 A: {list(reversed(a_bits))}  (高位在前)")
    print(f"  加数 B: {list(reversed(b_bits))}  (高位在前)")
    print()

    for i in range(4):
        s, carry_out = full_adder(a_bits[i], b_bits[i], carry_in)
        sum_bits.append(s)

        print(f"  [第 {i} 位] (最低位为第 0 位)")
        print(f"    输入: A[{i}]={a_bits[i]}, B[{i}]={b_bits[i]}, 进位入={carry_in}")
        print(f"    输出: 和位={s}, 进位出={carry_out}")

        if carry_out:
            print(f"    >> 产生进位!Carry={carry_out} 将传递给第 {i+1} 位")

        carry_in = carry_out  # 当前进位成为下一位的进位入
        print()

    print(f"  计算结果: {list(reversed(sum_bits))}  (高位在前)")
    print(f"  最终进位: {carry_in}")
    print("=" * 55)

    return sum_bits, carry_in


def decimal_to_4bit(n):
    """将 0-15 的十进制数转为 4 位二进制列表(低位在前)"""
    return [(n >> i) & 1 for i in range(4)]


def bits_to_decimal(bits):
    """将二进制位列表(低位在前)转为十进制整数"""
    return sum(bit * (2 ** i) for i, bit in enumerate(bits))


# ============================================
# 测试用例
# ============================================

print(">>> runoob 半加器测试 <<<")
for a in (0, 1):
    for b in (0, 1):
        s, c = half_adder(a, b)
        print(f"  half_adder({a}, {b}) => sum={s}, carry={c}")
print()

print(">>> runoob 全加器测试 <<<")
test_cases = [
    (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1),
    (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)
]
for a, b, cin in test_cases:
    s, c = full_adder(a, b, cin)
    print(f"  full_adder({a}, {b}, carry_in={cin}) => sum={s}, carry={c}")
print()

print(">>> runoob 4 位加法器测试: 7 + 5 <<<")
a_dec, b_dec = 7, 5
a_bits = decimal_to_4bit(a_dec)
b_bits = decimal_to_4bit(b_dec)
result_bits, final_carry = adder_4bit(a_bits, b_bits)
result_dec = bits_to_decimal(result_bits)
print(f"  期望: {a_dec} + {b_dec} = {a_dec + b_dec}")
print(f"  结果: {result_dec} (进位={final_carry})")
print(f"  runoob 验证: {'通过!' if result_dec == a_dec + b_dec else '失败!'}")
print()

print(">>> runoob 额外测试: 10 + 6 (可能溢出) <<<")
a_bits2 = decimal_to_4bit(10)
b_bits2 = decimal_to_4bit(6)
result_bits2, carry2 = adder_4bit(a_bits2, b_bits2)
result_dec2 = bits_to_decimal(result_bits2)
full_result = result_dec2 + (carry2 * 16)
print(f"  期望: 10 + 6 = 16")
print(f"  4 位结果: {result_dec2}, 进位: {carry2}, 完整 5 位值: {full_result}")
print(f"  runoob 验证: {'通过!' if full_result == 16 else '失败!'}")

运行代码,观察进位如何在每一位之间传播——最低位先算,产生进位后传给下一位,下一位再算,产生进位再往后传。这就是「行波进位」名称的由来。


交互演示:4 位加法器级联动画(runoob 进位传播演示)

下方用纯 div+SVG 模拟了一个 4 位行波进位加法器。点击按钮逐步演示进位如何从第 0 位(最低位)传到第 3 位(最高位),也可以用「自动演示」观看完整过程。

4 位行波进位加法器 —— 进位传播动画

加数 A(高位在前)
加数 B(高位在前)
全加器 #3(最高位)
A=0 B=0
进位入 Cin=0
和 Sum=?
进位出 Cout=?
全加器 #2
A=0 B=0
进位入 Cin=0
和 Sum=?
进位出 Cout=?
全加器 #1
A=0 B=0
进位入 Cin=0
和 Sum=?
进位出 Cout=?
全加器 #0(最低位)
A=0 B=0
进位入 Cin=0
和 Sum=?
进位出 Cout=?
计算结果: -
点击「下一步」开始演示进位传播