# CPU的核心部件

## 部件的分工

CPU执行程序的过程可以拆成取指令、译码、执行、写回几步，每一步由不同的部件承担。Simple-MIPS是32位架构：内存按字节编址，一条指令固定占4字节，通用寄存器也都是32位。下表给出四个核心部件的分工。

| 部件        | 保存或处理的内容       | 在指令执行中的作用                |
| :-------- | :------------- | :----------------------- |
| 程序计数器PC   | 下一条要执行的指令的内存地址 | 把取指地址交给内存；顺序执行时指向下一条指令   |
| 指令寄存器IR   | 当前正在执行的指令      | 暂存取回的32位指令，供控制单元译码       |
| 算术逻辑单元ALU | 两个输入操作数和一个输出结果 | 完成算术、逻辑、移位和比较运算          |
| 寄存器组      | 32个32位通用寄存器    | 存放参与运算的数据与地址，是ALU的直接数据来源 |

PC保存的是地址而不是指令本身；指令从内存取回后才放进IR。这两个部件都与“当前执行到哪条指令”有关，区别在于PC指向下一条，IR保存当前这一条。

## 各部件的职责

### 程序计数器PC

PC存储下一条要执行的指令的内存地址。可以把它想成读书时的书签，标记下一次从哪里继续。顺序执行时，它在取指后增加4：一条Simple-MIPS指令是32位，即4字节，下一条指令正好在相邻的4字节之后。分支和跳转指令会直接改写PC，顺序执行、循环和条件分支都靠这一点实现。

PC只负责提供地址，不判断该地址上是什么内容；取回指令之后的工作由IR和控制单元接手。

### 指令寄存器IR

取指时，内存按PC给出的地址送回32位指令，这条指令先放进IR，再由控制单元译码并发出执行信号。IR只保存当前这一条指令，取下一条时会覆盖它的内容。

一条Simple-MIPS指令的32位通常划分为几个字段：

* **操作码**：指出指令类型，例如加载、存储、减法；
* **寄存器编号**：指出源寄存器和目标寄存器；
* **立即数或地址偏移**：供内存访问或算术运算使用的常数。

### 算术逻辑单元ALU

ALU是执行运算的部件，通常接收两个操作数，输出一个结果。它处理的运算包括：

* 算术运算：加、减、乘、除；
* 逻辑运算：与、或、非、异或；
* 移位运算：左移、右移，包含逻辑移位和算术移位；
* 比较运算：比较两个数的大小，设置条件标志。

执行哪一种运算由控制单元根据操作码决定。ALU本身不区分当前是哪条指令，它只对送进来的操作数执行被指定的运算。

### 寄存器组

寄存器是CPU内部的高速存储单元，用来临时存放数据和地址。它的访问通常在1个时钟周期内完成，比内存快得多；数量有限，Simple-MIPS有32个通用寄存器；每个寄存器固定32位。

寄存器按用途有习惯命名：

| 寄存器         | 用途          |
| :---------- | :---------- |
| `$zero`     | 恒为0         |
| `$at`       | 汇编器临时使用     |
| `$v0`–`$v1` | 函数返回值       |
| `$a0`–`$a3` | 函数参数        |
| `$t0`–`$t9` | 临时寄存器       |
| `$s0`–`$s7` | 需要跨调用保留的寄存器 |

`$zero`的值由硬件固定，写入也会被丢弃，因此常用来提供常数0。以上是常见命名，完整编号共32个；寄存器编号如何出现在指令里，见[Simple-MIPS教学指令集](/knowledge/cs/computer-architecture/simple-mips/)。

### 一条指令的取指与执行

1. PC把地址送到内存，内存返回该地址上的32位指令；顺序执行时PC同时增加4。
2. 指令进入IR，控制单元按操作码译码，确定要读哪些寄存器、做什么运算、是否访问内存。
3. 寄存器组把操作数送入ALU，ALU完成运算；如果指令是load或store，还要由内存完成读写。
4. 结果写回寄存器组，PC已经指向下一条指令。

> [!NOTE]
> PC加4的单位是字节：一条指令4字节，所以相邻两条指令的地址相差4。把“加4”理解成“跳4条指令”会差4倍。

## 例子

**跟踪`sub $t0, $t1, $t2`的部件流转**

设当前PC为`0x00400000`，`$t1`的值为9，`$t2`的值为4，执行

```text title="sub.s"
sub $t0, $t1, $t2
```

1. **取指**：PC把`0x00400000`交给内存，取回该地址上的32位指令，PC变为`0x00400004`。

2. **译码**：指令进入IR。控制单元从操作码认出这是减法，从寄存器编号字段读出源寄存器 `$t1`、`$t2`和目标寄存器 `$t0`。

3. **执行**：寄存器组把9和4送入ALU，ALU做减法，得到5。

4. **写回**：结果写回 `$t0`。PC已经指向`0x00400004`处的下一条指令。

执行后 `$t0`的值为5，`$t1`与 `$t2`不变。

::\:

**用 `$zero`复制寄存器的值**

`add $t0, $t1, $zero`的第二个操作数是恒为0的 `$zero`，加法不改变 `$t1`的值，只把它复制到 `$t0`。这条指令与`move $t0, $t1`做同一件事，两种写法的取舍见[Simple-MIPS教学指令集](/knowledge/cs/computer-architecture/simple-mips/)。

## 交互探索

逐步执行一条减法指令，查看PC、IR、ALU和目标寄存器的状态。

交互演示：一条减法指令经过哪些部件？。逐步查看取指、译码、执行和写回中的部件状态。 请在站点操作演示。

## 练习

1. 取回的指令在执行前先存放在哪个部件？

   * A.  程序计数器PC
   * B.  指令寄存器IR
   * C.  算术逻辑单元ALU
   * D.  寄存器组

   答案：B

   取指后指令进入IR，由控制单元译码；PC保存的是地址。

2. 一条Simple-MIPS指令长32位，顺序执行时PC每次增加多少字节？

   PC增加 \_\_\_\_\_\_（step） 字节。

   答案：4

   32位等于4字节，相邻指令的地址相差4。

3. 哪个寄存器的值恒为0？

   * A.  `$at`
   * B.  `$t0`
   * C.  `$zero`
   * D.  `$s0`

   答案：C

   `$zero`由硬件固定为0，常用来提供常数0。

4. ALU可以直接完成下列哪些运算？

   * A.  两个寄存器相减
   * B.  按位与、或、非、异或
   * C.  左移和右移
   * D.  把内存中的数据读入寄存器

   答案：A、B、C

   读内存由load指令经内存完成，ALU只处理送进来的操作数。
