第19章:两种典型的微处理器

8080微处理器

因特尔8080是一个8位微处理器,运行的时钟频率为2MHz,寻址长度为16位(2162^{16}共64KB)。8080的每个时钟周期都是500ns,每条指令长度为131 \sim 3字节不等,需要4184 \sim 18个时钟周期执行。我们接下来花一些时间来研究8080的指令集。

加载和保存指令

# 加载,操作码为3Ah
LDA A, [aaa]
# 储存,操作码为32h
STA [aaa], A

注意到这里在逗号前的被称为目的操作数,逗号后的为源操作数。此处的A表示累加器的寄存器,加载命令表示将地址在aaa处的数据加载到累加器。

寄存器和MOV指令

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。

标志位

  1. 8080的标志位(flag)共有5个:CF(进位)、ZF(零)、SF(符号)、PF(奇偶)、AF(辅助)

  2. 标志位用一个专门的8位寄存器保存,该寄存器被称为程序状态字(Program Status Word, PSW)

  3. LDA、MOV、STA等不会影响标志位,ADD、SUB、SBB、ADC可能会影响标志位

  4. 运算最高位为1时,SF置为1,表示负数

  5. 运算结果为0时,ZF置为0

  6. CF在ADD、ADC产生进位以及SUB和SBB不产生借位时置为1(其实都是最高位产生进位输出)

  7. 以下两条指令直接控制进位标志位

操作码

指令

含义

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

DAA和CMA

CMA(操作码27),是对累加器中数取反的操作,该指令与如下指令等价:

DAA(Decimal Adjust Accumulator),即十进制调整累加器。8080中有一种BCD码来用二进制表示十进制,即用每4bit二进制表示十进制的一位,变化范围从0000到1001,因此在BCD码下,一个字节可以表示两个十进制位。如果想将十进制的27和94相加,只需要执行

执行DAA指令的时候,我们需要用到前面提到的辅助标志位AF。

加减1和移位指令

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位的循环移动

堆栈(stack)

  • 8080堆栈的工作原理:专门用一个16位寄存器(堆栈指针,Stack Pointer, SP)存地址,每次执行push就将SP的值-1,然后把寄存器的数据存放在对应的地址处;每次执行pop就就将SP的值+1,然后把对应地址的值取回到寄存器。

  • 一般会把SP的值设置为0000h,这样第一次push就会存到FFFFh处,即存储器的最高地址,与程序执行代码远离。

  • 如果堆栈push太多覆盖到程序代码就会发生堆栈上溢错误(stack overflow),或者pop太多导致反向取到地址开头的程序处,发生下溢(stack underflow)。

16位操作指令

还有一些指令可以对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数据进行交换

外围设备(peripheral)

在某些微处理器中,外围设备占据了一些存储器地址,称之为内存映像I/O(memory-mapped I/O)。8080中,额外加设了256个地址专门用于进行I/O交互。

  1. OUT指令(操作码D3)用于把累加器中的内容写入到紧跟指令的I/O端口(OUT PP);

  2. IN指令(操作码DB)与OUT相反;

  3. 8080有一个INT(interrupt,中断)端口,使处理器马上注意到外部设备;

  4. 当8080复位之后,就不在响应中断,必须执行EI(操作码F3)指令来恢复响应

  5. DI指令(操作码FB)则禁止中断

  6. RST(Restart)指令与CALL指令类似,将当前的PC数据保存之后,立刻跳转到指定地址。共有RST070 \sim 7八个指令(操作码C7,CF,DF... ),分别跳转到0000h,0008h一直到0038h。

  7. NOP指令(操作码00)使得处理器什么都不做。

因特尔芯片的后续发展

  1. 1978年8086芯片

  2. 因特尔个人电脑x86系列处理器:186、286、386、486

  3. 因特尔奔腾芯片(Intel Pentium)

6800处理器

  • 摩托罗拉6800处理器是一个64KB,1MHz处理器,在寄存器、累加器、标志位等很多方面都与因特尔8080有些区别。

  • 摩托罗拉在保存多字节数据时,将最高有效位放在最前面,而因特尔则放在最后面。前者被称为big-endian,后者则被称为little-endian,至今仍是两种类型处理器的主要区别。

  • 苹果Macintosh最早采用的就是6800的后续产品68000。

Last updated