硬件08:CPU
CPU
我们要讨论的微处理器出现于1974年,这一年,英特尔公司在4月推出了8080处理器,它是一个8位的微处理器,包括6000个晶体管,运行时的时钟频率为2MHz,寻址空间为64KB。
管脚
8080是40个管脚的集成电路,它最常见的IC封装如下:
下面给出了8080的40个管脚的功能说明图:
这些管脚的功能分为以下几类:
1、电源电压:管脚20(5V)、管脚11(-5V)、管脚28(12V)、管脚2(接地)。
2、时钟输入:管脚22和15,这些信号可以方便的由英特尔生产的8224时钟信号发生器产生。
3、16个用于寻址的输出信号:它们是A0-A15。
4、8个既可以做输入又可以做输出的信号:它们是D0-D7。
5、10个控制信号,如RESET(复位)、WR(对RAM阵列的写输入)等。
STA/LDA/MOV/MVI指令
在8080中,指令的长度可以是1字节、2字节或者3字节,一共有244个指令。在之前的设计中,我们使用了非常重要的两条指令,分别是load(将指定地址中的字节加载到累加器A)和store(累加器A中的内容被保存到指定的地址),8080中也有类似的指令,每个操作后也跟着一个16位的地址,它们是STA(Store Accumulator,存储累加器的值到地址)、LDA(Load Accumulator,加载到累加器):
8080芯片的微处理器内部设置了6个寄存器(rigister),每个寄存器内部可以存放一个8位的数,它和累加器一样,本质都是锁存器。它用来存放程序运行过程中的数据,数据存在寄存器要远比存在内存中访问更方便。在8080中用B,C,D,E,H,L来表示6个寄存器,HL合起来可以构成一个16位的寄存器对。
MOV是一个非常重要的指令,它用来把一个寄存器中的内容转移到另一个寄存器,这些指令如下,再次注意两个参数中左边是目标操作数,右边是源操作数:
MOV的操作码由8位组成:01dddsss,ddd是目标操作数的代码,sss是源操作数的代码,它们表示的意义如下:
寄存器B和C、D和E也可以组成寄存器对,读取或保存这些地址时,可以用下面的指令:
MVI指令可以将某个单字节数据写入寄存器中:
设置寄存器的值的方式有几种,如用LDA指令直接操作地址,也可以用MOV指令将一个寄存器写入另一个寄存器,前者称为直接寻址,后者称为间接寻址,还有第三种方式,被称为立即数寻址,也就是使用MVI直接设置值。
算术运算指令
之前在设计时我们用到了四种基本运算指令,分别是加法ADD、进位加法ADC、减法SUB、借位减法SBB,8080中也有这些指令:
可以发现这些指令的操作数中总有A(累加器),相当于将计算结果运算后再存入累加器中。用ADC指令和SBB指令可以完成16位甚至更多位数的加法和减法。以加法为例,假设现在寄存器对BC和DE各自保存了一个16位的数,我们要把这两个数相加,并且把结果保存在寄存器对BC中,可以编程如下:
MOV A, C
ADD A, E
MOV C, A 完成低字节操作加法,将结果存入C中
MOV A, B
ADC A, D
MOV B, A 完成高字节操作加法,将结果存入B中
其中ADC的执行自动计入了低字节的进位。
标志位
8080中共有5个标志位:CF(进位标志位)、ZF(零标志位)、符号标志位(SF)、奇偶标志位(PF)、辅助进位标志位(AF),在8080中有一个专门的8位寄存器来存放所有标志位,该寄存器被称为程序状态字(Program Status Word, PSW)。
算术运算指令的执行会影响这些标志位的值:
1、算术运算结果的最高位是1,则符号标志位SF被置位1,表示该结果是负数。
2、运算结果为0,零标志位ZF被置为0。
3、运算结果中1的位数是偶数,则奇偶标志位PF置为1,否则置为0。
4、当ADD和ADC运算产生进位或者SUB和SBB运算不发生借位时,进位标志位CF都置为1。
5、辅助进位标志位AF只有在运算结果的低4位向高4位有进位时才置为1,它只用于DAA(Decimal Adjust Accumulator,十进制调整累加器)中。
下列两条指令会直接影响进位标志位CF:
逻辑运算指令
8080的算术逻辑单元ALU除了可以进行算术运算,还能进行逻辑运算:
逻辑运算包括AND与、OR或、XOR异或这些按位运算治指令,这些指令把运算结果放到累加器中,还包括CMP这种比较指令,CMP指令不会改变累加器的值,它改变的是标志位,如:
CMP A, B
如果两个值相等,零标记位被置为1;如果A中的值小于B,则进位标志位置为1.
对立即数的算术逻辑指令
包括以下8种,分别对应上面4种算术指令和4种逻辑指令:
如以下代码:
MVI B, 25h
CMP A, B
可以被替换成
CPI A, 25h
取反CMA和十进制运算DAA
取反CMA(Complement Accumulator)的操作码对应为27,它对累加器中的数按位取反,此外我们还可以执行以下指令按位取反(与全1异或):
XRI A, FFh
DAA(Decimal Adjust Accumulator)是十进制调整累加器,在8080中专门设计了一个完整的小部件用于执行该指令,它提供了一种用二进制表示十进制数的方法,称为BCD码(binary-coded decimal),每4位为一段,每段数字范围是0000-1001.对应十进制的0-9,一个字节可以表示两个十进制数。
两个数字:27h和94h,用DAA指令相加的结果就是累加器中存放的是21h,而不是BBh,且进位标志位置1。
自增和自减指令
8080中提供了专门的指令用来对寄存器或存储器中的数进行加1或减1操作:
INR和DCR能影响除了CF之外的所有标志位。
循环移位指令
8080中包括4个循环移位指令,这些指令可以把累加器中的内容向左或向右移动1位:
RLC可以让10100111变为01001111,它的最左位会移动到最右边,同时CF置位1,RRC也一样,会将移出去的位补充在后面。
RAL和RAR的工作方式有所不同,RAL也可以产生移位,但是它会把CF中的值补充到最后一位,然后把累加器中原最高位移动到CF中。
移位操作可以让乘2和除2变得简单。
堆栈指令和其他寄存器指令
在编写程序时可能出现这样的情况,程序中需要做一个小的计算,该计算要用到寄存器A、B、C,于是可以先将这三个寄存器的值保存起来,然后计算完成后再把数据恢复。
保存寄存器A、B、C的值:
PUSH A
PUSH B
PUSH C
取回原来的数据,注意这里的顺序必须和压入是相反的,否则会引起错误:
POP C
POP B
POP A
程序中可以多次使用堆栈,如我们可以把B中的数据压入栈中两次,然后再依次取出。
堆栈本质上是一段普通的RAM空间,8080微处理器设置了一个16位寄存器对这段存储空间寻址,这个特殊的寄存器称为堆栈指针(SP,Stack Pointer),所以PUSH和POP指令是把对应的数据保存在堆栈或者从堆栈中取出,这些操作码如下:
PSW是用于保存标志位的寄存器,也可以将其中的值保存在堆栈中。
PUSH BC指令执行时会将堆栈指针减1,然后将B中的数据存入堆栈指针指向的地址,然后再次将堆栈指针减1,将C中的数据存入堆栈指针指向的地址。POP指令会将上述步骤反过来执行一遍,它会增加堆栈指针。操作堆栈时,可能出现存入的数据过多,导致覆盖掉原来的数据,这就是堆栈上溢,还可能出现过多使用POP导致无数据可取,这就是堆栈下溢。
LXI指令可以为寄存器赋值,包括堆栈指针SP:
还可以对寄存器或堆栈指针中的值加1或减1:
还可以通过寄存器给它们赋值:
可以将地址中的内容保存在寄存器中:
还可以把寄存器中的值加载到程序计数器(相当于Jump指令)和堆栈指针:
还有一些交换指令,它可以将两个操作数中的值进行交换:
跳转指令
Call指令会完成程序的跳转,在程序计数器中加载一个新的地址,而且它还会将原来的地址保存到堆栈。Return用来从堆栈中将原地址取出,并把他们加载到PC中。
有了这两个指令就能有效的记录“从何处跳转”,在它们的帮助下,程序员可以在程序中频繁使用子程序。
在执行下列语句后:
CALL Multiply
程序会把当前PC中的值保存在堆栈,然后跳转到标志位Multiply的指令,这是一个自定义的标志,该值在即将调用的子程序的开头,在子程序的末尾会有一个RET指令表示返回,这样就完成了对子程序的调用。在子程序中改变了其他寄存器的值,可以通过操作这些寄存器来得到结果。
跳转指令总结如下:
IO相关
微处理器需要和外围设备互相通信,在8080中,除了常规的65536个地址外,还另外增加了256个地址专门用来访问输入/输出设备,它们被称作I/O端口,I/O地址信号标记位A0-A7。
输出指令OUT将累加器中的内容写入到对应端口PP,输入指令IN把一个字节从指定的端口读入到累加器:
中断指令及其相应
外围设备有时需要获得处理器的注意,如按下键盘时,处理器应该马上注意到这个事件。这个过程由一个被称为中断(interrupt)的机制实现,由外围设备产生的信号连接至8080的INT输入端。
指令传入处理器中会执行以下指令进行响应:
这些指令都被称为Restart重启动指令,在执行过程中将当前PC中的数据保存到堆栈,然后根据参数不同立即跳转到特定的地址,执行该地址处的指令来处理中断。
有时处理器会执行DI(Disable Interrupts)禁止中断,在处理器复位后,会执行EI(Enable Interrupt)来允许中断:
NOP指令
这个指令的操作码是00,它代表no op(no operation),也就是无操作,它让处理器什么操作也不执行,保持处理器的运行状态,8080可以执行一批NOP指令而不会引起任何错误的发生。
其他的CPU
至此我们介绍了8080处理器的全部244个操作码。其余的CPU设计与8080处理器大同小异,虽然功能类似,但是不同的CPU的操作码和助记符不同,执行的机器码也不一样,很多细节方面也有变动:如端口设计、寄存器数量、标志位设计。
在另一个CPU:Motorola 6800中,也有LDA指令(将地址处的数据加载到累加器),但是和8080有所不同:
3Ah 7Bh 34h 代表把347Bh处的字节加载到累加器(8080)
B6h 7Bh 34h 代表把7B34h处的字节加载到累加器(6800)
这两种微处理器对于操作码后的地址的处理方式是不同的,8080假设低字节在前,6800假设低字节在后,前者的处理方式被称为little-endian,后者被称为big-endian,也就是小端法和大端法表示字节顺序,这两种处理方式至今都还存在。