---
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: 250
range_count: 10
html_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020?start=250&limit=10"
markdown_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=250&limit=10"
previous_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=240&limit=10"
next_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=260&limit=10"
---

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

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

## PDF_PHYSICAL_PAGE_INDEX=250 | PRINTED_PAGE=234

从存储器中取出指令的过程称为取指令（instructionfetch)。在我们设计的加法器中每一条指令的长度是3个字节。因为每次从存储器取回个字节，所以取每条指令需要的时间为3个时钟周期。此外，一个完整的指令周期需要4个时钟周期。这些变化必然使得控制信号更加复杂。

机器响应指令码做一系列操作的过程称为执行（execute）指令，但这并不能表明机器是一种有生命的东西，因为它不能自行分析机器代码并决定该做什么。每种机器码用其唯的方式触发多种控制信号，从而引发机器执行各种操作。

注意，为了让上面的加法器更加有用，我们牺牲了运算速度。使用同样的振荡器，它的运算速度只有本章提到的第一个加法器的1/4。这验证了-个称为1ANSTAAFL的工程准则，它的意思是“天下没有免费的午餐”。通常上帝总是很公平的，你改进了机器的某个方面，则其他方面就会受到损失，有得就有失。

如果不使用继电器构造这个电路的话，很显然，两个64KB的RAM阵列构成了电路中最主要的部分。事实上早就应该放弃这些部件了，甚至从一开始就应该决定只需要一个1KB的存储器。如果能确定所有的数据都可以存放在0000h\~03FFh的地址空间中，那么使用一个小于64KB的存储器加法器也可以很好地工作。

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

234

## PDF_PHYSICAL_PAGE_INDEX=251 | PRINTED_PAGE=235

但是你现在仍然可能不在意使用了2个RAM阵列。事实上，确实不必要在意。前面介绍了两种RAM阵列，一个用来存放指令码，另一个用来存放操作数据一—这种设计使得自动加法器的结构非常清晰和易于使用。但现在我们使用3字节长的指令格式.第二个和第三个字节用来指明操作数的存储地址，因此就没有必要再使用两个独立的RAM阵列。操作码和操作数可以存放在同一个RAM阵列。

为了实现这个设计，我们需要一个2-1选择器来确定如何对RAM阵列寻址。通常.和前面的方式相同，我们用-个16位的计数器来计算地址。数据RAM阵列的输出仍然连接到3个锁存器，分别用来保存指令代码及其对应操作数的16位地址，其16位的地址输出是2.1选择器的第种输入。地址被锁存后，可以通过选择器将其作为RAM阵列的地址输入。

我们已经对原电路做了不少改进，现在可以把操作指令和操作数据保存在同个RAM阵列中。例如，下图演示了如何把两个8位数相加，然后从结果中再减去--个8位数

17 自动操作

235

## PDF_PHYSICAL_PAGE_INDEX=252 | PRINTED_PAGE=236

通常，指令从0000h开始存放，这是因为当计数器复位后从该位置访问RAM阵列。最后的Halt指令存放在000Ch地址。我们可以把这3个操作数及它们的运算结果保存在RAM阵列的任何地址（当然这不包括最开始的13个字节，因为它们已经用来存放操作指令)，所以我们选择在从0010h地址开始保存操作数。

假设现在你发现需要在原来的结果中再加两个数，你可以向存储器中输入一些新的指令以替换原来所有的指令，但是你可能不愿意这么做。或许你更倾向于在原指令的地址后增加一些新的指令。第一步要做的就是把000Ch地址处的Halt指令替换为一个Load指令。但你仍然需要增加两条Add指令，一条Store指令，以及一条新的Halt指令。唯一的问题是，现在0010h地址已经保存了一些数据，因此需要把这些数据转移到较高的地址空间中，然后还需要修改那些指向这些地址空间的指令。

试想一下，把操作码和操作数存放在同一个RAM阵列并不是一个急于解决的问题。但可以肯定的是，这是一个迟早要解决的问题，不如现在就找一个解决办法吧。在当前的例子中，也许你更愿意从0020h地址开始存放新的指令，并从0030h处开始存放新的操作数据。

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

