SearchA-ZA › Acorn Atom

Acorn Atom

2013 Open source · GPL-3.0 Online

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.

Visit the project ↗

Visit the official site ↗

Runs on: Web browser

Acorn Atom Online Emulator

Play Acorn Atom using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Acorn Atom (Tape, with Floating Point)Acorn AtomAcorn AtomgreyOpen ⛶
Acorn Atom (Tape)Acorn AtomAcorn AtomgreyOpen ⛶
Acorn Atom (DOS)Acorn AtomAcorn AtomgreyOpen ⛶
Acorn Atom (AtoMMC + utilities)Acorn AtomAcorn AtomgreyOpen ⛶

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:

MemberKindWhat it does
execute(cycles)methodRun 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)methodReset the machine (BREAK).
peekmem(addr)methodRead a byte with no side effects — what the hex and disassembly views use.
readmem/writemem(addr[,v])methodBus read/write with hardware side effects; writemem pokes memory live.
pc, a, x, y, sfieldsThe 6502 registers, readable and writable.
pfieldThe status flags: p.asByte() / p.setFromByte(b), and p.c/z/i/d/v/n.
atomppiafieldThe 8255 PPIA: keyDownRaw([col,row]) / keyUpRaw([col,row]) drive the Atom key matrix directly.
debugInstructionhookAdd a per-instruction callback; return truthy to halt — real execution breakpoints for free.
debugRead / debugWritehooksPer-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) (not readmem), 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, and processor.p with asByte()/setFromByte()), read and written live each refresh.
  • Native breakpoints. Execution breakpoints ride processor.debugInstruction.add(pc => bps.has(pc)) — when it returns truthy, execute() returns false and our loop stops, so breakpoints are at-speed and free.
  • Native watchpoints. Memory watchpoints ride processor.debugWrite; a write to a watched address flips watchHit, and the instruction hook halts on the following opcode.
  • Single instruction step. execute(1) runs one clock; looping it until pc changes 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 by col*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 $E000 and 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.