跳转到内容

6502 汇编

25% developed
来自维基教科书,开放世界中的开放书籍

本书是 6502 汇编语言的指南。本书将教授 8 位 6502 处理器的不同内存寻址模式和指令。

如果你想进行 Atari 2600/8 位系列/5200/7800 编程、Commodore PET/VIC/64/128 编程、Acorn 8 位编程、Apple I/II 编程、NES 编程超级任天堂编程,你可能需要学习 6502 汇编语言编程。

汇编器的语法会有所不同 - 本书将始终使用以下语法

数字表示
语法 进制 示例
%00001111 二进制 LDA #%0001
$FA 十六进制 LDA #$0E
123 十进制 LDA #100

寄存器

[编辑 | 编辑源代码]

寄存器
寄存器 大小(位) 用途
累加器 (A) 8 用于对数据进行计算。
大多数指令可以直接在累加器上操作,而不是花费 CPU 周期访问内存。
X 寄存器 (X) 8 用作某些寻址模式中的索引。
Y 寄存器 (Y) 8 用作某些寻址模式中的索引。
程序计数器 (PC) 16 指向要执行的下一条指令的地址。
堆栈指针 (S) 8 存储堆栈索引,下一个堆栈元素将写入该索引。
该位置的地址为 $0100 + SP。SP 最初设置为 $FD

TSX 和 TXS 是唯一允许你直接修改 S 的指令。

状态 (P) 8 每一位表示一个状态标志。

标志指示 CPU 的状态,或有关先前指令结果的信息。PHP 和 PLP 可以从堆栈中保存/恢复 P。各种指令可以直接设置或清除 P 中的位:SEC、CLC、SEI、CLI、SED、CLD、CLV。
有关每个标志的描述,请参见下表。

状态标志
符号 名称 描述
7 N 负数

比较: 如果寄存器的值小于输入值,则设置。
否则: 如果结果为负数,即结果的第 7 位被设置,则设置。

6 V 溢出

算术运算: 如果在加法或减法期间发生有符号溢出,即结果的符号与输入和累加器的符号不同,则设置。

BIT: 设置为输入的第 6 位。

其他: 原始 6502 具有一个名为 "SO" (Set Overflow) 的外部引脚,硬件可以使用它来设置 V 标志。其目的是比 IRQ 更快地响应硬件事件。大多数常见的 6502 兼容平台没有使用此功能,或者不使用它。

5 - (未用) 始终设置
4 B[1] 断点 如果 BRK 指令触发中断请求,则设置
3 D 十进制 十进制模式[2]: 算术指令将把输入和输出视为 "二进制编码十进制" (BCD) 数字。


例如,$09 + $01 = $10 注意: NES 中的 6502 兼容 CPU 没有实现十进制模式,因此十进制模式不会改变该 CPU 上的行为,但你仍然可以清除/设置该标志。

2 I 中断禁用 在设置时禁用 IRQ 中断。NMI 和 RESET 不受影响。
1 Z

比较: 如果寄存器的值等于输入值,则设置

BIT: 如果将累加器与输入进行逻辑与运算的结果为 0,则设置。

否则: 如果结果为零,则设置。

注意: 比较 (CMP、CPX、CPY) 指令通过减法来工作,但不保留结果。因此,如果值为 0,则 Z 将被设置,因此这就是为什么 BEQ 测试 Z 标志,以及为什么在 BEQ 之前不必 CMP #0。

0 C 进位 进位/借位标志用于数学和旋转操作

算术运算: 如果在加法或减法期间发生无符号溢出,即结果小于初始值 (或等于初始值,如果进位标志在进入时被设置),则设置。

比较: 如果寄存器的值大于或等于输入值,则设置。

移位: 设置为输入的消除位的的值,即左移时为第 7 位,右移时为第 0 位。

内存布局

[编辑 | 编辑源代码]

16 位的值以小端方式存储在内存中,因此最低有效字节存储在最高有效字节之前。例如,如果地址 $0000 包含 $FF 并且地址 $0001 包含 $00,则从 $0000 读取两个字节的值将得到 $00FF

有符号整数采用二进制补码,可以表示从 -128 (%10000000) 到 +127 (%01111111) 的值。如果整数为负数,则第 7 位被设置。

