• 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.

Key phraseA 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.

display25

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