Classroom Glossary Public page

Week 3: 6502 Assembly, The Friendliest CPU

2,936 words

Meet the 6502. Learn its small instruction set. Step through real game code in Mesen's debugger. By the end of the week you can read 6502 assembly without a reference card open.


Theme

The MOS Technology 6502 is the chip inside the original Nintendo Entertainment System. It is also the chip inside the Apple II, the Commodore 64, the Atari 2600, the BBC Micro, and dozens of other early home computers. It was designed in 1975 by a small team that broke away from Motorola to build a CPU cheaper and simpler than what existed at the time. They succeeded. The 6502 is the friendliest 8-bit CPU ever made; it has roughly 56 instructions (compared to the 8080's 244 or the Z80's even larger set), three registers (the accumulator A, the index X, the index Y), and a 16-bit address space (64 KB).

This week you learn the 6502 instruction set well enough to read real NES game code. You use Mesen's built-in debugger to step through the game's PRG-ROM one instruction at a time. By the end of the week you can read assembly like LDA $80; STA $0200 and explain in plain English what the CPU just did.

This is the foundation for week 4 (ROM hacking): you cannot meaningfully modify a ROM until you can read the code that the ROM contains.

Reading list (~60 minutes)

  1. Easy 6502 by Nick Morgan at skilldrick.github.io/easy6502, the canonical "learn 6502 in an afternoon" tutorial. Read the whole thing. This is your single most important reading this week. Plan to spend ~45 minutes on it.
  2. 6502.org "Instruction Set Reference" at 6502.org/users/obelisk/6502/reference.html, a quick reference card you will keep open while doing this week's labs. Skim once; you will refer back to it dozens of times this week.
  3. NESdev wiki: "CPU" at wiki.nesdev.org/w/index.php/CPU, the NES-specific 6502 reference (the NES uses a 6502 variant called the Ricoh 2A03 / 2A07; it is a 6502 with the decimal mode removed and the audio hardware integrated). Read the introduction and the "Status flags" section.

Lecture outline (~2 hours)

Section 1: What is a CPU instruction?

  • A CPU instruction is a small action: load a value, store a value, add two numbers, jump to a different part of the program
  • Each 6502 instruction is 1, 2, or 3 bytes
  • The first byte is the opcode (what action to take). The remaining bytes (if any) are the operand (the data the action uses)
  • The CPU fetches the next instruction from memory, decodes the opcode, executes the action, and increments its program counter to the byte after the instruction. Repeat forever (until the program ends or jumps somewhere else)

Section 2: The 6502 register file

  • The 6502 has exactly 3 general-purpose registers: A (the accumulator), X (the X index), Y (the Y index)
  • Each is 8 bits wide (holds a value 0-255)
  • There is also a 16-bit program counter PC (which instruction comes next), an 8-bit stack pointer SP, and a 1-byte status register (the flags)
  • That is the entire visible state of the CPU. Three registers plus a few flags. Compare to a modern x86_64 CPU which has 16 64-bit general-purpose registers, 32 vector registers, and dozens of model-specific registers; the 6502 is tiny
  • This smallness is why we use the 6502 to teach assembly: there is almost nothing to memorize

Section 3: The instructions that matter most

  • LDA / LDX / LDY, load a value into the accumulator (or X, or Y). Example: LDA #$05 loads the number 5 into A
  • STA / STX / STY, store the accumulator (or X, or Y) to a memory address. Example: STA $0200 writes A's current value to memory at address 0x0200
  • TAX / TAY / TXA / TYA, transfer between registers. Example: TAX copies A into X
  • INX / INY / DEX / DEY, increment or decrement X or Y by 1
  • CMP / CPX / CPY, compare a register against a value; sets the zero and carry flags
  • BEQ / BNE / BCC / BCS, branch if equal (zero flag set), not equal, carry clear, carry set. The branch destinations are 1-byte signed offsets from the current address (so the branch can reach -128 to +127 bytes from itself)
  • JMP, unconditional jump to a 16-bit absolute address
  • JSR / RTS, jump to subroutine, return from subroutine. The 6502 has a small built-in stack at addresses 0x0100-0x01FF; JSR pushes the return address; RTS pops it
  • ADC / SBC, add with carry, subtract with carry. The carry flag matters because the 6502 cannot add 16-bit numbers in one instruction; you add the low bytes (with carry clear), then add the high bytes (with carry set if the low add overflowed)
  • AND / ORA / EOR, bitwise operations
  • ASL / LSR / ROL / ROR, shift left, shift right, rotate left, rotate right

