J — 运算方法与运算器

运算器 (ALU) 是 CPU 中执行算术和逻辑运算的核心部件。理解从半加器到 Booth 乘法再到 IEEE 754 浮点运算的底层实现,是理解 CPU 数据通路的基础。

加法器设计基础

半加器 (Half Adder)

完成两个 1 位二进制数的加法,不考虑低位进位输入。

输入 A输入 B和 S (Sum)进位 C (Carry)
0000
0110
1010
1101
S = A XOR B
C = A AND B
flowchart LR
 A["A"] --> XOR["XOR"]
 B["B"] --> XOR
 XOR --> S["S = A XOR B"]
 A --> AND["AND"]
 B --> AND
 AND --> C["C = A * B"]

全加器 (Full Adder)

加上低位进位 Cin,完成完整的一位加法:

ABCinSCout
00000
00110
01010
01101
10010
10101
11001
11111
flowchart TD
 A["A"] --> XOR1["XOR"]
 B["B"] --> XOR1
 XOR1 --> XOR2["XOR"]
 Cin["Cin"] --> XOR2
 XOR2 --> S["S = A XOR B XOR Cin"]
 A --> AND2["AND"]
 B --> AND2
 Cin --> AND1["AND"]
 XOR1 --> AND1
 AND1 --> OR["OR"]
 AND2 --> OR
 OR --> Cout["Cout = AB + Cin*(A XOR B)"]

逻辑表达式:

S = A XOR B XOR Cin
Cout = A*B + (A XOR B)*Cin

行波进位加法器 (Ripple Carry Adder)

将 n 个全加器 (FA) 级联,进位从低位逐位传递到高位:

A[n-1] B[n-1] A[1] B[1] A[0] B[0] C0=0
 | | | | | | |
 FA[n-1] <- Cn-1 ... FA[1] <- C1 FA[0] <- C0
 | | |
 S[n-1] S[1] S[0]

延迟分析:每个全加器的 Cout 经过 2 级门延迟 (AND + OR),n 位行波进位 = O(2n) 门延迟。对于 n = 64,约 128 级门延迟,无法在一个周期内完成。因此现代加法器使用超前进位。

超前进位加法器 (Carry Look-ahead Adder, CLA)

进位生成 (Generate) 与进位传播 (Propagate)

对第 i 位定义两个关键信号:

  • 进位生成 Gi = Ai * Bi ---- 当 Ai 和 Bi 均为 1,本位一定产生进位
  • 进位传播 Pi = Ai XOR Bi ---- 当 Ai 和 Bi 只有一个为 1,若低位有进位则本位上可以传播到高位

进位递归关系:

C[i+1] = Gi + Pi * Ci

4 位 CLA 的进位展开

C1 = G0 + P0*C0
C2 = G1 + P1*G0 + P1*P0*C0
C3 = G2 + P2*G1 + P2*P1*G0 + P2*P1*P0*C0
C4 = G3 + P3*G2 + P3*P2*G1 + P3*P2*P1*G0 + P3*P2*P1*P0*C0

所有 4 位进位由 Ai, Bi, C0 经过三级门延迟 (AND/OR 树) 同时生成,与位数无关。这就是 “超前进位” (Look-ahead) 的含义。

flowchart TD
 subgraph "Pi, Gi 生成 (1级门延迟)"
 A0["A0"] & B0["B0"] --> PG0["P0=A0^B0; G0=A0*B0"]
 A1["A1"] & B1["B1"] --> PG1["P1=A1^B1; G1=A1*B1"]
 A2["A2"] & B2["B2"] --> PG2["P2=A2^B2; G2=A2*B2"]
 A3["A3"] & B3["B3"] --> PG3["P3=A3^B3; G3=A3*B3"]
 end
 subgraph "CLA块 (2级门延迟)"
 PG0 & PG1 & PG2 & PG3 & C0["C0"] --> CARRY_UNIT["超前进位逻辑 (CLA)"]
 end
 CARRY_UNIT --> C1["C1"] & C2["C2"] & C3["C3"] & C4["C4"]
 subgraph "求和 (1级门延迟, 与CLA并行)"
 PG0 --> S0["S0 = P0^C0"]
 PG1 --> S1["S1 = P1^C1"]
 PG2 --> S2["S2 = P2^C2"]
 PG3 --> S3["S3 = P3^C3"]
 end

