6502 汇编
本书是 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 | 负数 |
比较: 如果寄存器的值小于输入值,则设置。 |
6 | V | 溢出 |
算术运算: 如果在加法或减法期间发生有符号溢出,即结果的符号与输入和累加器的符号不同,则设置。
其他: 原始 6502 具有一个名为 "SO" (Set Overflow) 的外部引脚,硬件可以使用它来设置 V 标志。其目的是比 IRQ 更快地响应硬件事件。大多数常见的 6502 兼容平台没有使用此功能,或者不使用它。 |
5 | - | (未用) | 始终设置 |
4 | B[1] | 断点 | 如果 BRK 指令触发中断请求,则设置 |
3 | D | 十进制 | 十进制模式[2]: 算术指令将把输入和输出视为 "二进制编码十进制" (BCD) 数字。
|
2 | I | 中断禁用 | 在设置时禁用 IRQ 中断。NMI 和 RESET 不受影响。 |
1 | Z | 零 |
比较: 如果寄存器的值等于输入值,则设置
否则: 如果结果为零,则设置。 注意: 比较 (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 |
每条指令使用十三种内存寻址模式中的一种,这决定了指令的操作数。每个模式都提供了一个示例。
累加器被隐式地用作操作数,因此不需要指定地址。
示例
使用没有操作数的 ASL (算术左移) 指令时,累加器始终是左移的值。
ASL
操作数是隐式的,因此不需要指定它。
示例
这里隐式的操作数是 X,即传输的源,和 A,即传输的目标。
TXA
操作数直接用于执行计算。
示例
将值 $22
加载到累加器中。
LDA #$22
指定完整的 16 位地址,并使用该地址处的字节执行计算。
示例
将地址 $D010
处的数值 $24
加载到 X 寄存器中。
LDX $D010
单个字节指定内存第一页($00xx
)中的地址,也称为零页,并使用该地址处的字节执行计算。
示例
将地址 $0002
处的数值加载到 Y 寄存器中。
LDY $02
指定的偏移量将加到程序计数器 (PC) 中存储的当前地址上。偏移量范围为 -128 到 +127。
示例
将偏移量 $2D
加到程序计数器 (例如 $C100
) 中的地址。分支 (如果执行) 的目标地址将为 $C12D
。
BPL $2D
使用指定地址处存储的低字节序的 2 字节数值执行计算。仅用于 JMP
指令。
示例
读取地址 $A001
和 $A002
,分别返回 $FF
和 $00
。然后跳转到地址 $00FF
。
JMP ($A001)
将 X
中的数值加到指定的地址,得到一个和地址。使用和地址处的数值执行计算。
示例
将 X
中的数值 $02
加到 $C001
,得到和 $C003
。使用地址 $C003
处的数值 $5A
执行带进位加 (ADC
) 操作。
ADC $C001,X
将 Y
中的数值加到指定的地址,得到一个和地址。使用和地址处的数值执行计算。
示例
将 Y
中的数值 $03
加到 $F001
,得到和 $F004
。将地址 $F004
处的数值 $EF
加 1 (INC
) 并将 $F0
写回 $F004
。
INC $F001,Y
将 X
中的数值加到指定的零页地址,得到一个和地址。使用和地址处的数值执行计算。
示例
将 X
中的数值 $02
加到 $01
,得到和 $03
。将地址 $0003
处的数值 $A5
加载到累加器中。
LDA $01,X
将 Y
中的数值加到指定的零页地址,得到一个和地址。使用和地址处的数值执行计算。
示例
将 Y
中的数值 $03
加到 $01
,得到和 $04
。将地址 $0004
处的数值 $E3
加载到累加器中。
LDA $01,Y
将 X
中的数值加到指定的零页地址,得到一个和地址。加载和地址 (LSB) 和和地址加 1 (MSB) 处的 2 字节对中存储的低字节序地址,并使用该地址处的数值执行计算。
示例
将 X
中的数值 $02
加到 $15
,得到和 $17
。地址 $0017
和 $0018
处的地址 $D010
将是累加器中数值 $0F
存储的位置。
STA ($15,X)
将 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 | ||||||||||||||||||||||||||||||||||||||||||
|
|
| ||||||||||||||||||||||||||||||||||||||||||
将累加器存储到内存: STA |
将索引 X 存储到内存: STX |
将索引 Y 存储到内存: STY | ||||||||||||||||||||||||||||||||||||||||||
A -> M 标志:无 |
X -> M 标志:无 |
Y -> M 标志:无 | ||||||||||||||||||||||||||||||||||||||||||
|
|
|
将内存加到累加器并带进位: ADC |
将内存从累加器中减去并带借位: SBC | ||||||||||||||||||||||||||||||||||||
A + M + C -> A 标志:N,V,Z,C |
A - M - ~C -> A 标志:N,V,Z,C | ||||||||||||||||||||||||||||||||||||
|
|
将内存加 1: INC |
将索引 X 加 1: INX |
将索引 Y 加 1: INY | ||||||||||||||||||
M + 1 -> M 标志:N,Z |
X + 1 -> X 标志:N,Z |
Y + 1 -> Y 标志:N,Z | ||||||||||||||||||
|
|
| ||||||||||||||||||
将内存减 1: DEC |
将索引 X 减 1: DEX |
将索引 Y 减 1: DEY | ||||||||||||||||||
M - 1 -> M 标志:N,Z |
X - 1 -> X 标志:N,Z |
Y - 1 -> Y 标志:N,Z | ||||||||||||||||||
|
|
|
算术左移一位: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 | ||||||||||||||||||||||||
|
| ||||||||||||||||||||||||
循环左移一位: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 | ||||||||||||||||||||||||
|
|
累加器与内存执行与运算:AND |
累加器与内存执行或运算:ORA |
累加器与内存执行异或运算:EOR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
A & M -> A 标志:N,Z |
A | M -> A 标志:N,Z |
A ^ M -> A 标志:N,Z | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
负号 (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 | ||||||||||||||||||||||||||||||||||
|
|
|
测试内存中的位与累加器:BIT
A & M
标志:N = M7,V = M6,Z
寻址模式 | 操作码 |
---|---|
a | 2C |
# | 89 |
zp | 24 |
当进位位为 0 时分支:BCC |
当进位位为 1 时分支:BCS | ||||||||
当 C = 0 时分支 标志:无 |
当 C = 1 时分支 标志:无 | ||||||||
|
| ||||||||
当结果不为 0 时分支:BNE |
当结果为 0 时分支:BEQ | ||||||||
当 Z = 0 时分支 标志:无 |
当 Z = 1 时分支 标志:无 | ||||||||
|
| ||||||||
当结果为正时分支:BPL |
当结果为负时分支:BMI | ||||||||
当 N = 0 时分支 标志:无 |
当 N = 1 时分支 标志:无 | ||||||||
|
| ||||||||
当溢出位为 0 时分支:BVC |
当溢出位为 1 时分支:BVS | ||||||||
当 V = 0 时分支 标志:无 |
当 V = 1 时分支 标志:无 | ||||||||
|
|
将累加器传输到索引 X:TAX |
将索引 X 传输到累加器:TXA | ||||||||
A -> X 标志:N,Z |
X -> A 标志:N,Z | ||||||||
|
| ||||||||
将累加器传输到索引 Y:TAY |
将索引 Y 传输到累加器:TYA | ||||||||
A -> Y 标志:N,Z |
Y -> A 标志:N,Z | ||||||||
|
| ||||||||
将堆栈指针传输到索引 X:TSX |
将索引 X 传输到堆栈指针:TXS | ||||||||
S -> X 标志:N,Z |
X -> S 标志:无 | ||||||||
|
|
将累加器压入堆栈:PHA |
从堆栈中拉出累加器:PLA | ||||||||
A -> S 标志:无 |
S -> A 标志:N,Z | ||||||||
|
| ||||||||
将处理器状态压入堆栈:PHP |
从堆栈中拉出处理器状态:PLP | ||||||||
P -> S 标志:无 |
S -> P 标志:全部 | ||||||||
|
|
处理器状态存储为一个字节,从高到低位的标志位如下: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 | ||||||||
|
| ||||||||
清除十进制模式:CLD |
设置十进制模式:SED | ||||||||
0 -> D 标志:D = 0 |
1 -> D 标志:D = 1 | ||||||||
|
| ||||||||
清除中断禁用状态:CLI |
设置中断禁用状态:SEI | ||||||||
0 -> I 标志:I = 0 |
1 -> I 标志:I = 1 | ||||||||
|
| ||||||||
清除溢出位:CLV |
|||||||||
0 -> V 标志:V = 0 |
|||||||||
|
中断: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 |
- NES编程:任天堂娱乐系统使用6502的一个版本
- 65c02汇编,6502的衍生产品,用于许多家用电脑、电子游戏机和嵌入式系统。
- 超级任天堂编程:超级任天堂使用65c816,6502的衍生产品
- 苹果公司历史:早期苹果电脑都使用6502的某些版本
- 计算机历史/微型计算机的兴起
- X86反汇编/反汇编器和反编译器#反汇编8位CPU代码提到一些6502反汇编器
- 计算机编程/Hello world#累加器 + 索引寄存器机器:MOS技术6502,CBM内核,MOS汇编语法
- MOS 技术 6502,wikipedia.org
- Owad,Tom,“Apple I Replica Creation”,Syngress,2005。ISBN 193183640X
- 6502.org 6502微处理器资源,特别是教程和入门页面。
- 6502指令集,masswerk.at
- 6502系列CPU参考由Michael Steil撰写,pagetable.com
- NMOS 6502操作码由John Pickens等人撰写,6502.org
- 维基versity:学习 6502 汇编,wikiversity.org