Digital Design / Module 3: Verilog that means hardware
17. Parameters and generate
Write one design that scales.
See it move
The idea
Parameters make one design work at many sizes. They are fixed when the design is elaborated, before simulation or synthesis starts, so they aren't runtime variables.
module fifo #(parameter WIDTH = 8, parameter DEPTH = 16) (
input [WIDTH-1:0] din,
output [WIDTH-1:0] dout
// other ports
);
localparam AW = $clog2(DEPTH);
reg [WIDTH-1:0] mem [0:DEPTH-1];
reg [AW:0] wptr, rptr;
endmodule
fifo #(.WIDTH(32), .DEPTH(64)) u_fifo (/* ports */);$clog2 gives the address width: 6 for a depth of 64. Use localparam for values derived inside the module, so nobody can override them by mistake.
generate
A generate loop replicates hardware at elaboration. It runs before the circuit exists, and the result is just more hardware.
module add_n #(parameter N = 8)
(input [N-1:0] a, b, output [N-1:0] s, output cout);
wire [N:0] c;
assign c[0] = 1'b0;
assign cout = c[N];
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin : bit_slice
full_adder fa(.a(a[i]), .b(b[i]), .cin(c[i]),
.s(s[i]), .cout(c[i+1]));
end
endgenerate
endmoduleChange the width in the animation above to see what a larger N does to the carry chain.
Loops in always blocks
A for loop inside an always block is also legal, and it is also not sequential at runtime. It unrolls into parallel logic, all of which settles within one clock cycle. A loop with a hundred iterations means a hundred copies of the logic.
Common mistake
Using a parameter to mean something a signal should mean. If you must change a value while the chip is running, it needs to be a register or an input port, because a parameter can't change after elaboration.
Key ideas
- Parameters set widths and depths at elaboration.
- $clog2 computes address widths.
- generate replicates hardware; it is not a runtime loop.
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
for (i = 0; i < N; i = i + 1) begin : slice
Why it failed
i < N-1 builds N-1 adders. Bit N-1 of the sum and carry N are never driven, so they float, which shows as z or x. Off-by-one errors in generate loops produce missing hardware, not a simple numeric mistake.
Interview questions
coreWhat's the address width of a depth-64 memory?
$clog2(64) = 6.
basicWhat does $clog2 return for 16, 17 and 100?
It returns the ceiling of log2: 4 for 16, 5 for 17 and 7 for 100. That is the number of address bits needed to give each entry its own address, so depth 17 needs 5 bits even though it is only one more than 16. A depth of 100 needs 7 bits, since 2^7 = 128.
basicWhat is the difference between parameter and localparam?
A parameter can be overridden from outside when the module is instantiated. A localparam cannot; it is fixed inside the module, usually derived from parameters, such as an address width computed from a depth. Using localparam for derived values stops anyone from overriding them inconsistently.
coreA module is instantiated twice, once with WIDTH = 8 and once with WIDTH = 32. How much hardware is that?
Two separate circuits: an 8-bit version and a 32-bit version. Parameters are fixed at elaboration, so each instance is built at its own size, and the 32-bit one is roughly four times larger in its datapath. Nothing is shared or selected at runtime.
coreFor N = 4 and inputs 3 and 5, the sum reads z000 and cout reads z. What is wrong?
for (i = 0; i < N-1; i = i + 1) begin : bit_slice
full_adder fa(.a(a[i]), .b(b[i]), .cin(c[i]),
.s(s[i]), .cout(c[i+1]));
endThe loop stops at N-2, so only three full adders are placed. Bit 3 of the sum and c[4] have no driver and float at z, while the low three bits happen to be correct. Change the condition to i < N. It compiles without error because a loop that places fewer copies is perfectly legal code.
coreWhat hardware does this loop describe, and how wide must cnt be?
always @(*) begin
cnt = 0;
for (i = 0; i < 8; i = i + 1)
cnt = cnt + d[i];
endIt is a population count: the number of 1s in an 8-bit input. The loop does not run in time; it unrolls into eight additions that all settle within one cycle, which tools can restructure into an adder tree. The result ranges from 0 to 8, so cnt needs $clog2(9) = 4 bits; 3 bits would wrap 8 to 0.
coreWhy give a generate loop's block a label such as bit_slice?
The label gives every copy a stable hierarchical name, such as add_n.bit_slice[3].fa, which you need for waveform debugging, assertions and constraints. Without a label, tools make up names like genblk1, which are harder to read and can change when the code is edited. Many style guides require the label.
advancedA FIFO computes AW = $clog2(DEPTH) and declares reg [AW-1:0] addr;. What happens when DEPTH is 1?
$clog2(1) is 0, so the declaration becomes reg [-1:0], which is a legal 2-bit range rather than a zero-width signal. The code compiles, but the address logic no longer means what the designer intended. Parameterised designs should check their edge values, for example by requiring DEPTH of at least 2 or handling the case explicitly.
advancedHow does this code let one module have either a registered or a combinational output?
generate
if (REG_OUT) begin : g_reg
always @(posedge clk) y <= d;
end else begin : g_comb
always @(*) y = d;
end
endgenerateThe generate if is evaluated at elaboration, so exactly one branch becomes hardware for each instance. With REG_OUT = 1 there is a flop and y updates at the clock edge; with REG_OUT = 0 there is just a wire-like path. It is the right tool for a choice fixed per instance. A choice that must change while the chip runs needs a real signal and a mux instead.