The NES's 6502
@xromdev/cpu6502 v0.1.0 · MITThe 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.
- 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 $8000In memory — disassembled
click a line for a breakpoint- loop:
- A
- $00
- 0 · 00000000
- X
- $00
- 0 · 00000000
- Y
- $00
- 0 · 00000000
- S
- $FA
- 250 · 11111010
- PC
- $8000
- 7
- cycles
- 0
- instructions
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.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| $0000 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0010 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0020 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0030 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0040 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0050 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0060 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0070 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0080 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $0090 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00A0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00B0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00C0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00D0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00E0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
| $00F0 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
yellow — written by the last step
All 256 opcodes
151 documented, 105 undocumented — all of them run. The next instruction is lit.
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));