6502 的程序计数器是 16 位宽,因此可以寻址高达 2^16 (65536) 字节的内存。某些内存区域专用于特定用途

内存区域
区域 内容 描述
$0000 - $00FF 零页内存的第一页,访问速度比其他页快。
指令可以用单个字节而不是两个字节来指定零页内的地址,因此使用零页而不是其他任何页的指令执行时只需要少一个 CPU 周期
$0100 - $01FF 堆栈后进先出数据结构。从 $01FF$0100 向后增长。
由一些传输堆栈子程序指令使用
$0200 - $FFFF 通用可以用于任何所需用途的内存。
使用 6502 处理器的设备可以选择保留子区域用于其他用途,例如 内存映射 I/O

内存寻址模式

[编辑 | 编辑源代码]

每条指令使用十三种内存寻址模式中的一种,这决定了指令的操作数。每个模式都提供了一个示例。

累加器: A

[编辑 | 编辑源代码]

累加器被隐式地用作操作数,因此不需要指定地址。

示例

使用没有操作数的 ASL (算术左移) 指令时,累加器始终是左移的值。

ASL

隐式: i

[编辑 | 编辑源代码]

操作数是隐式的,因此不需要指定它。

示例

这里隐式的操作数是 X,即传输的源,和 A,即传输的目标。

TXA

立即数: #

[编辑 | 编辑源代码]

操作数直接用于执行计算。

示例

将值 $22 加载到累加器中。

LDA #$22

绝对地址: a

[编辑 | 编辑源代码]

指定完整的 16 位地址,并使用该地址处的字节执行计算。

示例

将地址 $D010 处的数值 $24 加载到 X 寄存器中。

LDX $D010

零页:zp

[编辑 | 编辑源代码]

单个字节指定内存第一页($00xx)中的地址,也称为零页,并使用该地址处的字节执行计算。

示例

将地址 $0002 处的数值加载到 Y 寄存器中。

LDY $02

相对:r

[编辑 | 编辑源代码]

指定的偏移量将加到程序计数器 (PC) 中存储的当前地址上。偏移量范围为 -128 到 +127。

示例

将偏移量 $2D 加到程序计数器 (例如 $C100) 中的地址。分支 (如果执行) 的目标地址将为 $C12D

BPL $2D

绝对间接: (a)

[编辑 | 编辑源代码]

使用指定地址处存储的低字节序的 2 字节数值执行计算。仅用于 JMP 指令。

示例

读取地址 $A001$A002,分别返回 $FF$00。然后跳转到地址 $00FF

JMP ($A001)

带 X 索引的绝对:a,x

[编辑 | 编辑源代码]

X 中的数值加到指定的地址,得到一个和地址。使用和地址处的数值执行计算。

示例

X 中的数值 $02 加到 $C001,得到和 $C003。使用地址 $C003 处的数值 $5A 执行带进位加 (ADC) 操作。

ADC $C001,X

带 Y 索引的绝对:a,y

[编辑 | 编辑源代码]

Y 中的数值加到指定的地址,得到一个和地址。使用和地址处的数值执行计算。

示例

Y 中的数值 $03 加到 $F001,得到和 $F004。将地址 $F004 处的数值 $EF 加 1 (INC) 并将 $F0 写回 $F004

INC $F001,Y

带 X 索引的零页:zp,x

[编辑 | 编辑源代码]

X 中的数值加到指定的零页地址,得到一个和地址。使用和地址处的数值执行计算。

示例

X 中的数值 $02 加到 $01,得到和 $03。将地址 $0003 处的数值 $A5 加载到累加器中。

LDA $01,X

带 Y 索引的零页:zp,y

[编辑 | 编辑源代码]

Y 中的数值加到指定的零页地址,得到一个和地址。使用和地址处的数值执行计算。

示例

Y 中的数值 $03 加到 $01,得到和 $04。将地址 $0004 处的数值 $E3 加载到累加器中。

LDA $01,Y

带 X 索引的零页间接: (zp,x)

[编辑 | 编辑源代码]

X 中的数值加到指定的零页地址,得到一个和地址。加载和地址 (LSB) 和和地址加 1 (MSB) 处的 2 字节对中存储的低字节序地址,并使用该地址处的数值执行计算。

