# 1.3 计算机体系结构的含义
<!-- source: p0029, p0030 -->

计算机设计者面临的任务十分复杂：先确定一台新计算机应当重视哪些属性，再设计一台在成本、功耗与可用性约束之内，尽可能提高性能与能效的计算机。这项任务涉及许多方面，包括指令集设计、功能组成、逻辑设计和实现。实现可能涵盖集成电路设计、封装、供电与冷却。要优化设计，就必须熟悉极为广泛的技术，从编译器、操作系统，一直到逻辑设计和封装。

几十年前，“计算机体系结构”通常仅指指令集设计，计算机设计的其他方面则被称为“实现”。这种说法往往暗含一种判断：实现乏味，或者缺乏挑战。我们认为，这种看法是不对的。体系结构设计者或计算机设计者的工作远不止指令集设计，项目其他方面的技术障碍，很可能比指令集设计本身更具挑战性。在介绍计算机体系结构设计者面临的更广泛挑战之前，我们先简要回顾指令集体系结构。

## 指令集体系结构：对计算机体系结构的狭义理解

本书用指令集体系结构（instruction set architecture，ISA）一词，指程序员实际可见的指令集。ISA 是最重要的计算机接口，因为它界定了软件与硬件之间的边界。下面的简要回顾将以 80x86、ARMv8 和 RISC-V 为例，说明 ISA 的七个维度。最普及的 RISC 处理器来自 ARM（Advanced RISC Machine）。2023 年，出货的 300 亿颗芯片采用了 ARM 处理器，数量超过采用 80x86 处理器的芯片出货量的 100 倍；80x86 通常简称 x86。附录 A 和附录 K 将进一步介绍这三种 ISA。

RISC-V（读作“RISC Five”）是一种现代 RISC 指令集，2010 年诞生于加利福尼亚大学伯克利分校，后来应产业界的请求，免费开放供各方采用。除了完整的软件栈——编译器、操作系统与模拟器——之外，还有多种可以自由获取的 RISC-V 实现，能够用于定制芯片或现场可编程门阵列（FPGA）。RISC-V 的开发比最早的 RISC 指令集晚了 30 年，因此既继承了前辈的优秀思想，例如大量寄存器、易于流水线化的指令和精简的操作集合，又避开了它们的疏漏或失误。它是前文所述 RISC 体系结构中一个自由、开放且设计优雅的例子，这也解释了为什么已有 200 多家公司加入 RISC-V International 基金会，其中包括 Alibaba、AMD、Google、Intel、MIPS、Nokia、NVIDIA、Qualcomm、Samsung、Seagate、Siemens、Sony 和 Western Digital。2024 年，RISC-V 片上系统（见 1.6 节）的出货量达到 20 亿颗，预计到 2031 年将增至每年 300 亿颗。本书以 RISC-V 的整数核心 ISA 作为讲解实例。

### ISA 的类别

