← 返回知识卡片总目录

DS1号 · 自学教材系列 | 计算机软硬件

第 5 章 组合逻辑:从门到加法器

有了逻辑门,下一步就是把它们拼成能算数的电路。这一章我们要亲手搭出一台「加法器」——让一堆晶体管真的能算出 1+1=2。顺着这条路,你还会看到:减法根本不需要单独做,因为上一章的补码早就把答案藏好了。

🎯 本章学习目标

  1. 理解「组合逻辑」的定义:输出只由当前输入决定。
  2. 掌握半加器、全加器的原理与真值表。
  3. 理解多位加法器如何通过「进位串联」工作。
  4. 理解补码如何让减法复用加法电路,以及编码器/译码器/多路选择器的作用。

5.1组合逻辑:输出只看「当下」

由逻辑门拼成、没有「记忆」的电路,叫 组合逻辑电路(combinational logic)。它的定义很纯粹:输出只由「此时此刻」的输入决定,一旦输入变了,输出立刻跟着变。

正因为没有记忆,组合逻辑不能「记住」任何历史。它就像一个纯函数:同样的输入,永远得到同样的输出。这一章,我们先用组合逻辑完成一件看似简单、实则奠基的大事——做加法。

核心思想 组合逻辑 = 无记忆、只由当前输入决定的电路。它是「计算」的基础;而「记忆」的能力,要等到下一章时序逻辑才能获得。

在动手搭电路之前,先把「怎么描述一个组合逻辑」说透。工程师手里有三种等价的语言,说的是同一件事:

数字设计的日常流程就是在这三种语言之间来回翻译:设计时从真值表出发,写出表达式,化简后画出门电路;排查故障时则反过来——从实际电路反推真值表,看它到底在「按什么规则办事」。三种语言互相咬合,你后面每一章都会反复用到这套流程。

不过真值表有个要命的软肋:行数随输入个数指数爆炸——n 个输入就有 2ⁿ 行。3 个输入 8 行,手写无妨;10 个输入 1024 行,勉强能打印;20 个输入超过一百万行,人类已经没法逐行读完。所以真实工程里,工程师不会硬啃一张巨型真值表,而是把大函数按功能切成小块:表决、比较、选择、进位……每块行数可控,各自写成小表达式,再组合起来。本章后面每一节(加法器、译码器、MUX)本质上都是在做这种「功能切块」——切块,是人对抗指数爆炸的第一件武器。

还有一点需要提前澄清:定义里说「输入一变,输出立刻跟着变」——这个「立刻」其实并不严格成立。信号穿过任何一个门都需要时间,通常在皮秒到纳秒量级,我们记每个门的延迟为 τ(tau,希腊字母,工程里常用来表示时间常数)。输入变化后,输出要经过若干个 τ 才「追」到新值并稳定下来。这一点点延迟平时无关紧要,但当几百个门串成一条长链(比如 5.4 节的行波加法器),延迟就会层层累积,成为电路快慢的决定因素。「传播延迟」(propagation delay)这个词,请先记住,马上会用到。

📐 例题 1 三人表决器:从真值表到电路

某电气测试台设了「三人表决」机制:三名机务人员各持一个按钮,按下为 1、松开为 0。至少两人同意(输出 Y=1),测试台才允许通电。请列出真值表,写出 Y 的表达式,并用 A=1、B=0、C=1 的情形验证。

思路 三输入共 2³=8 种组合,逐一判断「是否至少两个 1」,填出真值表;再把表中 Y=1 的每一行翻译成一个「与项」,全部「或」起来——这是从真值表到表达式的标准套路。

解 真值表中 Y=1 的行共有 4 行:101、110、011、111。逐行翻译:
101 → A·B̄·C; 110 → A·B·C̄; 011 → Ā·B·C; 111 → A·B·C。
全部或起来:Y = A·B̄·C + A·B·C̄ + Ā·B·C + A·B·C。
这个式子还能化简:把 111 行分别和另外三行配对合并——111 与 110 合并,消去取值不同的 C,得 A·B;111 与 101 合并得 A·C;111 与 011 合并得 B·C。于是:

Y = A·B + A·C + B·C

验证 A=1、B=0、C=1:A·B = 1·0 = 0;A·C = 1·1 = 1;B·C = 0·1 = 0。三路或起来 Y = 0+1+0 = 1,通电允许。再抽查 111 行:三项全为 1,Y=1,与真值表一致。

答:Y = AB + AC + BC,「任意两人都按下」即通电。 ⚠ 易错点:一是别把「至少两人」错写成 A·B·C(那是三人全按才动作);二是初学时最容易漏掉 111 这一行——它藏了三次,化简后反而让式子变短,这正是下一节「化简」的价值。

这类「多个条件里凑够几个才放行」的逻辑,在工程里有个正式名字,叫多数表决(majority voting):核电站控制、飞行关键系统的传感器判读(比如三余度温度传感器取「至少两个一致」的读数),都靠它提高容错——个别一路传感器坏了、报错了,表决器依然能给出正确结论。你刚推的表达式 AB+AC+BC,就是最常用的三输入表决电路。