对于 64 位加法器,通常采用多层 CLA 结构:

4位 CLA 块 (1层) -> 构成 16位 块间 CLA (2层) -> 4个 16位 构成 64位 (3层)
总延迟约为 O(log n) 级别,远优于 O(n) 的行波进位。

组间成组进位 (Block Carry Look-ahead)

对一组 m 位定义块级生成和传播:

  • 块进位生成 G* = 组内可产生进位 (不需要 C0 的帮助)
  • 块进位传播 P* = 只要有 C0 就能将进位传到组外
对 4 位块:
G* = G3 + P3*G2 + P3*P2*G1 + P3*P2*P1*G0
P* = P3*P2*P1*P0
C_block_out = G* + P* * C_block_in

移位运算

类型操作空位填充x86 指令计算结果 (对补码)
逻辑左移所有位左移右端补 0SHL / SAL无符号 x 2^n; 补码 x 2^n (未溢出时)
逻辑右移所有位右移左端补 0SHR无符号 / 2^n
算术右移所有位右移左端补符号位SAR补码有符号 / 2^n (向负无穷取整)
循环左移所有位左移, 移出的最高位回填最低位循环ROL位旋转
循环右移所有位右移, 移出的最低位回填最高位循环ROR位旋转
带进位循环左移将 CF 作为额外位参与循环 (CF->最高位->…->最低位->CF)含 CFRCL多倍精度大数移位
带进位循环右移同上反向含 CFRCR多倍精度大数移位

示例 (8 位值 1011 0101 = B5h):

原始值: 1 0 1 1 0 1 0 1 (B5h, 无符号 181, 补码 -75)
SHL 1: 0 1 1 0 1 0 1 0 (6Ah, 无符号 106) CF=1
SHR 1: 0 1 0 1 1 0 1 0 (5Ah, 无符号 90) CF=1
SAR 1: 1 1 0 1 1 0 1 0 (DAh, 补码 -38) CF=1 (符号位 1 填充)
ROL 1: 0 1 1 0 1 0 1 1 (6Bh) CF=1
ROR 1: 1 1 0 1 1 0 1 0 (DAh) CF=1

定点乘法

原码一位乘法 (Sign-Magnitude Multiplication)

符号位与数值位分开处理:符号位 = Sx XOR Sy (两符号异或即负数),数值位 = 绝对值相乘。

示例:X = -0.1011, Y = +0.1101, 结果符号 = 1 XOR 0 = 1 (负), 数值 = 0.1011 x 0.1101

原码无符号乘法过程 (移位相加法):

步骤部分积 (高位)乘数/部分积 (低位)Y 最低位操作
初始0.00001 1 0 11+
加后0.1011右移一位
1 后0.01011 1 1 00+0 (不加)
2 后0.0101右移一位
2 后0.00101 1 1 11+
加后0.1101右移一位
3 后0.01101 1 1 11+
加后1.0001右移一位
4 后0.10001 1 1 1-结束

结果:数值 = 0.10001111, 符号 = 1 -> X x Y = -0.10001111

原码乘法每次迭代:看乘数最低位,为 1 则加被乘数,为 0 则加 0,然后部分积和乘数联合右移一位。

补码一位乘 — Booth 算法

Booth 算法直接对补码表示的乘数和被乘数做乘法,无需分离符号位。一次考察乘数相邻的两位:

