INTRODUCTION
One of the main limitations of the ZX80 was its lack of support for floating point arithmetic, with it's 4K ROM only being large enough to support 16-bit integer maths. This was resolved when Sinclair announced the release of the 8K ROM upgrade for the ZX80, which would end up being the ROM used in the ZX81. Notionally 8K ROM BASIC is a superset of 4K ROM BASIC, but programs saved to cassette using the original 4K ROM cannot be loaded into a ZX80 running the new 8K ROM (and vice versa). The bytes of a program are saved on the cassette using the same encoding scheme by both ROMs, but the meaning of the bytes is different due to programs being held in memory using incompatible formats. The 8K ROM has a larger number of system variables than the 4K ROM and those that are similar do not occupy the same memory locations as that used by the 4K ROM. The 8K ROM introduced a hidden two byte length value at the start of each BASIC line, and used a different assignment of character codes for keywords. It's new floating point support saw a hidden floating point representation embedded after each numeric value in a BASIC line. These, along with other changes in the way the program contents were structured, meant that files saved by a 4K ROM could only be loaded by a 4K ROM, and files saved by an 8K ROM could only be loaded by an 8K ROM.
However, since the encoding of bytes saved to cassette is identical between the two ROMs it is technically possible for one ROM to read in the bytes saved by the other ROM, it is just that it can't interpret what they mean due to the differences in BASIC environment and BASIC program format. But this limitation only applies when trying to interpret the bytes as BASIC. If a pure machine code program could be read in and directly executed then having a valid BASIC environment present would become irrelevant. Take for example the case of the various Flicker-free games released for the ZX80. The flicker-free mechanism can only be achieved by using machine code, and typically flicker-free games were sold in 4K ROM and 8K ROM versions. The machine code of the two versions would essentially be the same, with just two differences - the remapping of the some character codes for the block graphic and symbol characters, and different addresses for the calls to the display routines in the ROM made by the game's display driver. So as far as the machine code was concerned, it was largely irrelevent which ROM was actually fitted inside the ZX80. However, a valid BASIC environment was still required in order to run the machine in the first place after loading from cassette. But imagine if the machine code could be invoked directly after loading then the only issues to overcome would be the alternate character codes and different ROM display routine addresses. It would be very easy for a machine code program to detect which type of ROM was present and to patch itself to adjust the character codes used and the addresses called into the ROM display routines.
Another possibility would be not to have a shared machine code program that is patched for one or other ROM, but to simply deploy one of two ROM specific payloads. These payloads could hold copies of whole .o and .p files and hence would just need to be copied to the appropriate place in memory. This approach would allow deployment of ROM specific versions of a BASIC program.
This page presents a technique for solving these issues.
BENEFITS
There are a number of benefits having a single file that loads in using either ROM:
- When released on cassette, there is no need to create separate cassettes for each ROM type, or to record the two ROM versions on different side of the same cassette.
- When released on cassette, there are fewer files to actually record and hence the recording process is faster, with a shorter tape length required which could mean a cheaper cassette could be used.
- Both ROM versions of a game are always kept together and so cannot get separated or out of sync from each other.
- A single version number can be used thats cover both ROM variants of a game, hence version numbers can't get out of sync between the two ROMs.
- When machine code is shared by both ROM configurations, bug fixes need only be made once.
METHOD OF OPERATION
There are two main hurdles to overcome:
- Getting the LOAD routine of each ROM to believe the file is valid and therefore load it. This just requires getting the ROM routines to identify the correct length of file so that all bytes are read in.
- Once all bytes have been loaded, control must be immediately passed to a machine code routine without the need for a valid BASIC environment to be present.
DETERMINING THE LENGTH OF THE FILE TO LOAD
The LOAD routine in the 4K ROM and that in the 8K ROM only actually care about the value of one system variable - E_LINE. This system variable specifies the end address of the program and is used by the LOAD routine to know when the stop reading in bytes. For a program to be loaded by both ROMs, the file must be structured such that E_LINE is valid to both ROM LOAD routines. E_LINE occupies the 11th and 12th bytes of the system variables for a 4K ROM program, but occupies the 12th and 13th bytes of the system variables for a 8K ROM program. However, there isn't an overlap of E_LINE values since an 8K ROM file actually begins with a file name and so these bytes shift the 4K ROM's E_LINE bytes clear of the 8K ROM's E_LINE bytes. As a result, both E_LINE values can hold valid values suitable for their respective ROMs. Using a file name length of 13 bytes was found to be the most practical to ensure there were no conflicts between the system variables required by the BASIC interpreters of the two ROMs.
The structure of a dual ROM loading file is shown below:
PASSING CONTROL TO A MACHINE CODE ROUTINE AFTER LOADING
Once all bytes up to the value of the applicable E_LINE, control is returned to the BASIC interpreter and the action performed is different between the two ROMs. The 4K ROM will attempt to list the BASIC program area whereas the 8K ROM will read system variable NXTLIN to determine the address of the BASIC line that the program will automatically run from.
For the 8K ROM, the value of NXTLIN does not have to point at an actual BASIC line within the BASIC environment area but can point at any location within memory that happens to contain a validly formed BASIC statement. So a dummy BASIC line consisting of a RAND USR statement is set up in the file and NXTLIN set to point at it. This line will be interpreted after loading has been completed and the USR function called, thereby passing control to the machine code routine at the specified address. The routine may be situated anywhere convenient within RAM and once it has control then it may manipulate all of the RAM in whatever way it wishes.
The start up mechanism for the 8K ROM is shown below:
For the 4K ROM, the auto-run technique devised by Martin Korth in 2009 must be used to transfer control to a machine code routine. The ZX80 ROM attempts to list the BASIC program after a file has finished loaded in. Martin's auto-run technique uses a dummy BASIC line that contains an assembly instruction with bit 6 set such that it will actually get executed instead of being rendered as a display character. A specific instruction must be used that modifies the H register such that the next display file location will be the machine code routine situated at $4040 and not the next character in the display file. Since the machine code routine is within the 16K-32K region and not the 48K-64K region, the display hardware does not prevent instructions with bit 6 reset from being executed and so the machine code routine now has full control. However it must first disable interrupts but after that it is thenn free to manipulate all of the RAM in whatever way it wishes. The auto-run mechanism imposes the fairly minor constraint that the program file must end at a $xx3D address boundary, which is necessary to ensure that the L register holds $40 when a LD H,L instruction is execute to set HL to $4040.
The start up mechanism for the 4K ROM is shown below:
The dual ROM file is structured such that the 4K ROM and 8K ROM startup mechanisms to transfer control to their respective machine code routines do not overlap each other. When combined, the complete dual ROM loading mechanism is shown below:

