第19章:两种典型的微处理器
Last updated
Last updated
因特尔8080是一个8位微处理器,运行的时钟频率为2MHz,寻址长度为16位(共64KB)。8080的每个时钟周期都是500ns,每条指令长度为字节不等,需要个时钟周期执行。我们接下来花一些时间来研究8080的指令集。
注意到这里在逗号前的被称为目的操作数,逗号后的为源操作数。此处的A表示累加器的寄存器,加载命令表示将地址在aaa处的数据加载到累加器。
8080中除了A,还有B,C,D,E,H,L6个8位寄存器,其中H、L约定俗成地用于组合起来表示一个16位数据,H代表高位,L代表低位。8080中有一组63个单字节指令用于将数据在寄存器之间移动:
操作码
指令
操作码
指令
操作码
指令
操作码
指令
40
MOV B, B
50
MOV D, B
60
MOV H, B
70
MOV [HL], B
41
MOV B, C
51
MOV D, C
61
MOV H, C
71
MOV [HL], C
42
MOV B, D
52
MOV D, D
62
MOV H, D
72
MOV [HL], D
43
MOV B, E
53
MOV D, E
63
MOV H, E
73
MOV [HL], E
44
MOV B, H
54
MOV D, H
64
MOV H, H
74
MOV [HL], H
45
MOV B, L
55
MOV D, L
65
MOV H, L
75
MOV [HL], L
46
MOV B, [HL]
56
MOV D, [HL]
66
MOV H, [HL]
76
HLT
47
MOV B, A
57
MOV D, A
67
MOV H, A
77
MOV [HL], A
48
MOV C, B
58
MOV E, B
68
MOV L, B
78
MOV A, B
49
MOV C, C
59
MOV E, C
69
MOV L, C
79
MOV A, C
4A
MOV C, D
5A
MOV E, D
6A
MOV L, D
7A
MOV A, D
4B
MOV C, E
5B
MOV E, E
6B
MOV L, E
7B
MOV A, E
4C
MOV C, H
5C
MOV E, H
6C
MOV L, H
7C
MOV A, H
4D
MOV C, L
5D
MOV E, L
6D
MOV L, L
7D
MOV A, L
4E
MOV C, [HL]
5E
MOV E, [HL]
6E
MOV L, [HL]
7E
MOV A, [HL]
4F
MOV C, A
5F
MOV E, A
6F
MOV L, A
7F
MOV A, A
注意到以上的操作码都有这样的二进制形式:01dddsss,按照如下的方式赋值,
B = 000
C = 001
D = 010
E = 011
H = 100
L = 101
[HL] = 110
A = 111
我们发现其实ddd就表示目标操作数,sss表示源操作数。因为用3bit进行编码,ddd处的硬件可以是一个3-8译码器,而sss可以用一个8-1选择器选择输出的是哪一个的信息。
另外B、C以及D、E也可以组成16位数,具体的操作码如下:
操作码
指令
操作码
指令
02
STAX [BC], A
0A
LDAX A, [BC]
12
STAX [DE], A
1A
LDAX A, [DE]
还有一种双字节传送指令被称为传送立即数(MVI),第一个字节为操作码,第二个字节为数据,将某个单字节数据传送到寄存器,例如:
聪明的我们会注意到中间三位还是表示目的操作数。
接下来是加法(ADD)、进位加法(ADC)、减法(SUB)、借位减法(SBB)。
操作码
指令
操作码
指令
80
ADD A, B
90
SUB A, B
81
ADD A, C
91
SUB A, C
82
ADD A, D
92
SUB A, D
83
ADD A, E
93
SUB A, E
84
ADD A, H
94
SUB A, H
85
ADD A, L
95
SUB A, L
86
ADD A, [HL]
96
SUB A, [HL]
87
ADD A, A
97
SUB A, A
88
ADC A, B
98
SBB A, B
89
ADC A, C
99
SBB A, C
8A
ADC A, D
9A
SBB A, D
8B
ADC A, E
9B
SBB A, E
8C
ADC A, H
9C
SBB A, H
8D
ADC A, L
9D
SBB A, L
8E
ADC A, [HL]
9E
SBB A, [HL]
8F
ADC A, A
9F
SBB A, A
ADD指令格式:10-000-XXX
ADC指令格式:10-001-XXX
SUB指令格式:10-010-XXX
SBB指令格式:10-011-XXX
假设我们想做一个16位运算,两个16位数分别保存在BC和DE,我们将结果保存在BC。
8080的标志位(flag)共有5个:CF(进位)、ZF(零)、SF(符号)、PF(奇偶)、AF(辅助)
标志位用一个专门的8位寄存器保存,该寄存器被称为程序状态字(Program Status Word, PSW)
LDA、MOV、STA等不会影响标志位,ADD、SUB、SBB、ADC可能会影响标志位
运算最高位为1时,SF置为1,表示负数
运算结果为0时,ZF置为0
CF在ADD、ADC产生进位以及SUB和SBB不产生借位时置为1(其实都是最高位产生进位输出)
以下两条指令直接控制进位标志位
操作码
指令
含义
37
STC
令CF置1
3F
CMC
令CF取反
8080还能执行逻辑运算:AND、OR、XOR、CMP。其中CMP是比较运算,改变的是标志位。
如果A中的数等于25h,那么ZF置为1,如果小于25h,则CF置为1。
操作码
指令
操作码
指令
A0
AND A, B
B0
OR A, B
A1
AND A, C
B1
OR A, C
A2
AND A, D
B2
OR A, D
A3
AND A, E
B3
OR A, E
A4
AND A, H
B4
OR A, H
A5
AND A, L
B5
OR A, L
A6
AND A, [HL]
B6
OR A, [HL]
A7
AND A, A
B7
OR A, A
A8
XOR A, B
B8
CMP A, B
A9
XOR A, C
B9
CMP A, C
AA
XOR A, D
BA
CMP A, D
AB
XOR A, E
BB
CMP A, E
AC
XOR A, H
BC
CMP A, H
AD
XOR A, L
BD
CMP A, L
AE
XOR A, [HL]
BE
CMP A, [HL]
AF
XOR A, A
BF
CMP A, A
立即数也有8种算术和逻辑运算,可以看到与对应的寄存器指令之间的关系,以及指令内的变化规律:
立即数指令
操作码二进制
寄存器指令
操作码二进制
ADI A, xxx
11-000-110
ADD
10-000-XXX
ACI A, xxx
11-001-110
ADC
10-001-XXX
SUI A, xxx
11-010-110
SUB
10-010-XXX
SBI A, xxx
11-011-110
SBB
10-011-XXX
ANI A, xxx
11-100-110
AND
10-100-XXX
XRI A, xxx
11-101-110
XOR
10-101-XXX
ORI A, xxx
11-110-110
OR
10-110-XXX
CPI A, xxx
11-111-110
CMP
10-111-XXX
CMA(操作码27),是对累加器中数取反的操作,该指令与如下指令等价:
DAA(Decimal Adjust Accumulator),即十进制调整累加器。8080中有一种BCD码来用二进制表示十进制,即用每4bit二进制表示十进制的一位,变化范围从0000到1001,因此在BCD码下,一个字节可以表示两个十进制位。如果想将十进制的27和94相加,只需要执行
执行DAA指令的时候,我们需要用到前面提到的辅助标志位AF。
8080中有特殊执行加减1的指令:INR和DCR,可以影响除了CF之外的所有标志位,指令格式如下
另外还有4个循环移位指令,其只能影响到CF标志位
操作码
指令
含义
07
RLC
使累加器循环左移1位
0F
RRC
使累加器循环右移1位
17
RAL
带进位的累加器循环左移1位
1F
RAR
带进位的累加器循环右移1位
工作原理:
RLC和RRC:整体向左或者右移动1位,被移出的一位接到头部/尾部,CF位与被移出的位保持一致
RAL和RAR:带CF位循环移动,相当于9位的循环移动
8080堆栈的工作原理:专门用一个16位寄存器(堆栈指针,Stack Pointer, SP)存地址,每次执行push就将SP的值-1,然后把寄存器的数据存放在对应的地址处;每次执行pop就就将SP的值+1,然后把对应地址的值取回到寄存器。
一般会把SP的值设置为0000h,这样第一次push就会存到FFFFh处,即存储器的最高地址,与程序执行代码远离。
如果堆栈push太多覆盖到程序代码就会发生堆栈上溢错误(stack overflow),或者pop太多导致反向取到地址开头的程序处,发生下溢(stack underflow)。
还有一些指令可以对SP或者寄存器对(BC、DE、HL)执行16位数操作
LXI(Load Extended Immediate):加载扩展的16位立即数。例如:
INX和DCX:16位立即数的加减1操作
DAD:16位加法,一般用来计算地址,例如
8080有五个标志位,除了非条件跳转,跳转指令还支持ZF(zero)、CF(carry)、PF(parity奇偶性)、SF(sign正负号)4种共8个跳转。
跳转的方式有三种,除了标准的Jump外,还有Call指令和Return指令。Call指令在执行时,程序计数器(Program Counter, PC)加载一个新的指令执行地址,并且将现在的地址保存在堆栈中;Return指令从堆栈中弹出两个字节,并且加载到PC中。因此,Call和Return指令可以使得程序执行子程序。
利用跳转指令可以实现乘法子程序:
注意Multiply等三个标志表示地址。
程序中调用乘法子程序的方法为:
条件
操作码
指令
操作码
指令
操作码
指令
None
C9
RET
C3
JMP aaa
CD
CALL aaa
Z not set
C0
RET
C2
JNZ aaa
C4
CNZ aaa
Z set
C8
RET
CA
JZ aaa
CC
CZ aaa
C not set
D0
RET
D2
JNC aaa
D4
CNC aaa
C set
D8
RET
DA
JC aaa
DC
CC aaa
Odd parity
E0
RET
E2
JPO aaa
E4
CPO aaa
Even parity
E8
RET
EA
JPE aaa
EC
CPE aaa
S not set
F0
RET
F2
JP aaa
F4
CP aaa
S set
F8
RET
FA
JM aaa
FC
CM aaa
注:对于乘法,可以使用移位运算使得计算时间从O(乘数)减少到O(log(乘数))。
操作码
指令
含义
22h
SHLD [aaa], HL
将HL中的数据储存到地址aaa处
2Ah
LHLD HL, [aaa]
将地址aaa处的数据储存到HL中
E9h
PCHL PC, HL
加载HL中数据到程序计数器
F9h
SPHL SP, HL
加载HL中数据到堆栈指针
E3h
XTHL HL, [SP]
将HL数据与堆栈顶部两字节进行交换
EBh
XCHG HL, DE
将HL数据与DE数据进行交换
在某些微处理器中,外围设备占据了一些存储器地址,称之为内存映像I/O(memory-mapped I/O)。8080中,额外加设了256个地址专门用于进行I/O交互。
OUT指令(操作码D3)用于把累加器中的内容写入到紧跟指令的I/O端口(OUT PP);
IN指令(操作码DB)与OUT相反;
8080有一个INT(interrupt,中断)端口,使处理器马上注意到外部设备;
当8080复位之后,就不在响应中断,必须执行EI(操作码F3)指令来恢复响应
DI指令(操作码FB)则禁止中断
NOP指令(操作码00)使得处理器什么都不做。
1978年8086芯片
因特尔个人电脑x86系列处理器:186、286、386、486
因特尔奔腾芯片(Intel Pentium)
摩托罗拉6800处理器是一个64KB,1MHz处理器,在寄存器、累加器、标志位等很多方面都与因特尔8080有些区别。
摩托罗拉在保存多字节数据时,将最高有效位放在最前面,而因特尔则放在最后面。前者被称为big-endian,后者则被称为little-endian,至今仍是两种类型处理器的主要区别。
苹果Macintosh最早采用的就是6800的后续产品68000。
RST(Restart)指令与CALL指令类似,将当前的PC数据保存之后,立刻跳转到指定地址。共有RST八个指令(操作码C7,CF,DF... ),分别跳转到0000h,0008h一直到0038h。