Search › A-Z › G › Bit Corporation Gamate
Bit Corporation Gamate
The Gamate (Bit Corporation, 1990) is an 8-bit handheld game console and one of the many Game Boy rivals of its era. This emulator is authored from scratch in JavaScript around Mike Chambers' public-domain Fake6502 NMOS 6502 core, wired to the Gamate bus, its custom 160x150 four-shade LCD controller, the AY-3-8910 sound chip and the joystick. It is plugged into the in-page debugger, where you can single-step the 6502, set breakpoints and watchpoints, and inspect the whole memory map.
Runs on: Web browser
Bit Corporation Gamate Online Emulator
Play Bit Corporation Gamate using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Bars & Beeps (6502 demo) | Bit Corporation Gamate | Bit Corporation Gamate | open | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
The Gamate emulator is authored in JavaScript with no WebAssembly and no CDN. Three self-hosted scripts make the machine: the vendored public-domain Fake6502.js (Mike Chambers' NMOS 6502 interpreter), the shared ay8910.js PSG, and gamate-machine.js — the Gamate bus, LCD video controller, joystick latch and IRQ timer wired around that CPU. The boot script owns the loop, so the debugger can pause, single-step and breakpoint it.
Boot and load the ROMs. A 4 KB open BIOS holds the CPU vectors at $FFFA–$FFFF; on reset it initialises the stack and jumps to the cartridge at $6000:
var gm = new Gamate();
gm.loadBIOS(biosBytes); // $E000..$EFFF, mirrored to $FFFF
gm.loadCart(cartBytes); // $6000..$DFFF
gm.attachAudio(AY8910, EmuAudio.sampleRate);
gm.reset(); // pulls the reset vector from $FFFC/$FFFD
The machine surface. Everything the loop and the debugger touch is a plain method on the machine object:
| Method | What it does |
|---|---|
step() | Execute one 6502 instruction and service the IRQ timer (an IRQ every 16384 cycles). Returns the cycle count. |
stepInsn() | Execute exactly one instruction with no timer side effects — the debugger's step primitive. |
renderFrame() | Scan the two VRAM bitplanes into the 160×150 RGBA framebuffer, applying scroll / blank / plane-swap. |
read(a) / write(a,v) | The live CPU bus (drives the video auto-increment port and the AY registers). |
peek(a) | A side-effect-free bus read for the debugger — never advances the VRAM port or touches a latch. |
getReg(n) / setReg(n,v) | Read / write A, X, Y, SP, PC, P on the live CPU. |
key(bit,down) | Press / release a button on the active-low JOY latch (D-pad, A, B, Start, Select). |
Debugger integration
Because the machine is authored in JS the debugger wiring is direct: the boot script owns the run-loop and publishes window.EMU_BOOT, and gamate-debug.js calls EmuKit.defineMachine with the live handles.
A host-owned loop. With no breakpoints or watchpoints the loop runs a whole frame of cycles at once; otherwise it steps one instruction at a time, comparing PC against the breakpoint set and re-checking watched addresses between instructions:
function stepFrame(){
var budget = gm.cyclesPerFrame(), used = 0;
while (used < budget) {
if (bps.has(gm.getReg('PC'))) { running = false; return; }
var before = snapshotWatched();
used += gm.step(); // one 6502 instruction + IRQ timer
if (watchedChanged(before)) { running = false; return; }
}
}
- Side-effect-free reads. The hex and disassembly views read through
gm.peek(), which returns RAM / ROM / register-file bytes directly and, crucially, does not advance the VRAM auto-increment port or the AY latch, so auto-polling a view never corrupts machine state. - Direct register state. Fake6502 exposes
a/x/y/sp/pc/flagsas live fields;getReg/setRegread and write them, and the status byte is surfaced as clickable N/V/B/D/I/Z/C flag chips. - Execution breakpoints are a JS
Setof PC values checked before each instruction; write watchpoints snapshot the watched addresses before an instruction (viapeek) and compare after, pausing on a change. - Reused decoder. The Gamate CPU is a plain NMOS 6502, so the shared
debugger/src/cpus/mos6502.jsdisassembler is bound directly — no new decoder was needed.
Architecture
The Gamate (Bit Corporation, 1990) is an 8-bit handheld game console and a Game Boy rival of its era. It is built around a 6502 (the UMC UM6502, an NMOS 6502 clone) clocked at 4.433 MHz ÷ 2 ≈ 2.22 MHz, with a 160×150 LCD showing four shades of grey.
- 6502 CPU — accumulator A, index registers X and Y, stack pointer S, 16-bit PC and the N/V/B/D/I/Z/C status flags.
- Memory map — 1 KB system RAM at
$0000(mirrored to$1FFF), the AY PSG at$4000, the joystick latch at$4400, the LCD registers at$5000, the cartridge window at$6000–$DFFF, and the 4 KB BIOS at$E000(mirrored into the vector page). - Video — a custom LCD controller with 16 KB of video RAM addressed through a windowed, auto-incrementing port:
XPOS/YPOSlatch a 13-bit word address, and a byte-pair (two bitplanes) at that word carries eight 2-bit pixels.LCDCONcontrols display-blank, plane-swap, the window mode and the auto-increment direction;XSCROLL/YSCROLLscroll the visible window. - Timer & interrupts — a hardware timer asserts the 6502 IRQ line about 2.25 times per frame (every 16384 CPU cycles).
The 6502 core is Mike Chambers' compact public-domain Fake6502, reused unchanged; the rest of the machine — bus, LCD controller, PSG glue, input and timer — is authored in gamate-machine.js, and the debugger reads it live each refresh.
Sound
Pattern S (register-driven synthesis) with the shared AY-3-8910. The Gamate's sound hardware is a General Instrument AY-3-8910 PSG mapped at $4000–$400F and clocked at 4.433 MHz ÷ 4 = 1.108 MHz. Rather than write a new chip, the machine instantiates the site's shared debugger/src/chips/ay8910.js at that clock.
How writes are captured. The board decodes a write to $4000 + r as “set AY register r”, so the bus write handler simply calls ay.writeReg(a & 0x0f, v). The three square-wave tone channels, the noise LFSR and the hardware envelope are all reproduced by the shared core from those register values.
Sample rate / pitch. The AY core is constructed with EmuAudio.sampleRate, so it advances its generators by (clock/8)/sampleRate internal ticks per output sample and the pitch is exact with no resampling. Each video frame the loop calls ay.renderStereo(Math.round(EmuAudio.sampleRate/60)) and pushes the interleaved-stereo Int16 block to the shared EmuAudio sink; a one-pole DC blocker in the core removes the unipolar DAC offset.
Mute contract. window.EMU_BOOT.transport exposes isMuted() / setMute(m) delegated straight to EmuAudio. Audio starts muted (browsers block audio before a gesture); the shell's Sound button resumes the AudioContext and unmutes from a real click. Sound is fully supported and plays whenever a cartridge programs the AY-3-8910; the page starts muted until the Sound button is clicked.