Note that all locations prior to the 65th byte, i.e. up to and including the 8K ROM system variable CDFLAG at $403B, should contain $00, except for the last byte of the file name which should have bit 7 set.
MACHINE CODE ROUTINES
When reading a file, the 8K ROM will discard the file name bytes and will load the program bytes to address $4009. For the 4K ROM, it will start reading the 'file name' bytes to address $4000 and will continuing to read in the 'program' bytes to the subsequent locations. As mentioned earlier, introducing a file name of 13 bytes long produces the best results in terms of avoiding conflict between the system variable bytes required by each ROM. However, a consequence of this is that the file doesn't get loaded to the same address range for both ROMs. Using the 4K ROM, the file contents are loaded 4 bytes higher than they do for the 8K ROM. This means the machine code routine for the 4K ROM must take this offset into account, which it might simply do by first shifting all RAM contents down in memory by 4 bytes. Ideally a file name that is only 9 bytes long would be used as then both the 4K ROM and 8K ROM would load the file data to an identical memory range, but unfortunately a shift of 9 bytes results in conflict between the required system variables.
Once control has been passed to the corresponding machine code routine, there are two types of deployment mechanism the routine can use. Either each ROM has its own payload and this gets deployed to a target location specific to the ROM, or there is a single payload which is patched as appropriate to customise it for use with the active ROM. The former is useful for deploying BASIC programs that might looks similar but are otherwise encoded differently, whereas the latter is useful for a machine code program that run on both ROMs and just requires a few tweaks to some graphic/symbol character codes and ROM display calls, e.g. a flicker-free game. Both approaches can utilise compression to reduce the loading time and to maximumise the available payload size.