Inspired by Ben Eater's "Let's build a video card" aka "World's worst video card" two part projects on YouTube, I was inspired to consider designing a display adapter for my passive backplane TTL mini-computer project.
Basic TTL Setup for H- and V-sync
I might have taken the C64 VIC-II chip, but those aren't available any more. The only available old video chips I can find still are the MC6845 chip or MC6847. However, I don't think that's much fun, because the MC6845 doesn't really do much that I can't do with individual TTL chips, and both are too restrictive in what type of resolution they can support, and complicated by the way the are designed for character modes and have high res graphics only as an afterthought.
It's pretty clear that the whole thing is essentially two sets of 74LS161 binary counters (and 74LS93 for a 2 x 2 bit counter for the highest bits) for horizontal and vertical, pixel/line, blank and sync generators. The counters count starting with a proper crystal, e.g., 25.175 MHz for a 640 x 480 display. Then the counters are monitored by two comparator assemblies which I build from 74LS86 XOR gates and a bunch of OR 74LS32 OR gates to get a low signal only when the counter matches the preset goal. The goal is stored in a register, and because there are 4 such goals for end of display, end of "front porch", end of sync, end of "back porch", I choose the sets of 74LS171 4 x 4 bit register files. The selection of the goal is driven by another 2 bit counter, for which I use the separate two bits of the 74LS93 (a 4 bit counter which looks like I can split it into 2 x 2-bit counters driven by CKA and CKB). Anyway, that counter is triggered by the goal reached and then switches to the next goal. I can leave the low 3 bits from the horizontal counter out of the comparison and the 1 bit from the vertical counter. This gives me a programmable sync pulse generator.
The data load lines for these register files can be mapped on the data bus and register address on the address bus so the goals can be programmed (I remember the X11 server configuration used to have a display configuration sections where you had to put these numbers in.)
Memory Access, Address Generator
Now comes the problem of an address generator. Let's say I just want to display a monochrome graphics, so each pixel is one bit. I want to map them into memory by columns first and then rows, of course. Just like a binary PBM file, that is packed, with no memory lost at the end of each line to complete the next power of two set. So that is where the "address mapping" comes in. I figure I might just as well run a continuous counter the low 3 bits of the horizontal counter are anyway for the bits and not mapped to the memory, then the memory address counter will be reset when the vertical front porch starts. It would have to be a 21 or so bit counter. I don't suppose there is any value of trying to get smart with implementing multiplication to generate a contiguous pixel address from the pixel and line counters.
Pixel Clock and CPU Clock Synchronization and Contention
Now the memory access. Each 8 pixels I would need to fetch a new byte. And my initial idea is to just read from the general memory that the CPU uses. Similar to what the C64 did. You specify a base address of the bitmap and the VIC-II reads it out. I won't do the weird tiling but read each line as one straight row of pixel-bits. Once I fetch the byte for the next 8 pixels on each of the 8 pixels of that byte I use a shift register to produce the monochrome on/off signal for that pixel.
I am designing that computer such that the pixel clock and the main bus clock are independent. In fact, I want to make it such that I can single-step the computer. But of course I couldn't single-step the video card. The way to do that would be to separate the memory bus (data and address) completely. With the video card having constant access, and the rest of the computer placing memory request in an address latch and then at the next free moment (during the time that the video card shifts out the 8 bits) that request will be fulfilled. Since the video card will be running at a much higher speed (25 MHz) while the rest of the TTL computer will run at maybe 1 hardly 4 and 10 MHz as a long shot, the waiting for the next open moment from the video card should not be a major problem. At normal operating speed it could even be timed like that, at 640 x 480 the pixel clock is 25.125 and divided by 8 the CPU clock would run at 3.14 MHz, exactly during the time that the video does not access the RAM. More likely, a 320 x 240 image which would run the CPU at 1.57 MHz. More in the realm of the feasible for a passive back-plane multi-card CPU.
However, there might be a problem if I need to access the video memory though the back plane, I have all these long wire runs at high frequency anyway. How do I resolve that? Would I have to have a separate VRAM on the "video card"? And then would I map this VRAM into the address space so the bus can write to it at CPU/backplane speed? Let's say I solder it all on a single board, similarly to a home computer. So forget about the backplane issue, and let me wonder how the memory contention between video card and CPU is resolved?
Let's start with the hardest problem. If I want to single-step the CPU, I would emulate that by not generating the clock from a key press, but rather, when the operator presses the step key, exactly one pulse from the CPU clock, derived from the pixel clock, will be allowed through at exactly the right time. Also, if I wanted to turn down the speed, instead of changing a potentiometer of an RC driven 555 timer, I would use a counter to run the computer at full speed, half speed, quarter speed, ..., and so on, with a 21 bit counter I could bring the frequency down to 0.37 Hz, which would be fine for a slow enough action to see things, and in fact, instead of the waste of all these counter bits, I would run it at overclocked turbo speed 3.14 MHz, normal speed, at 1.57 Mhz, then one per frame 60 Hz, and divide that down 30, 15, 7.5, ..., 0.23 Hz using an 8 bit counter.
But now forget about this slow and single stepped approach, and let it run at the normal speed of 1.57 MHz, one CPU clock cycle for every 8 pixels, if I wanted to do a raster line interrupt (triggered by another, independent, comparator assembly on the line counter to trigger the IRQ. I guess I would get about the same behavior that I know how to get on a Commodore 64, i.e., I get the raster line interrupt but by the time I actually get to handle it, it is just a little late. So, as I now have re-created what I remember is the behavior of a C64 like computer, I wonder if this is actually how it was done? I.e. run the VIC-II chip on the memory bus all the time and open a window to the CPU every like 8 pixels? If not, what is another way to handle this memory contention?
Memory Speed, size, SRAM, DRAM?
Another thing I wonder is memory speed and if I should even bother with DRAM and then all that refresh headache?
I can get DIP packages for SRAM at reasonable prices even at 128 k x 8 bit per one chip, 256 k x 8 isn't too hard either and 512 k x 8 bit exists. And those are 50 or 70 ns, that is much faster than the C64's DRAM, right? Anyway, 512 k x 8 is where it ends with DIP packages, right? There is no 1 MB x 8 SRAM DIP package, right? But one MB should not be too difficult to put together, and that was all the VAX 11/780 had. Even if I do multi-color graphics with 6 bit RGB (and who know 2 bit of an alpha channel?) Or a 256 color palette, then I can fit 800 x 600 comfortably into a half megabyte. So I could run the rest of the code on the other half, which isn't too bad. These SRAM chips seem to be much easier to handle and the price difference today in this legacy hardware doesn't even warrant worrying about DRAM.
Should I use DRAM? In that case I would need a refresh counter run the whole entire time. But everything seems to be so much harder with DRAM. You then suddenly have to worry about RAS and CAS addressing phases, and I suppose the reason why DRAM refresh was on the VIC-II was probably because it was constantly busy with the memory bus anyway. Right? Even at 320 x 200 and plus the borders, you had, what, 400 pixels line width? NTSC with 525 scan lines at roughly 30 Hz you'd have a 8 MHz pixel clock? Then the 6502 was clocked at only 1 MHz, meaning 8:1 ratio, and hence the VIC-II would be all over the DRAM during 7 out of 8 moments.
Do I have that understanding about right?
Back to the backplane then: if I have to keep the VRAM on the same board in order to not get into problems with high MHz frequencies on the backplane, then I might divide everything differently. In that case I might even use 3 chips of 4 bit RAM, then I would have 4 bits for each color channel without wasting 2 bits. That would be 4096 colors, very good. But what chips are available at 4 bits? I don't find any. So if I wanted to do such a 4 bit per color channel, I need between 1 and 6 MB VRAM. And I suppose it could be DRAM because the video card will go over most of it all the time anyway, so might just as well refresh. That VRAM would be mapped into the address space of the CPU rather than the general memory being accessed by the video signal generator. This memory mapping can be handled by a virtual memory management component of that computer. So now am I recreating the VAX 11/780? (I used to have one in my garage and didn't understand how stuff works on the digital electronics level until now.)