Peace Memorial Park, Hiroshima

ZX Spectrum "Chrome"

I still own two ZX Spectrum 48K from my very early days of home computing. The first is my own one, I got it from my parents as a Christmas present back in 1985. The other one was owned by a friend. It was broken and couldn't be repaired, so he first intended to throw it away, but then gave it to me instead.

So here are the two sisters…

Two Sinclair ZX Spectrum 48K

This article is about the restauration of my own ZX Spectrum. There will be a follow-up for the other one.

Let's have a look under the hood. This Speccy has a standard Issue Two board, with a floating transistor on the CPU as an usual factory modification of that board.

My own Speccy. The ROM, ULA, and a 74LS532 are socketed.

It looks alright so far. Let's find out if it is still working.

Composite Mod

All home computers of that era were designed to be connected to the "antenna in" of an ordinary color TV. The TV was tuned to UHF channel 36 to receive the signal. The picture quality was quite okay back in those days, but poor for today's standards.

Today, almost all TVs have a composite input, so there is no need for modulating the signal any more. Luckily the ZX Spectrum can be easily modified to give a composite signal. First, the two existing wires on the side of the TV modulator are unsoldered and just bent to the side (so the mod can be reversed if desired). Inside the modulator, the resistor is disconnected from the RCA jack. Then a new wire is connected from the outside's former signal pad straight to the RCA jack.

The old two wires are removed. A new wire is connected to the left "signal" pad.Inside the modulator, the resistor is disconnected, and the wire is soldered to the RCA jack.

After that modification, the Spectrum can be directly connected to the TV's "composite in". The modification can be easily reverted, and there is no need to drill an additional hole into the case.

Testing

I have lost the original ZX Spectrum PSUs, but any stabilized 9 V PSU with at least 1.5 A will do as a replacement. It is very important to check the polarity of the barrel plug! Most modern PSUs have the positive pole at the inside of the plug, while the ZX Spectrum expects the positive pole at the outside:

Many Speccys certainly have been killed by using a replacement PSU with the wrong polarity.

I powered it up, and to my surprise it was still working!

Almost 40 years old, and still working.

All I would need to do now is giving it the usual technical overhaul.

Recapping

The first thing is to replace the electrolytic capacitors. The old ones dry out over the years, and lose their capacity. Some may even leak and damage the PCB.

To keep the old look, many people prefer axial caps with the classic shape and a blue (or at least black) color.

High quality axial caps are difficult to find and quite expensive. I chose to use Vishay capacitors with an expected lifetime of 2,000 h. However the reference photos of the retailer deceived me. The nice black "classic" caps turned out to have an odd shape, and an aluminum or plastic grey color. They are not of an inferior quality, quite the contrary, but they just don't look vintage. I still decided to use them.

Shiny new caps that should last for much longer.Sadly these ones don't have a classic look, but they will do their job.

There is a trap on the Issue 2 boards: the polarity of C46 is indicated backwards on the silkscreen. The capacitor must be installed with the positive end to the left.

C46 (the upper one) is correctly installed with the positive end to the left, while the silkscreen claims it's on the right.

After the recapping, I thoroughly washed the board with IPA and a toothbrush. Then it was time for another test.

Operation Successful…

…but the patient died. This is what I saw when I powered it up again:

This doesn't look good...

Obviously I broke something. 😯 But what?

I first checked the voltages, but they were all right. No chip was getting hot, except of the ULA, but that's normal.

The ROM chip is socketed, so I removed it. Without the ROM, the CPU always executes the same instruction (RST 38h), which fills the memory and results in a distinct screen pattern.

Screen pattern without ROM, indicating that the CPU is working.

The pattern was there, so the CPU was fine, but it had some noise in it. I suspected a broken RAM chip, and the signal on the data bus actually looked a bit strange on the scope. I started to replace a few suspicious RAM chips, but to make a long story short, it didn't change anything. I was clearly on the wrong track.

I tested the ROM, but it was fine. I swapped the ULA with the one from the other Speccy, but it didn't help either. I checked all the capacitors I had replaced, but they had the correct values and orientation, even that infamous C46.

What has just happened that damaged a previously working Spectrum so badly?

I noticed that, as the only standard chip, IC23 was socketed on that machine. It must have been from a previous repair, because unlike all the other soldering joints, the lead was yellowish there. When I touched the joints with the tip of my soldering iron, they were also sizzling. This was just scrap. I completely removed the old lead, and soldered in a new socket.

