夫道成于学而藏于书,学进于振而废于穷。
文章目录
- 完善加法器
- 加入代码的加法器
- 扩大加数范围
- 自由调用地址的加法器
- 合并代码RAM和数据RAM
- Jump指令
- 硬件实现
- 条件Jump指令
- 零转移的硬件实现
- 条件Jump指令的例子
- 总结
完善加法器
我们在第14章介绍了一个可以进行连加的加法器,接下来我们对它进行扩展:
我们将数据用之前讲到的RAM进行存储,以达到回看这些数据的效果。
振荡器和16位计数器连接起来既可以计时,又可以计数,这也是上一篇文章讲过的。在这里,我们用它来计数,通过顺序存储所需要的数据,我们做到将RAM中的数据依次加入加法器中进行相加。
十六位计数器从0000h开始,每隔一段时间输出+1,一直到FFFFh。那么只要从0000h开始存储数据就可以了。
通过同步时钟(Clk),可以保证完成一个完整的加法过程;当摁下清零开关(Clr)时,锁存器和计数器同时清零,重新从第一个数开始计算。
我们也可以把计算结果直接写回到RAM中,这样虽然将原来的加数覆盖掉了,但胜在省掉了灯泡。
接下来,我们将进入另一个阶段的完善。
加入代码的加法器
上面的加法器有一个明显的缺点,就是它的加法过程一旦开始,便不能再结束,计数器到FFFFh后会回到0000h重新计数。因此我们需要进行改进,即引入代码,让它能够自动执行额外的操作。
我们用例子说明这些代码是怎样加到装置中去且在装置中发挥作用的。假设我们先对三个数相加,再对两个数相加,再对三个数相加,那么只用上图装置可以表示为:
最左边是地址,表格中是存储的具体数值,最右边是说明。我们用这种形式来表示加入代码后加法器的操作。
首先思考一下我们暂时需要什么样的操作。下表是我们现在需要的操作:
- Load:将RAM中的数加载到加法器中作为第一个加数。
- Store:将锁存器中的数存储到RAM中。
- Add:将RAM中的数加载到加法器中与之前的数相加。
- Halt:停止所有操作。
思考一下Load操作和Add操作的区别。Load操作不需要将锁存器中的结果存到加法器中,但Add操作需要。
这样的话,我们就能表示在上面的例子中,我们需要进行什么样的操作:
要实现这些操作,我们需要再加上一些硬件。第一,我们需要加入一个存储代码的RAM,它和存储数据的RAM一起同时被计数器控制,称为代码RAM阵列;第二,执行Load指令时,锁存器的输入直接从数据RAM阵列中得到,而执行Add指令时,则从加法器中得到,这样的话就需要加入一个2-1选择器。具体如下所示:
我们可以通过输出不同的代码来控制电路中的控制信号,这可以通过加入一些逻辑门实现,这里省略。
接下来,再次完善代码,我们加入减法:
这里减法的代码是21h,与加法代码只在最后一位不同。检测到这个不同后,将从数据RAM阵列中取出的数进行取反。如下图所示,在该图中,减法与加法不同的位称为C0:
其他操作的问题暂时解决,接下来我们将扩展加数的范围,将它们从8位扩展到16位。
扩大加数范围
我们的目标是不改动加法器的位数,使装置能进行16位加数的运算。很容易想到,我们将16位的数分成两半,先将低字节相加,再将高字节相加即可:
结果的低字节存储在0002h,高字节存储在0005h。
接下来,我们需要解决进位的问题。这个问题实际上在前面已经解决,只需要存储一个信号作为进位就可以了,用一位存储器来存储即可。
加入进位信号存储器后,原来的Add代码就不能完全进行自动操作了,我们需要再引入一个专门用于进位情况的代码,就称作Add with Carry(进位加法)。在执行Add操作时,我们将最高位的进位输出存储到一位存储器中;在进行16位的高字节数相加时,我们使用进位加法代码,它将进位存储器中的信号也当作加数一起在加法器内相加。进行16位的减法运算时,这个存储器也可以存储对高字节的借位,可以再引入一个代码Subtract with Borrow(借位减法)进行操作。
加入Add with Carry和Subtract with Borrow后,我们的代码如下所示:
注意,当进行16位加法高字节运算时,我们一般都使用进位加法进行运算。减法同理。
自由调用地址的加法器
我们通过扩展指令的方式,将要调用的地址单独编码在指令RAM而非直接用计数器指明地址,这样就可以实现对地址的自由调用。具体实现方案如下所示:
16位计数器连接代码RAM阵列,在代码RAM中依次取出代码。将每个代码扩展为三个字节,第一个字节指明执行什么操作,余下两个字节指明调用数据RAM的16位地址。
由于计数器依次前进1,因此一个指令需要三个时钟周期,而一个完整的指令周期需要四个时钟周期,因此需要更为复杂的控制信号,这里省略。不论如何,这个加法器的速度只有原来的1/4,但是它拥有更完善的功能。
合并代码RAM和数据RAM
事实上,我们可以将这两个RAM合为一个。例如,如果我们有一个指令为10h 32h 52h。不难解释这个指令:加载3252h处的数据到加法器中。正常情况下,我们会用后两个字节在数据RAM的3252h处找到数据。但如果我们将3252h处的数据也存入代码RAM的3252h处,我们就可以省略掉数据RAM而用代码RAM来同时存储代码和数据。实现如下:
我们先选择计数器作为2-1选择器的输出,从RAM中取出代码,代码的后两个字节被锁存器保存后折回作为2-1选择器的第二个输出(32h,52h),这样就能从RAM中取出3252h处的数据,这个数据最后从右上角输出到加法器中。我们用FFh(Halt指令)来分割存储代码的区域和存储数据的区域。
Jump指令
还存在一个问题是,如果我们还想要重新写入更多的指令,我们就必须用其他指令替换掉Halt指令,但这样的话,我们就可能把原本是数据的区域当成存储代码的区域。为了解决这个问题,我们引入一个新的Jump指令,这个指令可以指定下一次执行的指令地址而不一定必须顺序执行。有了这个指令,就不需要用Halt指令来分割区域了。将这个指令加入指令表;
Jump指令后面跟一个16位的数,表示要跳转的地址。
硬件实现
不难想到,Jump指令通过控制16位计数器来实现其功能。进一步讲,它通过控制Pre和Clr信号来控制计数器的输出。Pre信号强制Q=1,Clr信号强制Q=0。我们新增的电路如下:
- 置位信号为0,复位信号为0时,三个门的输出都为0,A无法控制Pre和Clr信号。
- 置位信号为0,复位信号为1时,Pre信号为0,Clr信号为1,A无法控制Pre和Clr信号
- 置位信号为1,复位信号为0时,设A信号为a,则Pre信号输入为a,Clr信号输入为¬a,计数器的输出取决于A输入,达到Jump指令的目的
- 置位信号为1,复位信号为1的情况不合法
在实际电路中,由于只增加了A信号和置位输入,且A信号输入来源于RAM阵列中Jump指令的操作数,置位信号遇到30h(Jump指令操作数)时就设为1,因此对电路改动不是很大:
条件Jump指令
在有些情况下,我们需要达到一些条件时再进行Jump指令,这需要引入条件Jump指令。下面是引入条件Jump指令后的指令表:
我们可以看到,在指令表中,我们一共有四个标准:零,非零,进位,非进位。其中进位很好理解,当进位锁存器输出值满足要求时进行Jump指令。对于零转移(非零转移)来说,当加法器最后一次相加得到的值为0(不为0)的时候,我们执行Jump指令。
零转移的硬件实现
为了实现零转移和非零转移,我们需要新增一个零锁存器:
当加减相关的四条指令被执行时,锁存器进行锁存最后的结果并输出到零标志位,根据零标志位,决定是否执行Jump指令。
条件Jump指令的例子
下面是一个用到条件Jump指令的例子:执行相乘A7h和1Ch运算。
很容易想到将A7h相加28(1Ch)遍即可,循环相加利用Jump指令很好实现,如果想要控制相加遍数,那么利用非零转移指令即可。用某种方式存储1Ch,每一次相加后将1Ch减1,未减到0时,Jump指令使循环相加过程继续;减到0时,Jump指令不执行,直接结束。
下面是实际实现的指令序列:
这一部分存储了乘数、被乘数和结果要保存的地方。
这一部分执行相加运算,将上一次相加的结果再次与被乘数相加。
这一部分是重点,包含了五条指令。在前三条指令中,我们将乘数与FFh相加。注意,这里的FFh被看作一个数,乘数和FFh相加就相当于将乘数减1.第四条指令是条件Jump指令,它对当前加法器的输出,即乘数的当前值进行检测,如果乘数不为0,那么Jump指令执行,回到加法部分继续进行相加运算;如果乘数为0,说明加法过程已经进行28遍,Jump指令不再调用,直接执行FFh,即Halt指令。
总结
到这里,我们已经完成了一个计算机应有的功能,通过这些简单的指令,我们可以做到各种运算:乘除法,平方,开根……计算机也就此诞生。
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!