Breadboard Z80 Computer

A few years ago, I discovered that my dad had a small collection of SRAMs, CMOS logic ICs, and a few Z80 microprocessors kicking around in his workshop. At the time, inspired by recently discovering Ben Eater’s YouTube channel, I attempted to make a simple functioning computer from these parts, but I lacked the knowledge to put together anything that worked.
Over the recent summer break, I decided to take another crack at it. Armed with a bit more computer engineering knowledge than I had previously, I put together a very simple design that I thought could work. Using an Arduino Nano to take care of much of the heavy lifting in terms of interfacing and bootloading, I was able to put together something functional using just a few of the chips.
My first order of business was to figure out the memory structure. In possession of two HM6264P-12 8K SRAMs, I wanted to glue them together to open up 16K of available memory for my computer to work with. With a couple of OR gates and NOT gates, I implemented the following design to allow all 16K to be addressed:

Logic circuits for addressing the SRAM

Which allowed me to implement the memory using only two 74-series logic ICs.
Before wiring up the Z80, I wanted to make sure I would be able to address and access all the memory, and also ensure that the memory was still working after at least 30 years of dormancy.
This gave rise to my implementation of the bootloader and system control functionality of the arduino. I wanted the Arduino to be able to do three basic things:

  1. Hold the Z80 in reset to ensure all of its outputs are high-z while the program code is uploaded (and subsequently release it from reset once the bootloading is complete)

  2. Upload the program code to the beginning of memory

  3. Provide some method of debugging the program code as it runs.

1 seemed to be simple enough- connect one of the Arduino’s GPIO outputs directly to the Z80’s reset line (foreshadowing - this would prove to be a mistake). As for 2, this would require some way of interfacing the arduino with the system’s memory. Once I was confident that the Arduino could read and write from memory, uploading the program code would be a simple task. There are many ways to approach 3- the simplest solution I could come up with was to simply connect one of the upper address lines to a digital input, and monitor that along with the write enable signal coming from the Z80. The program code could then write to any memory location above the address corresponding to that line, and the Arduino would be able to detect that write and print the data to the UART. A crude solution that requires the Arduino to operate much faster than the Z80 itself, but it should work for simple bringup. More complex communications (perhaps having the Arduino provide a few memory locations that act as proper peripheral control registers) could be implemented later.

For the Arduno’s memory access functionality, I decided that it could be done with four 8-bit shift registers: Two to write to the 16 address lines, one to output data on the data bus, and one to read data from the data bus. If the shift registers were bidirectional (that is, parallel load, shift out, and shift in) this could be done with three, as well as provide an easier means for the arduino to monitor the address lines during execution. I only had classic unidirectional shift registers on hand, however. Shown below is the configuration I came up with:

Shift register configuration for Arduino's memory interface

A notable point of convenience is that the shift registers I had on hand all had tri-state outputs, so it would be easy to remove them from their respective buses to avoid bus conflicts (this would also be a bit of an oversight on my part)


Having never used chatGPT to generate large amounts of code before, I figured it would be interesting to give it a try. I was surprised at how quickly and accurately it was able to generate the correct Arduino code for most of my requests; there were however a few mistakes that needed to be fixed manually, as well as the loss of context after many queries that led to the code changing certain conventions over time, which led to a couple of semantic errors. Despite these issues, I was without a doubt able to produce a fully working program that met all of my needs far faster than I would have been able to do if I had written the code from scratch. In the future, I beleive the most effective method (given the same tools) would be to generate individual function defenitions (or similarily modular code blocks) and assemble the code myself to ensure that the correct flow and functionality is achieved. This would also lead to easier troubleshooting, as I would be more intimately familiar with the structure of the code from the beginning. Feel free to have a look at the git repo if you are curious to see what I ended up with.

Having successfully generated the necessary Arduino code, it was time to start testing the entire system. In hidsight, I should have put some effort into planning the wiring beforehand, as it ended up being a complete nightmare once all of the address and data lines were hooked up, along with all of the control signals and the shift registers.

Debugging the shift register memory interface. ChatGPT had gotten almost everything right, except for the latch timing of the shift-in register.

Having successfully generated the necessary Arduino code, it was time to start testing the entire system. In hidsight, I should have put some effort into planning the wiring beforehand, as it ended up being a complete nightmare once all of the address and data lines were hooked up, along with all of the control signals and the shift registers.