如今，几乎所有 ISA 都属于通用寄存器体系结构，操作数要么位于寄存器中，要么位于存储器中。x86 有 16 个通用寄存器，以及 16 个可以存放浮点数据的寄存器；RISC-V 则有 32 个通用寄存器和 32 个浮点寄存器（见[图 1.4](/book/ch01/03-defining-architecture/#fig-1.4)）。这一类 ISA 有两种常见形式：一种是 x86 这样的寄存器—存储器 ISA，许多指令在执行时都可以访问存储器；另一种是 ARMv8 和 RISC-V 这样的 load-store ISA，只能用加载（load）或存储（store）指令访问存储器。自 1985 年以来发布的所有 ISA 都采用 load-store 形式。

<!-- source: p0031 -->

| 寄存器 | 名称 | 用途 | 保存方 |
| --- | --- | --- | --- |
| `x0` | `zero` | 常量 0 | 不适用 |
| `x1` | `ra` | 返回地址 | 调用方 |
| `x2` | `sp` | 栈指针 | 被调用方 |
| `x3` | `gp` | 全局指针 | — |
| `x4` | `tp` | 线程指针 | — |
| `x5–x7` | `t0–t2` | 临时寄存器 | 调用方 |
| `x8` | `s0/fp` | 保存寄存器／栈帧指针 | 被调用方 |
| `x9` | `s1` | 保存寄存器 | 被调用方 |
| `x10–x11` | `a0–a1` | 函数参数／返回值 | 调用方 |
| `x12–x17` | `a2–a7` | 函数参数 | 调用方 |
| `x18–x27` | `s2–s11` | 保存寄存器 | 被调用方 |
| `x28–x31` | `t3–t6` | 临时寄存器 | 调用方 |
| `f0–f7` | `ft0–ft7` | 浮点临时寄存器 | 调用方 |
| `f8–f9` | `fs0–fs1` | 浮点保存寄存器 | 被调用方 |
| `f10–f11` | `fa0–fa1` | 浮点函数参数／返回值 | 调用方 |
| `f12–f17` | `fa2–fa7` | 浮点函数参数 | 调用方 |
| `f18–f27` | `fs2–fs11` | 浮点保存寄存器 | 被调用方 |
| `f28–f31` | `ft8–ft11` | 浮点临时寄存器 | 调用方 |
{#fig-1.4 num="1.4" caption="RISC-V 寄存器的名称、用途与调用约定" .wide .expand}

除 32 个通用寄存器（`x0–x31`）外，RISC-V 还有 32 个浮点寄存器（`f0–f31`），每个可容纳一个 32 位单精度数或一个 64 位双精度数。在过程调用前后必须保持原值的寄存器，标记为由被调用方（Callee）保存。

### 1. 存储器寻址

几乎所有桌面计算机和服务器，包括 x86、ARMv8 和 RISC-V，都按字节寻址来访问存储器操作数。ARMv8 等体系结构要求对象必须对齐。对于一个大小为 \(s\) 字节、字节地址为 \(A\) 的对象，若 \(A \bmod s = 0\)，则访问是对齐的，参见原书 A-8 页的图 A.5。80x86 和 RISC-V 不强制要求对齐，但操作数对齐时，访问通常更快。

### 2. 寻址方式

寻址方式不仅指定寄存器和常量操作数，也指定存储器中对象的地址。RISC-V 的寻址方式有寄存器寻址、立即数寻址（用于常量）和位移寻址。位移寻址将一个常量偏移量加到寄存器内容上，形成存储器地址。x86 除支持这三种方式外，还支持位移寻址的三种变体：不使用寄存器的绝对寻址、使用两个寄存器的带位移基址加变址寻址，以及使用两个寄存器且将其中一个乘以操作数字节大小的带位移基址加比例变址寻址。去掉位移字段后，它还支持与后三种相对应的形式，即寄存器间接寻址、变址寻址，以及基址加比例变址寻址。ARMv8 除了支持 RISC-V 的三种寻址方式，还支持 PC 相对寻址、两个寄存器之和，以及两个寄存器中一个按操作数字节大小缩放后再求和的方式。它还支持自动递增和自动递减寻址，用计算出的地址替换参与地址计算的某个寄存器的内容。

<!-- source: p0032 -->

### 3. 操作数的类型与大小

与多数 ISA 一样，x86、ARMv8 和 RISC-V 支持的操作数大小包括：8 位（ASCII 字符）、16 位（Unicode 字符或半字）、32 位（整数或字）、64 位（双字或长整数），以及 IEEE 754 的 32 位单精度和 64 位双精度浮点数。x86 还支持 80 位浮点数，即扩展双精度。随着近来 AI 热潮兴起，新一代处理器已经将 16 位半精度乃至更短的浮点格式纳入可选功能，参见第 7 章。

### 4. 操作

操作大致可分为数据传送、算术与逻辑、控制（下面介绍）以及浮点运算。RISC-V 是一种简单、易于流水线化的 ISA，也是当今 RISC 体系结构的代表。[图 1.5](/book/ch01/03-defining-architecture/#fig-1.5) 概括了 RISC-V 的整数 ISA，[图 1.6](/book/ch01/03-defining-architecture/#fig-1.6) 列出其浮点 ISA。x86 的操作集合则丰富和庞大得多，参见附录 K。

<!-- source: p0033 -->

| 指令类型／操作码 | 指令含义 |
| --- | --- |
| **数据传送** | **在寄存器与存储器之间，或在整数寄存器与浮点／特殊寄存器之间传送数据；存储器寻址方式只有“12 位位移量＋通用寄存器（GPR）的内容”。** |
| `lb, lbu, sb` | 加载字节、加载无符号字节、存储字节，数据传入或传出整数寄存器。 |
| `lh, lhu, sh` | 加载半字、加载无符号半字、存储半字，数据传入或传出整数寄存器。 |
| `lw, lwu, sw` | 加载字、加载无符号字、存储字，数据传入或传出整数寄存器。 |
| `ld, sd` | 加载双字、存储双字，数据传入或传出整数寄存器。 |
| `flw, fld, fsw, fsd` | 加载单精度浮点数、加载双精度浮点数、存储单精度浮点数、存储双精度浮点数。 |
| `fmv._.x, fmv.x._` | 在整数寄存器与浮点寄存器之间复制数据；`_` 取 `S` 时为单精度，取 `D` 时为双精度。 |
| `csrrw, csrrwi, csrrs, csrrsi, csrrc, csrrci` | 读计数器并写状态寄存器；计数器包括时钟周期、时间和已完成指令提交的数量（instructions retired）。 |
| **算术／逻辑** | **对 GPR 中的整数或逻辑数据进行操作。** |
| `add, addi, addw, addiw` | 加法；立即数加法（立即数均为 12 位）；只对 32 位执行加法并将结果符号扩展到 64 位；只对 32 位执行立即数加法。 |
| `sub, subw` | 减法；只对 32 位执行减法。 |
| `mul, mulw, mulh, mulhsu, mulhu` | 乘法；只对 32 位执行乘法；取乘积的高半部分；有符号数乘无符号数并取高半部分；无符号乘法并取高半部分。 |
| `div, divu, rem, remu` | 除法、无符号除法、求余、无符号求余。 |
| `divw, divuw, remw, remuw` | 除法和求余，含义同上，但只使用低 32 位，并对 32 位结果进行符号扩展。 |
| `and, andi` | 按位与、立即数按位与。 |
| `or, ori, xor, xori` | 按位或、立即数按位或、按位异或、立即数按位异或。 |
| `lui` | 加载高位立即数：将立即数加载到寄存器的第 31–12 位，然后进行符号扩展。 |
| `auipc` | 将立即数放在第 31–12 位、低位补零后与 PC 相加；与 `JALR` 配合，可将控制转移到任意 32 位地址。 |
| `sll, slli, srl, srli, sra, srai` | 移位：逻辑左移、逻辑右移、算术右移；均有移位量来自寄存器和来自立即数的形式。 |
| `sllw, slliw, srlw, srliw, sraw, sraiw` | 移位，含义同上，但只移动低 32 位，并对 32 位结果进行符号扩展。 |
| `slt, slti, sltu, sltiu` | 小于时置位、与立即数比较后小于时置位；分别提供有符号与无符号形式。 |
| **控制** | **条件分支与跳转；采用 PC 相对寻址或通过寄存器寻址。** |
| `beq, bne, blt, bge, bltu, bgeu` | 根据 GPR 相等、不等、小于、大于等于的比较结果进行分支，提供有符号与无符号形式。 |
| `jal, jalr` | 跳转并链接：保存 `PC+4`；目标为 PC 相对地址（`JAL`）或由寄存器给出（`JALR`）。若指定 `x0` 为目的寄存器，则执行普通跳转。 |
| `ecall` | 向支持程序运行的执行环境发出请求，该环境通常是操作系统。 |
| `ebreak` | 供调试器使用，将控制转移回调试环境。 |
| `fence, fence.i` | 同步线程，以保证存储器访问的顺序；在写入指令存储器时，同步指令与数据。 |
{#fig-1.5 num="1.5" caption="RISC-V 指令的一个子集" .wide .expand}

RISC-V 包含基本指令集（原图写作 `R64I`），并提供可选扩展：乘除法（`RVM`）、单精度浮点（`RVF`）以及双精度浮点（`RVD`）。本图包括 `RVM`，下一图给出 `RVF` 与 `RVD`。附录 A 详细介绍 RISC-V。[^isa-names]

<!-- source: p0034 -->

| 指令类型／操作码 | 指令含义 |
| --- | --- |
| **浮点** | **对双精度（DP）与单精度（SP）格式执行浮点操作。** |
| `fadd.d, fadd.s` | 双精度、单精度加法。 |
| `fsub.d, fsub.s` | 双精度、单精度减法。 |
| `fmul.d, fmul.s` | 双精度、单精度浮点乘法。 |
| `fmadd.d, fmadd.s, fnmadd.d, fnmadd.s` | 双精度、单精度乘加；双精度、单精度负乘加。 |
| `fmsub.d, fmsub.s, fnmsub.d, fnmsub.s` | 双精度、单精度乘减；双精度、单精度负乘减。 |
| `fdiv.d, fdiv.s` | 双精度、单精度浮点除法。 |
| `fsqrt.d, fsqrt.s` | 双精度、单精度浮点平方根。 |
| `fmax.d, fmax.s, fmin.d, fmin.s` | 双精度、单精度浮点最大值与最小值。 |
| `fcvt._._, fcvt._._u, fcvt._u._` | 转换指令：`FCVT.x.y` 将类型 `x` 转换为类型 `y`；`x` 和 `y` 可为 `L`（64 位整数）、`W`（32 位整数）、`D`（双精度）或 `S`（单精度）。整数可为无符号类型（`U`）。[^fcvt] |
| `feq._, flt._, fle._` | 比较浮点寄存器中的浮点数，并将布尔结果写入整数寄存器；`_` 取 `S` 时为单精度，取 `D` 时为双精度。 |
| `fclass.d, fclass.s` | 向整数寄存器写入一个 10 位掩码，用以指示浮点数的类别，如负无穷、正无穷、负零、正零、NaN 等。 |
| `fsgnj._, fsgnjn._, fsgnjx._` | 只改变符号位的符号注入指令：复制另一个源操作数的符号位、取另一个源操作数符号位的反值，或取两个符号位的异或。 |
{#fig-1.6 num="1.6" caption="RISC-V 的浮点指令" .wide .expand}

RISC-V 包含基本指令集（原图写作 `R64I`），并提供单精度浮点（`RVF`）和双精度浮点（`RVD`）可选扩展。DP 表示双精度，SP 表示单精度。

<!-- source: p0032 -->

### 5. 控制流指令

几乎所有 ISA，包括这里的三种，都支持条件分支、无条件跳转、过程调用和返回。三者都采用 PC 相对寻址，将地址字段与 PC 相加来确定分支地址。不过，它们之间也有一些细微区别。RISC-V 的条件分支指令（原文写作 `BE`、`BNE` 等[^be]）检查寄存器内容，而 x86 和 ARMv8 的分支指令检查算术或逻辑操作在执行时附带设置的条件码位。ARMv8 和 RISC-V 的过程调用将返回地址放入寄存器，而 x86 的调用指令 `CALLF` 则将返回地址压入存储器中的栈。

### ISA 的编码

指令编码有两种基本选择：定长与变长。ARMv8 和 RISC-V 的指令长度均为 32 位，这简化了指令译码。[图 1.7](/book/ch01/03-defining-architecture/#fig-1.7) 给出了 RISC-V 的指令格式。x86 采用变长编码，长度为 1–18 字节。 变长指令所占空间可以小于定长指令，因此，同一程序编译为 x86 代码时，通常比编译为 RISC-V 代码更小。请注意，前面介绍的各种选择都会影响指令的二进制编码方式。例如，寄存器数量与寻址方式数量都会显著影响指令长度，因为寄存器字段与寻址方式字段可能在一条指令中出现多次。后来，ARMv7 和 RISC-V 分别提供了 Thumb-2 和 RV64IC 扩展，通过混合使用 16 位和 32 位指令来缩小程序体积。ARMv8 仍只使用 32 位指令。这些 RISC 体系结构紧凑版本的代码体积，比 x86 还小，参见附录 K。

<!-- source: p0034 -->

![RISC-V 的 R、I、S、B、U、J 六种基本指令格式，标出了位编号、寄存器字段、功能字段、立即数字段和操作码。](/figures/ch01/fig-1-7.png)
{#fig-1.7 num="1.7" caption="RISC-V 基本指令集体系结构的指令格式"}

所有指令均为 32 位。R 格式用于整数寄存器之间的操作，例如 `ADD`、`SUB` 等；I 格式用于加载和立即数操作，例如 `LD`、`ADDI`；B 格式用于分支，J 格式用于跳转并链接；S 格式用于存储。为存储指令单独设置一种格式，使三个寄存器字段（`rd`、`rs1`、`rs2`）在各格式中的位置始终保持一致。U 格式用于宽立即数指令 `LUI` 和 `AUIPC`。

图内用语：`opcode` 为操作码，`rd` 为目的寄存器字段，`rs1`、`rs2` 为源寄存器字段，`funct3`、`funct7` 为功能字段，`imm` 为立即数字段；R-type 等分别表示 R 型等指令格式。位号与字段名称按原图保留。

<!-- source: p0035 -->

当今指令集之间的差异已经不大，而应用领域却各有特点，因此，计算机体系结构设计者在 ISA 设计之外面临的其他挑战尤其突出。正因如此，从本书第四版开始，除这段简要回顾外，指令集的大部分内容都安排在附录中，参见附录 A 和附录 K。

## 完整意义上的计算机体系结构：围绕目标与功能需求设计组成和硬件

计算机实现包含两个部分：组成（organization）和硬件（hardware）。组成指计算机设计的较高层次方面，例如存储系统、存储互连，以及内部处理器或 CPU 的设计。CPU 即中央处理单元，负责实现算术、逻辑、分支和数据传送。微体系结构（microarchitecture）一词也常被用来代替“组成”。例如，AMD EPYC 和 Intel Xeon 都实现 x86 指令集，ISA 相同，但采用了不同的组成：它们的流水线与缓存组织差异很大。

随着每个微处理器中开始包含多个处理器，人们也开始用“核心”（core）来称呼处理器。“多核”（multicore）逐渐流行起来，取代了“包含多个处理器的微处理器”这种冗长表述。中央处理单元或 CPU 通常指微处理器中的某一个核心，因为 PMD、PC 和服务器中的微处理器如今几乎都是多核的。

硬件指计算机的具体实现细节，包括详细的逻辑设计和封装技术。一个计算机产品系列中，常常有一些计算机采用相同的 ISA 和非常相似的组成，却在硬件实现细节上有所不同。例如，Intel Core i7（见第 3 章）与 Intel Xeon E7（见第 5 章）几乎相同，但提供了不同的时钟频率与存储系统，使 Xeon E7 更适合服务器。

本书中的“体系结构”涵盖计算机设计的全部三个方面：ISA、组成（或微体系结构），以及硬件。

计算机体系结构设计者必须设计出既满足功能需求，又达到价格、功耗、性能和可用性目标的计算机。[图 1.8](/book/ch01/03-defining-architecture/#fig-1.8) 概括了设计新计算机时需要考虑的需求。很多时候，设计者还必须先确定功能需求究竟是什么，这本身就是一项重要工作。这些需求可能是市场推动下的具体功能。应用软件通过决定计算机将被如何使用，通常会影响某些功能需求的选择。如果某一种 ISA 已经拥有大量软件，设计者就可能决定让新计算机采用现有指令集。如果某类应用拥有庞大的市场，设计者也可能将有助于计算机在该市场上竞争的要求纳入设计。后续章节将深入考察其中的许多需求与特性。

<!-- source: p0036 -->

| 功能需求 | 典型的必要功能或支持特性 |
| --- | --- |
| **应用领域** | **计算机的目标用途。** |
| 物联网／嵌入式计算 | 往往需要为图形、视频或其他特定应用扩展提供专门支持；可能需要限制与控制功耗；存在实时约束（第 2、3、5、7 章；附录 A、E）。 |
| 个人移动设备 | 满足多种任务的实时性能要求，包括图形、视频和音频的交互性能；重视能效（第 2–5、7 章；附录 A）。 |
| 通用桌面计算机 | 为多种任务提供均衡性能，包括图形、视频和音频的交互性能（第 2–5 章；附录 A）。 |
| 服务器 | 支持数据库与事务处理；增强可靠性与可用性；支持可扩展性与安全性（第 2、5、7 章；附录 A、D、F）。 |
| 集群／仓库级计算机 | 面向大量独立任务的吞吐性能；存储器纠错；energy proportionality，即功耗随负载成比例变化的能力；安全性（第 2、6、7 章；附录 F）。 |
| **软件兼容层次** | **决定计算机可以利用多少已有软件。** |
| 编程语言层面的兼容 | 给予设计者最大的灵活性；需要新的编译器（第 3、5、7 章；附录 A）。 |
| 目标代码或二进制兼容 | 指令集体系结构已完全确定，灵活性很小，但无需再投入软件开发或程序移植（附录 A）。 |
| **操作系统需求** | **支持所选操作系统所必需的功能（第 2 章；附录 B）。** |
| 地址空间大小 | 非常重要的特征，可能限制应用范围（第 2 章）。 |
| 存储管理 | 现代操作系统所必需，可采用分页或分段（第 2 章）。 |
| 保护 | 不同操作系统和应用具有不同需求：分页与分段、虚拟机（第 2 章）。 |
| **标准** | **市场可能要求遵循特定标准。** |
| 浮点 | 格式与算术运算：IEEE 754 标准（附录 J），以及用于图形、信号处理或 AI 的专门算术运算。 |
| I/O 接口 | 用于 I/O 设备：Serial ATA、Serial Attached SCSI、PCI Express（附录 D、F）。 |
| 操作系统 | UNIX、Windows、Linux、iOS、Android。 |
| 网络 | 支持不同网络：Ethernet、InfiniBand（附录 F）。 |
| 编程语言 | 语言（ANSI C、C++、Java、Fortran）会影响指令集（附录 A）。 |
{#fig-1.8 num="1.8" caption="体系结构设计者需要面对的部分关键功能需求" .wide .expand}

左栏描述需求类别，右栏给出具体实例，并列出讨论相应问题的章节与附录。

设计者还必须留意技术与计算机使用方式的重要趋势，因为这些趋势不仅影响未来的成本，也影响一种体系结构的生命力。

[^isa-names]: 译注：`R64I`、`RVM`、`RVF`、`RVD` 均为原图中的写法。
[^fcvt]: 译注：原图写的是“从类型 x 转换为类型 y”。
[^be]: 译注：正文原文写作 `BE`，[图 1.5](/book/ch01/03-defining-architecture/#fig-1.5) 则列为 `beq`。
