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.

loading core…

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

ItemWhat it exercises
256-entry opcode dispatch → jump tableThe 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 machineThe 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 watchingA 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 tooThe 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 modeThe 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.