generate loops
in VerilogTraditional 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
endmoduleThe 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.
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
endmoduleTo 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.
In the src/
directory, create files named full_adder.v and adder.v containing the module
definitions given above.
Edit the testbench template in testbench.v and make the following
additions:
a, b, and c_in like this: {a,b,c_in} <= clk_count;Place this assignment in the always block
labeled “CREATE STIMULI”.
a and b have four bits, and c_in has one bit, there are a total of
9 input bits. To verify all input combinations, we need to simulate 2^9
cases. The clk_count signal
takes gets a unique value every clock cycle, so after 2^9 cycles we
should have incremented through all the test cases. Find the termination
condition in the testbench, and change it so that it stops when clk_count equals 2^9.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.
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.vThe 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.
generate
blocksWhen 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
endgenerateThen 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 mainIndicate on Canvas that your assignment is done.