HP-41C
This is a browser build of the HP-41C, the landmark programmable, alphanumeric, expandable RPN calculator. The HP-41C (1979) is built on the HP Nut - a 56-bit serial BCD processor - and boots to its RPN display showing 0.0000, ready for entry. The core is written from scratch in JavaScript, ported behaviour-for-behaviour from Eric Smith's nonpareil, so the whole machine, the NUT microcode ROM, the nibble RAM, the Coconut LCD driver and the scanned keyboard, is emulated with no wasm.
Because the loop is ours, the Nut plugs into the shared debugger: single-step the processor, read and write the A/B/C/M/N field registers and nibble RAM, disassemble Nut code, and set breakpoints and RAM write-watchpoints. Type on the on-screen HP-41 keyboard or your physical keyboard (ESC = ON, ← clears). The HP-41 system ROM is © Hewlett-Packard, self-hosted here (the redistributable "GFF" dump) for emulation.
Runs on: Web browser
HP-41C Online Emulator
Play HP-41C using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| HP-41C RPN | HP-41C | HP-41C | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
There is no upstream runtime to vendor: the HP Nut CPU, the Coconut LCD driver and the keyboard scanner are written from scratch in JavaScript, ported behaviour-for-behaviour from Eric Smith's nonpareil (proc_nut.c, digit_ops.c, coconut_lcd.c; GPL-3.0). Three plain-global scripts load in order:
// 1. the system ROM image, 2. the Nut core + LCD, 3. our boot loop
<script src="hp41-rom.js"></script> // window.HP41_ROM: 12288 10-bit words (pages 0-2)
<script src="hp41-nut.js"></script> // window.HP41Nut(rom) -> the machine
Boot. Construct the machine, run the Nut from address 0 so the ROM powers on (a cold machine shows MEMORY LOST, exactly like real hardware), dismiss it with the back-arrow key so it settles to 0.0000, then paint the 12-digit 14-segment LCD each frame from the display registers:
var M = HP41Nut(HP41_ROM);
runToSleep(); // power-on -> idle (MEMORY LOST)
M.press(0xc3); runToSleep(); M.release(0xc3); // clear to 0.0000
(function loop(){
for (var i=0; i<budget; i++) M.step(); // one Nut instruction each
render(M.segments()); // 12 x 14-segment bitmaps -> canvas
requestAnimationFrame(loop);
})();
The machine is ordinary objects - no wasm heap - so the debugger reaches every nibble directly:
| Member | Kind | What it does |
|---|---|---|
M.step() | method | Run exactly one Nut instruction (one or more machine cycles). The single-step primitive. |
M.executeCycle() | method | Run one machine cycle: fire the per-cycle keyboard scan and LCD refresh, then execute one word. |
M.a / M.b / M.c / M.m / M.n | field | The five 56-bit working registers as 14-nibble arrays. Also M.g, M.p, M.q, M.fo, M.s[], M.pc, M.stack[], M.carry, M.decimal. |
M.getUcode(a) | method | Read a 10-bit ROM word (side-effect free), for the hex/disassembly views. |
M.ramPeek/ramPoke(reg,nib) | method | Read/write one RAM nibble with no side effects - the debugger's memory + watchpoint surface. |
M.press(kc) / M.release(kc) | method | Press/release a key by Nut hardware keycode - how both the on-screen keyboard and the physical keyboard inject keys. |
M.segments() | method | The 12 current 14-segment display bitmaps (with punctuation + annunciator bits) for the canvas. |
Debugger integration
The plug-in (hp41c-debug.js) reads the live core from window.EMU_BOOT and calls EmuKit.defineMachine with a transport, the Nut register set and the memory. The Nut is a new architecture with no existing decoder, so a disassembler ships as /debugger/src/cpus/nut.js.
The Nut decoder is a port of nonpareil's dis_nut.c. It splits on the low two bits of the 10-bit word: 00 the large misc/special family (status, pointer, register moves, RAM & display I/O), 01 the two-word long branch (gosub/golong with a 16-bit absolute target), 10 the field-select arithmetic on A/B/C over one of eight fields (p x wp w pq xs m s), and 11 the short conditional branch. One wrinkle: the shared hex/disasm views mask memory reads to 8 bits, but Nut words are 10 bits - so the embed publishes window.NUT_WORD_READ and the decoder pulls full-width words through it, keeping true word addresses.
What was customised for the debugger. The whole core is authored to be inspected: registers are read and written straight off the machine object, single-step is M.step(), and the run loop is ours. A cold-boot to 0.0000 was scripted (power-on, then a back-arrow keystroke to clear the authentic MEMORY LOST). Breakpoints are a host-side Set of 16-bit PC word addresses: when any are set the loop steps one instruction at a time and compares the PC. Write watchpoints sample the watched RAM nibble addresses after each stepped instruction and halt on a change, so a write anywhere on the RAM bus (register store or display I/O) trips them. A/B/C/M/N are 56 bits, shown as a low and a high 28-bit half.
Architecture
The HP-41C (1979) is HP's landmark programmable, alphanumeric, expandable RPN calculator, built on the HP Nut - a 56-bit serial BCD CPU (HP part 1LA5).
- Nut core - five 56-bit working registers
A B C M N(fourteen 4-bit digits each) operated a FIELD at a time (p x wp w pq xs m s), the two-digit scratchG, two 4-bit digit pointersP/Q, the 8-bit flag-outputFO, fourteen status flagsS0..S13, a 4-level return stack and a carry. Arithmetic runs in BCD decimal or hex mode. Program memory is 10-bit words in pages of 4096. - ROM - the HP-41 operating system occupies pages 0-2 (12K words): the NUT microcode that implements the RPN stack, the function catalog, alpha, and program execution.
- RAM - nibble-register data store (each register is 14 digits). The C model fits the status registers plus four memory chips; user registers and programs live here.
- Coconut LCD driver (HP part 1LA4) - a bus peripheral at address
0xFDholding three 12-digit registersA/B/C; each digit's code(c<<6 | (b&3)<<4 | a)indexes a 14-segment character generator, with two bits ofbselecting the period/comma/colon punctuation and a 12-bit annunciator register beneath. - Keyboard - a scanned matrix; each key is a hardware keycode
(column<<4 | row)latched into a buffer the ROM reads. The ON key (0x18) wakes the Nut from its low-power light/deep sleep.
Every part is a plain JavaScript object, which is what makes the Nut a clear, fully steppable debugging target in the browser.