236

## PDF_PHYSICAL_PAGE_INDEX=253 | PRINTED_PAGE=237

现在，两部分指令的位置分别起始于地址0000h和0020h，而两部分操作数据的地址分别起始于0010h和0030h。我们希望自动加法器从0000h开始执行所有指令完成计算任务。

我们必须移除000Ch处的Halt指令，这里的移除是指用其他代码替换它。但仅仅如此是不够的，问题在于不论我们用什么来替换Halt指令，保存在地址000Ch的字节都会被当做指令代码。更糟糕的是，从这个位置开始，存储器中每隔3个字节的地址：000Fh，0012h，0015h，0018h，001Bh以及001Eh，这些地址保存的字节也会被当做指令代码来处理。如果其中的一个字节恰好为11h(这是Store指令的代码)将会发生什么？如果 Store指令后面的2个字节的地址所指向的位置刚好为0023h，那又会发生什么呢？它导致的结果是，加法器将累加器中的内容写入这个地址，而这个地址中已经保存了重要的数据。即使这些情况都没有发生，加法器从存储器的001Eh地址之后取到的下一条指令的位置将是0021h，不是0020h，而事实上0020h才是下一条指令的存储地址。

17 自动操作

237

## PDF_PHYSICAL_PAGE_INDEX=254 | PRINTED_PAGE=238

要是保留000Ch地址处的Halt指今，我们能寻求到更好的解决办法吗？

不过，我们可以用个称为Jump（跳转）的新指令来替换Halt指令。现在把它加入到指令表。

通常情况下自动加法器是以顺序方式对RAM降列寻址的。Jump指令改变了机器的这种寻址方式，取而代之的昆从某个指定的地比始寻址。这种指令有时也被称作分支(branch）指令或者Goto指令，即“转到另↑位置”。

在上面的例子中，我们可以用↑Jump指令来替换000Ch地址外的Halt指令。

$$
\begin{array} { r }  \mathrm { ( ) 0 0 0 C h : } \left[ \begin{array} { c } { \vdots 0 0 \displaystyle \lambda } \\ { \vdots \displaystyle \lambda } \\ { \left[ 0 0 \displaystyle \lambda \right] } \\ { \displaystyle - \frac { 1 } { 2 0 0 \displaystyle \lambda } } \end{array} \right] \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { Z } \mathbb { H } \mathbb { Z } \mathbb { H } \mathbb { Z } \mathbb { O } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H } \mathbb { H }  \end{array}
$$

30h即Jump指令的代码。其后的两个字节中存放的16位地址就是自动加法器要执行的下一条指令的地址。

因此在上面的例子中，自动加法器仍然从0000h地址开始，依次执íT一条Load指令，一条 Add 指令，一条Subtract 指令和—条 Store 指令。之后执行一条 Jump 指令，跳转至地址 0020h 继续依次执行一条Load 指令，两条 Add 指令，条Store指令，最后执行-条Halt 指令。

Jump指令通过作用于16位计数器实现其功能。无论何时，只要自动加法器遇到Jump指令，计数器就会被强制输出该Jump指令后的16位地址。这可以通过16位计数器的D型边沿触发器的预置（Pre）和清零（Clr）输入来实现：

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

238

## PDF_PHYSICAL_PAGE_INDEX=255 | PRINTED_PAGE=230

这里要再次声明，在正常的操作下，Pre和Clr端的输入都应该是 ${ \mathfrak { 0 } } _ { \circ }$ 但是，当Pre=1，Q=1；当Clr=1，则Q=0。

如果你希望向一个触发器加载一个新的值（用A表示，代表地址），可以像下图所示这样连接。

通常，置位信号为 $\mathbf { 0 } _ { \mathcal { O } }$ 此时，触发器的预置端输入为0，在复位信号不为1的情况下，清零信号也为 ${ \mathfrak { d } } _ { \circ }$ 在这种情况下，触发器就可以独立清零，而不受置位信号的影响。当置位信号为1时，如果A为1，则清零输入为0，预置输入为1；如果A为0，则预置输入0，清零输入为 ${ \mathfrak { k } } _ { \circ }$ 这就意味着Q端将被设置为与A端相同的值。

