Generics in VHDL

Generics are important enough to warrant their own example. They are used by the digital designer for two main purposes:

Purpose #1: Create code that is flexible and easily reused. This might add a little bit of extra work up front, but it will decrease development time later on significantly. Making a change to a generic will propagate the change everywhere that the generic gets used. Also, other hardware designers can leverage your code for their own purposes and only need to change generics for their own operation.

Purpose #2: To set conditions of operation. This is particularly helpful for debug purposes. Adding code that executes conditionally based on generics is good to turn on/off pieces of logic.

Important Note: Generics Are Static! They must be defined at compile time and they are not allowed to change during operation.

For example, the purpose of the code below is to keep track of row/col counters. Every clock pulse increments the column counter. When the column counter is at the end, it resets to zero and increments the row counter by one. When the row and col counters are at their limit, they reset to zero. This a raster image. (Left to right, top to bottom).

In the VHDL below there are three generics. The first g_DEBUG prints out debug statements in the simulator when g_DEBUG is set to 1. This satisfies purpose #2 above. The other two generics set the number of rows and the number of columns in the image display that the FPGA is interfacing to. This satisfies purpose #1 above. If another designer wants to reuse this code to interface to a different sized (row/col) image source, he or she simply needs to change the generics and the code will still work.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity example_generic is
  generic (
    g_DEBUG      : natural := 1;        -- 0 = no debug, 1 = print debug
    g_IMAGE_ROWS : natural := 16;
    g_IMAGE_COLS : natural := 10
    );
end example_generic;

architecture behave of example_generic is

  constant c_CLK_PERIOD : time := 10 ns;  -- 100 MHz

  signal r_CLK_TB : std_logic := '0';
  
  signal r_ROW : natural range 0 to g_IMAGE_ROWS := 0;
  signal r_COL : natural range 0 to g_IMAGE_COLS := 0;
  
begin

  -- Generates a clock that is used by this example, NOT synthesizable
  p_CLK : process
  begin
    r_CLK_TB <= not(r_CLK_TB);
    wait for c_CLK_PERIOD/2;
  end process p_CLK;
  

  -- Keeps track of row/col counters, limits set by generics
  -- This process is synthesizable
  p_IMAGE : process (r_CLK_TB)
  begin
    if rising_edge(r_CLK_TB) then
      if (r_ROW = g_IMAGE_ROWS-1 and r_COL = g_IMAGE_COLS-1) then
        r_ROW <= 0;
        r_COL <= 0;
      elsif r_COL = g_IMAGE_COLS-1 then
        r_ROW <= r_ROW + 1;
        r_COL <= 0;
      else
        r_COL <= r_COL + 1;
      end if;
    end if;
  end process;


  -- Prints debug statements if g_DEBUG is set to 1. (not synthesizable)
  p_DEBUG : process (r_CLK_TB)
  begin
    if rising_edge(r_CLK_TB) then
      if g_DEBUG = 1 then
        report ("ROW = "  & natural'image(r_ROW) &
                " COL = " & natural'image(r_COL)) severity note;
      end if;
    end if;
  end process;
  
end behave;


Console Output:
# ** Note: ROW = 0 COL = 0
#    Time: 0 ns  Iteration: 1  Instance: /example_generic
# ** Note: ROW = 0 COL = 1
#    Time: 10 ns  Iteration: 1  Instance: /example_generic
# ** Note: ROW = 0 COL = 2
#    Time: 20 ns  Iteration: 1  Instance: /example_generic
# ** Note: ROW = 0 COL = 3
#    Time: 30 ns  Iteration: 1  Instance: /example_generic
# ** Note: ROW = 0 COL = 4
#    Time: 40 ns  Iteration: 1  Instance: /example_generic
# ** Note: ROW = 0 COL = 5
ETC...

Modelsim simulation wave output
This shows the rollover of r_ROW from 15 to 0.

Help Me Make Great Content!     Support me on Patreon!     Buy a Go Board!