CPU Instruction Reference
Last updated:
Skip this grid if it's your first time.
Implied (Imp) — 2 cycles
2 _ = bus.read(PC); do the op
Accumulator (A) — 2 cycles
2 _ = bus.read(PC); op on A // rmw on A, no bus access
Immediate (Imm) — 2 cycles
2 DL = bus.read(PC); PC++ // DL = operand
Zero Page (ZP) — read/write 3, rmw 5
2 ADL = bus.read(PC); PC++; ADH = 0
Access type: read
3 DL = bus.read(ADH:ADL)
Access type: write
3 bus.write(ADH:ADL, reg)
Access type: rmw
3 DL = bus.read(ADH:ADL)
4 bus.write(ADH:ADL, DL) // dummy: the value just read
5 bus.write(ADH:ADL, r) // the modified value
Zero Page,X (ZPX) — read/write 4, rmw 6
2 ADL = bus.read(PC); PC++; ADH = 0
3 _ = bus.read(ADH:ADL); ADL = ADL + X // wraps in ZP
Access type: read
4 DL = bus.read(ADH:ADL)
Access type: write
4 bus.write(ADH:ADL, reg)
Access type: rmw
4 DL = bus.read(ADH:ADL)
5 bus.write(ADH:ADL, DL) // dummy
6 bus.write(ADH:ADL, r)
Zero Page,Y (ZPY) — read/write 4
Same cycles as ZPX with Y, read/write only (no rmw).
Absolute (Abs) — read/write 4, rmw 6
2 ADL = bus.read(PC); PC++
3 ADH = bus.read(PC); PC++ // effective address now in ADH:ADL
Access type: read
4 DL = bus.read(ADH:ADL)
Access type: write
4 bus.write(ADH:ADL, reg)
Access type: rmw
4 DL = bus.read(ADH:ADL)
5 bus.write(ADH:ADL, DL) // dummy
6 bus.write(ADH:ADL, r)
Absolute,X (AbsX) — read 4–5, write 5, rmw 7
2 ADL = bus.read(PC); PC++
3 ADH = bus.read(PC); PC++; (ADL, CL) = ADL + X
4 DL = bus.read(ADH:ADL) // ADH not fixed for page cross yet
ADH = ADH + CL // no-op if CL == 0
Access type: read
if (CL == 0): done // 4 cyc
5 DL = bus.read(ADH:ADL) // 5 cyc
Access type: write
5 bus.write(ADH:ADL, reg)
Access type: rmw
5 DL = bus.read(ADH:ADL)
6 bus.write(ADH:ADL, DL) // dummy
7 bus.write(ADH:ADL, r)
Absolute,Y (AbsY) — read 4–5, write 5, rmw 7
Same cycles as AbsX with Y.
(Indirect,X) (IzX) — read/write 6, rmw 8
2 ADL = bus.read(PC); PC++; ADH = 0 // ADL = ZP base address
3 _ = bus.read(ADH:ADL); ADL = ADL + X // wraps in ZP
4 IAL = bus.read(ADH:ADL) // pointer low, parked
5 ADL = ADL + 1; ADH = bus.read(ADH:ADL); ADL = IAL // ADL wraps in ZP
Access type: read
6 DL = bus.read(ADH:ADL)
Access type: write
6 bus.write(ADH:ADL, reg)
Access type: rmw
6 DL = bus.read(ADH:ADL)
7 bus.write(ADH:ADL, DL) // dummy
8 bus.write(ADH:ADL, r)
(Indirect),Y (IzY) — read 5–6, write 6, rmw 8
2 ADL = bus.read(PC); PC++; ADH = 0 // ADL = ZP address
3 IAL = bus.read(ADH:ADL) // pointer low, parked
4 ADL = ADL + 1; ADH = bus.read(ADH:ADL); ADL = IAL // ADL wraps in ZP
5 (ADL, CL) = ADL + Y
DL = bus.read(ADH:ADL) // ADH not fixed for page cross yet
ADH = ADH + CL // no-op if CL == 0
Access type: read
if (CL == 0): done // 5 cyc
6 DL = bus.read(ADH:ADL) // 6 cyc
Access type: write
6 bus.write(ADH:ADL, reg)
Access type: rmw
6 DL = bus.read(ADH:ADL)
7 bus.write(ADH:ADL, DL) // dummy
8 bus.write(ADH:ADL, r)
Relative (Rel) — 2 / 3 / 4 cycles
2 DL = bus.read(PC); PC++ // DL = signed offset
if (branch not taken): done // 2 cyc
3 NEGATIVE = DL bit 7
(PCL, CL) = PCL + DL // treat DL as unsigned for the add
_ = bus.read(PCH:PCL)
if (NEGATIVE == CL): done // 3 cyc
4 PCH = PCH + (NEGATIVE ? -1 : +1)
_ = bus.read(PCH:PCL)
Indirect (Ind) — 5 cycles, JMP only
2 IAL = bus.read(PC); PC++ // pointer low, parked
3 ADH = bus.read(PC); PC++ // pointer high → ADH
4 PCL = bus.read(ADH:IAL) // target low byte
5 IAL = IAL + 1 // no carry into ADH — bug
PCH = bus.read(ADH:IAL) // target high byte
JMP ($02FF) reads the high byte from $0200, not $0300. The
increment of IAL at cycle 5 doesn't carry into ADH — classic 6502
bug, not fixed in the 2A03.
Special instructions
These don't decompose into mnemonic + mode; each has its own sequence.
JSR — jump to subroutine
Single opcode 20. 6 cycles.
2 ADL = bus.read(PC); PC++ // target low parked in ADL
3 _ = bus.read(01:SP) // internal operation
4 bus.write(01:SP, PCH); SP--
5 bus.write(01:SP, PCL); SP--
6 PCH = bus.read(PC); PCL = ADL
The address pushed is the PC of JSR's high-operand byte, not the next
instruction. RTS pops it and increments by 1, so the round trip
lands on the byte right after the JSR.
RTS — return from subroutine
Single opcode 60. 6 cycles.
2 _ = bus.read(PC)
3 _ = bus.read(01:SP)
4 SP++; PCL = bus.read(01:SP)
5 SP++; PCH = bus.read(01:SP)
6 PC = PCH:PCL; PC++
BRK — force interrupt
Single opcode 00. 7 cycles. Pushes PC and status, then jumps through
the IRQ/BRK vector at $FFFE:$FFFF.
2 _ = bus.read(PC); PC++ // padding byte: BRK skips the next byte
3 bus.write(01:SP, PCH); SP--
4 bus.write(01:SP, PCL); SP--
5 bus.write(01:SP, P | 0x30); SP-- // status pushed with B and bit 5 set
6 PCL = bus.read(FFFE); I = 1
7 PCH = bus.read(FFFF)
The hardware interrupt sequence (NMI/IRQ/RESET) runs these same cycles
but suppresses the PC++ and the B flag, and reads a different vector
($FFFA NMI, $FFFE IRQ, $FFFC RESET).
RTI — return from interrupt
Single opcode 40. 6 cycles. Pulls status, then PC — and unlike RTS,
no final PC++.
2 _ = bus.read(PC)
3 _ = bus.read(01:SP)
4 SP++; P = bus.read(01:SP) // B and bit 5 ignored on pull
5 SP++; PCL = bus.read(01:SP)
6 SP++; PCH = bus.read(01:SP)
PHA / PHP — push
PHA (48) pushes A; PHP (08) pushes the status byte. 3 cycles.
2 _ = bus.read(PC)
3 bus.write(01:SP, src); SP-- // src = A (PHA) or P | 0x30 (PHP)
PHP pushes with B and bit 5 set, same as BRK.
PLA / PLP — pull
PLA (68) pulls into A (sets N, Z); PLP (28) pulls the status
byte (B and bit 5 ignored). 4 cycles.
2 _ = bus.read(PC)
3 _ = bus.read(01:SP)
4 SP++; dst = bus.read(01:SP) // dst = A (PLA) or P (PLP)
KIL — jam
Opcodes 02 12 22 32 42 52 62 72 92 B2 D2 F2 (also called JAM/HLT).
These lock the CPU: it stops fetching and hangs until reset. No defined
cycle count. You'll never hit one in correct code; a well-behaved
emulator can treat it as a fatal error.
Mnemonics
LDA — load accumulator (read)
A := input. Flags: N - - - - - Z -.
LDX — load X (read)
X := input. Flags: N - - - - - Z -.
LDY — load Y (read)
Y := input. Flags: N - - - - - Z -.
STA — store accumulator (write)
output = A.
STX — store X (write)
output = X.
STY — store Y (write)
output = Y.
TAX — transfer A to X
X := A. Flags: N - - - - - Z -.
TAY — transfer A to Y
Y := A. Flags: N - - - - - Z -.
TXA — transfer X to A
A := X. Flags: N - - - - - Z -.
TYA — transfer Y to A
A := Y. Flags: N - - - - - Z -.
TSX — transfer SP to X
X := SP. Flags: N - - - - - Z -.
TXS — transfer X to SP
SP := X. The only transfer that doesn't set N/Z.
ADC — add with carry (read)
A := A + input + C. Flags: N V - - - - Z C. V is set when the signed
result doesn't fit (i.e., the result's sign disagrees with the
operands'). The 2A03 has decimal mode disabled, so D never affects the
result.
SBC — subtract with carry (read)
A := A - input - (1 - C). Flags: N V - - - - Z C. Same V rule as
ADC. Opcode EB is an undocumented duplicate of the documented E9.
CMP — compare accumulator (read)
Compares A with input — a subtract that sets flags only.
C := A >= input, Z := A == input, N := bit 7 of the difference.
Flags: N - - - - - Z C.
CPX — compare X (read)
Compares X with input, like CMP. Flags: N - - - - - Z C.
CPY — compare Y (read)
Compares Y with input, like CMP. Flags: N - - - - - Z C.
AND — logical AND (read)
A := A & input. Flags: N - - - - - Z -.
ORA — logical OR (read)
A := A | input. Flags: N - - - - - Z -.
EOR — logical exclusive OR (read)
A := A ^ input. Flags: N - - - - - Z -.
BIT — bit test (read)
Z := (A & input) == 0, N := input[7], V := input[6]. A is not
modified. Flags: N V - - - - Z -.
ASL — arithmetic shift left (rmw)
output = input << 1; C := input[7]. Flags: N - - - - - Z C. Has an
Accumulator form (0A).
LSR — logical shift right (rmw)
output = input >> 1; C := input[0]. Flags: N - - - - - Z C. N
always ends 0 (bit 7 shifts in as 0). Has an Accumulator form (4A).
ROL — rotate left (rmw)
output = (input << 1) | C; C := input[7]. Flags: N - - - - - Z C.
Has an Accumulator form (2A).
ROR — rotate right (rmw)
output = (input >> 1) | (C << 7); C := input[0]. Flags:
N - - - - - Z C. Has an Accumulator form (6A).
INC — increment memory (rmw)
output = input + 1. Flags: N - - - - - Z -. C is left untouched even
on wrap ($FF → $00); only ADC propagates carry.
DEC — decrement memory (rmw)
output = input - 1. Flags: N - - - - - Z -. C untouched, same as INC.
INX — increment X
X := X + 1. Flags: N - - - - - Z -.
INY — increment Y
Y := Y + 1. Flags: N - - - - - Z -.
DEX — decrement X
X := X - 1. Flags: N - - - - - Z -.
DEY — decrement Y
Y := Y - 1. Flags: N - - - - - Z -.
CLC / SEC — clear / set carry
C := 0 / C := 1. Flags: - - - - - - - C.
CLI / SEI — clear / set interrupt disable
I := 0 / I := 1. Flags: - - - - - I - -.
CLD / SED — clear / set decimal
D := 0 / D := 1. Flags: - - - - D - - -. The 2A03 ignores
D in arithmetic, but the flag itself still toggles and can be read back.
CLV — clear overflow
V := 0. Flags: - V - - - - - -. There is no SEV.
BPL / BMI — branch on sign
BPL taken when N == 0, BMI when N == 1. Relative.
BCC / BCS — branch on carry
BCC taken when C == 0, BCS when C == 1. Relative.
BNE / BEQ — branch on zero
BNE taken when Z == 0, BEQ when Z == 1. Relative.
BVC / BVS — branch on overflow
BVC taken when V == 0, BVS when V == 1. Relative.
JMP — unconditional jump
The operand bytes are the destination, loaded straight into PC;
there's no read or write at that address. So JMP Abs is 3 cycles
(fetch opcode, ADL, ADH→PC), not the 4 of an Abs read — it never
touches ADH:ADL. Indirect JMP has the page-boundary bug detailed in
the Ind mode section.
NOP — no operation
Does nothing but burn cycles. The documented form is EA (implied, 2
cycles). The undocumented forms come in an implied flavour (1A 3A 5A 7A DA FA, identical to EA) and operand-bearing flavours (immediate,
ZP, ZPX, Abs, AbsX) that compute an address and take an input they
discard.
SLO — shift left, OR (rmw, illegal)
output = input << 1; C := input[7]; then A := A | output. Flags:
N - - - - - Z C. = ASL + ORA.
RLA — rotate left, AND (rmw, illegal)
output = (input << 1) | C; C := input[7]; then A := A & output.
Flags: N - - - - - Z C. = ROL + AND.
SRE — shift right, EOR (rmw, illegal)
output = input >> 1; C := input[0]; then A := A ^ output. Flags:
N - - - - - Z C. = LSR + EOR.
RRA — rotate right, ADC (rmw, illegal)
output = (input >> 1) | (C << 7); C := input[0]; then
A := A + output + C (the new C from the rotate). Flags:
N V - - - - Z C. = ROR + ADC.
DCP — decrement, compare (rmw, illegal)
output = input - 1; then compare A - output. Flags: N - - - - - Z C.
= DEC + CMP.
ISC — increment, SBC (rmw, illegal)
output = input + 1; then A := A - output - (1 - C). Flags:
N V - - - - Z C. = INC + SBC. Also called ISB / INS.
LAX — load A and X (read, illegal)
A := X := input. Flags: N - - - - - Z -. = LDA + LDX. The
immediate form (AB) is unstable — see below.
SAX — store A AND X (write, illegal)
output = A & X. Neither A nor X changes.
ANC — AND, copy N to carry (read, illegal)
A := A & input; C := A[7]. Immediate only. Flags: N - - - - - Z C.
ALR — AND, shift right (read, illegal)
A := A & input; then A := A >> 1, C := old A[0]. Immediate only. Flags:
N - - - - - Z C. Also called ASR.
ARR — AND, rotate right (read, illegal)
A := A & input; then rotate A right, but V and C come from bits 6 and 5
of the result, not a normal rotate. Immediate only. Flags:
N V - - - - Z C. Check a hardware reference if you need the exact V/C.
AXS — (A AND X) minus input (read, illegal)
X := (A & X) - input, a compare-style subtract with no borrow.
Immediate only. Flags: N - - - - - Z C. Also called SBX.
XAA — magic AND (read, illegal, unstable)
A := (A | magic) & X & input, where magic is an unpredictable
constant. Immediate only. Flags: N - - - - - Z -. Avoid; real code
never uses it. Also called ANE.
SHA — store A AND X AND H+1 (write, illegal, unstable)
output = A & X & (ADH + 1). AbsY and IzY. The AND
with the high address byte makes it unstable across page crosses. Also
called AHX.
SHX — store X AND H+1 (write, illegal, unstable)
output = X & (ADH + 1). AbsY. Same instability as
SHA. Also called SXA.
SHY — store Y AND H+1 (write, illegal, unstable)
output = Y & (ADH + 1). AbsX. Same instability as
SHA. Also called SYA.
TAS — transfer A AND X to SP, store (write, illegal, unstable)
SP := A & X; then output = SP & (ADH + 1). AbsY.
Also called SHS / XAS.
LAS — load A, X, SP from input AND SP (read, illegal)
A := X := SP := input & SP. AbsY. Flags: N - - - - - Z -. Also called
LAR.