示例

X 中的数值 $02 加到 $15,得到和 $17。地址 $0017$0018 处的地址 $D010 将是累加器中数值 $0F 存储的位置。

STA ($15,X)

带 Y 索引的零页间接: (zp),y

[编辑 | 编辑源代码]

Y 中的数值加到指定地址 (LSB) 和指定地址加 1 (MSB) 处的 2 字节对中存储的低字节序地址处的地址。使用和地址处的数值执行计算。实际上,寻址模式实际上重复了累加器寄存器的数字。

示例

Y 中的数值 $03 加到地址 $002A$002B 处的地址 $C235,得到和 $C238。然后,将累加器与 $C238 处的数值 $2F 进行异或运算。

EOR ($2A),Y

这些是 6502 处理器的指令,包括 ASCII 可视化、受影响标志的列表以及可接受寻址模式的操作码表。

加载和存储

[编辑 | 编辑源代码]
将内存加载到累加器: LDA    将内存加载到索引 X: LDX    将内存加载到索引 Y: LDY   

M -> A

标志:N,Z

M -> X

标志:N,Z

M -> Y

标志:N,Z

寻址模式 操作码
a AD
a,x BD
a,y B9
# A9
zp A5
(zp,x) A1
zp,x B5
(zp),y B1
寻址模式 操作码
a AE
a,y BE
# A2
zp A6
zp,y B6
寻址模式 操作码
a AC
a,x BC
# A0
zp A4
zp,x B4
将累加器存储到内存: STA    将索引 X 存储到内存: STX    将索引 Y 存储到内存: STY   

A -> M

标志:无

X -> M

标志:无

Y -> M

标志:无

寻址模式 操作码
a 8D
a,x 9D
a,y 99
zp 85
(zp,x) 81
zp,x 95
(zp),y 91
寻址模式 操作码
a 8E
zp 86
zp,y 96
寻址模式 操作码
a 8C
zp 84
zp,x 94
将内存加到累加器并带进位: ADC    将内存从累加器中减去并带借位: SBC   

A + M + C -> A

标志:N,V,Z,C

A - M - ~C -> A

标志:N,V,Z,C

寻址模式 操作码
a 6D
a,x 7D
a,y 79
# 69
zp 65
(zp,x) 61
zp,x 75
(zp),y 71
寻址模式 操作码
a ED
a,x FD
a,y F9
# E9
zp E5
(zp,x) E1
zp,x F5
(zp),y F1

递增和递减

[编辑 | 编辑源代码]
将内存加 1: INC    将索引 X 加 1: INX    将索引 Y 加 1: INY   

M + 1 -> M

标志:N,Z

X + 1 -> X

标志:N,Z

Y + 1 -> Y

标志:N,Z

寻址模式 操作码
a EE
a,x FE
zp E6
zp,x F6
寻址模式 操作码
i E8
寻址模式 操作码
i C8
将内存减 1: DEC    将索引 X 减 1: DEX    将索引 Y 减 1: DEY   

M - 1 -> M

标志:N,Z

X - 1 -> X

标志:N,Z

Y - 1 -> Y

标志:N,Z

寻址模式 操作码
a CE
a,x DE
zp C6
zp,x D6
寻址模式 操作码
i CA
寻址模式 操作码
i 88

移位和旋转

[编辑 | 编辑源代码]
算术左移一位:ASL    逻辑右移一位:LSR   

C <- 7 6 5 4 3 2 1 0 <- 0

标志:N,Z,C

0 -> 7 6 5 4 3 2 1 0 -> C

标志:N,Z,C

寻址模式 操作码
a 0E
a,x 1E
A 0A
zp 06
zp,x 16
寻址模式 操作码
a 4E
a,x 5E
A 4A
zp 46
zp,x 56
循环左移一位:ROL    循环右移一位:ROR   

C <- 7 6 5 4 3 2 1 0 <- C

标志:N,Z,C

C -> 7 6 5 4 3 2 1 0 -> C

标志:N,Z,C

寻址模式 操作码
a 2E
a,x 3E
A 2A
zp 26
zp,x 36
寻址模式 操作码
a 6E
a,x 7E
A 6A
zp 66
zp,x 76
累加器与内存执行与运算:AND    累加器与内存执行或运算:ORA    累加器与内存执行异或运算:EOR   

