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