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
| Item | Why |
|---|---|
| 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 supply | For 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] inis 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 << selshifts 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
| Problem | Fix |
|---|---|
| Glitches on the output when selector changes | Insert a one‑clock‑cycle register after the mux to synchronize the output. |
| Input voltage levels don’t match FPGA I/O | Use level shifters or voltage dividers to bring signals into the 0‑3.3 V range. |
| Timing warnings during synthesis | Reduce the clock speed or add a small pipeline stage inside the mux. |
| Pin assignment conflicts | Double‑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.
- → Step-by-Step Guide: Selecting the Ideal Video Wall Controller for Small-Scale Digital Signage @walltechinsights
- → Build a 4-bit Binary Calculator Using Only NAND Gates @gatecraft
- → How to Reduce Power Consumption in FPGA Designs: A Step-by-Step Guide for Embedded Engineers @fpgainsights
- → Digital Literacy 101: How to Safely Navigate Online Banking for Beginners @techmadesimple
- → Step-by-step guide to building a 4-bit binary counter with basic ICs @logiclab