Verilog Code for a 4:1 MUX: 3 Styles + Testbench

A 4:1 multiplexer routes one of four data inputs (d0–d3) to a single output y, chosen by a 2-bit select bus sel. Below is complete, simulator-ready Verilog code for a 4:1 mux written three ways — behavioral (case), dataflow (assign), and structural (gate-level) — each paired with a self-checking testbench that drives all four select lines and compares the output against a golden reference. Every block compiles and runs under Icarus Verilog, and the simulation log you see is the real output.

The three coding styles (copy, compile, run)

All three modules share the same ports: a 4-bit input bus d, a 2-bit sel, and a 1-bit output y. Pick whichever style your course or RTL standard expects — they are logically identical and synthesize to the same hardware.

1. Behavioral style (case statement)

// mux4to1_case.v  -- 4:1 MUX, behavioral (case) style + self-checking testbench

module mux4to1_case (
    input  wire [3:0] d,    // data inputs d0..d3
    input  wire [1:0] sel,  // 2-bit select
    output reg        y      // output
);
    always @(*) begin
        case (sel)
            2'b00: y = d[0];
            2'b01: y = d[1];
            2'b10: y = d[2];
            2'b11: y = d[3];
            default: y = 1'b0;
        endcase
    end
endmodule

module tb_mux4to1_case;
    reg  [3:0] d;
    reg  [1:0] sel;
    wire       y;
    integer    s, t, errors;
    reg        expected;

    mux4to1_case dut (.d(d), .sel(sel), .y(y));

    initial begin
        errors = 0;
        $display("time sel  d[3:0]  y  exp");
        // Drive all 4 select lines; for each, test several data patterns
        for (s = 0; s < 4; s = s + 1) begin
            for (t = 0; t < 4; t = t + 1) begin
                sel = s[1:0];
                case (t)
                    0: d = 4'b0000;
                    1: d = 4'b1111;
                    2: d = 4'b1010;
                    3: d = 4'b0101;
                endcase
                #5;                 // let combinational logic settle
                expected = d[s];    // golden reference
                $display("%4t  %b   %b    %b  %b", $time, sel, d, y, expected);
                if (y !== expected) begin
                    errors = errors + 1;
                    $display("  ** MISMATCH at sel=%b d=%b : got %b expected %b", sel, d, y, expected);
                end
            end
        end
        if (errors == 0)
            $display("PASS: all 16 vectors matched across every select line.");
        else
            $display("FAIL: %0d mismatch(es).", errors);
        $finish;
    end
endmodule
time sel  d[3:0]  y  exp
   5  00   0000    0  0
  10  00   1111    1  1
  15  00   1010    0  0
  20  00   0101    1  1
  25  01   0000    0  0
  30  01   1111    1  1
  35  01   1010    1  1
  40  01   0101    0  0
  45  10   0000    0  0
  50  10   1111    1  1
  55  10   1010    0  0
  60  10   0101    1  1
  65  11   0000    0  0
  70  11   1111    1  1
  75  11   1010    1  1
  80  11   0101    0  0
PASS: all 16 vectors matched across every select line.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp5_qxk_1s/mux4to1_case.v:54: $finish called at 80 (1s)

2. Dataflow style (assign with ternary)

// mux4to1_assign.v  -- 4:1 MUX, dataflow (assign) style + self-checking testbench

module mux4to1_assign (
    input  wire [3:0] d,    // data inputs d0..d3
    input  wire [1:0] sel,  // 2-bit select
    output wire       y      // output
);
    // Nested conditional (ternary) operators select one of four inputs.
    assign y = sel[1] ? (sel[0] ? d[3] : d[2])
                      : (sel[0] ? d[1] : d[0]);
endmodule

