SearchA-ZA › Acorn Archimedes

Acorn Archimedes

1987 Open source · GPL-2.0 Online

The Acorn Archimedes was the first desktop computer built around the ARM, Acorn's own 32-bit RISC processor. This is Sarah Walker's Arculator emulator compiled to WebAssembly, running an A3000 (Acorn ARM2, 4 MB) and booting RISC OS 3.11 straight to the desktop in your browser.

Visit the official site ↗

Runs on: Web browser

Acorn Archimedes Online Emulator

Play Acorn Archimedes using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
RISC OS 3Acorn ArchimedesAcorn ArchimedesAcorn RISC OSgreyOpen ⛶

Machines emulated

Operating systems

Chips

Notes

Embedding

Arculator is a C emulator that uses SDL2 for video, sound and input. We compiled it to WebAssembly with Emscripten (-sUSE_SDL=2 -sUSE_WEBGL2=1); the emulator owns its own emscripten_set_main_loop, and we drive/inspect it from JavaScript through exported EMSCRIPTEN_KEEPALIVE functions. We self-host everything (no CDN): arculator.js (Emscripten glue), arculator.wasm (the emulator), and arculator.data (a preloaded MEMFS image holding the RISC OS 3.11 ROM, the extension ROM and CMOS).

// Module is configured BEFORE the glue loads; the canvas MUST have id="canvas"
// for Emscripten SDL2. The preloaded arculator.data.js is loaded first, then the
// glue auto-runs main() -> arc_init() -> RISC OS boots on the default arc.cfg.
var Module = {
  canvas: document.getElementById('canvas'),
  onRuntimeInitialized: function () { publishEmuBoot(Module); }  // wire the debugger
};
Exported hookWhat it does
arc_pause_main_thread() / arc_resume_main_thread()Stock Arculator hooks: set/clear the flag the main loop checks once per frame.
arc_dbg_step(n)Run the ARM core for exactly n instructions (true single step), then stop.
arc_dbg_run_ms(ms)Advance one ~50 Hz frame (the debugger "step frame" control).
arc_key_set(scancode, down)Set/clear a host key (SDL scancode) for the on-screen keyboard.
arc_do_reset()Reset the machine.

The physical keyboard and mouse need no extra wiring: Emscripten's SDL2 port maintains SDL_GetKeyboardState from the browser's key events, and the mouse runs in RISC OS's absolute-pointer mode over the canvas.

Debugger integration

This is a Tier-4 "WASM core, full debugger" integration: Arculator keeps the ARM state inside the wasm module, so we rebuilt the core with tiny state-export hooks (a new emscripten_debug.c plus four one-line edits). The golden rule holds - the getters only sample state when the debugger asks (~10×/s and once per step); the per-instruction path pays one predictable branch (if (dbg_trap)), and only when a breakpoint, watchpoint or step is actually armed.

What the debugger needsHow the hook provides it
R0-R15 (r15 = PC + PSR on this 26-bit core)arc_dbg_get_reg(i) / arc_dbg_set_reg(i,v) read & write the core's uint32_t armregs[16] directly - fully writable.
Disassembly origin (current PC)arc_dbg_pc() = (PC-8)&0x3fffffc, matching Arculator's own debugger.
Memory (side-effect-free)arc_dbg_read(addr) wraps the core's readmemf_debug(), which reads RAM/ROM only and never touches an I/O latch.
Single steparc_dbg_step(n) runs the real ARM core for exactly n instructions - a true per-instruction advance, not a time-slice.
Execution breakpointsAn address set checked at the end of each instruction (only while armed); on a hit the main loop pauses.
Write watchpointsThe core's inline CPU store path (writememb/writememl in arc.h - the fast path RAM writes actually take) flags a hit when a watched word is written; the loop stops after that instruction.

Exactly what we changed. One new file src/emscripten_debug.c (the exports + the break/watch/step logic) and one header src/emu_dbg.h, plus four minimal edits guarded by #ifdef ARCWEB:

// src/arm.c  - end of the execarm() instruction loop
if (dbg_trap && dbg_post_execute()) { total_cycles = 0; break; }   // bp/wp/step halt

// src/arc.h  - the inline writememb()/writememl() CPU store path
if (dbg_trap) dbg_on_write(a);                                    // watchpoint check

// src/emscripten_main.c  - arcloop(), after arc_run()
if (arc_dbg_take_halt()) pause_main_thread = 1;               // pause on halt

// src/input_sdl2.c  - keyboard_poll_host(), so on-screen keys survive
key[c] = state[c] | vkey[c];                                   // OR in the overlay

