SearchA-ZH › HP-41C

HP-41C

1979 Open source · GPL-3.0 Online

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.

Core project (nonpareil) ↗

Visit the official site ↗

Runs on: Web browser

HP-41C Online Emulator

Play HP-41C using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
HP-41C RPNHP-41CHP-41CgreyOpen ⛶

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:

MemberKindWhat it does
M.step()methodRun exactly one Nut instruction (one or more machine cycles). The single-step primitive.
M.executeCycle()methodRun 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.nfieldThe 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)methodRead a 10-bit ROM word (side-effect free), for the hex/disassembly views.
M.ramPeek/ramPoke(reg,nib)methodRead/write one RAM nibble with no side effects - the debugger's memory + watchpoint surface.
M.press(kc) / M.release(kc)methodPress/release a key by Nut hardware keycode - how both the on-screen keyboard and the physical keyboard inject keys.
M.segments()methodThe 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 scratch G, two 4-bit digit pointers P/Q, the 8-bit flag-output FO, fourteen status flags S0..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 0xFD holding three 12-digit registers A/B/C; each digit's code (c<<6 | (b&3)<<4 | a) indexes a 14-segment character generator, with two bits of b selecting 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.