在上一课中,我们介绍了 半加器(Half Adder)。
半加器可以把两个二进制位相加。
它有两个输入:
|
1 2 3 |
A B |
并产生两个输出:
|
1 2 3 |
Sum Carry |
只需要两个逻辑门:
|
1 2 3 |
XOR → Sum AND → Carry |
如果我们只需要计算两个二进制位,这已经足够了。
但真正的计算机几乎不会只处理一个二进制位。
CPU 通常需要处理:
|
1 2 3 4 5 |
8-bit 16-bit 32-bit 64-bit |
甚至更大的数据。
一旦开始进行多位二进制加法,就会出现一个新的问题:
某一位的计算,可能需要接收前一位产生的进位。
而半加器没有这个输入。
为了解决这个问题,我们需要一个更完整的加法电路:
全加器(Full Adder)。
全加器可以把三个一位二进制输入相加,并产生两个输出:
|
1 2 3 |
Sum Carry-out |
1. 半加器的问题
先看一个简单的二进制加法:
|
1 2 3 4 |
11 + 01 ---- |
从最右边开始:
|
1 2 |
1 + 1 = 10 |
因此这一位写下:
|
1 2 |
0 |
作为 Sum。
同时产生一个进位:
|
1 2 |
1 |
这个 1 必须送到左边下一位。
于是下一位的计算就不再只是:
|
1 2 |
1 + 0 |
而是:
|
1 2 |
1 + 0 + 1 |
最后这个 1,就是上一位传过来的进位。
问题就在这里。
半加器只有两个输入:
|
1 2 3 |
A B |
它没有地方接收这个额外的进位。
所以,半加器无法独立完成一般的多位二进制加法。
这正是 全加器 要解决的问题。
全加器增加了第三个输入,让前一位产生的进位可以参与当前这一位的计算。
2. 三个输入,两个输出
一个全加器有三个输入:
|
1 2 3 4 |
A B Cin |
其中:
|
1 2 |
Cin = Carry-in |
也就是进位输入。
它产生两个输出:
|
1 2 3 |
S = Sum Cout = Carry-out |
也就是:
|
1 2 3 4 5 6 |
┌─────────────┐ A ───────►│ │ B ───────►│ FULL ADDER │────► S Cin ─────►│ │────► Cout └─────────────┘ |
全加器计算的实际上是:
|
1 2 |
A + B + Cin |
因为三个输入都只有一位,所以最大的情况是:
|
1 2 |
1 + 1 + 1 = 3 |
十进制的 3 用二进制表示就是:
|
1 2 |
11 |
所以两个输出位已经足够:
|
1 2 |
Cout S |
例如:
|
1 2 |
1 + 1 + 1 |
结果为:
|
1 2 3 |
Cout = 1 S = 1 |
也就是:
|
1 2 |
11 |
因此,全加器可以处理从:
|
1 2 |
000 |
到:
|
1 2 |
111 |
这 8 种输入组合,并产生一个最多两位的二进制结果。
3. 全加器真值表
全加器有三个二进制输入。
每个输入都可能是 0 或 1,所以总共有:
|
1 2 |
2 × 2 × 2 = 8 |
种组合。
完整真值表如下:
| A | B | Cin | Sum (S) | Carry-out (Cout) |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 | 1 |
这张表已经完整描述了全加器的行为。
接下来,我们分别研究两个输出:
|
1 2 3 |
Sum Carry-out |
4. 找出 Sum
先只看 Sum:
| A | B | Cin | S |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 0 |
| 1 | 1 | 1 | 1 |
这里有一个非常明显的规律。
当三个输入中有奇数个 1 时:
|
1 2 |
S = 1 |
例如:
|
1 2 3 4 5 |
001 → S = 1 010 → S = 1 100 → S = 1 111 → S = 1 |
这正好符合 XOR 的性质。
因此:
|
1 2 |
S = A XOR B XOR Cin |
用布尔代数符号表示:
|
1 2 |
S = A ⊕ B ⊕ Cin |
为了理解电路,我们可以把它分成两步。
第一步:
|
1 2 |
S1 = A XOR B |
这里的 S1 表示第一个半加器产生的中间 Sum。
第二步:
|
1 2 |
S = S1 XOR Cin |
所以最终:
|
1 2 |
S = A XOR B XOR Cin |
这已经给了我们一个非常重要的提示:
全加器可以用两个半加器连接起来实现。
5. 找出 Carry-out
现在只看 Carry-out:
| A | B | Cin | Cout |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 0 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 |
| 1 | 1 | 0 | 1 |
| 1 | 1 | 1 | 1 |
可以看到:
只要三个输入中至少有两个是 1,Cout 就会变成 1。
例如:
|
1 2 |
0 + 1 + 1 = 10 |
所以:
|
1 2 3 |
S = 0 Cout = 1 |
同样:
|
1 2 |
1 + 1 + 0 = 10 |
也会产生进位。
而:
|
1 2 |
1 + 1 + 1 = 11 |
同样:
|
1 2 |
Cout = 1 |
Carry-out 的一个常见布尔表达式是:
|
1 2 |
Cout = AB + ACin + BCin |
也可以写成更容易对应实际电路的形式:
|
1 2 3 4 |
Cout = (A AND B) OR (Cin AND (A XOR B)) |
如果使用前面定义的中间 Sum:
|
1 2 |
S1 = A XOR B |
那么这个式子还可以写成:
|
1 2 3 4 |
Cout = (A AND B) OR (Cin AND S1) |
这种写法能非常直观地帮助我们理解:
为什么两个半加器再加一个 OR 门,就可以组成一个全加器。
6. 用两个半加器构造全加器
这是本课最重要的概念之一。
我们并不需要从零开始设计全加器。
前面已经学过半加器,所以可以直接利用已有模块:
|
1 2 3 4 |
2 Half Adders + 1 OR Gate |
就可以组成一个完整的 Full Adder。
整个过程可以分为两个阶段。
第一个 Half Adder
第一个半加器负责计算:
|
1 2 |
A + B |
它产生两个输出:
|
1 2 3 |
S1 = A XOR B C1 = A AND B |
其中:
|
1 2 |
S1 |
是第一个半加器产生的中间 Sum。
而:
|
1 2 |
C1 |
是第一个半加器产生的 Carry。
概念上:
|
1 2 3 4 |
A ─────┐ ├──► HALF ADDER 1 ───► S1 B ─────┘ └──► C1 |
但是现在还没有结束。
因为我们还有第三个输入:
|
1 2 |
Cin |
没有参与计算。
7. 第二个 Half Adder
第二个半加器接收两个输入:
|
1 2 3 |
S1 Cin |
其中:
|
1 2 |
S1 = A XOR B |
于是第二个半加器计算:
|
1 2 |
S1 + Cin |
并产生:
|
1 2 3 |
S = S1 XOR Cin C2 = S1 AND Cin |
因此:
|
1 2 |
S = (A XOR B) XOR Cin |
也就是:
|
1 2 |
S = A XOR B XOR Cin |
这就是全加器最终的 Sum 输出。
整个过程实际上非常容易理解:
第一个 Half Adder:
|
1 2 |
A + B |
产生:
|
1 2 |
S1 |
然后第二个 Half Adder 再计算:
|
1 2 |
S1 + Cin |
于是第三个输入 Cin 就被加入了整个计算。
8. 为什么还需要 OR Gate?
现在我们已经有了两个可能的 Carry:
|
1 2 3 |
C1 C2 |
第一个半加器可能产生:
|
1 2 |
C1 = A AND B |
第二个半加器也可能产生:
|
1 2 |
C2 = Cin AND S1 |
而:
|
1 2 |
S1 = A XOR B |
所以也可以写成:
|
1 2 |
C2 = Cin AND (A XOR B) |
无论是:
|
1 2 |
C1 = 1 |
还是:
|
1 2 |
C2 = 1 |
都意味着整个全加器必须产生:
|
1 2 |
Cout = 1 |
因此,只需要用一个 OR Gate 把这两个 Carry 合并:
|
1 2 |
Cout = C1 OR C2 |
完整结构可以表示为:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
┌───────────────┐ A ───────►│ │ │ HALF ADDER 1 │───► S1 ──────┐ B ───────►│ │ │ └───────────────┘ ▼ │ ┌───────────────┐ │ C1 │ │ │ Cin ─►│ HALF ADDER 2 │───► S │ │ │ │ └───────────────┘ │ │ │ │ C2 ▼ ▼ ┌────────────────────────────┐ │ OR │ └────────────────────────────┘ │ ▼ Cout |
因此,这种最直观的 Full Adder 实现一共使用:
|
1 2 3 4 |
2 XOR gates 2 AND gates 1 OR gate |
也就是:
两个半加器 + 一个 OR 门。
9. 完整计算一个例子
假设:
|
1 2 3 4 |
A = 1 B = 0 Cin = 1 |
我们要计算:
|
1 2 |
1 + 0 + 1 |
结果应该是:
|
1 2 |
10 |
下面看看电路是怎样一步一步算出来的。
第一个 Half Adder
首先计算:
|
1 2 |
A XOR B |
也就是:
|
1 2 |
1 XOR 0 = 1 |
因此:
|
1 2 |
S1 = 1 |
再计算 Carry:
|
1 2 |
1 AND 0 = 0 |
所以:
|
1 2 |
C1 = 0 |
第一个半加器得到:
|
1 2 3 |
S1 = 1 C1 = 0 |
第二个 Half Adder
接下来,把:
|
1 2 |
S1 |
和:
|
1 2 |
Cin |
相加。
也就是:
|
1 2 |
1 + 1 |
Sum 为:
|
1 2 |
1 XOR 1 = 0 |
因此:
|
1 2 |
S = 0 |
第二个 Carry 为:
|
1 2 |
1 AND 1 = 1 |
所以:
|
1 2 |
C2 = 1 |
现在我们有:
|
1 2 3 |
C1 = 0 C2 = 1 |
最后经过 OR Gate:
|
1 2 |
Cout = C1 OR C2 |
所以:
|
1 2 |
0 OR 1 = 1 |
得到:
|
1 2 3 |
Cout = 1 S = 0 |
最终结果:
|
1 2 3 |
Cout S 1 0 |
也就是:
|
1 2 |
10 |
与:
|
1 2 |
1 + 0 + 1 = 10 |
完全一致。
10. Half Adder vs. Full Adder
现在,半加器和全加器之间的区别已经非常清楚了。
Half Adder
输入:
|
1 2 3 |
A B |
输出:
|
1 2 3 |
Sum Carry |
它计算:
|
1 2 |
A + B |
Full Adder
输入:
|
1 2 3 4 |
A B Cin |
输出:
|
1 2 3 |
Sum Cout |
它计算:
|
1 2 |
A + B + Cin |
所以,两者最重要的区别,并不仅仅是 Full Adder 使用了更多逻辑门。
真正关键的区别是:
Full Adder 可以接收前一个二进制位产生的 Carry-in。
正因为有了:
|
1 2 |
Cin |
我们才能把多个 Full Adder 一个接一个连接起来。
这也是全加器能够处理多位二进制运算的根本原因。
11. 从一个 Full Adder 到多位二进制加法
现在我们终于可以看到 Full Adder 真正重要的地方。
假设需要把两个 4-bit 二进制数相加:
|
1 2 3 |
A3 A2 A1 A0 B3 B2 B1 B0 |
我们可以为每一个 bit 使用一个 Full Adder。
也就是:
|
1 2 3 4 |
A0 ─┐ B0 ─┼──► FA0 ──Cout──► FA1 ──Cout──► FA2 ──Cout──► FA3 Cin ─┘ |
更完整地表示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
A0,B0 ─► FA0 ─► S0 │ ▼ Carry A1,B1 ─► FA1 ─► S1 │ ▼ Carry A2,B2 ─► FA2 ─► S2 │ ▼ Carry A3,B3 ─► FA3 ─► S3 │ ▼ Final Carry |
关键在于:
前一级 Full Adder 的 Carry-out,会成为下一级 Full Adder 的 Carry-in。
也就是:
|
1 2 3 4 |
Cout0 → Cin1 Cout1 → Cin2 Cout2 → Cin3 |
这样,多个只会计算一位的 Full Adder,就可以共同完成一个完整的多位二进制加法。
一个非常小的逻辑模块,就这样开始扩展成真正接近 CPU 内部算术电路的结构。
12. 为什么 Full Adder 很重要?
Full Adder 看起来只是一个很小的数字电路。
但它解决了二进制运算中一个极其重要的问题:
如何把一个 bit 位置产生的信息传递给下一个 bit 位置。
一个 Full Adder 处理一个 bit。
多个 Full Adder 连接起来,就可以处理很多 bit。
整个发展过程可以表示为:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Logic Gates │ ▼ Half Adder │ ▼ Full Adder │ ▼ Multi-Bit Adder │ ▼ Computer Arithmetic |
我们最开始学习的是:
|
1 2 3 4 |
AND OR XOR |
这些非常简单的布尔逻辑操作。
然后,我们把它们组合成:
|
1 2 |
Half Adder |
再加入 Carry-in,得到:
|
1 2 |
Full Adder |
接着,把多个 Full Adder 连接起来,就可以得到:
|
1 2 |
Multi-Bit Adder |
也就是说:
计算机中的二进制算术,并不是一个神秘的软件过程。
它可以直接从最基础的逻辑门,一层一层构造出来。
Conclusion
**Full Adder(全加器)**是一种可以把三个一位二进制输入相加的数字电路。
它有三个输入:
|
1 2 3 4 |
A B Cin |
和两个输出:
|
1 2 3 |
Sum Cout |
Sum 的逻辑表达式是:
|
1 2 |
S = A XOR B XOR Cin |
Carry-out 可以写成:
|
1 2 3 4 |
Cout = (A AND B) OR (Cin AND (A XOR B)) |
一个最直观的 Full Adder 可以由:
|
1 2 3 4 |
2 Half Adders + 1 OR Gate |
构成。
第一个 Half Adder 计算:
|
1 2 |
A + B |
并产生中间 Sum:
|
1 2 |
S1 = A XOR B |
以及:
|
1 2 |
C1 = A AND B |
第二个 Half Adder 再计算:
|
1 2 |
S1 + Cin |
得到最终的:
|
1 2 |
Sum |
以及第二个 Carry:
|
1 2 |
C2 |
最后 OR Gate 将:
|
1 2 3 |
C1 C2 |
合并成:
|
1 2 |
Cout |
Full Adder 相比 Half Adder 最重要的升级,就是增加了:
|
1 2 |
Carry-in |
正因为可以接收前一个 bit 位置产生的进位,一个 Full Adder 才能与下一个 Full Adder 连接。
于是:
|
1 2 |
1-bit Full Adder |
就能够进一步扩展成:
|
1 2 3 4 5 6 |
4-bit 8-bit 16-bit 32-bit 64-bit |
的多位加法器。
那么接下来,一个自然的问题就是:
如果把多个 Full Adder 连在一起,Carry 一个接一个往前传,会发生什么?
这就引出了下一种非常重要的数字电路:
Ripple-Carry Adder(行波进位加法器)。
