No, not that T80. That T80. As you know, I'm working on the UnIC project and while I was working towards my first form-factor build, the news broke that Zilog will discontinue the Z80 processor. This is a sad news but can be turned into motivation: The first use-case of UnIC could be a Z80 replacement.
Getting started with the T80 core
So, I started working on the core. First, I put together a simulation environment, something where I could run some test code.
This wasn't terribly hard thought I had to scrounge around the web to find a fork of the original T80 project that had any test-bench to it. Once I had that, I could easily adopt it to my need.
Tool misery
The T80 project is in VHDL. There is an open-source VHDL simulator, called GHDL. There is also an open-source Verilog simulator (iverilog). Now, both of these tools only understand their own languages and their internal implementation is different enough that never the two shall meet. This means that I don't have a way to work on mixed-mode projects with a hodge-podge of Verilog and VHDL at least with these tools. There are commercial alternatives, most notably ModelSIM (or whatever it's called this month). Intel even offers a free (limited) license to it, if you can figure out their byzantine registration process, that is. I gave up; YMMV.
So, even though I'm not all that familiar with VHDL, I resigned to to build the whole test-bench in that language. Oh well.
The biggest hurdle in this whole process was to get some Z80 code inside the simulator. This involved getting Z80 code compiled, translated into something that then can be fed into the VHDL simulator. As it turns out there are several Z80 assemblers out there, all with their unique file formats and even assembly syntax. All incompatible with one another of course. Published assembly examples work with one but not the other. The authors usually neglect to say which assembly flavor they're using. It's a trial-and-error. In some cases I couldn't even trace down an assembler that worked with the code at hand. I of course am completely unfamiliar with the Z80 instruction set, so I would have preferred to work with a high-level language, such as C. There are C compilers for the Z80, but now I have to figure out the matching toolchain such that the C compiler generates assembly in the format that the assembler expects them to be in. In short: it's a mess. Even after figuring all that out, there's the problem of how to get the generated binary into the simulator; or to be more precise how to initialize the simulated SRAM with the generated content. I ended up writing a small python script to do that: take a binary file and spit out a VHDL file based on a template that contains the appropriate magic incantations for content initialization:
#!/bin/python
import argparse
parser = argparse.ArgumentParser(
prog='ram_init',
description='Crate an initialized SRAM block from a binary file as content',
epilog='================================'
)
parser.add_argument('-i', '--infile', required=True)
parser.add_argument('-t', '--template', required=True)
parser.add_argument('-o', '--outfile', required=True)
parser.add_argument('-n', '--entity_name', required=True)
#parser.add_argument('-e', '--endian', default='big', choices=('big', 'little'))
args = parser.parse_args()
with open(args.template, "rt") as template_file:
template = template_file.read()
content_str = ""
with open(args.infile, "rb") as in_file:
while (byte := in_file.read(1)):
byte_as_int = int.from_bytes(byte, 'big')
content_str += f' X"{byte_as_int:02x}",\n'
content_str = content_str[:-2] # delete final new-line and comma
final = template.replace("$$$CONTENT$$$", content_str)
final = final.replace("$$$ENTITY$$$", args.entity_name)
with open(args.outfile, "wt") as out_file:
out_file.write(final)
With my precious content in the SRAM I could start the testing process.
Simulator fun
GHDL is not the fastest kid on the block when it comes to simulation. The test-bench I was using simulated a real UART with real dividers and bit-lines wiggling. The top-level had a wrapper that re-interpreted these wiggling lines (start bits, parity, the whole nine yards) and finally spit the output out on the console. Needless to say that anything interesting took a lot of clock cycles just spinning around waiting for the UART. This not only takes a long time to simulate, but the generated waveform files are also dominated by boring stuff: the actual instruction execution is lost in all the noise.
I ended up ripping all this out and replacing a simple implementation where a write to a certain I/O address would end up on the console.
I also needed some kind of a reference implementation. I.e. a SW simulator. I've decided to use Yaze-AG as this was available in source-code form (so I could hack in what I needed), seemed to enjoy continuous support and appeared to be held in high regard.
Another simulator I used was the Visual Z80 project. This is not even a gate-level, but a transistor-level simulation of a Z80 processor. Very impressive project and for very short code-sequences the absolute reference. I used it to validate certain assumptions about signal timings.
Pretty soon the project gotten to the point where basic code could execute in the processor. Not all that surprising, given that I started off from a verified core.
Onto the FPGA
The next step was to get some real mileage into the core. One can execute a few thousand instructions in a simulator, but not anything substantial. I needed to modify my test-bench to be synthesizable and downloadable to a real FPGA. This wasn't all that hard, especially because the nice folks whom I've borrowed the test environment from already included a 16550 compatible serial controller core. This was the controller that I ripped out early on to improve performance. Back it went now into a new top level with a thin wrapper for pin-mapping, clocks and tri-state buffers.
I'm using my UnIC development board for this exercise. This means putting together a project in the GoWIN toolset, which is rather ... spartan. That's not necessarily bad though, simplicity can be an advantage. I have just two complaints: the IDE stores some project configurations (such as alternate pin usage) in the user configuration file along with for example editor history. Synthesis doesn't report hierarchical resource usage so it's very hard to figure out why a project is as large as it is. Neither of these are deal-breakers, just annoying.
With the serial port working and connected to an FTDI chip (actually the same FTDI chip that acted on another port as the JTAG programmer), I pretty soon was running code on the FPGA. Hello world level code, but hey! I was clocking the core at 27MHz as that is the crystal that I had on the board. This of course is much faster then any Z80 could do, but that's not a problem. It just makes testing faster. Only one does have to take that into account when configuring the UART.
ZEXALL
Quite a word, isn't it? Rolls off the tongue. Anyway, this piece of code was written a long time ago for testing Z80 implementations (mostly software simulators) against the original Z80 ISA.
Part of the problem with the Z80 is that it didn't do full instruction decode, that is to say that instruction code sequences not specified in the datasheet did ... something. They were in essence undocumented instructions. Some folk started discovering them and - the useful ones at least - saw some use in actual SW. So it was necessary for simulator developers to accurately simulate these instructions.
Thus the need for extensive testing for all instructions - documented or otherwise - was born. The result was ZEXALL, written by Frank Cringle. It's an inscrutable peace of code that executes (through self-modifying code, because, you know, why not) all instructions on the Z80 and records the results (i.e. register value changes) in a fingerprint in the form of a CRC. This CRC is then compared with the value captured on a real Z80. Instructions are grouped into two dozen or so tests and executed one after another.
This code is really good for getting assurance that your implementation works. It's completely useless in telling you what you screwed up.
Worse - as I discovered after a few days of frustration - the CRCs actually depend on the load-address of the code. Recompile to a different address, or change the beginning of the assembly so that the actual tests are executed at a different address then originally intended and the CRCs come out different.
Now, this is important because the original was written to run as an application under CP/M, while I was running it on bare metal. I had to replace the I/O routines (at the beginning of the executable) and change the start address from 0x0100 to the reset vector: 0x0000.
You see why I was staring for days at CRC mismatches.
Even after figuring all this out, how would I know what the expected CRCs should be in my environment? Eventually I decided to get a reputable software simulator (Yaze-AG) and just assume that whatever the reported CRC on that platform is, is what the CRC should be. That of course also meant that I needed to modify Yaze-AG to be able to execute exactly the same binary that I was running on the VHDL model. This meant delve into yet another unfamiliar code-base (the third if you keep count) to make this happen.
After a while, I managed to get my ZEXALL binary run on the simulator, record the CRCs and modify the source so that the expectation is what Yaze-AG reported. At this point ZEXALL passed on the soft-simulator. Did it pass on the FPGA as well? Yes, yes it did! This of course is not a big surprise, the T80 core itself is well-tested, being the main workhorse core for the MiSTer project.
Bus cycles
Be the T80 be as robust is it may, that doesn't mean it's perfect. I was fairly confident in its ability to execute Z80 code - so all this work so far was just to verify what I expected all along - but much less in the accuracy of its bus interface; The MiSTer people would never actually needed to interface it to real HW.
You see, the Z80 is funky in that it changes bus-control signals on both falling and rising edges. Not even consistently. For instance, the MREQ_N signal, which is used to mark memory transfers gets asserted on a falling edge of the clock consistently but de-asserted on the rising edge for M1 cycles and on the falling edge for others. Lovely.

