showcase component

VGA Color-Bar Generator

Open in playground

A VGA signal is two counters wearing a trench coat. hcount_r walks every pixel position in a line, vcount_r walks every line in a frame, and everything else is comparison. While both counters sit in the visible region, rgb shows one of eight vertical color bars picked by the pixel position. Outside it, the output is forced dark and the sync pulses fire.

Each line is longer than its visible part. After the last pixel comes the front porch, then the sync pulse, then the back porch, and only then the next line. The frame repeats the same pattern vertically with lines instead of pixels. The default generics are the real 640x480 at 60 Hz numbers, giving an 800 by 525 total grid from a 25 MHz pixel clock. Both sync pulses are active-low, the standard polarity for this mode.

The shipped waveform runs a scaled-down 32 by 8 frame, covering one whole 672-cycle frame plus the opening pixels of the next. The logic is untouched; only the generics shrink.

In the waveform: follow one line of rgb stepping through all eight bar codes while hsync stays high, then dropping low inside the blanking gap. vsync stretches across two entire lines.

In the netlist: two counter register rows and a cloud of constant comparators, nothing else. There is no memory anywhere; the picture exists only as timing.

The counters that drive every video signal are built from first principles in Counters.

-- VGA color-bar generator: 640x480@60 timing by default, eight vertical
-- bars across the visible region, active-low sync pulses (the standard
-- polarity for this mode). Generics scale the whole frame down for fast
-- simulation without touching the logic.
library ieee;
use ieee.std_logic_1164.all;

entity vga_color_bars is
  generic (
    H_VISIBLE : natural := 640;
    H_FRONT   : natural := 16;
    H_SYNC    : natural := 96;
    H_BACK    : natural := 48;
    V_VISIBLE : natural := 480;
    V_FRONT   : natural := 10;
    V_SYNC    : natural := 2;
    V_BACK    : natural := 33
  );
  port (
    clk   : in  std_logic;
    rst   : in  std_logic;
    hsync : out std_logic;
    vsync : out std_logic;
    rgb   : out std_logic_vector(2 downto 0)
  );
end entity vga_color_bars;

architecture rtl of vga_color_bars is
  constant H_TOTAL : natural := H_VISIBLE + H_FRONT + H_SYNC + H_BACK;
  constant V_TOTAL : natural := V_VISIBLE + V_FRONT + V_SYNC + V_BACK;
  constant BAR_W   : natural := H_VISIBLE / 8;

  signal hcount_r : natural range 0 to H_TOTAL - 1;
  signal vcount_r : natural range 0 to V_TOTAL - 1;

  -- The classic bar order: white, yellow, cyan, green, magenta, red,
  -- blue, black, as {r, g, b} bits.
  function bar_color(constant h : natural) return std_logic_vector is
  begin
    if    h < 1 * BAR_W then return "111";
    elsif h < 2 * BAR_W then return "110";
    elsif h < 3 * BAR_W then return "011";
    elsif h < 4 * BAR_W then return "010";
    elsif h < 5 * BAR_W then return "101";
    elsif h < 6 * BAR_W then return "100";
    elsif h < 7 * BAR_W then return "001";
    else                     return "000";
    end if;
  end function;
begin

  count_p : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        hcount_r <= 0;
        vcount_r <= 0;
      elsif hcount_r = H_TOTAL - 1 then
        hcount_r <= 0;
        if vcount_r = V_TOTAL - 1 then
          vcount_r <= 0;
        else
          vcount_r <= vcount_r + 1;
        end if;
      else
        hcount_r <= hcount_r + 1;
      end if;
    end if;
  end process count_p;

  -- Sync pulses live inside the blanking region, after the front porch.
  hsync <= '0' when hcount_r >= H_VISIBLE + H_FRONT
                and hcount_r < H_VISIBLE + H_FRONT + H_SYNC
           else '1';
  vsync <= '0' when vcount_r >= V_VISIBLE + V_FRONT
                and vcount_r < V_VISIBLE + V_FRONT + V_SYNC
           else '1';

  rgb <= bar_color(hcount_r) when hcount_r < H_VISIBLE
                              and vcount_r < V_VISIBLE
         else "000";

end architecture rtl;
Waveform loading...