c64js
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.
Runs on: Web browser
c64js Online Emulator
Play c64js using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| C64 BASIC | c64js | Commodore 64 (breadbin) | open | Open ⛶ | |
| Hello World demo | c64js | Commodore 64 (breadbin) | open | Open ⛶ |
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:
| Member | Kind | What it does |
|---|---|---|
mos6510.process() | method | Execute exactly one 6510 instruction; returns its cycle count. The single-step primitive. |
mos6510.reset() | method | Reset the CPU (reloads PC from the $FFFC vector). |
mos6510.register | field | Live registers: .a .x .y .pc .sp and .status (a flags object with .getStatusByte() / .setStatusFlags(b)). |
cpuMemoryManager.readByte(a) | method | Read the banked CPU bus (sees KERNAL / BASIC / CHARGEN / I/O per memoryMode). |
cpuMemoryManager.writeByte(a, v) | method | Write the CPU bus, poke memory live for cheats or a debugger. |
memoryManager.ram.memory | field | The raw 64 KB RAM array, under the ROM/I/O banks. |
vic2 | field | The VIC-II; setScreenCanvas + process(cycle, toggle) raster the picture into the canvas. |
fileLoad.writeAutoRun() | method | Prime 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 bymemoryMode.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 asRombanks.
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.