A & M -> A

标志:N,Z

A | M -> A

标志:N,Z

A ^ M -> A

标志:N,Z

寻址模式 操作码
a 2D
a,x 3D
a,y 39
# 29
zp 25
(zp,x) 21
zp,x 35
(zp),y 31
寻址模式 操作码
a 0D
a,x 1D
a,y 19
# 09
zp 05
(zp,x) 01
zp,x 15
(zp),y 11
寻址模式 操作码
a 4D
a,x 5D
a,y 59
# 49
zp 45
(zp,x) 41
zp,x 55
(zp),y 51

比较和测试位

[编辑 | 编辑源代码]

负号 (N)、零 (Z) 和进位 (C) 状态标志 用于条件 (分支) 指令。

所有比较指令都以相同的方式影响标志。

条件 N Z C
寄存器 < 内存 1 0 0
寄存器 = 内存 0 1 1
寄存器 > 内存 0 0 1
比较内存和累加器:CMP    比较内存和索引 X:CPX    比较内存和索引 Y:CPY   

A - M

标志:N,Z,C

X - M

标志:N,Z,C

Y - M

标志:N,Z,C

寻址模式 操作码
a CD
a,x DD
a,y D9
# C9
zp C5
(zp,x) C1
zp,x D5
(zp),y D1
寻址模式 操作码
a EC
# E0
zp E4
寻址模式 操作码
a CC
# C0
zp C4

测试内存中的位与累加器:BIT

A & M

标志:N = M7,V = M6,Z

寻址模式 操作码
a 2C
# 89
zp 24
当进位位为 0 时分支:BCC    当进位位为 1 时分支:BCS   

当 C = 0 时分支

标志:无

当 C = 1 时分支

标志:无

寻址模式 操作码
r 90
寻址模式 操作码
r B0
当结果不为 0 时分支:BNE    当结果为 0 时分支:BEQ   

当 Z = 0 时分支

标志:无

当 Z = 1 时分支

标志:无

寻址模式 操作码
r D0
寻址模式 操作码
r F0
当结果为正时分支:BPL    当结果为负时分支:BMI   

当 N = 0 时分支

标志:无

当 N = 1 时分支

标志:无

寻址模式 操作码
r 10
寻址模式 操作码
r 30
当溢出位为 0 时分支:BVC    当溢出位为 1 时分支:BVS   

当 V = 0 时分支

标志:无

当 V = 1 时分支

标志:无

寻址模式 操作码
r 50
寻址模式 操作码
r 70
将累加器传输到索引 X:TAX    将索引 X 传输到累加器:TXA   

A -> X

标志:N,Z

X -> A

标志:N,Z

寻址模式 操作码
i AA
寻址模式 操作码
i 8A
将累加器传输到索引 Y:TAY    将索引 Y 传输到累加器:TYA   

A -> Y

标志:N,Z

Y -> A

标志:N,Z

寻址模式 操作码
i A8
寻址模式 操作码
i 98
将堆栈指针传输到索引 X:TSX    将索引 X 传输到堆栈指针:TXS   

S -> X

标志:N,Z

X -> S

标志:无

寻址模式 操作码
i BA
寻址模式 操作码
i 9A
将累加器压入堆栈:PHA    从堆栈中拉出累加器:PLA   

A -> S

标志:无

S -> A

标志:N,Z

寻址模式 操作码
i 48
寻址模式 操作码
i 68
将处理器状态压入堆栈:PHP    从堆栈中拉出处理器状态:PLP   

P -> S

标志:无

S -> P

标志:全部

寻址模式 操作码
i 08
寻址模式 操作码
i 28

处理器状态存储为一个字节,从高到低位的标志位如下:NV--DIZC。

子程序和跳转

[编辑 | 编辑源代码]

跳转到新位置:JMP

通过更改程序计数器的值跳转到新位置。

警告: 当与 绝对间接 地址模式一起使用时,当指定地址为 $xxFF 时,硬件错误会导致意外行为。
例如,JMP ($11FF) 将从 $11FF 读取低字节,从 $1100 读取高字节,而不是从 $1200 读取高字节,正如人们预期的那样。这是由于间接地址的低字节溢出没有进位到高字节。

