This is a repo that includes a selection of original HP-48 calculator binaries relating to CHIP-8 interpreters - specifically the Chip-48 and Super-Chip platforms. They were obtained to allow CHIP-8 games to be run on an original 1990s HP48S calculator for OctoJam III, a game jam where people are asked to create new CHIP-8 games with Octo, a modern and very accessible interpreter. As of 2017, this project has extended itself to include binaries for the HP48 G series calculators.
Via analysis of the specifications for these interpreters, as well as contemporary programs which ran with them, it has been previously observed that there are some variations in how certain instructions are implemented on these platforms versus how they are implemented on CHIP-8's original platform, the RCA 1802 microcomputers of the 70s - these variations will be referred to as "Quirks", or similar. After having actually obtained an original calculator and investigating the binaries further, several more previously unknown quirks have been observed. The goal of this repo is to investigate the origin of these quirks, and offer some explanation as to what might have caused them.
I would like to clarify that, these interpreters were written by hobbyists for fun, and shared freely on the internet via newsgroups. As far as I'm aware, they were not particularly widely used or analyzed, and they likely did not have access to original implementations or games to verify their interpreters behaved in exactly the same way. This project is not intended to be a criticism of their work, more an investigation and documenting of how and why they behaved as they do. That these interpreters exist to begin with is likely responsible for CHIP-8 being remembered the way it is, so, I'm thankful to them for that.
I would like to add, it is very possible to emulate the HP48 calculator and obtain information without real hardware. I have had success with Emu48. Make sure a) you appreciate that this is a Reverse Polish Notation calculator, so, put things on the stack and then apply instructions, and b) that you must match S or G bios files with S or G binaries, as the display buffer is in a different place.
Here are some explanations of some terms:
An interpreted Assembly Language designed in the 1970s by Joseph Weisbecker to allow for people to share games across a handful of home computer platforms. It has since become popular for programmers to write a CHIP-8 interpreter in their language of choice, to become familiar with how simple microprocessors can work, as there are already a good selection of programs available for it.
A popular graphing calculator launched in the early 90s by HP. It supported the creation of programs via assembly language for its Saturn microprocessor. Similar in a lot of ways to the popular Ti81, it did however not support an accessible language for general programming, such as BASIC. As a result, (I believe/am guessing) Andreas Gustafsson thought to write a CHIP-8 interpreter for the HP48 so people could more easily write programs and games, and play those that had been already written before.
The name of the CHIP-8 interpreter for the HP48 S that was written by Andreas Gustafsson. It implements only the original 1970s instruction specification without (deliberate?) modifications. The source code for version 2.25 of this interpreter was found in newsgroup archives and is included as part of this Repo.
An expanded instruction set of the CHIP-8 interpreted language created by Erik Bryntse, with additions such as a higher resolution and scrolling. It was first implemented on the HP48, and used the openly available CHIP-48 source. I'm aware of a version 1.0 (SC10) and a version 1.1 (SCHIP) - the Superchip spec and behavior that is most publicly apparent are for version 1.1. The source code for version 1.0 only has been found, and offers some insight, but version 1.1 was modified for speed and involved a lot of rewriting in several key sections.
OK, back to what this repo is about:
Aside from the goal of providing a nice extra feature for Octojam III, where compatible games would be shown running on 1990s hardware, I hoped to investigate the actual functionality of these original platforms in detail. This is because many later & current interpreters implement the written Super-Chip instruction specification, which was widely available, rather than what the program as written may have actually done - the specifics of which would be limited to those with an HP48 and the interest to verify. As a result, many 'quirks' - behavior in an interpreter that diverges from the original CHIP-8 interpreters written in the 1970s for home computers of the era - can be observed. Previously these quirks were identified based on programs written in the era, however these were limited to the examples that could be discovered and the ideas people had at the time - as a result, having a working device on hand and a team of inquisitive minds has yielded the discovery of several more quirks that were previously unknown.
Many of these additional quirks were quite unexpected and their individual origins were a question that piqued (in particular) my interest - were they deliberate changes? Mistakes? Typos? I decided to investigate each that I became aware of, trying and ascertain & subsequently document their origin, and, if possible, create a new SCHIP binary for the HP48 that conforms closer to the behavior observed on the Cosmac VIP's interpreter, and thus of Octo's default setup: This binary is called SCHPC (SCHIP Compatability). This would make it far easier to take a program from Octo, and be able to run it on an HP48, without having to work around some of the more awkward quirks.
I have taken some additional notes on editing and understanding the binaries, as well as output from the tools I used, which is now available here. This is because I forgot a lot of this over the course of the past year and I should probably have written it down the first time. I have also included new binaries for SCHIP (1.1) and SCHPC, GCHIP and GCHPC, which have been modified for use on the G series calculators (several ram addresses change on the new platform, and these were hardcoded into the binary).
Behavior and Quirk Investigations
Where possible, these link to a fuller investigation blog of each quirk and the process of fixing it for SCHPC.
8XY6 & 8XYE (aka x >>= y, x <<= y)
Bit shifts a register by 1, VIP: shifts rY by one and places in rX, SCHIP: ignores rY field, shifts existing value in rX.
FX55 & FX65 (aka load vX, save vX)
Saves/Loads registers up to X at I pointer - VIP: increases I by X, SCHIP: I remains static.
BNNN (aka jump0)
Sets PC to address NNN + v0 - VIP: correctly jumps based on value in v0. SCHIP: also uses highest nibble of address to select register, instead of v0 (high nibble pulls double duty). Effectively, making it jumpN where target memory address is N##. Very awkward quirk.
Address space of CHIP-8 programs is limited to 0xFFF, 4 kibibytes, but, by spec, less 0x200 bytes as 0x000 to 0x1FF are 'reserved'. VIP: 0xEA0 and above apparently also reserved for system use. SCHIP: No such utilisation of memory space, but, maximum file length results in crash. 1 byte less is OK, so highest address must be 0xFFE / must have 1 byte free. Also, memory space is uninitialised and random!
16x16 Sprites & 0 Line Sprites (aka sprite vx vy 0)
Superchip claims to add 16x16 sprites by using instruction DXYN with n = 0 (n specified # of lines). These only function as expected in hires display mode. In low resolution, an 8x16 sprite (a normal width sprite but with 16 rows) is drawn. VIP draws 0 rows?
Swapping Display Modes (aka lores and hires)
Superchip has two different display modes, 64x32 and 128x64. When swapped between, the display buffer is not cleared. Pixels are modified based on being XORed in 1x2 vertical columns, so odd patterns can be created.
There is no sprite wrapping on any HP48 CHIP interpreter. Collisions will not occur wrapping around the display area.
An interesting and apparently often unnoticed change to the Super Chip spec is the following: "All drawing is done in XOR mode. If this causes one or more pixels to be erased, VF is <> 00, other-wise 00." In SCHIP extended screen mode (aka hires) only, SCHIP 1.1 will report the number of rows that include a pixel that XORs with the existing data, so the 'correct' way to detect collisions is Vf <> 0 rather than Vf == 1, as you may see values between 1 and 16. lores functions as expected; Vf will be only be set to 1 in a collision.
In extended screen mode (aka hires) only, sprites that are drawn such that they contain data that runs off of the bottom of the screen will a) not wrap around the screen and b) also set Vf based on the number of lines that run off of the bottom screen (not the sides), exactly as if they are colliding with something below it. The two aspects will accumulate together, so any normal collisions + overshooting rows will be collected.
Large Font (ie i := bighex vx)
Superchip includes a larger font. This larger font does not include hex characters A through F - the spec actually states this but I'm not sure anyone realised.
The HP48 calculator is much faster than the Cosmac VIP, but, there is no clear indication of how fast it is really is in interpreting CHIP code while trying to design compelling programs with Octo. The cmark77 program has been used and adapted to suggest that Octo speed settings of 13 to 32 cyc/frm for HP48 S calculators, and from 17 to 50 cyc/frm for HP48 G series calculators would be reasonable, depending on task, with hires mode actually improving the worst case graphical performance by almost double to give estimates of 22 to 32 cyc/frm and 33 to 50 cyc/frm on S and G respectively.
Scrolling instructions (aka scroll-down n, scroll-left and scroll right)
SCHIP's scroll left/right/down instructions were written with its existing display buffer in mind. As a result, they scroll the 128x64 display buffer by 4 or n pixels in the given direction, regardless of if you are in extended (hires) or normal (lores) resolution - low resolution simply doubles positions and draws 2x2 blocks, instead of large single pixels: it still has the same display buffer. This means that, in lores mode, you only scroll what is effectively 2 pixels to the left and right, and uh, scrolling downwards by an odd number is going to go really weird. There are more issues with vertical scrolling in lores that I have yet to fully understand.
None of this would have been possible without hpcalc.org, who have archived large amounts of information and software for a selection of HP calculators. One of the most helpful single information sources in this process has been an "Introduction to Saturn Assembly Language" by Gilbert Fernandes and Eric Rechlin - it includes a wealth of information about the instructions and design of the Saturn processor - the processor that is in the HP48 - as well as the op codes associated with them, which is what has allowed me to reverse engineer and modify the binaries. I have noted some mistakes in it but otherwise it has been absolutely invaluable. Also, I have tried many of the assembler and disassembly programs, and failed to get them to be functionally useful to me in short order - the one program I rely heavily on is Decode 1.3, a DOS based disassembler, which runs quite happily in DOSBOX, although I have discovered problems with this, too.
Finally, I'd like to thank the person whom owned my HP48 calculator(s) previously, and kept them safely and in working condition in Portugal for what I can only assume has been around 25 years.