# 5.1.1描述注册机的语言

> 译者： [https://sicp.comp.nus.edu.sg/chapters/96](https://sicp.comp.nus.edu.sg/chapters/96)

数据路径图和控制器图足以表示简单的机器（例如GCD），但是它们对于描述大型机器（例如JavaScript解释器）不方便。 为了使处理复杂的机器成为可能，我们将创建一种语言，该语言以文本形式表示数据路径图和控制器图给出的所有信息。 我们将从直接镜像图表的符号开始。

我们通过描述寄存器和操作来定义机器的数据路径。 为了描述一个寄存器，我们给它起一个名字并指定控制分配给它的按钮。 我们为每个按钮命名，并指定在按钮控制下进入寄存器的数据源。 （源是一个寄存器，一个常数或一个操作。）为了描述一个操作，我们给它命名并指定其输入（寄存器或常数）。

我们将机器控制器定义为_指令_和_标签_的序列，这些标签标识序列中的_入口点_。 指令是以下之一：

*   按下以将值分配给寄存器的数据路径按钮的名称。 （这对应于控制器图中的方框。）
*   `test`指令，执行指定的测试。
*   根据先前测试的结果，有条件分支（`branch`指令）到控制器标签指示的位置。 （测试和分支一起对应于控制器图中的菱形。）如果测试为假，则控制器应继续执行序列中的下一条指令。 否则，控制器应继续标签后的说明。
*   命名控制器标签的无条件分支（`go_to`指令），在该标签处可以继续执行。

机器在控制器指令序列的开始处启动，并在执行到达序列的末尾时停止。 除非分支更改了控制流程，否则指令将按照其列出的顺序执行。 图5.3 GCD机器的规格。

图 [5.3](96#fig_5.3) 显示了以此方式描述的GCD机器。 由于GCD机非常简单，因此该示例仅暗示了这些说明的一般性：每个寄存器只有一个按钮，并且每个按钮和测试在控制器中仅使用一次。

不幸的是，很难阅读这样的描述。 为了理解控制器指令，我们必须不断地参考按钮名称和操作名称的定义，并且要了解按钮的作用，我们可能必须参考操作名称的定义。 因此，我们将转换我们的表示法，以结合来自数据路径和控制器描述的信息，以便我们可以一起查看所有信息。

为了获得这种形式的描述，我们将用其行为的定义替换任意按钮和操作的名称。 也就是说，不是说（在控制器中）&lt;quote&gt;按钮`t&lt;-r`&lt;/quote&gt; ，而是说（在数据路径中）&lt;quote&gt;按钮`t&lt;-r`，是将`rem`操作的值分配给寄存器[ `t`&lt;/quote&gt; 和 &lt;quote&gt;`rem`操作的输入是寄存器`a`和`b`的内容，&lt;/quote&gt;我们将说（在控制器中）&lt;quote&gt;按下分配给寄存器[ `t`对寄存器`a`和`b`的内容进行`rem`运算的值。&lt;/quote&gt; 类似地，代替说&lt;quote&gt;测试`=`测试&lt;/quote&gt;而不是说（在数据路径中）&lt;quote&gt;测试`=`测试对寄存器`b`的内容进行操作 ]和常数0&lt;/quote&gt; ，我们将说&lt;quote&gt;对寄存器`b`和常数0的内容执行`=`测试。&lt;/quote&gt;我们将省略数据路径描述，仅保留控制器 序列。 因此，GCD机器描述如下：

```js
controller(
     "test-b",
     list(test(list(op("="), reg(b), constant(0))),
          branch(label("gcd-done")),
          assign("t", list(op("rem"), reg("a"), reg("b"))),
          assign("a", reg(b)),
          assign("b", reg(t)),
          go_to(label("test-b"))),
     "gcd-done");
```

这种描述形式比图 [5.3](96#fig_5.3) 中所示的形式更易于阅读，但它也有缺点：

*   对于大型机器，它更为冗长，因为每当在控制器指令序列中提到数据路径元素时，都会重复对数据路径元素的完整描述。 （在GCD示例中这不是问题，因为每个操作和按钮仅使用一次。）此外，重复数据路径描述会掩盖机器的实际数据路径结构； 对于大型机器来说，有多少个寄存器，操作和按钮以及它们是如何互连的，这并不明显。
*   由于机器定义中的控制器指令看起来像JavaScript表达式，因此很容易忘记它们不是任意的JavaScript表达式。 他们只能注明合法的机器操作。 例如，操作只能直接对常量和寄存器的内容进行操作，而不能对其他操作的结果进行操作。

尽管存在这些缺点，我们仍将在本章中始终使用这种寄存器机器语言，因为我们将更加关注理解控制器，而不是了解数据路径中的元素和连接。 但是，我们应该记住，数据路径设计对于设计真实机器至关重要。

<exercise>Use the register-machine language to describe the iterative factorial machine of exercise <ref name="ex:iterative-fact">[5.1](95#ex_5.1)</ref>.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_96_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

## 动作

让我们修改GCD机器，以便我们可以输入想要的GCD号码，并在终端上打印答案。 我们将不讨论如何使机器能够读取和打印，而是假设（就像我们在JavaScript中使用`prompt`和`display`时所做的那样）它们可用作原始操作。

操作`read`就像我们一直在使用的操作一样，它产生的值可以存储在寄存器中。 但是`read`不会从任何寄存器获取输入； 它的价值取决于我们正在设计的机器部件外部发生的事情。 我们将允许我们的机器操作具有这种行为，因此将像使用其他任何计算值的操作一样绘制并注明`read`的使用。

另一方面，操作`print`与我们一直在使用的操作有根本的不同：​​它不会产生要存储在寄存器中的输出值。 尽管有影响，但这种影响并不在我们正在设计的机器上。 我们将这种操作称为_操作_。 我们将在数据路径图中表示一个动作，就像表示一个计算值的操作一样（梯形包含动作的名称）。 箭头从任何输入（寄存器或常量）指向动作框。 我们还将按钮与动作相关联。 按下按钮即可进行操作。 为了使控制器按下操作按钮，我们使用了一种称为`perform`的新型指令。 因此，通过指令在控制器序列中表示打印寄存器`a`的内容的动作

```js
perform(list(op("print"), reg("a")))
```

图 [5.4](96#fig_5.4) 显示了新GCD机器的数据路径和控制器。 我们没有让机器在打印答案后停止，而是让它重新开始，以便它反复读取一对数字，计算其GCD并打印结果。 这种结构类似于我们在第4章的解释器中使用的驱动程序循环。 5.4 一种GCD机器，可读取输入并打印结果。