更贴近航空维护的例子是联锁(interlock):例如「舱门关闭」信号与「锁定销入位」信号都为 1,才允许执行下一步操作——这就是一个两输入与门。测试台上密密麻麻的通电条件、告警条件,剥开看几乎全是「与、或、非」的组合逻辑。可以说,凡是「条件判断」,就是组合逻辑的主场。

最后把这一节与下一章的关系用一张表钉牢,学完第 6 章回来对照,你会豁然开朗:

组合逻辑 vs 时序逻辑:一张表看懂分界线
对比维度组合逻辑时序逻辑(下一章)
输出由什么决定仅当前输入当前输入+历史状态
有没有反馈回路无(信号单向流动)有(输出绕回输入)
记忆能力无能记住状态
代表电路加法器、译码器、表决器锁存器、寄存器、计数器

5.2半加器:先算 1+1

两个一位的二进制数相加,一共只有四种情况:

一位二进制加法(A + B)
AB和 Sum进位 Carry
0000
0110
1010
1101

观察这张表,你能发现两个熟悉的门:

把「一个异或门 + 一个与门」拼起来,就得到能算一位加法的 半加器(half adder)。

半加器结构 输入A和B进入异或门得到和Sum,同时进入与门得到进位Carry。 A B 异或 XOR 与 AND Sum(和) Carry(进位)
图 5-1半加器 = 异或门(算和)+ 与门(算进位)。

📖 名词溯源:XOR 的「异或」 异或 XOR 全称 exclusive OR(排他的或),和普通 OR 的区别就在「1 和 1」这一行:OR 输出 1,XOR 输出 0。可以理解成「要么你、要么我,不能两个都要」——所以叫「排他」。

半加器解决了「一位加法」,但它有个明显的缺口:它没有考虑「低位传上来的进位」。真正做多位数加法时,每一位除了 A、B,还得加上前一位进过来的进位。这就需要升级版——全加器。

🧠 一句话记忆 半加器 = 异或(和)+ 与(进位),但它不知道「别人进上来的位」。

为什么偏偏是异或和与这两个门?这背后其实是算术本身的规定。二进制「逢二进一」,两个一位数相加,结果不外乎 0、1、2、3 四种,写成「本位+进位」就是:00、01、10、11(进位写在前)。观察规律:

Sum = (A + B) 除以 2 的余数(即 mod 2)= A ⊕ B
Carry = (A + B) 除以 2 的商(即 ⌊(A+B)/2⌋)= A · B

也就是说:「和」是除以 2 的余数,「进位」是除以 2 的商。而异或门恰好就是电路版的「mod 2」——它数一数输入里有奇数个 1 还是偶数个 1(奇数个 1 输出 1);与门则恰好是 1 bit 乘法(都为 1 才为 1)。两个门各司其职,一位加法器就这样从算术规定里「长」了出来。把这个 mod 2 的视角记住,下一节的全加器你会看得飞快。

顺带把异或的代数展开式也留在这里,化简电路时经常用到:

A ⊕ B = Ā·B + A·B̄ (「相异」为 1:A 为 0 且 B 为 1,或 A 为 1 且 B 为 0)

对照真值表读一遍这个式子:00 行两项都为 0;01 行 Ā·B = 1·1 = 1;10 行 A·B̄ = 1;11 行两项都为 0。四个「1」与异或门的行为完全一致——代数式与真值表又一次互为印证。

📐 例题 2 用卡诺图化简一个三输入告警逻辑

某告警电路有三个输入 A、B、C(按 ABC 的二进制取值编号 0~7)。真值表中输出 F=1 的组合编号为 3、4、5、6、7,即 F = Σ(3,4,5,6,7)。直接搭出来需要 5 个三输入与门加 1 个五输入或门。请用卡诺图化简,并说明化简的收益。

思路 卡诺图(Karnaugh map)是一种重排过的真值表:把相邻的输入组合画在相邻的格子里,凡是「逻辑相邻」(只有一位不同)的格子都可以圈在一起合并——被消去的,正是那个「不同」的变量。三变量卡诺图用 A 作行、BC 作列,列必须按格雷码顺序 00、01、11、10 排列,保证相邻两列恰好只差一位。

解 按编号填图(每格标注该格的十进制编号):

三变量卡诺图(A 为行,BC 为列,格雷码排列)
A\BC00011110
00(m0)0(m1)1(m3)0(m2)
11(m4)1(m5)1(m7)1(m6)

第一步,A=1 的整行四个 1(m4~m7)圈成一个四格圈:这四格里 B、C 把 0、1 取了个遍,全被消去,只剩 A。
第二步,A=0 行的 m3(BC=11)与 A=1 行的 m7(BC=11)上下相邻,圈成两格圈:A 被消去,剩 B·C。
两圈相或:

F = A + B·C

验证几行:F(0,1,1)(编号 3)= 0 + 1·1 = 1 ✓;F(1,0,0)(编号 4)= 1 + 0 = 1 ✓;F(0,0,1)(编号 1)= 0 + 0 = 0 ✓。八个组合逐行核对全部吻合。

