Skip to main content
Logo image

Section 20.4 VGA interface: Datapath

Figure 20.3.1 in the previous section showed the VGA interface organized as the familiar datapath and controller. It was shown how the controller may be constructed from a pair of counters, one for the horizontal (\(x\)) pixel address, and one for the vertical (\(y\)) pixel address, plus logic to decode these addresses into sync and blanking signals. The function of the sync pulses is to synchronize the raster scanning counters inside the monitor with the incoming video stream. The datapath may be imagined as a lookup table that takes in the pixel coordinates and outputs the corresponding RGB values. Additionally, the blanking signals are used to ensure that pixel values are zero outside the active image area. How the lookup table is constructed differentiates two approaches to designing the datapath.

Block RAM lookup table.

More typically called a video RAM or frame buffer, this is a block of read-write memory that contains the pixel values to be displayed. This is diagrammed in Figure 20.4.1. The memory has independent read and write ports, and in Xilinx terminology, is called a "simple dual-port block RAM" (for a complete description, see [19]). In this setup, the VGA controller is constantly accessing the RAM and streaming pixels to the display. Simultaneously, new data may be written into the RAM to update the display, although one must be careful to avoid, or at least mitigate the consequences of, reading and writing the same pixel in the same clock cycle.
described in detail following the image
Figure 20.4.1. Simple dual-port block RAM used as video memory.
Pixels in the image field are indexed by their \((x,y)\) coordinates, but the RAM is indexed by a single address. The mapping from pixel coordinates to memory address is given by the expression
\begin{equation*} \text{RADDR} = x + y \times \text{\#columns} \end{equation*}
where the number of columns for a VGA image is 640. The inactive pixels in the image field are not stored in the RAM. They are always zero.
A previous calculation showed that a 640 \(\times\) 480 image with full 12-bit RGB pixels requires more memory than is contained in the FPGA on the Basys 3. Two strategies for reducing the required memory are:
  • Use fewer colors. For most of the graphics applications in this course, the full 12-bit RGB supported by the Basys 3 is not needed. A black-and-white display needs only one bit per pixel. Four colors (e.g., red, green, blue, and black or white) can be encoded with only two bits, reducing memory requirements by a factor of six. Sixteen colors require four bits, yielding a threefold reduction in memory.
  • Use a smaller image. Your application may not really need the full 640 \(\times\) 480 resolution (think about classic video games, which looked "blocky" but were still fun to play). Reducing the displayed image to 320 \(\times\) 240 will reduce the required memory by a factor of four. To fill the screen at this lower resolution means that the displayed pixels will be 4\(\times\) larger. These larger pixels are drawn by modifying the mapping from the VGA pixel coordinates to block RAM address as follows:
    \begin{equation*} \text{RADDR} = (x/2) + (y/2) \times (\text{\#columns}/2) \end{equation*}
    where \(\text{\#columns}/2=320\text{,}\) and division by two is integer division (0/2 = 0, 1/2 = 0, 2/2 = 1, 3/2 = 1, etc), e.g., a one-bit right shift. In this way, the four pixels at screen coordinates (0,0), (0,1), (1,0), and (1,1) all have the color stored at memory location 0, and appear as a single larger pixel.

Checkpoint 20.4.2.

Check, using the \(2 \times 2\) pixel formula above, the following mappings from \((x,y) \longrightarrow \text{RADDR}\text{:}\)
  • (638,0), (639,0), (638,1), (639,1) \(\longrightarrow\) 319
  • (0,2), (0,3), (1,2), (1,3) \(\longrightarrow\) 320
  • (638,478), (639,478), (638, 479), (639,479)\(\longrightarrow\) 320\(\times\)240\(-\)1

Checkpoint 20.4.3.

With 640\(\times\)480 resolution, what is largest number of colors that can be displayed within the memory limits of the FPGA on the Basys 3?
Answer.
The simple calculation is (1800 Kbits)/(640\(\times\)480) = 5 bits (rounded down). We can display 32 colors. Considering how memory is actually organized (read the Xilinx documentation, [19]), we’ll be limited to four bits, or 16 colors.

Checkpoint 20.4.4.

With larger pixels, 320\(\times\)240 resolution, what is largest number of colors that can be displayed within the memory limits of the FPGA on the Basys 3?
Answer.
The simple calculation is, again, (1800 Kbits)/(320\(\times\)240) = 23 bits (rounded down). We’ll have no problem storing full 12-bit RGB.
The FPGA block RAMs are not pure combinational lookup tables. They may include an input register to hold the address, and an output register to hold the memory value fetched from that address. Thus, there may be a one or two clock cycle latency between the application of the address and the availability of the data. The design tool for the block RAM will tell you how much latency to expect, and you must be careful to delay the control signals by the same amount, so that they remain properly aligned with the pixel stream.

Logic-defined lookup table.

Sometimes, what is to be displayed is quite simple, just a few basic shapes against a constant background. (Think, for example, of a video game like the classic "Pong".) In this case, it can be inconvenient to use a full block RAM. Instead, simple logic can be used: "If the current \((x,y)\) is within the boundaries of an object, set the pixel to the color of the object."
For example, a good test of the correctness of your VGA controller design is a classic set of color bars
 1 
en.wikipedia.org/wiki/SMPTE_color_bars
, used to calibrate televisions, Figure 20.4.5.
described in detail following the image
Figure 20.4.5. Society of Motion Picture and Television Engineers (SMPTE) television test pattern.
Each bar is a rectangular area defined by minimum and maximum values of \(x\) and \(y\text{.}\) The lookup table is modeled in VHDL by nested if-then-else statements, e.g.,
entity vga_test_pattern is
   port( pixel_x, pixel_y: in  std_logic_vector(9 downto 0);   -- enough bits for 800
         color:            out std_logic_vector(11 downto 0)   -- 12-bit RGB
   );
end vga_test_pattern;

architecture behavioral of vga_test_pattern is
-- Predefine RGB mixtures for the colors
-- SMPTE 75% color bars (12/16 = 75%)
constant WHITE:     std_logic_vector(11 downto 0) := x"ccc";  -- red, green, blue
constant YELLOW:    std_logic_vector(11 downto 0) := x"cc0";  -- red, green, no blue
...

signal urow, ucolumn: unsigned(9 downto 0);

begin

urow <=  unsigned(pixel_y); ucolumn <= unsigned(pixel_x);

pixel_color: process(urow, ucolumn)
begin
   color <= BLACK;		-- default

   -- Large vertical bars
   if (urow >= 0) and (urow < 320) then
      if    (ucolumn >= 0)  and (ucolumn < 92)  then color <= WHITE;
      elsif (ucolumn >= 92) and (ucolumn < 184) then color <= YELLOW;
      ...
      end if;

   -- Middle row of bars
   elsif ...

   -- Bottom row of bars
   elsif ...

   end if;
end process pixel_color;
end behavioral;

Checkpoint 20.4.6.

Download the complete file vga_test_pattern_12.vhd from Canvas. Create a top-level with the test pattern and your VGA controller as components. Create a bitstream file, connect the Basys 3 to a VGA monitor, download your bitstream file to the Basys 3, and check that the test pattern appears correctly on the monitor.
This example used a static lookup table. In the next section we will see how this method can also be used to create simple moving objects, like bouncing balls.