Search › A-Z › P › PET 2001 Emulator (masswerk)
PET 2001 Emulator (masswerk)
The masswerk PET 2001 emulator is a browser-based Commodore PET 2001 emulator by Norbert Landsteiner, based on an original PET emulation by Thomas Skibo. It emulates the 6502 CPU and CRT display and adds keyboard, tape and disk image handling plus a built-in BASIC and 6502 assembler/disassembler.
Runs on: Web browser
PET 2001 Emulator (masswerk) Online Emulator
Play PET 2001 Emulator (masswerk) using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| PET BASIC | PET 2001 Emulator (masswerk) | Commodore PET 2001 | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
masswerk's PET 2001 is a set of plain JavaScript modules. Vendor them and load them in dependency order, but skip its own controller (pet2001ctrl.js) — that owns the page UI and a requestAnimationFrame loop that can't be paused or stepped, which the debugger needs. Instead replicate the controller's run(): build the machine, hand it a canvas context and a keyboard, and drive it from a loop you control.
var ctx = document.getElementById("screen").getContext("2d");
var keys = new PetKeys(null); // physical keyboard → the PET key matrix
var pet = new Pet2001();
pet.init(controllerStub, ctx, keys, { RAM_SIZE:0x4000, ROM_VERSION:"2" });
(function loop(){ pet.cycle(16667); requestAnimationFrame(loop); })(); // ~1 MHz / 60 Hz
The machine is plain objects. Everything the debugger needs is a method on the machine or its CPU:
| Member | Kind | What it does |
|---|---|---|
pet.cycle(ticks) | method | Run the machine for ticks clocks (steps I/O + CPU); returns <0 if the CPU jams. cycle(1) runs one instruction — the single-step primitive. |
pet.reset() | method | Reset the machine. |
pet.read(a) / pet.write(a,v) | method | CPU bus read/write — poke memory live. |
pet.getCPUStatus() | method | Live 6502 state: {pc,a,x,y,sp,sr, c,z,i,d,b,v,n}. |
pet.setRegister(r,v) | method | Write a register (pc a x y sp sr). |
pet.setFlag(f,v) | method | Write a status flag (c z i d b v n). |
PetKeys(elementId) | class | The keyboard: attaches window key events and drives io.setKeyrows(), the PET key matrix. |
Because the CPU and memory are ordinary JavaScript, the debugger single-steps with cycle(1), reads and writes the registers through getCPUStatus / setRegister, and implements breakpoints and watchpoints as host-side checks around the loop — no changes to the emulator.
Debugger integration
The masswerk PET even ships its own debugger (petdebug.js) built on the same getCPUStatus / setRegister surface, which confirms the machine is fully introspectable from JavaScript. Our plug-in uses that surface directly.
- Registers come from
pet.getCPUStatus()each refresh and are written back withsetRegister/setFlag. - Memory is
pet.read/write; the hex view skips the$E8xxI/O window so auto-polling never trips a VIA side effect. - Single step is
pet.cycle(1)(one instruction). - Breakpoints and watchpoints are host-side: the boot loop checks the PC against a set before each instruction, and wraps
pet.writeto catch a watched address.
Architecture
The PET 2001 is Commodore's first computer — a 6502, a discrete-logic video circuit driving a 40×25 character display, two 6520 PIAs and a 6522 VIA for I/O, and Commodore BASIC in ROM. masswerk's emulation keeps each as its own module:
cpu6502— the 6502 core, cycle-stepped.pet2001— the machine: RAM, ROM banks and the bus routing thatread/writefollow.pet2001video— the character-ROM video, rastered to the canvas.pet2001io— the PIAs/VIA and the keyboard matrix (setKeyrows).pet2001ieee— the IEEE-488 bus, used to load.prgprograms.pet2001roms— the character, BASIC and KERNAL ROMs, embedded.
It boots straight to ### COMMODORE BASIC ### — a complete, inspectable machine with nothing to load.
Sound
The PET's sound is a single bit: the 6522 VIA's CB2 line, gated by the shift register and clocked by timer T2, drives a small piezo speaker — a square-wave "CB2 sound". This is a V-native integration: masswerk's core already ships a complete WebAudio pipeline in pet2001audio.js (its own AudioContext with a ScriptProcessorNode and a gain node). The I/O chip samples the CB2 level every CPU tick and pushes it to that pipeline via audio.writeSignal(); the pipeline resamples the 1 MHz signal stream down to the context's sample rate on demand. We do not reroute it through the shared sink — we simply enable it and gate it.
The embed had the core built with USE_AUDIO:false and never loaded the audio module; we load pet2001audio.js and build with USE_AUDIO:true. Because the pitch and waveform come from the emulated VIA (T2 period + shift-register value) and the module resamples against audioContext.sampleRate internally, no rate constant needs to be set — pitch is inherent in the hardware emulation.
The mute contract lives on EMU_BOOT.transport: isMuted() returns our flag and setMute(m) drives the native pipeline — mute calls audio.setVolume(0) + audio.suspend(), unmute calls audio.setVolume(1) (which reconnects the gain chain and resumes the context). It starts muted, since browsers block audio before a user gesture; the shared Sound button unmutes from a real click.
The PET boots to the READY. BASIC prompt and stays there; we do not force any startup noise. Sound is fully supported through the native CB2 pipeline, so anything that drives it (a BASIC program that POKEs the VIA shift register and T2, or any software that toggles CB2) is heard the moment it plays, once the Sound button unmutes.