Skip to content

Video out

Everything so far has been small and slow: a few bytes, a few thousand times a second. A picture is the opposite. A monitor wants the colour of every pixel, every time it draws the screen, sixty times a second: 25 million pixels a second, 25 times faster than the 6502 runs. No CPU like it could keep up, and none tried: home computers had a video chip that read the picture from memory by itself, as on Memory and buses, and the CPU only changed the memory. That’s what vga.v is: a little video chip, 40 columns by 30 rows of text in 16 colours, through the Tiny VGA PMOD on the carrier board.

A VGA monitor draws the picture the way the old tube monitors did, and still expects it to arrive that way: along each line from left to right, and line by line from top to bottom. The signal carries the colour of each pixel as the drawing reaches it, on three wires (red, green and blue, each a voltage from 0 to 0.7 V), and two more say when to start again:

  • HSYNC, a pulse between lines: go back to the left;
  • VSYNC, a pulse between pictures: go back to the top.

Around the pulses the picture is black, the blanking: a tube’s beam needed time to swing back, and the timings kept the margins. For 640 × 480 at 60 pictures a second, the standard says:

The 640 × 480 picture, as the time it takes to draw. Each line is 800 pixel times: 640 shown, then 16 of black, 96 of HSYNC pulse, 48 of black. Each picture is 525 lines: 480 shown, then 10, 2 of VSYNC, 33. So only 73% of the time shows anything. At 25.125 MHz (the pixel clock here: the standard’s is 25.175), a line takes 31.84 µs and a picture 16.72 ms, 59.82 a second.

The monitor locks onto the sync pulses’ timing, and close enough is good enough: the PLL’s 25.125 MHz is 0.2% off the standard 25.175, and every monitor shows it.

The UPduino’s clock is 12 MHz, and the picture needs 25.125. The UP5K has a PLL (a phase-locked loop): an oscillator of its own that it keeps locked to the 12 MHz in a fixed ratio. Its settings make 12 × 67 / 32 = 25.125 MHz (the open tools’ icepll works them out, given the frequency wanted). So board.v has two clocks: 12 MHz for the CPU and everything else, 25.125 for the video.

Two clocks bring a problem with them. A signal made on one clock and read on the other can change just as the other clock’s edge arrives, and the flip-flop reading it can be left undecided (metastable). The CPU writes the screen’s memory on its clock; the video reads it on the other. The block RAM solves it: each of the iCE40’s block RAMs has a write port and a read port, each with its own clock. The CPU writes through one, the video reads through the other, and the memory itself is where the two clocks meet. The few signals that cross otherwise (the end of each frame, going the other way) go through two flip-flops, as a button’s does.

A whole picture of pixels would take 640 × 480 × a byte = 300 KB, more than the UP5K has. Text mode is the classic answer: the screen is 40 × 30 characters, and the memory holds only which character is in each place (1,200 bytes), plus its colours (1,200 more). The shapes are in a font: 128 characters of 8 × 8 pixels, a byte a row, 1 KB. Each pixel of the font is drawn 2 × 2 on the screen, so a character covers 16 × 16 and 40 × 30 of them fill 640 × 480.

A character from the font: its code picks 8 bytes, a row each; each bit set is a pixel in the foreground colour, each bit clear the background. On the screen each is 2 × 2. The font is in block RAM (loaded from font8x8.hex when the FPGA starts: Daniel Hepper’s public-domain 8 × 8 font), and the CPU can rewrite any character’s bytes to make its own shapes. Codes $10–$1F are quarter blocks, for simple graphics.

For each pixel, vga.v works out which character it’s in, reads that character’s code and colours, reads the row of the font it needs, and picks the bit: three steps, a clock each, so the sync pulses are delayed three clocks too, to stay in line with the picture.

4 · Colours, scrolling and the end of a frame

Section titled “4 · Colours, scrolling and the end of a frame”

Each character has two colours, a foreground and a background, each one of 16 from a palette: 16 registers the CPU can change, each a colour of the 64 the PMOD can make (2 bits each of red, green and blue). Changing a palette entry changes everything in that colour at once, which old games used for flashing and fading. A character with bit 7 of its code set is drawn the other way round.

SCROLL says which row of memory appears at the top of the screen. To scroll a terminal up a line, nothing has to be moved: add one to SCROLL, and the row that was at the top wraps round to become the new bottom one, to be cleared and written. Moving the text up the old way would mean rewriting 1,160 characters (from a copy in RAM, since the screen can’t be read back): over ten milliseconds of the 6502’s time. This takes one write.

And the CPU needs to know when the picture’s been drawn: changing the screen while it’s being drawn can show half of the old picture and half of the new. So at the start of the vertical blank, the frame’s last 45 lines, the video sets a flag, and can interrupt: 60 times a second, a moment to change things unseen.

AddressRegister
$8100–$810FPALETTEthe 16 colours: bits 4–5 red, 2–3 green, 0–1 blue
$8110STATUSbit 7: a frame has ended since cleared (write: clear)
$8111CTRLbit 0: interrupt when a frame ends
$8112SCROLLthe row of memory shown at the top, 0 to 29
$9000–$94AFcharacters40 a row, write only
$9800–$9CAFcoloursbackground × 16 + foreground, write only
$A000–$A3FFthe font8 bytes a character, write only

The memories are write only: their one read port is the video’s. (A real system would keep a copy in RAM if it needed to read the screen back.)

The computer with video, its picture drawn below. Text in colour clears the screen and writes lines in different colours, and the quarter blocks. A terminal on the screen: type in the serial terminal (below the monitor) and it appears on the screen; at the bottom, it scrolls by changing SCROLL. A bouncing ball moves a step each frame, from the interrupt at the end of each one.

The video takes 555 logic cells and 8 of the UP5K’s 30 block RAMs: 3 for the characters, 3 for the colours and 2 for the font. Together with everything else on the board it fills the chip, so board.v can leave it out (VIDEO = 0) when the room is wanted for something else.

  • A VGA monitor wants each pixel as it’s drawn: 640 × 480 visible inside 800 × 525 pixel times, with HSYNC and VSYNC pulses in the blanking, at 25 MHz.
  • A PLL makes the pixel clock from the board’s 12 MHz; the CPU and the video run on different clocks, and meet in dual-port block RAM.
  • Text mode stores characters and colours, and draws them from a font: 3.4 KB for the whole screen.
  • A palette, hardware scrolling and an interrupt at the vertical blank let a slow CPU make a lively screen.
  • The VESA timing standards for 640 × 480 and the rest, collected in many places, and tinyvga.com’s tables of them.
  • The Tiny VGA PMOD’s design, at github.com/mole99/tiny-vga.