generate loops in Verilog

Generate Loops in Verilog

Traditional for and while loops are “behavioral” loops. Verilog also supports structural loops that create repeated instances of a submodule, or repeated assignments. This capability uses the generate syntax. As an example, we will implement a classic ripple carry adder using structural syntax.

You may be familiar with the standard full adder logic module, defined in the code below.

module full_adder
  (
      input      a,
      input      b,
      input      c_in,
      output reg s,
      output reg c_out
   );

   always @(*) begin
      s     = (a ^ b) ^ c_in;                     // sum
      c_out = (a & b) | (b & c_in) | (a & c_in);  // carry bit 
   end
endmodule

The ripple-carry adder is a string of full-adder modules, where the c_out (carry out) from each module connects to the c_in (carry in) of the next module. This is illustrated in the schematic figure shown below.

Ripple-carry adder schematic.

In Verilog, a structural model is a direct description of a schematic like the one in the figure. Some models require many repetitions of the same submodule, and it is inefficient to code each instantiation separately. The generate statement allows us to place and connect all the modules automatically. An additional benefit is that generate loops can be parameterized, so the structure is adjustable.

The code below describes an N bit ripple-carry adder with default size N=4.

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

  // Create a vector for the carry signals:
  wire [N:0] c;
  assign     c[0]  = c_in;
  assign     c_out = c[N];

  genvar i;
  generate 
     for (i=0; i<N; i=i+1) begin 
        full_adder fa (.a(a[i]),.b(b[i]),.c_in(c[i]),.s(s[i]),.c_out(c[i+1]));
     end
  endgenerate
endmodule

To connect the carry chain, we declared a wire vector named c, corresponding to the blue labels in the schematic diagram. The wire signals can be directly connected to submodule ports. To connect the end-points at c_in and c_out, we use assign statements.

Assigned Tasks

In the src/ directory, create files named full_adder.v and adder.v containing the module definitions given above.

Simulate the Design

Edit the testbench template in testbench.v and make the following additions:

      {a,b,c_in} <= clk_count;

Place this assignment in the always block labeled “CREATE STIMULI”.

After making the changes, run make simulate to verify the behavior. You should see a table of outputs like this:

clk:            0       a:   0  b:   0  c_in:  0        s:  0   c_out: 0
clk:            1       a:   0  b:   0  c_in:  0        s:  0   c_out: 0
clk:            2       a:   0  b:   0  c_in:  1        s:  1   c_out: 0
clk:            3       a:   0  b:   1  c_in:  0        s:  1   c_out: 0
clk:            4       a:   0  b:   1  c_in:  1        s:  2   c_out: 0
clk:            5       a:   0  b:   2  c_in:  0        s:  2   c_out: 0

Notice that a, b, and s are printed as decimal values thanks to the %d option in the $write statements:

$write("\ta:  %d", a);

Scroll through the results and verify that the correct sum and carry are produced.

View the Hierarchy

Next open build.tcl and notice that some changes were made. The synth_design line has some extra commands that request a partial synthesis:

synth_design -rtl -flatten_hierarchy none -top adder -mode out_of_context
write_verilog -cell adder -force rtl-synth.v

The options here request that Vivado perform “rtl” synthesis (not targeted to any specific FPGA chip), and preserve the design hierarchy. After synthesis, the result is written to a file called rtl-synth.v. Run make implement to perform the partial synthesis, then open the rtl-synth.v file. You should see that the generate loop is expanded into four full_adder modules that look like this:

  full_adder \genblk1[0].fa 
       (.a(a[0]),
        .b(b[0]),
        .c_in(c_in),
        .c_out(c_1),
        .s(s[0]));

The generate loop is assigned a default name, genblk1. The instances within genblk1 are referenced like a vector as genblk1[0], genblk1[1], and so on. Within each genblk1 instance, the full_adder instances are named genblk1[0].fa, genblk1[1].fa, and so on.

Named generate blocks

When testing and debugging a design, default names like genblk1 can be mysterious and confusing. Verilog allows named generate loops. Modify the generate loop in adder.v so that it has the name myadder, like this:

   generate 
      for (i=0; i<N; i=i+1) begin : myadder
         full_adder fa (.a(a[i]),.b(b[i]),.c_in(c[i]),.s(s[i]),.c_out(c[i+1])); 
      end
   endgenerate

Then modify the $write and $fwrite statements in testbench.v so that it prints submodule signals for myadder[0].fa. The generic format for referencing sub-module signals is submodule1.submodule2.signal_name, like this:

      $write("\t%b+%b+%b=%b+%b0",  // format string: a+b+c_in=s+c_out
          DUT.myadder[0].fa.a,  // submodule.generate_block.instance.signal
          DUT.myadder[0].fa.b,
          DUT.myadder[0].fa.c_in,
          DUT.myadder[0].fa.s,
          DUT.myadder[0].fa.c_out
         );

Repeat the make simulate and make implement actions. In the file rtl-synth.v you should notice that the generate instances are now named myadder[0].fa, myadder[1].fa, and so on.

Examine the final results in test_result.txt and verify that the submodule signals are correct.

Turn in your work using git:

git add src/*.v *.v *.txt *.tcl 
git commit . -m "Complete"
git push origin main

Indicate on Canvas that your assignment is done.