答:F = A + B·C。电路从「5 个与门+1 个或门、3 级延迟」减到「1 个与门+1 个或门、2 级延迟」——门数、延迟、功耗一起降。 ⚠ 易错点:卡诺图的列是格雷码排列,01 和 10 不相邻,不能隔着 11 圈一圈;另外圈必须圈成 2 的幂次大小(1、2、4、8 格),不能圈 3 格。

这个例子暴露了一个朴素但重要的工程事实:同一个真值表,可以有无数种电路实现,但只有一种最省的。真值表是「合同」,化简是「优化」,门电路是「施工」。今天的芯片设计早就交给 EDA 工具(综合器)自动做这件事,工程师只要提交真值表或高级描述;但工具给出的结果要不要信、为什么长这样,仍需要人懂卡诺图这层原理——尤其是航空电子这类对每一个门都要斤斤计较的场合。

最后辨析一个字眼:半加器的「半」是什么意思?它不是「功能减半」,而是只完成了「本位相加」这一半任务,没接「外来进位」那一半。就像一个只会现做现吃、不会接收别人递碗的厨房:单独用没问题,一到流水线(多位相加)就缺一环。补上这一环的,就是全加器。

5.3全加器:把进位也考虑进去

全加器(full adder)有三个输入:A、B,以及来自低位的进位 Cin;产生两个输出:本位的和 Sum,以及传给高位的进位 Cout。

全加器真值表(A + B + Cin)
ABCinSumCout
00000
00110
01010
01101
10010
10101
11001
11111

全加器的电路,本质上就是两个半加器串起来:先用一个半加器算 A+B,得到中间和与进位;再用第二个半加器把「中间和」与 Cin 相加;两个进位再过一个或门合并,得到最终 Cout。

全加器由两个半加器组成 两个半加器串联,进位通过或门合并。 半加器 1 半加器 2 A B Cin Sum 或门合并两个进位 → Cout
图 5-2全加器 = 两个半加器 + 一个或门。第一个半加器算 A+B,第二个把 Cin 加进去,进位合并。

🧠 一句话记忆 全加器 = 半加器 × 2 + 或门,多了一个「进上来的位」。

从代数上给全加器把把关。三个一位数相加,总和取值 0~3。「本位和」依然是除以 2 的余数,「进位」是商是不是 0。把 5.2 节的 mod 2 视角直接推广:

Sum = A ⊕ B ⊕ Cin (三个输入里 1 的个数为奇数时,本位得 1)
Cout = A·B + Cin·(A ⊕ B) (两种「攒够二」的途径:A、B 本来就凑一对,或中间和再与进位凑一对)

注意 Sum 这一行:三个变量连着异或,数的是「1 的个数的奇偶」——奇数个 1 输出 1。对照真值表数一遍:111 行有三个 1,Sum=1;110 行有两个 1,Sum=0;101 行两个 1,Sum=0……与表格严丝合缝。这就是异或门的「奇偶校验」天赋,将来数据校验(奇偶校验位)用的正是它。

再看两个半加器方案的正确性关键:两个半加器各吐一个进位,最后用一个或门合并——凭什么或门合并不会「丢进位」或「多进位」?把两个进位写出来:第一个半加器的进位 c₁ = A·B,第二个半加器的进位 c₂ = Cin·(A ⊕ B)。假如 c₁ = 1,说明 A、B 都为 1,那么 A ⊕ B = 0,于是 c₂ 必为 0——两个进位不可能同时为 1。既然互斥,「或」起来就既不丢也不重,正是总和的进位。一个看似随手画上的或门,背后藏着一次严格的互斥论证——读电路图时要能看出这层「为什么对」。

📐 例题 3 全加器追踪:1 + 1 + 1 = 11₂

一个全加器,输入 A=1、B=1、Cin=1。请追踪两个半加器内部的每一步信号,求 Sum 与 Cout,并用十进制核对。

思路 按信号流向一步步「过门」:先过半加器 1(A 与 B),再过半加器 2(中间和与 Cin),最后进位过或门。把每个中间信号都写出来,别跳步。

解 第一步,半加器 1:A ⊕ B = 1 ⊕ 1 = 0(中间和 S′ = 0);A·B = 1(进位 c₁ = 1)。
第二步,半加器 2:S′ ⊕ Cin = 0 ⊕ 1 = 1(这就是最终 Sum);Cin·S′ = 1·0 = 0(进位 c₂ = 0)。
第三步,或门合并进位:Cout = c₁ + c₂ = 1 + 0 = 1。
十进制核对:1 + 1 + 1 = 3,二进制 3 = 11₂,即本位 Sum=1、进位 Cout=1,与逐门追踪的结果一致。

答:Sum = 1,Cout = 1,输出「11」即十进制的 3。 ⚠ 易错点:最常见的错误是拿 Cin 直接去和 A、B「三输入一起异或」时忘了它对进位的贡献——Sum 对了但 Cout 算错。记住进位有两条独立来源(A·B 与 Cin·S′),缺一不可。

