---
schema_version: "library-reader-v1"
source_type: "original_book"
title: "编码 隐匿在计算机软硬件背后的语言"
author: null
edition: null
publisher: null
year: null
isbn: "9787121181184"
library_document_id: "38270854-596f-4a26-8183-db8c968c6020"
source_sha256: "249786274d83909f0bddc34d77f81ed8f0b14d1eac8843c772a37a3ecbfe2cfc"
pdf_physical_page_count: 438
pdf_physical_page_index_base: 0
range_start: 240
range_count: 10
html_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020?start=240&limit=10"
markdown_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=240&limit=10"
previous_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=230&limit=10"
next_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=250&limit=10"
---

# 编码 隐匿在计算机软硬件背后的语言

> 页码规则：PDF_PHYSICAL_PAGE_INDEX 是从 0 开始的权威文件物理页索引；PRINTED_PAGE 是页面中识别出的印刷页码，可能为空，二者不得混用。

## PDF_PHYSICAL_PAGE_INDEX=240 | PRINTED_PAGE=224

如前所述，最初的自动加法器的8位锁存器的输出要作为数据RAM阵列的输入，这就是Save指令的功能。还需要做另一个改变：以前8位加法器的输出是8位锁存器的输入，但现在为了执行Load指令，数据RAM阵列的输出有时也要作为8位锁存器的输入，这种新的变化需要一个2-1选择器来实现。改进后的自动加法器如下图所示。

图中略去了一些组件，但是仍然清晰地描述了各个组件之间的8位数据通路。16位的计数器为两个RAM阵列提供地址输入。通常，数据RAM阵列的输出传入到8位加法器执行加操作。8位锁存器的输入可以是数据RAM阵列的输出（当执行Load指令时)，也可以是加法器的输出（当执行Add指令时)，这种情况下就需要2-1选择器。通常，锁存器电路的输出又流回到加法器中，但是当执行Save指令时，它就成为了数据RAM阵列的输入数据。

编码隐匿在计算机软硬件背后的语言

224

## PDF_PHYSICAL_PAGE_INDEX=241 | PRINTED_PAGE=225

上图缺少的是控制所有这些组件的信号，它们统称为控制信号，包括16位计数器的“时钟”输入和“清零”输入，8位锁存器的“时钟”输入和“清零”输入，数据RAM阵列的“写”（W）输入，2-1选择器的“选择”（S）输入。其中的一些信号很明显是基于代码RAM阵列的输出，例如，如果代码RAM阵列输出是Load指令，那么2-1选择器的“选择”输入必须是0（即选择数据RAM的输出）。只有当操作码是指令 Store时，数据RAM阵列的“写”(W)输入必须是1。这些控制信号可以通过逻辑门的各种组合来实现。

利用最少的附加硬件和一些新增的操作码，可以让这个电路从累加器中减去一个数。第1步是向操作码表增加一些代码。

17 自动操作

225

## PDF_PHYSICAL_PAGE_INDEX=242 | PRINTED_PAGE=226

对于 $\mathbf { A d d }$ 和Subtract的代码，其区别仅在于最低有效位，我们称该位为 $\bf { C _ { 0 \odot } }$ 如果操作码为21h，除了数据RAM阵列的数据传入加法器之前要取反，并且加法器进位输入置1之外，电路所做的操作与执行Add指令所做的操作相同。在这个增加了一个反相器的改进电路中， $\mathbf { C _ { 0 } }$ 信号可以完成这两项任务。改进后的电路结构图如下。

编码—隐匿在计算机软硬件背后的语言

226

## PDF_PHYSICAL_PAGE_INDEX=243 | PRINTED_PAGE=227

列中的代码（操作码）和数据（操作数）完成该运算。

Load操作完成之后，累加器中的值更新为56h，加法操作完成后累加器中的值为56h与2Ah的和，即80h。Subtract操作使数据RAM阵列的下个值（38h）按位取反，得到C7h。当加法器的进位输入置1时，取反得到C7h，然后使其与80h相加：

$$
\begin{array} { r } { \mathrm { C ^ { \prime } / f _ { 1 } } } \\ { \mathrm { ~ } } \\ { \mathrm { ~ } } \\ { \mathrm { ~ \mathfrak { f } ^ { \prime } ~ } } \\ { \mathrm { ~ \mathfrak { f } ^ { \prime } ~ } } \\ { \mathrm { ~ \mathfrak { f } ^ { \prime } ~ } } \end{array}
$$

