SearchA-ZS › SimCoupe

SimCoupe

1999 Open source · GPL-2.0-or-later Online

SimCoupe, by Simon Owen, is a SAM Coupé emulator written in C++. This build runs it in the browser as WebAssembly and boots straight to SAM BASIC. It uses Kosarev's cycle-accurate Z80 core, and is wired to the emulators.org in-frame debugger so you can single-step the Z80, read and write registers and the paged 64K memory, and set execution breakpoints.

Visit the official site ↗

Runs on: Web browser

SimCoupe Online Emulator

Play SimCoupe using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
SAM Coupé BASICSimCoupesam-coupegreyOpen ⛶

Machine emulated

The SAM Coupé (Miles Gordon Technology / SAM Computers, 1989), a Z80B home computer, a would-be ZX Spectrum successor with a custom ASIC, four screen modes and a SAA1099 stereo sound chip.

Chips

Notes

Embedding

SimCoupe is a C++ SAM Coupe emulator. This is the SamCoupeWeb fork built to WebAssembly with Emscripten and SDL2. Vendor the three build artefacts and load the glue script; the emulator then runs its own emscripten_set_main_loop, boots to SAM BASIC with no media, and renders into a WebGL canvas with id="canvas".

Boot. Define Module before loading samcoupeweb.js: give it the canvas, the command-line arguments (which pick the SAM system ROM out of the preloaded virtual file-system), and a print hook to notice the Main::Init: Success! banner. When onRuntimeInitialized fires the export table is live and we publish window.EMU_BOOT:

var Module = {
  canvas: document.getElementById('canvas'),
  arguments: ['-usewebgl','1', '-rom','/Resource/samcoupe.rom', '-drive2','1', '-visiblearea','1'],
  locateFile: function(p){ return '/emulator/sam-coupe/src/' + p; },
  onRuntimeInitialized: function(){ /* publish EMU_BOOT (see below) */ }
};

Control. The debugger needs pause / step / register + memory access, all of which are thin JavaScript calls onto the export hooks compiled into the module (Module._emudbg_*). The run loop is owned by C, so pausing is a flag the loop honours once per frame, never a per-instruction callback:

Hook (C export)From JavaScriptWhat it does
emudbg_pause(on)Module._emudbg_pause(1)Set the emulator's g_fPaused flag; the emscripten main loop checks it once per frame and stops advancing.
emudbg_step()Module._emudbg_step()Run exactly one Z80 instruction (cpu.on_step() + events + interrupt), completing the video frame when the cycle counter wraps.
emudbg_step_frame()Module._emudbg_step_frame()Advance to the end of the current 50Hz video frame.
emudbg_pc() / emudbg_reg(i) / emudbg_set_reg(i,v)Module._emudbg_reg(3)Sample or poke one Z80 register by index (AF BC DE HL IX IY SP PC I R, the shadows, IM, IFF).
emudbg_read(a) / emudbg_read_block(a,ptr,n) / emudbg_write(a,v)Module._emudbg_read(a)Read or write one byte of the SAM's paged 64K address space, side-effect-free (straight through the paging pointers, no I/O).
emudbg_bp_set(a,on) / emudbg_bp_has(a) / emudbg_bp_clear()Module._emudbg_bp_set(a,1)Arm or clear a native execution breakpoint on a logical PC.
EMS_SamKey(samkey,down)Module._EMS_SamKey(56,1)Hold or release a SAM key by its matrix code, for the on-screen keyboard.

The physical keyboard already works through SDL2 whenever the canvas is focused, so typing is character-accurate. Everything is sampled on demand at ~10 Hz while the debugger window is open; with the window closed the emulator runs at full native speed, untouched.

Debugger integration

This is a Tier 4 integration: the CPU state lives inside a compiled wasm module, so SimCoupe was rebuilt from source with a small set of export hooks. The changes are confined to three files and add nothing to the per-instruction hot path.

1. Base/CPU.cpp - the hooks. An extern "C" block reads the live global cpu (a z80::z80_cpu<sam_cpu> from Kosarev's z80 library) and SAM's paging pointers. Registers come straight off the core's accessors; memory is read through AddrReadPtr() so it is side-effect-free:

extern "C" {
  EMSCRIPTEN_KEEPALIVE uint32_t emudbg_reg(int i){ switch(i){ case 3: return cpu.get_hl(); /* ...AF BC DE HL IX IY SP PC I R, shadows, IM, IFF... */ } }
  EMSCRIPTEN_KEEPALIVE int emudbg_read(uint32_t a){ return *AddrReadPtr(a & 0xffff); }
  EMSCRIPTEN_KEEPALIVE void emudbg_step(){ cpu.on_step(); /* + CheckEvents + maskable interrupt + frame-end */ }
  EMSCRIPTEN_KEEPALIVE void emudbg_pause(int on){ g_fPaused = on; }
}

Execution breakpoints are native and cheap. A std::set<uint16_t> of PC values is checked inside CPU::ExecuteChunk() at instruction boundaries, but only when the set is non-empty, mirroring SimCoupe's own Breakpoint::breakpoints.empty() guard, so an idle debugger costs nothing. On a hit it sets g_fPaused and returns; a if (g_fPaused) return; at the top of ExecuteChunk() makes the pause take effect immediately. Watchpoints are not wired: Memory::Write() is inlined on the hot path, and instrumenting it would violate the "no per-cycle hook" rule, so the plug-in reports watchpoints as unavailable.

2. Base/Keyboard.cpp - the on-screen keyboard. SimCoupe rebuilds its key matrix from the physical keyboard every frame in Keyboard::Update(). A 9-byte override array is merged in at the end of that function, and EMS_SamKey(samkey, down) holds or releases a key by its eSamKey matrix code, so on-screen and physical keys coexist.

3. Build glue. The new exports are added to -sEXPORTED_FUNCTIONS and getValue,setValue,HEAPU8 to -sEXPORTED_RUNTIME_METHODS. The step primitive is one Z80 instruction (SimCoupe exposes cpu.on_step()), so single-instruction stepping is exact, not coarse.

The plug-in (sam-coupe-debug.js) reads these hooks through window.EMU_BOOT and calls EmuKit.defineMachine with the Z80 register set, the paged 64K bus and an on-screen SAM keyboard. It reuses the shared z80 disassembler (/debugger/src/cpus/z80.js).

Architecture

The SAM Coupe (MGT / SAM Computers, 1989) is a Z80B machine clocked at 6 MHz - a would-be ZX Spectrum successor, largely Spectrum-screen compatible but with a custom ASIC ("ASIC"/gate-array) driving a richer display and paged memory.

  • CPU - Zilog Z80B at 6 MHz. SimCoupe emulates it with Kosarev's cycle-accurate z80 C++ core, wrapped as sam_cpu with SAM-specific memory-contention and I/O callbacks.
  • Memory - up to 512K paged into the Z80's 64K address space in four 16K sections through the LMPR/HMPR/VMPR paging registers; the 32K system ROM lives at the bottom on boot. The debugger's memory view reads the live paged bus, so it follows whatever is currently banked in.
  • Display - the ASIC offers four screen modes, from the Spectrum-compatible Mode 1 to the 256×192 16-colour Mode 4, from a 128-colour palette.
  • Sound - a Philips SAA1099 stereo sound chip (six channels), emulated via SAASound.
  • Storage - twin 3.5″ floppy drives; this build boots to BASIC with no disk.

Because the whole machine is compiled C++, the debugger reaches it through the export hooks rather than live JavaScript objects, but the resulting register, memory and step access is the same as the pure-JavaScript machines on the site.