我们需要为16位计数器的每一位设置一个这样的触发器。一旦加载了某个特定的值，计数器就会从该值开始计数。

然而，这对电路的改动并不是很大。从RAM阵列锁存得到的16位地址既可以作为2-1选择器（它允许该地址作为RAM阵列的地址输入）的输入，也可以作为16位计数器

17自动操作

230

## PDF_PHYSICAL_PAGE_INDEX=256 | PRINTED_PAGE=240

置位信号的输入。

显然，只有当指令代码为30h并且其后的16位地址被锁存时，我们才必须确保置位信号为1。

毋庸置疑，Jump指令的确很有用。但与之相比，一个在我们想要的情况下跳转的指令更加有用，这种指令称做条件跳转（Conditional Jump)。也许说明该命令重要性的最好方法是这样一个问题：怎样让自动加法器进行两个8位数的乘法运算？例如，我们如何利用自动加法器得到像 A7h与1Ch相乘这种简单运算的结果呢？

这其实很简单。两个8位数相乘得到一个16位数，为了方便起见，把该乘法运算中涉及的3个数均表示为16位数。第一步确定要把乘数和乘积保存到什么位置。

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

240

## PDF_PHYSICAL_PAGE_INDEX=257 | PRINTED_PAGE=241

$$
\begin{array} { l }  { 1 0 0 0 0 \mathrm { h } \Bigg \{ \begin{array} { l l } { { 0 0 \mathrm { { 0 \bar { h } } } } } \\ { { 0 0 \mathrm { { \bar { h } } } } } \end{array} \biggr \} \begin{array} { l } { { 1 6 \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } } } \\ { { \mathrm { \bar { A } } \mathrm { { 7 } } \mathrm { { \bar { h } } } } } \end{array} } \\  { 1 0 0 2 \mathrm { { \bar { h } } } _ { : } [ \begin{array} { l } { { 0 0 \mathrm { { \bar { h } } } } } \\ { { 0 0 \mathrm { { \bar { h } } } } } \end{array} ] \begin{array} { l } { { 1 6 \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { \bar { k } } } } \\ { { 1 \mathrm { C h } } } \end{array} } \\   1 0 0 4 \mathrm { { \bar { h } } } _ { : } [ \begin{array} { l } { { 0 0 \mathrm { { h } } } } \\ { { 0 0 \mathrm { { h } } } } \end{array} ] ^ { \ast } = - \begin{array} { l } { { 1 6 \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { \bar { k } } \mathrm { \bar { k } } \mathrm { \bar { k } } } } \\  { 1 6 \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { { \bar { k } } } \mathrm { \bar { k } } \mathrm { \bar { k } } } \end{array} ] \mathrm { \bar { k } } \mathrm  { \bar { k } }  \end{array}
$$

大家都知道A7h和1Ch相乘的结果（即十进制的28）和把28个A7h累加的结果相同。因此保存在地址1004h和1005h的字节实际上是累加的结果。下图演示了如何把A7h加到该地址。

当这6条指令执行完毕之后，存储器1004h和1005h地址保存的16位数与A7h乘以1 的结果相同。因此，为了使存放于该地址的值等于A7h与1Ch相乘的结果，要把这6条指令再反复执行27次。为了达到这个目的，可以在0012h地址开始把这6条指令连续输入27次；也可以在0012h处保存一个Halt指令，然后将复位键连续按28次得到最终结果。

当然，这两种方式都不是很理想。它们都要求你做重复做许多遍烦琐的事情：输入一系列指令或者反复按复位键，而重复的次数就等于乘数。你当然是不会愿意用这种方式来进行16位数乘法运算的。

17 自动操作

241

## PDF_PHYSICAL_PAGE_INDEX=258 | PRINTED_PAGE=242

但如果在地址 0012h 处放置一条 Jump指令会怎样呢？这个指令使得计数器再次从0000h处开始计数。