module tb_mux4to1_assign;
    reg  [3:0] d;
    reg  [1:0] sel;
    wire       y;
    integer    s, t, errors;
    reg        expected;

    mux4to1_assign dut (.d(d), .sel(sel), .y(y));

    initial begin
        errors = 0;
        $display("time sel  d[3:0]  y  exp");
        for (s = 0; s < 4; s = s + 1) begin
            for (t = 0; t < 4; t = t + 1) begin
                sel = s[1:0];
                case (t)
                    0: d = 4'b0001;
                    1: d = 4'b0010;
                    2: d = 4'b0100;
                    3: d = 4'b1000;
                endcase
                #5;
                expected = d[s];
                $display("%4t  %b   %b    %b  %b", $time, sel, d, y, expected);
                if (y !== expected) begin
                    errors = errors + 1;
                    $display("  ** MISMATCH at sel=%b d=%b : got %b expected %b", sel, d, y, expected);
                end
            end
        end
        if (errors == 0)
            $display("PASS: dataflow mux routed d[sel] correctly for all select lines.");
        else
            $display("FAIL: %0d mismatch(es).", errors);
        $finish;
    end
endmodule
time sel  d[3:0]  y  exp
   5  00   0001    1  1
  10  00   0010    0  0
  15  00   0100    0  0
  20  00   1000    0  0
  25  01   0001    0  0
  30  01   0010    1  1
  35  01   0100    0  0
  40  01   1000    0  0
  45  10   0001    0  0
  50  10   0010    0  0
  55  10   0100    1  1
  60  10   1000    0  0
  65  11   0001    0  0
  70  11   0010    0  0
  75  11   0100    0  0
  80  11   1000    1  1
PASS: dataflow mux routed d[sel] correctly for all select lines.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpqh0optou/mux4to1_assign.v:47: $finish called at 80 (1s)

3. Structural style (gate-level)

// mux4to1_struct.v  -- 4:1 MUX, structural (gate-level) style + self-checking testbench

module mux4to1_struct (
    input  wire [3:0] d,    // data inputs d0..d3
    input  wire [1:0] sel,  // 2-bit select
    output wire       y      // output
);
    wire ns0, ns1;          // inverted select lines
    wire a0, a1, a2, a3;    // per-input AND terms

    not u_n0 (ns0, sel[0]);
    not u_n1 (ns1, sel[1]);

    and u_a0 (a0, d[0], ns1, ns0);   // sel = 00
    and u_a1 (a1, d[1], ns1, sel[0]);// sel = 01
    and u_a2 (a2, d[2], sel[1], ns0);// sel = 10
    and u_a3 (a3, d[3], sel[1], sel[0]);// sel = 11

    or  u_or (y, a0, a1, a2, a3);    // combine the one active term
endmodule