Y[i] (当前)Y[i+1] (上一位/补位)操作原理
00部分积 + 0, 右移一位连续的 0: 无操作
01部分积 + [X]补, 右移一位0->1 上升沿: 加
10部分积 + [-X]补, 右移一位1->0 下降沿: 减
11部分积 + 0, 右移一位连续的 1: 无操作

示例:X = -3 (补码 11101, 5bit 表示), Y = +5 (补码 00101, 5bit)
期望积 = -15, 10 位补码 = 1111110001

步骤部分积高位 (5+1 符号扩展)乘数/低位补位Y[0]Y[补]操作
初始0000000010101 0+[-X]补 = +00011
+后000011001010算术右移
移后000001100101
10000011001010 1+[X]补 = +11101
+后111110100101算术右移
移后111111010010
21111110100101 0+[-X]补 = +00011
+后000010010010算术右移
移后000001001001
30000010010010 1+[X]补 = +11101
+后111110001001算术右移
移后111111000100
41111110001000 0+0
+后111111000100算术右移
移后111111100010结束

结果:[高位] 111111 [低位] 10001 = 1111110001 (10位补码) = -15. 正确。

Booth 算法的关键:算术右移保持符号位不变,通过补码加减直接完成乘法,巧妙避免了原码乘法中符号位分离的多余操作。

定点除法

恢复余数法 (Restoring Division)

对无符号整数 P (被除数) / D (除数) 实现 n 位除法:

算法 (n位, 恢复余数法):
R = 0 (初始余数 = 0)
for i = 0 to n-1:
 1. R, Q 联合左移一位 (R || Q << 1)
 2. R = R - D (试探减法)
 3. if R >= 0:
 Q[n-1-i] = 1 (够减 -> 上商 1)
 else:
 R = R + D (不够减 -> 恢复: 加回 D)
 Q[n-1-i] = 0 (上商 0)

示例:0.1010 / 0.1101 (4 位定点小数)

步骤操作部分余数 R商 Q说明
初始-0.10100.0000
1左移: R=1.0100; -D=1.0100-0.1101=0.0111 >=00.01110.0001够减, 上商1
2左移: R=0.1110; -D=0.1110-0.1101=0.0001 >=00.00010.0011够减, 上商1
3左移: R=0.0010; -D=0.0010-0.1101=-0.1011 <00.0010 (恢复)0.0110不够减, 恢复, 上商0
4左移: R=0.0100; -D=0.0100-0.1101=-0.1001 <00.0100 (恢复)0.1100不够减, 上商0

结果:商 = 0.1100, 余数 = 0.0100 x 2

恢复余数法的缺点:不够减时需要额外的一次加法来恢复余数,平均增加了约 n/2 次加法操作。

不恢复余数法 / 加减交替法 (Non-Restoring Division)

优化:不够减时不恢复余数,而是下一步做加法

规则:
if R >= 0: 上商 1, 下一步做 R' = 2*R - D (左移后减)
if R < 0: 上商 0, 下一步做 R' = 2*R + D (左移后加)

最后一步若余数为负,额外做一次 +D 恢复正余数。

同例 (0.1010 / 0.1101) 用加减交替法:

步骤操作部分余数 R商 Q说明
初始-0.10100.0000
1R - D = 0.1010 - 0.1101 = -0.0011 <0-0.00110.0000上商 0, 下一步 +D
2左移: R= -0.0110; +D = -0.0110+0.1101 = 0.0111 >=00.01110.0001上商 1, 下一步 -D
3左移: R=0.1110; -D = 0.1110-0.1101 = 0.0001 >=00.00010.0011上商 1, 下一步 -D
4左移: R=0.0010; -D = 0.0010-0.1101 = -0.1011 <0-0.10110.1100上商 0, 末步为负需恢复
恢复+D = -0.1011+0.1101 = 0.00100.00100.1100最终余数

结果:商 = 0.1100, 余数 = 0.0010 x 2

浮点运算

