[Electronics]  [Files]   [Links]

Electronics » the Z80 project » gfx system

My "V1 Z80 Project" - A bit of a mess really.
Ever get the urge to make a complete Z80-based computer system from scratch? I did once. I even made my own membrane keyboard and display (featuring the ultra high resolution realism of an 8x8 grid of red LEDs.. Pong has never looked so err.. primative)

Due to the ad hoc nature of the project it ended up as a sprawling mass of cables and PCBs but it was interesting to tinker with. One thing that I would've liked to do was make my own graphics "card" so I could connect it to the TV and have a proper display. Unfortunately, I didn't really have enough technical info at the time, so for that and other reasons the Z80 project gathered dust...

Fast forward about a decade (!) and I suddenly find myself with (too much) time on my hands, looking for a project to fiddle about with.. Of course with the power of the Internet now at a geek's fingertips, obtaining technical information is not a problem and sure enough, after a bit of browsing I had the necessary PAL TV timing and voltage specifications.

Especially useful was this guy's website. He's produced TV output (and games) using a single PIC 16C84 microcontroller! I've used PICs myself a lot in the past so I wrote some quick test code to produce a stable TV signal using Rickard's info as a template. At first I only sent signals to the composite video line, this allows a grey scale image to be produced with a slowish microcontroller. Colour via composite needs a faster microcontroller and I didnt have any at the time so I tried using 3 ports from the PIC to control the TV's SCART RGB lines instead - see below, right. (I've put together a simple demo circuit based on a PIC 16F628. The code is quite easy to follow as it doesn't do much except create the timing frame and show some colour bars. Download code/schematic here )

RGB colour TV output from a PIC16C84

Unfortunately even in RGB mode, the PIC 16C84/16F84 (and more recent 16F62x) range of microcontrollers are too slow to produce video at a decent horizontal resolution on their own. With a 5Mhz instruction clock (20Mhz source clock) the pixel size would be almost four times that of a Sinclair ZX Spectrum (each pixel needs 2 instructions, a read and write instruction - limiting the pixel clock to 2.5Mhz). Also the PICs I was using didnt have enough ports to be able to fetch data from external memory without a lot external latching which would slow things down even more.

