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...