浮点加减法

设 X = Sx x Mx x 2^(Ex), Y = Sy x My x 2^(Ey), 求 X +- Y:

flowchart TD
 X["X: (Sx, Ex, Mx)"] --> UNPACK["拆分符号/阶码/尾数"]
 Y["Y: (Sy, Ey, My)"] --> UNPACK

 UNPACK --> COMP["比较阶码: d = |Ex-Ey|"]
 COMP --> ALIGN["对阶: 小阶向大阶对齐<br/>阶码较小的尾数右移 d 位<br/>(移出的位进入保护位 G/R/S)"]
 ALIGN --> ADDSUB["尾数相加/减<br/>(根据 Sx, Sy 及操作符决定)"]
 ADDSUB --> NORM["规格化:<br/>结果 < 1 时左规 (尾数左移, 阶码-1)<br/>结果 >= 2 时右规 (尾数右移, 阶码+1)"]
 NORM --> ROUND["舍入<br/>(按 IEEE 754 舍入模式<br/>处理 G/R/S 保护位)"]
 ROUND --> CHECK["溢出检查:<br/>阶码上溢/下溢<br/>尾数=0 时为特例零"]
 CHECK --> PACK["组装结果"]
 PACK --> RESULT["结果浮点数"]

步骤详解

  1. 对阶 (Align):小阶向大阶看齐,尾数右移 delta_E 位,阶码增至与大的相同。右移过程中丢失的低位进入保护位 (Guard, Round, Sticky)。

  2. 尾数加/减:两尾数按符号+操作符决定实际做加法还是减法。尾数隐含的 1 (IEEE 754 normalized) 在运算前恢复。

  3. 规格化:

  • 右规:若相加产生进位 (M >= 2),尾数右移 1 位,阶码 +1
  • 左规:若相减后前导零过多 (M < 1),尾数左移到最高位为 1,阶码相应减少
  1. 舍入:由于对阶和规格化可能产生多余的位 (G/R/S 三位),需按舍入模式处理。

  2. 溢出检查:

  • 阶码上溢 (Exponent Overflow):EX > 最大指数 -> 报告正无穷或负无穷
  • 阶码下溢 (Exponent Underflow):EX < 最小指数 -> 报告非规格化数或 0

浮点乘法

X x Y = (Sx XOR Sy) x (Mx x My) x 2^(Ex + Ey - bias)

步骤:

  1. 符号:结果符号 = Sx XOR Sy
  2. 阶码相加:E_result = Ex + Ey - bias (减去多余的 bias)
  3. 尾数相乘:Mx x My,使用定点乘法 (如 Booth)
  4. 规格化:尾数乘积范围为 [1, 4),若 >= 2 则右规一次 (尾数右移 1, 阶码 +1)
  5. 舍入 + 溢出检查

IEEE 754 舍入模式

模式缩写规则
最近偶数舍入 (Round to Nearest, Ties to Even)RN / RNE舍入到最近的可表示值;若恰好位于两个值正中间 (tie),选择最低有效位为 0 (偶数) 的值。IEEE 754 默认模式
向正无穷舍入 (Round toward +infinity)RU总是向 +inf 方向取最近的可表示值 (ceil)
向负无穷舍入 (Round toward -infinity)RD总是向 -inf 方向取最近的可表示值 (floor)
向零舍入 (Round toward Zero)RZ截断 (truncation),无偏置,直接丢弃多余位

最近偶数舍入 (Round to Nearest Even) 解决了普通四舍五入的统计偏差问题。大量浮点运算中 0.5 始终向上舍入会导致结果渐渐偏大 (positive bias)。偶数舍入使得大约一半的 tie 向上舍、一半向下舍,长期期望误差趋于 0。

