Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
    • Combinational Timing Overview
    • Muxes, Decoders and Encoders
      • Mux Theory and Design Patterns
      • A Parameterized Mux
      • Binary Decoder and One-Hot Enables
      • Priority Encoder
    • Arithmetic Blocks
    • Comparators and Shifters
    • Tri-State and Shared Buses
    • Latch Inference
    • Arithmetic Datapaths
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Muxes, Decoders and Encoders
  1. Topics
  2. RTL Design
  3. Combinational Design
  4. Muxes, Decoders and Encoders
  5. Mux Theory and Design Patterns

Part 2 · Combinational · Intermediate

Mux Theory and Design Patterns

A 4:1 mux written three ways, with select rules and a testbench.

On this page

    Why this matters

    A case with a missing branch quietly turns a mux into a latch, and casez or casex can match a request that is not there. The coding style you pick decides both the hardware and what the simulator hides.

    After this lesson you can: build an N:1 mux as a tree of 2:1 muxes, choose between case, array and AND-OR forms, and explain unique, priority, casez and casex.

    What a mux is

    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⌉.

    A 4:1 mux as a tree: two 2:1 muxes share sel[0], a third uses sel[1]. The highlighted path is sel = 2, which delivers c.

    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).

    Coding styles

    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.

    case

    Small N where each input has a meaning

    Always include default (or cover all values) to avoid a latch.

    Array index

    Parameterized N, register files, tables

    Index out of range returns x in simulation; guard it.

    case, casez, casex, unique and priority

    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.

    req_decode.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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
    endmodule
    Check yourself Can a unique 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.

    AND-OR form

    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).

    Common mistakes

    Common pitfall

    In a case without default that does not cover every select value, the output keeps its previous value on the missing cases, which synthesizes a latch. Use default, or assign a default value before the case.

    Interview

    Likely question: “Build an 8:1 mux from 2:1 muxes.”

    • Tree of seven 2:1 muxes in three levels.

    • Depth grows as the ceiling of log2 of N.

    • Write it as a case with default, or an indexed array.

    Check yourself You write an 8:1 mux as a chain of seven ternary operators. What breaks first: the hardware or the reader?

    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.

    Check yourself A 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.

    Check yourself When is the AND-OR mux form better than a binary-select mux?

    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 case a default or assign outputs first

    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

    Testbench

    All three styles plus the testbench that proves they agree for binary select values.

    mux4_ternary.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 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
    
    Check yourself Unknown selector: predict each style output when 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.

    Remember

    • An N:1 mux is a tree of 2:1 muxes with depth ceil(log2 N).

    • Plain case with a default is the safe choice. Avoid casex, because X and Z in the selector become wildcards.

    • AND-OR form is for selects that are already one-hot; with no select active it gives 0.

    Rule of thumb: give every case a default, or assign the output before it.

    Keep going

    • Practice: Write a 2:1 multiplexer in SystemVerilog

    • Code lab: Build a 4-bit 4:1 mux

    • Interview: run a mock interview on this topic

    Related topics

    • Parameterized mux

    • Latch inference

    Previous

    40 / 350

    Next: A Parameterized Mux

    Up next

    Up nextA Parameterized Mux

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Binary Decoder and One-Hot Enables
    • Priority Encoder
    clksel0123a1b2c3d4y1234
    A 4:1 mux stepping through every select value