The basic idea is to use bus-snooping to reconstruct the graphics output of any computer that exposes the data-lines. Since the CPU (or other chip) goes and fetches data from RAM in order to display the screen, parsing that data out ought to let me display the screen myself, without having to solder chips into, and cut tracks on ancient and hard-to-replace hardware...
In my case, the target was the 8-bit atari line (XL/XE, since they have the ports on the back). I am planning to do this with an FPGA, and since there's an FPGA, all it takes is a bitstream-swap to support different computer typse - XL, or XE (with the Cartridge + ECI instead of the parallel port) and possibly even the ST (via the cartridge port, though this would only be a VDI-compatible output because of the limited address space, so no memory-mapped graphics).
The block diagram at the project page looks like:
The idea is to (starting at the host computer and moving up to the actual display:
- Have a small PCB that mates with the host port and has a mini-SAS 8087 cable socket on it. That socket can carry 36 signals, just about enough for both ST and 8-bits, and it's thin, flexible and even looks reasonably nice. See below for the XE version.
- Have a main PCB that converts the voltages down to 3.3v, then passes them to an FPGA so the bus protocol (whatever it is) can be parsed into a standard format that can be passed onto the next stage in a packetised format, which is
- An FPGA implementation of the slave-side of the secondary memory interface on a raspberry pi. All R-Pi's have this interface (yes, even the new 5, and going back to the very first Pi). It's criminally under-used in the Pi community IMHO - it's a low-latency, high (50-100MBytes/sec) bandwidth interface with DMA direct to user-space. You lose pretty much all the GPIO pins, but hey, you have an FPGA for that...
- A raspberry Pi. This will boot into a captive application (at least by default, there's nothing that requires this though, it's just that I'm dedicating the Pi to the task). I'm currently using a '4 but am glancing over at the '5 as well This application is a client to the display service (Gemd), and is responsible for interpreting the packets and either sending responses or performing actions (like: expand this line of host-memory video into something the Pi can display).
- The client app talks to "Gemd", which opens up a socket to listen on (currently a local unix socket for speed, but no reason it couldn't be TCP meaning over-the-network applications). Actually, any number of client apps can connect to the socket, send commands over it, and listen for events/replies coming back. The back-end Gemd service is written in QT, (in C++) but the front-end API (which looks remarkably like the AES/VDI ) is written in C. All the front-end API does is marshal the arguments into a serialised form and send them over the pipe to Gemd.
- Gemd will provide a VDI physical workstation of 1920x1080x32-bit, and offer the standard 256 "pens" to GEM for when you want to pretend it's indexed.
The FPGA has direct access to 8MB of PSRAM (rather than DDR, mainly because it's easy to write a PSRAM interface and routing is easier too ) and it can provide access to that RAM to the host via what I'm calling "memory apertures". At least on the XL/E, any RAM location can be supplied by an external module by bringing low signals on the bus. I intend to do that as a matter of course, so all RAM will be serviced by the FPGA. That means I can define a different base-address for (say) 8 sets of pages (low-page to high-page) on-the-fly, allowing access to the entire 8MB within the 64KB of 8-bit address space in a really flexible manner.
Memory apertures would use some defined addresses in peripheral space to specify how memory in a range of pages would be accessed, for example:
Code: Select all
$+0000 : Base address in PSRAM for start of memory aperture $+0004 : Page address in host-memory for start of memory aperture $+0005 : Number of pages to map $+0006 : 'stride', or pages per horizontal line $+0007 : width in bytes of each virtual line
Peripheral expansion slots
I'm intending to provide "slots" on the main PCB using PCIEx1 connectors so you can just plug in boards with "gold-finger" edge connectors. Not sure how many yet, but (say) 4 to 6. These will interact using serial ports (at various baud rates configured by pulling lines low on the edge-connector) or SPI interfaces, talking to an RP2040, which in turn will feed commands/data to the FPGA over a dedicated bus.That bus can be routed to the host or to the Pi's SMI bus by the FPGA, allowing data to be sent to either host or Pi. It'll be bidirectional of course, so you can send data *to* the peripheral slots too.
On that note, and since the slots are defined by an interface, it ought to be possible to write software "peripherals" on the Pi which interact with the host computer as if they were hardware. A simple buffering scheme ought to make this work pretty well.
There's a Pi there, and it's running Linux - networking ought to be a cinch, just a matter of routing bytes around, since all the hard work is already done on the Pi.
"Hard disks" via SD or USB Pen drive
Similarly, plugging in a USB drive on the Pi could make it available to the host computer
One of the really nice things about the RP2040 is that you can just plug it in, hit the DFU button, and it appears as a USB flash drive itself. That makes it trivial to upgrade in the field. I intend to append the FPGA bitstream to the RP2040 config file, and the RP2040 will then reconfigure the FPGA flash, holding the FPGA in reset while it does so. Upgrading the firmware, then, is just "plug the device into a computer while pressing the DFU button, and copy the firmware to the newly-appearing "drive".
I recognised there was a fair number of options and possible configuration, and I wanted to make it easy to build on and use, so I decided I needed a GUI environment to run this in. One of the things I want to implement is a "desktop" like environment for the host computer, so if you move the mouse, the pointer appears and if you move to the top-left of the screen, you can flip to a desktop which allows you to "download" applications to the host, as we'll as do any configuration... To try and make sure that using the Pi was feasible for all this, I decided to write up the software-side first this time, and so QGem was born. If I was going to implement a GUI, then GEM seemed pretty appropriate for an Atari computer system So Gemd is a lot like an X11 server, except that it runs VDI/AES rendering instead of raw Xlib.
It then occurred to me that if I wanted a high-res desktop for the ST/TT, the cartridge port could be a nice interface to bind the FPGA to. You can read (easily) and fake writes with addresses using the second bank of addresses. That's not a world away from a socket interface...
Another thought is that I can implement a sort-of direct-rendering interface by using shared memory between the Gemd server and the client application. It's not too hard to have the client-app expanding (say) an 8 KByte video-screen from the XL/XE into a 320x200 32-bit RGBA memory space, which just happens to be where Gemd reads a QPixmap from every time it refreshes the screen. Since it's client/server, you get one CPU writing data and one CPU reading data, with the synchronisation happening within the shared memory pool.
To give some idea of where I am, the simple XE interface card looks like:
The VDI code is functionally complete (ie: I'm assuming there's a whole boat-load of bugs, but all the calls I need at least have an implementation). I've been testing it along the way, and although the below isn't exactly stunning (it's just test code), it does show user-defined fill-patterns and line-styles, vro/vrt_cpyfm, writing modes, flood fill, text alignment, markers, outline text, and in my case the line styles etc. work as you increase the width of a line, plus you have arbitrary text rotation It seems a lot faster than my TT as well, and the below is actually at 1080p...
The C code that generates the below is (at least currently) here. If you jump to the main() function, it looks remarkably like a bunch of VDI calls ...
Anyway, that's where it is. Currently I have an implementation (at least at the 'C function-call' level) of the VDI, and I've just finished the first pass at getting resource files loaded in the AES code. Next up is to finish the rest of the rsrc library, and then start drawing OBJECTs before starting to tackle some of the higher-level constructs like windows and getting the event library working.
- Implement a host-computer-side API which sends commands to a client-app, which in turn sends commands to QGem. You end up with GEM on your host computer. Not a huge advantage for an ST, but significant for an XL...
- Add a 68K emulator, intercept TRAPs and redirect to the C-API ... see if we can run native 68k programs on the Pi via emulation. No use for games that write directly to memory but good for "serious" apps. Hmm... unless that direct-rendering idea pans out, in which case emulation of games might be feasible too (hey, this is the pie-in-the-sky section)