SearchA-ZC › Cambridge Z88

Cambridge Z88

1987 Open source · GPL-2.0 Online

An in-browser Cambridge Z88 that boots the OZ operating system straight to its Index. The interpreted JavaScript Z80 core is paired with a purpose-written port of the Z88's one custom chip, the NEC uPD65031 "Blink" gate array — memory bankswitch, real-time clock, interrupts, keyboard and the 640×64 grey-green supertwist LCD. The whole machine is plain JavaScript objects, so the shared debugger can single-step the Z80, read and write registers, inspect memory through the Blink bank map, and set breakpoints and write watchpoints live.

The Z88 (1987) was Clive Sinclair's Cambridge Computer portable: a slim A4 notepad with a rubber keyboard, built-in applications (PipeDream, BBC BASIC, Diary, Calculator) resident in ROM, and famously long battery life.

About the Cambridge Z88 project ↗

Visit the official site ↗

Runs on: Web browser

Cambridge Z88 Online Emulator

Play Cambridge Z88 using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Cambridge Z88 — OZ 4.6Cambridge Z88Cambridge Z88greyOpen ⛶
Cambridge Z88 — OZ 4.7Cambridge Z88Cambridge Z88greyOpen ⛶

Machines emulated

Chips

Notes

Embedding

The Z88 core is assembled at load time from three plain-global scripts: the JSSpeccy namespace shim, the interpreted JSSpeccy.Z80 core (reused unchanged — it takes pluggable memory and ioBus objects), and blink.js, a port of the NEC uPD65031 "Blink" gate array. We drive the machine ourselves instead of any built-in loop, so the debugger can pause and step it.

Boot. Fetch the OZ ROM, build the Blink, generate the Z80 with contention off (the Z88 has no contended memory) and give it Blink-backed bus and I/O:

JSSpeccy.buildZ80({ traps: [], applyContention: false });
var blink = Z88.Blink(romBytes);
var memory = { read: blink.memRead, write: blink.memWrite };
var ioBus  = { read: blink.ioRead, write: blink.ioWrite };
var display= { nextEventTime: null, doEvent: function(){} };  // we raster the LCD ourselves
var z80 = JSSpeccy.Z80({ memory: memory, ioBus: ioBus, display: display });
z80.reset();  // PC=0 -> OZ boot bank; the ROM sets IM 1 and drives the Blink

Run loop. The Blink RTC ticks every 5 ms (16384 T-states at 3.2768 MHz). Each tick we assert the Z80 interrupt when the Blink's /INT line is active, run a tick's worth of instructions, then advance the clock; the LCD is rastered a few times a second:

function tick(){
  if (blink.intAsserted()) z80.requestInterrupt();   // Blink -> Z80 IM 1 (RST 38h)
  z80.runFrame(TICK);
  z80.setTstates(z80.getTstates() - TICK);
  blink.rtcTick();                              // RTC counters + keyboard / TICK interrupt
}

Because the CPU, the bus and the Blink are ordinary JavaScript, single-stepping is z80.runFrame(z80.getTstates()+1), registers are the core's get*/set* accessors, and breakpoints and watchpoints are host-side checks — no change to the CPU core.

Debugger integration

z88-debug.js reads the live machine from window.EMU_BOOT and calls EmuKit.defineMachine with the shared z80 disassembler, the full Z80 register set, and two memory chips.

Side-effect-free memory through the bank map. The Z88 has no flat 64K — every access is translated by the Blink through four segment registers (SR0–SR3), and the bottom 8K of segment 0 flips between boot ROM and RAM on the COM register's RAMS bit. The hex/disasm view therefore reads through blink.memRead (the same translation the CPU uses, but with no I/O side effects) so what you see is exactly what the Z80 sees. A second chip exposes the raw 512K internal RAM linearly for inspecting the screen file and OZ workspace.

function stepInsn(){ z80.runFrame(z80.getTstates() + 1); }  // exactly one instruction

Breakpoints are a Set of PC values; when any are set the loop steps one instruction at a time and compares getPC(). Watchpoints wrap the bus write path (blink.memWrite) and halt when a watched CPU address is written — the address is the logical Z80 address, translated through the same bank map. The Blink interrupt line, RTC and LCD keep running while paused only when you step, so timing stays faithful.

Architecture

The Z88 is a Z80 (3.2768 MHz) surrounded by one custom chip, the NEC uPD65031 Blink, which does almost everything:

  • Memory. A 4MB physical space of 16K banks — pages 00–1F internal ROM (OZ), 20–3F internal RAM, 40–FF the three external card slots. The Z80's 64K is four 16K segments; SR0–SR3 bind a bank to each. Segment 0's lower 8K is special: it shows the boot ROM, or internal RAM when the COM register's RAMS bit is set.
  • LCD. A 640×64 grey-green supertwist panel. The Blink rasters it from a screen file (base register SBR) of 16-bit cells — each names a character and attribute bits (hi-res/lo-res, reverse, grey, underline, flash, cursor); fonts live in ROM at the PB0–PB3 pixel-base registers. Low-res cells are 6×8, high-res 8×8.
  • RTC & interrupts. Five cascading counters (TIM0–TIM4) tick from 5 ms up to 64K minutes; a 100Hz TICK, plus keyboard and flap sources, raise the single Z80 interrupt (IM 1) gated by the INT/STA mask and status registers.
  • Keyboard. An 8×8 matrix read through the KBD port with the column select on the high address byte.
  • OZ. The 512K ROM operating system: the Index launcher and the built-in application suite (PipeDream, BASIC, Calculator, Diary…), all resident.

This port models the Blink faithfully from the hardware documentation and the MAME device, and reuses the interpreted JSSpeccy Z80 — every part is plain JavaScript, which is what makes it a good debugging target.