Could this have been the problem? I gave power to the Speccy. And yes, it was working again! 😄

I guess that when I cleaned the board with IPA, I partially dissolved the flux in these old soldering joints, making them cold. IC23 is used for the proper access timing of the upper RAM. With a bad timing, the upper RAM might just have disturbed the entire data bus.

Finishing Works

With the Speccy brought back to live, I did some final cleanups.

A defective TR4 is a common cause for a broken power converter. It was still working here, but I precautionary replaced it with a ZTX651, which is the more reliable successor type.

I also preemptively replaced the 7805 voltage regulator by a fresh one, and used thermal paste for better cooling. It's common in the retro scene to replace the 7805 with a modern step-down converter that does not need any cooling, but I decided against it. I like to feel the heat of a working ZX Spectrum.

The ribbons of the keyboard membrane got brittle over the years, and already started to break. Luckily there are new membranes available on the market. And since I was on it, I also ordered a transparent replica case, a black rubber keyboard mat, and a chrome faceplate. I especially like the idea of a transparent case making the inside of this old computer visible.

The restaured ZX Spectrum 48K "Chrome Edition"

The first of both sisters is restored now. The other one might be more difficult to restore though, as it was said to be "broken beyond repair". Let's find out.

R Tape loading error, Part 2

In the first part I showed how the Sinclair ZX Spectrum stored data on tape. This second part explains what is stored, and what causes a tape loading error.

The ZX Spectrum BASIC offers a SAVE command for saving all kind of data. It can be used to save a BASIC program, variable arrays, but also arbitrary parts of memory. These files are always saved in two separate blocks. The first block is called header. It contains the file name, data type, and other meta information. The second block follows about a second later and contains the data itself.

The internal structure of each block is identical. The first byte distinguishes between header ($00) and data blocks ($FF). The final byte is a parity checksum. Everything between these two bytes is the payload.

A header block always contains a payload of 17 bytes. The first byte identifies the file type, followed by the file name (10 characters), followed by the length of the data block, and closed by two optional parameters that have different meanings depending on the file type. The length and the two parameters consume two bytes each, with the lower byte coming first because the Z80 CPU is little endian.

This is an example header block of a screenshot:

00$00 = Header
0003$03 = Binary file (Code or SCREEN$)
0153S
0268h
0372r
0465e
0564d
062E.
077Az
086Fo
096En
1065e
11001BLength: 6912 bytes ($1B00)
130040Parameter 1, here: starting address ($4000)
150000Parameter 2, here: unused
20Parity

A screenshot is actually just a memory dump that starts at address $4000 (which is the starting address of the screen buffer) and is exactly 6912 bytes long (the ZX Spectrum has a resolution of 256×192 monochrome pixels plus 32×24 bytes color attributes, giving a screen buffer size of 6912 bytes).

For other file types, the two optional parameters have different meanings. For example, a BASIC program file stores the line number to start at after loading.

The final byte is the parity. It is used for error detection, and computed just by XOR-ing all the bytes that have been read. The result must be $00, otherwise a "R Tape loading error" is reported.

This kind of error detection is rather weak. Due to the nature of the XOR operation, two wrongs give a right. This means that when the block contains an even number of bad bits at the same position, they will be undetected. It is also not possible to correct reading errors, as the XOR operation only allows to identify the position of the bad bit, but not the actual byte that contained the error. More sophisticated error correction algorithms would have slowed down the loading process, though.

The parity is computed as a final step, after all the bytes have been read from the block on tape. For that reason, the loader can only decide at the end of the recording whether the loading was successful or not.

But then, why does the tape loading error sometimes appear while the block is still loading? Well, in the first part I have explained that the loading routine just reads an unknown number of bytes. It ends when waiting for a pulse change took to long. Now, if there is an audio gap on tape, the signal seems to end just in the middle of the block. It is then very likely that the parity checksum is wrong because there are still bytes missing.

Some simple copy protections made use of the way the Spectrum loads data from tape. A very common way were “headerless” files, where the header block was left out and only the data block was recorded on tape. The BASIC LOAD command was unable to read those files because of the missing header.

R Tape loading error

In the early time of home computers, at the beginning of the 1980's, hard disks and even floppy disks were too expensive for home use. The cheapest way for storing large amounts of data was the cassette tape. Cassettes and tape recorders were affordable and available in almost any household.

