Disassembling HP-97 program cards

Scan of the cover image for the Programming section of the HP-97 users manual

In my first post about the HP-97 I started restoring the hardware of the device. In my second post I started reading the cards. In this post, I make sense of those cards, and ultimately write a "disassembler" for them. I have disassembler in quotes here, because it's not a true disassembly. The contents of the program cards aren't exactly machine code, and the instructions aren't exactly assembly. It is, however, the closest word to what I'm doing that you might already be familiar with.

Checking off Checking Checksums

I left the last post unable to correctly calculate the checksums on the cards. I shared that post with Teenix, and he added another section to his notes document. Embarrassingly, the problem was staring me straight in the face. If we recall the table I shared:

2220004$1F high
b009f85$1F low
03839b8$1E high
514e000$1E low
53e08f8$1D high
0000000$1D low
0000000$10 low
1791c1a202219computed checksum
79ca029mod 16
7acb14bcard checksum
0101122abs error

Notice that the error on nibble 1 (\(n_1\)) is exactly equal to \(\frac{n_0}{n_{16}}\)? The portion that would be truncated is actually carried!! The error of \(n_6\) is equal to what would carry from \(n_5\). Nibble 5 is missing \(n_4\). So on, and so forth.

With that sorted, I was able to correctly verify the checksum on all 46 captured cards.

Making sense of the stored instructions

The next struggle was figuring out exactly how the instructions are stored on the magnetic cards. In this case, I spent quite a bit of time going down the rabbit hole of 6-bit instruction words. This was not for no reason, as the users manual, service manual, and the HP Journal article all discuss the 6-bit words. I simply couldn't get the math to work out; no matter how you try, you can't pack 6-bit words into the 56-bit registers in a clean way! They simply don't divide! Ultimately the epiphany came when I made the modification to the calculator that I threatened in the previous article.

Hacking the HP-97 CRC chip

I tack-soldered small wires onto the CRC chip for each of the signals that go to the card reader. Even though the motor still didn't work, I could use these signals to learn a lot more about what's happening. By entering a program into the calculator, then trying to write it to a card, I could see the impact of adding single instructions. It became clear that each instruction added exactly one byte to the card.

Then, I was able to derive more value from the thread in the classic notes document relating to how the 56-bit registers in the calculator's memory are written to the card. The program memory sits in the middle of a 64-word memory bank, flanked by the primary and secondary register files. Each of these are organized into 16-word pages. The register files contain registers 0-9, A-E, and an indirect register I, for a total of 16. And, the program memory goes from memory address 0x10 to 0x2F. Interestingly, instruction zero (first) is at 0x2F, and the program grows "upward".

With this information, it was clear that instructions are simply 1-byte wide, and that 7 of them are packed within a single 56-bit register, and a register is stored as two 28-bit records on the card. Any unused instructions are filled with 0x00, which is a Run/Stop or R/S instruction. You can think of those as No-ops.

Once I figured that out, I started looking for some reference for all the instruction OpCodes, and what instruction they mapped to. I initially started entering simple programs into the calculator, printing them out, saving them to a card (virtually) and writing the opcode numbers in the margin of the tape.

Scan of HP program tape with notes

Eventually, while playing around with the Stat-Pac that I scanned, and the version I downloaded from Teenix's website (they were similar, but enigmatically different) I realized that I could just rely on the emulator's depiction of the program; the numbers matched. Essentially, I loaded a few cards into the emulator and compared the register contents to the program steps that the emulator displayed. Clearly there was some notion of this mapping in the emulator, but I don't believe it's open source.

HP97 emulator showing the same program

Anyway, I spent a couple hours loading programs and adding instructions that I hadn't figured out yet. Eventually, I was left with quite a few opcodes that didn't have instructions. I certainly didn't expect that 100% of the available opcodes would be valid, but I was sure I was missing some. In part, because my disassembler crashes when it hits an unknown opcode, and there were a few in my stack of cards. Then, I noticed in Appendix E that HP had helpfully listed every possible instruction, what it prints out, and what keys must be pressed to achieve it. I was able to enter the remainder of those into the emulator to get the code for them.

Long story short, I now have a complete notion (that I think is accurate) of all the HP-97 opcodes, what they mean, how they'll print, and what keycodes are necessary to create them in my open source HP-97 program card disassembler.

Ultimately, there are something like 7 opcodes that seem not to be associated with a published instruction. I'm dying of curiosity for what'll happen if I write them to a card! Likely, I'll just get an error, but it'll be fun to see if anything else happens. This must be what fuzzers and crackers feel like all. the. time.

Reading and writing HP-97 emulator card files (hpp format)

It should be clear that I found Teenix's emulator to be highly valuable. And, it was clear that I'd want to be able to directly load my scanned cards into it. That necessitated writing a serde (seralizer/deserializer) for the format. While the classics notes document now has a description of the file format, the emulator is extremely fussy, and it won't accept cards that aren't exactly the same as the ones he produces. Everything down to the precise line terminations for each line must match exactly.

For example, the first two lines: NeWe (magic number) and file size have only carriage returns (no, not linefeeds, like unix endings. just carriage returns). The rest of the lines must be carriage return/linefeed combos (windows line endings). And, the end of the file must have a carriage return and linefeed. Once I learned those tricks, his document is accurate.

Having that out of the way allows me to take a real life Stat-Pac card (or any, of course), load it into my HP-97, capture the output, and run it in the emulator. That's extremely cool!!

Disassembler output examples

Just for fun, here's the command-line output of the program, loading that same area of a circle program:

Processing 01_AreaOfCircle:
000 *LBLA     21 11
001    X²        53
002    Pi     16-24
003     ×       -35
004   RTN        24
005   R/S        51

I even tried to get the printing of the lines just right. There was only one frustration around that, and that was the inverse trigonometric operators. The HP-97's printer has a special character set that includes a superscript -1, which is a single-width character. I simply couldn't figure out a way to achieve that with unicode. So, I had to cheat, and let them take up the column that's normally reserved to have an * to let the labels stand out more. For example, from the diagnostics card:

032  ISZi  16 26 45
041 *LBL2     21 02
042     2        02
043     5        05
044  STOI     35 46
045   SIN        41
046 SIN⁻¹     16 41
047  GSB3     23 03
048   COS        42
049 COS⁻¹     16 42
050  GSB3     23 03
051   TAN        43
052 TAN⁻‍¹     16 43
053  GSB3     23 03
054    →P        34
055    →R        44
056  GSB3     23 03
057   SIN        41
058  →HMS     16 35
059  HMS→     16 36
060 SIN⁻¹     16 41

Next steps...

From now, the next steps are more nebulous. I'm still hoping my lead on a new sense amplifier and Rom-0 chips comes through, so I can repair my device. If that doesn't happen, though, Teenix is still working on a replacement CPU board. I'll certainly buy one of those regardless of whether I "need" it. Also, I'm strongly considering designing a replacement for the card reader board that's based around a mixed-signal microcontroller. I think there are probably devices that had good analog comparators and motor drivers. I think 2 comparators and a motor driver would be all that's needed besides some basic programmability. This, of course, would be a lot more time consuming to design. At a minimum, it's fun to think about making a copy of the PCB that's mechanically compatible with the existing PCB that would let me have a play with loading and reading cards outside of the calculator.

1662 Words