SearchA-ZC › c64js

c64js

2016 Open source · MIT Online

c64js is a Commodore 64 emulator written entirely in JavaScript by Mikael Borgbrant. It runs in-browser, rendering the VIC-II picture to an HTML5 canvas, and bundles the KERNAL, BASIC and character ROMs so it boots straight to the C64 BASIC prompt with no downloads. Its 6510 CPU, memory banking and RAM are plain JavaScript objects, so the whole machine can be stepped and inspected live.

Visit the project on GitHub ↗

Visit the official site ↗

Runs on: Web browser

c64js Online Emulator

Play c64js using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
C64 BASICc64jsCommodore 64 (breadbin)openOpen ⛶
Hello World democ64jsCommodore 64 (breadbin)openOpen ⛶

Machines emulated

Chips

Notes

Embedding

c64js is a set of plain-global JavaScript modules (no bundler). Vendor src/js/** and load them in dependency order, then drive the machine yourself instead of calling main.start() - its internal requestAnimationFrame loop cannot be paused or stepped from outside, which the debugger needs.

Boot. Install the three bundled ROMs, initialise the bus, CPU and VIC-II, point the VIC-II at a <canvas>, then run your own loop built on mos6510.process() (one 6510 instruction, returns its cycle count):

memoryManager.kernel    = new Rom(0xe000, romDump.kernel);
memoryManager.basic     = new Rom(0xa000, romDump.basic);
memoryManager.character = new Rom(0x0000, romDump.character);
cpuMemoryManager.init();
mos6510.init(cpuMemoryManager);
vic2.setScreenCanvas(canvas);
vic2.init(vicMemoryManager);
(function frame(){                          // ~19656 VIC sub-cycles per frame
  for (var i = 0; i < 19656; i++) {
    if (cpuCycles === 0) cpuCycles = mos6510.process();  // one instruction
    cpuCycles--; vic2.process(i, 0);
  }
  requestAnimationFrame(frame);
})();

The machine is plain objects. Everything the debugger needs is a live global, with no wasm heap to reach into:

MemberKindWhat it does
mos6510.process()methodExecute exactly one 6510 instruction; returns its cycle count. The single-step primitive.
mos6510.reset()methodReset the CPU (reloads PC from the $FFFC vector).
mos6510.registerfieldLive registers: .a .x .y .pc .sp and .status (a flags object with .getStatusByte() / .setStatusFlags(b)).
cpuMemoryManager.readByte(a)methodRead the banked CPU bus (sees KERNAL / BASIC / CHARGEN / I/O per memoryMode).
cpuMemoryManager.writeByte(a, v)methodWrite the CPU bus, poke memory live for cheats or a debugger.
memoryManager.ram.memoryfieldThe raw 64 KB RAM array, under the ROM/I/O banks.
vic2fieldThe VIC-II; setScreenCanvas + process(cycle, toggle) raster the picture into the canvas.
fileLoad.writeAutoRun()methodPrime the keyboard buffer with Shift+RUN/STOP; a KERNAL LOAD trap then injects a queued .prg and runs it.

Because the CPU, bus and RAM are ordinary JavaScript, the debugger single-steps with mos6510.process(), reads and writes registers straight off mos6510.register, and implements breakpoints and watchpoints as host-side checks around those calls, no changes to the emulator core.

Architecture

c64js is a readable, interpreted C64. Each chip is its own global object; the CPU steps one instruction at a time and the VIC-II is clocked in lockstep at sub-cycle granularity:

  • mos6510 - the 6510 CPU interpreter (a 6502 core plus the two on-chip I/O port lines that bank the memory map). process() runs one instruction.
  • cpuMemoryManager - the PLA banking logic: which of RAM, BASIC, KERNAL, CHARGEN or I/O answers each address, driven by memoryMode.
  • vic-ii-6569 - the VIC-II video chip, rastered into the canvas (text and standard modes; sprites and some bitmap modes are incomplete).
  • cia-6526 ×2, keyboard - timers, the keyboard matrix and joystick ports.
  • sid (jsSID) - the 6581 sound chip; optional, and silent without a user gesture.
  • romDump.basic / .kernel / .character - the three system ROMs, embedded as byte arrays, installed as Rom banks.

The emulator is a proof-of-concept graphically, but its CPU and memory are faithful and, crucially, fully exposed, which is exactly what makes it a good debugging target.