6502 CPU Simulator
Games & Classics
A 6502/65C02 processor, emulated on a Super Nintendo. Watch a scrolling
disassembly listing step through a program one instruction at a time — the bright line is the
instruction that just ran — while the matching ALU logic gate lights up below
(AND, OR, XOR as classic gate shapes; ADD, SUB, SHL, SHR, CMP as blocks). This is compiler
stress-test #102: the emulator's 256-entry switch(opcode) is lowered by
llvm-mos to a jump table on
the 65816, with the 6502's 16-bit program counter running as native 16-bit arithmetic.
Self-running — the disassembly scrolls and the ALU gate for each instruction lights up.
Click the screen to focus, then watch. Tab away and it pauses. (The listing steps deliberately — each instruction holds ~1.5 s so you can read it.)
What it is
The program running inside the simulated 6502 is the assembly equivalent of the
classic hello.c — set a colour register to green, store a sentinel byte, then
loop. The loop is arranged to fire every one of the eight ALU gate types in turn, so the
gate panel cycles through the whole set. The 6502 is the machine being simulated; the SNES
(a 65816) is just the hardware running the C emulator that draws it all.
// The simulated 6502 program — the assembly equivalent of hello.c:
// set a colour register to green, store a sentinel, then loop
// exercising every ALU gate type.
LDA #$E0 STA $F0 // colour lo (SNES_RGB(0,31,0) = $03E0)
LDA #$03 STA $F1 // colour hi
LDA #$42 STA $FF // sentinel = 0x42 ("hello.c: sentinel = 0x42")
LDX #$08 // loop counter
loop:
CLC ADC #$01 // ADD gate
AND $FF // AND gate
EOR $FF // XOR gate
ORA $FF // OR gate
ASL // SHL gate
LSR // SHR gate
CMP $FF // CMP gate
DEX // SUB gate
BNE loop
JMP restart
// The simulator itself is one 256-entry switch(opcode) dispatch —
// which the llvm-mos backend lowers to a jump table on the 65816.
Each of the 256 fetch-decode-execute steps decodes an opcode byte through the emulator's
opcode table, executes it (updating A/X/Y, the status flags and the 16-bit PC),
and returns which ALU gate fired. The disassembly window shows the two prior instructions,
the one just executed (bright), and the next one up.
Compiler stress-test #102 — Round 6: hardening the fixes
| Item | What it exercises |
|---|---|
| 256-entry opcode dispatch → jump table | The heart of the simulator is a single switch(opcode) over all 256 possible bytes. The llvm-mos 65816 backend lowers this dense switch to a real jump table — an indexed indirect jump (jmp (addr,x)) rather than a chain of compare-and-branch. The build gate confirms it: the disassembly contains 4 jump tables. This is the load-bearing codegen path the demo exercises. |
| 16-bit program counter on an 8-bit machine | The simulated 6502 is 8-bit (A, X, Y) but its program counter is 16-bit. On the 65816 under +mos-a16 that PC arithmetic is native 16-bit adds bracketed by rep/sep width switches — 97 of them across the simulator. The status-register flag math (N, V, Z, C from each 8-bit ALU result) is 8-bit bit-manipulation on top. |
| what you are watching | A scrolling disassembly listing rendered in the recovered Great Waldo Search 16×16 font (its built-in drop-shadow makes each line pop). The bright line is the instruction that just executed; below it, one of eight ALU gates lights up — AND, OR, XOR drawn as their classic logic-gate silhouettes, ADD/SUB/SHL/SHR/CMP as blocks. The register + flag strip tracks A, X, Y and the NV-BDIZC bits live. |
| 65C02 too | The disassembler decodes the full 65C02 superset — STZ, TRB, TSB, PHX/PHY/PLX/PLY, BRA and the zero-page-indirect (zp) addressing mode — not just the NMOS 6502. The embedded program uses plain 6502 opcodes; the extended table is exercised by the gate CRC folding across all 256 opcode bytes. |
| result: correct across every mode | The simulator runs 256 fetch-decode-execute steps and folds A, X, PC and the status register into a rolling CRC each step. host == default == +mos-a16 == +mos-xy16 on both MAME and bsnes-jg, -verify-machineinstrs clean. No far pointers. A clean positive — the jump-table dispatch and 16-bit PC arithmetic are correct, now guarded against regression. |
Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg and MAME.
Hit Verify fidelity to reproduce the build gate's WRAM assert
(gate CRC 0xAC8A — a fold of A, X, PC and the status register across 256 executed instructions) live in this tab.
No far pointers — host == default == +mos-a16 == +mos-xy16,
-verify clean.