That is roughly 25 of the 56 instructions. With those 25 you can read most of the code in a real NES game.

Section 4: Addressing modes

  • Immediate: LDA #$05, the operand IS the value
  • Zero-page: LDA $80, load from memory address 0x0080 (zero page is the first 256 bytes of RAM; faster to access)
  • Absolute: LDA $0200, load from any 16-bit address
  • Indexed: LDA $0200,X, load from address (0x0200 + X)
  • Indirect: JMP ($FFFE), jump to the address stored at 0xFFFE
  • The same opcode (LDA) has different machine-code bytes for different addressing modes. Example: LDA #$05 is bytes A9 05; LDA $80 is bytes A5 80; LDA $0200 is bytes AD 00 02

Section 5: How NES games actually use the 6502

  • NES games are real-time: the game runs once per frame (60 times per second on NTSC)
  • The PPU triggers an interrupt at the start of each frame (the "NMI" interrupt)
  • The game's NMI handler reads the controller, updates positions, decides what happens next frame, writes new sprite data to the PPU, then returns
  • Between NMI calls, the main game loop runs whatever logic does not need to happen exactly at the frame boundary
  • A simple NES game's CPU code is maybe 8-16 KB of 6502 assembly, organized as a main loop and an NMI handler

Labs (~4 hours)

Two labs this week.

Lab 3.1: Easy 6502 hands-on (labs/lab-3-1-easy-6502.md)

  • Goal: Work through the Easy 6502 tutorial's exercises in the in-browser simulator. By the end you have written a small 6502 program that draws a colored pixel to the simulator's screen
  • Time: ~90 minutes
  • Artifact: your final Easy 6502 program saved as a .txt file; a screenshot of it running

Lab 3.2: Disassemble a real ROM in Mesen (labs/lab-3-2-mesen-debugger.md)

  • Goal: Load your homebrew ROM in Mesen; open the built-in debugger; find the reset vector (the address the CPU jumps to when the game boots); read the first 30-50 instructions of the game; write a plain-English description of what those instructions do
  • Time: ~90 minutes
  • Artifact: ~200 words of plain English explaining the boot sequence of your homebrew ROM, written in your lab journal

Independent practice (~2.5 hours)

Pick any one or two:

  1. Try the 8bitworkshop sandbox. Open https://virtuscyberacademy.org/8bitworkshop/ in your browser. It is a complete in-browser NES development environment with a code editor, assembler, and emulator. Try modifying one of the example projects and running it.
  2. Read another simple homebrew ROM in Mesen's debugger. Pick a different homebrew ROM than your week 1 / week 2 ROM. Find its reset vector. Read its first 50 instructions. Compare the boot sequences.
  3. Practice reading the status flags. Set a breakpoint partway through your homebrew ROM. When it hits, look at the status flags display in Mesen's debugger. The flags are N (negative), V (overflow), B (break), D (decimal, always 0 on NES), I (interrupt disable), Z (zero), C (carry). Try predicting which flags will be set after the next instruction; step the instruction; see if you were right.
  4. Hand-assemble a small program. Pick 5-10 lines of 6502 assembly. Without using an assembler, look up the opcode bytes in the 6502.org reference and write the program out as raw bytes. Verify your hand-assembly by typing those bytes into a hex editor and disassembling them in 8bitworkshop.

Reflection prompts (~30 minutes)

  1. The 6502 has 3 general-purpose registers. A modern x86_64 CPU has 16. What do you think the 6502 designers gave up by choosing 3? What did they gain?
  2. The same opcode mnemonic (LDA) has different machine-code bytes for different addressing modes. Why do you think the CPU designers chose to encode the addressing mode in the opcode byte rather than in a separate "addressing mode" byte?
  3. Easy 6502 calls the 6502 "the friendliest 8-bit CPU." Now that you have written a few programs, do you agree? What about it was friendly? What was hard?
  4. You used Mesen's debugger to step through a real game's first 50 instructions. What was the most surprising thing you saw?
  5. Looking ahead to week 4 (ROM hacking): now that you can read 6502 assembly, what kind of change to a ROM do you think would be easy to make? What would be hard?

Visual Helpers

