SearchA-ZS › SAE - Scripted Amiga Emulator

SAE - Scripted Amiga Emulator

2012 Open source · GPL-2.0 Online

The Scripted Amiga Emulator (SAE), by Rupert Hausberger, is a from-source port of the UAE core to pure JavaScript. Here it runs a Commodore Amiga 500 (Motorola 68000) live in the browser and plugs into the shared in-page debugger.

To keep the boot licence-clean, it uses the open-source AROS Kickstart replacement ROM in place of the Cloanto-copyright Amiga Kickstart. With that ROM and no floppy inserted, the Amiga reaches its animated insert-disk boot screen, a genuine, non-black Amiga picture rendered by the real Agnus / Denise / Copper chain.

The debugger reads and writes the 68000 registers (D0-D7, A0-A7, PC, SR with the X N Z V C flags), disassembles and hex-dumps the 24-bit address space with the shared 68000 decoder, and lets you pause, resume and frame-step the machine. Type on the physical keyboard once the screen has focus, or use the on-screen keyboard on mobile.

Visit the official site ↗

Runs on: Web browser

SAE, Scripted Amiga Emulator Online Emulator

Play SAE, Scripted Amiga Emulator using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Amiga 500 (AROS Kickstart)SAE - Scripted Amiga EmulatorAmiga 500openOpen ⛶

Machines emulated

Operating systems

Chips

Notes

Embedding

SAE is a set of plain-global JavaScript modules (no bundler). Vendor src/sae/*.js and load them in SAE's own dependency order, construct the emulator, hand it a Kickstart ROM and a <canvas>, then start it:

var sae = new ScriptedAmigaEmulator();
var cfg = sae.getConfig();
sae.setModel(SAEC_Model_A500, 0);          // an Amiga 500 (68000, OCS)
cfg.video.id  = "saeVideo";                // a <canvas> already in the page
cfg.video.api = SAEC_Config_Video_API_Canvas;   // 2D canvas (headless-friendly)
cfg.audio.mode = SAEC_Config_Audio_Mode_Off;    // no autoplay gesture needed
cfg.memory.rom.data = arosKickstartBytes;       // Uint8Array, 512 KiB AROS ROM
cfg.memory.rom.size = arosKickstartBytes.length;
cfg.memory.extRom.data = arosExtBytes;          // AROS extended ROM
cfg.memory.extRom.size = arosExtBytes.length;
sae.start();                                   // async; hook.event.started fires when live

The machine is plain globals. Once constructed, every part of the Amiga is reachable as an ordinary global, no wasm heap to reach into:

MemberKindWhat it does
SAER_CPU_regsfieldThe live 68000 register file: .d[0..7], .a[0..7], .pc, the supervisor bit .s and the condition flags .x .n .z .v .c (plus .intmask, .t1/.t0).
SAER_CPU_getPC()methodThe live program counter (accounts for the prefetch offset).
SAER_Memory_get8(a)methodRead one byte of the 24-bit 68000 address space through the memory-bank table (chip RAM at $000000, ROM at $F80000 …).
SAER_Memory_put8(a,v)methodWrite the bus, poke memory live.
SAER_CPU_run_func()methodRun the 68000 until the next vertical blank, one video frame of execution. The frame-step primitive.
sae.pause(b) / sae.reset(h,k)methodPause/resume and reset requests (processed by SAE's own loop; SAER.paused reflects the state).

Because the CPU, bus and RAM are ordinary JavaScript, the debugger reads and writes the registers straight off SAER_CPU_regs, scrubs memory with SAER_Memory_get8, disassembles it with the shared 68000 decoder, and advances a frame at a time with SAER_CPU_run_func - with no changes to the emulator core.

Debugger integration

Wiring SAE into the shared in-browser debugger needed two things: a boot shim that layers a controllable loop over SAE's own loop, and an open, license-clean boot ROM.

1 · A controllable loop over SAE's loop. SAE owns its execution: m68k_cycle() runs one frame via SAER_CPU_run_func() and reschedules itself with setTimeout(…,0), and pause/resume/reset go through an internal command flag. We drive that surface directly so the debugger's transport works:

// pause / resume use SAE's own command path
pause:  function(){ if (SAER.running && !SAER.paused) sae.pause(true); },
resume: function(){ if (SAER.running &&  SAER.paused) sae.pause(false); },
// frame-step: run exactly one frame WITHOUT rescheduling the auto-loop
step: function(n){ while (n-- > 0) SAER_CPU_run_func(); }

Limitation: granularity is a frame, not an instruction. SAE's per-instruction work lives inside a private compiled dispatch loop (runNormal/runPrefetch000) whose locals (the opcode table, the cycle counters, the prefetch) are not reachable from outside, so a true single-instruction step would mean forking the CPU core. The debugger therefore steps a whole video frame at a time via SAER_CPU_run_func(). For the same reason execution breakpoints and watchpoints are surfaced in the UI but are not enforced by SAE's dispatch loop. Registers, memory, disassembly, pause/resume, reset and frame-step are all live and accurate.

2 · An open boot ROM. The original Amiga Kickstart is Cloanto copyright and is not hosted here. The boot instead uses the AROS Kickstart replacement (AROS Public Licence, freely redistributable): a 512 KiB ROM whose CRC32 matches the value SAE's roms.js records for the built-in AROS ROM. With that ROM and no floppy, the Amiga reaches its animated insert-disk boot screen, a genuine, non-black Amiga picture rendered by the real Agnus/Denise/Copper chain.

Techniques for deeper access. The plug-in reaches into the running machine through SAE's globals, with no fork of the core:

  • Direct registers. SAER_CPU_regs holds d[], a[] and the flag booleans; the status register is assembled from them (T1 T0 S M, the interrupt mask, and X N Z V C), read and written live.
  • Side-effect-free reads. The hex and disassembly views read with SAER_Memory_get8 but skip the custom-chip and CIA windows ($A00000–$BFFFFF and $D80000–$DFFFFF) so auto-polling the view can never clear a latch or acknowledge an interrupt.
  • Shared 68000 decoder. Disassembly uses the framework's m68000 decoder pointed at that byte reader, the same big-endian 68000 map SAE itself follows.
  • Frame-step. Calling SAER_CPU_run_func() while paused advances exactly one frame and returns at the next vertical blank, without restarting SAE's self-scheduling loop.

Architecture

SAE is a faithful, from-source JavaScript port of the WinUAE/UAE core. Each Amiga chip is its own global object hung off the emulator instance:

  • cpu.js / m68k.js - the Motorola 68000 (and optional 020/030/040) interpreter and its run loop. SAER_CPU_regs is the register file; SAER_CPU_run_func runs a frame.
  • memory.js - the 24-bit address-bank table: chip RAM, slow/fast RAM, the Kickstart and extended ROMs, and the custom/CIA I/O windows.
  • custom.js, blitter.js, copper.js, playfield.js - Agnus/Denise: DMA, the blitter, the copper co-processor and the bitplane playfield, rastered into the canvas by video.js.
  • cia.js ×2, the 8520 CIAs (timers, the keyboard serial line, floppy control, the parallel/serial handshakes).
  • disk.js, audio.js - the floppy subsystem (ADF/DMS) and Paula's four audio channels.
  • roms.js - the ROM database; here it recognises the open AROS Kickstart replacement by CRC32 and maps it at $F80000.

Because it is all ordinary JavaScript with global handles to the CPU, the bus and RAM, SAE makes a rich 68000 debugging target; the whole Amiga is inspectable while it runs.