This post has been a long time in the making. I’ve been distracted by various things: work, family, and CNC related projects. I will create a short post describing my first successfully completed CNC project after this one, since I’m pretty proud of it.
But onto the topic at hand. The road to a video display began with a stand-alone breadboard rig consisting of:
- EPF10K10 (PDF) in PLCC84 in a breadboard adapter
- EPC2 (PDF) configuration flash
- JTAG IDC10 header
- 25.175 MHz oscillator for the pixel clock
- DB15 socket on an adapter
The setup is very similar to when I had one of these FPGAs attached to some breadboard previously, which was itself used to prototype the FPGAs used in the MAXI09 board. The setup looked like this:
The oscillator is at the top of the picture. The unusual frequency, 25.175 MHz, is the pixel clock for a 60 Hz refresh, 640 by 480 display. This is a nice video mode to work from as it allows 80 column text with 8 pixels across a character square. I started off using a little adapter board that came with a Xilinx FPGA dev board. I bought this dev board five years ago but have not yet got around to even unwrapping it. However I remembered it came with a DB15 SVGA adapter board incorporating a 3 bit resistor DAC so I’ve made use of it here. I have also made up my own, breadboard mountable, DB15 adapter board, which will be useful for prototyping up my own resistor DAC. In the mean time I’m only using 1 bit colour for each of the red, green and blue components so I don’t need a DAC.
A brief summary of how VGA works is probably in order.
There are a fair few diagrams around, but after trying to understand several I found this one the easiest to follow:
In a 640 x 480 display the total line length is actually 800 pixels, but only the first 640 pixels contain data. The trailing 160 pixels consists of the front “porch”, the separate horizontal sync signal, and the back “porch”. During the sync time the beam on a CRT sweeps back and onward to the next raster line. The porches, I believe, can hold pixel data for the overscan portion of the display but aren’t usually used. The vertical plane is similar. Pulses for each horizontal and vertical plane are used to synchronise the receiver (ie. the circuitry in the monitor) to the generated signal. In an LCD of course there’s no electron beam, and the analogue VGA signal is converted to a digital signal for the panel inside the monitor.
The length of the various synchronisation pulses and the four porches is set by the video mode and there are dozens, if you include different refresh rates. It is easier to consider these intervals in terms of pixels instead of an amount of time. I found a fantastic list of the various modes on a site about bit banging VGA with a microcontroller. For 640 x 480 with a 60 Hz refresh rate the various pixel counts are:
- Pixel clock: 25.175 MHz
- Visible area: 640
- Front porch: 16
- Sync: 96
- Back porch: 48
- Total: 800
- Visible: 480
- Front porch: 10
- Sync: 2
- Back porch: 33
- Total: 525
So after wiring up the breadboard and verifying the FPGA and EPC2 config flash could be programmed, the first job was to verify that the oscillator was working correctly. This was trivial to do with my Saleae Logic 16 analyser. Next up, generating the horizontal and vertical sync signals. Using the VHDL guide linked above I decided it was time to learn about variables. Previously all my VHDL used signals, but to hold the scanning position a variable inside a sequential process is more appropriate, since the value held is never used outside the process. The code is pretty trivial:
process (CLOCK) variable H_COUNT : INTEGER RANGE 0 TO 800 - 1 := 0; variable V_COUNT : INTEGER RANGE 0 to 525 - 1 := 0; begin if (CLOCK'Event and CLOCK = '0') then if (H_COUNT < 800) then H_COUNT := H_COUNT + 1; else H_COUNT := 0; if (V_COUNT < 525) then V_COUNT := V_COUNT + 1; else V_COUNT := 0; end if; end if; end if; if (H_COUNT >= 640 + 16 and H_COUNT < 640 + 16 + 96) then LINE_CLOCK <= '1'; else LINE_CLOCK <= '0'; end if; if (V_COUNT >= 480 + 10 and V_COUNT < 480 + 10 + 2) then FRAME_CLOCK <= '1'; else FRAME_CLOCK <= '0'; end if; end process;
FRAME_CLOCK and LINE_CLOCK are later routed to the vertical and horizontal sync pins respectively.
I should probably be using named constants for the various intervals. Using the logic analyser I verified the timing matched up with what was expected.
The two sync signals are TTL level and attach directly to the FPGA via the DB15 board. The only other connection is the ground wire. Somehow I was surprised when my Dell TFT recognised the signals and announced that it was displaying a – albeit a very blank – picture at 640 by 480 with a 60Hz refresh.
The next step was to add some screen data. The classic and simplest test pattern is some red, green and blue bars:
This was accomplished by adjusting the colour based on the Y position. 480 breaks nicely into thirds.
After getting this working it was impossible to resist adding in various coloured patterns, eventually even coming up with a simple animation:
Even to me, it is strange to think that there is no computer, of any description, driving the display!
Just for even more fun, I added a bouncing ball, well, square:
Unfortunately I discarded the code for these little demos. Another purpose of writing them is to get a feel for how much resources would be needed for what I eventually want to implement: a decent display adapter for my 68K projects. The bouncing ball demo uses 23% of the logic elements available in the EPF10K10, which isn’t bad at all and a indicator that I should be able to implement a pretty feature-full VGA controller.
The next step was to think about displaying text. While it would have been perfectly possible to jump right into thinking about a bitmap display for my micro, a text based display is more immediately useful for displaying a console.
The general structure of the video controller is to be, when all the pieces are in place, similar to the V9958 used previously. Namely the video memory is not part of the MPU memory space but is instead attached only to the video controller: the memory is read to drive the video output and written to when the MPU instructs the controller.
This arrangement means there is no need for multiple bus masters on the main system memory, but has the complexity cost when it come to dealing with video memory updates: the controller needs to manipulate pointer registers that hold the address to write to, and synchronise those writes with the continual reads that are necessary to drive the display.
But before turning to adding memory to the FPGA circuit it was possible, using the RAM bits built into the EPF10K to construct a text based output test.
The EPF10K10 in PLCC84 had 3 blocks of 256 bytes. Interestingly this is exactly the number of bytes needed to hold the bit patterns for the 96 printable characters in the ASCII character set, assuming each character is 8 pixels wide and 8 pixels high. This is very convenient as it means, in the finished design, the screen of characters can be drawn without accessing the video memory twice: the actual character designs can be held in the internal RAM bits, with the video memory only holding the index of the character to show at each addressable character square.
The first step was to decide on a font design, or make one. Whilst it would have been fun to design my own 8 by 8 font I instead decided to borrow the Amstrad CPC font. It’s a nice font, probably the best one around back in the day. In the end I downloaded a ROM image and extracted the font data, turning it into the needed MIF format and importing it into the design. Due to the way the memories are utilised this requires creating three separate 256 byte blocks, each one holding the design for 32 characters. A multiplexor wraps these memory blocks and selects the right memory block based on the top two bits in the 7 bit address (which is the character index). For the low 32 control characters the multiplexor sets the result to a specific pattern to render a checkerboard character (control characters should never be displayed so it is useful to show a character design not used elsewhere).
All of this means it was possible to program the FPGA to show a test pattern consisting of different characters without first attaching the video memory:
Generating this display is obviously quite a bit more complex then bouncing a shape around.
- The X and Y pixel coordinates are translated both into character row/columns and pixel offsets within each character square, by utilising the bits above and below bit 3 of the pixel coordinate (the low 3 bits cycle across a single 8 pixel wide character).
- The character to display can thus be obtained by taking the character column and adding 32 (which is done by simply prepending 01 to the lower 5 bits of the character column)
- The memory address to read in the character array (FPGA RAM bits) is calculated as the character to show concatenated with the low 3 bits of the pixel Y coordinate.
- This will yield the 8 bits of character pixel data which needs to be shifted out of the R, G and B video lines. No actual shifting is done; instead the low 3 bits of the X pixel coordinate is used as index into the 8 bits read out of the FPGA RAM.
Hopefully that is sufficient to describe roughly how the above image was generated.
The next task was to attach a memory IC. This holds the character array: which character to show in each character square. Eventually it will hold bitmap data, but for now it holds the character to show in each square. Here’s a picture of the breadboard in this state:
As well as the memory IC (next to the oscillator can) this shot also shows the DB15 adapter PCB I had made up by allpcb..com. This replaced the bodge of wires to the adapter board that came with the Xilinx FPGA dev board I previously mentioned.
The memory IC is a little unusual in that is not the same Alliance part that I’ve previously used. The reason for this is access speed.
The AS6C4008 (PDF) used in both the MAXI09 and MINI000 is a decent, easy to use Static RAM, but it is no good as a video memory because of the slow access speed, given as 55nS in the datasheet.
Assuming a memory access for each and every pixel (which would be the case if the video mode had 256 colours) a memory access would be required every 1 over 25,175,000 seconds, or 39.73 (approximately) nS. Whilst I would not need accesses this fast for a text based display, using such a part would be limiting.
Searching around for faster SRAM parts revealed quite a few options, but considerably less when the search was narrowed to through-hole 5V-compatible parts. Actually I was surprised to find anything suitable – the electronics world generally having moved on to surface mount 3.3V or lower parts more then two decades ago – but eventually I found the IDT71256SA (PDF), a 32K x 8 SRAM in narrow DIP28 with an access time of 12nS, easily fast enough for a byte-per-pixel video mode at 640×480. Having only 32KB in a single IC is a bit of an annoyance though; if I ever want to play with graphical modes I will need to have multiple ICs with address decoding in the FPGA. This will require both board space and FPGA pins.
Unusually for me, I bought the 12nS SRAM ICs from mouser.com, one of the few times I’ve purchased new components. Part of the reason was needing to find a supplier of the 25.175Mhz 5V through-hole oscillator can. These parts are rare, and mouser.com appeared to be about the only place I could find them. Along with the SRAMs and oscillator can, I also bought some 1MB EEPROMs (which will be useful when I build a bigger 68K-based board), and a few other random parts. It’s fun getting new electronic components in the post!
So after attaching the SRAMs to the breadboard and configuring the FPGA pinning, the next step was to try to read from them and display the appropriate character on the screen. SRAMs generally contain random data at power up, so I was hoping for gibberish on the screen and that is what I got:
One hiccup not really observable in this picture unless you look very closely is that the first character at the left appears offset vertically by one pixel. This is an issue I have yet to correct.
Achieving this added quite a bit complexity.
Because there is only one memory IC on the board, the Chip Select can be tied low.Asserting the Output Enable pin, outputting an address to read on, and latching the returned 8 bit value, is all operated through a state machine driven by the 3 bits of pixel data within a single character square. There are 8 possible bit positions and they are used as follows:
- 000: Currently unused.
- 001: If we on the visible portion of the screen, set the READING state to 1 (which will assert Output Enable). The read address will also be placed on the address bus at the same time.
- 010: The character to display is latched from the memory data lines. This data is also routed to the character ROM (FPGA RAM array) address pins, when combined with the 3 bits of vertical position, as described above.
- 011: The character ROM clock is pulled high to trigger a read, at the same time the video memory Output Enable is de-asserted.
- 100: The character ROM clock is pulled low.
- 101-110: Unused.
- 111: The read font data is copied to a buffer to be displayed on the next character square.
Reading this description I’ve just written, I can see now why the first character on each row is shifted down: the first 8 bits of pixel information for each row are not available because the memory has not yet been read. Correcting that problem is going to be a challenge, I expect.
I have made some further progress and have actually managed to attach the MINI000 board and can control the output on the screen. I’ll write more about that in the post after next, which will cover my first proper CNC project…