Verilog Generate Configurable RTL Designs

Verilog generate statement is a powerful construct for writing configurable, synthesizable RTL. It can be used to create multiple instantiations of modules and code, or conditionally instantiate blocks of code. However, many Verilog programmers often have questions about how to use Verilog generate effectively. In this article, I will review the usage of three forms of Verilog generate—generate loop, if-generate, and case-generate.

Types of Verilog Generate Constructs

There are two kinds of Verilog generate constructs. Generate loop constructs allow a block of code to be instantiated multiple times, controlled by a variable index. Conditional generate constructs select at most one block of code between multiple blocks. Conditional generate constructs include if-generate and case-generate forms.

Verilog generate constructs are evaluated at elaboration, which occurs after parsing the HDL (and preprocessor), but before simulation begins. Therefore all expressions within generate constructs must be constant expressions, deterministic at elaboration time. For example, generate constructs can be affected by values from parameters, but not by dynamic variables.

A Verilog generate block creates a new scope and a new level of hierarchy, almost like instantiating a module. This sometimes causes confusion when trying to write a hierarchical reference to signals or modules within a generate block, so it is something to keep in mind.

Use of the keywords generate and endgenerate (and begin/end) is actually optional. If they are used, then they define a generate region. Generate regions can only occur directly within a module, and they cannot nest. For readability, I like to use the generate and endgenerate keywords.

Verilog Generate Loop

The syntax for a generate loop is similar to that of a for loop statement. The loop index variable must first be declared in a genvar declaration before it can be used. The genvar is used as an integer to evaluate the generate loop during elaboration. The genvar declaration can be inside or outside the generate region, and the same loop index variable can be used in multiple generate loops, as long as the loops don’t nest.

Within each instance of the “unrolled” generate loop, an implicit localparam is created with the same name and type as the loop index variable. Its value is the “index” of the particular instance of the “unrolled” loop. This localparam can be referenced from RTL to control the generated code, and even referenced by a hierarchical reference.

Generate block in a Verilog generate loop can be named or unnamed. If it is named, then an array of generate block instances is created. Some tools warn you about unnamed generate loops, so it is good practice to always name them.

The following example shows a gray to binary code converter written using a Verilog generate loop.

Example of parameterized gray to binary code converter

module gray2bin
#(parameter SIZE = 8)
(
  input [SIZE-1:0] gray,
  output [SIZE-1:0] bin
)

Genvar gi;
// generate and endgenerate is optional
// generate (optional)
  for (gi=0; gi<SIZE; gi=gi+1) begin : genbit
    assign bin[gi] = ^gray[SIZE-1:gi]; // Thanks Dhruvkumar!
  end
// endgenerate (optional)
endmodule

Another example from the Verilog-2005 LRM illustrates how each iteration of the Verilog generate loop creates a new scope. Notice wire t1, t2, t3 are declared within the generate loop. Each loop iteration creates a new t1, t2, t3 that do not conflict, and they are used to wire one generated instance of the adder to the next. Also note the naming of the hierarchical reference to reference an instance within the generate loop.

module addergen1
#(parameter SIZE = 4)
(
  input  logic [SIZE-1:0] a, b,
  input  logic            ci,
  output logic            co,
  output logic [SIZE-1:0] sum
);

wire [SIZE :0] c;
genvar i;

assign c[0] = ci;

// Hierarchical gate instance names are:
// xor gates: bitnum[0].g1 bitnum[1].g1 bitnum[2].g1 bitnum[3].g1
// bitnum[0].g2 bitnum[1].g2 bitnum[2].g2 bitnum[3].g2
// and gates: bitnum[0].g3 bitnum[1].g3 bitnum[2].g3 bitnum[3].g3
// bitnum[0].g4 bitnum[1].g4 bitnum[2].g4 bitnum[3].g4
// or gates: bitnum[0].g5 bitnum[1].g5 bitnum[2].g5 bitnum[3].g5
// Gate instances are connected with nets named:
// bitnum[0].t1 bitnum[1].t1 bitnum[2].t1 bitnum[3].t1
// bitnum[0].t2 bitnum[1].t2 bitnum[2].t2 bitnum[3].t2
// bitnum[0].t3 bitnum[1].t3 bitnum[2].t3 bitnum[3].t3

