generate if and generate case
In the previous article a parameter changed the width of a module. In this one a parameter chooses between two different circuits.
You know generate for from Module 5: it stamps out N copies of the same structure. Sometimes you do not want N copies of the same thing. You want one parameter to decide between, say, a ripple-carry adder for small widths and a faster structure for large ones, chosen automatically at instantiation. That is what generate if and generate case do. Same keyword, but instead of repeating structure they select it.
generate if: a fork decided at elaboration
module adder_auto #(parameter WIDTH = 8) (
input wire [WIDTH-1:0] a, b,
input wire cin,
output wire [WIDTH-1:0] sum,
output wire cout
);
generate
if (WIDTH <= 8) begin : gen_ripple
// Small width: plain ripple carry is fine, minimal area
assign {cout, sum} = a + b + cin;
end else begin : gen_fast
// Large width: split into two halves for a carry-select style speedup
wire [WIDTH/2-1:0] lower_sum;
wire lower_cout;
wire [WIDTH/2-1:0] upper_sum0, upper_sum1;
wire upper_cout0, upper_cout1;
assign {lower_cout, lower_sum} = a[WIDTH/2-1:0] + b[WIDTH/2-1:0] + cin;
assign {upper_cout0, upper_sum0} = a[WIDTH-1:WIDTH/2] + b[WIDTH-1:WIDTH/2] + 1'b0;
assign {upper_cout1, upper_sum1} = a[WIDTH-1:WIDTH/2] + b[WIDTH-1:WIDTH/2] + 1'b1;
assign sum = {lower_cout ? upper_sum1 : upper_sum0, lower_sum};
assign cout = lower_cout ? upper_cout1 : upper_cout0;
end
endgenerate
endmodule
Instantiate this at WIDTH = 4 and only the gen_ripple branch exists in the netlist. The whole gen_fast branch is dropped before synthesis starts, as if you had never written it. At WIDTH = 32 it is the other way round. The decision is made at elaboration, from the parameter value alone, so there is no multiplexer, no runtime check and no gates spent on the branch you did not take.
This is worth holding onto: if inside an always block builds a mux, because the condition can change at runtime. generate if builds nothing for the untaken branch, because the condition is fixed when the module is instantiated and can never change afterwards.
The block names matter here too
As with generate for in Module 5, the : gen_ripple and : gen_fast labels give each branch a hierarchical name. When you are debugging and need to look at a signal inside whichever branch was elaborated, the path looks like this:
tb.u_dut.gen_fast.upper_sum0
If the instance took the gen_ripple branch, that path does not exist in the simulation hierarchy at all, which is itself a handy check that the branch you expected got selected.
generate case: more than two options
With three or more structural variants, generate case reads better than a chain of generate if/else if:
module encoder_selectable #(parameter STYLE = "PRIORITY") (
input wire [7:0] req,
output reg [2:0] enc,
output reg valid
);
generate
case (STYLE)
"PRIORITY": begin : gen_priority
always @(*) begin
enc = 3'd0; valid = 1'b0;
if (req[7]) begin enc = 3'd7; valid = 1'b1; end
else if (req[6]) begin enc = 3'd6; valid = 1'b1; end
else if (req[5]) begin enc = 3'd5; valid = 1'b1; end
// ... remaining bits omitted for brevity
else if (req[0]) begin enc = 3'd0; valid = 1'b1; end
end
end
"ONEHOT": begin : gen_onehot
always @(*) begin
enc = 3'd0; valid = |req;
case (1'b1)
req[0]: enc = 3'd0;
req[1]: enc = 3'd1;
req[2]: enc = 3'd2;
req[3]: enc = 3'd3;
req[4]: enc = 3'd4;
req[5]: enc = 3'd5;
req[6]: enc = 3'd6;
req[7]: enc = 3'd7;
default: ;
endcase
end
end
default: begin : gen_invalid
// Unrecognized STYLE parameter: fail loudly at elaboration
initial begin
$display("ERROR: encoder_selectable STYLE=%s is not a recognized style", STYLE);
$finish;
end
end
endcase
endgenerate
endmodule
The parameter here is a string rather than a number. Verilog allows this, and at the instantiation site encoder_selectable #(.STYLE("ONEHOT")) u_enc (...) tells you what you are getting without looking up what STYLE = 1 or STYLE = 2 means.
The default branch is not dead code, it is a guard. If someone instantiates the module with a typo such as STYLE = "PRIORTY", the default branch catches it and fails at elaboration with a clear message, rather than silently generating no logic and leaving you to find the problem in simulation, or after tape-out.
When to use it
generate if and generate case earn their place in one situation: when a parameter should produce different hardware structures, not different widths of the same structure. The usual cases are a small-versus-large-width architecture switch like the adder above, a debug-versus-production build where assertion and monitor logic is compiled in only for simulation, and an FPGA-versus-ASIC target switch where one platform gets a vendor primitive and the other gets a generic RTL fallback. Vendor IP does this constantly: a memory controller might use generate case on a TARGET_DEVICE parameter to instantiate a hard block on one branch and portable RTL on another, behind the same interface.
Practice Exercise
Write a parameterized shifter module with a MODE string parameter that selects between "LOGICAL" (vacated bits filled with 0) and "ARITHMETIC" (vacated bits filled with the sign bit) right shift, using generate case. Include a default branch that fails at elaboration if MODE is not recognized.
Solution
`timescale 1ns / 1ps
`default_nettype none
module shifter #(parameter WIDTH = 8, parameter MODE = "LOGICAL") (
input wire [WIDTH-1:0] data_in,
input wire [$clog2(WIDTH)-1:0] shamt,
output wire [WIDTH-1:0] data_out
);
generate
case (MODE)
"LOGICAL": begin : gen_logical
assign data_out = data_in >> shamt;
end
"ARITHMETIC": begin : gen_arithmetic
assign data_out = $signed(data_in) >>> shamt;
end
default: begin : gen_invalid
initial begin
$display("ERROR: shifter MODE=%s is not recognized", MODE);
$finish;
end
end
endcase
endgenerate
endmodule
Instantiate shifter #(.WIDTH(8), .MODE("ARITHMETIC")) and only the gen_arithmetic branch, a signed >>> shift, exists in the elaborated design. With .MODE("LOGICAL") you get the plain >> branch. Misspell the mode string and elaboration stops with a message instead of building nothing.
That is the structural side of parameterization: generate if and generate case pick a structure once, at elaboration, and the branch not taken costs nothing. In the next article we put all of this to work on something bigger than an adder, a parameterized memory model that we will reuse for the rest of the module.