The addressing modes section is the single most common place where students get confused this week. Two instructions that look almost identical (LDA #$01 and LDA $01) mean very different things, and nothing about the instruction tells you which mode applies until you look for the #.

Mailbox metaphor for addresses: Picture 256 mailboxes in a row, each with a number on the front (its address) and a slip of paper inside (its value). LDA $01 says "go open mailbox 1 and read what is inside." LDA #$01 says "the number 1 is the value right here -- do not open any mailbox." See the Mailbox metaphor section in the cognitive tools supplement for a full diagram and worked examples.

Addressing Mode Flowchart: The supplement also has a decision tree you can draw on paper or print. The first question is always: does the operand start with #? Yes means literal value. No means address. The tree then branches into Zero Page, Absolute, Indexed, and Indirect from there. Having this on paper next to you during Lab 3.2 will cut your lookup time in half.

Bidirectional 6502 encoder / decoder

The two-panel tool below ties the two halves of this week together. Type 6502 assembly into the left panel and the right panel shows the opcode bytes that an assembler would emit. Type bytes into the right panel and the left panel shows the assembly those bytes decode to. Hover any byte to highlight its matching mnemonic; hover any mnemonic to highlight the bytes it emits. The tool covers the full standard 56-instruction set across all 13 6502 addressing modes; illegal and undocumented opcodes are out of scope for SPK-101.

Use this when you are doing Lab 3.2 (Mesen debugger) and want to confirm what an instruction in PRG-ROM actually means without flipping back to the reference card. Pick a worked example from the dropdown to see all 13 addressing modes in one pass; type your own asm or bytes in either panel to drive the round trip in real time.

Scene A: pick a worked example

The dropdown above the panels seeds 13 example instructions covering every addressing mode in the standard 6502 set. Pick one, watch the panels animate the round trip. Notice how the byte count varies: implied and accumulator are 1 byte, immediate and zero-page and the indirect-zp variants and relative are 2 bytes, absolute and indirect-absolute are 3 bytes. The first byte is always the opcode; subsequent bytes are the operand in little-endian order (low byte first when the operand is 16 bits).

Scene B: free-input encode

Edit the asm panel directly. Each line you type encodes immediately. The right panel updates in real time and the byte panel below it shows the contiguous byte stream that the assembler would emit. Errors (unknown mnemonic, illegal mode for that mnemonic, malformed operand) surface below the asm panel with a one-sentence diagnostic. Try writing LDA $80,Y: the encoder rejects it because LDA does not support zero-page-Y mode. Try STA #$00: rejected because immediate is a literal value and STA only writes to addresses. Mistakes get caught the moment you type them.

Scene C: free-input decode + predict-then-verify

Edit the byte panel. Each well-formed instruction (1, 2, or 3 bytes) decodes immediately and the asm panel updates to match. Illegal or undocumented opcodes halt the decoder and the leftover bytes show in red so you see exactly where the disassembler gave up. Below, the predict-then-verify form shows a byte sequence and asks you to pick the mnemonic and addressing mode before you check. Wrong picks render the correct answer with a one-sentence prose reason; right picks confirm with the canonical mode label.

Predict the instruction these bytes decode to:

Puzzle 1 of 13

Illegal Opcodes (Forward-stretch)

The standard 6502 has 56 mnemonics. The actual silicon decoder accepts roughly 105 more byte values, none of which MOS Technology documented. These are the illegal opcodes. Most of them either produce undefined behavior or corrupt CPU state in ways that vary by die revision, which is why the standard catalog above leaves them out. A small subset is stable enough that real NES games and demoscene productions use them, usually to save a byte or a cycle in a tight loop.

Toggling the catalog below feeds the illegal subset into the encoder + disassembler above. After flipping it on, try typing LAX $00 in the asm panel (load A and X with the same byte), or paste A7 00 into the byte panel (the disassembler decodes it to LAX zero-page). Toggle it back off and the same input produces a friendly rejection: the standard 56 do not include LAX.

Safety note: the catalog includes only the 12 stable illegal-opcode families (LAX, SAX, ANC, ALR, ARR, SBX, SLO, RLA, SRE, RRA, DCP, ISC). The genuinely-undefined ones (KIL, SHA, SHX, SHY, TAS, LAS, XAA) are intentionally excluded because their behavior varies by silicon batch and including them would teach you the wrong lesson about what "the 6502 does."

Illegal opcode catalog disabled. Standard 56 instructions only.


What comes next

Week 4 is the practical heart of the course. You take the skills from weeks 1-3 (running ROMs in an emulator, reading bytes in a hex editor, reading 6502 assembly) and make actual changes to a real ROM. Four labs that week: palette swap (change the color of something), text swap (change the title screen text), sprite swap (change what a sprite looks like), and level-byte tweak (change something about a level). By the end of week 4 you have your first "I made this" artifact: a modified ROM that runs in Mesen and shows your changes.


All technical terms used here are in the glossary.