The Z80 micrcomputer was a revolutionary chip in the 1970’s - a more capable alternative to the Intel 8080. The commercially smart move was that it implemented (almost exactly) the same instruction set as the 8080, while adding many extra registers and instructions.

The Z80 is an 8-bit micrcomputer (i.e. 8 bits of data, D0..D7) with support for up to 64 KB of memory (i.e. addresses of 16 bits, A0..A15). A separate group of instructions deals with I/O, so reads and writes can be either in memory, or in “I/O space”. This all clearly shows in Zilog’s original presentation of the Z80:

The 8080 and the Z80 were affordable enough to start a whole new trend in “hobby computing” in the 1970’s and 1980’s. Several other popular chips, each with their own instruction set, were the Mostek 6502, and Motorola’s 6800 and 6809.

As a kid, I used to read all I could about these fascinating new devices, trying to get my hands on (and my head around) as much information as would (sparsely) travel across the Atlantic ocean. This was way before the digital revolution of the internet.

How the world has changed. I’ll spare you a recount of all the obvious ways to compare the “then” with the “now”. Let me just single out this device, the Blue Pill:

Like the Z80, it has 2 rows of 20 pins (or holes). And like the Z80, it runs on 5V.

But that’s also where, some 40 years later, the comparison ends:

In summary: the BP is a fully functional computer, whereas the Z80 is just the (essential) CPU part of a more elaborate system. The BP is also far more powerful and efficient.

Let’s build a computer

The Z80 is a very elegant design, with a huge amount of software written for it. Primitive perhaps by today’s standards, but don’t rush to conclusions just yet: compilers, word processors, spreadsheets, games - it’s all available. And all this software runs in 64 KB.

When it comes to learning how a computer really works inside, the Z80 is actually quite attractive: the architecture is simple to understand, and the available tools make it fairly easy to peek and poke around, i.e. to tinker with it, without upsetting anything (or anyone). Today’s machines (as far as CPUs go, at least), are more of the same, plus some tricks.

Which brings me to the Big Retro Proposal™ in this article: let’s take a Blue Pill, and run an emulated version of a Z80 on it. As you will see, it’s not only surprisingly easy, but the result is also reasonably capable - a system which many hobby computer fans of those days would have loved to own, back then. All with a board costing less than €2.

The emulator

The trigger for this idea came from a project I recently discovered: the z80emu project, by Lin Ke-Fong (@anotherlin on GitHub). Not only is this a very nicely-written emulator, it’s also well-tested and has a simple way of tying into the outside world.

With emulation, it’s actually this tying into the outside world aspect that tends to require the most attention. Once you have a virtual Z80, how do you make it connect to anything? Do you virtualise that as well, and if so, up to what point? Without some careful choices, this could quickly turn into the totally unreal world of the Wachowski’s movie “The Matrix”.

Apart from this outer emulation context, there is also the question of what the resulting system should actually, ehm … do? This is the same question people had to deal with back in the days - tediously toggling in little programs, or making the Z80 read a program from an audio cassette deck, or (later) floppy disks. And all over again after a power cycle.

Setting the context

To avoid falling too deep into this rabbit hole, I will make some simple choices for now. Everything is emulated in software anyway, so it can easily be “upgraded” later on:

So the result will be a Blue Pill, no soldering or wiring whatsoever required, no original Z80 chips involved, nor any other hardware from that era, but still a virtual environment which is “real” (i.e. accurate) enough to run Z80 code written several decades ago.

It may not be everyone’s idea of retrocomputing, but it works for me, and it creates a very retro’ish (and safe) environment to learn exactly how a CPU works, i.e. inside that chip.

The Blue Pill acts as convenient host and conduit into the 1970’s world of the 8-bit Z80.

Preparations

Please go through this recent article titled “Getting started with the Blue Pill”, if you haven’t already. It describes how to set up the entire hardware, software, and upload environment for a Blue Pill. Everything below in this article hinges on being able to blink the Blue Pill’s on-board LED, and seeing the serial console output appear.

The first thing I did, was to copy Lin Ke-Fong’s code to a directory with the proper structure to be used in a PlatformIO project. The result can be seen here.

Next, I set up a platformio.ini file for PIO to compile and upload properly:

[env:bluepill]
build_flags = -I../common-z80
platform = ststm32
board = bluepill_f103c8
framework = stm32cube
upload_protocol = blackmagic
upload_port = /dev/cu.usbmodemE0C2C5A7
lib_deps = jeeh

The final step is to write the main application code and tie everything together.

Main application

I came up with a main program which wraps all the emulator code in a way compatible with the JeeH library. Some changes to the z80emu files were purely cosmetic, bringing it more in line with the C++ style I’m used to (the original z80emu is in “plain” C, not C++). Here is the gist of the new src/main.cpp code:

#include <jee.h>

extern "C" {
#include "context.h"
#include "z80emu.h"
}

UartBufDev< PinA<9>, PinA<10> > console;
PinC<13> led;
Context context;

void SystemCall (Context *ctx, int) {
    ...
}

