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’ve found that the Visual6502 team has done wonderful work with 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.
As I kept playing with the online tool, my wish list of additional features steadily grew. 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 the way I originally imagined it.
In this blog, I will give an overview of Z80 Explorer's capabilities and show a couple of useful features which 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’s guide is a separate online document located here: Z80 Explorer Guide. It is very concise; if something is still unclear, please email me and I will explain it better.
The tool is based on 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 quite fast: On my 4GHz i7-4790K CPU, I am able to run Z80 code at around 2.3kHz which is (only!) around 2000 times slower than the speed it would have run on the real silicon. At those “speeds”, it is not inconceivable to run some of the standard CPU diagnostic programs - so I did just that: I run a well-known ZEXALL diagnostic program.
That program normally takes 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 simply had to resume it, with no need to reset it and with 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 had 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 indeed 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 that is 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's 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 exactly 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 "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 which is a NAND gate of the clock (hence, a clock gating) with the net 1329. Selecting (double-clicking on) 1329 and asking for the schematic brings us even closer to what we expected to see:
Therefore, the 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 important nets).
Back to the latch 244 - and this part may not 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 the 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 of them using IX register, and then I run 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 as well as 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 that consists of those two nets (they also act 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 such a “Driven by” algorithm to build a tree of gates that contribute to the selected net.
Going the other way, the “Driving nets” option assists you to trace 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: In order 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 wanted 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 that are propagated across its die to literally every corner. Some of those networks are power, ground, reset, and clocking network. (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 very simple and intuitive to use.
The important 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 own experimentation ("test_blank.asm"). They still need to be compiled so a Z80 assembler (zmac.exe) is provided.
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 a number of specific conditions for observation. For example, you set up and then see what happens when the NMI is asserted at a certain clock. You can modify the test programs and set up your own conditions by the means of writing to the “trick box”, a simulated memory-mapped area. These locations are considered special by the Z80 Explorer simulation engine and writing to them will cause various events to happen: injecting an interrupt at a certain clock, holding the reset pin, and then 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. That, in addition to net tracing, schematic and annotations should provide a sufficient framework to understand how Z80 internally works.
To conclude with something slightly different: how about booting a ZX Spectrum within this simulation?
For that to happen, I had 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 am hoping that 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