1978 年问世的 Intel 8086 处理器奠定了现代 x86 架构的基础,其算术逻辑单元(ALU)是芯片中最复杂的关键部件之一。8086 的 ALU 支持 28 种不同的运算操作,从基础的加法、减法到位运算、移位以及 BCD 调整等特殊功能。然而,ALU 本身只是一个执行单元,真正决定它执行何种操作的是控制电路 —— 这套电路负责将微代码指令和机器指令 opcode 翻译成 ALU 能理解的控制信号。本文将从硅级逆向工程的角度,深入剖析这套控制逻辑的实现细节,揭示 CISC 处理器在控制路径上相对于 RISC 的复杂性所在。

微代码与 ALU 的两阶段交互

理解 8086 的 ALU 控制首先需要明白微代码与 ALU 之间的交互模式。8086 采用微代码实现大多数机器指令,每条机器指令由多条微指令组成。ALU 相关的微指令遵循一个特殊的两阶段模式:第一条微指令配置 ALU 的工作模式,第二条微指令才真正获取 ALU 的运算结果并将其存储到目标位置。这种设计意味着控制电路必须在配置阶段记住即将执行的具体操作,以便在后续阶段正确处理结果。

8086 的微架构采用了独特的两部分设计,每个微指令同时执行两个不相关的操作。第一部分负责在源操作数和目标操作数之间移动数据,第二部分则可以执行跳转、子程序调用、内存读写或 ALU 操作。ALU 操作由一个五位字段指定具体的运算类型,还有一个两位字段指定哪个临时寄存器提供输入。这两个字段在后续的控制电路中扮演着核心角色,因为它们直接决定了 ALU 的配置方式。

更巧妙的是,许多机器指令共享相同的微代码模板。例如 ADD、SUB、ADC、SBB、AND、OR、XOR 和 CMP 这八种算术逻辑指令使用完全相同的微代码序列,具体的 ALU 操作由硬件根据机器指令 opcode 自动填充。这种机制通过一个名为 "XI" 的伪 ALU 操作实现 —— 当微代码指定 XI 时,控制电路会将其替换为机器指令中规定的实际 ALU 操作。类似的还有 INC/DEC 指令、BCD 调整指令 DAA/DAS 以及 ASCII 调整指令 AAA/AAS,它们都复用同一套微代码模板。

以典型的算术指令为例(如 ADD AL, BLXOR [BX+DI], CX),其微代码由三条微指令组成。第一条将第一个操作数加载到临时寄存器并配置 ALU 执行 XI 操作(等待 opcode 替换);第二条加载第二个操作数到另一个临时寄存器,并在不需要回写内存时开始取下一条机器指令;第三条将 ALU 的结果(标记为 Σ)存储到目标位置,同时更新状态标志位。这套流程清晰地展示了 ALU 控制信号的时序要求 —— 控制电路必须记住配置阶段的 ALU 操作,以便在结果阶段正确地完成存储和标志更新。

基于查找表的 ALU 位电路设计

理解 8086 ALU 的内部实现有助于把握控制信号的作用方式。与早期微处理器(如 6502 为每种运算使用独立电路,再通过多路选择器选择输出)不同,8086 采用了一种更接近现代 FPGA 的方法 —— 通过两个查找表(LUT)为每一位 ALU 生成进位信号和输出信号。这种设计使得 ALU 可以通过改变查找表的内容来重新配置为执行任意运算,而无需改变硬件电路本身。

8086 ALU 单比特位的电路结构包含六个控制信号输入,这些信号来自控制逻辑。电路的核心是两个多路选择器(梯形符号表示),它们使用两个输入操作数的比特位作为选择信号,从控制信号中选取相应的输出值,分别用于控制进位生成和进位传播。通过向 ALU 输入不同的控制信号组合,这套电路可以配置为执行加法、减法或任何逻辑运算(如 AND、XOR 等)。8086 共有 16 个这样的位电路并行工作,处理 16 位数据。

这种基于 LUT 的设计带来了极大的灵活性,但同时也增加了控制逻辑的复杂度。8086 的指令集包含大量特殊情况:比较操作(CMP)与减法(SUB)功能相同,但比较操作会丢弃实际数值结果而仅更新标志位;带进位加法(ADC)和普通加法(ADD)需要不同的初始进位输入;减法操作将进位标志视为借位并取反;增量(INC)和减量(DEC)指令支持增量 2 和减量 2 模式,需要将增量信号输入到第 1 位而非第 0 位;移位操作更是各有各的特殊处理 —— 循环移位可能包含或排除进位位,算术右移需要复制符号位。因此,除了六个 LUT 控制信号外,ALU 还需要大量的附加控制信号来处理这些特殊情况。

控制逻辑在硅晶圆上的实现