工程世界里,全加器早已是「标准件」:芯片设计库里躺着现成的、验证过的全加器单元(cell),设计 64 位加法器时不需要重新发明它,直接像搭积木一样例化 64 次,再把注意力放在更高级的进位优化上。复用已验证的模块,而不是每次从头推——这是硬件工程与手工作坊的分水岭。你在例题 3 做的逐门追踪,本质上就是芯片验证工程师每天用仿真器做的工作:把输入打进去,逐拍检查每个内部信号,和「笔算预期」对账。

留一个辨析:既然可以直接做三输入异或门(Sum 一项就够了),为什么教科书都讲「两个半加器级联」?两个原因:其一,Cout 反正要单独的逻辑,而半加器级联把 Sum 与 Cout 的逻辑「打包」在两个熟悉的模块里,出错率低;其二,工程美学——用最少种类的模块搭出一切(还记得与非门完备性吗),比门数省一两个更重要。数字设计里「模块化优先于极致精简」是常态,你会在 CPU 一章再次看到这个哲学。

5.4多位加法器:串联起来算大数

有了全加器,算多位数就简单了:把一个全加器的进位输出,接到下一个全加器的进位输入,一位一位串起来。这种结构叫 行波进位加法器(ripple-carry adder)。

4 位行波进位加法器 四个全加器串联,每个的进位输出接下一个的进位输入。 FA0 FA1 FA2 FA3 进位→ 进位→ 进位→ 最低位 最高位
图 5-34 个全加器首尾相接,进位像「波浪」一样从低位传到高位,所以叫行波进位。

为什么叫「行波」?因为进位信号要一个全加器接一个全加器地「爬」过去——低位算完,进位才传给高位,高位才能开始算。位数越多,这个「波浪」爬得越久,所以行波加法器位数多了会变慢。后面 CPU 一章会看到,工程师如何用「超前进位」等技巧来加速。

核心思想 多位加法 = 全加器串联。进位像接力棒一样从低位传到高位。这就是 CPU 里「加法器」的原始形态——后面所有复杂运算,归根结底都是它在跑。

「爬得越久」到底久多久?现在把 5.1 节埋下的传播延迟 τ 请出来,算一笔明白账。在全加器内部,从 Cin 到 Cout 的进位路径要穿过两个门(半加器 2 的异或、再加或门),即每级 2τ;进位行波爬过 n 级后,最高位还要求和(再过 1τ),于是 n 位行波加法器的最坏总延迟约为:

tripple ≈ (2n + 1)·τ

更关键的问题是:为什么这个进位只能串行爬,不能大家并行算?因为第 i 位的进位输出,恰恰是第 i+1 位的输入——第 3 位要等第 2 位的进位才知道自己的三个输入是什么,而第 2 位又要等第 1 位……信息本身就是按位逐级产生的,电路再快也绕不开这个因果链。想打破它只有一个办法:用更多的门把每一位的进位直接从原始输入「预测」出来——这就是超前进位(carry lookahead):C₁ = A₀B₀,C₂ = A₁B₁ + A₀B₀C₁ = A₁B₁ + (A₀+B₀)·A₁B₁……一路代入展开,进位不再等待,但门的数量和扇入随位数急剧膨胀。快,是用面积换来的。

📐 例题 4 4 位行波加法器:逐位追踪与延迟估算

(a)用 4 位行波加法器计算 0110 + 1101,写出每一位的本位和与进位输出。(b)若每个门延迟 τ = 0.02 ns,估算 4 位与 64 位行波加法器的延迟,并与 3.5 GHz CPU 的时钟周期对比。

思路 (a)像列竖式一样逐位推进,从最低位开始,记清每一位的「本位和」与「向高位的进位」。(b)套用 t ≈ (2n+1)·τ,时钟周期用 T = 1/f 换算。

解 (a)逐位追踪(Cin0 = 0):

0110 + 1101 逐位追踪(从低位到高位)
位AiBiCinSiCout
第 0 位01010
第 1 位10010
第 2 位11001
第 3 位01101

四个本位和从高到低拼起来是 0011,最高位还吐出进位 1,所以完整结果是 10011₂ = 19。十进制核对:6 + 13 = 19 ✓。
(b)4 位:t ≈ (2×4+1)τ = 9τ = 9×0.02 = 0.18 ns;64 位:t ≈ (2×64+1)τ = 129τ = 129×0.02 = 2.58 ns。而 3.5 GHz 的时钟周期 T = 1/(3.5×10⁹) ≈ 0.286 ns。64 位行波加法器的一次加法 ≈ 2.58 ns,约等于 2.58 ÷ 0.286 ≈ 9 个时钟周期——对追求「一个周期出一个结果」的现代 CPU 来说,这样的加法器完全不合格,必须换成超前进位或前缀加法结构。

用 Python 把(a)的逐位过程完整仿真一遍,验证电路行为:

def full_adder(a, b, cin):
    s = a ^ b ^ cin
    cout = (a & b) | (cin & (a ^ b))
    return s, cout

