A 2:1 multiplexer implements y = s′·a + s·b. An N:1 mux generalizes this: the select value picks one input for the output.
In hardware it is a tree of 2:1 muxes. Its depth is ⌈log₂ N⌉.
Read the tree from the inputs on the left to y on the right. Each column is one level of 2:1 muxes.
Level 1: sel[0] picks one input from each pair, so both first-level muxes switch together.
Level 2: sel[1] picks which pair wins.
Depth: four inputs need two levels; eight would need three. That is ⌈log₂ N⌉.
The bottom row, y, always copies the input that sel names.
Slot 1: sel 0 selects a (1).
Slot 2: sel 1 selects b (2).
Slot 3: sel 2 selects c (3).
Slot 4: sel 3 selects d (4).
Three listings describe one hardware. The full code sits under Testbench below; the table summarizes the trade-offs.
Style | Best for | Watch out for |
|---|---|---|
Ternary | 2:1 muxes and simple conditional values | Nested chains imply priority and become unreadable. |
| Small N where each input has a meaning | Always include |
Array index | Parameterized N, register files, tables | Index out of range returns |
Start with plain case plus a default.
Plain case with a default is the safe choice. Every selector value maps somewhere, so no latch is inferred.
unique case promises non-overlapping matches. Without a default, simulation also warns when nothing matches, and synthesis may optimize unmatched values as don't-cares. A default is legal and gives fallback behavior, but suppresses that no-match warning. If unmatched selectors are illegal, check legality separately even when a safe default is present.
priority case is for planned overlap. Branch order picks the winner.
casez treats Z (including ?) in either the selector or the case items as wildcards. Masked constants are useful, but a disconnected selector bit can therefore match a real request. Check that the selector is known before relying on the decoded result.
casex also treats X and Z in the selector as wildcards. So an unknown selector can match the wrong branch. Avoid it.
For this decoder, req = 4'b000z matches ???1 and reports request 0. The test deliberately injects that disconnected bit: the known-input assertion must reject it even though the decoder produces a plausible index. In a normal protocol monitor, use $error in the assertion's failure branch; here the expected rejection is counted so the demonstration itself can pass.
module req_decode (
input logic [3:0] req,
output logic [1:0] idx,
output logic valid
);
always_comb begin
idx = 2'd0; valid = 1'b0;
casez (req)
4'b???1: begin idx = 2'd0; valid = 1'b1; end
4'b??10: begin idx = 2'd1; valid = 1'b1; end
4'b?100: begin idx = 2'd2; valid = 1'b1; end
4'b1000: begin idx = 2'd3; valid = 1'b1; end
default: begin idx = 2'd0; valid = 1'b0; end
endcase
end
endmodule
module tb_req_decode;
logic [3:0] req;
logic [1:0] idx;
logic valid;
int errors = 0, rejected = 0;
req_decode dut (.*);
initial begin
req = 4'b0001; #1;
assert (!$isunknown(req)) else errors++;
if ({valid, idx} !== 3'b100) errors++;
req = 4'b0000; #1;
if (valid !== 1'b0) errors++;
req = 4'b000z; #1;
assert (!$isunknown(req)) else rejected++;
if ({valid, idx} !== 3'b100) errors++; // Z is a wildcard, not an idle request
if (rejected != 1) errors++;
if (errors == 0) $display("PASS: known requests decode; the input guard catches selector Z");
else $display("FAIL: errors=%0d rejected=%0d", errors, rejected);
$finish;
end
endmoduleunique case include a default, and how do you still detect an illegal selector?Yes. A default defines fallback behavior and suppresses the unique no-match warning; overlap checking remains. Add a separate legality assertion, for example assert (!$isunknown(sel) && sel < 2'd3); for legal codes 0, 1 and 2. Without a default, unmatched values produce a simulation warning and may become synthesis don't-cares, so do not assume that a warning alone guarantees safe hardware behavior.
A mux can also use AND-OR form: y = (s0 & a) | (s1 & b) | ....
Use it when the selects are already one-hot, like decoder or arbiter outputs. It has shallow depth: one AND stage plus one wide OR.
With bounded fan-in f, the OR becomes a tree: the idealized AND-OR depth is roughly 1 + ceil(log_f N), plus routing and load. It grows more slowly than a literal N-deep priority chain, not at constant cost. It works only if at most one select is active (all-zero selects give 0).
The reader. The hardware still multiplexes, but the chain hides its priority and resists review. A case or an indexed array says the same thing plainly.
case mux covers three of four select values and has no default. What does synthesis build for the missing value?A latch. The output must hold its old value, so the tool infers storage. The pitfall above shows the two one-line fixes.
When the selects are already one-hot (arbiter grants, decoded enables). It avoids converting one-hot to binary and back and has shallow depth. It requires the one-hot guarantee, so add an assertion for it.
Do | Avoid |
|---|---|
Give a | Letting a missing select hold state and grow a latch |
Use the AND-OR form only for one-hot selects | Feeding it overlapping selects and hoping the OR sorts it out |
Reach for the array-index form as N grows | Nesting ternary operators past one level |
All three styles plus the testbench that proves they agree for binary select values.
// 1) ternary chain: compact for 2:1, grows unreadable beyond that
module mux4_ternary (input logic [7:0] a, b, c, d, input logic [1:0] sel, output logic [7:0] y);
assign y = (sel == 2'd0) ? a :
(sel == 2'd1) ? b :
(sel == 2'd2) ? c : d;
endmodule
// 2) case statement: clear, easy to extend, the usual choice
module mux4_case (input logic [7:0] a, b, c, d, input logic [1:0] sel, output logic [7:0] y);
always_comb begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule
// 3) array index: scales to any N and is easy to parameterize
module mux4_index (input logic [7:0] a, b, c, d, input logic [1:0] sel, output logic [7:0] y);
logic [7:0] in [4];
always_comb begin
in[0] = a; in[1] = b; in[2] = c; in[3] = d;
y = in[sel];
end
endmodule
sel is 2'bxx.Plain case matches no item and takes default, so it returns d. The array index has no known entry, so y is unknown. Each ternary comparison with an unknown selector is unknown, so the chain merges a, b, c and d bitwise: bits where all four agree stay known and the rest are unknown.