In this blog article, I'm going to explain how the Sinclair ZX Spectrum stored programs on cassette tapes. Other home computers of that time, like the Commodore 64 or Amstrad CPC, worked in a similar fashion.

Cassette tapes were designed to store audio signals like voice or music, so the inventors of the home computers had to find a way to convert data to audio signals. The easiest way is to serialize the data to a bit stream of 1's and 0's, and generate a long rectangular wave cycle for "1" and a short rectangular wave cycle for "0". This is what the ZX Spectrum actually does!

A short wave cycle is generated by giving power to the audio output for 855 so called T-states, and then turning off the power for another 855 T-states. A "T-state" is the time of a single clock pulse of the Z80-A CPU. As the CPU of a classic ZX Spectrum is clocked with 3.5 MHz, a T-state has a duration of 286 ns. The duration of a short wave cycle is thus 489 µs, giving an audio frequency of about 2,045 Hz. The long wave cycle is just twice as long.

Due to all kind of filters in the analog audio path, the rectangular signal is smoothed to a sinusoidal signal when played back. A Schmitt trigger inside the ZX Spectrum's hardware converts the audio signal back to a rectangular shape. Since the audio signal can have different amplitudes or could even be inverted, the hardware only cares for signal edges, not for levels. All that the loader routine now has to do is to measure the duration of the pulses, regenerate the bit stream, and put the bytes back together.

If you think that things cannot be that easy, you are right. 😄 The most difficult part for the loader is to find the start of the bit stream. If it is off by only one cycle (or even just a pulse), all the bytes are shifted by one bit, and the result is useless. All kind of noise on the tape makes it impossible to just wait for the signal to start, though.

For this reason, the recording starts with a leader signal, followed by a sync wave cycle, followed by the bit stream itself. The leader signal is just a continuous wave with a pulse length of 2,168 T-states, giving an 806 Hz tone that is displayed by red and cyan border colors on the TV. The sync wave cycle is a pulse of 667 T-States "on", followed by 735 T-states "off". After that, the actual data stream begins, which is displayed in blue and yellow border colors. When the last bit was transmitted, the data stream just ends.

So when the ZX Spectrum loads a file from tape, it first waits for the 806 Hz leader signal. If it was detected for at least 317 ms, it waits for the sync pulses, then it starts reading the bit sequence until there is a timeout while waiting for the next pulse.

It is a very simple way to store data on tape. And still, it is surprisingly reliable. After 30 years, I could recover almost all files from my old cassette tapes. Some of them were of the cheapest brands I could get my hands on back in 1987.

The only disadvantage is that this method is very slow. With 489 µs for a "0" and 978 µs for a "1", saving just 48 KBytes of data can take up to 6 minutes, giving an average bit rate of 1,363 bps (yes, bits per second). If we were to save a single 3 MBytes mp3 file that way, it would take almost 5 hours (and 5 cassettes with 60 minutes recording time each).

Some commercial games used speed loaders and copy protections. Speed loaders just reduced the number of T-states for the pulses, which increased the bit rate. Some copy protections used a "clicking" leader tone, where the leader signal was interrupted before the minimal detection time of 317 ms was reached. The original loader routine could not synchronize to these kind of signals, so it was impossible to read those files into copy programs. Those protection measures could still be circumvented by copying directly from tape to tape, but this only worked a few times due to increasing audio noise.

In the next article, I will take a deeper look at the bit stream contents, and I will also explain where the dreaded "R Tape loading error" comes from.

Z80 Disassembler

Some days ago, I was adding a Z80 disassembler to my tzxtools. I could not find one for Python, so I decided to write my own. The result fits into a single Python source file. This article is the Making-of…

The Zilog Z80 is an 8 bit processor. This means that (almost) all instructions only consume 1 byte. For example, the instruction ret (return from subroutine) has C9 as byte representation. Some commands are followed by another byte (as a constant to be used, or a relative jump displacement) or another two bytes (as a 16 bit constant or absolute address). Some examples:

C9------retReturn from subroutine
3E23----ld a,$23Load constant $23 into A register
C33412--jp $1234Jump to address $1234

Note that for 16 bit constants, the bytes seem to be reversed in memory. This is because the Z80 is a so-called little endian CPU, where the lower byte comes first. Some other processor families (like the 68000 ) are big endian and store the higher word first.

