Here is the complete map of the HP35 ROM 0. All routines are programmed in three ROM chips (each of which contains 256 instructions of 10 bits each).
Only one of each is used at any time: if one is turned on, unselected ROMs turn
off.
In the following tables, instruction addresses are given in octal.
I have never seen any original assembly listing of the HP35 ROMs. All information given below are pure reverse engineering.
Labels and symbolic instruction codes can be inferred from the HP45 "US Patent n° 4001569" listing.
Object code can be dumped by optical ways
(P. Monta) or by electronic ways (E. Smith and J. Laporte).
ROM simulators had been developed and they reproduce faithfully the results
given by the genuine hardware (even the bugs, if any).
I dumped myself (2006) -key by key- the object code of two real HP35 (old buggy ROM and new ROM) and could confirm bit by bit the object listing.
As the HP35 micro code is now completely
deciphered and understood, it could have been easy to "guess" the real labels
of the unknown symbolic listing. For example, address 00376, labeled l00376 by
Smith was certainly "rcl0" in the original listing, as it handles the RCL key
and can be directly compared with its HP45 counterpart.
But I decided though to keep the original naming by Eric Smith for the sake of consistency.
This hard work has a story : One day at the beginning
of the 80's, I found in San Francisco a book by James Farvour, “Microsoft Basic
decoded & other mysteries” that I still own and open from time to time. Farvour
commented step by step TRS-80's Rom : the Microsoft Basic.
At that time my HPs (35 and 65) were always close at hand.
I remember well having said to myself : I must do the same for the HP 35!
It took me 25 years!
Now the real stuff.
ROM 0 contains the
code for:
- 35 KEY entry points,
- PWO routine,
- output format routine (formatting number in displayable and normalized forms
back from math routines),
- data entry routine (number digits and exponents),
- stack push handling,
- part of add and sub routines,
- CHS handling,
- fix to floating point formatting,
- the main idle loop (wait a key and display routine).
All the operating software of the calculator is here.
ROM 0 existed in two versions:
- old 1972 version with 3 major bugs "CHS" handling,
exp(ln(2.02), arc,
- new ROM end of 1972 and post 1972 models + updated models.
I explain below the mapping of the new ROM, but you can have a look at my explanation on the two major bugs: the "exp(ln (2.02))=2" and the "arc tan" bug.
(to fully understand the code, please refer to the corresponding pages, on this site).
1) Entry points
00000 | jsb 00067 | Key CLR | Entry Points | |
00001 | go to 00277 | Error go to blinking display | ||
00002 | 0 -> s8 | Key e^x | When a key is hit, the row and column detected form a | |
00003 | go to 00005 | Key ln | 6 bit key code and a “key down” signal is raised (status S0). | |
00004 | 1 -> s5 | Key log | This is only one hard wired flag S0. | |
00005 | 1 -> s9 | In other machines (Woodstock) more flags are used | ||
00006 | 1 -> s2 | Key x^y | (PRGM, battery ok, etc). | |
00007 | goto 02010 | The key code is “token”, a displacement to be added | ||
00010 | jsb 00264 | Key RCL | to 0000 to build the entry point address of the code | |
00011 | go to 00376 | serving the key. | ||
00012 | go to 00027 | Key STO | For a math function key (eg. “sin”), the context is preserved | |
00013 | go to 00060 | Key ! (rdwn) | and parameters are stored in the registers ; for a “digit key” | |
00014 | stack -> a | Key x <> y | (forming a number) the exponent of register A (a[x]) is used ; | |
00015 | go to 00331 | saved before entering the entry point (eg. “dig6”) and restored after. | ||
00016 | 0 -> a[w] | Key 1/x | Here are from (hp35 keyboard) top to bottom, | |
00017 | a + 1 -> a[p] | the key codes (entry points) generated: | ||
00020 | 0 -> b[w] | |||
00021 | select rom 1 | goto asn12 | CLR=0, which is also the entry point for PWO (slide ON) | |
00022 | dig6 | a + 1 -> a[x] | Key "6" | e^x = 02, "ln" = 03, "log" = 04, "x^y" = 06 |
00023 | dig5 | a + 1 -> a[x] | Key "5" | |
00024 | dig4 | a + 1 -> a[x] | Key "4" | tan = 50, "cos" = 52, "sin" = 53, "arc" = 54, "sqrt(x) = 56 |
00025 | go to dig3 | |||
00026 | jsb 00232 | Key "+" | RCL = 10, "STO" = 12, "Rdwn" = 13, "x<>y" = 14, "1/x" = 16 | |
00027 | c exchange m | Relay to "STO" routine | ||
00030 | m -> c | CLX = 70, "EEX" = 72, "CHS" = 73, "ENTER^ = 76 | ||
00031 | go to 00077 | |||
00032 | dig3 | a + 1 -> a[x] | Key "3" | "9" = 62, "8" = 63, "7" = 64, "-" = 66, |
00033 | dig2 | a + 1 -> a[x] | Key "2" | |
00034 | dig1 | a + 1 -> a[x] | Key "1" | "6" = 22, "5" = 23, "4" = 24, "+" = 26 |
00035 | return | |||
00036 | 3 -> p | Key "*" (mult) | "3" = 32, "2" = 33, "1" = 34,"x" = 36 | |
00037 | 0 - c -> c[x] | |||
00040 | stack -> a | Pi=42, "." = 43,"0" = 44,"/" = 46 | ||
00041 | go to 00020 | |||
00042 | go to 00164 | Key PI | The “arc” key is a prefix : it raises flag 10. | |
00043 | 3 -> p | Key "," (dec. Point) | ||
00044 | return | Key "0' | The memory locations of missing key codes (11, 15 etc) are used | |
00045 | no operation | by the code at the start of the routine) | ||
00046 | go to 00040 | Key "/" | e.g. 00076 Key ENTER : | |
00047 | 1 -> s5 | Relay for "sin" | c -> stack | |
00050 | 1 -> s1 | Key ""tan" | clear status | |
00051 | go to 00056 | shift right a[w] | ||
00052 | 1 -> s9 | Key "cos" | jsb fst2zx | |
00053 | go to 00047 | Key "sin" | …/… | |
00054 | 1 -> s10 | Key "arc" | ||
00055 | go to 00302 | |||
00056 | 0 -> b[w] | Key SQR | One exception though: missing key code 45 is a "no operation" | |
00057 | goto 01060 | the only case on the HP 35 ROM | ||
00060 | down rotate | Relay for rotate | ||
00061 | go to 00333 | These entry points must be studied dynamically as part | ||
00062 | dig9 | a + 1 -> a[x] | Key "9" | of the "display and wait" routine. This is the paradigm of this class |
00063 | dig8 | a + 1 -> a[x] | Key "8" | of calculators (Classic, Woodstock, Spice etc. |
00064 | dig7 | a + 1 -> a[x] | Key "7" | One entry point is not the location where control starts |
00065 | go to dig6 | but where it passes to service a key. | ||
00066 | go to sub0 | Key "-" (sub) | ||
00067 | clear registers | Relay for PWO and CLR | General references are: | |
00070 | jsb of12 | Key Clx | "The key code tokens" | |
00071 | go to fst2zx | "The display and wait routine" | ||
00072 | go to eex2 | Key EEX | ||
00073 | shift right a[w] | Key CHS | Math key routines are explained in the dedicated chapter: | |
00074 | 1 -> s3 | e.g. "ln" is described in "HP 35 Logarithm Algorithm" | ||
00075 | go to 00166 | Utility routines activated by keys like "ENTER" are | ||
00076 | c -> stack | Key ENTER | described in | |
00077 | clear status | |||
00100 | shift right a[w] | “Status bit flags”, | ||
00101 | jsb fst2zx | “Digit Entry”. | ||
You should start with "HP35 Operating software", | ||||
“Overall firmware architecture”. |
2) Other code in ROM 0
00102 | l00102: | a -> b[w] | Number format conversion | Ref : “Output format” |
00103 | 0 -> a[xs] | 0102-0113 | ||
00104 | shift left a[ms] | Routines 0102-0113 and 0340-0361 handle the 4 type of display and automatic conversion | ||
00105 | l00105: | a - 1 -> a[x] | in scientific notation (SN): | |
00106 | if no carry go to l00340 | 1- numbers to be displayed as "100." : 107-346-361 | ||
00107 | if c[xs] = 0 | 2- numbers to be displayed as ".01" : 107-111-346-361 | ||
00110 | then go to l00346 | 3- numbers to be displayed as "1 10-12 : 106-340-346-361 | ||
00111 | a exchange b[ms] | 4- numbers to be displayed as “1012” :106-340-346-361 | ||
00112 | 13 -> p | (results > than 1010 or < are 10-2 automatically converted in SN). | ||
00113 | go to l00346 | |||
At 0106 the floating-point exponent sign (in A) is tested <> 0 : if no carry (<> 0) | ||||
control branches at 0340, if carry (fp = 0) goto 0107. | ||||
This is the two SN cases (no carry) forms like : "1 10-12 and “1012” | ||||
At 0107 the normalized exponent signed is tested = 0 or not. | ||||
If = 0 goto 0346 and not follow thru 110 etc. | ||||
This is the two other cases : exp. sign = 0 (0346) forms like “100.” and exp sign <> 0 | ||||
forms like “.01” (decimal point first). | ||||
Code between 0102-0113 is roughly sorting the 4 cases. | ||||
Code between 0340–0361 is building the mask and adjusting floating point | ||||
representation. The loop 0342-0105 is decrementing and SR A and | ||||
decrementing pointer p is doing the SN conversion. | ||||
00114 | eex7: | p - 1 -> p | Exponent building (loop "eex7-eex8") | |
00115 | c + 1 -> c[x] | Mask in B is used (swept from left to right) to build exponent part of C | ||
00116 | eex8: | if b[p] = 0 | in the loop "eex7"-"eex8" | |
00117 | then go to eex7 | |||
00120 | 1 -> s11 | number has been entered mantissa first (other case is "1 00") | ||
00121 | shift right a[ms] | (tested in 0363) | ||
00122 | a exchange c[m] | Floating Point in A | ||
00123 | if s4 = 0 | S4=1 EEX has been pressed, if S4=0 goto "den1" | ||
00124 | then go to den1 | (flag set in 0362) | ||
00125 | jsb of14 | Exponent entered with EEX, call output ofrmat | ||
00126 | go to fst2zx | go display and wait | ||
00127 | of11: | 0 -> c[wp] | OVER and UNDERFLOW conditions | |
00130 | c - 1 -> c[wp] | Math routines signal their out of limit status (exp C = 900 or 100) | ||
00131 | 0 -> c[xs] | display is built accordingly | ||
00132 | a + b -> a[x] | 9.999999999 or 0. | ||
00133 | if no carry go to of13 | |||
00134 | of12: | 0 -> c[w] | CLR and PWO entry points | |
00135 | of13: | clear status | Return form maths routine, format the output | |
00136 | c -> a[w] | build the output context (using normalized form in C, make FP in A and mask in B) | ||
00137 | of14: | 12 -> p | Init context | |
00140 | a -> b[x] | |||
00141 | c -> a[x] | |||
00142 | if c[xs] = 0 | Normal negative or overflow/underflow ? | ||
00143 | then go to of15 | |||
00144 | 0 - c -> c[x] | |||
00145 | c - 1 -> c[xs] | |||
00146 | if no carry go to of11 | test for over/underflow | ||
00147 | 5 -> p | |||
00150 | of15: | a exchange c[x] | ||
00151 | if s4 = 0 | if EEX Not pressed goto 0102 (normal path) | ||
00152 | then go to l00102 | |||
00153 | a exchange b[x] | If EEX key pressed | ||
00154 | 0 -> b[x] | build the "1 00" mask | ||
00155 | jsb dsp1 | |||
00156 | shift left a[x] | and fall thru "eex3" | ||
00157 | eex3: | shift right a[w] | Exponent entry (exponent digits) Ref : “Digits entry” | |
00160 | if p # 12 | from eex2 0360 (see eex2 at 0363) | ||
00161 | then go to den7 | if S11=0 mantissa entered, 2 cases | ||
00162 | a exchange c[wp] | p=12, main case, eex4-eex5-eex6-eex7-eex8-0123-of14-fst2zx | ||
00163 | go to eex4 | p=3 after a "." -> den7 | ||
00164 | l00164: | jsb l00264 | This is the code for the pi constant | |
00165 | select rom 1 | address = 1264 | ||
00166 | l00166: | if s4 = 0 | CHS entry point | |
00167 | then go to chs3 | if EEX was pressed S4=1 | ||
00170 | a exchange c[wp] | negate mantissa in C | ||
00171 | 0 - c - 1 -> c[xs] | else goto "chs3" main entry point to negate C mantissa | ||
00172 | eex4: | c -> a[w] | Exponent entry (exponent digits) Ref : “Digits entry” | |
00173 | if c[xs] = 0 | (this is new ROM algorithm, buggy old roms are different)) | ||
00174 | then go to eex5 | 2 ways to enter exp digits: | ||
00175 | 0 -> c[xs] | - sign digits "." digits | ||
00176 | 0 - c -> c[x] | - sign digits EEX digits sign | ||
00177 | eex5: | 13 -> p | Entry point is eex4 from den6 | |
00200 | eex6: | shift left a[ms] | ||
00201 | c - 1 -> c[x] | When no exponent the loop 0204-eex6 preserves leading zeros (ex. "000123") | ||
00202 | if a[s] >= 1 | The loop "eex8-exx7" actually build the normalized exponent in C | ||
00203 | then go to eex8 | For numbers enterd with "." the âth is "den6" 0373 0375 | ||
00204 | if a[ms] >= 1 | For numbers entered with EEX the entry poin is "eex2 at 0362 | ||
00205 | then go to eex6 | |||
00206 | 0 -> c[x] | |||
00207 | den1: | jsb dsp1 | Data entry (digits & numbers) Ref : “Digits entry” | |
00210 | shift right a[ms] | A number is entered using numeric key, decimal point “.”, “EEX” key and CHS key. | ||
00211 | den7: | c -> a[s] | “den2” is the main entry point (the digit just got is in a[x]), pointer p = 12 when entering | |
00212 | den2: | if p # 12 | digits but p=2 if dp “.” is pressed. | |
00213 | then go to den4 | Code between “den2” & “den4” places the new digit in place | ||
00214 | b -> c[w] | according the current mask in B. | ||
00215 | c + 1 -> c[w] | In "den3" digits are "slided" in place | ||
00216 | 1 -> p | Exit point is “den5” or “den6” whether dp has been used. | ||
00217 | den3: | shift left a[wp] | Normal exit thru “den6” updates the current mask (a digit can be part of a number). | |
00220 | p + 1 -> p | A call to “eex4” is made (regular entry point in EEX) to normalized the exponent in C. | ||
00221 | if c[p] = 0 | If there is a “.” Control goes to “den5” and then “den6” | ||
00222 | then go to den3 | where the current mask is updated. | ||
00223 | den4: | a exchange c[w] | This is the normal exit point. | |
00224 | if p # 3 | If no “.” control goes to “eex4” to form exponent in C | ||
00225 | then go to den5 | |||
00226 | 0 -> c[x] | |||
00227 | 1 -> s6 | (see "den 5" and "den6" below) | ||
00230 | go to eex4 | |||
00231 | sub0: | 0 - c - 1 -> c[s] | Floating point addition and subtraction (Ref: Floating point Arithmetic) | |
00232 | l00232: | stack -> a | If the operation is a subtraction, the code is exactly the same but entry point is "sub0" | |
00233 | 0 -> b[w] | The first action of the code is to drop values from the stack, to have Y in A | ||
00234 | a + 1 -> a[xs] | Next B is zeroed | ||
00235 | a + 1 -> a[xs] | Exponents of registers A and C are corrected so absolute values can be compared. | ||
00236 | c + 1 -> c[xs] | The exponent comparison is by adding two to exponent and sign fields | ||
00237 | c + 1 -> c[xs] | of both registers A and C. | ||
00240 | if a >= c[x] | At 0240 the comparison is done. | ||
00241 | then go to add4 | If a[x] > = c[x] (exponent of C smaller then exponent of A), we will simply exchange | ||
00242 | a exchange c[w] | both registers, so that the smallest number is always in C and the biggest in A. | ||
00243 | add4: | a exchange c[m] | ||
00244 | if c[m] = 0 | Mantissa alignment : biggest fraction part will be shifted right (division by 10) | ||
00245 | then go to add5 | and the exponent of biggest number will be incremented. | ||
00246 | a exchange c[w] | |||
00247 | add5: | b exchange c[m] | Next in routine “add4”, exponent and fraction parts of both numbers | |
00250 | add6: | if a >= c[x] | are separated. At “add6” fraction part alignment is done and exit at 0276 | |
00251 | then go to l00276 | when both fractional part are aligned. In routine “add12”, | ||
00252 | shift right b[w] | signs are processed to do the “addition” or “subtraction. | ||
00253 | a + 1 -> a[x] | |||
00254 | if b[w] = 0 | |||
00255 | then go to l00276 | goto "add12" thru a relay at 0276 | ||
00256 | go to add6 | |||
00257 | fst3: | 0 -> a[ms] | Stack handling routine | |
00260 | if s3 = 0 | |||
00261 | then go to l00264 | |||
00262 | a - 1 -> a[s] | If "CHS" flag S3=1 | ||
00263 | 0 - c - 1 -> c[s] | negate C mantissa sign | ||
00264 | l00264: | if s7 = 0 | S7=1 automatic push | |
00265 | then go to fst5 | S7 = 0 : no stack push | ||
00266 | c -> stack | stack push | ||
00267 | fst5: | 1 -> s7 | mask as done | |
00270 | 0 -> c[w] | build mask for a digit "29999…999" | ||
00271 | c - 1 -> c[w] | d° | ||
00272 | 0 - c -> c[s] | d° | ||
00273 | c + 1 -> c[s] | d° | ||
00274 | b exchange c[w] | |||
00275 | return | |||
00276 | l00276: | select rom 1 | relay to "add12" addr=1276 | |
00277 | l00277: | jsb of12 | ERROR entry point | |
00300 | 1 -> s5 | Set flag for blinking display | ||
00301 | go to fst2zx | go to the farm | ||
00302 | l00302: | shift right a[w] | DISPLAY AND WAIT FOR A KEY | |
00303 | dsp7: | c -> a[s] | Entry point, called from disp1 | |
00304 | l00304: | 0 -> s8 | ||
305 | go to dsp8 | |||
306 | dsp2: | c + 1 -> c[xs] | ||
307 | dsp3: | 1 -> s8 | dsp3-dsp5 Main IDLE loop | |
310 | if s5 = 0 | In the middle of it, blink the display | ||
00311 | then go to dsp5 | |||
00312 | c + 1 -> c[x] | |||
00313 | if no carry go to dsp2 | |||
00314 | dsp4: | display toggle | ||
00315 | dsp5: | if s0 = 0 | No key, so "once around" | |
00316 | then go to dsp3 | |||
00317 | dsp8: | 0 -> s0 | A KEY has been pressed | |
00320 | dsp6: | p - 1 -> p | ||
00321 | if p # 12 | disp6-0322 : debounce loop | ||
00322 | then go to dsp6 | |||
00323 | display off | |||
00324 | if s8 = 0 | make sure a key is served only once | ||
00325 | then go to dsp4 | |||
00326 | shift left a[w] | |||
00327 | 0 -> s5 | |||
00330 | keys -> rom address | jump to token address | ||
00331 | l00331: | c -> stack | Exchange x><y entry point | |
00332 | a exchange c[w] | |||
00333 | l00333: | jsb of13 | Return point | Ref : “Digits entry” |
00334 | 1 -> s7 | |||
00335 | fst2zx: | jsb dsp1 | 00333 is the main return point when a math routine is completed. | |
00336 | jsb fst3 | A call to of13 is made to format the display (A & B with C) | ||
00337 | go to den2 | Flag 7 is raised to request a stack push. | ||
Then back to the “display and wait” loop at “fst2zx” | ||||
When a key is hit, the “return” instruction brings back to 00336 where the stack | ||||
chore is made, before going servicing the key entry. | ||||
00340 | l00340: | shift right a[ms] | Mask building routines | Ref : “Output format” |
00341 | p - 1 -> p | 0340-0361 | ||
00342 | if p # 2 | Code between 0340-0345 is run only by SN numbers (FP exponent in A <> 0). | ||
00343 | then go to l00105 | Numbers are converted if possible to fixed format : 1. 03 -> 1000 | ||
00344 | 12 -> p | |||
00345 | 0 -> a[w] | Note that in the 35 this conversion occurs | ||
00346 | l00346: | 0 -> a[ms] | only on math routine return. | |
00347 | a + 1 -> a[p] | |||
00350 | a + 1 -> a[p] | Code between 0346-0361 is positioning the decimal point | ||
00351 | 2 -> p | in the mask “.” = “2” and filling the digits to be blanked with “9”. | ||
00352 | l00352: | p + 1 -> p | This code decides if exponent is blanked or not : | |
00353 | a - 1 -> a[p] | if entry point is 0346 the exponent in the mask is “999” = blanked. | ||
00354 | if no carry go to l00357 | |||
00355 | if b[p] = 0 | |||
00356 | then go to l00352 | |||
00357 | l00357: | a + 1 -> a[p] | ||
00360 | a exchange b[w] | |||
00361 | return | |||
00362 | eex2: | 1 -> s4 | Exponent entry (exponent digits) Ref : “Digits entry” 0362-0365 | |
00363 | if s11 = 0 | 3 cases : Exponent entered with EEX ( eex key fist or after mantissa) | ||
00364 | then go to dig1 | If EEX key : entry point is “eex2”, If EEX pressed first, goto dig1, to make mask "1 00" | ||
00365 | go to eex3 | If not (mantissa built first) goto “eex3” to build the exponent. In C and the display mask. | ||
00366 | chs3: | 0 - c - 1 -> c[s] | CHS was used, negate C sign | |
00367 | dsp1: | 0 -> s10 | Entry point in "display and wait a key" loop fom point "fst2zx" | |
00370 | go to dsp7 | Route back from math routines : 0333-"of13"-"fst2zx-"dsp1" | ||
00371 | den5: | if s6 = 0 | If s6=0 make room for the decimal point "." | |
00372 | then go to den6 | |||
00373 | p - 1 -> p | If s6=1 make 2 places | ||
00374 | den6: | shift right b[wp] | ||
00375 | jsb eex4 | and "eex4" will build the mask accordingly | ||
00376 | l00376: | m -> c | The code for RCL | |
go to l00333 | Place m into register C and back to the farm |
J. Laporte
20 October 2006