SimCoupe
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.
Runs on: Web browser
SimCoupe Online Emulator
Play SimCoupe using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| SAM Coupé BASIC | SimCoupe | sam-coupe | grey | Open ⛶ |
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 JavaScript | What 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
z80C++ core, wrapped assam_cpuwith 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.