SearchA-ZC › cpcjs

cpcjs

2013 Open source · GPLv3 Online

cpcjs is the Amstrad CPC 464 emulator "Roland", written entirely in JavaScript by Antonio Villena. It runs in-browser, rendering the Gate Array picture to an HTML5 canvas, and bundles the CPC 464 firmware so it boots straight to the Locomotive BASIC "Ready" prompt with no downloads. Its Zilog Z80 CPU, paged memory and RAM are plain JavaScript globals, so the whole machine can be stepped and inspected live.

Visit the project on SourceForge ↗

Visit the official site ↗

Runs on: Web browser

cpcjs Online Emulator

Play cpcjs using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Amstrad CPC 464 BASICcpcjsAmstrad CPC 464greyOpen ⛶

Machines emulated

Chips

Notes

Embedding

cpcjs is the pure-JavaScript Roland Amstrad CPC emulator (project EmuScriptoria, GPLv3). It is a set of plain-global scripts with no bundler: vendor them and load them in the project's own order, the Z80 core, then the AY, then the CPC machine, then the 464 specifics, then the video/init unit.

Boot. The core reads a single initialisation blob from the global string emul: a splash prefix, then the 32 KB firmware ROM (OS + Locomotive BASIC) at offset 0x30045, then a 64 KB RAM image. Fetch that blob, publish it as emul, then call init() - it installs the ROM into rom[0]/rom[1], fills RAM, points the Z80 at PC=0 and the machine boots the firmware to the BASIC Ready prompt:

var by= new Uint8Array(arrayBuffer), s= '';
for (var i= 0; i < by.length; i++) s+= String.fromCharCode(by[i]);
window.emul= s;                 // the core reads emul.charCodeAt(0x30045 + n)
init();                         // loads ROM+RAM, resets the Z80, boots to BASIC

Because init() starts its own uncontrollable loop (setInterval(run,20) or a Web-Audio onaudioprocess callback), we clear it immediately and instead call run() ourselves from requestAnimationFrame, which is what lets the debugger pause, single-step and breakpoint the machine.

The machine is plain globals. Everything the debugger needs is a live global. There is no wasm heap to reach into:

MemberKindWhat it does
g[m[pc>>14&3][pc++&16383]]()exprExecute exactly one Z80 instruction: fetch the opcode through the paged bus and dispatch it. The single-step primitive.
run()methodRun one whole CPC video frame (scanline-timed, firing the two raster interrupts).
pc, sp, a, b, c, d, e, h, lfieldsThe live Z80 registers. IX = xl|xh<<8, IY = yl|yh<<8; i, r, iff, im too.
f() / setf(v)methodsRead / write the flags byte (the core keeps flags lazily in fa,fb,fr,ff).
m[a>>14&3][a&16383]fieldThe banked CPU bus: four 16 KB pages, each ROM or RAM per the Gate Array.
mw[], rom[0], rom[1]fieldsThe raw 64 KB RAM, and the OS (lower) and BASIC (upper) ROMs.
z80interrupt()methodDeliver a maskable interrupt (the CRTC fires it every 52 scanlines).

Debugger integration

The cpcjs-debug.js plug-in reads the live emulator from window.EMU_BOOT and calls EmuKit.defineMachine. Single-stepping is just the opcode dispatch g[m[pc>>14&3][pc++&16383]](); the register set is read straight off the globals and written back through them (flags via f()/setf()); the disassembler is the shared z80 decoder.

Breakpoints and watchpoints. Our loop replicates run()'s scanline schedule but checks the program counter against a breakpoint Set before every instruction, and (for watchpoints) compares the watched RAM cells after each instruction, pausing the moment either fires. Memory reads for the hex/disasm views go through m[] directly, which is side-effect-free on the CPC because all I/O is port-mapped rather than memory-mapped.

function stepOne(){ r++; g[m[pc>>14&3][pc++&16383]](); }   // one Z80 instruction

Architecture

Roland is a faithful, readable CPC written entirely in JavaScript. Each chip is plain code driven from one scanline-timed frame loop:

  • z80pc.js - the Zilog Z80 core: a flat dispatch array g[] covering the base page and the CB / ED / DD (IX) / FD (IY) / DDCB / FDCB sub-tables, with CPC instruction timing.
  • jcpc.js - the Gate Array and CRTC 6845: run() clocks the Z80 per scanline and fires the raster interrupt; the Gate Array selects the screen mode, palette and ROM/RAM banking.
  • out464s0.js - the video look-up tables and paintScreen(), which rasters the CPC's mode 0/1/2 bitmap into the canvas, plus init().
  • j464.js - the CPC 464: the 8255 PPI ports, the keyboard matrix, and SNA/TAP snapshot handling.
  • ay.js - the AY-3-8912 sound chip (silent until a user gesture resumes Web-Audio).
  • rom[0] / rom[1] - the 464 firmware: the OS ROM and Locomotive BASIC 1.0, installed from the initialisation blob.

The CPU, bus and RAM are ordinary JavaScript objects, so the debugger single-steps by calling the opcode dispatch, reads and writes registers straight off the globals, and implements breakpoints and watchpoints as host-side checks around those calls, with no change to the emulator core.