硬件06:操作码和汇编程序
操作码和汇编程序
将数字输入到内存
之前我们已经实现了一个可以迭代求和的加法器,这个加法器有一个很大的缺陷,那就是如果要把100个二进制数加起来,必须坐在加法器前,将所有的数一个一个输入,然后累加,输错一个就计算失败。如果我们能把100个二进制数输入到RAM阵列中而不是直接加法,后面我们对数据的修改就会容易很多,我们可以尝试组成下列电路:
要使用这个机器,必须首先闭合清零开关,清除锁存器的内容并把16位计数器置为0000h(h代表是十六进制数),然后闭合RAM控制面板的控制端开关,选择地址并输入一组一组的8位数,然后闭合RAM控制面板的控制端开关(断开控制面板对RAM的控制),同时断开清零开关,观察灯泡的运行结果。
当清零开关断开后,整个电路开始工作,此时16位计数器输出0000h,RAM此时受到外部电路的影响,将对应地址的数字传递给加法器,时钟信号切换后发生了两件事:
1、计算结果保存在锁存器中,灯泡根据锁存器的值发亮,同时锁存器把计算结果传递给加法器
2、16位计数器增1,指向下一个地址单元
时钟信号不断切换,加和的操作不断继续,最后完成对所有数的加和。但是这个电路有一些缺陷,我们无法停止这种相加的操作,而且无法处理溢出和负数的问题。
我们可以将计算结果写回到RAM中,改进的电路如下:
这个图省略了一部分清零和时钟输入。这个加法器可以将计算结果保存在RAM中,最后的计算结果可以通过RAM控制面板上的灯泡显示出来。
引入操作码
如果我们想要制作一个自动加法器,让其不仅可以做加法运算,还能自主的确定要累加多少个数字,而且还要把计算结果存入RAM中,假如我们要对三个数进行求和,然后对另外两个数进行求和,然后再对另外三个数进行求和,一开始我们把这些数保存在RAM中:
方格中代表存储器的内容,这些标记的单元就是我们要存放计算结果的位置。要达成这个功能,我们要完成4个步骤:
1、将存储器中的字节加载到累加器中
2、将存储器中的字节加到累加器中
3、将累加器的计算结果取出放入存储器中
4、让自动加法器停止
如果简单的给RAM中输入一组数字就能期望系统完成整个操作是不可能的,我们必须借助一些数字代码来标识加法器要做的每一项工作:加载、相加、保存和停止。
可以设置一个独立的RAM,其中存放一些数字代码,而不是需要求和的数,两个RAM组成的结构如下:
通过控制面板可以将数字代码写入代码RAM中,我们需要四个代码来完成这个功能:
代码RAM中的存储情况应该是这样:
代码RAM中存放的每一个代码都对应数据RAM中的数据,这种数字代码被称为指令码或操作码。
最终组成的电路系统如下:
2-1选择器的作用是,有时需要让加法器的值输出到锁存器(保存计算结果),有时需要将数据RAM的值输出到锁存器(load指令)。
操作码之所以能让电路呈现不同的动作,是通过控制锁存器的时钟输入和清零输入、数据RAM阵列的写输入、2-1选择器的选择输入来完成的,这些都是基于代码RAM阵列输出的指令,通过逻辑门的组合来实现的。
支持减法
如何支持减法呢,只需要引入一个新的操作码:减法,然后在数据传入加法器之前取反即可,改进后的电路结构如下:
支持多位加减法
有时我们需要在计算过程中保存1位数字,如果对于一个只能计算8位加法的加法器,要计算两个16位数字的和,此时我们可以把16位数字拆成高8位和低8位两组数字的和,低8位可能会产生进位,此时需要保存这个进位值,我们可以在电路中引入一个进位锁存器来专门保存这个值,新增一个操作码进位加法(Add with Carry)来控制它。同理,为了支持16位数字的减法,我们需要一个新的操作码:借位减法(Subtract and Borrow)。此时我们的加法器虽然有位数限制,却可以通过扩展操作码的方式将可以计算的位数拓展到很大。
将地址加入指令中
到现在为止,加法器不允许在随后的计算中重复使用前面的计算结果,这意味着如果两个计算序列很像,第二个计算序列也必须重新开始,不能用之前的结果。产生上述情况的原因在于我们构造的加法器中的代码RAM和数据RAM是完全同步顺序的,这样的一一对应关系造成了计算非常不灵活。
要改变这种局面,首先要对操作码进行一种变革,到目前为止的操作码很简单,只需要1个字节就可以表示完全,现在设计一套新的操作码,除了halt指令外每个指令占用3个字节,第一个字节为代码本身,后两个字节保存地址用来指明数据RAM的一个存储单元。
假如我们要对两个数进行求和,修改前的两个RAM:
修改后的代码RAM及每个指令的解释:
这样就极大的扩展了操作码的功能。
在之前的计算中,要操作的数据和数据结果都顺序的存在一起,扩展了操作码的功能后,我们可以把数据都分散存储在数据RAM的任意位置,这样就可以把计算过程中的结果指定存在某个位置,在接下来的计算中很容易引用之前的结果。
在设计电路时,我们需要把代码RAM阵列的数据输出到3个8位锁存器中,每个锁存器保存3字节指令的1个字节,第二和第三个锁存器的输出构成了数据RAM的16位地址:
至此我们的计算器完成了很多功能,但是操作码的占用空间从原来的1字节到3字节。16位计数器原来取1字节的数据需要一个时钟周期,现在取3字节需要3个时钟周期,一个完整的指令周期需要4个时钟周期,它的运算速度只有最简单加法器的1/4,这说明:老天是公平的,改进了机器的某个方面,则其他方面就会有损失。
我们也可以把两个RAM合并成为一个:
地址来源除了16位计数器产生以外,还可能来自于8位锁存器指向的地址数。
支持乘法
重要的操作指令:跳转(Jump),它可以将两段程序的执行连接起来,改变顺序执行的寻址方式,这种指令也被称为分支(branch)指令或Goto指令。
如果我们要实现乘法,实际上就是做多次累加,如果手动输入程序,这个程序会很长,借助跳转指令我们可以重复执行累加的操作,但是这个过程不会停止。我们需要一种Jump指令,它只让这个过程重复执行所需要的次数。这个功能可以用一个锁存器来实现:
这个锁存器只有当8位加法器的输出全为0时,才存储一个值1,它被称为零锁存器(Zero latch)。零锁存器中的值在程序中相当于一个标志位,我们引入下列四条指令:
这四条指令检查锁存器的值,根据其中标记位的值来决定是否进行跳转,这里还用到了进位锁存器。要完成乘法功能,这里用的是非零转移指令,只有当加法器结果为0时才会停止跳转。
当执行乘法操作时,RAM中部分指令:
前三个指令分别是load、add、store,第四个指令就是非零转移指令。进行加法的次数专门存在一个地址中,每次执行累加之后,都将次数加上001Eh地址中的值,加上FFh相当于减1,然后检查该值的结果是否为0,如果不为0继续循环计算,指到该值为0,停止跳转。
汇编程序
至此我们解决了加法、减法和乘法,而且还可以支持循环操作,我们可以用这些原理建立除法操作等更复杂的操作,这就已经可以称之为一个小型计算机了,它有处理器、存储器(RAM阵列)、输入设备(控制面板上的开关)、输出设备(控制面板上的灯泡)。处理器包括若干组件,8位反相器和8位加法器共同构成了算术逻辑单元(Arithmetic Logic Unit),即ALU,在更复杂的计算机中ALU不仅可以做算术运算,还能处理逻辑运算;16位的计数器被称为程序计数器(PC,Program Counter)。
至今为止我们使用了很多的操作码,下表是这些操作码和助记符:
助记符提升了编写程序的可读性,比如“将1003h地址处的字节加载到累加器”可以用下列语句替代:
LOD A, [1003h]
紧跟助记符右侧的A代表累加器名,A是目标操作数,[1003h]代表源操作数,方括号代表取地址中的值。
类似的,有:
把001Eh地址的字节加到累加器:
ADD A, [001Eh]
把累加器中的内容保存到1003h地址:
STO [1003h], A
如果零标志位不是1则跳转到0000h地址处:
JNZ 0000h
指令存在0000h地址空间:
0000h: LOD A, [1005h]
数据存在1000h地址空间:
1000h: 00h, A7h
上述的乘法程序也可以用这样的语言表示,在表示过程中最好不要把地址固定住,因为它们是可变的,用定义好的变量代表这些地址值,可以让程序变得更灵活、易读性更好。
这种语言被称为汇编语言,每一句都对应这机器语言中的某些特定字节。有了汇编语言,更复杂的逻辑可以用程序来完成了。
我们制造的计算机还有一个很严重的缺陷,用我们这种方式构造一个64KB的RAM阵列需要几百万个继电器,这几乎是不可能的,但是已经有人制造出一种全新的设备,保证性能的同时将机器大大缩小。