SearchA-Z0-9 › 6502.ts

6502.ts

2014 Open source · MIT Online

6502.ts is a full-featured Atari 2600 emulator written in TypeScript for both Node.js and the browser, including sound and CRT phosphor simulation. Its 'Stellerator' web front-end lets users import, manage and play VCS ROMs directly in the browser.

Visit the official site ↗

Runs on: Web browser

6502.ts Online Emulator

Play 6502.ts using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Flapping6502.tsAtari 2600openOpen ⛶
Flapping6502.tsAtari 2600 JropenOpen ⛶
Starfield demo6502.tsAtari 2600openOpen ⛶

Machines emulated

Chips

Notes

Embedding

6502.ts ships a browser front-end (Stellerator) whose convenience wrapper runs the machine inside a Web Worker, great for play, but the CPU is then only reachable as periodic snapshots. For a live debugger we vendor a tiny bundle of the machine core and drive the Board on the main thread ourselves.

Boot a cartridge. Build a Config, turn the ROM bytes into a cartridge, construct a Board, and wire its video output to a <canvas> through a VideoEndpoint (it hands you a ready-to-blit ImageData each frame):

const { Board, Config, CartridgeFactory, VideoEndpoint } = window.STELLERATOR;
const cfg  = Config.create({ tvMode: 0 /* ntsc */, enableAudio: false });
const cart = await new CartridgeFactory().createCartridge(romBytes);
const board = new Board(cfg, cart);
const video = new VideoEndpoint(board.getVideoOutput());
video.newFrame.addHandler(m => { ctx.putImageData(m.get(), 0, 0); m.release(); });
(function frame(){ board.tick(262 * 228); requestAnimationFrame(frame); })();

The machine is plain objects. Everything the host page needs hangs off the board:

MemberKindWhat it does
board.tick(n)methodAdvance n TIA colour clocks; the 6507 steps once every third. One NTSC frame is 262×228 clocks.
board.getCpu().statefieldLive 6507 registers: a x y s, p (the 16-bit program counter) and flags (the 8-bit P status register).
board.getCpu().executionStatefieldboot / fetch / execute; watching for the fetch boundary is how we single-step whole instructions.
board.getBus().peek(a)methodSide-effect-free read of the 6507 bus (masked to 13 bits: TIA, PIA RAM, cartridge).
board.getBus().poke(a, v)methodWrite the bus, poke memory live for cheats or a debugger.
board.getJoystick0()methodPlayer-1 joystick: getUp/Down/Left/Right/Fire(), each a switch with toggle(down).
board.getControlPanel()methodThe console switches: getResetButton(), getSelectSwitch(), difficulty and colour switches.
board.getVideoOutput()methodThe TIA as a video source (160 px wide, variable height); feed it to a VideoEndpoint.

Debugger integration

The plug-in (6502-ts-debug.js) reads the live board from window.EMU_BOOT and registers the 6507 with the shared debugger. Because the ROM is fetched asynchronously the board appears late, so the plug-in polls for EMU_BOOT before wiring up.

Mind the register names. On the 6502.ts CPU state.p is the program counter and state.flags is the P status byte, the opposite of the usual 6502 mnemonic. We map PC ← state.p and P ← state.flags and reuse the shared mos6502 disassembler:

pc: () => cpu.state.p & 0xffff,
registers: () => [
  { name: 'PC', value: cpu.state.p, width: 16, set: v => cpu.state.p = v & 0xffff },
  { name: 'P',  value: cpu.state.flags, width: 8, set: v => cpu.state.flags = v & 0xff },
  /* A, X, Y, SP and the N V B D I Z C flag bits */
]

Owning the clock. The boot script runs the frame loop itself, so it can pause, single-step and breakpoint. As tick(1) is one colour clock and the CPU only advances every third, single-stepping watches executionState: leave the current fetch, then run to the next one - exactly one instruction. Execution breakpoints are a PC set tested at each fetch; watchpoints wrap bus.write and pause when a watched address is stored. Memory windows read through peek() so auto-refresh never disturbs TIA/PIA state.

Architecture

6502.ts is a cycle-accurate Atari 2600 written in TypeScript. The Board ties the chips together and is clocked at the TIA colour-clock rate:

  • StateMachineCpu - a micro-stepped MOS 6507 (a 6502 with only 13 address pins, so its 8 KB space mirrors across the bus). Its state and executionState are plain fields.
  • Tia - the Television Interface Adaptor: it generates the picture line by line into a 160×N RGBA surface and the two audio channels. It is the video source behind getVideoOutput().
  • Pia (RIOT), 128 bytes of RAM, the interval timer and the I/O ports the joysticks and console switches read.
  • Cartridge* - one class per bank-switching scheme; CartridgeFactory sniffs the ROM and picks the right one (the bundled games are plain 4K).
  • Bus - decodes each 13-bit address to TIA ($00–$7F), PIA ($80 select) or cartridge (A12), with a side-effect-free peek/poke pair for debugging.

The whole machine is ordinary objects with no wasm heap, so the debugger single-steps the CPU, reads registers straight off state, and implements breakpoints and watchpoints as host-side checks around tick() - with no changes to the emulator core.