标志:无

寻址模式 操作码
a 4C
(a) 6C

跳转到新位置并保存返回地址:JSR

跳转到子程序。

下一个指令之前的地址 (PC - 1) 被压入堆栈:首先是高字节,然后是低字节。由于堆栈向后增长,因此返回地址在内存中存储为小端数。
PC 被设置为目标地址。

标志:无

寻址模式 操作码
a 20

从子程序返回:RTS

从子程序返回到调用它的 JSR 的位置。

返回地址从堆栈中弹出 (先弹出低字节,然后弹出高字节)。
返回地址递增并存储在 PC 中。

标志:无

寻址模式 操作码
i 60

从中断返回:RTI

从中断返回。

P 从堆栈中弹出。
PC 从堆栈中弹出。

标志:全部

寻址模式 操作码
i 40

设置和清除

[编辑 | 编辑源代码]
清除进位位:CLC    设置进位位:SEC   

0 -> C

标志:C = 0

1 -> C

标志:C = 1

寻址模式 操作码
i 18
寻址模式 操作码
i 38
清除十进制模式:CLD    设置十进制模式:SED   

0 -> D

标志:D = 0

1 -> D

标志:D = 1

寻址模式 操作码
i D8
寻址模式 操作码
i F8
清除中断禁用状态:CLI    设置中断禁用状态:SEI   

0 -> I

标志:I = 0

1 -> I

标志:I = 1

寻址模式 操作码
i 58
寻址模式 操作码
i 78
清除溢出位:CLV   

0 -> V

标志:V = 0

寻址模式 操作码
i B8

中断:BRK   

强制中断

标志:B = 1,I = 1

寻址模式 操作码
i 00

无操作:NOP   

无操作

标志:无

寻址模式 操作码
i EA

指令表

[编辑 | 编辑源代码]
指令表
高半字节 低半字节
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00 BRK i ORA (zp,x) ORA zp ASL zp PHP i ORA # ASL A ORA a ASL a
10 BPL r ORA (zp),y ORA zp,x ASL zp,x CLC i ORA a,y ORA a,x ASL a,x
20 JSR a AND (zp,x) BIT zp AND zp ROL zp PLP i AND # ROL A BIT a AND a ROL a
30 BMI r AND (zp),y AND zp,x ROL zp,x SEC i AND a,y AND a,x ROL a,x
40 RTI i EOR (zp,x) EOR zp LSR zp PHA i EOR # LSR A JMP a EOR a LSR a
50 BVC r EOR (zp),y EOR zp,x LSR zp,x CLI i EOR a,y EOR a,x LSR a,x
60 RTS i ADC (zp,x) ADC zp ROR zp PLA i ADC # ROR A JMP (a) ADC a ROR a
70 BVS r ADC (zp),y ADC zp,x ROR zp,x SEI i ADC a,y ADC a,x ROR a,x
80 STA (zp,x) STY zp STA zp STX zp DEY i BIT # TXA i STY a STA a STX a
90 BCC r STA (zp),y STY zp,x STA zp,x STX zp,y TYA i STA a,y TXS i STA a,x
A0 LDY # LDA (zp,x) LDX # LDY zp LDA zp LDX zp TAY i LDA # TAX i LDY a LDA a LDX a
B0 BCS r LDA (zp),y LDY zp,x LDA zp,x LDX zp,y CLV i LDA a,y TSX i LDY a,x LDA a,x LDX a,y
C0 CPY # CMP (zp,x) CPY zp CMP zp DEC zp INY i CMP # DEX i CPY a CMP a DEC a
D0 BNE r CMP (zp),y CMP zp,x DEC zp,x CLD i CMP a,y CMP a,x DEC a,x
E0 CPX # SBC (zp,x) CPX zp SBC zp INC zp INX i SBC # NOP i CPX a SBC a INC a
F0 BEQ r SBC (zp),y SBC zp,x INC zp,x SED i SBC a,y SBC a,x INC a,x

参考资料

[编辑 | 编辑源代码]
  1. [1],B标志位不代表实际的CPU寄存器
[编辑 | 编辑源代码]

进一步阅读

[编辑 | 编辑源代码]
华夏公益教科书