for(i=0; i<SIZE; i=i+1) begin:bitnum
  wire t1, t2, t3;
  xor g1 ( t1, a[i], b[i]);
  xor g2 ( sum[i], t1, c[i]);
  and g3 ( t2, a[i], b[i]);
  and g4 ( t3, t1, c[i]);
  or g5 ( c[i+1], t2, t3);
end

assign co = c[SIZE];

endmodule

Generate loops can also nest. Only a single generate/endgenerate is needed (or none, since it’s optional) to encompass the nested generate loops. Remember each generate loop creates a new scope. Therefore the hierarchical reference to the inner loop needs to include the label of the outer loop.

Conditional If-Generate

Conditional if-generate selects at most one generate block from a set of alternative generate blocks. Note I say at most, because it may also select none of the blocks. The condition must again be a constant expression during elaboration.

Conditional if-generate may be named or unnamed, and may or may not have begin/end. Either way, it can contain only one item. It also creates a separate scope and level of hierarchy, like a generate loop. Since conditional generate selects at most one block of code, it is legal to name the alternative blocks of code within the single if-generate with the same name. That helps to keep hierarchical reference to the code common regardless of which block of code is selected. Different generate constructs, however, must have different names.

Conditional Case-Generate

Similar to if-generate, case-generate can also be used to conditionally select one block of code from several blocks. Its usage is similar to the basic case statement, and all rules from if-generate also apply to case-generate.

Direct Nesting of Conditional Generate

There is a special case where nested conditional generate blocks that are not surrounded by begin/end can consolidate into a single scope/hierarchy. This avoids creating unnecessary scope/hierarchy within the module to complicate the hierarchical reference. This special case does not apply at all to loop generate.

The example below shows how this special rule can be used to construct complex if-else if conditional generate statements that belong to the same hierarchy.

module test;
  parameter p = 0, q = 0;
  wire a, b, c;

  //---------------------------------------------------------
  // Code to either generate a u1.g1 instance or no instance.
  // The u1.g1 instance of one of the following gates:
  // (and, or, xor, xnor) is generated if
  // {p,q} == {1,0}, {1,2}, {2,0}, {2,1}, {2,2}, {2, default}
  //---------------------------------------------------------

  if (p == 1)
    if (q == 0) begin : u1 // If p==1 and q==0, then instantiate
      and g1(a, b, c); // AND with hierarchical name test.u1.g1
    end
    else if (q == 2) begin : u1 // If p==1 and q==2, then instantiate
      or g1(a, b, c); // OR with hierarchical name test.u1.g1
    end
    // "else" added to end "if (q == 2)" statement
    else ; // If p==1 and q!=0 or 2, then no instantiation
  else if (p == 2)
    case (q)
      0, 1, 2:
        begin : u1 // If p==2 and q==0,1, or 2, then instantiate
          xor g1(a, b, c); // XOR with hierarchical name test.u1.g1
        end
      default:
        begin : u1 // If p==2 and q!=0,1, or 2, then instantiate
          xnor g1(a, b, c); // XNOR with hierarchical name test.u1.g1
        end
    endcase

endmodule

This generate construct will select at most one of the generate blocks named u1. The hierarchical name of the gate instantiation in that block would be test.u1.g1. When nesting if-generate constructs, the else always belongs to the nearest if construct. Note the careful placement of begin/end within the code Any additional begin/end will violate the direct nesting requirements, and cause an additional hierarchy to be created.

Named vs Unnamed Generate Blocks

It is recommended to always name generate blocks to simplify hierarchical reference. Moreover, various tools often complain about anonymous generate blocks. However, if a generate block is unnamed, the LRM does describe a fixed rule for how tools shall name an anonymous generate block based on the text of the RTL code.

First, each generate construct in a scope is assigned a number, starting from 1 for the generate construct that appears first in the RTL code within that scope, and increases by 1 for each subsequent generate construct in that scope. The number is assigned to both named and unnamed generate constructs. All unnamed generate blocks will then be given the name genblk[n] where [n] is the number assigned to its enclosing generate construct.

It is apparent from the rule that RTL code changes will cause the unnamed generate construct name to change. That in turn makes it difficult to maintain hierarchical references in RTL and scripts. Therefore, it is recommended to always name generate blocks.

Conclusion

Verilog generate constructs are powerful ways to create configurable RTL that can have different behaviours depending on parameterization. Generate loop allows code to be instantiated multiple times, controlled by an index. Conditional generate, if-generate and case-generate, can conditionally instantiate code. The most important recommendation regarding generate constructs is to always name them, which helps simplify hierarchical references and code maintenance.

