Writing Reusable VHDL Code using Generics and Generate Statements
In this post we look at the use of VHDL generics and generate statements to create reusable VHDL code. This includes a discussion of both the iterative generate and conditional generate statements.
As with most programming languages, we should try to make as much of our code as possible reusable. This allows us to reduce development time for future projects as we can more easily port code from one design to another.
In VHDL, we can make use of generics and generate statements to create code which is more generic. When we use these constructs, we can easily modify the behavior of a component when we instantiate it.
Looks look at both of these constructs in more detail.
VHDL Generic
In VHDL, generics are a local form of constant which can be assigned a value when we instantiate a component. As generics have a limited scope, we can call the same VHDL component multiple times and assign different values to the generic.
We can use generics to configure the behaviour of a component on the fly.
As we saw in the post on VHDL entities and architectures, we use an entity to define the inputs and outputs of any component we write. This gives us an interface which we can use to interconnect a number of components within our FPGA.
In addition to inputs and outputs, we also declare generics in our entity.
The VHDL code snippet below shows the method we use to declare a generic in an entity.
entity <module_name> is
generic (
<generic_name> : <type> := <default_value>
);
port (
-- Port declarations
);
end entity <module_name>;
The <generic_name> field in the VHDL code above is used to give an identifier to our generic. We use this identifier to call the generic value within our code, much like with a normal signal, port or variable.
We can also assign a default value to our generic using the <default_value> field in the example above. This is useful as it allows us to instantiate the component without having to specifically assign a value to the generic.
When we instantiate a component in a VHDL design unit, we use a generic map to assign values to our generics. The code snippet below shows how we use a generic map to assign values to our generics in VHDL.
<instance_name> : entity <library>.<module_name> is
generic map (
<generic_name> => <value>
)
port map (
-- Port connections
);
VHDL Generic Example
In order to better understand how we can declare and use a generic in VHDL, let’s consider a basic example. For this example we will look at a design which features two synchronous counters, one which is 8 bits wide and another which is 12 bits wide.
To implement this circuit, we could write two different counter components which have a different number of bits in the output. However, this is an inefficient way of coding our circuit.
Instead, we will write a single counter circuit and use a generic to change the number of bits.
As it is not important to understanding how we use generics, we will exclude the RTL code in this example. Instead, we will look only at how we declare and instantiate an entity which includes a generic in VHDL.
The code snippet below shows how we would write the entity for the counter circuit.
entity counter_example is
generic (
count_width : integer := 8
);
port (
clock : in std_logic;
reset : in std_logic;
count : out std_logic_vector(count_width-1 downto 0)
);
end entity counter_example;
In this example we see how we can use a generic to adjust the size of a port in VHDL. Rather than using a fixed number to declare the port width, we substitute the generic value into the declaration.
This is one of the most common use cases for generics in VHDL. We can use this approach to dynamically alter the width of a port, signal or variable.
Now we need a component which we can use to instantiate two instances of this counter. This component will have two inputs – clock and reset – as well as the two outputs from the instantiated counters.
In the counter code above, we defined the default counter output as 8 bits. This means that we can instantiate the 8 bit counter without assigning a value to the generic.
However, we must assign the generic a value when we instantiate the 12 bit counter.
We can see from the VHDL code below how we use a generic map to override the count_width value when instantiating the 12 bit counter.
entity top_level is
port map (
clock : in std_logic;
reset : in std_logic;
count_8 :
count_12 : out std_logic_vector(11 downto 0)
);
end entity top_level;
architecture struct of top_level is
begin
-- Instantiation of the 8 bit counter
-- In this instance we can use the default
-- value of the generic
count_8bit : entity work.counter_example
port map(
clock => clock
reset => reset
count => count_8
);
-- Instantiation of the 12 bit counter
-- In this instance we must override the
-- value of the count_width generic
count_12bit : entity work.counter_example
generic map (
count_width => 12
)
port map(
clock => clock
reset => reset
count => count_12
);
end architecture struct;
Generate Statements
We use the generate statement in VHDL to either conditionally or iteratively generate blocks of code in our design. This allows us to selectively include or exclude blocks of code or to create multiple instances of a given code block.
The generate statement was introduced in VHDL-1993 and was further improved upon in the VHDL-2008 standard.
We can only use the generate statement outside of processes, in the same way we would write concurrent code.
In addition to this, we have to use either the if or the for keyword in conjunction with the generate command. We use the if generate statement to conditionally generate code whilst the for generate statement iteratively generates code.
We can write any concurrent statements which we require inside generate blocks, including process blocks, component instantiations and even other generate statements.
For Generate Statement in VHDL
The for generate statement allows us to iteratively create multiple instances of a code block. We use the for generate statement in a similar way to the VHDL for loop which we previously discussed.
In fact, we can broadly consider the for generate statement to be a concurrent equivalent to the for loop. However, there are some important differences.
The code snippet below shows the general syntax for the iterative generate statement in VHDL.
<generate_name>: for <variable> in <range> generate
-- Code to generate goes here
end generate <generate_name>;
As we can see from this snippet, the iterative generate statement syntax is very similar to the for loop syntax. In fact, the code is virtually identical apart from the fact that the loop keyword is replaced with generate.
Unlike with a lot of VHDL statements, we must give a label to all generate statements which we write.
We typcially use the for generate statement to describe hardware which has a regular and repetitive structure.
For example, we may wish to describe a number of RAM modules which are controlled by a single bus. If we use a for generate statement rather than manually instantiating all of the components in the array then we can reduce our code overhead.
For Generate Example
To better demonstrate how the for generate statement works, let’s consider a basic example. For this example, we will use an array of 3 RAM modules which are connected to the same bus.
Each of the RAM modules has a write enable port, a 4-bit address bus and 4-bit data input bus. These ports are all connected to the same bus.
In addition, each of the RAMs has a 4-bit data out bus and an enable signal, which are independent for each memory.
The circuit diagram shows the circuit we are going to describe.
We need to declare a 3-bit std_logic type to use in the iterative generate statement so that we can connect to the RAM enable ports. We can then connect a different bit to each of the ports based on the value of the loop variable.
For the data output bus, we must also create an array which we can connect to the output.
We could do this by creating a 12-bit std_logic_vector type and assigning the read data to different 4-bit slices of the array. However, a more elegant solution is to create our own VHDL array type which consists of 3 4-bit std_logic_vectors.
Again, we can then use the loop variable to assign different elements of this array as required.
The VHDL code snippet below shows how we would write this code using the for generate statement.
-- Code to declare an array type
type slv_array_t is array (0 to 2) of std_logic_vector(3 downto 0);
rd_data_array : slv_array_t;
-- Generate the RAM modules
gen_ram_array: for i in 0 to 2 generate
ram_module : entity work.ram_model
port map (
clock => clock,
enable => enable(i)
wr_en => wr_en,
addr. => addr,
wr_data => wr_data
rd_data => rd_data_array(i)
);
end generate gen_ram_array;
If Generate Statement in VHDL
The if generate statement allows us to conditionally include blocks of VHDL code in our design.
We use the if generate statement in a similar way to the VHDL if statement which we previously discussed. In many ways, we can consider the if generate statement to be a concurrent equivalent to the if statement.
The code snippet below shows the general syntax for the if generate statement.
<generate_name>: if <condition> generate
-- Code to generate goes here
end generate <generate_name>;
The if generate statement was extended in the VHDL-2008 standard so that it can use multiple branches. As a result of this, we can now use the elsif and else keywords within an if generate statement.
Prior to the VHDL-2008 standard, we would have needed to write a separate generate statement for each of the different branches.
The code snippet below shows the general syntax for an if generate statement using VHDL-2008 syntax.
<generate_name>: if <condition> generate
-- Code to generate goes here
elsif <condition> generate
-- Code to generate goes here
else generate
-- Code to generate goes here
end generate <generate_name>;
As we can see from this snippet, the conditional generate statement syntax is very similar to the if statement syntax. In fact, the code is virtually identical apart form the fact that the then keyword is replaced with generate.
As we discussed before, it is mandatory to give generate statements a label.
We use the if generate statement when we have code that we only want to use under certain conditions.
One example of this is when we want to include a function in our design specifically for testing. We can use an if generate statement to make sure that we only include this function with debug builds and not with production builds.
If Generate Example
To better demonstrate how the conditional generate statement works, let’s consider a basic example.
For this example, we will write a test function which outputs the value 4-bit counter. As this is a test function, we only need this to be active when we are using a debug version of our code. When we build a production version of our code, we want the counter outputs to be tied to zero instead.
We will use a boolean constant to determine when we should build a debug version.
The code snippet below shows the implementation of this example.
-- Use a constant to control our build
constant debug_build : boolean := true;
-- Conditional generate statement
gen_test_count: if (debug_build) generate
-- Basic counter output for debug
counter_stimulus:
process (clock, reset) is
begin
if (reset = '1') then
-- Reset the counter to zero
test_count <= (others => '0');
elsif rising_edge(clock) then
-- Increment the counter allowing the counter
-- to roll round to zero naturally
test_count <= test_count+1;
end if;
end process counter_stimulus;
else generate
-- Tie the test signal to zero for production runs
test_count <= (others => '0');
end generate gen_test_count;
This example code is fairly simple to understand. If we set the debug_build constant to true, then we generate the code which implements the counter.
If we are building a production version of our code, we set the debug_build constant to false. In this case, the else branch of our code is executed and the counter is tied to zero.
As we previously discussed, we can only use the else branch in VHDL-2008.
When we use earlier versions of VHDL then we have to use a pair of if generate statements instead. One of these statements covers the case when debug_build is true whilst the other covers the case when it is false.
The code snippet below shows how we would do this.
-- Use a constant to control our build
constant debug_build : boolean := true;
-- Conditional generate statement for true case
gen_test_count: if (debug_build) generate
-- Basic counter output for debug
counter_stimulus:
process (clock, reset) is
begin
if (reset = '1') then
-- Reset the counter to zero
test_count <= (others => '0');
elsif rising_edge(clock) then
-- Increment the counter allowing the counter
-- to roll round to zero naturally
test_count <= test_count+1;
end if;
end process counter_stimulus;
end generate gen_test_count;
-- Conditional generate statement for true case
gen_test_count_gnd: if (debug_build = false) generate
-- Tie the test signal to zero for production runs
test_count <= (others => '0');
end generate gen_test_count_gnd;