$$
0 . 0 1 1 2 \mathrm { h } : \left[ \begin{array} { c } { \mathrm { 3 ( ) h } } \\ { \mathrm { o { ( 0 ) h } } } \\ { \mathrm { o { ( h ) } } } \\ { \mathrm { o { ( 0 h ) } } } \end{array} \right] ^ { \sharp \sharp } \overline { { \mathfrak { H } } } ^ { \sharp } \overline { { \mathfrak { H } } } ^ { \sharp } \overline { { \mathfrak { H } } } ^ { \sharp } \overline { { \mathfrak { H } } } 0 . 0 0 0 \mathrm { h } \dot { \mathfrak { L } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overset { \cdot } { \mathfrak { H } } \overline { { \mathfrak { H } } } \overset { \cdot } { \mathfrak { H } } \overline { { \mathfrak { H } } } \overset { \cdot } { \mathfrak { H } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } : \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak { H } } } \overline { { \mathfrak \mathfrak { H } } } \overline { { \mathfrak \mathfrak { H } } } \overline  { \mathfrak \mathfrak { H } } \overline { \mathfrak { H } \mathfrak }
$$

这的确是一个巧妙的方法。第一次执行完指令之后，位于存储器的1004h和1005h地址的16位数等于A7h乘1，然后Jump指令使下一条指令从存储器顶部开始执行。第二次执行指令后，该16位数等于A7h乘2，最后其结果可以等于A7h乘1Ch。但是，这个过程不会停止下来，它会一直反复执行下去。

我们需要的是这样一种Jump指令，它只让这个过程重复执行所需要的次数，这种指令就是条件跳转指令，它并不难实现。要实现它，要做的第一步是增加一个与进位锁存器类似的1位锁存器。该锁存器被称为零锁存器（Zerolatch)，这是因为只有当8位加法器的输出全部为0时，它锁存的值才是1。

使或非门的输出为1的唯一方法是其所有的输入全为 $\scriptstyle \mathbf { 0 } _ { \circ }$ 与进位锁存器的时钟输入一样，只有当 Add、Subtract、Add with Carry、Subtract with Borrow 这些指令执行时，零锁存器才锁存1个数，该数称做零标志位（Zero flag）。注意，它是以一种似乎是相反的方式工作的：当加法器的输出全为0时，零标志位等于1；当加法器的输出不全为0时，零标志位等于0。

有了进位锁存器和零锁存器以后，我们可以为指令表新增4条指令。

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

242

## PDF_PHYSICAL_PAGE_INDEX=259 | PRINTED_PAGE=243

例如，非零转移指令（JumpIf Not Zero)只有在零锁存器的输出为0时才会跳转到指定的地址。换言之，如果上一步的加法、减法、进位加法、或者借位减法等运算的结果为0时，将不会发生跳转。为了实现这个设计，只需要在常规跳转命令的控制信号之上再加一个控制信号：如果指令是JumpIf Not Zero，那么只有当零标志位是0时，16位计数器才被触发。

下图中0012h地址之后的指令即两个数相乘所用到的上表中的所有指令。

$$
\begin{array} { r l }  \operatorname* { m i n } _ { 1 } \{ \begin{array} { l l }  \operatorname* { m i n } _ { 1 } \{ \begin{array} { l l } { 0 . 1 0 6 1 \} } &  \operatorname { i n } _ { 1 } \{ \mathrm { ~ t o : 1 : 0 : 5 6 1 4 \} \mathrm { i : 1 : 6 1 : 1 : 0 : 1 6 1 : 1 4 \} } \\ { 0 . 1 0 6 1 } \end{array}  } \\ { [ \begin{array} { l } { 1 . 0 0 6 1 } \\ { 0 . 0 1 6 1 } \end{array} ] \} } \\ { \operatorname* { m i n } _ { 1 } \{ \begin{array} { l l } { 0 . 0 1 6 1 \mathrm { i : 1 : 0 : 1 6 1 : 0 : 1 6 4 : 7 : 1 6 1 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 } } \\ { 0 . 0 1 6 1 } \end{array}  } \\  \operatorname* { m i n } _ { 1 } \{ \begin{array} { l }  0 . 0 0 6 1 \mathrm  i : 1 4 ~ 1 : 0 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 : 1 4 :  \end{array} \end{array} \end{array}
$$

17 自动操作

243

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