SearchA-ZP › PCW Joyce

PCW Joyce

1985 Open source · MIT System ROMs · grey Online

PCW Joyce is a CP/M system for the Amstrad PCW that runs in the browser and boots straight to the A> console prompt. The Amstrad PCW is a Zilog Z80 computer that boots CP/M, so this build runs a real Z80 CPU core (Molly Howell's Z80.js) and loads the genuine Digital Research Console Command Processor as real Z80 code. An original JavaScript BDOS and BIOS host the CCP over a small RAM disk, and the console is drawn to a green-phosphor canvas text grid like the PCW's own monitor. Because the CCP runs as real Z80 code and calls the BDOS through CALL 5, the whole operating system single-steps with the full Z80 register file live.

Visit the Z80.js CPU core on GitHub ↗

Visit the official site ↗

Runs on: Web browser

PCW Joyce Online Emulator

Play PCW Joyce using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Amstrad PCW CP/MPCW JoyceAmstrad PCW 8256Digital Research CP/MgreyOpen ⛶

Machine

Operating system

Chips

Notes

Embedding

The Amstrad PCW is a Z80 machine that boots CP/M, so this build is two independent pieces: a vendored Zilog Z80 CPU core (z80-core.js, Molly Howell's Z80.js) and an original JavaScript CP/M environment (pcw-machine.js). CP/M itself is the genuine Digital Research Console Command Processor, loaded as a Z80 binary at 0xF400; the BDOS and BIOS are emulated in JavaScript, RunCPM-style, by trapping the well-known CP/M entry addresses. Because the CCP is real Z80 code and calls the BDOS through the CALL 5 vector, the whole operating system is visible to the debugger.

Boot. Install the page-zero vectors, the BDOS serial number the CCP validates, and the BIOS jump table; load the CCP; then run your own loop over cpu.run_instruction() (one Z80 instruction) that intercepts the trap addresses by program counter:

var cpu = new Z80({ mem_read, mem_write, io_read, io_write });  // vendored Z80 core
installLow();                              // 0x0005 JMP BDOS, serial @0xFC00, BIOS @0xFE00
loadCCP(0xF400);                         // genuine DR CCP, run as real Z80 code
cpu.setPC(0xF400);
(function frame(){
  for (var i = 0; i < BUDGET; i++) {
    var pc = cpu.getPC();
    if (pc === BDOS_TRAP) { bdos(); continue; }   // handle in JS, then RET
    if (isBiosTrap(pc)) { bios(pc); continue; }
    cpu.run_instruction();                   // one Z80 instruction
  }
  requestAnimationFrame(frame);
})();

Reading and writing Z80 state. Z80.js keeps its registers in a closure and exposes them through getState() / setState(). The trap loop needs the program counter every instruction, so the only change to the vendored core is four one-line accessors added to its public object, which read the closure variables without cloning the whole register file:

MemberKindWhat it does
cpu.run_instruction()methodExecute exactly one Z80 instruction; returns its cycle count. The single-step primitive.
cpu.getPC() / cpu.setPC(v)addedCheap live program counter, for the trap loop and single step. Matching getSP / setSP too.
cpu.getState() / cpu.setState(s)methodThe full register file (A B C D E H L, the shadow set, IX IY I R SP PC, both flag sets and the interrupt mode) as a plain object. Read for the register window, write to poke a register.
mem_read(a) / mem_write(a,v)methodThe 64 KB address space the CPU sees, a plain Uint8Array you can poke live.
bdos() / bios(n)trapJavaScript implementations of the CP/M system calls, reached by intercepting the CCP's CALL 5 and BIOS-vector addresses.

Because the CPU and memory are ordinary JavaScript, the debugger single-steps with run_instruction(), reads and writes registers straight off the core, and implements breakpoints and watchpoints as host-side checks around the loop, with no change to the CP/M code itself.

Debugger integration

The plug-in (pcw-debug.js) describes the machine to the shared debugger and nothing more:

  • Registers are the full Z80 set, read live each refresh and written back: AF BC DE HL, the index registers IX IY, the alternate set AF' BC' DE' HL', I R and the interrupt mode, plus PC SP and the S Z H P/V N C flags. A small facade turns the core's getState / setState into 16-bit pair accessors.
  • Disassembly uses the shared z80 decoder, so the CCP, the BIOS traps and any transient program render as proper Z80 mnemonics.
  • Memory is the full 64 KB, read side-effect-free straight from the RAM array, so you can watch the CCP, the TPA and a running .COM at once.
  • Single step is one run_instruction(); breakpoints and watchpoints are host-side checks in the boot loop. Because console input parks the CPU at the BDOS trap, stepping at the A> prompt does nothing until you type. Step through a transient program such as HELLO or TEST instead.

Architecture

The Amstrad PCW 8256 (1985), sold as "Joyce", is a Z80 word-processing computer bundled with a green monitor, a printer and CP/M. This build keeps the CPU and the operating system honest, and approximates the rest:

  • CPU is a genuine Zilog Z80 interpreter, so the register file the debugger shows (index registers, the alternate set, the true Z80 flags) is real, and the CCP's code single-steps as Z80.
  • CCP is the genuine Digital Research Console Command Processor: it prints A>, reads a line, runs its built-ins (DIR, TYPE, ERA, REN) or loads a .COM transient into the TPA at 0x0100.
  • BDOS and BIOS are emulated in JavaScript over a small read-only RAM disk; the console is a glass-TTY drawn to a 90×32 canvas text grid in the PCW's green phosphor.

The disk carries a tiny greeting, HELLO.COM, and two freely-distributed CPU diagnostics, TEST.COM and 8080PRE.COM, so DIR shows files and you can run and single-step a real transient. This is a CP/M machine themed for the PCW rather than a gate-array-accurate PCW, and it runs CP/M 2.2's CCP rather than the PCW's shipped CP/M Plus, but the Z80 and the CCP are real and it boots to the working prompt.