If you search around the interwebz for homebrew projects you’ll discover a treasure trove of clever solutions. But many of these involve implementing some form of existing technology. For example, you can employ a 6522 VIA chip to produce an SPI interface so you can hook up to a Compact Flash or SD card device. You’ll need to write some code to handle the FAT file system, but there are lots of examples of people doing just that.
I didn’t want to do that. Part of the fun of building your own computer is coming up with your own protocols. And yes, many parts of the Zolatron project have taken inspiration from elsewhere. But, where possible, I want to invent my own stuff. My solutions might be slow, inefficient and unreliable, but they’re mine, dammit.
As to why I want some kind of ‘disk drive’ (I’m using that term loosely), it’s quite simple. Currently, all the software for the Zolatron is in ROM (or EEPROM, to be precise). Every time I write new code, I have to power down the machine, pull out the EEPROM chip, put it in the programmer, write the code to it and then do the previous steps in reverse. It’s not difficult, but it is time consuming. And, as we’ll see, it has the downside that I need to be in the same room as the Z64.
I already have a Raspberry Pi Zero 2 W – dubbed the Imp – connected via serial to the Zolatron. It acts as my terminal and I access it over SSH from my laptop or desktop machines. And I was looking at all those lovely, unused GPIO pins on the Pi thinking about how they remind me of an IDE connector. Hmm…
And so it’s time to crack out another 65C22 Versatile Interface Adapter (VIA). I’m using one of its eight-bit ports as a byte-wide parallel data bus. And I’m using four of the pins on the other port as signal wires for my bit-banged interface – to which I have given the ambitious name of ZolaDOS.
The solution is in two parts. There’s client code on the Zolatron, in the ROM. And there’s a server program (called zolados) running on the Raspberry Pi. I wrote the server code in Go. I won’t go too deeply into that here. I will, at some point, upload it to my GitHub.
Let’s talk about those control signals. There are two managed by the Zolatron (ie, they are outputs on the Zolatron and inputs on the Pi):
- Client Active (CA)
- Client Ready (CR)
And there are two controlled by the Raspberry Pi server:
- Server Active (SA)
- Server Ready (SR)
All of these signals are active low.
All operations are inititated by the Zolatron, so the server’s zolados program starts off just listening and waiting for a sign that something has to be done. At this point, the Z64 is treating the data port as an OUTPUT and the Pi has it set to being an INPUT. The Z64 also has a 16-bit pointer set in memory to point to where the first byte of data should go.
So far, I’ve only got as far as being able to load a program into memory. This is how it works:
- When you type LOAD into the command line of the Zolatron, this initiates the loading sequence.
- The Z64 takes /CA low to alert the Pi that something is happening. The Pi then starts watching the /CR line.
- The Z64 puts a numeric ‘opcode’ value on the data bus. This is a code that tells the Pi what kind of operation the Z64 wants to perform. At the moment, we only have LOAD, for which the code is 8.
- The Z64 takes the /CR line low to let the Pi know that there’s data ready and starts watching the /SR line.
- The Pi sees the /CR signal, reads the code from the data bus and strobes the /SR line low to indicate that it has read it. It starts watching the /CR line.
- On seeing the /SR signal, the Z64 takes the /CR line high again, sets the data port to INPUT and starts watching the /SA line.
- Following the /SR signal change, the Pi sets the data port to OUTPUT and takes the /SA line low. What follows next is the actual transfer of data and most of the following steps are repeated for each byte.
- The Z64 watches the /SR line.
- The Pi puts a byte of data on the data bus and then strobes the /SR line low. If it hasn’t finished sending data, it starts watching for the /CR line to go low, then high. If there’s no more data to send, the Pi takes the /SA line high.
- Seeing the strobe, the Z64 reads the byte of data, copies it to a memory location, increments the pointer, checks to see that the /SR signal has gone high again (and waits if it hasn’t). The Z64 then checks the /SA line. If this has gone high, it means the server has finished sending and we’re all done. If not, the Z64 strobes the /CR line low to indicate it has read the byte.
- If there’s more data to read, loop back to step 8.
- Otherwise, the Pi resets the data port to INPUT and the Z64 sets it to OUTPUT and we’re finished.
You can see that’s there’s a lot of waiting involved, each instance of which is potentially blocking. And so I made life difficult for myself by building in timeouts for each stage. Getting the length of the timeouts right was critical and caused much frustration at first, but was ultimately worth it. On the Z64 side, I used the VIA’s Timer 1 for these timeouts.
Does it work?
It actually works surprisingly well. But you may have noticed no mention anywhere of filenames. That’s because there aren’t any.
The Pi is programmed to send the contents of a predefined file – named zd.bin. That’s it.
That might seem a little limiting, but at this stage it’s enough. My desire is to be able to upload user programs into the RAM of the Z64, and this achieves that. What follows is my workflow for writing code now. This assumes that the zolados server program is running in the background on the Imp, and that I have a SSH session open to the Imp where I’m running a terminal program communicating with the Z64:
- Write the software in VS Code on any machine I like (usually my iMac or MacBook Air) in any room of the house (including lounging on the sofa).
- I run a shell script that assembles the code and automatically uploads it (using SCP) to the Imp.
- I type LOAD into the terminal.
- I type RUN into the termal.
What could be simpler?
To upload a different program to the Z64, I simply switch directories in VS Code and use the same shell script to upload that program.
I am planning to extend the LOAD command to allow for named programs, although that’s going to involve a lot more code on the Z64, including error checking for things like ‘file not found’. But for now, I’m very happy that I can get user code into RAM and can now write and run software without constantly reprogramming the EEPROM.
How fast is it?
You may be thinking that this bit-banged interface sounds slow. And you’d be right. But I can live with it. A session with the logic analyser suggests that I’m getting more than 400 bytes/sec transfer speed – closer to 480B/s for the actual data transfer bit, ignoring the preamble overhead at the beginning. An 8KB program would therefore take about 18 seconds to load.
So the manufacturers of NVMe drives are not going to be losing any sleep. But it’s still better than cassette tape (I think). And so far it has proved to be very reliable.