Writing Reusable Verilog Code using Generate and Parameters
In this blog post we look at the use of verilog parameters and the generate statement to write verilog code which is reusable. This includes examples of a parameterized module, a generate for block, generate if block and generate case block.
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.
We have two constructs available to us in verilog which can help us to write reusable code – parameters and generate statements.
Both of these constructs allow us to create more generic code which we can easily modify to suit our needs when we instantiate a component.
In the rest of this post, we look at both of these constructs in more detail.
Verilog Parameter
In verilog, parameters are a local form of constant which can be assigned a value when we instantiate a module.
As parameters have a limited scope, we can call the same verilog module multiple times and assign different values to the parameter. This allows us to configure the behaviour of a module on the fly.
As we discussed in the post on verilog modules, we must define an interface to a module when we write one.
We can then use this interface to interconnect a number of different modules within our FPGA design.
As a part of this interface, we can declare parameters as well as the inputs and outputs of the module.
The verilog code snippet below shows the method we use to declare a parameter in a module. When we declare a parameter in a verilog module like this, we call this a parameterized module.
module <module_name> #( parameter <parameter_name> = <default_value> ) ( // Port declarations );
The <parameter_name> field in the verilog code above is used to give an identifier to our parameters.
We use this identifier to call the parameter value within our code, much like with a normal variable.
We can also assign a default value to our parameter 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 parameter.
When we instantiate a module in a verilog design unit, we can assign a value to the parameter using either named association or positional association. This is exactly the same as assigning a signal to an input or output on the module.
However when we write code which uses the verilog 1995 standard, we can only use positional association to assign values to a parameter.
The verilog code snippet below shows the methods we use to assign a value to a parameter when instantiating a module.
// Example of named association <module_name> # ( // If the module uses parameters they are connected here .<parameter_name> (<parameter_value>) ) <instance_name> ( // port connections ); // Example of positional association <module_name> # (<parameter_values>) <instance_name> ( // port connections );
Verilog Parameterized Module Example
In order to better understand how we use parameters in verilog, let’s consider a basic example.
For this example, let’s consider a design which requires two synchronous counters. One of these counters is 8 bits wide whilst the other is 12 bits wide.
To implement this circuit, we could write two different counter components which have different widths. However, this is an inefficient way of coding our circuit.
Instead, we will write a single counter circuit and use a parameter to change the number of bits in the output.
As it is not important to understanding how we use parameterized modules, we will exclude the functional code in this example.
Instead, we will look only at how we declare and instantiate a parameterized module in verilog.
The verilog code snippet below shows how we would write the interface for the parameterized counter module.
module counter #( parameter BITS = 8; ) ( input wire clock, input wire reset, output reg [BITS-1 : 0] count );
In this example we see how we can use a parameter to adjust the size of a signal in verilog.
Rather than using a fixed number to declare the port width, we substitute the parameter value into the port declaration.
This is one of the most common use cases for parameters in verilog.
In the verilog code above, we defined the default value of the BITS parameter as 8.
As a result of this, we only need to assign the parameter a value when we want an output that isn’t 8 bits.
The code snippet below shows how we would instantiate this module when we want a 12 bit output.
In this instance, we must over ride the default value of the parameter when we instantiate the verilog module.
counter # ( .BITS (12) ) count_12 ( .clock (clock), .reset (reset), .count (count_out) );
Although we use named association in the example above, we can also use positional association to assign values to a parameter in verilog.
The code snippet below shows how we would use positional association to assign the value of 12 to the BITS parameter.
counter # (12) count_12 (clock, reset, count_out);
Verilog Generate Statements
We use the generate statement in verilog 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.
We can only use the generate statement in concurrent verilog code blocks. This means we can’t include it within always blocks or initial blocks.
In addition to this, we have to use either an if statement, case statement or a for loop in conjunction with the generate keyword.
We use the if and case generate statements to conditionally generate code whilst the for generate statement iteratively generates code.
We can write any valid verilog code which we require inside generate blocks. This includes always blocks, module instantiations and other generate statements.
The generate block was introduced in the verilog 2001 standard. As a result of this, we can’t use this construct in verilog 1995 based designs.
Let’s look at the three different types of generate block which we can use in our verilog designs.
Generate For Loop in Verilog
We can use a verilog for loop within a generate block to iteratively create multiple instances of a piece of code.
We typcially use the generate for loop approach to describe hardware which has a regular and repetitive structure.
For example, we may wish to describe a number of RAM modules which we want to control using a single bus.
If we use a generate block rather than manually instantiating all of the modules then we can reduce our code overhead.
The code snippet below shows the general syntax for the generate for block in verilog.
// Declare the loop variable genvar <name>; // Code for the generate block generate for (<initial_condition>; <stop_condition>; <increment>) begin // Code to execute end endgenerate
As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog for loop.
However, there are two important differences between this approach and the normal for loops.
First of all, we must declare the loop variable using the genvar type.
The second difference is that we declare the loop within a generate block rather than a normal procedural block such as a verilog always block.
This difference is important as it alters the fundamental behaviour of the code.
When we write a generate for block we are actually telling the verilog compiler to create multiple instances of the code block.
In contrast, when we use the normal for loop we are telling the verilog complier to create a single instance of the code block but execute it multiple times.
As an example, let’s look a very simple use case where we want to assign data to a 2 bit vector.
The verilog code below shows how we would do this using a generate for and a for loop. In both cases, the functionality of the code is the same but the structure produced is very different.
// Example using the for loop always @(posedge clock) begin for (i = 0; i < 2; i = i + 1) begin sig_a[i] = 1'b0; end end // Example using the generate for block generate for (i = 0; i < 2; i = i + 1) begin always @(posedge clock) begin sig_a[i] = 1'b0; end end endgenerate
If we were to unroll the for loop example, we would get the code show below.
always @(posedge clock) begin sig_a[0] = 1'b0; sig_a[1] = 1'b0; end
In constrast, unrolling the generate for code would result in the code shown below.
always @(posedge clock) begin sig_a[0] = 1'b0; end always @(posedge clock) begin sig_a[1] = 1'b0; end
From this, we can see how the generate for is fundamentally different to the for loop.
Verilog Generate For Example
To better demonstrate how the verilog generate for 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 input and 4-bit data input. These signals are all connected to the same bus.
In addition, each of the RAMs has a 4-bit data output bus and an enable signal, which are independent for each RAM block.
The circuit diagram shows the circuit we are going to describe.
We need to declare a 3 bit vector which we can then use to connect to the RAM enable ports. We can then connect a different bit to each of the RAM blocks based on the value of the loop variable.
For the data output bus, we could create a 12 bit vector and connect the read data output to different 4-bit slices of the vector.
However, a more elegant solution is to use an array which consists of 3 4-bit vectors. Again, we can then use the loop variable to assign different elements of this array as required.
The verilog code snippet below shows how we would code this circuit using the for generate statement.
// rd data array wire [3:0] rd_data [2:0]; // vector for the enable signals wire [2:0] enable; // Genvar to use in the for loop genvar i; generate for (i=0; i<=2; i=i+1) begin ram ram_i ( .clock (clock), .enable (enable[i]), .wr_en (wr_en), .addr (addr), .wr_data (wr_data), .rd_data (rd_data[i]) ); end endgenerate
After synthesizing this code, we get the circuit shown below.
If Generate Statement in Verilog
We use the generate if block in verilog to conditionally include blocks of verilog code in our design.
We can use the generate if 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 a generate if statement to make sure that we only include this function with debug builds and not with production builds.
The code snippet below shows the general syntax for the verilog generate if statement.
generate if (<condition1>) begin // Code to execute end else if (<condition2>) begin // Code to execute end else begin // Code to execute end endgenerate
As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog if statement.
However, there is a fundamental difference between these two approaches.
When we write a generate if statement we are actually telling the verilog compiler to create an instance of the code block based on some condition.
This means that only one of the branches is compiled and any other branch is excluded from compilation. As a result of this, only one of the branches can ever be used within our design.
In contrast, when we use the if statement the entire if statement will get compiled and each branch of the statement can be executed.
Each time the if statement code is triggered during simulation, the condition is evaluated to determine which branch to execute.
Verilog Generate If Example
To better demonstrate how the verilog generate if statement works, let’s consider a basic example.
For this example, we will write a test function which outputs the value of a 4-bit counter.
As this is a test function, we only need this to be active when we are using a debug build.
When we build a production version of our code, we tie the counter outputs to ground instead.
We will use a parameter to determine when we should build a debug version.
The code snippet below shows the implementation of this example.
// Use a parameter to control our build parameter debug_build = 0; // Conditionally generate a counter generate if (debug_build) begin // Code for the counter always @(posedge clock, posedge reset) begin if (reset) begin count <= 4'h0; end else begin count <= count + 1; end end end else begin initial begin count <= 4'h0; end end endgenerate
When we set the debug_build variable to 1, the synthesizer produces the circuit shown below. In this case, the synthesis tool has produced a four bit counter circuit.
However, when we set the debug_build parameter to 0 then the synthesis tool produces the circuit shown below. In this instance, the synthesis tool has tied all bits of the count signal to ground.
Case Generate in Verilog
We use the generate case statement in verilog to conditionally include blocks of verilog code in our design.
The generate case statement essentially performs the same function as the generate if statement.
This means we can also use the generate case statement when we have code which we only want to include in our design under certain conditions.
For example, we could design a test function which we only want to include in debug builds.
We can then use the generate case statement to determine which version of the code gets built.
The code snippet below shows the general syntax for the generate case statement in verilog.
generate case (<variable>) <value1> : begin // This branch executes when <variable> = <value1> end <value2> : begin // This branch executes when <variable> = <value2> end default : begin // This branch executes in all other cases end endcase endgenerate
As we can see from this example, the syntax for this approach is virtually identical to the syntax we saw in the post on the verilog case statement.
However, there is a fundamental difference between these two approaches.
When we write a generate case statement we are actually telling the verilog compiler to create an instance of the code block based on a given condition.
This means that only one of the branches is compiled and any other branch is excluded from compilation. As a result of this, only one of the branches can ever be used within our design.
In contrast, when we use the case statement the entire case statement will get compiled and each branch of the statement can be executed
Each time the case statement code is triggered during simulation, the condition is evaluated to determine which branch to execute.
Verilog Generate Case Example
To better demonstrate how the verilog generate case statement works, let’s consider a basic example.
As the case generate statement performs a similar function to the if generate statement, we will look at the same example again.
This means that we will write a test function which outputs the value of a 4-bit counter.
As this is a test function, we only need this to be active when we are using a debug build.
When we build a production version of our code, we tie the the counter outputs to ground instead.
We will use a parameter to determine when we should build a debug version.
The verilog code below shows the implementation of this example using the generate case statement.
// Use a parameter to control our build parameter debug_build = 0; // Conditionally generate a counter generate case (debug_build) 1 : begin // Code for the counter always @(posedge clock, posedge reset) begin if (reset) begin count <= 4'h0; end else begin count <= count + 1; end end end default : begin initial begin count <= 4'h0; end end endcase endgenerate
When we set the debug_build variable to 1, the synthesizer produces the circuit shown below. In this case, the synthesis tool has produced a four bit counter circuit.
However, when we set the debug_build parameter to 0 then the synthesis tool produces the circuit shown below. In this instance, the synthesis tool has tied all bits of the count signal to ground.