I was right, some of the falling and rising edges of control signals were half a cycle off. This took me a while to sort out as moving the control signals around also meant that address and data needed to be presented on the buses at a different time, which of course also meant that the values of those signals needed to be computed earlier in the core.
Anyway, a few days of screwing around with this unfamiliar core resulted something that - at least in simulation - looked very much like the bus-cycles a true Z80 would generate.
All along this time, I was already able to run ZEXALL to completion on the FPGA platform, so one would have hoped that I didn't screw up the internals of the core too badly either. (A side-note here: the memories and the serial port that I put in the FPGA really were rather lenient in what they accepted for bus-cycles. The fact that they worked and continued to work meant little in terms of bus-cycle accuracy or even correctness.)
This was also the time when the Visual Z80 project proved indispensable as reference material.
Even with all that, I can't really vouch for instruction timings, that is that all instructions take exactly the same number of cycles as they do on a real Z80. That's really really difficult to test without a very good test program. So, does it work? The proof is in the pudding as they say, but where is the pudding?
Next steps
I'll leave you on this cliff-hanger. The reason is this: in order to make further progress, I need a cadaver of a machine. I need a Z80 based machine where I can replace the CPU with my own contraption.
I've been looking into this for a while now. In fact, if I'm really desperate, I could even claim to have such a machine: a Timex Sinclair 1000 (a.k.a ZX81). This little monster has a bad rep, but I think it's a really neat piece of engineering - I should publish a write-up on all the marvelous ways the engineers optimized this tiny machine. Truth to be told though, it is weird. It is very limited, uses the Z80 in some very special ways, and that keyboard <shudder>! So no, I would not be looking forward to using that machine for bring-up. I also looked at MSX machines, but those are weird in other ways. Many of them have a single-layer (!) PCB design, may or may not have the CPU integrated into their chipset. Not ideal either. What I really would like to have is a CP/M machine. After some searching around, I've settled on a PCW8256 from Amstrad. So, the next step is going to be to acquire one of those, get a little familiar with it and then go at it with the soldering iron.
For now, all I have to show for all this effort is the published fork of the T80 project at https://github.com/andrastantos/unic_t80.
All of that will happen in the future though, so till then - to quote another T8xx - "I'll be back".
Leave a comment
Comments are reviewed before they are published, so yours will not show up right away.
You can use
**strong**and*italic*text,`code`, and lists (lines starting with-or1.). Everything else, HTML included, is shown as it was typed.