Logic Design - Module Parameters and Generate Block [Verilog]

[Edit of Image1]

Introduction

Hey it's a me again @drifter1!

Today we continue with the Logic Design series on Verilog to cover some additional advanced topics. More specifically, we will talk about Module Parameters and the Generate Block. Using parameters modules become more reusable as they can be instantiated using different parameters each time for different modules in a design, or maybe even completely different designs. Using the generate block, modules or certain statements can be instantiated conditionally, and repeated multiple times if necessary.

So, without further ado, let's dive more into it!


Module Parameters

When defined within a module definition, Parameters are similar to arguments passed to functions during a function call. It's thus common to use them in order to make the bit width of module ports tweakable, control parts of the design etc.

Parameter

Parameters are defined using the parameter keyword followed by the name of the parameter (mostly in capital letters). They are also assigned a default value.

parameter PARAMETER_NAME = default_value;

Parameterized Module

The parameters of a module are declared similar to the port declaration. The parameters can thus enclosed in parentheses and split by comma. It's important to note that the parameter declaration comes before the port declaration so that those parameters can be applied to the ports, and that a # character is appended to the front, as shown below.

module module_name #([parameter list]) ([port map]);

Similar to port declaration, it's of course also possible to define the parameters after the module name, as statements.

module module_name;
    parameter PARAMETER_NAME = default_value;
    //port declarations
endmodule

Instantiation and Overriding Parameters

When instantiating a parameterized module, the default values are used, and no special syntax is required for that. In order to override those default values, the new parameters are passed within #( ), the same way as they where declared.

The module instantiation code is:

module_name #(parameter list)  ([port map]);

It's also possible to override each value after the instantiation using the defparam keyword, by accessing the specific parameter using <instance_identifier>.PARAMETER_NAME, as shown below.

module_name <instance_identifier> ([port map]);

defparam <instance_identifier>.PARAMETER_NAME = new_value;

N-bit Adder Example

A simple parameterized adder can be specified as follows:

module adder
    #(
        parameter N = 4
    )
    (
        output [N - 1 : 0] c,
        input [N - 1 : 0] a, b
    );

    assign c = a + b;

endmodule

Instantiating an 8-bit adder requires tweaking the N parameter using:

adder #(N = 8) d0 ([port map]);

or:

adder d0 ([port map]);

defparam d0.N = 8;

Generate Block

The generate block allows for the instantiation of multiple instances or the conditional instantiation of any module or block of statements. Parameters are used for that purpose.

Generate Syntax

A generate block is declared between the generate and endgenerate keywords. It can't contain port or parameter declarations. Within its body can be module instantiations, continuous assignments, always blocks, primitives...basically anything.

generate
    // to generate 
endgenerate

The generate blocks can be split into two main types:

  • Generate Loops (for)
  • Conditional Generation (if-else, case)

Generate For

The first type of generate block is the generate for loop. Such a block is particularly useful in cases where N instances of a module need to be generated. Variables of the type genvar are declared for the purpose of looping. It's common to use i as the looping variable.

Instantiating a module (or specific statements) N times looks like this:

genvar i;

generate
    for (i = 0; i < N; i = i + 1) begin
        // module instantiation
    end
endgenerate

Generate If

Combining a generate statement with a if statement allows for conditional generation. Depending on the value of some parameter, commonly of Boolean 0-1 type, specific modules or statements can be generated. It's thus even possible to specify that a specific section of a design is not generated at all!

Suppose we have a USE_ADDER parameter of 0-1 type. Specifying the conditional generation of the adder is simply:

generate
    if (USE_ADDER)
        // instantiate adder
endgenerate

When USE_ADDER is 0, then nothing would be generated!

Let's now suppose that a parameter ADDER_TYPE specifies the use of a particular adder (type 0 and type 1).

Tweaking the previous code, we can easily end up with the required code, which is:

generate
    if (ADDER_TYPE)
        // instantiate type 1 adder
    else
        // instantiate type 0 adder
endgenerate

Generate Case

The case statement can be used in a similar manner to the if statement, but works better when there are multiple choices.

Let's suppose that a ADDER_TYPE parameter allows for the specification of 3 types of adders. Generating the required adder for each case, is as simple as:

generate
    case (ADDER_TYPE)
        0: // instantiate ripple-carry adder
        1: // instantiate carry-lookahead adder
        2: // instantiate carry-save adder
    endcase
endgenerate

That way, a design can be optimized for area, performance, throughput, latency etc. by simply tweaking a single parameter.


RESOURCES:

References

  1. http://www.asic-world.com/verilog/veritut.html
  2. https://www.chipverify.com/verilog/verilog-tutorial
  3. https://www.javatpoint.com/verilog

Images

  1. https://www.flickr.com/photos/creative_stock/5227842611

Block diagrams and other visualizations were made using draw.io


Previous articles of the series


Final words | Next up

And this is actually it for today's post!

Next time we will get into some more features of Verilog that we haven't covered yet...

See Ya!

Keep on drifting!

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now