例 (保留两位小数, 最近偶数舍入):
 2.345 -> 2.34 (tie: 5 的最近数是 34 和 35, 34 最低位 4 是偶数 -> 选 2.34)
 2.355 -> 2.36 (tie: 35 和 36, 36 最低位 6 是偶数 -> 选 2.36)
 2.365 -> 2.36 (tie: 36 和 37, 36 最低位 6 是偶数 -> 选 2.36)
 2.351 -> 2.35 (非 tie, 更近 2.35)

Guard / Round / Sticky 三位保护位用于舍入判断:

名称含义
GGuard bit尾数最低有效位后面的第一位
RRound bitG 后面的第二位
SSticky bitR 后面所有位的逻辑 OR (若任何一位为 1 则 S=1)

浮点加法完整示例

X = 1.5 (单精度: 0 01111111 10000000000000000000000)
Y = 0.75 (单精度: 0 01111110 10000000000000000000000)

步骤操作结果
拆包Ex=127(0x7F), Mx=1.100…0; Ey=126(0x7E), My=1.100…0
对阶d=1, Y 尾数右移 1, Ey=127My=0.1100…0, G=0, R=0, S=0
尾加Mx + My = 1.100…0 + 0.110…0= 10.010…0 (= 2.25)
右规尾数右移 1, 阶码+1=128M=1.00100…0, G=0, R=1, S=0
舍入 (RN)GRS=010 (非tie) -> 截断M=1.00100…0
组装S=0, E=10000000, M=00100…0= 2.25 (0x40100000)

ALU 设计

一个简单的 n 位 ALU 结构:

flowchart TD
 A["A[n-1:0] (总线A)"] --> MUX_A["MUX (操作数选择)"]
 B["B[n-1:0] (总线B)"] --> MUX_B["MUX (操作数选择)"]
 FUNC["ALUSel[3:0]<br/>(来自控制单元)"] --> MUX_A
 FUNC --> MUX_B
 MUX_A --> FN["功能单元 (Function Unit):<br/>加法器, 与门阵列, 或门阵列, 异或门阵列,<br/>移位器, 比较器, 零检测器"]
 MUX_B --> FN
 FUNC --> FN
 FN --> RESULT["Result[n-1:0]<br/>(送写回总线或地址总线)"]
 FN --> FLAGS["标志位输出"]

 subgraph "标志位 FLAGS"
 Z["Z = (Result==0) -> 零标志"]
 C["C = Cout[n-1] -> 进位标志"]
 V["V = Cout xor Cout[n-2] -> 溢出标志"]
 N["N = Result[n-1] -> 符号标志"]
 end

常用 ALU 操作编码:

ALUSel操作计算说明
0000ADDResult = A + B无符号/补码加法
0001SUBResult = A - B无符号/补码减法
0010ANDResult = A & B (按位)
0011ORResult = AB (按位)
0100XORResult = A ^ B (按位)
0101NORResult = ~(AB)
0110SLTResult = (A < B 补码比较)Set Less Than
0111SLTUResult = (A < B 无符号比较)
1000SLL / SLLVResult = B << A[4:0]逻辑左移
1001SRL / SRLVResult = B >> A[4:0]逻辑右移
1010SRA / SRAVResult = B >>> A[4:0]算术右移
1011LUIResult = B << 16Load Upper Immediate (RISC-V)

溢出判断逻辑

对加法:
 V = (A[n-1] == B[n-1]) && (A[n-1] != Result[n-1])
 (两个同号数相加, 结果异号 -> 溢出)

对减法:
 V = (A[n-1] != B[n-1]) && (A[n-1] != Result[n-1])
 (减数取反 = 加相反数, 等价于两个异号数相加, 结果与A同号则不溢出)

等价硬件实现 (进位比较):
 V = Cout[n-1] XOR Cout[n-2]

验证:0111 + 0001 = 1000 (7+1=8, 但补码中 1000 = -8, 溢出). Cout[3]=0, Cout[2]=1 -> 0 XOR 1 = 1 -> V=1. 正确。

本章与其他模块的链接