Earlier this week, I realized that I have a single piece of full-size prototyping board left. In a fit of madness, I decided that the best course of action was to build a small Z80 computer on it.
The Zilog Z80 is a CPU from the 70’s, powering venerable machines such as the TRS-80 and Sinclair ZX Spectrum (i.e. before computers were just machines to be dickheads to each other remotely).
I began by reading over the work of others, to get an idea of how I might build a simple computer. Some of the projects I found and reviewed were:
I particularly enjoyed the final project, which made use of a PSoC 5 development kit – I think the PSoC line is very flexible, and perhaps doesn’t see enough use in hobbyist projects, if only due to it’s obscure ecosystem compared to AVR/PIC.
Unlike modern microcontrollers, the Z80 does not include on-board ROM: instead, upon booting it asserts signals to request a single byte of memory from address 0, and will run whatever code is provided to it – therefore, instead of “programming” a Z80 as you would do with an AVR, you would program EEPROM’s and have the Z80 load them.
Incidentally, this is how game cartridges worked – by exposing the CPU’s address and data lines (and by extension, the system’s address and data buses), a set of EEPROM’s could feed additional data to the system. Alternatively, a microcontroller inside the cartridge could assert control of the bus, and run code entirely independently. It is my great misfortune that my project was done on a limited-space prototyping board, and I could not expose an expansion header that provided power and bus access.
Design-wise, I settled on a hybrid design, using an ATMega32A bootloader, 2 32KB blocks of static RAM, and some logic IC’s. The AVR would boot first, filling the SRAM with code while holding the Z80’s reset low. The AVR would then relinquish control of the address and data buses, then provide a clock signal and reset the Z80, allowing the machine to boot. Some virtual logic analyzer code within the AVR then allows us to inspect the Z80’s buses, and if need be, play the part of a hardware debugger.
I began by constructing a test circuit, based on this. To confirm signs of life, I wired up the ATMega32A to generate a slow 5Hz clock signal to the Z80, with the data bus pulled down to 0 (i.e. a stream of nop’s), and LED’s on the address lines. You can see this working here:
At this point, I decided my next task was to build some code to program the SRAM chips. While a traditional Z80 would use an EEPROM (containing CP/M, BASIC or what have you), it is fine to use an SRAM chip for this instead, as long as you load it with data before the Z80 comes out of reset. Programming a parallel SRAM was much simpler than I thought, and was simply a matter of setting the address and data buses and pulsing chip enable and write enable.
With the EEPROM’s programmed, I moved on to wiring up the Z80. The Z80 CPU comes with a 16-bit address bus, so I used a hex inverter and a 74-series OR gate to build a SRAM bank selector depending on chip enable and the highest bit of the address bus (pulled down, as we never asserted this from the AVR). The rest of the pins were wired up to the AVR, with some quirks:
- The wait pin was wired up as an input with a pull up for the Z80 and an input for the AVR. We control the clock and can stretch it for a delay.
- I couldn’t get the Z80 to relinquish control of the bus control (i.e. tri-state everything) entirely using BUSREQ, so I put current limiting resistors on WR/RD and the corresponding pins on the AVR. This is only used during bootloading – let me know if you’ve got a reliable way to fix this.
- Strong pull down resistors were used on the upper bits of the address bus of the Z80 – these needed to be 0 during the bootloader process, as they’re address inputs on the SRAM chips, but otherwise the Z80 could have control
The completed work looked somewhat OK:
Note the additional resistors on the right of the hex inverter on the top right – I managed to purchase an open-drain version when I went to Jaycar, and the cost of this oversight was 5 resistors.
The back-side can be accurately represented by the bowl of noodles emoji.
Some quick test code later, and the Z80 was able to load and execute a test loop, reading code from memory and correctly performing math and control transfer operations.
The final hurdle was to correctly implement IO instructions. The IO of the Z80, according to the datasheet, was an 8-bit address and data bus (that is: memory could have 64k, but only 255 IO ports existed), and a special IOREQ data pin. OUT instructions were no problem as all the AVR’s bus pins could remain as inputs, but IN instructions required flipping the bus direction and some careful timing:
As you can see by reviewing the slightly diagonal datasheet that’s just images so you can’t search (and which definitely isn’t a troll from Zilog), there is some delay between the assertion of IOREQ / RD and the moment the CPU recognizes the data bus. I found I could reliably set this by forging a single clock cycle (after which IOREQ / RD are no longer asserted low), which I did, and confirmed with some simple test code:
To me, this marks the completion of building a core Z80 system. Everything else is simply implementing peripheral support – with the CPU working and having a line of communication to the outside world, the AVR here can play the role of “hardware gatekeeper”, bridging peripheral IO requests to appropriate peripherals, or in it’s simplest mode, simply relaying serial output.
Thankyou to everyone who attempted similar projects before me – I would not have been able to complete this project so quickly without standing on the shoulders of giants. You can find my code here, along with a collection of datasheets and saved reference material so you don’t need to visit HTTP sites from the age of Geocities. I hope this proves useful to someone else.