Classroom Public page

Week 3: 6502 Assembly, The Friendliest CPU

1,740 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 curriculum-supplements/spk-101-younger-learner-tools.md 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.


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.