SearchA-ZW › Williams Arcade

Williams Arcade

1982 Arcade Open source · GPL-3.0 Online

Williams Arcade is Williams Electronics' second-generation arcade board (1980–82): a Motorola 6809 driving a bitmap display, a hardware blitter and 6821 PIAs. This build runs the board in the browser, a pure-JavaScript machine model plus an MIT 6809 core, and boots Robotron: 2084 (Vid Kidz / Williams, 1982) straight into attract mode, with Joust as a sibling on the same hardware. It is wired to the emulators.org in-frame debugger so you can single-step the 6809, read and write the registers and the full 64K memory, set execution breakpoints and write watchpoints, and use the on-screen twin-stick pad.

Visit the official site ↗

Runs on: Web browser

Williams Arcade Online Emulator

Play Williams Arcade using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
Robotron: 2084Williams ArcadeWilliamsgreyOpen ⛶
JoustWilliams ArcadeWilliamsgreyOpen ⛶
SinistarWilliams ArcadeWilliamsgreyOpen ⛶

Machine emulated

The Williams arcade board (1980–82) - a Motorola 6809 at ~1 MHz with 48 KB of RAM, up to 48 KB of bank-switched ROM and 1 KB of battery-backed CMOS. Its 304×256, 16-colour bitmap lives directly in RAM, drawn by a custom hardware blitter, and the monitor is turned 90° in the cabinet. It ran Defender, Stargate, Robotron: 2084, Joust, Bubbles and Sinistar.

Chips

Notes

Embedding

The Williams "6809" board (Defender, Stargate, Robotron, Joust, Bubbles, Sinistar) is a single Motorola 6809 at ~1 MHz driving a 304×256, 16-colour bitmap held directly in main RAM, with a hardware blitter for fast sprite copies and a pair of 6821 PIAs for the controls. This build vendors the pure-JavaScript machine model from 8bitworkshop (GPL-3.0) together with Martin Maly's MIT CPU6809 core, and drives them from a loop the host owns so the debugger can control the machine.

Boot. The 0xC000-byte program image is loaded (Robotron by default), the CPU is pointed at the memory bus and reset through the $FFFE vector, and it runs its self-test straight into attract mode:

var m = new WilliamsMachine({ isDefender: false });
m.loadROM(WILLIAMS_ROMS['robotron']);   // 12 x 4K program ROMs, concatenated
m.cpu.init(write, m.membus.read, 0);       // bind bus + reset (reads $FFFE)
m.banksel = 1;                            // ROM banked in at $0000-$8FFF
(function loop(){ m.advanceFrame(); requestAnimationFrame(loop); })();

The machine is plain objects. Everything the debugger needs is a method or field on the machine or its CPU:

MemberKindWhat it does
m.advanceFrame(trap)methodRun one 304-line video field, driving the two 6821 PIAs' count240 (line 240) and VA11 (every 32 lines) interrupt lines from the beam position and offering the resulting 6809 IRQ at each instruction boundary. trap() is polled before every instruction so the host can stop on a breakpoint or watchpoint.
m.cpu.advanceInsn()methodExecute exactly one 6809 instruction and return its cycle count — the single-step primitive.
m.cpu.saveState() / m.cpu.set(reg,v)methodRead the whole register file (PC A B X Y U SP DP CC) and write any register back — how the debugger shows and edits registers.
m.read(a) / m.write(a,v) / m.readConst(a)methodLive bus access and a side-effect-free read (the $C000-$CBFF I/O window is skipped) for the memory views.
m.ram / m.rom / m.nvram / m.palettefieldThe 48K work+video RAM, the 48K program ROM, the 1K CMOS and the 16-entry palette, as plain arrays.
m.pia6821 / m.setSwitch(i,mask,down)fieldThe eight input latches and a helper to set/clear a control bit — how coins, start and the twin sticks reach the game.

Video. Writes to $0000-$97FF land two 4-bit pixels each into a 256×304 buffer; the display is the Williams monitor rotated 90°, so the host un-rotates the buffer onto the canvas each field. Palette writes to $C000-$C00F repaint live.

Debugger integration

The debugger drives a host-owned run loop: because the 6809 interpreter runs one instruction at a time in JavaScript, the loop can pause, single-step and check breakpoints between any two instructions — no changes to the CPU core are needed beyond one added register setter.

window.EMU_BOOT.transport exposes the controls the shared debugger calls:

  • pause / resume / isPaused — stop or restart the requestAnimationFrame loop.
  • stepInsn(n) — call cpu.advanceInsn() exactly n times and redraw, so a single step advances the PC by one instruction.
  • step(n) — advance n whole 60 Hz video fields (with their PIA-driven count240 / VA11 interrupts).
  • breakpoints — a Set of PC values. When non-empty the frame loop runs instruction-by-instruction and pauses before executing an address in the set; when empty it runs a full field at speed, so an idle debugger costs nothing.
  • watchpoints — the CPU's memory-write path is wrapped (the core is re-init'd with a write shim), so a store to a watched address pauses the loop. This is the one place the core is extended.