def ripple_add(A, B):          # 4 位行波进位加法
    c, out = 0, 0
    for i in range(4):
        s, c = full_adder((A >> i) & 1, (B >> i) & 1, c)
        out |= s << i
    return out | (c << 4)

print(ripple_add(0b0110, 0b1101))   # 6 + 13 = ?

运行输出:19——最高位进位被并进第 5 位,结果正是二进制 10011。

答:(a)S = 0011、Cout = 1,结果 10011₂ = 19;(b)4 位 ≈ 9τ = 0.18 ns,64 位 ≈ 129τ = 2.58 ns,约为 0.286 ns 时钟周期的 9 倍。 ⚠ 易错点:追踪时忘记「进位要送进下一位」——第 2 位产生的进位 1 必须出现在第 3 位的 Cin 列里;另外别把结果漏掉最高位进位,4 位加 4 位可能得到 5 位结果。

回望历史会更有感觉:1970 年代初的 4 位微处理器,一次加法正好要在几个时钟周期里等行波爬完,算 8 位数就得「低半字节加一次、高半字节带着进位标志再加一次」。那句「多字节运算靠进位标志接力」,到现在依然是嵌入式工程师的基本功——CPU 里专门有「带进位加」(ADC)这条指令,就是为接力准备的。今天的 64 位 CPU 早已用超前进位把一次加法压进一个周期,但例题 4 那笔账,永远是理解「快是拿什么换来的」的起点。

把行波与超前进位摆在一起,就看清了数字设计最核心的一组权衡:

行波进位 vs 超前进位:面积换速度的典型交易
方案门数/面积延迟适用场景
行波进位最少(每级固定 1 个全加器)(2n+1)τ,随位数线性变慢位数少、不赶时间:控制逻辑、地址加法
超前进位急剧增多(进位项展开膨胀)可压到常数级(约 4τ 量级)位数多、要速度:CPU 主加法器

没有免费的午餐——要么花门,要么花时间。往后你在任何一本体系结构书里看到「加法器优化」,本质都是在这两个极端之间找平衡点。

5.5减法与补码:加法器通吃

你可能要问:那减法呢?难道还要单独造一台「减法器」?——不需要。这正是第 2 章补码埋下的伏笔:减去一个数 = 加上它的补码。

回忆补码的「取反加一」。在电路里,这一步出奇地简单:

于是,A − B 就变成「A + (B 的取反) + 1」,全程只用一个加法器。工程师在加法器外面套上「取反 + 选位」的小电路,就同时得到了加法和减法两套能力。

核心思想 减法器其实不存在——补码把「减」翻译成了「加」,让一台加法器通吃加减。这正是第 2 章「补码让减法变加法」在硬件里的兑现。

这个思想可以继续推广:乘法和除法也能拆成一连串加法和移位(乘法 = 多次累加;除法 = 多次累减)。所以,一台看似强大的 CPU,最底层往往只靠一个加法器在默默工作。

🧠 一句话记忆 减法 = 加补码 = 取反 + 加一。加法器一个顶俩,甚至顶更多。

把「套在加法器外面的那圈小电路」画具体一点。逐位取反用非门当然可以,但工程师有更巧的一招——用异或门做「条件取反」。异或有个妙用:X ⊕ 0 = X(直通),X ⊕ 1 = X̄(取反)。于是在 B 的每一位前面放一个异或门,异或门的另一端全部并到一根线上,这根线叫 Sub 控制信号:Sub = 0,B 原样进加法器(做加法);Sub = 1,B 逐位取反(做减法的一半)。而「加一」呢?把同一根 Sub 线再接到最低位的 Cin 上——Sub = 1 时,加法器白捡一个「+1」。一根线,同时完成「取反+加一」两个动作,这就是教科书上经典的加减两用电路(add/sub unit):

加减两用电路:一根 Sub 线切换加与减 B 输入经异或门阵列受 Sub 信号控制逐位条件取反,Sub 同时接最低位进位输入,送入四位加法器。 B 端异或门阵列 Sub=1 逐位取反,Sub=0 直通 4 位行波加法器 A + B′ + Cin B3…B0(被加减数输入) A3…A0(加数输入) 取反后的 B′ S3…S0(结果) Sub 控制信号 Sub 同时接管:异或门另一端 + 最低位 Cin(「加一」白捡)
图 5-4加减两用电路:Sub=0 时 B 直通、Cin=0,做加法;Sub=1 时 B 逐位取反、Cin=1,正好完成「取反加一」,做减法。

这套电路还牵出两个必须分清的「标志位」概念,它们是 CPU 条件判断的基石:

注意 C 和 V 经常「意见不合」:同一个 0110+1101,无符号看是 6+13=19,C=1 表示「无符号溢出」;补码看是 6+(−3)=3,完全合法。所以 CPU 干脆两个标志都存,程序按自己的数据类型选用。第 8 章讲条件跳转时,它们会反复登场。

📐 例题 5 4 位补码加减法:验算三种情形

