PCW Joyce
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 ↗
Runs on: Web browser
PCW Joyce Online Emulator
Play PCW Joyce using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Amstrad PCW CP/M | PCW Joyce | Amstrad PCW 8256 | Digital Research CP/M | grey | Open ⛶ |
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:
| Member | Kind | What it does |
|---|---|---|
cpu.run_instruction() | method | Execute exactly one Z80 instruction; returns its cycle count. The single-step primitive. |
cpu.getPC() / cpu.setPC(v) | added | Cheap live program counter, for the trap loop and single step. Matching getSP / setSP too. |
cpu.getState() / cpu.setState(s) | method | The 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) | method | The 64 KB address space the CPU sees, a plain Uint8Array you can poke live. |
bdos() / bios(n) | trap | JavaScript 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 registersIX IY, the alternate setAF' BC' DE' HL',I Rand the interrupt mode, plusPC SPand theS Z H P/V N Cflags. A small facade turns the core'sgetState/setStateinto 16-bit pair accessors. - Disassembly uses the shared
z80decoder, 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
.COMat 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 theA>prompt does nothing until you type. Step through a transient program such asHELLOorTESTinstead.
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.COMtransient into the TPA at0x0100. - 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.