Breadboard computers are Ben Eater's thing, and he can keep them.

If I continue this project, I’m going to do the right thing and make up a PCB. A breadboard computer is neat, but a nightmare to debug. I thought at one point half the RAM died for no reason, when it turned out that it was just a loose connection to the power rail on that half of the breadboard.

My idea for a simple “hello world” program would be to simply have the Z80 wait in a busyloop for a bunch of cycles, increment a counter and then write the counter value to the aformentioned memory location for the Arduino to display. With some gameboy assembly experience under my belt already, Z80 assembly was pretty easy to get my head around (at least for a super simple program like this one). I then used the GNU Z80 assembler to generate a binary, and the another “ChatGPT special” python script to convert it into a C declaration of a static byte array that the Arduino compiler would recognize. The program code could then be packed up along with the Arduino code, and uploaded into memory at will. In the future, I would most likely add an SD card reader or something so that the Arduino wouldn’t have to be reprogrammed every time the program code changes.

The code seemed to work at first- the Z80 seemed to be writing something to that memory location, but it seemed to be inconsistent. Sometimes it would dump out a bunch of bytes, and sometimes it would dump out one and then stop. I was monitoring the M1 signal, as well as the clock, reset, and various control signals coming from the Z80. The M1 signal proved to be invaluable for debugging.

OP code fetch timing diagram from the Z80's datasheet

We can see here that the M1 signal should be pulled low at the beginning of an op code fetch. This should be a pretty valuable indication that the Z80 is trying to execute code.

This is where the trouble began- at first, I saw the M1 line going low many times, and different bus transactions appeared to be taking place. A bunch of garbage data was being output on the UART, but at least it was something. Then, as time went on, I saw that M1 line going low less and less, to the point where it would only go low once, and then the CPU would stop trying to execute code. I eventually concluded that the Z80 I had must have been faulty- I swapped it out for the other one (a slightly newer model it seemed), which then went through the exact same cycle- it appeared to fetch a lot of code at first, before eventually giving up and only trying to fetch a single instruction. I assume that there was something internal that was not able to properly decode instructions anymore, so it failed to continue execution. For a long time I assumed it may have been the wiring, or that the clock speed was too high (which would be unsurprising with all of the sketchy breadboard wiring at play).
It wasn’t until after some reflection I realized what the true problem most likely was- I had made a gross oversight in the computer’s design.

I had completely laid my trust in the Arduino to keep everything high-z when the Z80 was active- which was mostly true when the Arduino was up and running. What I didn’t account for, is that when the Arduino is being programmed, the programmer asserts its reset signal. During this time, there is a brief moment when the outputs are in an undefined state. This is a big problem- because the Z80’s reset line might be released at the same time that the shift register’s output enable lines are active, causing a bus conflict. Without realizing it, I was repeatedly causing the Z80 and the shift registers, as well as the memory control signals connected to both devices, to fight each other at maximum strength. This is almost certainly why I watched two different Z80s die slowly before my eyes.
The solution to this problem would involve a much more robust mechanism to prevent bus conflicts. I have a few ideas on how to achieve this- it would require a bunch of extra chips, but the safest option would be to connect all of the shared signals to tri-state buffers, and then connect the buffer enables all to some sort of phyiscal dual-throw switch. This would ensure that any of the conflicting signals could not be enabled all at once. I think that it might be possible to have a fail safe mechanism connected to the Arduino’s reset line that would tri-state everything when the arduino gets reset, and maybe even introduce a slight delay from this point (likely using a 555 timer, like the old school Commodores) to ensure everything is stable before any devices are allowed to take control of the bus.

All in all, this was sort of a quick hacky project that I just decided to throw together for fun. Even though I wasn’t successful in the end, I know what I could do to make it all work, if I choose to revisit the project. Luckily, the Z80 is an extremely common part (which has continued to be produced all the way to modern times), so I shouldn’t have much trouble finding replacements to try again with. Maybe I’ll go for an all-SMT board to make it nice and compact(student friendly!) For now however, I’m back in school so i’m not likely to come back to this until January, provided something else doesn’t pique my intrest. □

Previous
Previous

(1/2) Adventures in RISC-V - the Angry Goose Initiative

Next
Next

Adventures in Retro Game Programming