最后的结果是48h。（有十进制中，80加4再减人56等十12)

过有自没有找到台活的解决归法宿中题，加法器技连接到工的同有蟲备的商度只有8位。以前提出过的个解决办法是把两午8位加法器（具他的大部分设备也用两个）连在一起，构成↑16位的设备。

但还有代价更小的解决办法，假如你想把两个16位的数相加，比如，

$$
\begin{array} { c } { { 7 6 \mathrm { A B h } } } \\ { { } } \\ { { + 2 3 2 \mathrm { C h } } } \\ { { \ } } \end{array}
$$

这种16位的加法先单独处理最石边的字节（通常称之为低字节）：

$$
\begin{array} { r } { \mathbf { A B h } } \\ { \frac { + 2 \mathbf { C h } } { \mathbf { D } / \mathbf { h } } } \end{array}
$$

然后再计算最左边的字节，即高字节的和：

17自动操作

227

## PDF_PHYSICAL_PAGE_INDEX=244 | PRINTED_PAGE=228

$$
\frac { 7 6 \mathrm { h } } { 9 9 \mathrm { h } }
$$

得到相同的结果99D7h。因此，如果我们把两个16位的数用这种方式保存在存储器中，就像下面这样：

$$
\begin{array} { r l } & { \begin{array} { r l } & { \mathrm { i t s i g } } \\ & { \mathrm { 0 0 0 0 } \mathrm { h : } } \\ & { [ \begin{array} { l l l } { \mathrm { 1 0 h } } & { \mathrm { l o a d } } & { \mathrm { 0 0 0 0 h : } } \\ { \mathrm { 2 0 h } } & { \mathrm { A d d } } & { \mathrm { 2 C h } } \\ { \mathrm { 1 1 h } } & { \mathrm { l o t e r e } } & { \mathrm { 2 C h } } \end{array} ] } \end{array} } \\ & { \begin{array} { r l } & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 1 0 h } } \end{array} ] \mathrm { ~ L o a d } } \\ & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 2 0 h } } \end{array} ] \mathrm { ~ L o a d } } \\ & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 1 1 h } } \end{array} ] \mathrm { ~ A d d } } \\ & { [ \begin{array} { l } { 1 1 \mathrm { h } } \\ { \mathrm { 1 1 h } } \end{array} ] \mathrm { ~ H o r e } } \end{array} } \end{array} \begin{array} { r l } & { \mathrm { 2 0 h } } \\ & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 2 0 h } } \end{array} ] \times } \\ & { [ \begin{array} { l } { 1 0 \mathrm { \overline { { ~ 7 } } t r } } \\ { \mathrm { 4 1 h } } \end{array} ] \times } \\ & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 4 1 h } } \end{array} ] \times } \end{array} \begin{array} { r l } & { \mathrm { 2 0 h } } \\ & { [ \begin{array} { l } { 1 0 \mathrm { h } } \\ { \mathrm { 2 0 h } } \end{array} ] \times } \\ &  [ \begin{array} { l }  1 0 \mathrm { \overline { { ~ 7 } } t r } \mathrm { \overline { { ~ 9 } } t r } \mathrm { \overline { { ~ 1 0 } } } \mathrm { ~ \overline { { ~ 8 } } t r } \mathrm { \overline { { ~ 1 0 } } } \mathrm  ~  \end{array} \end{array}
$$

运算结果 D7h 将被保存到地址 0002h，而结果99h将被保存到地址0005h。

当然并非所有的情况都是这样处理，只是上面的例子中用到了这种方法。如果要把76ABh和236Ch这两个16位的数相加该怎么做呢？在这个例子中，对两个数的低字节求和时将会产生一个进位：

$$
\frac { \mathbf { A B h } } { \mathbf { 1 1 7 h } }
$$

产生的这个进位必须与两个数的高字节的和再相加：

$$
\begin{array} { r } { \mathbf { l h } } \\ { + 7 6 \mathbf { h } } \\ { \frac { + 2 3 \mathbf { h } } { 9 \mathbf { A } \mathbf { h } } } \end{array}
$$

最后的计算结果为9A17h。

我们能够改进自动加法器的电路，使它可以正确地进行16位数的加法操作吗？答案是肯定的，我们需要做的仅仅是在第一步运算时保存低字节数运算的进位输出，并把它作为下一步高字节数运算的进位输入。如何保存1位呢？1位锁存器就是最好的选择了，该锁存器应该被称为进位锁存器（Carrylatch)。