通过化学方法去除 8086 芯片的金属层并用显微镜观察,可以清晰地看到 ALU 控制逻辑在硅晶圆上的物理实现。控制信号从右侧进入,首先被锁存器捕获;然后通过两个 PLA(可编程逻辑阵列)进行解码,生成的信号流向左侧,最终连接到 ALU 的各个控制端口。这套布局直观地展示了信息在芯片中的流动方向:微指令信息进入、解码逻辑处理、控制信号输出到执行单元。

当微代码指定 XI 操作时,实际的 ALU 操作码需要根据机器指令进行替换,这个替换工作由 XI 多路选择器在锁存之前完成。由于 8086 指令集极其复杂,XI 机制的实现并不像看起来那么简单。这个多路选择器接收三类输入:来自一个特殊寄存器(称为 "X" 寄存器)的三个指令比特、来自指令寄存器的另一个比特,以及来自组译码 ROM(Group Decode ROM)的最后一个比特。X 寄存器存储机器指令的第 3-5 位,对于 XI 操作,这些位成为 ALU 操作码的第 0-2 位;指令寄存器的第 6 位提供第 3 位;组译码 ROM 则为特定指令提供第 4 位。这种分散的 opcode 解码方式正是 CISC 处理器复杂性的典型体现。

控制电路使用三个触发器来跟踪微指令指定的临时寄存器,每个寄存器对应一个触发器。微指令中的两位字段指定临时寄存器后,控制电路解码该字段并激活相应的触发器,触发器的输出连接到 ALU 以启用对应的临时寄存器。每条机器指令开始时,这些触发器会被重置,默认选择临时寄存器 A。类似地,控制电路使用五个触发器存储微指令中的五位操作码字段,每条机器指令开始时这些触发器也被重置,确保默认操作码为 0(ADD)。这个默认机制的一个重要好处是,如果某条指令需要执行 ADD 操作而恰好操作码被重置为默认值,就不需要单独的微指令来配置 ALU,从而节省一个微周期。

PLA 解码与控制信号生成

五位操作码触发器的输出连接到操作 PLA,这个 PLA 负责将操作码解码为 27 个独立的控制信号。其中约 15 个信号进一步输入到查找表 PLA,生成 ALU 所需的六个查找表控制信号。在 LUT PLA 的最左侧,有一些特殊的大电流驱动电路,用于放大控制信号后再发送到 ALU—— 这些驱动电路的设计涉及到 8086 芯片中一项精妙的模拟电路技术。

bootstrap 驱动电路解决了 NMOS 电路的一个根本性问题。NMOS 晶体管在拉高电平时表现不佳,由于晶体管的物理特性,输出电压会低于栅极电压,差额为一个阈值电压 VTH(约半伏或更高)。bootstrap 电路利用电容效应来克服这一限制:当时钟为高电平且输入为 +5V 时,点 A 的电压约为 4.5V;当时钟变低时,第二个晶体管的栅极因反相时钟信号而变高,由于电容耦合效应,源极和漏极电压上升时栅极电压会被拉升至超过其原始电压,可能额外获得约两伏的电压提升。高电压栅极产生完整的电压输出,避免了 VTH 造成的压降。

bootstrap 电路被用于 ALU 的查找表控制信号有两个原因。首先,这些控制信号驱动传输晶体管,而传输晶体管会受到阈值电压的压降影响,因此需要尽可能高的初始控制电压。其次,每个控制信号连接到 16 个晶体管(每个 ALU 位一个),这是一个相当大的负载,因为每个晶体管都有栅极电容。增加驱动电压有助于克服 RC 延迟,提升电路性能。

从 CISC 与 RISC 的视角审视设计选择

每当审视 8086 的控制电路时,都能深切感受到 RISC 与 CISC 处理器之间的本质差异。在 RISC 处理器(如 ARM)中,指令解码是直接的,处理器电路也相对简单。但在 8086 这样的 CISC 处理器中,边角情况和复杂性无处不在。例如,8086 机器指令有时在第一个字节中指定 ALU 操作,有时在第二个字节中指定,有时在其他位置指定 —— 这正是需要 X 寄存器锁存器、XI 多路选择器和组译码 ROM 的原因。

8086 的 ALU 还包含一些晦涩的操作,包括四种 BCD 调整指令和七种移位指令,这使得 ALU 本身也比 RISC 处理器复杂得多。从另一个角度看,x86 架构长达四十余年的成功也证明了这种复杂性带来的好处 —— 丰富的指令集和灵活的寻址模式使得程序员能够更直接地表达复杂算法,微代码层面的优化也为后续的指令级并行和超标量设计奠定了基础。

理解 8086 ALU 的控制机制不仅是计算机考古的乐趣,更能为现代处理器设计提供历史参照。当今的超标量处理器虽然采用了流水线、乱序执行和分支预测等先进技术,但其控制信号生成的核心挑战 —— 如何高效地将高级指令翻译成芯片实际执行的操作 —— 与四十年前的 8086 面临的本质问题并无二致。


参考资料

  • Ken Shirriff, "Notes on the Intel 8086 processor's arithmetic-logic unit", righto.com (2026)