# 6. 指令与汇编

本节我们来看如何设计最基本的算术逻辑运算指令，这些指令的运算通过前面章节提到的[16位ALU](/s2e3/3.md)来完成。我们先回顾一下该 ALU 的示意图：

![16 Bit ALU](/files/-MdWJOMG8AM4GSW0cTcJ)

ALU 通过6个控制位来确定执行何种操作，最多可以支持 2^6=64 种运算，因此理论上我们可以将想要完成的运算依次排列即可，例如规定 000001 执行 x+y, 000010 执行 x-y, 000011 执行 x\&y 等等。这种思路虽然简单直观，但并不适合设计复杂系统。

贯穿全文，我们始终在努力呈现一种方法论：复杂的组件是通过简单的组件构造而成的！这种组件化、模块化的思路是构造大型、复杂系统的核心思想。所以此处我们为 ALU 设计指令时，最好的策略也是通过组合各种更原始的操作，来达成最终的计算效果。所以此处我们让每个控制位负责一个独立的微操作，然后通过组合的方式达成复杂的计算效果。下表是6个控制位的职责：

| Control Bit | 0     | 1                        |
| ----------- | ----- | ------------------------ |
| zx          | -     | x = 0, 将 x 设置为0          |
| nx          | -     | x = !x, 将 x 按位取反         |
| zy          | -     | y = 0, 将 y 设置为0          |
| ny          | -     | y = !y, 将 y 按位取反         |
| f           | x + y | x & y                    |
| no          | -     | out = !out, 将结果 out 按位取反 |

可以将 ALU 的运算过程看着是顺序地执行各个控制位对应的操作，然后得到最终的结果，这样我们能够得到的指令表如下：

{% hint style="info" %}
其实也不是严格意义上的顺序执行，控制位 zx 与 nx 是对输入 x 进行预处理，而 zy 与 ny 是对输入 y 进行预处理，这两个处理是可以并行执行的。
{% endhint %}

| Table 1:         |    |    |    |   |    |        |
| ---------------- | -- | -- | -- | - | -- | ------ |
| ALU Instructions |    |    |    |   |    |        |
| zx               | nx | zy | ny | f | no | out    |
| 1                | 0  | 1  | 0  | 1 | 0  | 0      |
| 1                | 1  | 1  | 1  | 1 | 1  | 1      |
| 1                | 1  | 1  | 0  | 1 | 0  | -1     |
| 0                | 0  | 1  | 1  | 0 | 0  | x      |
| 1                | 1  | 0  | 0  | 0 | 0  | y      |
| 0                | 0  | 1  | 1  | 0 | 1  | !x     |
| 1                | 1  | 0  | 0  | 0 | 1  | !y     |
| 0                | 0  | 1  | 1  | 1 | 1  | -x     |
| 1                | 1  | 0  | 0  | 1 | 1  | -y     |
| 0                | 1  | 1  | 1  | 1 | 1  | x + 1  |
| 1                | 1  | 0  | 1  | 1 | 1  | y + 1  |
| 0                | 0  | 1  | 1  | 1 | 0  | x - 1  |
| 1                | 1  | 0  | 0  | 1 | 0  | y - 1  |
| 0                | 0  | 0  | 0  | 1 | 0  | x + y  |
| 0                | 1  | 0  | 0  | 1 | 1  | x - y  |
| 0                | 0  | 0  | 1  | 1 | 1  | y - x  |
| 0                | 0  | 0  | 0  | 0 | 0  | x & y  |
| 0                | 1  | 0  | 1  | 0 | 1  | x \| y |

> &#x20;注意，在二进制补码中对任意整数 x 按位取反后（即执行 !x 操作），实际得到的数值是 -x - 1, 因为 x + !x = 111….11, 在二进制补码中这是 -1 的编码方式。

我们来看一下 x + 1（对应控制码：011111）的执行过程：

1. zx 为 0, 不对 x 做任何操作
2. nx 为 1, 于是 x = !x, 从数值上来讲此时 x = -x - 1
3. zy 为 1, y = 0
4. ny 为 1, y = !y, 此时 y = -1
5. f 为 1, 所以 out = x + y = -x -2, 最右侧的 x 表示最原始的x输入数值
6. no 为 1, out = !out, 此时 out 的数值为 -(-x - 2) - 1 = x + 1

这样的逻辑拆分极大的简化了电路设计，由于每个控制位都在做一个二选一的操作，这可以通过之前介绍过的 Selector 来控制，完整的电路设计如下图所示：

![ALU Impl](/files/-MdWV_ztxtiNoVK85Kys)

图中 ng 可以通过一个 16-1 Selector 来选择 out 的最高位，而 zr 可以通过一个 16 位的或非门实现。

确定了ALU的控制信号之后，我们可以来看一条完整的CPU计算指令还缺哪些方面：

1. 如何指定输入，即 x 与 y \
   CPU 从寄存器取数据是最快的，所以我们可以通过指定两个寄存器的方式来指定输入。寄存器可以有固定的编号，例如如果 CPU 有 8 个通用寄存器的话，可以用三位数字对其进行编码，并且约定 x 在前，y 在后。例如 000001 表示使用编号为 000 的寄存器作为 x, 使用编号为 001 的寄存器作为 y, 如果不需要 y 的话可以只提供 x 的寄存器编号。
2. 如何存放输出，即 out, ng 与 zr \
   计算结果我们可以约定存入 x 的寄存器中，这样可以降低指令长度。ng 与 zr 可以使用固定的方式存入专门的寄存器中（例如一个叫着 FLAGS 的寄存器），在指令中可以不用指定。
3. CPU 如何知道这是一条计算指令 \
   上一节我们提到三类指令：计算类、数据传输类、流程控制类，因此我们可以通过两位数据来进行区分，这里令 00 表示计算类指令、01 表示数据传输类指令、10 表示流程控制类指令。这两位用以区分指令种类，我们可以将其放在指令的最前面

因此我们可以约定计算类指令的格式为：**<指令种类：2位><控制信号：6位><输入 x 与 y 的寄存器编号：3 位或者 6 位>**, 这样的话当 CPU 获取到指令 `00011111000` 时，就会将寄存器 000 中的数字加一。而对指令进行解码、寄存器寻址等操作由 CPU 的控制单元（CU）完成。

机器指令是二进制的，非常不便于人类记忆，我们可以发明一些助记符来帮助编程，即将二进制的指令符号化。例如我们可以将寄存器进行编号：A, B, C等；使用英文单词替换 ALU 的控制符，使其更有语义，例如上述指令可以简称 INC, 这样上述指令就可以写成：INC A, 而加法指令可以写成：ADD A, B. 这种符号化的机器指令叫着汇编语言（Assembly Language），从汇编语言到机器指令的转换由汇编器（Assembler）来完成。

汇编语言不仅仅是机器指令的简单映射，而是基于机器语言的又一次抽象。因为汇编语言需要通过汇编器进行处理，所以我们可以在汇编器中增加更多的功能来支持其他的编程范式，例如使用变量、模块化编程等等。这是让我们脱离硬件思维，开始使用软件架构的思路来看待程序设计的第一步。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://s2.shizhz.me/s2e6.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
