How to Build a 16-Channel Digital Multiplexer on a Low-Cost FPGA

Ever tried to read a dozen sensor signals with a tiny microcontroller and hit the wall of limited pins? That’s the exact moment a 16‑channel multiplexer becomes a lifesaver, and with a low‑cost FPGA you can get the speed and flexibility of hardware without breaking the bank.

Why a 16-Channel Mux?

A multiplexer (or “mux”) is simply a switch that lets you pick one of many inputs and send it to a single output. In my first hobby project – a DIY weather station – I needed to read eight temperature probes and four humidity sensors. My MCU only had ten free pins, so I built a 4‑to‑1 mux with a 74HC157. It worked, but the extra logic chips cluttered the board and added latency.

When I moved to an FPGA on a $5 development board, I realized I could replace all those discrete chips with a single programmable block. A 16‑channel mux on an FPGA gives you:

  • More channels without extra wiring.
  • Fast switching – the FPGA can toggle selections in a few nanoseconds.
  • Re‑configurability – change the number of channels or add logic on the fly.

What You Need

ItemWhy
Low‑cost FPGA board (e.g., Lattice iCE40 or Xilinx Artix‑7 starter kit)Small, cheap, and has enough I/O for 16 inputs + control
16 digital input signals (logic‑level, 0‑3.3 V)The sources you want to multiplex
One 4‑bit selector (can be from a microcontroller or internal FSM)Chooses which input is passed through
One output pin (to MCU, ADC, or another block)Carries the selected data
Simple PCB or breadboard, jumpers, power supplyFor wiring everything together
Development software (IceStorm, Vivado, or open‑source toolchain)To write and load the HDL code

All of these can be found in a typical hobbyist kit. No need for exotic parts.

Step 1: Sketch the Logic on Paper

Before you open the IDE, draw a tiny block diagram. You have 16 inputs (I0 … I15), a 4‑bit selector (S3‑S0), and one output (Y). The truth is simple:

Y = I[S]   // where S is the binary number formed by S3..S0

In hardware terms, you need a decoder that turns the 4‑bit selector into a one‑hot 16‑bit signal, then a multiplexer tree that routes the chosen input to Y.

Step 2: Write the Verilog (or VHDL)

I prefer Verilog for quick prototypes. Here’s a clean version you can copy into your project:

module mux16 (
    input  wire [15:0] in,   // 16 parallel inputs
    input  wire [3:0]  sel,  // 4‑bit selector
    output wire        out   // selected output
);
    // One‑hot decode of selector
    wire [15:0] sel_onehot = 16'b1 << sel;

    // AND each input with its one‑hot line, then OR them together
    assign out = |(in & sel_onehot);
endmodule

A few notes for newcomers:

  • wire [15:0] in is a bundle of 16 single‑bit signals. If your inputs are wider (e.g., 8‑bit ADC data) you just replicate the module for each bit.
  • 16'b1 << sel shifts a single ‘1’ left by the selector value, creating a one‑hot word where only the chosen line is high.
  • The | operator reduces the 16‑bit result to a single bit by OR‑ing all bits together.

If you like VHDL, the same idea translates directly – just replace the shift and reduction operators with the appropriate VHDL syntax.

Step 3: Add a Simple Testbench

Before you flash the FPGA, verify the logic in simulation. A tiny testbench saves hours of debugging.

module tb_mux16;
    reg  [15:0] in;
    reg  [3:0]  sel;
    wire        out;

    mux16 uut (.in(in), .sel(sel), .out(out));

    initial begin
        // Apply a pattern where each input is its index LSB
        in = 16'b1010101010101010; // alternating 1s and 0s
        repeat (16) begin
            #10 sel = sel + 1;
            $display("sel=%b out=%b", sel, out);
        end
        $finish;
    end
endmodule

Run this with your chosen simulator (I use iverilog + gtkwave). You should see out follow the bit pattern of the selected input.

Step 4: Map the Pins

Every FPGA board has a pin‑out file (often a .pcf for Lattice or .xdc for Xilinx). Assign the 16 input pins, the 4 selector pins, and the output pin to physical pins that match your wiring.

Example for an iCE40 board (PCF format):

set_io in[0]  10
set_io in[1]  11
...
set_io in[15] 25
set_io sel[0] 30
set_io sel[1] 31
set_io sel[2] 32
set_io sel[3] 33
set_io out    40

Double‑check the board’s schematic to avoid conflicts with power or clock pins.

Step 5: Synthesize, Place, and Route

Run the toolchain:

yosys -p "synth_ice40 -top mux16 -json mux16.json" mux16.v
nextpnr-ice40 --json mux16.json --pcf board.pcf --asc mux16.asc
icepack mux16.asc mux16.bin

If you’re on a Xilinx board, replace the commands with vivado -mode batch steps. The tools will tell you if any timing constraints are missed. For a simple 16‑input mux, the timing is usually well within the FPGA’s capability.

Step 6: Load the Bitstream

Use the board’s programmer (USB‑Blaster, FTDI, or built‑in USB) to flash the .bin (or .bit) file. Most low‑cost boards have a tiny utility like iceprog or openFPGALoader.

iceprog mux16.bin

Watch the LED blink (if your board has one) – that’s a good sign the FPGA accepted the new configuration.

Step 7: Wire It Up

Now the fun part. Connect your 16 sensor lines to the input pins you assigned. Hook the selector lines to a microcontroller GPIO or to a small state machine inside the FPGA if you want autonomous scanning.

A quick way to drive the selector is to use a binary counter that increments every few milliseconds. That way each input gets a turn, and you can read the output with a single ADC channel.

// Example Arduino sketch to drive the selector
uint8_t sel = 0;
void setup() {
  for (int i=2; i<6; i++) pinMode(i, OUTPUT); // pins 2‑5 as selector
}
void loop() {
  for (sel=0; sel<16; sel++) {
    for (int i=0; i<4; i++) digitalWrite(2+i, (sel>>i)&1);
    delay(5); // give FPGA time to settle
    int value = analogRead(A0); // read multiplexed output
    // process value...
  }
}

You’ll see the ADC reading change as each sensor is selected. If you need higher speed, tighten the delay or use hardware SPI to shift out the selector bits.

Step 8: Add Optional Features

The beauty of an FPGA is that you can stack extra logic without extra chips. Some ideas:

  • Debounce the inputs if they come from mechanical switches.
  • Add a small FIFO to buffer the selected data before sending it out.
  • Implement a simple UART inside the FPGA to stream all 16 channels directly to a PC.

All of these can be added as separate modules and wired to the same selector.

Common Pitfalls and How to Avoid Them

ProblemFix
Glitches on the output when selector changesInsert a one‑clock‑cycle register after the mux to synchronize the output.
Input voltage levels don’t match FPGA I/OUse level shifters or voltage dividers to bring signals into the 0‑3.3 V range.
Timing warnings during synthesisReduce the clock speed or add a small pipeline stage inside the mux.
Pin assignment conflictsDouble‑check the board’s reference manual; some pins are reserved for configuration.

Wrap‑Up

Building a 16‑channel digital multiplexer on a low‑cost FPGA is a great way to learn both digital logic and practical board design. You replace a handful of chips with a single programmable device, gain speed, and keep the layout tidy. Follow the steps above, tweak the design to fit your own project, and you’ll have a flexible, reusable mux that can grow with your ideas.

Reactions
Do you have any feedback or ideas on how we can improve this page?