编码:隐匿在计算机软硬件背后的语言(8)--自动操作(二)
上一节中我们实现了加法器的自动操作。本文介绍减法的实现以及多位加法器的实现。
首先要新增一个操作码,如图所示
减法的操作码假设是21h。前面我们介绍了减法器编码:隐匿在计算机软硬件背后的语言(4)--二进制减法器
减法是将减数取反与被减数相加,然后加一。与加法的区别仅在于最低有效位。这里在上文基础上增加一个反相器,改进后电路结构如下
假设现在要把56h和2Ah相加,然后再减去38h,可以按照下图中两个RAM阵列中的操作码和数据来完成。
先得到56h和2Ah的和80h,然后将38h取反得到C7h,反向器的控制端为C0,同时也是加法器的进位输入,此时已经置一,然后80h+C7h+1h=48h。(86+42-56=72)
现在实现了减法。我们再来看一下多位的加法如何实现。以上都是8位,现在以16位为例。我们当然可以改变RAM使它的数据位宽为16位,但我们还有代价更小的方法。例如我们要计算76ABh+232Ch。我们可以这么做,先将16位的低8位相加,然后将高8位相加,组合后就可以得到结果。就像下面这样,
然后好像有什么问题,那就是如果低8位的和如果产生进位,上面的方法就会得到一个错误的结果。我们需要做的就是将低8位相加后的进位保存起来,既然只有一位进位,就用1位锁存器实现。如果有进位,就是1,否则就是0。然后在进行高位加法的时候把进位也加进去。但是目前的操作码好像无法完成这样的操作。相加的时候并没有区分有没有进位,所以我们在增加一个操作码,进位加法(Add with Carry)。这样我们就可以完成16位的加法:低8位用正常的加法操作,高8位用进位加法。就像下面一样
现在有了16位的加法,那16位的减法呢?
首先我们还有增加一个指令,因为减法本质上也是加法,也会产生“进位”,在减法里面叫做“借位减法”(Subtract and Borrow)。在进行减法的时候,低8位运算完之后如果产生进位,同样保存在1位锁存器中,进行高8位减法时同样将前一次的进位输出作为此次运算的进位输入。到目前为止,我么已经拥有了7个操作码,
需要注意的是,在进行多位加法运算时,为了结果正确性,除了低8位之外,其他的加法运算都要用Add with Carry。新增了进位加法和借位减法之后,我们处理的数据不再局限于8位,16位、32位以及更多位都可以实现。比如要实现32位的两个数相加,7A892BCDh+65A872FFh,则我们仅需一条Add和三条Add with Carry指令就能完成
虽然我们实现了减法、借位减法、加法和进位加法,同时也实现了16位或者更多位的运算,但是还是有很多不足之处。
比如,上图中我们存储第一个数时,存储单元的地址并不连续,他们分别存储在0000h、0003h、0006h和0009h的地址中。而为了得到结果,我们也要检查0002h、0005h、0008h和000Bh几个地址中的数。
此外,我们无法重复使用完成的计算中的结果。比如两个8位数相加然后减去一个8位数,如果我们想在两个8位数的和中减去另一个数,就需要重新进行计算。
产生上述不足的原因在于我们的自动加法器的代码存储器和数据存储器是同步的、顺序的且都是从0000h开始寻址,代码存储器的每一条指令对应的操作数都在数据存储器同样的位置。那么结果被存储之后就无法在加载到累加器中。要解决这个问题,我们需要对我们的电路结构进行大程度的改进。改进后有着更高的灵活性。
首先,对于我们的7个操作码,我们规定每个除了Halt操作外,每个操作码占据3个字节。第一个字节就是操作码本身,其余的两个字节用来存放一个16位的地址,这个地址就是该操作要用到的操作数的地址。例如,Load指令的后两个字节的地址就是将要加载的操作数的地址;Add指令字节后的两个字节存放的地址就是要进行累加的另一个操作数的地址;Store操作码字节后的两个字节,存放的地址是运算结果要存放的地址。
现在举一个例子,要求4Ah和B5h的和,改进之前操作码和操作数RAM阵列如下
改进之后则如下图所示
10h代表加载,后面的地址为00h和00h也就是0000h,意思是将4Ah存放到0000h,加载的时候去0000h取操作数;20h代表普通累加,将0001h的操作数累加到累加器中;11h代表存储,将结果存储到0002h。这样,通过这种方式我们就会用到以前从未使用的地址,而且这些地址可以不连续,结果也可以存储到另外的地址。地址可以分散在64K存储空间的任意位置。如下图所示
可以看出,保存在4001h和4003h处的两个操作数相加后存放到4005h,保存在4000h和4002h处的两个操作数进位相加后保存在4004h,可见这两个运算是两个16位操作数相加的运算。然而地址的选择更加灵活了。
实现该设计的关键是把代码RAM阵列E数据分别输出到3个8位锁存器中,每个锁存器保存该指令的一个字节。第一个锁存器是指令本身,第二个锁存器存放操作数地址的高字节,第三个存放低字节。第二个和第三个构成了数据RAM阵列的16位地址,结构图如下所示
从存储器中取出指令的过程成为取指令,上面的加法器中,每个指令3个字节,因此取一个指令需要三个时钟周期;此外操作数处理也需要一个时钟周期,这样一个完整的指令周期需要4个时钟周期。机器相应指令做一系列操作的过程叫做执行指令。
既然我们的地址可以比较随意的使用,那么还有必要使用两个RAM阵列么,显然只用一个我们就可以完成需要的运算。我们需要在上面的基础上加一个2-1选择器,同时去掉一个RAM阵列,改进后如下图所示
RAM的输出依然连接到三个锁存器,对应于指令和操作数地址。这个16位的地址同时又作为2-1选择器的输入。当地址被锁存后,通过2-1选择器就可以把它作为RAM的地址输入,这样我们就能访问RAM的对应地址,进行操作。这样,我们做了不少改进,最终将指令和数据放在同一个RAM阵列当中。下图演示了如何把两个8位数相加,再减去一个8位数。
通常,指令从0000h开始存放,因为计数器复位后从该位置开始访问RAM。Halt指令放在000Ch地址。运算中的操作数可以选择除了已经占据的地址之外的任意的地址。
未完待续~