Frost on a roof

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.

Recovering old ZX Spectrum tapes

The author, on Christmas Eve 1985 Since I am in the mood of heavy ZX Spectrum retro action, I dug out all my old computer tapes in an attept to digitize and convert them. It turned out to be more difficult than I thought...

The first trouble was to find a tape player. I had disposed my last tape recorder a couple of years ago. The new ones I found at Amazon looked nice on the first sight. They could be connected to the USB port or even digitize the tapes straight to an USB stick. The customer reviews were scaring off: cheap plastic, poor sound, digitizing to USB stick was only possible on battery power... I had more luck on eBay, where I found a genuine 1990’s Aiwa Walkman (it’s even a recorder, with auto reverse and Dolby NR) in good condition for about the same price.

I connected the Walkman to my computer’s microphone input using a cable with 3.5mm stereo jacks, selected the correct tape type (Normal or CrO2), and turned off Dolby NR. Then I digitized right away, using Audacity for recording and post processing. I used 16 bits per channel, and a 44100 Hz sampling rate. The ZX Spectrum provided a mono signal, so I chose the left or right channel (depending on the quality) and discarded the other one. Mixing down the stereo signal turned out to be problematic, as well as using a lossy file format like ogg.

The WAV files can be loaded into the Fuse Emulator, but it’s better to convert them to TZX files, as they are much smaller. There are a few tools for that, for example audio2tape that comes with Fuse. I wasn’t satisified with the result though, as the generated TZX files contained many CRC errors. MakeTZX is also worth a try, as it supports digital filters, but I was unable to make it run on Linux. Some other converter tools are for Windows only, and thus not very interesting. 😉

So I found myself writing a set of TZX tools in Python. It contains tzxwav, that’s yet another tool for converting WAV files to TZX files, but is robust against poor audio quality. It took me three weeks of work, and about 30 hours of tape material, until it was able to successfully read almost all of my tape recordings.

An advantage of TZX files is that they contain the raw ZX Spectrum binaries, so they are very easy to extract. tzxcat allows to retrieve single binaries from TZX files, which can then be converted into PNG files, BASIC sources or whatever, provided there are converters for it.

What I have now are TZX files of all my old ZX Spectrum tapes. It was very interesting to rediscover my old files, screens, programs and source codes. In 1987 and 1988, I wrote a lot of more or less useful tools, designed several fonts and completed two demos.