Acorn Atom
An in-browser emulator of the Acorn Atom, Acorn's 1980 home computer and the machine that led to the BBC Micro. It runs the Atom's MOS 6502 and Motorola MC6847 video display generator directly in JavaScript, with no download, and boots straight to the "ACORN ATOM" sign-on and the ">" prompt for Atom BASIC. It is built on Matt Godbolt's jsbeeb 6502 core, which includes Acorn Atom support.
Runs on: Web browser
Acorn Atom Online Emulator
Play Acorn Atom using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Acorn Atom (Tape, with Floating Point) | Acorn Atom | Acorn Atom | grey | Open ⛶ | |
| Acorn Atom (Tape) | Acorn Atom | Acorn Atom | grey | Open ⛶ | |
| Acorn Atom (DOS) | Acorn Atom | Acorn Atom | grey | Open ⛶ | |
| Acorn Atom (AtoMMC + utilities) | Acorn Atom | Acorn Atom | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
The Atom runs on jsbeeb's 6502 core, an ES-module project with no single-file build. We bundle a small boot entry with Vite into atom-boot.js, load it as <script type="module">, and drive the machine ourselves rather than using jsbeeb's own UI — so the loop can be paused and single-stepped, which the debugger needs.
Boot. The boot picks an Atom model, makes a Canvas over the page's <canvas>, builds the Video in Atom mode (which in turn creates the MC6847 VDG), constructs the AtomCpu6502, then runs a loop on processor.execute():
const model = findModel("Atom-Tape-FP");
const canvas = new Canvas(document.getElementById("screen"));
const video = new Video(model.isMaster, canvas.fb32, paint, { isAtom: true });
const processor = new AtomCpu6502(model, { video, soundChip, cmos, ... });
await processor.initialise(); // loads the Atom MOS, BASIC and FP ROMs
const ppia = processor.atomppia; // the 8255 keyboard / VDG-control port
(function loop(){ processor.execute(20000); requestAnimationFrame(loop); })();
The processor is fully inspectable. jsbeeb exposes the whole machine as ordinary properties — no wasm heap to reach into:
| Member | Kind | What it does |
|---|---|---|
execute(cycles) | method | Run for a number of clocks; returns false if a debug hook (breakpoint / watchpoint) stopped it. Single-step by looping execute(1) until pc changes. |
reset(hard) | method | Reset the machine (BREAK). |
peekmem(addr) | method | Read a byte with no side effects — what the hex and disassembly views use. |
readmem/writemem(addr[,v]) | method | Bus read/write with hardware side effects; writemem pokes memory live. |
pc, a, x, y, s | fields | The 6502 registers, readable and writable. |
p | field | The status flags: p.asByte() / p.setFromByte(b), and p.c/z/i/d/v/n. |
atomppia | field | The 8255 PPIA: keyDownRaw([col,row]) / keyUpRaw([col,row]) drive the Atom key matrix directly. |
debugInstruction | hook | Add a per-instruction callback; return truthy to halt — real execution breakpoints for free. |
debugRead / debugWrite | hooks | Per-access callbacks used here for memory watchpoints. |
Because it is all JavaScript, the debugger single-steps the 6502, reads and writes registers, disassembles from peekmem, and gets breakpoints and watchpoints from jsbeeb's own CPU debug hooks — with no changes to the core.
Debugger integration
Wiring the Atom into the shared in-browser debugger needed a boot shim and a set of techniques for reaching into the live machine — no fork of the emulator core.
1 · A boot shim, because jsbeeb has no drop-in build. jsbeeb is an ES-module project whose own main.js owns an internal requestAnimationFrame loop that cannot be paused or single-stepped from outside. So we wrote a small boot entry that constructs just the Atom and drives it from a loop we control. Execution breakpoints and watchpoints both ride jsbeeb's CPU hooks: a watched write flips a flag, and the next instruction halts:
// our loop, not jsbeeb's — so pause / step / breakpoints work
processor.debugInstruction.add(pc => bps.has(pc & 0xffff) || watchHit);
processor.debugWrite.add(addr => { if (wps.has(addr & 0xffff)) watchHit = true; });
function chunk(cyc){ watchHit = false; if (!processor.execute(cyc)){ running = false; } }
function stepInsn(){ const p = processor.pc; do { processor.execute(1); } while (processor.pc === p); }
2 · Bundled as an ES module, not an IIFE. Flattening jsbeeb's (circularly-importing) modules into a single IIFE scope throws "Cannot access X before initialization". Building with Vite in library mode and formats:['es'] keeps live module bindings; the page loads the bundle with <script type="module">. Because a module is deferred, the boot publishes window.EMU_BOOT asynchronously, so the debugger plug-in polls for it.
Techniques for deeper access. The plug-in reaches into the running Atom through jsbeeb's own surfaces:
- Side-effect-free reads. The hex and disassembly views scrub memory with
processor.peekmem(addr)(notreadmem), so auto-polling the view can never trip a PPIA / VDG read side effect. - Direct state. Registers are plain properties (
processor.pc/.a/.x/.y/.s, andprocessor.pwithasByte()/setFromByte()), read and written live each refresh. - Native breakpoints. Execution breakpoints ride
processor.debugInstruction.add(pc => bps.has(pc))— when it returns truthy,execute()returnsfalseand our loop stops, so breakpoints are at-speed and free. - Native watchpoints. Memory watchpoints ride
processor.debugWrite; a write to a watched address flipswatchHit, and the instruction hook halts on the following opcode. - Single instruction step.
execute(1)runs one clock; looping it untilpcchanges advances exactly one instruction. - Keyboard. The on-screen keys and the physical keyboard both call
atomppia.keyDownRaw([col,row])/keyUpRaw, addressing the Atom key matrix bycol*8+row.
Everything the debugger shows — registers read/write, memory hex/disassembly, follow-PC, single-step, breakpoints and watchpoints — is built from these, with no changes to the emulator core.
Architecture
The Acorn Atom (1980) is Acorn's pre-BBC home computer: a MOS 6502 with a Motorola MC6847 video display generator and an Intel 8255 for the keyboard, driven here by jsbeeb's cycle-stepped 6502 core.
AtomCpu6502— the 6502 interpreter and the Atom memory map: RAM and video RAM from$0000, the MC6847 screen memory at$8000, sideways/utility ROM banks at$A000, Atom BASIC at$C000, the FP / DOS ROM at$E000and the MOS kernel at$F000.Video6847— the MC6847 VDG, clocked off the core's video polltime; it reads Atom screen RAM and blits the alphanumeric, semigraphics and colour-graphics modes into the framebuffer painted to the canvas.AtomPPIA— the 8255 PPIA: port A selects the VDG mode and the keyboard column, port B returns the key-matrix row, port C carries CSS / 2.4 kHz / cassette lines and raises the VDG field-sync interrupt.- ROMs — the Atom MOS (the sign-on and
>monitor), Acorn Atom BASIC, and the optional Floating Point, DOS, AtoMMC and utility ROMs, loaded into their banks at boot.
Each execute() steps the CPU and clocks the VDG and PPIA in lockstep. Because every component is an ordinary object, the whole machine state is inspectable — which is what the debugger reads each refresh.