Z80 Explorer is a Zilog Z80 netlist-level simulator capable of running Z80 machine code and also an educational tool with features that help reverse engineer and understand this chip better.
Z80 Explorer is a tool I wished I had a few years ago when I first started looking at the photos of the Z80 chip die and was learning to reverse-engineer its features. The process was slow and painful as it involved deciphering the faint image traces into logic gates and functions.
Sometimes later, I found that the Visual6502 team has done wonderful work mapping the CPU’s traces into bitmaps representing various layers. Their online viewing tool is impressive, and one can learn a lot from using it, but like most online tools, it has limitations, which I quickly hit when trying to understand the chip behavior in more depth.
My wish list of additional features steadily grew as I kept playing with the online tool. I would have wanted it not only to be a fully functional and fast simulator but also to provide more elaborate ways to gain deeper insights into the chip’s internal behavior while also being educational, easy, and intuitive to use.
Fast forward to today, and with the help of repeated COVID-19 stay-at-home orders, I have written this tool to be how I initially imagined it.
In this blog, I will give an overview of Z80 Explorer‘s capabilities and show some valuable features that might be easy to miss even after reading the documentation. This blog may change periodically along with the tool itself, as I am actively developing it at the moment (Summer 2020).
The tool’s user guide is a separate online document: Z80 Explorer Guide. It is concise; if something is unclear, please email me, and I will explain it better.
The tool uses the Z80 dataset (layer images and netlist). It should be able to accommodate other NMOS chips with minimal changes. However, at this time, I haven’t done any other ports yet, as I was solely focused on Z80. The chip’s data (resources) are kept separate from the application and can be independently downloaded and updated from a shared Github repo. In particular, as the functions of various nets are better understood and the nets and buses get named, the list of the net names, tips, and annotations can grow and be shared.
Z80 Explorer is capable of running native Z80 code at the netlist level. That means as the instruction opcodes are fed to its pins, the binary 1s and 0s propagate through its internal nets of transistor gates and perform the function identical to what the silicon gates would do on a real chip.
The engine that runs it is pretty fast: On my original 4GHz i7-4790K CPU, when I wrote it, I could run Z80 code at around 2.3kHz, which is (only!) around 2000 times slower than the speed it would run on real silicon. On my new PC, Intel i7-13700K (5.6GHz), it runs at 4.1kHz. That is about 1000 times slower than the real chip.
At those “speeds”, it is not inconceivable to run some of the standard CPU diagnostic programs – so I did just that: I ran a well-known ZEXALL diagnostic program.
That program takes typically hours, even on a real Z80.
After a few days of running within the simulator, the list of passing tests kept growing. At one point, after a week or so, the simulator’s internal cycle counter overflowed its 32-bit variable, and the simulation stopped. I had to resume it, with no need to reset it and no loss to the accumulated progress.
I have added that version of ZEXALL to the app resources. It is modified from the original in that I sorted the tests by how long they run: with the quickest going first, it does not take too long before you start seeing some results, assuring you that it is running well.
Z80 Explorer implements “Image views,” which show various versions of chip images. Some of them are simply unmodified resource files shown as layers (diffusion, metal layer, etc.), and some are created as combinations of those; for example, “vss.vcc.nets.col” is a layer combined with the nets colored such that ground is shown as green, VCC as red, and the rest of the nets are colored according to the user-specified filters.
You can view different layers and create layer combinations if you hold down the Ctrl key while clicking on layer buttons or press a key assigned to each layer while holding down the Ctrl key.
The chip/layer view can also be annotated. At the start, the application loads a default annotation file, but you can load any other annotation file by dragging and dropping it into the application image view. For example, “annot_internals.json” (also located in the resource folder) contains a different set of annotations focused more on the internal features. Annotations are adaptive, so their text will show and hide as you zoom in and out of the image. They also can contain macros, which are tokens that expand into named net and bus values, and those are updated in real-time as the simulation runs.
In my older article here I looked at the Instruction Register. The signal that enables loading is a complementary WE (Write Enable) pair of control traces.
Can we find precisely at what time(s) the write enable, now called “load_ir”, is asserted? What is the internal logic equation that governs this control signal?
Using the Z80 Explorer‘s “Find” option to search for the “load_ir” signal name and then asking for the schematic of that net brings up this view:
Hence, the signal is generated by OR’ing net 255 with a latch. Let’s follow net 255, a NAND gate of the clock (hence, a clock gating), with net 1329. Selecting (double-clicking on) 1329 and asking for the schematic brings us even closer to what we expected to see:
Therefore, net 1329 is a clock-gated, NAND-combined signal, active when M3, T3, and PLA22 are active. PLA22 represents “IX/IY+CB” instruction extension decode. (The description of PLA22 is held in the application “tips” file, as are descriptions of all other PLA entries and some other essential nets).
Back to the latch 244 – and this part may not be too obvious unless you have some experience looking at the chip traces – the net 244 is at the bottom, and the latch set and reset signals are at the top, both clock-gated:
Asking for the schematic of net 1306 (the one connected from the top-left), which also acts as the latch reset:
we see that the latch will reset on the “internal reset” or a T3 cycle. The latch will be set on an M1 and T2 cycle edge (so it will show at M1/T3):
We can verify what we’ve found by running a hand-crafted test code. I used a template test program “test_blank.asm” to code in a couple of instructions, one using IX register, and then I ran it for a couple of cycles. In a Waveform view window, the result shows how the load_ir signal is being asserted at every M1/T3 and at M3/T3 when the instruction is using the IX/IY prefix (PLA22 active).
Next, load the “annot_internals.json” file by dropping it onto the application’s image view (the main pane).
You can zoom into the area where are all M and T latches located by pasting this command into the Command Window:
On the startup, the app will try to detect latches, and it will detect most of them automatically. For those not detected, you can add them as you find them. The easiest way to find latches is by using the “Driven by” option. After selecting a net and following its signal chain, if you see two nets being driven by each other in a co-dependent loop, you have found a latch consisting of those two nets (also acting as inverters). One of the app’s initialization files, “latches.ini” contains definitions of additional latches. You can add to that file as you find latches that the app did not detect.
The schematic view uses an expanded version of the “Driven by” algorithm to build a tree of gates contributing to the selected net.
Going the other way, the “Driving nets” option assists you in tracing an input net as it branches into the network. For example, pick the /RESET input pad and iterate “Driving nets,” following the highlighted lines. Soon, you should reach a “dead end,” with the nets, which apparently nothing is driving here:
img.setZoom(2.926); img.setPos(338,606); img.show(294,548,80,101)
About these commands: To create these zoom and position commands yourself, set up the desired view and then type “img.state()” in the Command window. The required lines will be printed in the Application Log window.
To obtain the coordinates used in the img.show() command, right-click and select an area you want to highlight, and then simply read the coordinates from the Log window and paste them into img.show() as arguments.
The particular network mentioned above contains reset input flops and latches. One of the control signals coming out of it is “int_reset,” or internal reset:
This signal branches off to different parts of the chip.
Every chip normally has several signals propagated across its die to literally every corner. Some of those networks are power, ground, reset, and clocking networks. (Newer chips implement various “gating” to parts of the design to limit the power consumption, but Z80 does not do such a thing.)
I have already mentioned the Waveform view. This view should be familiar to anyone who has worked with simulation tools like ModelSim; but even for the rest, it should still be straightforward and intuitive.
The critical thing to remember is to “name” the net that you wish to observe if it hasn’t been named yet before you can add it to the waveform view. Double-click on the net and select “Edit net name…”. You can type any name; a simple “n123” (where 123 was its net number) would suffice, especially if that is only a temporary net and you do not wish to keep it. You can always rename a net later if you’ve found a more suitable name, or you can delete the name.
The resources’ “tests” folder contains several Z80 code snippets. Some of them can be run as-is (“zexall.asm”), the others contain code to test specific functionality (“test_busrq.asm”) or are there to serve as a template for your experimentation (“test_blank.asm”). They can be built with a provided Z80 assembler (zmac.exe).
As you run different Z80 code snippets, you can see how selected nets behave in various scenarios. You can deduce what a signal does once you analyze its waveform.
Some of the “advanced” features of the app let you set up many specific conditions for observation. For example, you can set up and see what happens when the NMI is asserted at a particular clock cycle. You can modify the test programs and set up your conditions by writing to the “trick box,” a simulated memory-mapped area. These address locations are considered “special” by the Z80 Explorer’s simulation engine. Writing to them will cause various events: injecting an interrupt at a particular clock, holding the reset pin, releasing it, etc.
By carefully crafting a test, you can set up a quick, repeatable, and convenient simulation cycle and observe what the signals do and how the chip behaves. In addition to net tracing, schematics, and annotations, the software should provide a sufficient framework to understand how Z80 works internally.
To conclude with something slightly different, how about booting a ZX Spectrum within this simulation?
For that to happen, I executed this external JS script file. I have also provided the 48.rom file.
monitor.rom = 16384;
monitor.enabled = 0;
Less than an hour later…
Although many portions of the Zilog Z-80 CPU have been reverse-engineered (see excellent Ken Shirriff’s articles on Z80), many parts are still unknown or poorly understood. I hope this tool will allow the curious to finally completely understand the Z80’s inner operation.
Happy and fruitful Z80-exploring!
You can find Z80 Explorer at Github: https://github.com/gdevic/Z80Explorer
There is also a YouTube video that shows it in action: https://youtu.be/_dyngzTEnvw