Writing Reusable Verilog Code using Generate and Parameters

Share on facebook
Share on twitter
Share on linkedin

Table of Contents

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.

The verilog code snippet below shows the method we use to assign a value to a parameter when instantiating a module.

<module_name> # (
  // If the module uses parameters they are connected here
  .<parameter_name> (<parameter_value>)
)
<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.

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 clock 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 signal width, we substitute the parameter value into the 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. Although we use named association in the example below, we can also use positional association to assign values to a parameter in verilog.

counter # (
  .BITS (12)
) count_12 (
  .clock  (clock),
  .reset  (reset),
  .count  (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, case or a for statement 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-95 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 are controlled by 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 
  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 bus and 4-bit data input bus. 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.

Circuit diagram showing three RAM modules connected to a single bus.

We need to declare a 3 bit vector which can be used 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.

A circuit diagram showing 3 RAM blocks connected to a bus

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.

A circuit diagram showing 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.

A circuit diagram showing four buffers with their inputs tied 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 statement performs the same function as the if statemment, we will look at the same example again.

This means that 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 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.

A circuit diagram showing 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.

A circuit diagram showing four buffers with their inputs tied to ground

Exercises

What is the benefit of using parameterized modules?

show answer

We can configure the functionality of the module when we instantiate it. This allows us to make our code easier to reuse.

hide answer

What do we use generate blocks for in veirlog?

show answer

We use them to control the way that our designs are compiled and built. They allow us to conditionally include blocks of code in our design at compilation time.

hide answer

What is the main difference between a for loop and a generate for block?

show answer

The generate for block is evaluated at compile time, meaning only one branch of the code block is ever compiled. All the code of a for loop is compiled and it is evaluated continuously during simulations.

hide answer

Write a generate for block which instantiates 2 16 bit synchronous counters. The two counters should use the parameterized module example from earlier in this post.

show answer
// Variable for the generate loop
genvar i;

// Array for the outputs
wire [15:0] count_out [1:0]

// Generate the two counters
generate
  for (i=0; i < 2, i = i+1) begin
    counter # (
      .BITS (16)
    ) count_12 (
      .clock  (clock),
      .reset  (reset),
      .count  (count_out[i])
    );
  end
endgenerate 
hide answer

Write a generate for block which instantiates either an 8 bit counter or a 16 bit counter, based on the value of a parameter. The two counters should use the parameterized module example from earlier in this post. You can use either a generate case or a generate if block to write this code.

show answer
// Parameter to contr, teh generate block
parameter COUNT_16 = 0;

// Using a generate case statement
generate
  case (COUNT_16)
    0 : begin
      counter # (
        .BITS (16)
      ) count_16 (
        .clock  (clock),
        .reset  (reset),
        .count  (count16_out)
      );
    end
    default : begin
      counter # (
        .BITS (8)
      ) count_8 (
        .clock  (clock),
        .reset  (reset),
        .count  (count8_out)
      );
    end
  endcase
endgenerate

// Using a generate if statement
generate
  if (COUNT_16) begin
    counter # (
      .BITS (16)
    ) count_16 (
      .clock  (clock),
      .reset  (reset),
      .count  (count16_out)
    );
  end
  else begin
    counter # (
      .BITS (8)
    ) count_8 (
      .clock  (clock),
      .reset  (reset),
      .count  (count8_out)
    );
  end
endgenerate
hide answer

Enjoyed this post? Why not share it with others

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

ENJOYING THIS ARTICLE?

Why not join our mailing list and be the first to hear about our latest FPGA tutorials

Close