VHDL · Chapter 6.3 · Combinational Logic Design
Unintended Latches
An unintended latch is the bug that catches every VHDL engineer at least once. In a combinational process, if any path leaves an output unassigned, the language has no choice but to keep that output's previous value, and remembering a value is memory, so synthesis infers a latch where you intended pure logic. A latch is a level-sensitive storage element, and the result is a circuit that is harder to time, transparent to glitches, and a headache for static timing analysis. This lesson explains exactly how incomplete if and case bodies and missing defaults create latches, why latches are genuinely problematic, how to detect them in simulation and synthesis, and how complete assignment makes them impossible.
Foundation14 min readVHDLLatchCombinationalSynthesisDebuggingRTL
1. Engineering intuition — "keep the old value" means memory
Combinational logic must define its output for every input combination. If your code says "when this
condition holds, set the output" but never says what happens otherwise, you have left a gap — and VHDL
fills that gap with the only sensible rule it has: the output keeps whatever it had before. But
keeping a previous value is remembering, and remembering is memory. So that innocent missing else
silently asks for a storage element. Synthesis obliges with a latch, even though you were writing
combinational logic.
2. Formal explanation — what a latch is and why it appears
A latch is a level-sensitive memory: while its enable is active it is transparent (output follows
input), and when the enable goes inactive it holds the last value. Synthesis infers one whenever a
combinational process can reach end process with an output not assigned on the current path — the
"hold" behaviour is exactly a latch with an enable formed from the conditions that did assign it.
-- Incomplete: 'q' is assigned only when en='1'. When en='0', q must HOLD → latch.
process (all)
begin
if en = '1' then q <= d; end if; -- no else → q holds when en='0' → a transparent latch
end process;
-- This is, in fact, the textbook description of a D-latch: q follows d while en is high, holds when low.The cause is always the same: an output unassigned on some path. The common sources are a missing
else, a case without when others (or not covering an output in some branch), and an output set in
only some branches of an if/case.
3. Hardware interpretation — why latches are a problem
4. Simulation interpretation — the output that holds stale
In simulation the symptom is an output that stops tracking its inputs under the unassigned condition — it freezes at its last value instead of behaving as a function of the current inputs. A correct combinational output would follow the inputs continuously; the latched one holds.
Latched output (holds) vs correct combinational output (tracks inputs)
8 cycles5. Detection — how to catch latches early
Three signals reliably reveal an unintended latch:
-- 1) Synthesis/lint WARNING: "inferred latch for signal q" — read and act on these, never ignore.
-- 2) RTL-vs-gate mismatch: the output holds in gate sim where RTL (or intent) said it should not.
-- 3) Code smell: a combinational process where an output is NOT assigned on every path
-- (missing else, case without 'others', output set in only some branches).The most reliable habit is to treat every "inferred latch" warning as an error and to scan combinational processes for outputs lacking a top-of-process default.
6. Debugging example — the incomplete case
Expected: a combinational decoder. Observed: synthesis warns of an inferred latch on y, and
y holds stale values for some selectors. Root cause: the case assigned y in only three of its
branches (or lacked when others), so the uncovered selector value left y unassigned → latch.
Fix: assign y a default at the top of the process (or in every branch) and add when others.
Engineering takeaway: an inferred-latch warning on a combinational output almost always means an
incomplete if/case — default the output and cover every case.
-- BUG: 'y' not assigned for sel="11" → latch.
process (all) begin
case sel is
when "00" => y <= a; when "01" => y <= b; when "10" => y <= c;
end case; -- missing "11"/others AND no default → latch
end process;
-- FIX: default-then-override (and full coverage).
process (all) begin
y <= (others => '0'); -- default → every path drives y
case sel is
when "00" => y <= a; when "01" => y <= b; when "10" => y <= c;
when others => null; -- default already drove y
end case;
end process;7. Common mistakes & what to watch for
- Missing
else/ missing default. The classic cause; default every output at the top of a combinational process. casewithout full coverage. Addwhen othersand ensure every output is set (or defaulted) on every branch.- Output assigned in only some branches. Even with
when others, an output set in just a few branches latches on the rest unless defaulted. - Ignoring synthesis latch warnings. They are almost never benign in combinational logic — treat them as errors.
- Confusing an intended latch with an unintended one. Genuine latches are rare in modern RTL; if you see one inferred, assume it is a bug until proven deliberate.
8. Engineering insight & continuity
The unintended latch is not a deep mystery — it is the language's literal response to incompletely specified combinational logic: leave a path unassigned and it must remember, and remembering is a latch. That makes the cure equally simple and mechanical: assign every output on every path, which in practice means default-then-override at the top of every combinational process. Latches matter because they undermine timing closure and pass glitches, so a clean RTL flow treats every inferred latch as a defect. The next lesson, Default Assignments and Latch Avoidance, turns this cure into a disciplined, always-applied pattern and shows how it scales across multi-output blocks, FSM outputs, and complex decodes.