Hierarchy and Generate
Nobody writes a processor as a flat list of gates. A design is a tree: a top module instantiates a few large blocks, each of those instantiates smaller ones, and at the bottom are gates. Each level can be tested alone and each hides everything below it. This article is about doing that on purpose, and about the generate construct that lets one description stand for any number of instances.
Why
A 16-bit ALU as one flat module is some hundreds of gate instantiations, tested as a whole, with every bug traced through the whole thing. As a hierarchy it is a half adder of two gates, a full adder of two half adders and an OR, a 4-bit adder of four full adders, and a 16-bit adder of four of those. Each piece is small enough to see at once and to test alone, and a bug in the full adder is fixed in one place.
The 4-bit adder
`timescale 1ns / 1ps
`default_nettype none
module rca_4bit (
input wire [3:0] a,
input wire [3:0] b,
input wire cin,
output wire [3:0] sum,
output wire cout
);
wire c1, c2, c3;
full_adder u_fa0 (.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1));
full_adder u_fa1 (.a(a[1]), .b(b[1]), .cin(c1), .sum(sum[1]), .cout(c2));
full_adder u_fa2 (.a(a[2]), .b(b[2]), .cin(c2), .sum(sum[2]), .cout(c3));
full_adder u_fa3 (.a(a[3]), .b(b[3]), .cin(c3), .sum(sum[3]), .cout(cout));
endmodule
That is the schematic of section 2.1 as text: each box is an instance, each wire between boxes is a named wire, and the module boundary is the port list.
Generate
Four instantiations is fine. Sixty-four is not. A generate loop makes the instances at elaboration, before simulation starts:
`timescale 1ns / 1ps
`default_nettype none
module rca #(
parameter WIDTH = 4
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire cin,
output wire [WIDTH-1:0] sum,
output wire cout
);
wire [WIDTH:0] carries; // carries[0] is cin, carries[WIDTH] is cout
assign carries[0] = cin;
assign cout = carries[WIDTH];
genvar i;
generate
for (i = 0; i < WIDTH; i = i + 1) begin : gen_fa
full_adder u_fa (
.a (a[i]),
.b (b[i]),
.cin (carries[i]),
.sum (sum[i]),
.cout(carries[i+1])
);
end
endgenerate
endmodule
genvar i is a loop variable that exists only while the tool is building the design; it is not a signal and cannot be used in an always block or an assign. The loop is not something that runs; it is unrolled once, producing one copy of its body per value of i. The block label gen_fa is required when the body instantiates modules, and it gives each copy a name: gen_fa[0].u_fa, gen_fa[1].u_fa, and so on. After elaboration, rca #(.WIDTH(4)) is the same hardware as the four-instance version; only the source is shorter.
generate also takes if and case, for choosing between structures by parameter:
generate
if (FAST_ADDER) begin : gen_cla
cla_adder #(.WIDTH(WIDTH)) u_adder (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
end else begin : gen_rca
rca #(.WIDTH(WIDTH)) u_adder (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
end
endgenerate
Hierarchical names
Every instance has a path from the top of the design, instance names joined with dots, and a testbench can use that path to look at any signal at any depth:
`timescale 1ns / 1ps
`default_nettype none
module tb_rca;
reg [3:0] a, b;
reg cin;
wire [3:0] sum;
wire cout;
rca #(.WIDTH(4)) u_dut (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
initial begin
$dumpfile("rca_waves.vcd");
$dumpvars(0, tb_rca);
a = 4'd5; b = 4'd3; cin = 1'b0; #20;
$display("carry into bit 2 = %b", tb_rca.u_dut.carries[2]);
$display("a input of bit-2 adder = %b", tb_rca.u_dut.gen_fa[2].u_fa.a);
$finish;
end
endmodule
This is for testbenches and debugging only. A synthesisable module must never refer to a signal by hierarchical path: the tools reject it, and a module that knows the name of its parent is wrong in principle.
Habits
One job per module: an adder, a UART receiver, a FIFO controller. Keep modules under a few hundred lines; a longer one is usually two. Instance names that say what the instance is for, u_tx_fifo rather than u1. And keep the top level thin: the module that connects to the pins should instantiate the big blocks and wire them, with no always blocks and no logic of its own, because logic in the top level cannot be tested at module level.
module chip_top (
input wire clk, rst_n,
input wire rx_pin,
output wire tx_pin,
output wire [7:0] led
);
wire [7:0] rx_data;
wire rx_valid;
uart_rx u_rx (.clk(clk), .rst_n(rst_n), .rx(rx_pin), .data(rx_data), .valid(rx_valid));
led_ctrl u_led (.clk(clk), .rst_n(rst_n), .data(rx_data), .valid(rx_valid), .leds(led));
uart_tx u_tx (.clk(clk), .rst_n(rst_n), .data(rx_data), .start(rx_valid), .tx(tx_pin));
endmodule
A production SoC is hundreds or thousands of modules, ten or more levels deep, and it is manageable for exactly the reasons the 4-bit adder is.
Practice Exercise
Write rca_16bit as a wrapper around rca #(.WIDTH(16)), then a 32-bit adder from two of them with the carry chained.
Solution
`timescale 1ns / 1ps
`default_nettype none
module rca_16bit (
input wire [15:0] a, b,
input wire cin,
output wire [15:0] sum,
output wire cout
);
rca #(.WIDTH(16)) u_rca16 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
endmodule
`timescale 1ns / 1ps
`default_nettype none
module rca_32bit (
input wire [31:0] a, b,
input wire cin,
output wire [31:0] sum,
output wire cout
);
wire mid_carry;
rca_16bit u_low (.a(a[15:0]), .b(b[15:0]), .cin(cin), .sum(sum[15:0]), .cout(mid_carry));
rca_16bit u_high (.a(a[31:16]), .b(b[31:16]), .cin(mid_carry), .sum(sum[31:16]), .cout(cout));
endmodule
mid_carry is the whole point: the carry out of the low half is the carry into the high half.
The last article of this section puts the whole of 4.4 to work: a 4-bit ALU built from gates upward, with a testbench.