Building a 6502 Simulator in Python (Part 2)

Table of Contents

From a 12-instruction teaching CPU to a full MOS 6502 emulator, inspired by Ben Eater’s breadboard projects.

Why the 6502?

The MOS 6502 is the processor that defined home computing in the late 1970s and early 1980s. It ran the Apple II, the Commodore 64, the Atari 2600, and the Nintendo Entertainment System. It powered the first affordable personal computers and launched an industry.

What makes it perfect for a simulator project:

  1. Simple enough to understand completely. The 6502 has only 56 distinct instructions (with addressing mode variants giving 151 opcodes total). Compare that to a modern x86 with thousands of instructions. The 6502 is comprehensible by one person.

  2. Complex enough to be interesting. It has 13 addressing modes, a stack, interrupt handling, decimal arithmetic (BCD), and memory-mapped I/O. It’s a real architecture with real programs written for it.

  3. Ben Eater made it tangible. Ben Eater’s YouTube series shows a 6502 running on a breadboard, wired up with actual chips, an LCD display showing output, and LEDs blinking on the data bus. Watching physical signals flow through a real 6502 makes the architecture click in a way that documentation alone never does.

I want to build the software equivalent: a complete 6502 simulator with the same components Ben wires up on his breadboard (CPU, RAM, ROM, and an LCD display) but running in Python so I can program it, debug it, and extend it.

The Architecture

Registers

The 6502 has a minimal register set:

RegisterBitsPurpose
A8Accumulator, the main math register
X8Index register for array indexing
Y8Index register, secondary indexing
SP8Stack pointer, points into page $0100-$01FF
PC16Program counter, address of next instruction
P8Status register holding the CPU flags

The status register (P) packs 7 flags into one byte:

Bit:  7  6  5  4  3  2  1  0
Flag: N  V  -  B  D  I  Z  C

Memory Map

The 6502 addresses 64KB ($0000-$FFFF):

$0000-$00FF  Zero Page (fast access, shorter instructions)
$0100-$01FF  Stack (grows downward from $01FF)
$0200-$5FFF  RAM (general purpose)
$6000-$600F  I/O (LCD display, keyboard, etc.)
$8000-$FFFF  ROM (program code, loaded at startup)
$FFFA-$FFFB  NMI vector
$FFFC-$FFFD  Reset vector (where execution starts)
$FFFE-$FFFF  IRQ/BRK vector

The reset vector at $FFFC is key: when the CPU starts (or resets), it reads the 16-bit address at $FFFC-$FFFD and begins executing from there. This is how the boot process works. The ROM programmer stores their entry point address at $FFFC.

Addressing Modes

This is where the 6502 gets interesting. The same instruction (like LDA, “load accumulator”) has different opcodes depending on how you specify the operand:

ModeSyntaxExampleMeaning
Immediate#$nnLDA #$42Load the literal value $42
Zero Page$nnLDA $10Load from address $0010
Zero Page,X$nn,XLDA $10,XLoad from $0010 + X
Absolute$nnnnLDA $4000Load from address $4000
Absolute,X$nnnn,XLDA $4000,XLoad from $4000 + X
Absolute,Y$nnnn,YLDA $4000,YLoad from $4000 + Y
Indirect($nnnn)JMP ($FFFC)Jump to address stored at $FFFC
(Indirect,X)($nn,X)LDA ($20,X)Pointer from zero page + X
(Indirect),Y($nn),YLDA ($20),YPointer from zero page, then + Y

The indirect modes are what make the 6502 capable of complex data structures (arrays, linked lists, function pointers) despite having only three registers.

The Plan

The simulator is being built as a standalone Python project at professor314/6502-simulator. The components mirror what Ben Eater wires on his breadboard:

CPU Core

Memory System

LCD Display

Assembler

Debugger

From MM1 to 6502

The conceptual jump from my college MM1 to the real 6502:

FeatureMM16502
Registers8 general-purpose3 specific (A, X, Y) + SP, PC
Memory64 words65,536 bytes
Instructions1256 (151 with modes)
Instruction sizeFixed 12-bitVariable 1-3 bytes
Addressing modes113
StackNoneHardware stack at $0100-$01FF
InterruptsNoneNMI, IRQ, BRK
I/ONoneMemory-mapped
BCD arithmeticNoYes (decimal mode)

The core concept is identical though: fetch an instruction from memory, decode its fields, execute the operation, repeat. The MM1 taught me the pattern. The 6502 fills in the details of a real-world implementation.

What’s Coming

The 6502 simulator will be built in phases:

  1. CPU core: registers, memory, opcode table, instruction decoder
  2. Instruction execution: load/store, arithmetic, branches, stack operations
  3. Assembler: translate assembly source to machine code
  4. Peripherals: LCD display, memory-mapped I/O
  5. Debugger: step-through, breakpoints, memory view
  6. Visualization: terminal or matplotlib UI showing everything at once

The goal is to write a program in 6502 assembly, assemble it, load it into the simulator, and watch it execute instruction by instruction, with the virtual LCD showing “HELLO WORLD” just like Ben Eater’s breadboard computer does.

Source code: Physics-Modeling/6502-simulator on GitHub (in the 6502-simulator folder)

Follow along as I build it from the ground up.