# 3.3 Simple-MIPS指令（2）

## 阅读入口

* [Simple-MIPS教学指令集](/knowledge/cs/computer-architecture/simple-mips/)
* [CPU的核心部件](/knowledge/cs/computer-architecture/cpu-components/)

## 指令总表与控制流

### 指令总表

| 指令     | 类别      | 语法                    | 语义                                   |
| :----- | :------ | :-------------------- | :----------------------------------- |
| load   | 数据加载    | `lw $rt, offset($rs)` | 从地址 `$rs + offset`读出一个字，存入 `$rt`     |
| store  | 数据存储    | `sw $rt, offset($rs)` | 把 `$rt`的一个字写到地址 `$rs + offset`       |
| mov    | 数据移动    | `move $rd, $rs`       | `$rd = $rs`                          |
| sub    | 算术运算    | `sub $rd, $rs, $rt`   | `$rd = $rs - $rt`                    |
| shiftl | 逻辑左移    | `sll $rd, $rt, shamt` | `$rd = $rt << shamt`，右侧补0            |
| shiftr | 逻辑右移    | `srl $rd, $rt, shamt` | `$rd = $rt >> shamt`，左侧补0            |
| slt    | 比较      | `slt $rd, $rs, $rt`   | `$rs < $rt`时 `$rd = 1`，否则 `$rd = 0`  |
| sle    | 比较（伪指令） | `sle $rd, $rs, $rt`   | `$rs <= $rt`时 `$rd = 1`，否则 `$rd = 0` |
| beqz   | 条件分支    | `beqz $rs, label`     | `$rs == 0`时转到`label`                 |
| goto   | 无条件跳转   | `goto label`          | 直接转到`label`                          |

表中的`mov`、`shiftl`、`shiftr`是教学指令集里的名称，汇编写法分别是`move`、`sll`、`srl`；`goto`对应真实MIPS的`j`。读真实MIPS汇编时按真实指令集的助记符理解，不要混用。

### 访存指令的地址计算

load与store都采用“基址寄存器加偏移”的寻址方式：`$rs`中存放基址，`offset`是立即数，实际访问的地址为两者之和。偏移可以为0，此时直接访问 `$rs`指向的字。这两条指令一次都只移动一个32位的字。

可以借助一个类比记住方向：寄存器像手边的工作台，内存像书架；load把数据从书架搬到工作台，store把工作台的内容放回书架。所以load的 `$rt`是写入目标，store的 `$rt`是数据来源。

### 复制、减法与移位

复制指令把源寄存器的值写到目标寄存器，两个寄存器此后的值相同。Simple-MIPS把复制视为一条基本指令；真实MIPS通常用`add`或`or`与 `$zero`配合实现，例如`add $t0, $t1, $zero`。

`sub`把 `$rs`当作被减数、`$rt`当作减数，结果写入 `$rd`。减法由ALU完成，操作数来自寄存器组，写法与`slt`一样是三个寄存器。例如 `$t1`为10、`$t2`为3时，`sub $t0, $t1, $t2`使 `$t0`等于7。

`shamt`给出移动的位数。当左移结果未丢失高位时，左移`shamt`位相当于乘以 $2^{shamt}$；对无符号数，右移`shamt`位相当于除以 $2^{shamt}$ 并向下取整。这个“相当于除以”的结论只对非负整数成立：`srl`是逻辑右移，左侧补0，不能用它处理带符号数的除法。

> [!WARNING]
> 逻辑右移与算术右移不是同一条指令。`srl`在最高位补0，负数右移后会变成一个很大的正数，不能拿它代替除以2的幂。

### 控制流指令的组合方式

`slt`和`sle`不改变执行顺序，它们只把比较结果写成0或1；`beqz`检查一个寄存器是否为0，为0就转到标签；`goto`无条件转到标签，实质是把PC置为标签对应的地址，例如`goto Loop`回到循环开头。三者配合，才能让程序离开“顺序执行”：

1. 用`slt`把条件变成0或1。

2. 用`beqz`检查这个0或1，决定是否跳出。

3. 用标签和`goto`回到循环开头。

`sle`是伪指令，汇编器通常把它展开成与`slt`相关的指令序列。它的语义完整，可以直接在作业中使用，只是不要把它当成硬件直接实现的一条指令。

[来源：Simple-MIPS教学指令集](/knowledge/cs/computer-architecture/simple-mips/#properties)

本节只新增`slt`、`sle`、`beqz`、`goto`四条，其余指令在3.2已经用过。读总表时把每条指令的语义和它对PC的影响对上，后面的组合方式就只是这几条指令的拼接。

## 学习安排

3.2的数据指令只改寄存器或内存中的值，执行顺序仍由PC顺序推进；本节加入`slt`、`sle`、`beqz`、`goto`后，程序才能改变执行顺序。读这一节时把每条指令落到PC上：`beqz`和`goto`可能改写PC，其余指令执行完PC仍加4。

* 先看`slt`、`sle`如何把比较结果变成0或1，再看`beqz`如何用这个结果决定是否跳转，最后用标签和`goto`回到循环开头。
* `sle`是伪指令，汇编器会把它展开成与`slt`相关的指令序列；作业按`sle`的语义作答。
* 比较结果就是0或1，可以像其他整数一样参与后续运算。

## 课程约定

* 条件分支只有一个方向：`beqz`在寄存器为0时跳转。想表达“小于时继续”，要先把条件写成`slt`的结果，再决定0对应哪条分支。
* `goto`对应真实MIPS的`j`；教材统一写`goto`。
* 标签是指令地址的符号名，`beqz $t0, Loop`中的`Loop`不是变量，也不保存数据。
* `slt`、`sle`只写目标寄存器，不修改两个源寄存器。

## 易错点

* `beqz`只检查是否等于0，不检查正负。判断“大于”时不要直接套`beqz`，先想清楚哪个比较结果会变成0。
* `sle`是伪指令，但语义与普通指令一样可以直接使用；不要因为“伪”就认为它不能出现在作业里。
* 写循环时别忘了在循环体末尾用`goto`回到开头，否则PC顺序执行会直接离开循环。

## 读程序的顺序

读含分支的程序时，先标出所有标签和会改写PC的指令，再沿着`beqz`的两个出口分别走一遍。只看顺序执行的那条路径，容易漏掉循环退出后的分支。