一台 4 位加减两用电路(图 5-4),请分别计算并核对:(a)5 − 3;(b)3 − 5;(c)5 + 4,并说明(c)发生了什么。

思路 4 位补码的表示范围是 −8 ~ +7。减法 = A + (B 的补码),补码 = 逐位取反加一;(c)题要同时用无符号和有符号两副眼镜看结果。

解 (a)5 − 3:3 = 0011,取反得 1100,加一得补码 1101。0101 + 1101 = 1 0010,4 位装不下最高位,丢弃进位(C=1)后本位结果 0010 = 2 ✓(5−3=2)。
(b)3 − 5:5 = 0101,取反 1010,加一得 1011。0011 + 1011 = 0 1110,没有进位(C=0),结果 1110 按 4 位补码解读:−8+4+2 = −2 ✓(3−5=−2)。
(c)5 + 4:0101 + 0100 = 1001。无符号眼镜看:9,且 C=0,合法。有符号眼镜看:1001 最高位是 1,是个负数,等于 −7——两个正数相加却得到负数,V=1,溢出。按判定式复核:最高位的进位输入是第 2 位送来的 1,而最高位没有向前进位(输出 0),1 ⊕ 0 = 1 → V=1 ✓。结论:4 位机装不下 5+4 的有符号结果 9。

答:(a)=2(丢弃进位 1);(b)=−2(无进位);(c)无符号得 9 合法、有符号溢出(正+正得负),V 标志置 1。 ⚠ 易错点:减法算出的「进位」不是错误标志,而是补码减法的正常副产品,有符号减法要看的其实是 V 不是 C;反之,无符号运算关心 C 不关心 V——两副眼镜别戴错。

这套「标志位 + 加减两用电路」的组合拳,正是 CPU 里 ALU(下一章的常客)的雏形:控制器把 Sub 拉高或拉低,同一套门阵列立即切换身份。航空电子里也一样——机载计算机的加减运算、飞控增益的计算、燃油量的累减,底层都是这同一台加法器在转,工程师只需相信它,把精力放在系统层。

最后澄清一个容易绕晕的说法:既然「减法器不存在」,为什么 CPU 明明有 SUB(减法)指令?——指令是软件视角,电路是硬件视角。SUB 指令被执行时,控制器发出的控制信号恰好是「Sub=1」:取反阵列打开、进位口置 1,加法器顺路完成减法。指令层的「减」,在晶体管层面自始至终只有加法。「一个模块、两种人格」——按什么人格工作,由控制信号说了算。这个思想(控制信号给同一套硬件赋予不同能力)会在第 8 章 CPU 内部反复出现。

5.6编码器、译码器与多路选择器

加法器之外,还有几个「组合逻辑家族」的明星,它们在计算机里到处当配角,却不可或缺。

译码器(decoder):1 条线变成 N 条

译码器把 n 位输入,翻译成 2ⁿ 条输出线中的恰好一条为 1。比如 2 位输入 00、01、10、11,对应点亮 4 条线中的一条。它常用来「选中」某一行内存、某个寄存器——是寻址的基础。

译码器为什么天然适合「选中」?把它的行为写成代数:第 i 条输出线 Yi = 1,当且仅当输入正好等于 i 的二进制写法——也就是说,每条输出线对应一个最小项(minterm)。2 位输入的全部 4 个最小项,正好一一分配给 4 条输出线,不多不少、不重不漏。所以译码器又常被叫作「最小项发生器」:给它一个编号,它就把那一路的「灯」点亮。寻址时 CPU 把地址码送进来,哪条线亮,哪行存储单元、哪个寄存器就被「叫到」——这就是「选通」。

顺带一个日常工程案例:老式数字仪表上的七段数码管,靠的就是一种特殊的译码器——BCD 七段译码器,把 4 位二进制数字(0~9)翻译成 a~g 七段亮/灭的组合,你才能一眼读出 3 或 8。万用表、时钟、座舱仪表的旧式显示,全有它一份功劳。

编码器(encoder):N 条线变回 1 条

编码器是译码器的逆过程:在多条输入线中,检测哪一条为 1,输出它的编号。键盘里的「哪个键被按下」,就是靠编码器把「那一条线」变成「那个键的编码」。

实际键盘还有个现实问题:人手很快,两三个键可能「同时按下」。普通编码器遇到多线同时为 1 会输出错乱的编号。于是工程上给编码器加上优先级——优先编码器:约定编号大的优先(或小的优先),同时多键按下时只报优先级最高的那个。键盘控制器、中断控制器里,排队靠的都是它。

多路选择器(multiplexer,MUX):多选一的开关

多路选择器(MUX)像一个「多路切换开关」:有多路数据输入,用几根「选择线」决定把哪一路接到输出。可以把它理解成一个「电子旋转开关」,它是 CPU 内部数据通路的关键组件。