module tb_mux4to1_struct;
    reg  [3:0] d;
    reg  [1:0] sel;
    wire       y;
    integer    s, errors;
    reg        expected;

    mux4to1_struct dut (.d(d), .sel(sel), .y(y));

    initial begin
        errors = 0;
        $display("time sel  d[3:0]  y  exp");
        // One-hot data: only d[sel] is high, so a correct mux must output 1.
        for (s = 0; s < 4; s = s + 1) begin
            sel = s[1:0];
            d   = (4'b0001 << s);   // drive the selected line high
            #5;
            expected = d[s];
            $display("%4t  %b   %b    %b  %b", $time, sel, d, y, expected);
            if (y !== expected) begin
                errors = errors + 1;
                $display("  ** MISMATCH at sel=%b d=%b : got %b expected %b", sel, d, y, expected);
            end
            // Inverse one-hot: selected line low, others high -> output must be 0.
            d = ~(4'b0001 << s);
            #5;
            expected = d[s];
            $display("%4t  %b   %b    %b  %b", $time, sel, d, y, expected);
            if (y !== expected) begin
                errors = errors + 1;
                $display("  ** MISMATCH at sel=%b d=%b : got %b expected %b", sel, d, y, expected);
            end
        end
        if (errors == 0)
            $display("PASS: gate-level mux selected d[sel] on every select line.");
        else
            $display("FAIL: %0d mismatch(es).", errors);
        $finish;
    end
endmodule
time sel  d[3:0]  y  exp
   5  00   0001    1  1
  10  00   1110    0  0
  15  01   0010    1  1
  20  01   1101    0  0
  25  10   0100    1  1
  30  10   1011    0  0
  35  11   1000    1  1
  40  11   0111    0  0
PASS: gate-level mux selected d[sel] on every select line.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpuya1g926/mux4to1_struct.v:59: $finish called at 40 (1s)

How to run these files

Each block is a single self-contained file holding both the design and its testbench. With Icarus Verilog installed, compile and simulate in two commands:

iverilog -g2005 -o sim mux4to1_case.v
vvp sim

Swap the filename for the dataflow or structural version. The -g2005 flag selects the Verilog-2005 standard, which every snippet here targets.

Line-by-line walkthrough

Behavioral (case). The output y is declared reg because it is assigned inside an always block. The sensitivity list @(*) means the block re-evaluates whenever any input changes, so the logic stays purely combinational. The case (sel) maps each of the four select codes to one data bit, and the default branch guards against x/z on sel during simulation — it also prevents an accidental latch by making sure y is always assigned.

Dataflow (assign). Here y is a wire driven continuously. The nested ternary reads as: if sel[1] is 1 pick from the upper half (d3/d2), otherwise the lower half (d1/d0); the inner sel[0] then picks within that half. This mirrors how a mux is often built from two layers of 2:1 selects and is the most compact of the three.

Structural (gate-level). This spells out the classic sum-of-products: invert both select bits, form one 3-input AND per data line so exactly one AND term can be high for a given sel, then OR the four terms. It is the closest to the gate schematic and is useful when a course asks you to show primitive instantiation rather than inferred logic.

The self-checking testbench. Every testbench instantiates the design under test (dut), then loops the select value s from 0 to 3 so all four select lines are driven. For each select it applies data patterns, waits #5 for the combinational output to settle, and computes the expected answer with the one-line golden model expected = d[s] — indexing the data bus by the select value. The !== comparison (case-equality) catches x and z, not just 0/1 mismatches. An errors counter decides whether the run prints PASS or FAIL, so you never have to eyeball the waveform.

Common mistakes to avoid

  • Declaring y as wire in the behavioral style. Any signal assigned inside an always block must be reg. The opposite is also true: a signal driven by assign must be a wire.
  • Using @(sel) instead of @(*). If d is missing from the sensitivity list, simulation ignores data changes and your output looks “stuck,” even though synthesis would build correct hardware. Always use @(*) for combinational logic.
  • Dropping the default branch. Without it, an unhandled sel value can infer an unintended latch and hide x propagation during simulation.
  • Comparing with == in the checker. == returns x when either side is unknown, so a buggy design can slip through. Use !==/=== in self-checking testbenches.
  • Not waiting before sampling. Reading y in the same timestep you change the inputs can capture the old value. A small delay (#5) or an event-based wait lets the combinational output settle first.
  • Mismatched port widths. sel must be 2 bits for four inputs; a 1-bit sel only reaches d0 and d1.

Quick check

Why must y be a reg in the behavioral version but a wire in the dataflow version?

Signals assigned inside an always block must be reg (procedural assignment), while signals driven by a continuous assign statement must be wire. reg here does not mean a hardware flip-flop — the logic is still combinational.

How many bits wide must sel be for a 4:1 mux, and why?

Two bits. Two select lines give 2² = 4 combinations (00, 01, 10, 11), exactly enough to choose one of the four data inputs.

What does the testbench line expected = d[s] represent?

It is the golden reference model: indexing the data bus by the current select value gives the bit a correct mux should output, which is then compared against the design’s actual output.

Why use !== instead of == when checking the output?

!== is case-inequality: it compares 0, 1, x, and z exactly. Plain == yields x when an operand is unknown, so an x on the output could pass the check undetected.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD