The NES's 6502

@xromdev/cpu6502 v0.1.0 · MIT

The processor inside every NES, in TypeScript: all 256 opcodes — the undocumented ones games use too — with the cycle count of every instruction, so a caller knows when inside a frame each write happened. Below, it runs in your browser: write a little assembly, or take one of the programs, and step through it one instruction at a time.

npm install @xromdev/cpu6502npm ↗GitHub ↗README ↗NSF player →
opcodes, the undocumented 105 too
256
opcodes, the undocumented 105 too
gzipped, no dependencies
~4 KB
gzipped, no dependencies
a real NES, on a laptop
~60×
a real NES, on a laptop
opcodes checked against SingleStepTests
244
opcodes checked against SingleStepTests

The smallest loop there is: X from 10 to 0. Watch Z set when X reaches zero, and the branch stop being taken.

Source — edit it

6 bytes at $8000

In memory — disassembled

click a line for a breakpoint
  1. loop:
Run at
A
$00
0 · 00000000
X
$00
0 · 00000000
Y
$00
0 · 00000000
S
$FA
250 · 11111010
PC
$8000
Flags — P = $24
N0V0-1B0D0I1Z0C0
7
cycles
0
instructions
Next
LDX #$0A
$A2 · immediate · 2 bytes · 2 cycles

A real NES runs 1,789,773 cycles a second.

Memory

Zero page: the fastest RAM to reach — one-byte addresses, and pointers for ($nn),Y.

0123456789ABCDEF
$000000000000000000000000000000000000
$001000000000000000000000000000000000
$002000000000000000000000000000000000
$003000000000000000000000000000000000
$004000000000000000000000000000000000
$005000000000000000000000000000000000
$006000000000000000000000000000000000
$007000000000000000000000000000000000
$008000000000000000000000000000000000
$009000000000000000000000000000000000
$00A000000000000000000000000000000000
$00B000000000000000000000000000000000
$00C000000000000000000000000000000000
$00D000000000000000000000000000000000
$00E000000000000000000000000000000000
$00F000000000000000000000000000000000

yellow — written by the last step

All 256 opcodes

151 documented, 105 undocumented — all of them run. The next instruction is lit.

BRKORAJAMSLONOPORAASLSLOPHPORAASLANCNOPORAASLSLOBPLORAJAMSLONOPORAASLSLOCLCORANOPSLONOPORAASLSLOJSRANDJAMRLABITANDROLRLAPLPANDROLANCBITANDROLRLABMIANDJAMRLANOPANDROLRLASECANDNOPRLANOPANDROLRLARTIEORJAMSRENOPEORLSRSREPHAEORLSRALRJMPEORLSRSREBVCEORJAMSRENOPEORLSRSRECLIEORNOPSRENOPEORLSRSRERTSADCJAMRRANOPADCRORRRAPLAADCRORARRJMPADCRORRRABVSADCJAMRRANOPADCRORRRASEIADCNOPRRANOPADCRORRRANOPSTANOPSAXSTYSTASTXSAXDEYNOPTXAANESTYSTASTXSAXBCCSTAJAMSHASTYSTASTXSAXTYASTATXSTASSHYSTASHXSHALDYLDALDXLAXLDYLDALDXLAXTAYLDATAXLXALDYLDALDXLAXBCSLDAJAMLAXLDYLDALDXLAXCLVLDATSXLASLDYLDALDXLAXCPYCMPNOPDCPCPYCMPDECDCPINYCMPDEXSBXCPYCMPDECDCPBNECMPJAMDCPNOPCMPDECDCPCLDCMPNOPDCPNOPCMPDECDCPCPXSBCNOPISCCPXSBCINCISCINXSBCNOPSBCCPXSBCINCISCBEQSBCJAMISCNOPSBCINCISCSEDSBCNOPISCNOPSBCINCISC

How fast, here

Half a second of a tight loop — loads, adds, stores, shifts, branches — on the library, in your browser.

Use it

import { Cpu6502, disassemble } from '@xromdev/cpu6502';

const memory = new Uint8Array(0x10000);
memory.set([0xa2, 0x0a, 0xca, 0xd0, 0xfd, 0x00], 0x8000); // LDX #10 / DEX / BNE / BRK
memory[0xfffc] = 0x00;
memory[0xfffd] = 0x80; // reset vector → $8000

// The bus is yours: memory-mapped hardware is a read or a write that does something.
const cpu = new Cpu6502({
  read: (address) => memory[address],
  write: (address, value) => { memory[address] = value; },
});
cpu.reset();
while (memory[cpu.pc] !== 0x00) cpu.step(); // step() returns the cycles it took
console.log(cpu.x, cpu.cycles);

console.log(disassemble(memory.subarray(0x8000, 0x8006), 0x8000));