The Museum of HP Calculators
The hardware technology used in HP calculators improved steadily over the years, moving from multichip to single chip designs with greater speeds and capabilities. However, the programming model remained much the same from the HP-35 though the HP-30E/C series. This document provides a brief description of the programming style of these early models. The HP-01, HP-41C and the Saturn processors were similar. Their differences are highlighted below.
The HP calculator CPUs were optimized for floating point numbers. Each main register consisted of 14 of 4 bits each (56 bits per register) This allowed each register to hold a 10 digit mantissa, a 2 digit exponent and signs for the mantissa and exponent. Each digit or sign occupied 4 bits. This is commonly referred to as Binary Coded Decimal (BCD) encoding. The 56 bit registers were:
Many operations were controlled by a field select option. This allowed portions of each register to be accessed independently. The field select codes where:
|MS||Mantissa and Sign|
|P||Pointer (The nibble indicated by the P register)|
|W||Word (the entire register)|
|WP||Word up to and including nibble indicated by P register from right to left. For example, if P=3, WP would refer to nibbles 0, 1, 2, and 3.|
The sign nibbles are set to 0 if positive and 9 if negative.
The Saturn processors added field selects for Byte and Address.
The classic models used 8 bit instruction addresses plus ROM select instructions to activate the appropriate 256 word ROM. The 20 series used 12 bit addresses. The classics had a subroutine stack depth of one versus 2 for the 20 series.
The user stack was contained in on-chip memory, however, multiple user memories as found on the HP-45, financial memories and program memory all required additional data storage. To access registers in off-chip memory, the CPU first supplied the address to the storage chip using C-> DATA ADDRESS and then read or wrote the data at that address. 20 series machines added operations that allowed a read or write to off-chip memory with a single instruction.
In general, registers (or portions) could be cleared, copied, exchanged, incremented or decremented. In addition, registers could be shifted left or right and tested. Portions of registers to act on where indicated by enclosing the field select code in brackets after the instruction. Each instruction occupied 10 bits.
The following symbols are used in the Instructions below:
|fs||field select (one of the codes above: M, MS, etc.)|
|gr||general purpose register (A,B, or C)|
|bit||a value of 0 or 1|
|digit||a value between 0 and 9|
|stat||a status register bit number (0 - 11)|
|ptr||a pointer value (0-13)|
|rom||a rom number (max depends on model)|
|CLEAR REGISTERS||Set all 56 bit registers to zero.|
|0 -> gr[fs]||Set the selected part of gr register to zero.|
|A -> B[fs]
B -> C[fs]
C -> A[fs]
M -> C
C -> M
|Copy the selected part of the register on the left to the register on the right. Examples: B - > C[X]: copy the exponent part of B to the exponent part of C. A -> B[WP]: copy A to B from the right most nibble up through the nibble indicated by the P register.|
|A EXCHANGE B[fs]
A EXCHANGE C[fs]
C EXCHANGE B[fs]
C EXCHANGE M
|Exchange the selected portions of the two registers. Note that transfers to/from M are always full word transfers.|
|A + B -> A[fs]
A + C -> A[fs]
A + C -> C[fs]
C + C -> C[fs]
A - B -> A[fs]
A - C -> C[fs]
A - C -> A[fs]
|Perform math on the selected portions of the indicated registers|
|A + 1 -> A[fs]||Increment the selected portion|
|C + 1 -> C[fs]|
|A - 1 -> A[fs]||Decrement the selected portion|
|C - 1 -> C[fs]|
|-C -> C[fs]||Negate C (tens complement)|
|-C -1 -> C[fs]||Negate and decrement C (nines complement)|
|SHIFT RIGHT gr[fs]||Shift selected portion of gr right|
|SHIFT LEFT A[fs]||Shift selected portion of A left|
|IF B[fs] = 0
IF C[fs] = 0
IF A >= C[fs]
IF A >= B[fs]
IF A[fs] >= 1
IF B[fs] >= 1
|Test for selected part of of register (Follow these with THEN GO TO)|
|THEN GO TO lbl||Go to label lbl if true (must follow one of the IFs above)|
|GO TO lbl||Unconditional go to|
|IF NO CARRY GO TO lbl||Go to label if carry bit is not set|
|CLEAR STATUS||Clear all 12 status bits|
|bit -> Sstat||Set Status bit stat to bit (Example: 1 -> S10)|
|IF Sstat = 0||Test if status bit equal to 0 (follow with THEN GO TO)|
|IF Sstat # 1||Test if status bit not equal to 1 (same as above test)|
|ptr -> P||Set P register to ptr (ptrth nibble for P field select or through ptr for WP)|
|P + 1 -> P
P -1 -> P
|Increment and decrement P
|IF P # ptr||Test if P not equal to ptr (follow with THEN)|
|C -> STACK||Push C onto the stack (E->F, D->E, C->D) always whole word|
|STACK -> A||Pop stack into A (D->A, E->D, F->E) always whole word|
|ROTATE DOWN||Move D to C, E to D, F to E and the original C to F (just like pressing the Roll Down key.)|
|JSB lbl||Jump to Subroutine labeled xyz (one level of subroutines on the Classics, 2 on 20 series.)|
|RETURN||Return to the instruction after the JSB.|
|LOAD CONSTANT digit||digit -> C at the Pth nibble. P is then decremented so multiple digits can be loaded easily.|
|SELECT ROM rom||Select ROM #rom for addressing.|
|KEYS -> ROM ADDRESS||Jump to the offset of the current keycode in the currently selected ROM.|
|DISPLAY OFF||Turn the display off|
|DISPLAY TOGGLE||Toggle the LED display on/off.|
|C -> DATA ADDRESS||Set current data address to value in C* Precursor to reading or writing off-chip memory.|
|DATA -> C||Read data in to C register*|
|C -> DATA||Write data from C register*|
|NO OPERATION||Does nothing.|
* For example, to write the value in B into the address indicated by C, the program could do:
C -> DATA ADDRESS B -> C[W] C -> DATA
George Weigt has provided an opcode map in the articles forum.
Eric Smith was able to piece together the entire HP-45 firmware listing by reading patent disclosures. In addition to reading the firmware, you can run and step through it on this HP-45 Microcode simulator.
This code segment normalizes numbers as keyed into the user. The user input in the A and B registers is normalized into the C register. This code segment is from the HP Journal (used with permission.) Sign nibbles are 0 if positive, 9 if negative.
L01063 .....111.. FIX5: P - 1 -> P L01064 .1111.1.1. C + 1 -> C[X] L01065 ..111.11.1 -> L1073 JSB FIX7 . . . L01070 ..11.11.1. FIX3: 0 -> C[XS] ; restore + (0) sign after testing in FIX1 L01071 11.1..11.. FIX4: 13 -> P ; Correct Exponent for decimal position L01072 .22.214.171.124. C - 1 -> C[X] L01073 ........1. FIX7: IF B[P] = 0 L01074 ..11..1111 -> L1063 THEN GO TO FIX5 L01075 11....11.. 12 -> P L01076 1..11...1. FIX6: IF A[P] >= 1 ; Remove leading zeros & adjust exponent L01077 .11111..11 -> L1174 THEN GO TO FIX2 L01100 .1.....11. SHIFT LEFT A[M] L01101 .126.96.36.199. C - 1 -> C[X] L01102 ..11111..1 -> L1076 JSB FIX6 . . . L01146 .111111.1. FIX1: C + 1 -> C[XS] ; Exponent negative? (9 represents - sign) L01147 ..111...11 -> L1070 IF NO CARRY GO TO FIX3 L01150 ..188.8.131.52. 0 - C -> C[X] ; Complement Exponent L01151 ..111..1.1 -> L1071 JSB FIX4 . . . L01167 111.1.111. FIX0: A EXCHANGE C[W] ; Set C equal to A L01170 .11...111. C -> A[W] ; (there is no A -> C instruction) L01171 1..11..11. IF A[M] >= 1 ; A Mant # 0? L01172 .11..11.11 -> L1146 THEN GO TO FIX1 L01173 1.111..11. 0 -> A[M] ; Zero normal form L01174 111.1..11. FIX2: A EXCHANGE C[M]
The HP-01 was similar to the classic series except that its registers were only 48 bits wide.
The HP-41C added additional capabilities such as:
Saturn processors expanded on the HP-41C processor by widening the main registers to 64 bits, widening program addresses to 20 bits, expanding subroutines to 6 registers (plus two for the interrupt system.) and adding:
Additional information may be found on the Saturn page.
Go to the HP-45 Microcode Simulator
Go to classic series technology
Go to 20 series technology
Go to 30 series technology
Go to the main exhibit hall ????app ????APP ???3 ???? ??????