CPU Instruction Guide
Last updated:
The full opcode table lives in the CPU Instruction Reference. This page explains how to read it.
Don't read it top to bottom because checkpoint #2 sends you there one opcode at a time.
There's 256 instructions, and they are identified by a onebyte opcode
(0x00 to 0xff). Each instruction has its own fixed behavior.
Most instructions are a unique pair of mnemonic, addressing mode:
Other instructions are mnemonic.
The opcode byte just encodes that pair. LDA $42
(opcode A5), zero page and LDA #$42 (opcode A9) are different instructions,
they have the same mnemonic (LDA), but different addressing modes.
The addressing mode describes how a specific value is obtained. The mnemonic usually describes what to do with that value.
Special instructions
There's a small group of unique instructions that don't have an addressing mode. They do unique work and I call these special:
JSR,RTS(subroutine call/return).BRK,RTI(interrupt entry/exit).PHA,PHP,PLA,PLP(stack push/pull).- The interrupt service sequence itself (not an instruction, but the
CPU runs a fixed cycle pattern on NMI/IRQ/RESET — it mirrors
BRK).
Operands
Instructions can have 0, 1 or 2 operands. The importance of this is just for matching nestest, because the format... (complete this)
Illegal instructions
Also known as undocumented instructions in official docs. Many (most?) games make use of these instructions, so they are included here. and the genuinely unstable ones carry a warning.
Flags
Flags are shown as N V - B D I Z C: a letter means the instruction sets
that bit, - means it's preserved. N and Z track the result — the
last value the instruction computes (the register it loads, the byte it
writes, or its final register update), which isn't always the output.
C and V, when touched, are spelled out in the entry.
Addressing modes
Addresses are written high:low when
assembled across cycles — typically ADH:ADL for the operand address
being built, or PCH:PCL for the program counter. Cycle 1 (opcode
fetch) is omitted from every mode below.
Access type. A memory mode doesn't pin down the cycles on its own: it works out where the operand is, but what happens at that address depends on the instruction's access type:
- read — fetch the operand from the address (
LDA,ADC,CMP, …). - write — store a register to the address (
STA,STX,STY). - read-modify-write (RMW) — read the operand, change it, write it back
(
INC,DEC,ASL,LSR,ROL,ROR).
Each instruction below is tagged with its access type. The memory modes share
their address-calculation cycles and then branch on it, shown as
Access type: read / write / rmw. Two things to notice in the branch:
- RMW writes twice — first the unmodified value (a dummy write the hardware
can't skip), then the modified one,
r. Each RMW instruction definesrin terms ofDL(e.g.ASL→r = DL << 1); see the instruction list. - Indexed writes and RMW don't get the page-cross shortcut. A read can finish a cycle early when the index stays on the page; write and RMW always pay the extra cycle, because they have to settle the address before touching it.
Implied, Accumulator, and Immediate have no memory operand to branch on, so
their single form is listed as-is — Accumulator is an RMW on A with no bus
access.
Opcodes in each group are listed ascending by value.
Dummy reads
Each cycle is written as assignments, Zig-style.
bus.read(addr) is a read bus cycle returning the byte at addr;
bus.write(addr, val) is a write cycle. _ = bus.read(addr) is a
dummy read: the CPU still drives the cycle on the bus, it just throws
the byte away (_ discards it, as in Zig). The hardware can't skip
these. // starts a comment.