编码隐匿在计算机软硬件背后的语言

228

## PDF_PHYSICAL_PAGE_INDEX=245 | PRINTED_PAGE=229

为了使用进位锁存器，还需要另一个操作码，我们称之为“进位加法”（AddwithCarry)。当进行8位数加法时，使用的是常规的Add指令。加法器的进位输入是0，它的进位输出将会保存到进位锁存器（尽管它根本不会被用到）。

如果要对两个16位的数进行加法运算，我们仍然使用常规的Add指令对两个低字节数进行加法运算。加法器的进位输入是0，而其进位输出被锁存到进位锁存器中。当把两个高字节数相加时，要使用新的Add with Carry指令。在这种情况下，两个数相加时要用进位锁存器的输出作为加法器的进位输入。因此，如果第一步低字节数的加法运算有进位，则该进位将用于第二步高字节数的加法运算；如果没有进位，则进位锁存器的输出是0。

如果要进行16位数的减法运算，则还需要一个新的指令，称为“借位减法”（SubtractandBorrow)。通常，Subtract指令需要将减数取反并且把加法器的进位输入置1。进位输出通常不是1，因此应该被忽略。但对16位数进行减法运算时，进位输出应该保存在进位锁存器中。在进行第二步的高字节减法运算时，锁存器保存的结果应该作为加法器的进位输入。

在加入了Add with Carry 和 Subtract and Borrow之后，目前我们已经有了 7 个操作码，如下表所示。

在执行减法或借位减法运算时，送入加法器中的操作数需要进行取反预处理。加法器的进位输出是进位锁存器的数据输入。无论何时，执行加法、减法、进位加法或借位减法中的任一种运算，进位锁存器都是同步的。当执行减法运算，或进位锁存器的数据输入为1且正在执行进位加法或者借位减法运算时，8位加法器的进位输入都是置1的。

17 自动操作

229

## PDF_PHYSICAL_PAGE_INDEX=246 | PRINTED_PAGE=230

需要记住的是，只有当前一次的加法或者进位加法操作使加法器产生进位输出时，Add wiuh Carry指令才会使8位加法器的进位输入置1。因此，只要进行多字节数加法运算，不管实际是否需要，都应该使用Add withCairy指令。为了保证编码的正确，使前面提到16位加法正常进行，可用如下方法。

操作数是该方法都可拥执门。

增加了两个新的操作码之后，我们经极大地扩展了加法器的动能，1再局限于8位数加法运异通过执行进位加法操作，司以对16位数24位数、位数40位数，基军更多位的数基行加法三算假如要井门两1位数7A891MC1h和6A811的加法三昇，我区要杀Add指相杀AddwCanry指，如图所个。

编码——隐匿在计算机软硬件背后的语言

230

## PDF_PHYSICAL_PAGE_INDEX=247 | PRINTED_PAGE=231

当然，把这些数依次输入存储器并不是最好的做法。因为你不但要使用开关来输入这些数，而且保存这些数的存储单元的地址也不是连续的。例如，7A892BCDh从最低字节开始，每个字节依次保存在0000h，0003h，0006h，0009h中。而为了得到最后的结果，还需要检查0002h，0005h，0008h，000Bh这几个地址中的数。

除此之外，当前设计的自动加法器不允许在随后的计算中重复使用前面的计算结果。假设我们要对三个8位数求和，然后再从中减去一个8位数并保存结果。这可能需要一条Load指令，两条 Add指令，-一条 Subtract指令以及一条Store指令。但如果想从原来的求和结果（3个8位数的和)中减去另一个数该怎么做呢？这个求和结果已经不能被访问了，每次我们使用它的时候都必须重新计算。

产生上述情况的原因就在干我们构造的自动加法器具有如下的特性：它的代码存储器和数据存储器是同步的、顺序的，并且都从0000h开始寻址。代码存储器中的每一条指令对应数据存储器中相同地址的存储单。日执行了条Store指令，相应的，就会有数被保存到数据存储器中，而这个数将能重新加载到累加器。

