65c02 汇编
本书是 65c02 汇编语言的指南。本书将讲解 8 位 WDC 65c02 处理器的不同内存寻址模式和指令。
这是对 6502 汇编 书籍的编辑,增加了 65c02 上的新指令/模式。
语法在不同的汇编器之间会有所不同 - 本书将在整本书中使用以下语法
语法 | 进制 | 示例 |
---|---|---|
%00001111 |
二进制 | LDA #%0001
|
$FA |
十六进制 | LDA #$0E
|
123 |
十进制 | LDA #100
|
65C02 CPU 有一个 8 位 数据 总线,以及一个 16 位地址总线。所有 寄存器 都是 8 位的,除了 16 位的 程序计数器 (PC) 寄存器。因此 CPU 被认为是 8 位的。
地址总线是 16 位意味着 CPU 可以访问 2^16 个独立的字节内存,从地址 $0000 到地址 $FFFF,也就是 65536 字节 (64KB).
与外围设备(例如视频、音频、磁盘和游戏系统的控制器)的通信通常通过 内存映射 I/O 进行。
内存被划分为“页”,每页 256 个字节(在 8 位偏移量的范围内)。第 n 页是内存中的第 n 页,起始地址为 256*n,结束地址为 (256*(n+1))-1。例如,“零页”从地址 0 开始,到地址 255 结束。有关详细信息,请参阅下面的 内存布局。
不到一半的 65c02 CPU 操作码 处理存储在零页中的内存。存储在零页中的内存通常需要更短的时间来处理。
寄存器 | 大小(位) | 用途 |
---|---|---|
累加器 (A) | 8 | 用于对数据执行计算。 指令可以直接对累加器进行操作,而不是浪费 CPU 周期去访问内存 |
X 寄存器 (X) | 8 | 在某些 寻址模式 中用作索引 |
Y 寄存器 (Y) | 8 | 在某些 寻址模式 中用作索引 |
程序计数器 (PC) | 16 | 指向要执行的下一条指令的地址 |
堆栈指针 (SP) | 8 | 存储堆栈索引,下一个堆栈元素将插入到该索引中。 该位置的地址为 $0100 + SP 。SP 最初设置为 $FD |
状态 (SR) | 8 | 每一位代表一个状态标志。标志指示 CPU 的状态,或关于先前指令结果的信息。 有关每个标志的描述,请参阅下表 |
位 | 符号 | 名称 | 描述 |
---|---|---|---|
7 | N | 负 |
比较:如果寄存器的值小于输入值,则设置 |
6 | V | 溢出 |
算术运算:如果在加法或减法期间发生有符号溢出,则设置,即结果的符号与输入和累加器的符号不同 |
5 | - | (未用) | 始终设置为 |
4 | B[1] | 中断 | 如果 BRK 指令触发中断请求,则设置 |
3 | D | 十进制 | 十进制模式[2]:数学指令将把输入和输出视为十进制数。 例如 $09 + $01 = $10 |
2 | I | 中断禁用 | 设置后,将禁用中断 |
1 | Z | 零 |
比较:如果寄存器的值等于输入值,则设置 |
0 | C | 进位 | 进位/借位标志用于数学和循环操作 算术运算:如果在加法或减法期间发生无符号溢出,则设置,即结果小于初始值(或等于初始值,如果进位标志在进入时被设置) |
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 |
每个指令使用 13 种内存寻址模式之一,该模式决定指令的操作数。每个模式都提供了一个示例。
累加器被隐含地作为操作数,因此不需要指定地址。
示例
使用 ASL(算术左移)指令,没有操作数,累加器始终是被左移的值。
ASL
操作数是隐含的,因此不需要指定。
示例
这里隐含的操作数是 X(传输的源)和 A(传输的目的地)。
TXA
操作数直接用于执行计算。
示例
值 $22 被加载到累加器中。
LDA #$22
指定一个完整的 16 位地址,该地址处的字节用于执行计算。
示例
地址 $D010
处的值被加载到 X 寄存器中。
LDX $D010
单个字节指定内存第一页($00xx
)中的地址,也称为零页,该地址处的字节用于执行计算。
示例
地址 $0002
处的值被加载到 Y 寄存器中。
LDY $02
与隐含(i)基本相同。不同之处在于,这些指令执行堆栈操作;从堆栈中压入或弹出操作数。
指定的偏移量被添加到程序计数器(PC)中存储的当前地址。偏移量范围为 -128 到 +127。
示例
偏移量 $2D
被添加到程序计数器(例如 $C100
)中的地址。分支的目的地(如果执行)将是 $C12D
。
BPL $2D
存储在指定地址处的低字节序的两位字节值用于执行计算。仅由 JMP
指令使用。
示例
地址 $A001
和 $A002
被读取,分别返回 $FF
和 $00
。然后跳到地址 $00FF
。
JMP ($A001)
X
中的值被添加到指定的地址以获得一个总和地址。加载存储在总和地址(LSB)和总和地址加 1(MSB)的两个字节对处的低字节序地址,并在该地址处的值用于执行计算。仅由 JMP
指令使用。
示例
X
中的值 $06
被添加到 $EO15
,总和为 $EO1B
。读取地址 $E01B
和 $E01C
处的地址 $D010
。然后跳到地址 $D010
。
JMP ($E015,X)
X
中的值被添加到指定的地址以获得一个总和地址。总和地址处的值用于执行计算。
示例
X
中的值 $02
被添加到 $C001
,总和为 $C003
。地址 $C003
处的 $5A
值用于执行 _带进位的加法_(_ADC
_)操作。
ADC $C001,X
Y
中的值被添加到指定的地址以获得一个总和地址。总和地址处的值用于执行计算。
示例
Y
中的值 $03
被添加到 $F001
,总和为 $F004
。地址 $F004
处的 $EF
值被递增(_INC
_),并向 $F004
写回 $F0
。
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)的两个字节对处的低字节序地址,并在该地址处的值用于执行计算。
示例
X
中的值 $02
被添加到 $15
,总和为 $17
。地址 $0017
和 $0018
处的地址 $D010
将是累加器中 $0F
值存储的位置。
STA ($15,X)
加载存储在零页地址(LSB)和零页地址加 1(MSB)的两个字节对处的低字节序地址,并在该地址处的值用于执行计算。
示例
地址 $0015
和 $0016
处的地址 $D010
将是累加器中 $0F
值存储的位置。
STA ($15)
Y
中的值被添加到存储在指定地址(LSB)和指定地址加 1(MSB)的两个字节对处的低字节序地址处的地址。总和地址处的值用于执行计算。实际上,寻址模式完全重复了累加器寄存器的数字。
示例
Y
中的值 $03
被添加到地址 $002A
和 $002B
处的地址 $C235
,总和为 $C238
。然后,累加器与 $C238
处的 $2F
值进行异或运算。
EOR ($2A),Y
这些是 6502 处理器的指令,包括 ASCII 可视化、受影响标志的列表以及可接受寻址模式的操作码表。
加载和存储
[edit | edit source] 将内存加载到累加器: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 标志:无 | ||||||||||||||||||||||||||||||||||||||||||||
|
|
| ||||||||||||||||||||||||||||||||||||||||||||
将零存储到内存:STZ |
||||||||||||||||||||||||||||||||||||||||||||||
0 -> M 标志:无 |
||||||||||||||||||||||||||||||||||||||||||||||
|
算术
[edit | edit source] 将内存加到累加器带进位:ADC |
将内存减去累加器带借位:SBC | ||||||||||||||||||||||||||||||||||||||||
A + M + C -> A 标志:N,V,Z,C |
A - M - ~C -> A 标志:N,V,Z,C | ||||||||||||||||||||||||||||||||||||||||
|
|
增量和减量
[edit | edit source] 将内存加一:INC |
将索引 X 加一:INX |
将索引 Y 加一:INY | ||||||||||||||||||||
M + 1 -> M 标志:N,Z |
X + 1 -> X 标志:N,Z |
Y + 1 -> Y 标志:N,Z | ||||||||||||||||||||
|
|
| ||||||||||||||||||||
将内存减一:DEC |
将索引 X 减一:DEX |
将索引 Y 减一:DEY | ||||||||||||||||||||
M - 1 -> M 标志:N,Z |
X - 1 -> X 标志:N,Z |
Y - 1 -> Y 标志:N,Z | ||||||||||||||||||||
|
|
|
移位和旋转
[edit | edit source] 算术左移一位: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 | ||||||||||||||||||||||||
|
|
逻辑
[edit | edit source] 将内存与累加器 AND 操作:AND |
将内存与累加器 OR 操作:ORA |
将内存与累加器 XOR 操作:EOR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
A & M -> A 标志:N,Z |
A | M -> A 标志:N,Z |
A ^ M -> A 标志:N,Z | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
比较和测试位
[edit | edit source]负数(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 |
a,x | 3C |
# | 89 |
zp | 24 |
zp,x | 34 |
分支
[edit | edit source] 无条件分支:BRA |
|||||||||
分支如果 1 = 1 标志:无 |
|||||||||
|
|||||||||
分支如果进位清除:BCC |
分支如果进位设置:BCS | ||||||||
分支如果 C = 0 标志:无 |
分支如果 C = 1 标志:无 | ||||||||
|
| ||||||||
分支如果结果非零:BNE |
分支如果结果为零:BEQ | ||||||||
分支如果 Z = 0 标志:无 |
分支如果 Z = 1 标志:无 | ||||||||
|
| ||||||||
分支如果结果为正:BPL |
分支如果结果为负:BMI | ||||||||
分支如果 N = 0 标志:无 |
分支如果 N = 1 标志:无 | ||||||||
|
| ||||||||
分支如果溢出清除:BVC |
分支如果溢出设置:BVS | ||||||||
分支如果 V = 0 标志:无 |
分支如果 V = 1 标志:无 | ||||||||
|
|
与其他分支指令不同,BBR 和 BBS 具有两个操作数。用于设置或重置比较的 M 操作数始终是 zp 寻址模式。分支地址操作数是 r 寻址模式,与其他分支指令的含义相同。
分支如果位复位:BBR |
分支如果位设置:BBS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
分支如果 (M >> n) & 1 = 0 标志:无 |
分支如果 (M >> n) & 1 = 1 标志:无 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
传输
[edit | edit source] 将累加器传输到索引 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 标志:无 | ||||||||
|
|
堆栈
[edit | edit source] 将累加器压入堆栈:PHA |
将累加器从堆栈弹出:PLA | ||||||||
A -> S 标志:无 |
S -> A 标志:N,Z | ||||||||
|
| ||||||||
将索引 X 压入堆栈:PHX |
将索引 X 从堆栈弹出:PLX | ||||||||
X -> S 标志:无 |
S -> X 标志:无 | ||||||||
|
| ||||||||
将索引 Y 压入堆栈:PHY |
将索引 Y 从堆栈弹出:PLY | ||||||||
Y -> S 标志:无 |
S -> Y 标志:无 | ||||||||
|
| ||||||||
将处理器状态压入堆栈:PHP |
从堆栈中弹出处理器状态:PLP | ||||||||
P -> S 标志:无 |
S -> P 标志位:全部 | ||||||||
|
|
处理器状态存储为一个字节,其中从高位到低位的标志位为:NV--DIZC。
跳转到新位置:JMP
通过更改程序计数器的值跳转到新位置。
标志:无
寻址模式 | 操作码 |
---|---|
a | 4C |
(a) | 6C |
(a,x) | 7C |
跳转到新位置并保存返回地址:JSR
跳转到子程序
下一个指令之前的地址(PC - 1)被压入堆栈:首先是高字节,然后是低字节。由于堆栈向后增长,因此返回地址在内存中以小端序存储。
PC 设置为目标地址。
标志:无
寻址模式 | 操作码 |
---|---|
a | 20 |
从子程序返回:RTS
从子程序返回到使用 JSR
调用它的位置。
从堆栈中弹出返回地址(先弹出低字节,然后是高字节)。
返回地址加 1 并存储到 PC 中。
标志:无
寻址模式 | 操作码 |
---|---|
i | 60 |
从中断返回:RTI
从中断返回。
从堆栈中弹出 SR。
从堆栈中弹出 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 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
重置内存位:RMB |
设置内存位:SMB | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
M & ~(1 << n) -> M 标志:无
|
M | (1 << n) -> M 标志:无
|
中断:BRK
强制中断。
这是一个两字节指令,其中第二个字节被处理器忽略。第二个字节可以用作中断服务例程的参数。
标志位:B = 1, I = 1
寻址模式 | 操作码 |
---|---|
i | 00 |
无操作:NOP
无操作
标志:无
寻址模式 | 操作码 |
---|---|
i | EA |
等待中断:WAI
等待中断
标志:无
寻址模式 | 操作码 |
---|---|
i | CB |
停止模式:STP
停止模式
标志:无
寻址模式 | 操作码 |
---|---|
i | DB |
所有存在的操作码列表。操作码是“操作码”,是指令中的第一个字节。这个字节决定要执行的操作。多个汇编助记符(例如 ADC、BIT、JMP 等)对应于多个不同的操作码。每种寻址模式都对应一个操作码。例如,助记符 ASL 有多个操作码,每个操作码对应于 CPU 支持的一种寻址模式。
带星号 (*) 的操作码表示 6502 CPU 中不存在的操作码。
空白条目应与 NOP 指令(EA)的行为相同。
高位 nibble | 低位 nibble | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F | |
00 | BRK s |
ORA (zp,x) |
TSB zp * |
ORA zp |
ASL zp |
RMB0 zp * |
PHP s |
ORA # |
ASL A |
TSB a * |
ORA a |
ASL a |
BBR0 r * | |||
10 | BPL r |
ORA (zp),y |
ORA (zp) * |
TRB zp * |
ORA zp,x |
ASL zp,x |
RMB1 zp * |
CLC i |
ORA a,y |
INC A * |
TRB a * |
ORA a,x |
ASL a,x |
BBR1 r * | ||
20 | JSR a |
AND (zp,x) |
BIT zp |
AND zp |
ROL zp |
RMB2 zp * |
PLP s |
AND # |
ROL A |
BIT a |
AND a |
ROL a |
BBR2 r * | |||
30 | BMI r |
AND (zp),y |
AND (zp) * |
BIT zp,x |
AND zp,x |
ROL zp,x |
RMB3 zp * |
SEC i |
AND a,y |
DEC A * |
BIT a,x * |
AND a,x |
ROL a,x |
BBR3 r * | ||
40 | RTI s |
EOR (zp,x) |
EOR zp |
LSR zp |
RMB4 zp * |
PHA s |
EOR # |
LSR A |
JMP a |
EOR a |
LSR a |
BBR4 r * | ||||
50 | BVC r |
EOR (zp),y |
EOR (zp) * |
EOR zp,x |
LSR zp,x |
RMB5 zp * |
CLI i |
EOR a,y |
PHY s * |
EOR a,x |
LSR a,x |
BBR5 r * | ||||
60 | RTS s |
ADC (zp,x) |
STZ zp |
ADC zp |
ROR zp |
RMB6 zp * |
PLA s |
ADC # |
ROR A |
JMP (a) |
ADC a |
ROR a |
BBR6 r * | |||
70 | BVS r |
ADC (zp),y |
ADC (zp) * |
STZ zp,x |
ADC zp,x |
ROR zp,x |
RMB7 zp * |
SEI i |
ADC a,y |
PLY s * |
JMP (a,x) * |
ADC a,x |
ROR a,x |
BBR7 r * | ||
80 | BRA r * |
STA (zp,x) |
STY zp |
STA zp |
STX zp |
SMB0 zp * |
DEY i |
BIT # * |
TXA i |
STY a |
STA a |
STX a |
BBS0 r * | |||
90 | BCC r |
STA (zp),y |
STA (zp) * |
STY zp,x |
STA zp,x |
STX zp,y |
SMB1 zp * |
TYA i |
STA a,y |
TXS i |
STZ a * |
STA a,x |
STZ a,x * |
BBS1 r * | ||
A0 | LDY # |
LDA (zp,x) |
LDX # |
LDY zp |
LDA zp |
LDX zp |
SMB2 zp * |
TAY i |
LDA # |
TAX i |
LDY a |
LDA a |
LDX a |
BBS2 r * | ||
B0 | BCS r |
LDA (zp),y |
LDA (zp) * |
LDY zp,x |
LDA zp,x |
LDX zp,y |
SMB3 zp * |
CLV i |
LDA a,y |
TSX i |
LDY a,x |
LDA a,x |
LDX a,y |
BBS3 r * | ||
C0 | CPY # |
CMP (zp,x) |
CPY zp |
CMP zp |
DEC zp |
SMB4 zp * |
INY i |
CMP # |
DEX i |
WAI i * |
CPY a |
CMP a |
DEC a |
BBS4 r * | ||
D0 | BNE r |
CMP (zp),y |
CMP (zp) * |
CMP zp,x |
DEC zp,x |
SMB5 zp * |
CLD i |
CMP a,y |
PHX s * |
STP i * |
CMP a,x |
DEC a,x |
BBS5 r * | |||
E0 | CPX # |
SBC (zp,x) |
CPX zp |
SBC zp |
INC zp |
SMB6 zp * |
INX i |
SBC # |
NOP i |
CPX a |
SBC a |
INC a |
BBS6 r * | |||
F0 | BEQ r |
SBC (zp),y |
SBC (zp) * |
SBC zp,x |
INC zp,x |
SMB7 zp * |
SED i |
SBC a,y |
PLX s * |
SBC a,x |
INC a,x |
BBS7 r * |