int main() {
    console.init();
    console.baud(115200, fullSpeedClock());
    led.mode(Pinmode::out);

    [...]

    Z80Reset(&context.state);
    context.state.pc = 0x100;
    context.done = 0;

    do {
        Z80Emulate(&context.state, 4000000, &context);
        led.toggle();
    } while (!context.done);

    led = 0;
}

The src/context.h header is the interface between z80emu and the outside world:

#include "z80emu.h"
#include <stdint.h>

typedef struct {
    Z80_STATE state;
    uint8_t   mem [1<<14];  // size should be a power of two
    uint8_t   done;
} Context;

inline uint8_t* mapMem (void* cp, uint16_t addr) {
    Context* ctx = (Context*) cp;
    return ctx->mem + (addr % sizeof ctx->mem);
}

extern void SystemCall (Context *ctx, int request);

The Blue Pill has 20 KB RAM, of which 16 KB will be used for emulation. To mimic the way most systems worked back then, the upper 2 address lines are left “undecoded”, i.e. memory at address X can also be accessed at X+16K, X+32K, and X+48K. This way, all address space access is well-defined. It’s easily implemented with a logical “and” mask:

uint8_t readByte (uint16_t addr) {
    return ram[addr & 0x3FFF];
}

Loading a Z80 program into memory before starting the emulator, requires three steps:

  1. Convert the binary zexall.com file to a C header, using this command:

    xxd -i <zexall.com >zexall.h
    
  2. Include this header file, so it gets compiled into read-only flash memory:

    const uint8_t rom [] = {
    #include "zexall.h"
    };
    
  3. Before starting the emulation, copy the data into the Z80s simulated RAM:

    memcpy(ram, rom, sizeof rom);
    

That’s it. The emulator will start to execute its machine code, and as if by magic, the RAM will already contain a valid program. This little trick sure saved us a lot of toggling, eh?

The final step

The last step is to compile and upload the code. If you’ve copied everything from the bluepill-z80ex example in the Retro project, it should all compile without errors.

To actually try it out and see what’s going on, we need a connection to the serial console, as described in the “Getting started with the Blue Pill” article. This connection can be kept open, since it’s independent of the SWD upload mechanism of the Black Magic Probe.

Here is what gradually appears on the console once the SWD upload completes:

Z80 instruction exerciser
<adc,sbc> hl,<bc,de,hl,sp>....  OK
add hl,<bc,de,hl,sp>..........  OK
add ix,<bc,de,ix,sp>..........  OK
add iy,<bc,de,iy,sp>..........  OK
aluop a,nn....................  OK
aluop a,<b,c,d,e,h,l,(hl),a>..  OK
aluop a,<ixh,ixl,iyh,iyl>.....  OK
aluop a,(<ix,iy>+1)...........  OK
bit n,(<ix,iy>+1).............  OK
bit n,<b,c,d,e,h,l,(hl),a>....  OK
cpd<r>........................  OK
cpi<r>........................  OK
<daa,cpl,scf,ccf>.............  OK
<inc,dec> a...................  OK
[...]

Note: ZEXALL takes several hours (!) to complete. The on-board LED toggles every 2,000,000 simulated cycles to indicate that the emulation is still running.

Yeay, the code is running on what it thinks is a Z80, and it’s working exactly as intended!

The entire emulator easily fits in the Blue Pill, including the emulated RAM and ROM:

DATA:    [========  ]  84.2% (used 17236 bytes from 20480 bytes)
PROGRAM: [===       ]  29.8% (used 19532 bytes from 65536 bytes)

The LED blinks at 1.7 Hz, so this emulates almost twice as fast as the original 4 MHz Z80.

What is ZEXALL?

As mentioned, ZEXALL is an instruction exerciser. It tries to execute every possible machine instruction available in the Z80 instruction set, and compares the effect of each instruction with its expected outcome. If register “A” contains 255, and it is incremented, then register “A” should now contain 0, and all the other register should be unaffected. Furthermore, the “Z” (zero) flag should be set and the “N” (negative) flag should be clear.

It’s not possible to 100% exhaustively check every possible combination, if only because some instructions have an uncontrollable effect on the program (the “HALT” instruction, for example). Likewise, I/O instructions and interrupt handling cannot as easily be exercised this way. But it’s nevertheless an effective technique to catch most CPU logic problems, and an excellent tool to test emulators such as z80emu.

The way ZEXALL works is quite clever: it takes the CPU state (registers, mostly) and computes a checksum before and after the instruction it is testing. If they match expected values, then not all the details need to be compared individually.

As for what a Z80 machine instruction is - that’s a whole different can of worms, eh, topic!

Congratulations. You now have an (emulated, but) functional 8080 & Z80 computer, which led to the Altair 8800, Microsoft BASIC, CP/M, WordStar, Turbo Pascal, SuperCalc, then the x86 family, MS-DOS, Windows, Linux, MacOS, and that whole snazzy smörgåsbord of silicon-based personal devices we now carry around and stare into.