Skip to content

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.

how_a_latch_is_inferred.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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

unassigned path inferring a transparent latch with its problemsoutput assigned onsome paths onlyincomplete if/caseother paths → holdold valuelanguage keeps previousvalueinferred LATCHtransparent when enabled,holds otherwisetiming + glitchproblemsSTA pain,glitch-transparent,unintended12
How an unassigned path becomes a latch. A combinational process that assigns an output only under some conditions leaves the other paths with no assignment; the language holds the old value, which synthesis builds as a level-sensitive latch enabled by the assigning conditions. Latches are problematic: they are transparent while enabled (passing glitches), they complicate static timing analysis (timing through a transparent latch is hard to constrain), and they are often unintended — a sign the logic is incompletely specified rather than a deliberate storage choice.

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 cycles
Latched output (holds) vs correct combinational output (tracks inputs)en=0: q_latch HOLDS 7 (latch) while d changesen=0: q_latch HOLDS 7 …en=1: latch transparent again → follows den=1: latch transparen…en11001001d37925618q_latch37775558q_comb37925618t0t1t2t3t4t5t6t7
With the incomplete 'if en=1 then q<=d', q_latch follows d while en=1 but HOLDS its last value when en=0 — a transparent D-latch. The intended combinational output q_comb would track d at all times. The frozen-while-disabled behaviour is the simulation fingerprint of an unintended latch.

5. Detection — how to catch latches early

Three signals reliably reveal an unintended latch:

detection_checklist.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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.

case_latch_fix.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- 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.
  • case without full coverage. Add when others and 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.