HP-48
This is a browser build of the HP-48GX graphing calculator, running Daniel Nilsson's portable-C hpemu core compiled to WebAssembly. The HP-48 series (1990) is built on the HP Saturn, a 4-bit-nibble CPU with 64-bit working registers, and boots to the familiar RPL stack. Type on the on-screen HP-48 keyboard or your physical keyboard; the calculator's ROM, RAM, timers and 131×64 LCD are all emulated.
The Saturn is driven through a small set of on-demand hooks rather than its own run loop, so it plugs into the shared debugger: single-step the processor, read and write the A/B/C/D field registers and nibble memory, disassemble Saturn code, and set breakpoints and watchpoints. The HP-48 system ROM is © Hewlett-Packard and is self-hosted here for emulation.
Runs on: Web browser
HP-48 Online Emulator
Play HP-48 using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| HP-48GX RPL stack | HP-48 | HP-48 | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
The core is hpemu by Daniel Nilsson (GPL v2), an HP-48GX emulator whose Saturn CPU, bus, timers and keyboard are portable C. Its desktop build renders through SDL and owns its own loop; we compile just the core to WebAssembly with Emscripten, drop the SDL/GUI layer, and drive the processor ourselves so the debugger can pause and step it.
Boot. Load the wasm core, run the Saturn from address 0 so the ROM powers on, and paint the LCD out of video RAM every frame:
hp_init(); // bus_init: ROM + warm RAM image + reset
hp_boot(); // run the Saturn until the RPL stack is up
(function frame(){
hp_run(66667); // one video frame of Saturn cycles
blitLCD(); // hp_lcd() 131x64 grayscale -> canvas
requestAnimationFrame(frame);
})();
The core is reached through a handful of exported C functions (all in hp48hooks.c), each called on demand from JavaScript, never per instruction:
| Export | Kind | What it does |
|---|---|---|
hp_step() | fn | Run exactly one Saturn instruction (execute_instruction) plus the due timer/display cycle events. The single-step primitive. |
hp_run(cycles) | fn | Run a batch of Saturn cycles at full speed (one video frame's worth per call). |
hp_field(r,hi) / hp_set_field | fn | Read / write half of a 64-bit register A B C D (or a scratch R0..R4): low or high 8 nibbles. |
hp_scalar(i) / hp_set_scalar | fn | The scalar state: PC D0 D1 P ST HST, the carry and decimal-mode flags, IN OUT. |
hp_peek(a) / hp_poke(a,v) | fn | The 1M-nibble Saturn bus, resolved through the live memory map with no I/O side effects, so an auto-polling hex view is safe. |
hp_lcd(out) | fn | Copy the 131×64 four-level grayscale LCD (scanned out of video RAM) into a buffer for the canvas. |
hp_key_down(r,c) / hp_key_up | fn | Press / release a key in the hardware matrix by (row, col); hp_on_down/up for the ON key. |
Because every handle is a plain function call, the debugger reads registers and memory each refresh, single-steps with hp_step(), and implements breakpoints and watchpoints host-side, with no change to the emulator's hot path.
Debugger integration
The plug-in (hp48-debug.js) reads the live core from window.EMU_BOOT and calls EmuKit.defineMachine with a transport, the Saturn register set and the memory. The Saturn is a nibble machine with no existing decoder, so a new disassembler ships as /debugger/src/cpus/saturn.js.
The Saturn decoder is a port of hpemu's own disassembler: the opcode trie is walked one nibble at a time, and instruction length is derived from the highest nibble index each operand format references, the exact program-counter advance the core uses. It covers the field-select ALU ops (A/B/C/D over the P/WP/XS/X/S/M/B/W fields), the LC load-constant, GOTO/GOSUB/GOVLNG/GOSBVL branches, the D0/D1 pointer and memory (DAT0/DAT1) instructions and the tests; anything unrecognised decodes as one .nibble.
Exactly what was changed in the source. The only file added to hpemu is hp48hooks.c - a layer of EMSCRIPTEN_KEEPALIVE functions over the core. It reads the Cpu struct (the reg[4][16] A/B/C/D nibble arrays, d[2], p, pc, st, hst, the return stack), resolves the bus with the side-effect-free bus_fast_peek, and forwards keys to kbd_key_pressed/released. The SDL display layer is dropped: the LCD scan from display.c is reproduced without SDL, filling a grayscale buffer straight from video RAM. Nothing in the per-instruction path was touched.
Single-step is hp_step(). Breakpoints are a host-side Set of 20-bit PC values: when any are set the JavaScript loop runs one instruction at a time and compares the PC before each. Watchpoints sample the watched nibble addresses after each stepped instruction and halt on a change. A/B/C/D are 64 bits, so the registers window shows each as a low and a high 32-bit half.
Architecture
The HP-48GX (1993) is built on the HP Saturn, a 4-bit-nibble CPU: it presents nibble-addressed memory (a 20-bit, 1M-nibble space) and works on 64-bit working registers a field at a time.
- Saturn core - four 64-bit working registers
A B C D(sixteen nibbles each), five scratch registersR0..R4, two 20-bit data pointersD0/D1, a 4-bit field pointerP, the 16-bit program statusST, the hardware statusHST, an 8-level return stack and a carry. Arithmetic runs in hex or BCD decimal mode. - Memory - a nibble bus configured at boot by the ROM's
CONFIGsequence: 512 KB system ROM, 128 KB built-in RAM, the memory-mapped hardware registers, and two plug-in card ports. - Display - a 131×64 monochrome LCD scanned directly out of video RAM at a base address the ROM programs; the panel is refreshed a line at a time, and a short pixel-history gives the familiar 4-level grayscale.
- Keyboard - a 9-row matrix read through the
IN/OUTregisters; each key is a (row, col) pair, with ON wired to wake the CPU from its low-power SHUTDN state. - ROM - the HP-48GX operating system, a system image © Hewlett-Packard, installed into the ROM space.
Every part is reached from JavaScript through the small hp48hooks.c shim, which turns an otherwise SDL-bound emulator into a live, steppable Saturn debugging target in the browser.