PDF 物理页索引 250
文件中的第 251 页 · 印刷页码:234
从存储器中取出指令的过程称为取指令(instructionfetch)。在我们设计的加法器中每一条指令的长度是3个字节。因为每次从存储器取回个字节,所以取每条指令需要的时间为3个时钟周期。此外,一个完整的指令周期需要4个时钟周期。这些变化必然使得控制信号更加复杂。
机器响应指令码做一系列操作的过程称为执行(execute)指令,但这并不能表明机器是一种有生命的东西,因为它不能自行分析机器代码并决定该做什么。每种机器码用其唯的方式触发多种控制信号,从而引发机器执行各种操作。
注意,为了让上面的加法器更加有用,我们牺牲了运算速度。使用同样的振荡器,它的运算速度只有本章提到的第一个加法器的1/4。这验证了-个称为1ANSTAAFL的工程准则,它的意思是“天下没有免费的午餐”。通常上帝总是很公平的,你改进了机器的某个方面,则其他方面就会受到损失,有得就有失。
如果不使用继电器构造这个电路的话,很显然,两个64KB的RAM阵列构成了电路中最主要的部分。事实上早就应该放弃这些部件了,甚至从一开始就应该决定只需要一个1KB的存储器。如果能确定所有的数据都可以存放在0000h\~03FFh的地址空间中,那么使用一个小于64KB的存储器加法器也可以很好地工作。
编码——隐匿在计算机软硬件背后的语言
234
PDF 物理页索引 251
文件中的第 252 页 · 印刷页码:235
但是你现在仍然可能不在意使用了2个RAM阵列。事实上,确实不必要在意。前面介绍了两种RAM阵列,一个用来存放指令码,另一个用来存放操作数据一—这种设计使得自动加法器的结构非常清晰和易于使用。但现在我们使用3字节长的指令格式.第二个和第三个字节用来指明操作数的存储地址,因此就没有必要再使用两个独立的RAM阵列。操作码和操作数可以存放在同一个RAM阵列。
为了实现这个设计,我们需要一个2-1选择器来确定如何对RAM阵列寻址。通常.和前面的方式相同,我们用-个16位的计数器来计算地址。数据RAM阵列的输出仍然连接到3个锁存器,分别用来保存指令代码及其对应操作数的16位地址,其16位的地址输出是2.1选择器的第种输入。地址被锁存后,可以通过选择器将其作为RAM阵列的地址输入。
我们已经对原电路做了不少改进,现在可以把操作指令和操作数据保存在同个RAM阵列中。例如,下图演示了如何把两个8位数相加,然后从结果中再减去--个8位数
17 自动操作
235
PDF 物理页索引 252
文件中的第 253 页 · 印刷页码:236
通常,指令从0000h开始存放,这是因为当计数器复位后从该位置访问RAM阵列。最后的Halt指令存放在000Ch地址。我们可以把这3个操作数及它们的运算结果保存在RAM阵列的任何地址(当然这不包括最开始的13个字节,因为它们已经用来存放操作指令),所以我们选择在从0010h地址开始保存操作数。
假设现在你发现需要在原来的结果中再加两个数,你可以向存储器中输入一些新的指令以替换原来所有的指令,但是你可能不愿意这么做。或许你更倾向于在原指令的地址后增加一些新的指令。第一步要做的就是把000Ch地址处的Halt指令替换为一个Load指令。但你仍然需要增加两条Add指令,一条Store指令,以及一条新的Halt指令。唯一的问题是,现在0010h地址已经保存了一些数据,因此需要把这些数据转移到较高的地址空间中,然后还需要修改那些指向这些地址空间的指令。
试想一下,把操作码和操作数存放在同一个RAM阵列并不是一个急于解决的问题。但可以肯定的是,这是一个迟早要解决的问题,不如现在就找一个解决办法吧。在当前的例子中,也许你更愿意从0020h地址开始存放新的指令,并从0030h处开始存放新的操作数据。
编码隐匿在计算机软硬件背后的语言
236
PDF 物理页索引 253
文件中的第 254 页 · 印刷页码: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 物理页索引 254
文件中的第 255 页 · 印刷页码: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 物理页索引 255
文件中的第 256 页 · 印刷页码: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 物理页索引 256
文件中的第 257 页 · 印刷页码:240
置位信号的输入。
显然,只有当指令代码为30h并且其后的16位地址被锁存时,我们才必须确保置位信号为1。
毋庸置疑,Jump指令的确很有用。但与之相比,一个在我们想要的情况下跳转的指令更加有用,这种指令称做条件跳转(Conditional Jump)。也许说明该命令重要性的最好方法是这样一个问题:怎样让自动加法器进行两个8位数的乘法运算?例如,我们如何利用自动加法器得到像 A7h与1Ch相乘这种简单运算的结果呢?
这其实很简单。两个8位数相乘得到一个16位数,为了方便起见,把该乘法运算中涉及的3个数均表示为16位数。第一步确定要把乘数和乘积保存到什么位置。
编码——隐匿在计算机软硬件背后的语言
240
PDF 物理页索引 257
文件中的第 258 页 · 印刷页码: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 物理页索引 258
文件中的第 259 页 · 印刷页码: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 物理页索引 259
文件中的第 260 页 · 印刷页码: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