Egghead.page Logo

How Sinclair ZX81 SAVE and LOAD Commands Work Internally

The Sinclair ZX81 relied on standard audio cassette tapes for persistent storage, utilizing a straightforward yet ingenious method to transfer data between memory and magnetic media. This article explores the internal mechanics of the SAVE and LOAD commands, detailing how the Z80 processor and Uncommitted Logic Array (ULA) collaborated to serialize RAM contents into audio pulses and deserialize incoming signals back into executable code. We will examine the specific bit encoding standards, the structure of data blocks, checksum verification processes, and the precise timing routines housed within the ROM that made data storage possible on such limited hardware.

The Hardware Interface and ULA Role

At the hardware level, the ZX81 did not possess a dedicated disk controller or complex storage interface. Instead, it used a simple serial interface connected to the edge connector or later 3.5mm jack sockets labeled EAR and MIC. The Uncommitted Logic Array (ULA), a custom chip designed to reduce component count, played a critical role in this process. The ULA handled the precise timing required to generate and read audio signals, offloading some of the burden from the Z80 CPU. When saving, the ULA converted digital data from the CPU into voltage changes suitable for a cassette recorder. When loading, it interpreted voltage changes from the tape into digital signals the CPU could process.

Serial Data Encoding Standards

Data on the ZX81 was stored as a series of audio tones representing binary data. The system used a specific encoding scheme where bits were defined by the number of cycles or pulses. A binary 0 was represented by a single cycle consisting of a high state followed by a low state, lasting approximately 855 microseconds. A binary 1 was represented by two cycles, effectively doubling the duration to roughly 1710 microseconds. This method, often referred to as pulse-width modulation, allowed the system to distinguish between bits based on the duration of the signal rather than frequency shifts, simplifying the required circuitry.

The SAVE Command Process

When a user executed the SAVE command, the ROM routine initiated a sequence to write the contents of a specific area of RAM to the tape. The process began with a leader tone, a long series of pulses designed to allow the cassette motor to reach stable speed and the loading circuitry to synchronize. Following the leader, the system wrote a header block containing metadata such as the filename and the length of the data block. Finally, the actual data block was transmitted. During this transmission, the Z80 CPU fetched bytes from memory and passed them to the ULA, which generated the corresponding audio pulses. A checksum byte was calculated and appended to the end of the data block to ensure integrity.

The LOAD Command Process

The LOAD command reversed this operation, requiring precise synchronization to interpret incoming audio signals correctly. The ROM routine initially entered a waiting state, listening for the leader tone to establish timing baseline. Once synchronized, the system began sampling the input line from the EAR socket. The CPU measured the duration of each pulse to determine whether it represented a 0 or a 1. These bits were assembled into bytes and written into the designated area of RAM. As each block was received, the system calculated a checksum and compared it against the transmitted checksum byte. If the values matched, the data was considered valid; if not, the process halted.

Error Handling and System Limitations

The ZX81’s storage mechanism was susceptible to noise and timing discrepancies inherent in consumer cassette tapes. If the checksum verification failed during a LOAD operation, the system would display the infamous R Tape loading error message and halt execution. This error could result from tape degradation, incorrect volume levels, or interference. Because the CPU was heavily involved in the timing loops for serial communication, the screen would often go blank or display vertical stripes during SAVE and LOAD operations. This occurred because the video generation logic shared resources with the CPU, and the intensive timing requirements of the cassette interface took precedence over screen refresh routines.