- Overview
- Detailed operations
- ROM Mapping
1) HP25 vs HP21 vs HP35
The HP-25 was designed as an advanced programmable scientific calculator with a 2048 instruction (10 bits each) ROM (the HP-21 had 1024 instructions and the HP-35 had 768 instructions 256 * 3).
It had a data storage chip with 16 registers of 56 bits each (total = 224 bytes) :
- 8 registers were used for user data (8*14 BCD digits),
- 7 registers were used for user programs (7*56= 392 bits == 49 program lines of 8 bit each),
- 1 were used for the LAST x function.
The firmware study is organized in the following order:
- this 'overview' page will cover the ‘key phrase’ logic, the ‘Key codes” computation, and 3 general sequences (- the Initialization after power up & the ‘wait a key’ loop, the ‘enter a digit sequence’, and the function sequence (ln for example).
- different tabs (upper part of the page) will cover different routines (launching a program, SST, storing data, different display formatting type FIX, SCI, ENG, etc…).
- a last tab will introduce a ROM map for the HP-25.
2) Key phrases
One HP-25 major advance (as compared to the HP-55 or HP-65) is its programming model based on "key phrases" rather than keystrokes.
A key phrase is merely a sequence of keystrokes that together perform one compact operation. For example, STO + 2 is a 3 keystroke "key phrase” : 23 51 02 that takes only one step (see display's photo : step 01 key phrase '23 51 02' entered as 'STO' '+' '2', in PRGM mode).
Another example, prefixed keys takes one step “f sin” and is displayed '14 04'.
In memory such a "key phrase" will be coded with an 8 bit "key code" ; the same token will be used in program memory (PRGM mode) or in direct RUN mode.
Visually, each key on the keyboard has a 2-digit keycode :
- the digit keys being coded 00 through 09,
- all other keys are coded by their position on the keyboard matrix. : for example, the blue 'g' key is coded 15 = row 1, column 5.
The program memory contains space for 49 key codes encoding 49 key phrases : 1 register is 14 digits long = 7 bytes.
49 key codes require 7 Ram registers.
Note that this model allows another innovative advance : the "SST Single Step RUN".
As indicated above, in direct RUN mode, the same "key code" allows the user to execute his program one key phrase at a time with the SST key (a debug key) held down.
When requested by the keyboard or read from program memory the same 'key code " is serviced in the same way.
In SST mode the display will show the program line number (user program counter) and the key phrase that is to be executed (not the code code ; that's the innovation).
When releasing SST the operation's result will appear.
Debugging a program is quite easy : the user can execute the program flow step by step.
3) 8 bit key codes
As explained earlier, an 8 bit key code is used in both modes (PRGM and RUN) to access the HP-25 functions. In the former ‘Classics” models HP-35, 45 etc, this value -which is a token built by the keyboard hardware- was used as a displacement on a table in ROM where the action or a jump to the action was coded.
Another more flexible scheme is used in the Woodstock : the token returned by the hardware and the key-code are two different things. A keystroke returns a ‘token’ which branches to a place in ROM where a key-code is computed and stored in the A register (specifically a[2] and a[1]) and used by the ‘a -> rom address’ to jump to the appropriate rom space and to service the action requested.
Let’s take an example. When key ‘x<->y’ is pressed, a ‘token’ ‘103’o is return by the keyboard hardware and is used as a displacement on Rom 1 (address 0400o).
Adress 503o is the entry point to action ‘kxexy’ : x exchange y key. At this step, register C is null. By a series of steps ‘c + 1 -> c[x ]’ and tests the value in C is incremented to ‘C=000000000000fa’ and control branches to label ‘execut:’ where register A is prepared for execution : C is copied to A and a[2] is forced to 1 - for actions of this type (a + 1 -> a[xs]).
Then the instruction ‘a -> rom address’ (address 1771o) is executed with A=210000000001fa.
From the program counter the current Rom start is extracted : PC AND (NOT 0377o) = 01400o = 0300h (rom 3).
Then a[2]=’a’ <<6 and a[1]=’f’ are added making 0300hex + 10hex + ‘f’hex = 31fhex = 1437o, where execution continues.
NB: to study this in detail, please use the emulator with a breakpoint in 503o.
Use the 'token' code table given below (addresses are in octal starting @ 0400o + displacement):
Most 'tokens' - after being decoded - will end up @ address 1771o where is located the main switching instruction 'a -> rom address'.
Token table (HP25 ROM 1 ).
e.g. the RCL key : key code = 100 > address 0400o + 100 = 0500o.
L00500: | 0100011011 | krcl: go to ercl | ; RCL key | 100 | |
L00501: | 1100011011 | ksto: go to esto | ; STO key | 101 | |
L00502: | 0111101110 | kroll: c + 1 -> c[x ] | ; roll down key | 102 | |
L00503: | 1010010111 | kxexy: if n/c go to kyexy1 | ; x <>y key | 103 | |
L00504: | 0111101110 | ksig+: c + 1 -> c[x ] | ; sigma + key | 104 | |
…/… | |||||
L00540: | 0111101110 | k9: c + 1 -> c[x ] | ; 9 key | 140 | |
L00541: | 1001110111 | k8: if n/c go to k7. | ; 8 key | 141 | |
L00542: | 1001111111 | k7: go to k6. | ; 7 key | 142 | |
L00543: | 1110010100 | k-: if 1 = s sto 14 | ; - key | 143 | |
L00544: | 0100000000 | then go to esto1 | |||
…/… | |||||
L00560: | 0111101110 | k3: c + 1 -> c[x ] | ; 3 key | 160 | |
L00561: | 0111101110 | k2: c + 1 -> c[x ] | ; 2 key | 161 | |
L00562: | 1010011111 | k1: if n/c go to k1. | ; 1 key | 162 | |
L00563: | 0111101110 | k*: c + 1 -> c[x ] | ; * key | 163 | |
…/… | |||||
L00620: | 0000100000 | kr/s: select rom 0 | ; r/s key | 220 | |
L00621: | 1101100111 | k.: go to k.. | ; . Key | 221 | |
L00622: | 1000010000 | k0: return | ; 0 key | 222 | |
L00623: | 0111101110 | k/: c + 1 -> c[x ] | ; / key | 223 | |
…/… | |||||
L00640: | 0111101110 | k6: c + 1 -> c[x ] | ; 6 key | 240 | |
L00641: | 0110110111 | k5: if n/c go to k5. | ; 5 key | 241 | |
L00642: | 0110111111 | k4: go to k3. | ; 4 key | 242 | |
L00643: | 0111101110 | k+: c + 1 -> c[x ] | ; + key | 243 | |
…/… | |||||
L00660: | 1001011111 | kf: go to ef | ; f yellow key | 260 | |
L00661: | 0111100011 | kgto: go to egto | ; gto key | 261 | |
L00662: | 0011100000 | kbst: select rom 3 | ; bst key | 262 | |
L00663: | 0011100000 | bsst: select rom 3 | ; sst key | 263 | |
L00664: | 0010001101 | kg: jsb zap | ; g blue key | 264 | |
…/… | |||||
L00720: | 1101010111 | keex: go to eeex | ; eex key | 320 | |
L00721: | 0000100000 | kchs: select rom 0 | ; chs key | 321 | |
L00722: | 0000000000 | nop | ; nop | 322 | |
L00723: | 1101110011 | kenter: go to eenter | ; enter key | 323 | |
L00724: | 0111101110 | kclx: c + 1 -> c[x ] | ; clx key | 324 |
Key-codes table (Hex values)
The following table gives for each action the key-code built in register A register (a[2] and a[1]) before executing the ‘a -> rom address’.
There are 3 columns, one for the 'plain' key and 2 other for the prefixed keys (f yellow prefix or g blue prefix).
note: x = 4 bit digit (STO 2 = '2a').
plain | f | g | ||
f | s[10]=1 | |||
g | s[9]=1 | |||
SST | - | 5x | - | |
BST | - | 6x | - | |
GTO xx | xx | 7x | - | |
STO x | xa | - | - | |
RCL x | xb | - | - | |
x<>y | fa | da | ea | |
R! | fb | db | eb | |
SUM+ | fc | dc | - | |
ENTER | f6 | - | - | |
CHS | f7 | d7 | e7 | |
EEX | f8 | d8 | e8 | |
CLx | f9 | d9 | e9 | |
. (dp) | f4 | d4 | e4 | |
- | f0 | d0 | e0 | |
+ | f1 | d1 | e1 | |
x | f2 | d2 | e2 | |
/ | f3 | d3 | e3 | |
R/S | f5 | d5 | e5 | |
0 | c0 | a0 | b0 | |
1 | c1 | a1 | b1 | |
2 | c2 | a2 | b2 | |
3 | c3 | a3 | b3 | |
4 | c4 | a4 | b4 | |
5 | c5 | a5 | b5 | |
6 | c6 | a6 | b6 | |
7 | c7 | a7 | b7 | |
8 | c8 | a8 | b8 | |
9 | c9 | a9 | b9 |
4) Init routine & Wait a key loop.
Let’s begin this detailed study by the ‘Init’ routine : when the machine is started, the control is given to this code and the sequence ends up in the ‘wait a key’ loop ; the idle state.
This code builds the execution context:
- status register (display mode FIX? SCI or ENG, number of digits, angle mode (DEG, GRD, RAD)
- display context (‘dp’ decimal point position, mantissa & exponent sign, not displayed digit mask)
- and loops waiting for a key to be pressed.
Only 9 instructions are dedicated to initialization tasks.
The rest of the code ($output, deb, $outpz, round, $plain, $wait) is a common sequence executed returning from ‘operation’ routine to round a result and format output context.
4.1) Status information.
The first thing done is to build the status default context:
Clear Ram,
FIX 2 display,
DEG mode,
RPGM STEP=00.
It takes only 8 instructions to do it.
Entering the routine @ 0236o, the RAM section (16*56 bits) is initialized to zero
Next, @ 0240o, the status pattern is built in register C=20000000000202 and finally is stored in m1.
The status is coded simply:
digit 0 = number of digits displayed,
digit 2 = display mode, SCI=0, ENG=1, FIX=2
digit 3, digit 4 = PRGM Step #
digit 13 = angle mode, GRD=0, RAD=1, DEG=2,
M1=20000000000202
^DEG ^ FIX 2
^=FIX
The initialization job is done in 9 steps
00000: go to pwo
00236: pwo: c -> data address ; point to RAM zone
00237: clear data regs ; clear 16 registers
00240: c + 1 -> c[x] ; “1” in c[0]
00241: c + 1 -> c[xs] ; “1” in c[2]
00242: c + 1 -> c[s] ;“1” in c[13]
00243: c + c -> c[w] ; doubled making “20000000000202”
00244: m1 exchange c ; save status in m1
00245: 0 -> c[w ] ; raz ‘c’
00246: outpu0: m2 exchange c ; start of display routine for init
Then the control goes to label ‘$outpu:’ (0247o) which is the reentry point when returning from all action routines and follows to the ‘wait a key’ loop, which is the idle state.
This code is the main highway executed each time the machine is back from a calculation : that is to say it has to handle all kind of display formats (on the HP-25 : FIX x, SCI x or ENG x where x is the number of digits to display).
Note that in the ‘init’ case, the display mode is initialized by default to FIX 2.
To fully understand this code, you will have to study it in 3 contexts : FIX, ENG and SCI.
But first, let us say a few words about the display masking techniques used.
4.2 Display masking
We have already met the major display evolution : the size.
The display in the HP-21 25 series was shorter compare to HP-35 : 12 digits instead of 15 to fit in the new plastic case (but the calculator was still keeping its 10-digit precision).
The display was reengineered consequently.
The decimal point was moved next to a digit and two of the display digits play a double role, mantissa and -when needed- exponent sign and digit.
The Display takes 12 digits to be formatted, using 2 registers A and B :
A holds the digits to be displayed – 0x0f being the blanking order- while B holds the signs (Mantissa and Exponent – coded with digit ‘2’) and the décimal point (coded with digit ‘1’).
C holds the number in full precision.
The table below shows the scheme:
e.g. the for number 6.931471805 -01 in SCI 4 (Scientific format 4 digits), the 3 registers are :
A |
|
0 |
6 |
9 |
3 |
1 |
5 |
f |
f |
f |
9 |
0 |
1 |
0 |
0 |
B |
|
2 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
0 |
C |
|
0 |
6 |
9 |
3 |
1 |
4 |
7 |
1 |
8 |
0 |
5 |
9 |
9 |
9 |
Note that A and B only use 12 positions.
Another evolution implies new firmware capabilities: the 3 formats FIX, SCI ad ENG.
It implies rounding off code and a mechanism to hide (zap) part of the digit.
Let us study how it is handled.
We are returning from action ln(2) in SCI 4 mode.
Say we have ask the natural log of 2 (ln(2)).
Back from the ln() function, SCI mode, DSP=4, the stack context is @ 0247
label $outpu:
A=06931471805999
B=00000000000000
C=M2=06931471805999
M1=20000000000104
The display shows : '6.9315 -01' but the register C knows the result better!
The file here has the trace on the emulator.
First, subroutine “deb” is called to build the display mask in B.
The mask in B will be finally '21000000020000' ; '2' being the place of the mantisa and exponent sign (if any) and '1' being the decimal point's place.
The code doesn't know yet the number of digits to show.
So it starts with '21' to build a new mask (addr 341-355) and place it in B.
Next it retrieves the status from m1 (addr 255) to figure out which display is used ;
@ 261 the number of digit (4) is in A while the type of display (SCI) is still in C.
The digit c[xs] is decremented to zero to guess the type : at 1205 it is known we are using SCI.
At 1215 we are ready to round the result retrieved fro m2 and place it in C.
We have in A the number of digits to display, we set p to 12, and we enter a loop decrementing a[x ] and p at each turn.
The rounding routine is named 'rounda'.
In our example, the effect is simple from C=06931471805999
the register A will end up to the value before zapping
A=06931543610899
and
A=06931500000000 after zapping.
@1223 the call is made to the routine 'zappo' which will 'zap' (hide) the digits right of the pointer p (0 -> a[wp]) ; filling the right part of register A with digit '0x0f' a blinking signal for the display mechanism (in ROM).
A=069315 f f f f f f f f
@1244 the exponent mask ('901' = -1) is built in C and next passed to A, digits 6 to 10 hidden (sci 4). But B must then be corrected to have the correct exponent sign in place.
This is the role of routine $dmeex entered in 00161.
Finnally, before returning, @0170 both register A and B are formatted:
A=069315 f f f90100
B=21000000020000
and the control enters the display loop @01251 outpu2 and next the wait a key loop.
By placing a break point at address 01753, in the emulator, you can experiment different display type (FIX 2, ENG 9, for example).
The key point is to understand the difference between the 'rounda' and the 'zappo' routine.
Before hiding unneeded digits a rounding action is performed.
5) Enter a digit and call a function sequence (ln).
Now, I am going to give an idea of the complete 'nominal' process : enter a number, and perform a function on it.
To keep it simple, a one digit number is entered and the ln function ('f' 'ln' keys in fact) is performed.
As usual, you will follow it with the full commented trace produced by the emulator (here).
The story begins in the idle loop, the calc waiting for a key (0742-0746).
Note that a key on the keyboard is linked to status flag s[15 ] ;
note also that the flags are 'named' in the assembler mnemonics : 'key' here and 'runpgm' for the switch (0743).
@0744 the flag 15 is raised, so the loop is broken and branching is done to displ3, where the decimal mode is taken, p and c[x ] are reset, and the key just hit is tranfered in register A.
@0770 the 'keys -> rom address' is performed jumping at 0561 label 'k2' where the value is built in C : C=00000000000002.
The one byte function token is built @0675 eplai1 (0xc2) and the 'function' execution of is performed @01771 (end of the sequence execute 01766-01771) by an operation 'a -> rom address' that take the 2 digits A[2] & A[1] of register A to compute the branch address :
pc = pc & ~0377
pc += ((a [2] << 4) + a [1]))
; the third digit being forced to 1 giving 0x1c2
From 01771o = 0x3f9 it gives address 0x31c = 1434o.
(0x300 + (a [2] << 4) + a [1]).
@1434, selecting rom 0, we enter the 'digent' routine to build the digit entered.
Flag 8 'firdig' is signaling the first digit.
We are goint to build masks.
To make it short (see the trace for details) at the end of this sequence we have formatted the display
A=02 f f f f f f f f f f f f B=21000000000000
C=00000000000000
M2=02000000000000
Note: this is not a number just a digit ; C is set to zero and the first digit is kept in M2. Please experiment with a multidigit number.
Ready to wait the next key @0753 'hi i'm woodstock' (NOP).
@0660 we have acquired the yellow prefix 'f' key.
We call 'zap' to clean C and start to build the key phrase '14' as if we were in PRGM mod : shared code.
@0633 flag 9 'f' is raised just to remember.
@0651 label 'crap', we now know it is not PRGM mode, so we return to the loop (@753 hi i'm woodstock).
@770 the key is decoded 'keys -> rom address ' and a branching is made to addr 0752 label k7, and the value 7 is built in C by iterations (k7,k6, K5 ...). On return (eplai1) the hex token is built in C : 0xc7.
@0701 flag 'f' is tested the hex code is corrected accordinly ot 0xa7 (not '7' but 'ln') and finally 'a -> rom address' is met @02444, branching to 02775 address of the 'ln' routine.
Looking closely to the code for ln, you can recognize the exact code that can be found in the HP-35 at address 02021o.
Good code was saved.
All rights reserved.
7) Detailed PRGM operations
I'm going now (April 2013) to detail the HP25 operation in PRGM mode : entering and running a program.
7.1 Entering a program
Here we study the portion of the rom ENTERing a program in memory step by step, that is to say.
- retrieve program counter from register m2,
- retrieve program instructions starting reg 9 (7 by 7),
- update program counter,
- store new instruction code in memory,
- build key phrase.
But first a few notions on the HP-25 ram organization.
HP-25 ROM map
HP-21 subset