So there are 256 instructions only, which makes it pretty easy to disassemble them. I used an array of 256 entries, where each entry contains the instruction of the respective byte as a string. For constants, I have used placeholders like "##" or "$". If such a placeholder is found in the instruction string after decoding, the appropriate number of bytes are fetched, and the placeholder is replaced by the value that was found.

If we were to write a disassembler for the 8080 CPU, we were done now. However, the Z80 has some extensions that need to be covered, namely two extended instruction sets and two index registers.

One set of extended instructions is selected by an $ED prefix, and contains rarely used instructions. The other instruction set is selected by a $CB prefix and has bit manipulation and some rotation instructions.

EDB0----ldirCopy BC bytes from HL to DE
ED4B7856ld bc,($5678)Loads value from address $5678 into BC register pair
CBC7----set 0,aSet bit 0 in A register

For the $ED prefix, I used a separate array for decoding the instructions. The $CB instructions follow a simple bit scheme, so the instructions could be decoded by a few lines of Python code.

The Z80 provides two index registers, called IX and IY. They are used when the instruction is prefixed with a $DD or $FD byte, respectively. These prefixes basically use the selected index register instead of the HL register pair for the current instruction. However, if the (HL) addressing mode is used, an additional byte sized offset is provided. The index registers can be combined with the $CB prefix, which can make things complicated.

E5------push hlPush HL to stack
DDE5----push ixPush IX to stack (same opcode E5, but now with DD prefix)
FDE5----push iyPush IY to stack (now with FD prefix)
FD2180FFld iy,$FF80Load $FF80 constant into IY register
DD7E09--ld a,(ix+9)Load value at address IX+9 to A register (offset is after opcode)
CBC6----set 0,(hl)Set bit 0 at address in HL
FDCB03C6set 0,(iy+3)Set bit 0 at address IY+3 (offset is before opcode)

When the disassembler detects a $DD or $FD prefix, it sets a respective ix or iy flag. Later, when the instruction is decoded, every occurance of HL is replaced by either IX or IY. If (HL) was found, another byte is fetched from the byte stream and used as index offset for (IX+dd) or (IY+dd).

There is one exception. The examples above show that the index offset is always found at the third byte. This means that when the index register is combined with a $CB prefix, the actual instruction is located after the index. This is a case that needed special treatment in my disassembler. If this combination is detected, then the index offset is fetched and stored before the instruction is decoded.

Phew, this was complicated. Now we’re able to disassemble the official instruction set of the Z80 CPU. But we’re not done yet. There are a number of undocumented instructions. The manufacturer Zilog never documented them, they are not quite useful, but they still work on almost any Z80 CPU and are actually in use.

Most of them are covered just by extending the instruction arrays. Additionally, the $DD or $FD prefixes do not only affect the HL register pair, but also just the H and L registers, giving IXH/IYH and IXL/IYL registers. This is covered by the instruction post processing. A very special case is the $CB prefix in combination with index registers, giving a whole bunch of new instructions that store the result of a bit operation in another register. This actually needed special treatment by a separate $CB prefix instruction decoder.

Finally, the ZX Spectrum Next is going to bring some new instructions like multiplication or ZX Spectrum hardware related stuff. They were again covered by extending the instruction arrays. The only exceptions are the push [const] instruction where the constant is stored as big endian, and the nextreg [reg],[val] instruction that is (as the only instruction) followed by two constants.

And that’s it. 😄 This is how to write a Z80 disassembler in a single afternoon.

Recovering old ZX Spectrum tapes, Part 2

The ZX Spectrum was a comparable cheap home computer, and thus the tape loading and saving mechanisms have not been very sophisticated. The tape recording is just a stream of short waves (0 bit) and long waves (1 bit). The stream starts with a leader signal (a series of even longer waves) and a single sync pulse. So, in the theory, reading a tape recording means measuring single wave lengths, by taking the time between two zero-crossings, and converting them into a sequence of bytes.

But then again, we are dealing with 1980's analog technique. In practice, we will find signals like this. A click produced an additional zero-crossing that is to be ignored. Also, the amplitudes and DC offsets change all the time.

And pooof... There went another week of nightly hacking Python code, having very close looks at audio waves, and searching for clues about why tzxwav won't behave like I expect it to behave. But I think the result is worth looking at now! tzxwav now reads almost all of my tape samples without those dreaded CRC errors. If there are CRC errors, the sample was usually so damaged that it would need manual restauration.

And as a bonus, it is now almost twice as slow as before. 🤭 But speed was never a goal anyway, as people are likely to convert their old tapes only once.