What is real vs approximate. Registers, the PC+PSR word, memory, disassembly, play/pause, single-step, execution breakpoints and write watchpoints are all real and come from Arculator's own core. Register write-back covers the full r0-r15 file and the PSR flag bits. Memory is shown through the MEMC logical map (the addresses the ARM sees); I/O windows read back as 0xff rather than perturbing hardware.

Architecture

The Acorn Archimedes (1987) was the first desktop computer built around the ARM - Acorn's own 32-bit RISC processor. Arculator models the A305-A5000 family; the default here is an A3000: an Acorn ARM2 (ARMv2, a 26-bit machine where r15 holds both the program counter and the processor status), the MEMC1a memory controller, the VIDC video controller and the IOC I/O controller, with 4 MB of RAM, running RISC OS 3.11 from ROM.

  • arculator.wasm / arculator.js - Arculator 2.2 (Sarah Walker) compiled with Emscripten + our debug hooks.
  • arculator.data - preloaded MEMFS: roms/riscos311/ros311 (RISC OS 3.11, 2 MB), roms/arcrom_ext (extension ROM), and CMOS defaults.

How to rebuild from scratch (reproducible).

# toolchain: Homebrew emscripten 6.0.3 on PATH (/opt/homebrew/bin), python3, node
# 1. clone the Emscripten Arculator port (powers archi.medes.live)
git clone https://github.com/pdjstone/arculator-wasm.git && cd arculator-wasm
# 2. fetch the grey RISC OS ROMs from Sarah Walker's official distribution
curl -Ls https://b-em.bbcmicro.com/arculator/Arculator_V2.2_Linux.tar.gz | tar xz roms
# 3. add src/emscripten_debug.c + src/emu_dbg.h and the four #ifdef ARCWEB edits
#    (arm.c, mem.c, emscripten_main.c, input_sdl2.c); add emscripten_debug to OBJS.
# 4. modern-clang fixes: CFLAGS += -std=gnu17 -Wno-error; guard hostfs.c bool
#    typedef with #ifndef bool; point the file_packager rule at Homebrew's copy.
# 5. build the wasm target (FULL_FAT bundles ROMs + cmos into arculator.data):
make -j8 FULL_FAT=1 wasm
# artefacts: build/wasm/arculator.{js,wasm,data,data.js} -> emulator/archimedes/src/

Sound

The Archimedes' sound is the VIDC's 8-channel stereo sound system: the C core mixes it and Arculator, being an SDL2 program, hands the samples to SDL2 for playback. Compiled with Emscripten, that SDL2 audio path becomes a real WebAudio graph, so the sound already works through the core's own pipeline. This is therefore a native-pipeline (Pattern V-native) integration: we do not reroute the audio through the site's shared EmuAudio sink, and /debugger/src/audio.js is not loaded. The context Emscripten's SDL2 creates is Module.SDL2.audioContext, and the core pushes each block through Module.SDL2.audio.scriptProcessorNode, which SDL2 wires straight to that context's destination.

The page must start silent until the visitor asks for sound, but SDL2 auto-resumes its context on any user gesture anywhere on the page. To keep control we splice a GainNode between the core's scriptProcessorNode and the destination and hold it at 0 while muted; a ~300 ms guard re-asserts silence (and re-splices the gain once SDL2 lazily opens audio) so a stray click never leaks sound:

function audioWire(){
  var ctx = Module.SDL2.audioContext;
  if (!_gain){ _gain = ctx.createGain(); _gain.gain.value = _muted ? 0 : 1; _gain.connect(ctx.destination); }
  var node = Module.SDL2.audio.scriptProcessorNode;   // SDL2's output node
  if (!_rerouted && node){ node.disconnect(); node.connect(_gain); _rerouted = true; }
}
function setMute(m){
  _muted = !!m; audioWire();
  _gain.gain.value = _muted ? 0 : 1;
  _muted ? Module.SDL2.audioContext.suspend() : Module.SDL2.audioContext.resume();
}

The standard mute contract lives on window.EMU_BOOT.transport as isMuted() and setMute(m); it starts muted, so the machine is inaudible until the shell's Sound button is clicked. That real click both satisfies the browser's audio-gesture rule and calls setMute(false), which sets the gain to 1 and resume()s the context; muting again drops the gain to 0 and suspend()s it.

The machine boots straight to the RISC OS desktop and stays there; we do not force any startup noise. Sound is fully supported through the pipeline above, so anything the software plays (the desktop beep, an application, a game, or a BBC BASIC SOUND or VDU 7 the visitor types) is heard the moment the machine produces it, once Sound is unmuted.