美文网首页
编码:自动加法器(一)

编码:自动加法器(一)

作者: 汪小鱼 | 来源:发表于2021-10-26 09:22 被阅读0次

    1 前言

      本文是基于《编码》、《穿越计算机的迷雾》两部著作进行读后整理的记录性博客。对书中较为重要的内容进行归纳整理进行二次创作,略去了繁琐的讲述细节,力求简明扼要。


    编码:一种由若干符号和规则组成的系统,用来向计算机表述指令。

    2 正文

    2.1 累加器

      在前面的文章中我们介绍了加法器,下面是其结构图,这个版本的加法器包括一个 8 位的锁存器,用于对 8 个开关的输入数据进行迭代求和。

      上图中 8 位锁存器利用触发器来保存 8 位数据。使用这个设备时,首先需要按下清零开关使锁存器中的内容全部都变为 0,然后通过开关输入第一个数。加法器只是简单地将这个数字和锁存器输出的 0 进行求和,因此相加的结果与原先输入的数字是一样的。按下相加开关可以把这个数保存在锁存器中,最后会点亮某些灯泡以显示它。现在通过开关输入第二个数,加法器把它与已经存放在锁存器中的第一个数相加。再次按下相加开关,就可以把相加的结果存入锁存器中,并通过灯泡显示这个结果。通过这种方式,可以把一串数相加并显示运行结果。

      很显然,上面的加法器存在着一个很大的缺陷:假如要把 100 个二进制数加起来,你必须端坐于加法器前,并且耐心地输入所有的数并累加起来。但是当你终于完成时,却发现其中有两个数输错了,而你只能重复一遍所有的工作。

      基于上述介绍本文将设计一种新的电路设备,用来实现累加,这种用来累加多个数的锁存器称做累加器(accumulator)。

      在前面的内容中我们利用触发器构造了一个 64KB 的 RAM 阵列,这里我们添加一个控制面板连接到电路帮助我们操作。如下图所示,即为 64KB RAM 阵列结构图:

      结合我们前面文章中介绍的 RAM 阵列,如果把这 100 个需要进行累加的二进制数输入到 RAM 阵列中而不是直接输入到加法器中,一旦需要修改一些数据,我们的工作将会变得容易得多。

      因此,现在我们要做的是如何把 RAM 阵列和累加器连接起来。这里我们用 RAM 阵列的输出信号可以替代加法器的开关,用一个 16 位的计数器控制 RAM 阵列的地址信号。在这个电路中,RAM 阵列的数据输入信号和写操作端信号可以省去。

      要使用它,首先要闭合清零开关,这样做的目的是,清除锁存器中的内容并把 16 位计数器的输出置为 0000h,然后闭合 RAM 控制面板的控制端开关。现在你可以从地址 0000h 开始输入一组你想要相加的 8 位数。如果有 100 个数,那么它们将被存放在 0000h~0063h 的地址空间中。然后闭合 RAM 控制面板的控制端开关(这样控制面板就不再控制 RAM 阵列了),同时断开清零开关。

    数值后面加上字母 h 代表这个数是一个十六进制数

      我们来分析一下它的工作过程:当清零开关第一次断开时,RAM 阵列的地址输入是 0000h。RAM 阵列的该地址中存放的 8 位数值是加法器的输入数据。加法器的另一个输入数据为 00h,因为此时锁存器也已经清零了振荡器提供的时钟信号——一个可以在 0,1 之间快速切换的信号。清零开关断开后,当时钟信号由 0 跳变为 1 时,将有两件事同时发生:锁存器保存加法器的计算结果,同时 16 位计数器增 1,指向 RAM 阵列的下一个地址单元。清零开关断开之后,时钟信号第一次从 0 跳变为 1 时,锁存器就将第一个数值保存下来,同时计数器增加为 0001h;当时钟发生第二次跳变时,锁存器保存之前两个数的求和结果,同时计数器增加为 0002h;按这种方式往复操作。

      这个电路存在一些明显的缺陷:我们无法停止计算,而且在 RAM 阵列中,剩余部分存储的数都是 00h,当计数器达到 FFFFh时,它会重新回滚到 0000h,这时加法器会再一次把所有的数累加到已经计算出来的结果中去。当然还有的问题是,它只能做加法运算,并且只能做 8 位数的加法。

      概括来说,我们需要一种 “万能机”,它可以很方便地对;两个数,10 个数甚至 100 个数求和,并且所有计算结果都可以很方便地使用。

      我们可以去掉与锁存器连接的灯泡,取而代之的是把锁存器的输出端连接到 RAM 阵列的数据输入端,这样就可以把计算结果写回到 RAM 阵列中去,如下图所示。

      上图中略去了自动加法器的其他部分,其中包括振荡器和清零开关,这是因为我们不再需要特别标注计数器和锁存器的清零及时钟输入。此外,我们利用了 RAM 阵列的数据输入,同时也需要一个方法去控制 RAM 的写入信号。

    把关注点集中到我们需要解决的问题,我们现在想要配置一个自动加法机,使它不仅仅可以对一组数字做累加运算,还希望它能够自主确定要累加多少个数字,而且记住在 RAM 中存放了多少个计算结果。但不难发现,针对我们想要实现的功能,结合上述电路,我们需要对各种操作类似编写程序一样进行明确指明,什么时候相加,什么时候停止计算,结果作何处理。仅凭加法机自身很难达到 “自动”,其实生活中我们接触的电脑也是如此,根据程序指令执行相应的操作。

      例如,我们需要先对三个数进行求和,然后再对两个数进行求和,最后再对三个数进行求和。想象一下,我们可以把这些数保存在 RAM 阵列中以 0000h 开始的一组空间中,这些数存储在 RAM 阵列中的具体形式如下图所示。

    在本文以及后续内容中,将用这样的形式表示一小段存储器,方格表示的是存储器的内容,地址标记在方格的左边,

      我们希望自动加法器能够做四个事:进行加法操作,首先它要把一个字节从存储器中传送到累加器中,这个操作称为加载(Load)。第二个操作把存储器中的一个字节加(Add)到累加器的内容中去。第三个操作把累加器中的计算结果取出并存放到存储器中。另外我们需要用一个方法令自动加法器停(Halt)下来。

      我们借助具体的例子详细介绍这一过程,以上文提到的自动加法器所做的运算为例来说明。

    (1)把 0000h 地址处的内容加载到累加器。
    (2)把 0001h 地址处的内容加到累加器中。
    (3)把 0002h 地址处的内容加到累加器中。
    (4)把累加器中的内容存储到 0003h 地址处。
    (5)把 0004h 地址处的内容加载到累加器。
    (6)把 0005h 地址处的内容加到累加器。
    (7)把累加器中的内容存储到 0006h 地址处。
    (8)把 0007h 地址处的内容加载到累加器。
    (9)把 0008h 地址处的内容加到累加器。
    (10)把 0009h 地址处的内容加到累加器。
    (11)把累加器中的内容存储到 000Ah 地址处。
    (12)令自动加法器停止工作。

      我们还希望自动加法器能方便地停下来,以便于查看 RAM 阵列中存放的值。

      该如何来完成这些工作呢?能不能仅仅简单地向 RAM 阵列中输入一组数,然后期待自动加法器正确地完成所有工作呢?答案是否定的。对于 RAM 阵列中的每一个数,我们还需要用一些数字代码来标识加法器要做的每一项工作:加载、相加、保存和终止。

      也许存放这些代码的最简单的方法是把它们存放在一个独立的 RAM 阵列中。这个 RAM 应该和第一个 RAM 同时被访问。但是这个 RAM 中存放的是不需要求和的数,而是一些数字代码,用来标记自动加法器对第一个 RAM 中指定地址要做的一种操作。这两个 RAM 可以分别被标记为 “数据”(第一个 RAM 阵列)和 “代码”(第二个 RAM 阵列)。其结构如下图所示。

      我们已经清楚地认识到新的自动加法器能够把数据求和的结果写入到第一个 RAM 阵列(标记为 “数据”),而新的 RAM 阵列(标记为 “代码”)则只能通过控制面板写入。

      我们需要四个代码来标记新的自动加法器需要做的四个操作,这些代码可以任意指定。如下所示的是一种方案。

      如下图所示,比较一下该 RAM 阵列与存放累加数据的 RAM 阵列中的内容,不难发现,代码 RAM 阵列中存放的每一个代码都对应着数据 RAM 中要被加载或者加到累加器中的数,或者对应需要存回到数据 RAM 中的某个数。以这种方式使用的数字代码常常被称为指令码(instruction code)或操作码(operation code,opcode)。它们指示电路要执行的某种操作。

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

    16 位的计数器为两个 RAM 阵列提供地址输入。通常,数据 RAM 阵列的输出传入到 8 位加法器执行加操作。8 位锁存器的输入可以是数据 RAM 阵列的输出(当执行 Load 指令时),也可以是加法器的输出(当执行 Add 指令时),这种情况下就需要 2-1 选择器。通常,锁存器电路的输出又流回到加法器中,但是当执行 Save 指令时,它就成为了数据 RAM 阵列的输入数据。

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

    在前面我们将电路修改为两个 RAM 阵列的形式,其中一个 RAM 阵列对应我们想要执行的操作。观察上述电路不难发现我们无法得知这些指令操作是怎么控制电路的。但我们细想其实可以通过逻辑电路将操作对应的 RAM 阵列的输出与电路中的各个控制位进行连接,从而实现不同输出控制电路实现相应的操作。这里暂且忽略这些繁琐的控制信号,当作已经连接完成,代码对应相关操作已经实现。

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

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

    加上反相器实现减法操作

      假设现在要把 56h 和 2Ah 相加,然后再从中减去 38h,可以按照下图中两个 RAM 阵列中的代码(操作码)和数据(操作数)完成该运算。

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

    还有一个一直没有找到合适的解决办法的问题:加法器及连接到它的所有设备的宽度只有 8 位。对于较多位数的运算,宽度无法满足需求。当然我们可以选择将两个 8 位加法器连接在一起,构成一个 16 位的设备。但这里我们介绍另一种代价更小的解决方法。

      例如我们想把下列两个 16 位的数相加,比如:

      这种 16 位的加法可以先单独处理最右边的字节(通常称之为低字节):

      然后再计算最左边的字节,即高字节的和:

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

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

      当然并非所有的情况都是如此处理,对于分开计算后产生进位的加法该怎么做呢?例如 76ABh + 236Ch,对两个数的低字节求和时将会产生一个进位。产生的进位必须与两个数的高字节的和再相加。

      因此为了保证计算的正确性,我们必须要处理分开计算后的进位情况。我们需要做的仅仅是在第一步运算时保存低字节数运算的进位输出,并把它作为下一步高字节数运算的进位输入。

      如何保存 1 位呢?我们可以采用 1 位锁存器,该锁存器应该被称为进位锁存器。为了使用进位锁存器,还需要另一个操作码,我们称之为 “进位加法”。

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

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

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

      增加了两个新的操作码之后,我们已经极大地扩展了加法器的功能,它不再局限于 8 位数的加法运算。通过执行进位加法操作,可以对 16 位数、24 位数、32位数,甚至更多位的数进行加法运算。假如要进行两个 32 位数 7A892BCDh 和 65A872FFh 的加法运算,我们仅需要 1 条 Add 指令和 3 条 Add with Carry 指令,如下图所示。

      当然,把这些数依次输入存储器并不是最好的做法。因为你不但要使用开关来输入这些数,而且保存这些数的存储单元的地址也不是连续的。除此之外,当前设计的自动加法器不允许在随后的计算中重复使用前面的计算结果。

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


    3 小结

      编码:自动加法器(一)篇以前文介绍的 RAM 阵列为基础,结合 RAM 阵列设计出能够进行自动计算的加法器,但目前的加法器还存在很多问题,需要后续再进行改进。为了精简内容删减了部分较为详细的书写,仅作为整理总结。

    相关文章

      网友评论

          本文标题:编码:自动加法器(一)

          本文链接:https://www.haomeiwen.com/subject/ibtqaltx.html