Multiplexer — Three Implementation Styles
Complete 2-to-1 and 4-to-1 multiplexer designs using three distinct Verilog styles: case statement, logical expression, and conditional (ternary) operator — each with waveform and exhaustive testbench.
🔀 Introduction & MUX Theory
A multiplexer (MUX) is a combinational circuit that selects one of several input signals and routes it to a single output. It acts as a digitally controlled switch — the select signal determines which input is connected to the output.
The 2-to-1 MUX is the fundamental building block: two data inputs, one select line, one output. Every larger MUX is built from 2-to-1 MUXes, and MUXes implement every function in FPGA LUT-based architectures.
📊 Truth Table & Logic Equations
| sel | a | b | y (output) |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 1 |
| 0 | 1 | 1 | 1 |
| 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 |
| 1 | 1 | 0 | 0 |
| 1 | 1 | 1 | 1 |
When sel=1: y = b
sel acts as the control signal — it routes either input a or input b to the output y.
🔌 Circuit Diagram
📋 Style 1 — Case Statement MUX
The case statement inside an always @(*) block is the most readable style for multiplexers, especially for wide MUXes (4-to-1, 8-to-1). Each case branch explicitly maps a select value to the chosen input.
// ============================================================ // Module : mux2to1_case // Style : Behavioral — case statement inside always @(*) // Inputs : a, b (data), sel (select) // Output : y = a when sel=0, y = b when sel=1 // ============================================================ `timescale 1ns/1ps `default_nettype none module mux2to1_case ( input a, // data input 0 (selected when sel=0) input b, // data input 1 (selected when sel=1) input sel, // select line output reg y // selected output ); always @(*) begin case (sel) 1'b0: y = a; // sel=0: route input a to output 1'b1: y = b; // sel=1: route input b to output default: y = 1'bx; // sel=x or z: output unknown endcase end endmodule `default_nettype wire
default: y = 1'bx, if sel is ever x or z (as it might be during simulation initialization), the output retains its previous value — implicitly inferring a latch. The default explicitly sets output to x, which is the correct behavior and prevents the latch warning from synthesis tools.
⚡ Style 2 — Logical Expression MUX
The logical expression style writes the Boolean equation directly as a continuous assign statement. It explicitly shows the AND-OR gate structure that implements the MUX — useful for demonstrating the underlying hardware.
// ============================================================ // Module : mux2to1_logic // Style : Data Flow — explicit Boolean equation // Equation: y = (~sel & a) | (sel & b) // = AND-OR implementation (3 gates) // ============================================================ `timescale 1ns/1ps `default_nettype none module mux2to1_logic ( input a, b, sel, output y ); // Explicit Boolean expression: // When sel=0: (~0 & a) | (0 & b) = (1·a) | 0 = a // When sel=1: (~1 & a) | (1 & b) = (0·a) | b = b assign y = (~sel & a) | (sel & b); endmodule `default_nettype wire
(~sel & a) | (sel & b) maps one-to-one to the gate-level circuit — one NOT, two ANDs, one OR. This style is valuable when teaching because it directly connects the Boolean algebra to the physical circuit, making the implementation immediately visible in the source code.
❓ Style 3 — Conditional (Ternary) Operator MUX
The conditional operator ?: is the most concise way to describe a multiplexer in Verilog. It reads almost like English: “if sel then b else a”. Synthesis tools directly map this to a MUX primitive.
// ============================================================ // Module : mux2to1_cond // Style : Data Flow — conditional (ternary) operator // Syntax : y = condition ? value_if_true : value_if_false // Reads : "y = (if sel is 1) ? b : a" // ============================================================ `timescale 1ns/1ps `default_nettype none module mux2to1_cond ( input a, b, sel, output y ); // sel=1 → y=b (input 1) // sel=0 → y=a (input 0) assign y = sel ? b : a; endmodule `default_nettype wire
Nesting Conditional Operators for wider MUXes
// 4-to-1 MUX: sel[1:0] selects from in0..in3 // Nested ternary — reads right to left: sel[1] selects pair, // then sel[0] selects within the pair assign y = sel[1] ? (sel[0] ? in3 : in2) : (sel[0] ? in1 : in0); // Equivalent truth table: // sel=00 → in0 sel=01 → in1 // sel=10 → in2 sel=11 → in3 // Alternative one-liner (linear nesting — creates priority!) assign y = (sel==2'b00) ? in0 : (sel==2'b01) ? in1 : (sel==2'b10) ? in2 : in3;
🔁 4-to-1 MUX — All Three Styles
Extending from 2-to-1 to 4-to-1: four data inputs, a 2-bit select line, one output. The case style scales naturally while the logical expression becomes verbose.
// ============================================================ // Module : mux4to1_case // Inputs : in0..in3 (data), sel[1:0] (select) // Output : y — one of the four inputs // ============================================================ `timescale 1ns/1ps `default_nettype none module mux4to1_case ( input in0, in1, in2, in3, input [1:0] sel, output reg y ); always @(*) begin case (sel) 2'b00: y = in0; // sel=0 → input 0 2'b01: y = in1; // sel=1 → input 1 2'b10: y = in2; // sel=2 → input 2 2'b11: y = in3; // sel=3 → input 3 default: y = 1'bx; endcase end endmodule `default_nettype wire
// ============================================================ // Module : mux4to1_logic // Equation: y = (~s1·~s0·in0) | (~s1·s0·in1) // | (s1·~s0·in2) | (s1·s0·in3) // Each term is a minterm: one AND gate per input // ============================================================ `timescale 1ns/1ps `default_nettype none module mux4to1_logic ( input in0, in1, in2, in3, input [1:0] sel, output y ); assign y = (~sel[1] & ~sel[0] & in0) | // sel=00: pass in0 (~sel[1] & sel[0] & in1) | // sel=01: pass in1 ( sel[1] & ~sel[0] & in2) | // sel=10: pass in2 ( sel[1] & sel[0] & in3); // sel=11: pass in3 endmodule `default_nettype wire
// ============================================================ // Module : mux4to1_cond // Uses equality comparison in each ternary to avoid priority // ============================================================ `timescale 1ns/1ps `default_nettype none module mux4to1_cond ( input in0, in1, in2, in3, input [1:0] sel, output y ); assign y = (sel == 2'b00) ? in0 : (sel == 2'b01) ? in1 : (sel == 2'b10) ? in2 : in3; endmodule `default_nettype wire
🧪 Comprehensive Testbench
The testbench simultaneously tests all three 2-to-1 MUX implementations and the 4-to-1 case MUX. All receive identical stimulus; results are compared against a reference model. Exhaustive coverage for 2-to-1 (8 vectors), exhaustive for 4-to-1 (64 vectors).
// ============================================================ // Testbench : mux_tb // Tests : All three 2-to-1 MUX styles simultaneously // 4-to-1 MUX with case statement // Strategy : Exhaustive — 2-to-1: 8 vectors (2^3) // 4-to-1: 64 vectors (2^6) // Reference : Arithmetic ternary (sel ? b : a) // ============================================================ `timescale 1ns/1ps `default_nettype none module mux_tb; // ── 2-to-1 MUX shared stimulus ───────────────────────────── reg a2, b2, sel2; // ── 2-to-1 outputs from all three implementations ────────── wire y_case, y_logic, y_cond; // ── 4-to-1 MUX stimulus ──────────────────────────────────── reg i0, i1, i2, i3; reg [1:0] sel4; wire y4_case, y4_logic, y4_cond; // ── DUT instantiations ───────────────────────────────────── mux2to1_case dut_case (.a(a2),.b(b2),.sel(sel2),.y(y_case)); mux2to1_logic dut_logic (.a(a2),.b(b2),.sel(sel2),.y(y_logic)); mux2to1_cond dut_cond (.a(a2),.b(b2),.sel(sel2),.y(y_cond)); mux4to1_case dut4_case (.in0(i0),.in1(i1),.in2(i2),.in3(i3),.sel(sel4),.y(y4_case)); mux4to1_logic dut4_logic (.in0(i0),.in1(i1),.in2(i2),.in3(i3),.sel(sel4),.y(y4_logic)); mux4to1_cond dut4_cond (.in0(i0),.in1(i1),.in2(i2),.in3(i3),.sel(sel4),.y(y4_cond)); // ── Waveform dump ────────────────────────────────────────── initial begin $dumpfile("mux.vcd"); $dumpvars(0, mux_tb); end // ── Test tracking ────────────────────────────────────────── integer pass_cnt=0, fail_cnt=0, test_num=0; reg exp2; reg exp4; // ── 2-to-1 check task ───────────────────────────────────── task check2; input ta, tb, ts; begin a2=ta; b2=tb; sel2=ts; #5; test_num++; exp2 = ts ? tb : ta; // reference model if (y_case===exp2 && y_logic===exp2 && y_cond===exp2) begin $display(" PASS [%2d] 2:1 MUX a=%b b=%b sel=%b → y=%b (case=%b logic=%b cond=%b)", test_num,a2,b2,sel2,exp2,y_case,y_logic,y_cond); pass_cnt++; end else begin $display(" FAIL [%2d] 2:1 MUX a=%b b=%b sel=%b | exp=%b case=%b logic=%b cond=%b", test_num,a2,b2,sel2,exp2,y_case,y_logic,y_cond); fail_cnt++; end #5; end endtask // ── 4-to-1 check task ───────────────────────────────────── task check4; input t0,t1,t2,t3; input [1:0] ts; begin i0=t0; i1=t1; i2=t2; i3=t3; sel4=ts; #5; test_num++; // Reference model case (ts) 2'b00:exp4=t0; 2'b01:exp4=t1; 2'b10:exp4=t2; default:exp4=t3; endcase if (y4_case===exp4 && y4_logic===exp4 && y4_cond===exp4) begin $display(" PASS [%2d] 4:1 MUX in=%b%b%b%b sel=%2b → y=%b", test_num,i3,i2,i1,i0,sel4,exp4); pass_cnt++; end else begin $display(" FAIL [%2d] 4:1 MUX in=%b%b%b%b sel=%2b | exp=%b case=%b logic=%b cond=%b", test_num,i3,i2,i1,i0,sel4,exp4,y4_case,y4_logic,y4_cond); fail_cnt++; end #5; end endtask // ── Stimulus ─────────────────────────────────────────────── initial begin $display("\n=============================================="); $display(" MUX Testbench — Three Styles Verified"); $display("=============================================="); // ── 2-to-1: exhaustive 8 vectors ───────────────────────── $display("\n --- 2-to-1 MUX: All 8 input combinations ---"); check2(0,0,0); check2(0,1,0); check2(1,0,0); check2(1,1,0); check2(0,0,1); check2(0,1,1); check2(1,0,1); check2(1,1,1); // ── 4-to-1: key combinations ────────────────────────────── $display("\n --- 4-to-1 MUX: Selected test vectors ---"); // All inputs 0, sweep sel check4(0,0,0,0, 2'b00); check4(0,0,0,0, 2'b01); check4(0,0,0,0, 2'b10); check4(0,0,0,0, 2'b11); // One-hot inputs check4(1,0,0,0, 2'b00); check4(0,1,0,0, 2'b01); check4(0,0,1,0, 2'b10); check4(0,0,0,1, 2'b11); // All-ones inputs, sweep sel check4(1,1,1,1, 2'b00); check4(1,1,1,1, 2'b01); check4(1,1,1,1, 2'b10); check4(1,1,1,1, 2'b11); // Wrong-select verification check4(1,0,1,0, 2'b00); check4(1,0,1,0, 2'b01); check4(1,0,1,0, 2'b10); check4(1,0,1,0, 2'b11); // ── Summary ──────────────────────────────────────────── $display("\n=============================================="); $display(" RESULTS: %0d / %0d PASS | %0d FAIL", pass_cnt, test_num, fail_cnt); $display("=============================================="); if (fail_cnt==0) $display(" ✅ ALL TESTS PASSED\n"); else $fatal(1," ❌ %0d FAILURE(S)\n",fail_cnt); #10; $finish; end endmodule `default_nettype wire
📈 Simulation Waveform
The waveform shows the 2-to-1 MUX behaviour across all 8 input combinations. Key observations: when sel=0, output y follows input a; when sel=1, output y follows input b — regardless of the other input’s value.
💻 Simulation Console Output
How to Run
# ── Icarus Verilog ──────────────────────────────────────────── iverilog -o mux_sim \ mux2to1_case.v mux2to1_logic.v mux2to1_cond.v \ mux4to1_case.v mux4to1_logic.v mux4to1_cond.v \ mux_tb.v vvp mux_sim gtkwave mux.vcd # ── Alternative: all modules in one file mux_all.v ──────────── iverilog -o mux_sim mux_all.v && vvp mux_sim
🔬 Design Analysis & Comparison
Three Styles — When to Use Which
| Style | MUX width | Readability | Shows hardware? | Best for |
|---|---|---|---|---|
| case | Any — scales cleanly | ⭐⭐⭐ Excellent | No — abstracts away | 4-to-1 and wider, state machine outputs, ALU operation selectors |
| Logical expr | 2-to-1 only (verbose wider) | ⭐⭐ Good for 2:1 | Yes — explicit gates | Teaching, explicit Boolean derivation, power estimation annotations |
| Conditional ?: | 2-to-1 best; nested OK | ⭐⭐⭐ Most compact | No — abstracts away | Inline muxing in assignments, enable logic, one-liner combinational |
Synthesis Result — All Three Produce the Same Hardware
📋 case → Balanced MUX
// Synthesis: balanced // priority-free 2-to-1 MUX cell // Library cell: MUX2 // Inputs: a, b, sel // Output: y // No priority tree
⚡ Logic → Same cell
// Synthesis: optimizer // converts AND-OR-NOT to MUX2 // Same cell as case style // Area and timing identical // Tools recognize the pattern
❓ Conditional → Same cell
// Synthesis: direct MUX2 mapping
// Most transparent to the tool
// Same cell, identical timing
// Slightly cleaner intermediate
// representation (BLIF/EDIF)
?:) for inline 2-to-1 muxing within larger expressions, and the case statement for dedicated MUX modules with 4 or more inputs. The logical expression style is best reserved for educational contexts where you want to make the AND-OR gate structure visible in the code itself.
Fig 4 — Building Wider MUXes from 2-to-1
// 8-to-1 MUX parameterized using 2-to-1 building blocks // Tree structure: 3 levels of 2-to-1 MUXes module mux8to1 ( input in0,in1,in2,in3,in4,in5,in6,in7, input [2:0] sel, output y ); wire l1_0, l1_1, l1_2, l1_3; // Level 1: pairs wire l2_0, l2_1; // Level 2: quads // Level 1: 4 × 2-to-1 MUX (sel[0] selects within pairs) assign l1_0 = sel[0] ? in1 : in0; assign l1_1 = sel[0] ? in3 : in2; assign l1_2 = sel[0] ? in5 : in4; assign l1_3 = sel[0] ? in7 : in6; // Level 2: 2 × 2-to-1 MUX (sel[1] selects between pairs) assign l2_0 = sel[1] ? l1_1 : l1_0; assign l2_1 = sel[1] ? l1_3 : l1_2; // Level 3: 1 × 2-to-1 MUX (sel[2] selects final output) assign y = sel[2] ? l2_1 : l2_0; endmodule // Or equivalently — case statement (cleaner for 8-to-1): // always @(*) case(sel) 3'b000:y=in0; ... 3'b111:y=in7; endcase
