A parameterized mux takes N inputs of width W. The cleanest interface is one packed vector of N·W bits, with input i in bits [i*W +: W].
The select width follows from N. An indexed part-select picks the chosen slice.
Read the bar from bit 39 on the left to bit 0 on the right; each box is one input. The hex values 8'h10 to 8'h50 are the ones the testbench below loads.
Start bit: sel * W. For select 2 that is 16.
Width: +: W always takes exactly 8 bits upward, so the slice width is constant even though the start moves.
Spare selects: a 3-bit select can name 5, 6 and 7, which have no box. The guard below returns 0 for them.
N may be 1 (a single input, wired through): the select width is guarded as (N > 1) ? $clog2(N) : 1 because $clog2(1) is 0, which would give a [-1:0] select. N = 1 then takes a 1-bit select that is always 0.
N = 5 needs a 3-bit select. That select can encode 8 values. Selects 5, 6 and 7 are invalid.
Handling them here with an output of 0 keeps simulation free of x. That choice costs real logic for the invalid selects.
If invalid selects are provably impossible, add an assertion to check legality. The assertion does not change the hardware: this RTL still returns 0. Treating invalid selects as don't-cares needs different RTL plus a flow-supported synthesis assumption.
Choice | Option | Consequence |
|---|---|---|
Out-of-range select | Return 0 / return a fixed value / assert | A defined value avoids |
Input packing | One packed vector / unpacked array port | Packed vector works in plain Verilog and every tool; arrays need SystemVerilog support. |
N is a power of two | Select covers every value | No out-of-range case exists, so the guard disappears. |
sel reads 6. What does y read, and which line of the module decides it?y reads 0. The default y = '0 decides it, because the guard sel < N blocks the indexed pick.
$clog2(N) for N = 1, and what goes wrong with the declaration [$clog2(N)-1:0] sel?$clog2(1) is 0, so the declaration becomes [-1:0], a 2-bit-wide vector counting backwards. Parameterized blocks that may be instantiated with N = 1 need a special case (for example localparam SW = (N > 1) ? $clog2(N) : 1;).
sel < N guard in synthesis?It is always true, so synthesis removes it. The guard costs nothing, which is why it is safe to write it every time.
The full parameterized mux plus a testbench that sweeps every select, valid or not.
module mux_n #(
parameter int W = 8, // data width
parameter int N = 5 // number of inputs (need not be a power of two); N must be at least 1
) (
input logic [N*W-1:0] d, // d[i*W +: W] is input i
// N = 1 guard: $clog2(1) is 0, so [$clog2(N)-1:0] would be [-1:0]
input logic [(N > 1 ? $clog2(N) : 1)-1:0] sel,
output logic [W-1:0] y
);
always_comb begin
y = '0; // default for out-of-range select
if (32'(sel) < N) y = d[sel*W +: W]; // widen sel to compare with the 32-bit N
end
endmodule