In short: An 8-to-1 mux is one function, Y = I[S], and the case statement, the indexed assign and a balanced conditional-operator tree all synthesize to the same 3-level mux structure, while a flat priority chain of comparisons risks a slower, larger netlist unless the tool recovers it.
One function, three coding styles
An 8-to-1 multiplexer has eight data inputs, a 3-bit select bus and a single output. The behaviour never changes: with S = 3'b101 the output follows I5. What changes between coding styles is how much structure you hand to the synthesis tool, and how easy it is to introduce a bug the simulator will not show you. If you want the gate-level theory first, read our page on the 8 to 1 multiplexer; here we focus on RTL.
Style 1: the case statement
This is the form most teams standardise on because it reads like the truth table. Note the blocking assignments and the @(*) sensitivity list; both matter, as explained in blocking vs non-blocking assignments.
module mux8_case (
input [7:0] i,
input [2:0] s,
output reg y
);
always @(*) begin
case (s)
3'd0: y = i[0];
3'd1: y = i[1];
3'd2: y = i[2];
3'd3: y = i[3];
3'd4: y = i[4];
3'd5: y = i[5];
3'd6: y = i[6];
3'd7: y = i[7];
default: y = 1'bx;
endcase
end
endmodule
Because y is assigned inside a procedural block it must be declared reg, even though no flip-flop results; see reg vs wire in Verilog. The default branch is unreachable for two-state values but protects simulation when s carries X or Z, and the X assignment tells synthesis it may treat that case as don't-care.
Key fact: a case with all eight values covered, or a default branch, creates no latch. Delete both and a missing branch infers a latch that holds the old value of y.
Style 2: dataflow with assign
The shortest version uses a variable bit-select on a vector:
assign y = i[s];
That single line is legal, synthesizable and exactly equivalent to the case version. A more explicit dataflow form is the AND-OR sum of products, with each term gated by a decoded select:
assign y = (i[0] & ~s[2] & ~s[1] & ~s[0])
| (i[1] & ~s[2] & ~s[1] & s[0])
| (i[2] & ~s[2] & s[1] & ~s[0])
/* ... terms for i[3] to i[6] ... */
| (i[7] & s[2] & s[1] & s[0]);
Here y is a plain wire. The AND-OR form maps to eight 4-input AND terms feeding an 8-input OR in generic logic, but tools re-extract the mux function and map it to whatever the library offers, so it rarely produces different silicon. It does cost readability, and one mistyped inversion produces a bug that is hard to spot.
Style 3: the conditional operator
The ?: operator nests naturally, and the nesting you choose is your hint to the tool. Splitting on one select bit per level gives a balanced tree:
assign y = s[2] ? (s[1] ? (s[0] ? i[7] : i[6])
: (s[0] ? i[5] : i[4]))
: (s[1] ? (s[0] ? i[3] : i[2])
: (s[0] ? i[1] : i[0]));
The tempting alternative compares the full select against a constant at every step:
assign y = (s == 3'd0) ? i[0] :
(s == 3'd1) ? i[1] :
(s == 3'd2) ? i[2] : /* ... */ i[7];
Written this way, each stage reads as "only if the previous ones failed", which is a priority structure. Because the conditions are mutually exclusive, Design Compiler, Genus, Vivado and Yosys usually prove that and flatten the chain, but you are relying on an optimisation instead of stating your intent. In a design with timing pressure, that is a gamble you do not need.
How each one synthesizes
On a 7-series Xilinx or UltraScale part, a LUT6 has six inputs, which is exactly one 4:1 mux (four data plus two selects). An 8:1 mux therefore takes two LUT6s plus one MUXF7, a dedicated 2:1 mux inside the slice that joins their outputs, so a single bit costs 2 LUTs and 1 MUXF7 and still adds almost no routing delay. A 16:1 uses four LUTs, two MUXF7 and one MUXF8. A 32-bit-wide 8:1 bus costs about 64 LUTs. In an ASIC flow, Synopsys Design Compiler or Cadence Genus maps to MUX4 and MUX2 standard cells when the library has them, typically two MUX4 plus one MUX2, or seven MUX2 cells otherwise.
| Style | Declared type | Typical result | Risk |
|---|---|---|---|
| case | reg | Balanced mux tree | Latch if branches missing |
| assign i[s] | wire | Balanced mux tree | X-pessimism in sim if s is X |
| AND-OR assign | wire | Re-extracted to mux | Typos, poor readability |
| ?: per select bit | wire | Balanced mux tree | Parenthesis mistakes |
| ?: with == chain | wire | Chain, flattened if proven | Depth blow-up |
Key fact: all of these are functionally identical. Equivalence checking in Synopsys Formality or Cadence Conformal would pass them against each other; the differences are in QoR and in how safely each style survives edits.
Pitfalls and verification
- Incomplete sensitivity lists. Writing
always @(s)instead of@(*)makes simulation ignore changes oniwhile synthesis still builds a pure mux, so the waveform and the netlist disagree. Linting tools such as Synopsys SpyGlass flag this. - Latches. Any path through a combinational always block that skips the assignment to
yinfers a latch. Giveya default value at the top of the block if you cannot cover every branch. - X on the select. With
i[s], an X select returns X in simulation; a case statement drops to its default. Know which behaviour you want. - Mixing widths. If
sis declared 2 bits wide, a literal 3'd7 branch never matches and the top four inputs are unreachable without any warning from some simulators.
To check all 8 x 256 patterns cheaply, drive every select value against a random 8-bit input and compare to a reference expression i[s], then run the same bench against all three modules. Our guide to writing a Verilog testbench shows a self-checking structure that runs in Siemens Questa, Synopsys VCS or the free Icarus Verilog. Teams that need production-grade RTL reviews often bring in a design partner such as Avecas design services.
Key fact: in interviews, the expected answer to "which style is best?" is that the balanced case or indexed assign is clearest, and that you verify the synthesized netlist with equivalence checking, not by trusting the style.
Want to practise this on real FPGA boards and simulators? Join the hands-on Verilog and FPGA tracks in our CourseTron courses, or explore instructor-led VLSI training at ChipXpert.
Frequently asked questions
Can I use a casez or casex for an 8-to-1 mux?
You can, but there is no benefit. casez and casex exist for wildcard matching in decoders; a mux needs exact match on all three bits, and casex in particular can hide X-propagation bugs in simulation.
Does assign y = i[s] synthesize on every tool?
Yes. A variable bit-select on a vector is synthesizable in Vivado, Quartus, Design Compiler, Genus and Yosys, and maps to the same mux cells as an equivalent case statement.
Why is the output declared reg in the case version?
Verilog requires any variable assigned inside an always block to be a reg type. It describes a procedural variable, not a register, and the synthesized result is purely combinational.
How many LUTs does an 8-to-1 mux need?
On Xilinx 7-series and UltraScale devices, one bit takes two LUT6s plus one MUXF7. Other architectures differ, so check the synthesis utilisation report for your exact target.