The plug-in (williams-debug.js) reads these hooks and calls EmuKit.defineMachine with the full 6809 register set (read from saveState(), written through cpu.set(), with the E F H I N Z V C flags decoded from CC), memory chips for the 64K CPU bus / RAM / ROM / CMOS using the shared m6809 disassembler, and a custom twin-stick control view. Memory reads for the views use readConst, which skips the I/O window so auto-polling never ticks the video counter or a PIA.

Architecture

Williams' second-generation arcade board is one Motorola 6809 with 48 KB of RAM, up to 48 KB of ROM (bank-switched over the low RAM), 1 KB of battery-backed CMOS and two signature custom parts, all modelled here as plain JavaScript on WilliamsMachine:

  • CPU — the 6809, with the whole 64 KB memory map built from range decoders. $0000-$8FFF is RAM or banked ROM (a write to $9000 flips the bank); $9800-$BFFF is work RAM; $C000-$CFFF is the I/O page; $D000-$FFFF is fixed ROM.
  • Blitter — the "special chip" DMA copier: eight registers at $CA00 describe a source, destination, width, height and a flags byte (transparency, solid fill, shifted pixels, X/Y mirroring); one write to $CA00 runs the copy that draws every sprite.
  • Video — no framebuffer chip: the CRT simply scans $0000-$97FF, one byte per two horizontal-nibble pixels, through a 16-entry RRRGGGBB palette at $C000. The beam position also clocks the interrupt timing (below).
  • I/O & interrupts — two 6821 PIAs carry the coin door, start buttons and the joysticks, modelled faithfully here (data-direction and control registers, CA1/CB1 handshake edges, the IRQ flags, and interrupt-acknowledge on a data-register read). Only PIA 1's IRQ outputs reach the 6809: its CA1 = count240 (one edge per field at line 240) and CB1 = VA11 (toggling every 32 lines) are driven from the beam position, giving the game its periodic interrupt and frame timing. A second 6808 + 6821 + MC1408 DAC (+ HC55516 CVSD on Sinistar) plays sound (see Audio).
  • CMOS & watchdog — 1 KB of 4-bit non-volatile RAM at $CC00 holds bookkeeping and high scores; a 3-second watchdog resets the board unless the game keeps writing 0x39 to $CBFF.

Robotron: 2084 (Vid Kidz / Williams, 1982) uses this board with two 8-way joysticks — one to run, one to fire — and it is the default boot here; Joust (1982) is the same hardware and ships as a sibling config.

Sound

Pattern: the real sound board, emulated. On the Williams cabinet the sound lives on a separate board and this build now runs it for real: a Motorola M6808 (a 6800 with on-chip clock; identical instruction set) clocked at 3.579545 MHz / 4 = 894.886 kHz, executing this game's actual sound ROM, a 6821 PIA, an MC1408 8-bit DAC and — on Sinistar — an HC55516 CVSD chip for digitised speech. The sound ROM bytes are the original dumps, SHA1-verified against MAME (Robotron video_sound_rom_3, CRC c56c1d28; Joust video_sound_rom_4, CRC f1835bdd). The audio you hear is that board's own DAC output — not a synthesis.

How it is driven. Every main-CPU store to the sound latch ($C80E on the Robotron/Joust map, $CC06 on Defender's) is intercepted in the same write shim the watchpoints use and handed to WilliamsSound (williams-sound.js). Exactly as on the PCB, the 6-bit command has its top two bits forced high and is presented on the sound PIA's port B with a CB1 strobe that interrupts the M6808; the sound program reads the command in its IRQ handler and plays the effect by writing the DAC through PIA port A (and, for speech, clocking CVSD bits out of CA2/CB2). A compact fresh M6800 core (m6800.js) runs the ROM; when idle the ROM sits in WAI, so silence is genuinely silent.

Sample rate & sink. The board runs at its real 894.886 kHz; for each host output sample the M6808 is advanced by 894886 / sampleRate cycles and the DAC is boxcar-averaged over them (anti-aliasing), then DC-blocked. It emits exactly Math.round(sampleRate/60) interleaved-stereo Int16 pairs per video field, pushed through the shared EmuAudio.push() sink each frame from the host run-loop. The mono board mix is duplicated to both channels.

Mute and sound. transport.setMute/isMuted delegate to EmuAudio; audio starts muted and the Sound button resumes the AudioContext from a real click. The machine boots into its own attract mode. Press 5 to drop a coin and 1 (or Enter) for 1P Start to play, or use the on-screen controls. The sound board runs the whole time, so attract music and gameplay effects play in full once you click Sound.