FPGA

Verilog 8 to 1 MUX Case and Dataflow

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    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.

    8:1 mux: block view and truth table 8:1 MUX I[7:0] 8 S[2:0] Y S2 S1 S0 Y 0 0 0I0 0 0 1I1 0 1 0I2 0 1 1I3 1 0 0I4 1 0 1I5 1 1 0I6 1 1 1I7 CourseTron · coursetron.com
    Every coding style below must reproduce this truth table exactly.

    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.

    Priority chain Balanced tree S==0?S==1?S==2?S==6? Up to 7 comparator + mux stages in series if the tool does not flatten. 2:12:12:12:1 2:12:12:1 S[0]S[1]S[2] CourseTron · coursetron.com
    Seven 2:1 muxes in three levels beat a long comparison chain on depth, with the same total mux count.

    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.

    StyleDeclared typeTypical resultRisk
    caseregBalanced mux treeLatch if branches missing
    assign i[s]wireBalanced mux treeX-pessimism in sim if s is X
    AND-OR assignwireRe-extracted to muxTypos, poor readability
    ?: per select bitwireBalanced mux treeParenthesis mistakes
    ?: with == chainwireChain, flattened if provenDepth 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.

    Select value sweeping 000 to 111 I0I1I2I3 I4I5I6I7 MUX Y S=000 → Y=I0 S=001 → Y=I1 S=010 → Y=I2 S=011 → Y=I3 S=100 → Y=I4 S=101 → Y=I5 S=110 → Y=I6 S=111 → Y=I7 CourseTron · coursetron.com
    The orange path shows the input currently routed to Y as the select value counts upward every half second.

    Pitfalls and verification

    • Incomplete sensitivity lists. Writing always @(s) instead of @(*) makes simulation ignore changes on i while 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 y infers a latch. Give y a 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 s is 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.

    Advertisement