4 选 1 多路选择器 四路数据 D0 到 D3 进入数据入口,两根选择线 S1、S0 经译码后通过四个与门选通其中一路,再经或门输出。 D0 D1 D2 D3 与门 与门 与门 与门 或门 输出 F 四路数据入口(每拍只放行一路) 选择译码:S1 S0 S1S0=00 选 D0,01 选 D1, 10 选 D2,11 选 D3
图 5-54 选 1 MUX 的内部:选择线先「译码」出该放行哪一路,对应的与门打开(数据通过),其余与门被压成 0,最后或门把唯一放行的数据送到输出。

看懂图 5-5 的内部结构,MUX 就再也不是黑盒:选择线 S1S0 先经过译码,产生「四选一」的选通信号;每个数据 Di 和自己的选通信号做与——选中的那一路「与 1」原样通过,没选中的「与 0」被强行清零;最后或门把幸存的那一路送出去。写成代数就是一个漂亮的通式:

F = m₀·D₀ + m₁·D₁ + m₂·D₂ + m₃·D₃ (mi 是选择线构成的最小项)

这个式子透露了一个惊人的能力:既然任何真值表都能写成「最小项之和」,那只要把数据端 Di 按真值表接 0 或接 1,一个 MUX 就能实现任意逻辑函数。三变量的函数,用 8 选 1 MUX,把 8 个数据端按真值表对应接死即可——不用化简、不用卡诺图,MUX 就是一块「万能函数发生器」。CPU 里数据从哪个寄存器流向 ALU、总线占用让给谁,都是 MUX 在一拍一拍地扳道岔。

三种组合逻辑组件对比
组件作用一句话类比
译码器 Decodern 位输入 → 选通 2ⁿ 条输出中的一条「点兵点将」,点到谁谁亮
编码器 Encoder检测哪条输入线为 1 → 输出编号「谁举手了」,报出是谁
多路选择器 MUX多路输入中选一路输出「电子旋转开关」

🧠 一句话记忆 译码器「一对多」,编码器「多对一」,多路选择器「多选一」。

📐 例题 6 数据采集系统的 MUX 轮巡采样

某测试系统用 8 选 1 MUX 轮流采样 8 路传感器信号(D0~D7)。(a)需要几根选择线?(b)S2S1S0 = 100 时选通哪一路?从 000 开始每拍加一,扫完全部 8 路需要几拍?(c)若某个更复杂的系统选用了 5 根选择线的 MUX,它最多能接多少路输入?

思路 选择线位数 n 与可接路数是 2ⁿ 的关系;选通的路号就是把选择线当成二进制数读出来的数值。

解 (a)2ⁿ = 8 → n = 3,需要 3 根选择线(S2、S1、S0)。
(b)S2S1S0 = 100 即十进制 4,选通第 4 路(D4)。从 000 数到 111,共 8 拍扫完一圈——每拍恰好一路,不多不少(2³ = 8 正好分完)。
(c)5 根选择线 → 2⁵ = 32 路。

答:3 根;选通 D4、8 拍一轮;5 根选择线最多 32 路。 ⚠ 易错点:别把「第 4 路」和「S2S1S0=011」(第 3 路)弄混——路号从 0 计,100 是第 5 个路号但叫 D4;另外 2ⁿ 的关系别写成 n²。

📐 例题 7 用译码器给内存「分片选通」

一块 8 KB 的存储器由 8 片 1 KB 的存储芯片组成,用一片 3-8 译码器做「片选」:CPU 地址的低 10 位 A9~A0 接到每片芯片的片内地址,高 3 位 A12A11A10 接译码器输入,译码器 8 条输出各接一片芯片的「选中」端。(a)8 KB 的整体地址范围是多少?(b)A12A11A10 = 101 时选中第几片?这一片的地址范围是多少?(c)为什么片内地址用 10 位?

思路 1 KB = 1024 = 2¹⁰ 字节,所以片内需要 10 位地址寻址;8 片共 8×1024 = 8192 字节;第 k 片的起始地址 = k×1024。

解 (a)8192 字节 = 0x2000,地址从 0x0000 到 0x1FFF。
(b)A12A11A10 = 101 = 5,选中第 5 片。该芯片占 1 KB = 0x400 字节,起始地址 = 5×1024 = 0x1400,范围 0x1400 ~ 0x17FF。
(c)1 KB = 2¹⁰ → 片内地址恰好 10 位(A9~A0 覆盖 0x000~0x3FF 的 1024 个字节),10+3 = 13 根地址线,正好 2¹³ = 8192 个地址,一根不多一根不少。

答:0x0000~0x1FFF;第 5 片,0x1400~0x17FF;片内 10 位因为 1 KB = 2¹⁰。 ⚠ 易错点:地址范围是「闭区间」——结束地址是 0x17FF 而不是 0x1800(0x1800 已是下一片的起点);十六进制换算别靠感觉,5×1024 = 5120 = 0x1400 要落笔算。

MUX 还能级联扩容:两个 4 选 1 加一级 2 选 1,就是 8 选 1(高位选择线决定用哪一组,低位选择线在组内挑路);同理四个 4 选 1 加一级 4 选 1,就是 16 选 1。像搭积木一样一层层往上堆,数据通路的「路口」越修越宽——CPU 里的多路选择网络,就是这么一层层长出来的。

