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
= (a ^ b) ^ c_in; // sum
s = (a & b) | (b & c_in) | (a & c_in); // carry bit
c_out 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.
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
(.a(a[i]),.b(b[i]),.c_in(c[i]),.s(s[i]),.c_out(c[i+1]));
full_adder fa 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.
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:
-rtl -flatten_hierarchy none -top adder -mode out_of_context
synth_design -cell adder -force rtl-synth.v write_verilog
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:
[0].fa
full_adder \genblk1(.a(a[0]),
(b[0]),
.b(c_in),
.c_in(c_1),
.c_out(s[0])); .s
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
(.a(a[i]),.b(b[i]),.c_in(c[i]),.s(s[i]),.c_out(c[i+1]));
full_adder fa 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
[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
DUT.myadder);
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.