要解决这个难题，需要对自动加法器的设计做个根本性的程度极大的修收这想法现起平似平非常困难，但是很快你就会发现（我希望是文样）改进后的加法器且有更高的灵活性。

现在让我们立刻开始吧，目前已经有了1个操作码，如下所下。

每一个操作码在存储器中占1个字节。现在除了Halt操作码外，我希望每一个指令在存储器中仅占据3个字节的空间，其中第一个字节为代码本身，另外的两个字节用来存放1个16位存储器单元地址。对于Load指令来说，后两个字节保存的地址用来指明数据RAM阵列的一个存储单元，该单元存放的是需要被加载到累加器中的字节。对于

17 自动操作

231

## PDF_PHYSICAL_PAGE_INDEX=248 | PRINTED_PAGE=232

Add，Subtract，Add with Carry，Subtract with Borrow指令来说，该地址指明的存储单元所保存的是要从累加器中加上或减去的字节。对于Store指令来说，该地址指明的是累加器中的内容将要保存到的存储单元地址。

例如，当前加法器所能进行的最简单的运算就是对两个数求和。为了执行这个操作，需要按下面的方式设置代码RAM阵列和数据RAM阵列。

$$
\begin{array}{c} \begin{array} { r l } { { 4 } \overrightarrow { 1 } \overrightarrow { 1 3 } } & { { } } \\ { 0 . 0 0 0 \mathbf { h } \colon [ \begin{array} { l } { 1 0 \mathbf { h } } \\ { 1 0 \mathbf { h } } \end{array} ] \mathbf { L o a d } } & { { } } \\ { [ \begin{array} { l } { 2 0 \mathbf { h } } \\ { 2 0 \mathbf { h } } \end{array} ] \mathbf { A d d } } & { { } } \\ { [ \begin{array} { l } { 1 1 \mathbf { h } } \\ { \mathrm { F F h } } \end{array} ] \mathbf { S t o r e } } \end{array}   & { { } } \end{array} \begin{array} { r l } { \underbrace { \frac { 4 \mathbf { k } + \mathbf { B } } { 2 \mathbf { k } } } _ { \begin{array} { l } { 0 . 0 0 0 \mathbf { h } \colon [ 4 \mathbf { A } \mathbf { h } ] } \\ { 0 . 5 \mathbf { h } } \end{array} } } \\ { [ \begin{array} { l } { 1 0 \mathbf { h } } \\ { \mathrm { F r h } } \end{array} ] \mathbf { H } \mathbf { a d t } } & { { } } \end{array}
$$

在改进的自动加法器中，每条指令（除了Halt指令）需要3个字节。

每一条指令的代码（除了Halt指令）后跟两个字节，用来指明数据RAM阵列中16位的存储地址。这三个地址恰巧是0000h，0001h和0002h，但它们可以是任何其他可用的地址。

前面讲到了如何用 Add 或 Add with Carry 指令来对两个 16 位数—比如 76ABh 和232Ch求和。必须把两个数的低字节保存到存储器的0000h和0001h地址，把其高字节保存到0003h和0004h地址，运算的结果分别保存在0002h和0005h。

通过这种变化，我们可以用一种更合理的方式来保存这两个操作数及其运算结果，

编码—隐匿在计算机软硬件背后的语言

232

## PDF_PHYSICAL_PAGE_INDEX=249 | PRINTED_PAGE=233

可能会把它们保存到我们从未用到过的存储区域。

这6个存储单元不必像上图中这样全都连在一起，它们可以分散在整个64KB数据RAM阵列的任意位置。为了把这些地址中的数相加，代码RAM阵列中的指令必须用以下方式设置。

可以看到，保存在地址4001h和4003h处的两个低字节数先执行加法，其结果保存在4005h 地址处。两个高字节数（分别保存在 4000h 和4002h 处）通过 Add with Carry指令相加，其结果保存在地址4004h 处。如果去掉Halt指令并向代码 RAM中加入更多指令，随后的计算可以通过引用地址很方便地使用原来的那些操作数及其结果。

实现该设计的关键是把代码RAM阵列的数据输出到3个8位锁存器中。每个锁存器保存该3字节指令的一个字节。第一个锁存器保存指令代码本身，第二个锁存器保存地址的高字节，第三个锁存器保存地址的低字节。第二个和第三个锁存器的输出构成了数据RAM阵列的16位地址。

17 自动操作

233

下一段：[继续读取](https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=250&limit=10)