So at this point I decided to use the microcontroller just to provide the TV timing "framework" and design the serious bit of the graphics board using discrete logic ICs. I reckoned a 256x192 pixel display would serve my purposes so I modified the PIC RGB test code to produce an appropriate display window. This is basically what it does to create my display window within a non-interlaced 312-line PAL TV frame:

  • Do 56 blank lines (y border)
  • Do 192 display window lines (x-border, display, x-border)
  • Do 56 blank lines (y border)
  • Do special vertical sync lines (reset TV's raster beam)
  • Loop

Some detail (corrected 26/10/05 - oops!)

"Blank line" - 4uS sync low pulse, 60 uS delay..

"Display Window Line" - 4uS sync pulse, 18 us delay, 32 uS display window (256 pixels), 10 us delay..

"Vsync Lines" - For a non-interlaced display: 6 equalizing syncs, 5 long syncs, 5 equalizing syncs (see this page for details).

With the PIC microcontroller handling the TV timing, I planned out the other features of my graphics system. It was always going to be a bit-mapped display and I didn't want any horrendous Spectrum-like colour attributes. The simplest design idea would have been to fetch a byte for each pixel from an SRAM chip and use bits from that byte to drive the TV's RGB lines on or off. As well as being wasteful of memory, such a system would also limit to the display to 8 colours (the primary and secondary colours). Instead, pairs of bits could be fed to 3 digital to analogue converters: More colour resolution, less wasteful and 64 colours on screen. However, with one byte per pixel, even my 256x192 display would take 48K, slightly too much of the poor Z80's address space! Bank switching could be implemented but that would make access to the screen a pain. Anyway, the main reason this system wasn't used was the sheer amount of data the Z80 processor would have to shift in order to move anything around the screen - it just isn't up to that kind of workload.

Reading a single bitplane from an EPROM

In the end I went for Amiga-like bitplane system. In my design, several 1-bit pages (pretty much a Spectrum's display each, less attributes) are stacked "on top of" each other, and each pixel is created by combining the bits (one from each page) into a "pixel word" who's bitlength depends on the number of bitplanes used. Each bitplane would only take (32 bytes x 192) = 6KB and only one bitplane really needs to be mapped into Z80 address space at any one time. The CPU workload is also reduced because the CPU doesn't have to write to all the bitplanes if less colours are involved in an operation. The downside to this system is that it does complicate the design. Each pixel has to be constructed from bytes at separate locations in memory, shifted and combined. More on this later.

A 3-bitplane system with each bit directly controlling the R,G,B lines would make a simple circuit on the TV output side: As each line is either on or off no digital to analogue converter is required, just a single resistor on each line to drop the logic-level voltages down to 0.7 and 0 volts respectively. However, I wanted a colour palette with up to 16 colours on screen - so I specified 4 bitplanes and sent the 4-bit pixel word from the bitplane combination logic to the address lines of a small (and reasonably fast) SRAM chip which holds the palette data.

The palette SRAM produces a whole byte for each pixel but as 8 bits doesn't divide nicely between the three Red, Green & Blue channels I originally just ignored the top 2 bits and allocated 2 bits per channel giving 4 brightness levels for each colour component. To get the binary values to produce analogue voltages between the RGB-spec 0v and 0.7v I used 2 resistors per channel, a 750 ohm resitor on the MSB and a 1500 ohm resistor on the LSB. The other ends of the resistor pairs are connected together and sent to the TV's RGB lines via the SCART socket. As the TV effectively has 75 Ohm resistors to ground on each line, a potential divider is created that sets the correct voltage levels.

My newer "2 x 3 + 2" bit DAC.

This system gave a palette of 64 colours. I later improved this slightly using bits 6 and 7 from the palette SRAM output to give greater colour resolution on the Red and Green lines, simply by connecting them via 3000 Ohm resistors to the scart lines. There obviously wasn't a 9th bit for the Blue line so that had to stay at 2-bit resolution. The output circuit is shown on the right (the outputs are shown buffered by a 74HC574 latch IC). The composite sync line is also shown: 5 volts from the logic is buffered by the 10K resistor into a general purpose NPN transistor (emerging at about 4.3v because of the base-emitter voltage drop) and meets a potential divider formed by the 1K Ohm resistor at the emitter and the 75 Ohm resistor in the TV. 4.3v / (1000+75) * 75 = 0.3V - which is the level the TV requires for a "sync Inactive" signal (drops to zero volts for Active). The picture quality is perfectly fine even with simple R2R DAC affair - though I did have to keep the video ground trace seperate from the rest of the logic and route it the main supply "ahead" of the other PCBs to avoid on screen noise patterns.

Going back to the overall graphic system design, it occurred to me that it'd be quite trivial to implement hardware scrolling (a 0 to 7 pixel offset of the display window which saves the CPU having to shift bits manually). For vertical scrolling its just a case of skipping 0 to 7 lines at the top of the frame and for horizontal scrolling it just means delaying the output of each scan line by "x" pixel clock periods.

Now hardware scrolling isn't that much use unless you have a spare display buffer to build the new image (whilst the hardware scroll does the fine pixel scrolling). A second display buffer was therefore specified in my design - this would also mean that the Z80 could access one bank of video RAM at full speed whilst the video controller was reading pixels from the other.

One of the VRAM boards.

Another useful feature would be video-sync'd interrupts. With Commodore 64 style raster IRQs I could change the colour palette mid screen, do split screen scrolling and so on. I figured such a system would just require a scanline counter set at the beginning of each frame (or anytime afterwards) which would trigger the interrupt when it reached zero.

Well anyway, at this point my graphics system ended up consisting of five seperate 100x75mm PCBs stacked on top of each other. I used right-angled pin headers and 50-pin IDC (SCSI) cables and leads to connect them all together in a reasonably tidy way. Here's a brief outline of their functions from a more technical point of view:

PCB 1: Main video controller (dodgy schematic here) This board coordinates the TV display timing (generates syncs etc) and merges the 4 bitplanes fetched from the VRAM board(s) into a 4 bit nybble for each pixel to send to the palette PCB (four latching parallel to serial shift register ICs (74HC597) handle that). The video controller was orignally based around a PIC16F628 with external counter ICs for hardware scroll etc but now uses an SX28 at 32MHz to perform the same functions in software. The entire system's master clock is a 64MHz quartz oscillator module whose output is fed directly to the sprite PCB (which has an SX28 running at 64MHz), via a 74HC4024 counter/divider IC to the video controller SX28 (32MHz) and off-board to the Z80 CPU (8Mhz). Note that the SX28 (still) doesn't actually read in or process the video data itself - it just coordinates everything. Some signals it produces are only required locally, for example the shift clock to the four parallel-to-serial shift regsiter ICs. Other signals are used throughout the system (EG:X-mask, Y-mask (TV border periods) Sync for TV, Vertical Retrace etc). My SX28 source code for the video controller is here.

A video address bus is formed by the output of two 74HC4040 counter ICs plus several bits direct from the SX28. Three such bits are the 3 LSBs of the scanline number -I made those directly controllable for the vertical hardware scroll - they can be set to a 0-7 line offset at the start of the frame and incremented each line down. The MSB of these three is fed to the clock input of the upper 74HC4040 IC so when the count wraps, it increments the "coarse" video address.

The lowest 5 bits of other 74HC4040 IC hold the X-direction byte count (0-31) as the raster sweeps the screen horizontally, it is clocked by output 7 of a 74HC4017 decade counter. The sequential outputs of this IC are used to latch data from four bit-planes into the 74HC597 parallel-to-serial shift registers (each bitplane is addressed by the SX as it controls Video Address lines A13-A14 directly). The only other component is a 74HC157 data selector IC which channels the outputs of the X or Y hardware scroll latches to the SX28 (I ran out of SX input ports, so multiplexed these two 3-bit values).

PCB 2: Palette and output PCB. A small SRAM chip holds the palette table. Two 74HC157 IC switch its address bus between the Z80 (write only) and the video controller (read only). To keep things simple the palette can only be updated when the raster is off screen (either horizontal or vertical masking periods). The first 4 bits of the palette SRAM's address bus come from the video controller, the 2nd 4 bits from the output of the sprite controller, the other address lines are held low. The SRAM's data bus is directed to a either a 74HC574 latch (who's outputs are connected to resistors to form 3 DACs for the red, green and blue lines) or the Z80's data bus through a 74HC245 buffer IC for palette updates.

PCB 3: Video RAM board 1 (Buffer 0).. Has a 32KB SRAM IC to hold the display data in linear bitmap format. Four 4-bit data selector ICs (74HC157) switch the SRAM's address bus between the CPU and the video controller. Two 74HC245 ICs direct the SRAM's data bus to the Z80 (inputs) or video controller (outputs) at appropriate times. A circuit based on five standard 74HC logic ICs coordinate the switching between busses and provide the Z80's WAIT input (the Z80 is locked out of video memory during each active raster line - hence the desirability of two buffers).

PCB 4: Video RAM board 2 (Buffer 1). Pretty much as above, but selected when buffer 0 is not.

Mmmmmmm.. wobbly.

PCB 5: Main interface to Z80 bus. Decodes the Z80's address lines to make chip select signals for video RAM, palette RAM etc. Also has two 8bit data latches (74HC574) to hold the hardware scroll and bitplane / buffer select values. Two 4 bit (74HC193) counter ICs latch and count down the raster line sync'd interrupt position. A 74HC245 buffer allows the Z80 to read lines such as vertical sync, video_busy etc. A 74HC74 flip/flop holds the video IRQ flag.

Phew! Hope some of that was interesting / useful to someone... There's a video clip of my graphics system in action on this page BTW.

After finishing the graphics system my attention turned to other aspects of the Z80 Project (Keyboard interface, Operating System, IDE interface, Sound board etc). I eventually returned to it for one more addition - a hardware sprite PCB