References

17 thoughts on “Verilog Generate Configurable RTL Designs”

  1. Thanks for the Posting. I recently saw a “conditional generate” code that I wasn’t aware of. Good to see this detailed explanation.

    Reply
  2. Hi Jason,

    Is there a way to use generate blocks for signal declarations?
    When I check with the systemverilog LRM I see this sentence, “A generate block may not contain port declarations, specify blocks, or specparam declarations.”.

    What about internal signal declarations?
    For instance, if I have two different structs with different fields inside the structs, and I want to choose between the two types for an internal signal declaration type (note not a port) something like the below gives a compile error saying the signal is not declared.

    parameter integer unsigned some_parameter = 1;

    generate
    if (some_parameter == 1) begin
    type_a x;
    end else if (some_parameter = 0) begin
    type_b x;
    end
    endgenerate

    Then at some point below X is referenced.

    Thanks in advance!

    Reply
    • Hi Sami. That’s an interesting question. I tried something similar to your code and also encountered an error during compilation that the type is unknown. I suspect it’s because generate statement is only resolved during elaboration, but the type needs to already exist during compilation. How I usually handle something like that is if the parameter only affects a field within the struct, I parameterize that field to use the parameter, rather than use generate to select between two structs. Also, you can pass a TYPE as a parameter into a module, and use that TYPE in the struct. That may also achieve what you’re looking for.

      Reply
      • Thanks for the quick reply!
        Yes, what you suggested is the first approached I tried. I have parameters for the width of the fields in the struct. My hope with that approach is if the width parameter is set to 0 then that field should not exist. I wasn’t sure if that would be reality of what would happen. I noticed that in simulations that field shows up with negative index. For example:

        parameter FIELD_WIDTH = 0;

        logic [FIELD_WIDTH-1:0] x;

        This will show up as x[-1:0] in waveforms. What I don’t know is if this is a tool quirk or this will actually synthesize to two bits.

        Reply
  3. Hi Jason,
    Great article. Is it possible to change the loop index inside the generate block itself? For example, in a generate block, I have an if statement which generates two instantiations and in the else statement it instantiates one. So I’d like to increment the loop index in the if statement itself. Is it possible?
    Regards,
    Kunal

    Reply
  4. Nice post. Seeing that you are using instances of logic gates. Guess I can instantiate any other module, but what about processes such as “always” or “assign”?

    Reply
  5. Great article but there are several errors,

    module for_loop_synthesis (i_Clock);
    input i_Clock;
    integer ii=0;
    reg [3:0] r_Shift_With_For = 4’h1;
    reg [3:0] r_Shift_Regular = 4’h1;

    // Performs a shift left using a for loop
    always @(posedge i_Clock)
    begin
    for(ii=0; ii<3; ii=ii+1)
    r_Shift_With_For[ii+1] <= r_Shift_With_For[ii];
    end

    // Performs a shift left using regular statements
    always @(posedge i_Clock)
    begin
    r_Shift_Regular[1] <= r_Shift_Regular[0];
    r_Shift_Regular[2] <= r_Shift_Regular[1];
    r_Shift_Regular[3] <= r_Shift_Regular[2];
    end
    endmodule

    module for_loop_synthesis_tb (); // Testbench
    reg r_Clock = 1'b0;
    // Instantiate the Unit Under Test (UUT)
    for_loop_synthesis UUT (.i_Clock(r_Clock));
    always
    #10 r_Clock = !r_Clock;
    endmodule

    module gray2bin
    #(parameter SIZE = 8)
    (
    input [SIZE-1:0] gray,
    output [SIZE-1:0] bin
    );

    generate
    genvar gi;
    // generate and endgenerate is optional
    // generate (optional)
    for (gi=0; gi<SIZE; gi=gi+1) begin : genbit
    assign bin[gi] = ^gray[SIZE-1:gi];
    end
    // endgenerate (optional)
    endgenerate
    endmodule

    module gray2bin_tb(); //Testbench
    reg [15:0] in,out;

    gray2bin #(16) DUT ( .gray(in), .bin(out) );

    integer jj=0;

    initial begin

    for(jj=0;jj<16;jj=jj+1) begin
    in=jj;
    #5;
    end
    end

    endmodule

    i have corrected all of them for you

    thank you for making this cheers

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.