VHDL · Chapter 6.5 · Combinational Logic Design
Multiplexers
The multiplexer is the workhorse of combinational logic. It selects one of several inputs and routes it to the output under the control of a select signal, and almost every datapath is built from muxes. VHDL gives you four natural ways to write one. The when-else and if forms build a priority mux, while the with-select and case forms build a balanced mux, and indexing an array by an integer gives a clean wide mux. This lesson maps each style to the hardware it builds, shows when priority versus balanced selection matters, and applies the default-then-override discipline so a mux never accidentally latches. Choosing the right width and selection shape is the only real design decision.
Foundation13 min readVHDLMultiplexerSelectionPriority MuxDatapathRTL
1. Engineering intuition — route one of many
A multiplexer is a switch: many inputs, one output, and a select that decides which input gets through. It is the basic act of choosing in hardware — feed a register from one of several sources, pick an ALU result, steer a bus. Because choosing is so common, VHDL offers several syntaxes for it, but they all describe the same primitive: a selection network from N inputs to one output. The only real design decisions are the width (N) and whether the selection is priority (ordered) or balanced (value-based).
2. Formal explanation — four ways, two shapes
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- PRIORITY mux (ordered conditions): when/else (concurrent) or if (process).
y1 <= a when s1 = '1' else b when s2 = '1' else c; -- first true wins
-- BALANCED mux (one selector value): with/select (concurrent) or case (process).
with sel select
y2 <= d0 when "00", d1 when "01", d2 when "10", d3 when others;
-- WIDE mux by ARRAY INDEX: index a vector-of-vectors with an integer (numeric_std).
y3 <= data_array(to_integer(unsigned(sel))); -- clean N:1 muxwhen/else and if build a priority mux (a chain of 2:1 muxes, order = precedence);
with/select and case build a balanced mux (a flat one-of-N decode). Indexing an array by an
integer is the most concise wide mux. All are combinational; keep them latch-free (a final else /
when others / complete index range, plus defaults in a process).
3. Production RTL — datapath source select
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- 4:1 datapath mux feeding a register input, written as a balanced case in a process.
src_mux : process (all)
begin
mux_out <= (others => '0'); -- default → latch-free
case src_sel is
when "00" => mux_out <= alu_result;
when "01" => mux_out <= mem_data;
when "10" => mux_out <= imm_value;
when others => mux_out <= reg_data;
end case;
end process;What hardware does this become? A 4:1 multiplexer selecting the register-input source — src_sel
drives the mux select, and the four data sources are its inputs. The default plus full case coverage
guarantees no latch. (Written as mux_out <= data_array(to_integer(unsigned(src_sel))) it is the same
mux, more concisely, when the sources are collected into an array.)
4. Hardware interpretation — N:1 selection
5. Simulation interpretation — output follows the select
A mux is combinational, so its output continuously equals the currently-selected input — change the select or the selected input and the output follows after a delta.
A 4:1 mux: the select value routes the matching source to the output
8 cycles6. Debugging example — priority where balanced was meant (and a latch)
Expected: a value-based 4:1 mux. Observed: either subtly wrong selection, or an inferred latch.
Root cause: writing a value decode as an if/elsif ladder makes it a priority mux — fine if
conditions are exclusive, but it adds depth and, if the final else is missing, leaves a path
unassigned → latch. Fix: use case/with/select for value-based selection (balanced, exhaustive),
or ensure the if ladder has a final else. Engineering takeaway: match the construct to the
selection — case/with/select for value decode, when/else/if for ranked conditions — and always
close it (default / else / others) so the mux cannot latch.
-- RISK: value decode as a priority ladder, missing final else → latch + extra depth.
-- y <= d0 when sel="00" else d1 when sel="01" else d2 when sel="10"; -- no else → latch
-- FIX: balanced, exhaustive selection.
with sel select
y <= d0 when "00", d1 when "01", d2 when "10", d3 when others; -- balanced, no latch7. Common mistakes & what to watch for
- Missing final
else/when others. Leaves a path unassigned → latch. Always close the mux. - Using a priority style for value-based selection.
case/with/selectis balanced and clearer; reserveif/when/elsefor ranked conditions. - Index out of range on an array mux.
data_array(to_integer(unsigned(sel)))must haveselcover exactly the array range; an out-of-range index is an error. - Forgetting defaults in a process-style mux. Default the output at the top to stay latch-free.
- Very wide priority muxes. A long
ifladder has deep logic; a balancedcase/array index is faster and smaller for wide selection.
8. Engineering insight & continuity
The multiplexer is selection distilled, and writing one well is mostly about two choices: balanced
versus priority, and keeping it latch-free. Use case/with/select (or an array index) for the common
value-based mux, reserve priority styles for genuinely ranked conditions, and always close the
selection so no path is unassigned. With muxes — the route-one-of-many primitive — in hand, the next
lesson covers the complementary primitives that expand and compress encodings: Decoders and
Encoders, where a binary value fans out to one-hot (decode) or a one-hot/priority input compresses to
binary (encode).