把译码器与 MUX 摆在一起对照一次:译码器输出线上的 1 是「选通通知」(点名哪一路,线上不跑数据);MUX 输出的是「数据本身」(被选中的那一路的内容)。实际系统里两者经常成对出现:先用译码器点名(片选),再让数据进场(读写)。分清「通知」和「内容」,是读懂数据手册的第一步。

工程现实:竞争与毛刺——组合电路并不总是「一步到位」 真值表给人的印象是「输入一变、输出立刻变」,但门有传播延迟,而且同一个输入抵达输出常常有多条长短不一的路径。于是输入翻转的一瞬间,各路信号「到达有先后」(这叫竞争),输出可能先抖到一个错误值、再稳定到正确值——这个瞬时错误尖峰叫毛刺(glitch)。最干净的例子:A·A′ 在数学上恒为 0,可 A 翻转时非门要慢半拍,A 与 A′ 短暂同时为 1,与门就吐出一个窄脉冲。稳态永远是对的,错只发生在「翻转的路上」;但危害真实存在——毛刺让电路白白耗电,若被异步负载(锁存器、计数器一类)当成真信号采走,就会酿成错误。对策有三层:下游用时序电路在时钟沿采样(毛刺落在两次采样之间就被天然忽略)、给逻辑式加冗余项(把卡诺图上相邻的 1 圈进同一项,从源头消除特定毛刺)、让各条路径等长(EDA 工具自动做时序平衡)。这也补上了「组合电路的输出在两次翻转之间到底可信到什么程度」这最后一块拼图:稳态可信,翻转路上不可信——谁来保证「只在安定时看一眼」,正是第 6 章时钟要回答的问题。

本章小结

  1. 组合逻辑无记忆,输出只由当前输入决定。
  2. 半加器(异或 + 与)算一位加法;全加器(两个半加器 + 或门)额外处理进位。
  3. 多位加法器 = 全加器串联,进位「行波」传播。
  4. 减法 = 加补码(取反 + 加一),所以不需要单独造减法器。
  5. 译码器(一对多)、编码器(多对一)、多路选择器(多选一)是数据通路的基础组件。

读完本章,你应该能……

思考题

1. 半加器的「和」列为什么对应异或门,而不是或门?

提示:看 1+1 这一行,和是 0;异或输出 0,而或输出 1,所以是异或。

2. 全加器比半加器多了什么?多出来的输入从哪来?

提示:多了进位输入 Cin,来自更低一位的进位输出。

3. 在电路里,「取反加一」如何用加法器实现?

提示:每位过非门取反,再把 1 当作最低位进位 Cin 喂进加法器。

4. 译码器和多路选择器的区别是什么?各自用在什么场景?

提示:译码器一对多(选通一行内存);MUX 多选一(数据通路切换)。

5. 卡诺图上相邻的两个「1」为什么可以合并成一项?合并前后分别消去了什么?

提示:相邻格的输入组合只差一个变量(格雷码排列保证这一点),这个变量在一个格里取 0、另一个格取 1,两项相加时用 A + Ā = 1 把它整个消去。被消去的正是那个「不同」的变量,剩下的就是合并项。

6. 一个 16 位行波进位加法器,每个门延迟 τ,最坏情况下总延迟大约是多少 τ?换成 64 位呢?这说明行波结构的什么弱点?

提示:t ≈ (2n+1)τ。16 位 → 33τ;64 位 → 129τ。延迟随位数线性增长,位数一大就慢得没法用,这正是要用超前进位(用门数换速度)的原因。

7. 加减两用电路里,「+1」这步为什么说是「免费」的?做加法时它会误事吗?

提示:Sub 信号同时接到最低位 Cin——减法(Sub=1)时正好完成「加一」;加法(Sub=0)时 Cin=0,对加法毫无影响。一根线两用,不需要额外的加一电路。

8. 用两个 4 选 1 MUX 加一个 2 选 1 搭一个 8 选 1,一共需要几根选择线?各根线接到哪里?

解析:8 = 2³,需要 3 根(S2、S1、S0)。低位两根同时接到两个 4 选 1 的选择端,让它们各自在自己那 4 路里选出一路;高位一根接末级 2 选 1,决定最终采用哪个 4 选 1 的输出。规律:选择线根数 = 以 2 为底的对数——见 3 根选择线立刻反推它是 8 选 1,见 4 根就是 16 选 1。

9. 数学上 A·A′ 恒为 0,为什么实际电路里这个与门的输出不是一条安静的水平线?工程师靠什么保证这类抖动不酿成错误?

解析:非门有传播延迟,A 翻转的一瞬间 A′ 还没跟上,两个 1 短暂相遇,与门输出一个窄尖峰(毛刺)。稳态输出仍恒为 0,错只在「翻转的路上」。保证不酿错的通用办法是同步设计:下游触发器只在时钟沿采样,只要毛刺落在两次采样之间、且在采样沿到达前已稳定,它就被天然忽略;真正的危险在异步路径——这也是第 6 章请进时钟的根本原因之一。