VHDL · Chapter 16.3 · Synthesis and RTL Implementation
Inferring Flip-Flops, Latches, and Logic
Synthesis does not guess what hardware to build; it reads your RTL patterns and follows one rule: storage is inferred whenever a value must be remembered, that is, whenever it is not reassigned on every activation. Three patterns follow from this. A clocked process that assigns a signal on a rising clock edge infers an edge-triggered flip-flop, plus an asynchronous reset if you test reset before the edge. A combinational process that fails to assign a signal on some path must hold its old value, so it infers an unintended level-sensitive latch, which is almost always a bug. A combinational process that assigns on every path infers pure logic with no storage. This lesson shows the exact patterns that produce flip-flops, latches, and logic, and how to get the one you intend.
Foundation15 min readVHDLSynthesisFlip-FlopLatchInferenceRTL
1. Engineering intuition — storage means "must remember"
Synthesis does not guess; it follows one principle. If, for some activation of your logic, a signal is not given a new value, then the hardware must remember its old one — and remembering requires a storage element. So whether you get a flip-flop, a latch, or no storage at all comes down to when and whether a signal is assigned. A clocked process deliberately remembers between clock edges → flip-flop. A combinational process that forgets to assign on some path is forced to remember → latch (which you almost never wanted). A combinational process that always assigns never needs to remember → pure logic. Read your code through "is there an activation where this isn't assigned?" and you can predict the primitive every time.
2. Formal explanation — the three inference patterns
-- (1) FLIP-FLOP: clocked process, assignment under rising_edge → edge-triggered register.
process (clk) begin
if rising_edge(clk) then q <= d; end if; -- remembers d between edges → FF
end process;
-- with ASYNC reset (tested BEFORE the edge):
process (clk, rst) begin
if rst = '1' then q <= '0'; -- async reset
elsif rising_edge(clk) then q <= d; end if; -- FF with async reset
end process;
-- (2) LATCH (usually UNINTENDED): combinational process, signal NOT assigned on every path → it must HOLD.
process (all) begin
if en = '1' then y <= d; end if; -- no else → y holds when en='0' → LEVEL-SENSITIVE LATCH
end process;
-- (3) PURE LOGIC: combinational process/concurrent with FULL assignment → gates, NO storage.
process (all) begin
y <= d when en = '1' else '0'; -- assigned on EVERY path → combinational logic
end process;The rule in code: clocked + assign-under-edge → flip-flop (async reset if tested before the edge); combinational + missing assignment on a path → latch (the signal must hold); combinational + assigned on every path → logic (no storage). Inference is entirely about whether a value can go un-reassigned.
3. Production usage — getting the primitive you intend
-- WANT A REGISTER: clocked process (intended storage).
process (clk) begin if rising_edge(clk) then state <= next_state; end if; end process;
-- WANT PURE COMBINATIONAL LOGIC: assign on every path → NO latch. Use a default (6.4).
process (all) begin
y <= (others => '0'); -- DEFAULT assignment first → covers all paths
case sel is
when "00" => y <= a;
when "01" => y <= b;
when others => null; -- default already assigned → no latch
end case;
end process;
-- ACCIDENTAL LATCH (avoid): incomplete combinational assignment.
-- process (all) begin if sel="00" then y <= a; end if; end process; -- y holds otherwise → latch!What hardware does this become? The clocked state process becomes a flip-flop bank (the register you
wanted). The defaulted combinational process becomes a multiplexer / logic with no storage, because y is
assigned on every path. Drop the default and forget a branch, and the same process silently becomes a latch —
extra, level-sensitive storage that creates timing and reliability problems. The lever is entirely in your hands:
clock a process to get a register, fully assign a combinational one to get logic, and never leave a
combinational path unassigned unless you genuinely want a latch.
4. Structural interpretation — three patterns, three primitives
5. Simulation interpretation — flip-flop vs latch vs logic on one input
Same d and enable: edge FF (clocked) vs level latch vs transparent logic
8 cycles6. Debugging example — the accidental latch
Expected: a combinational signal becomes pure logic. Observed: the synthesis report warns of an
inferred latch (or timing fails on a level-sensitive path), even though you intended no storage. Root
cause: in a combinational process the signal is not assigned on every path — a missing else, an
uncovered case branch, or a conditional that leaves it unset — so the hardware must hold the old value,
which synthesis implements as a level-sensitive latch (the rule: un-reassigned ⇒ storage). Fix: assign the
signal on every path — add a default assignment at the top of the process (or an else/when others) so
no activation leaves it unset; for intended storage, use a clocked process instead. Engineering takeaway:
an inferred latch means a combinational signal can go un-reassigned — default-assign every output (or cover all
branches) to get logic, and clock the process when you actually want a register.
-- BUG: y unassigned when en='0' → holds → latch inferred.
-- process (all) begin if en='1' then y <= d; end if; end process;
-- FIX: default assignment covers all paths → pure logic, no latch.
process (all) begin y <= '0'; if en='1' then y <= d; end if; end process;7. Common mistakes & what to watch for
- Incomplete combinational assignment. A signal unassigned on any path infers a latch; default-assign or cover every branch.
- Wanting a register but writing combinational. Storage between cycles needs a clocked process; a combinational one cannot hold intentionally without becoming a latch.
- Async reset placement. For an async-reset FF, test reset before the
rising_edge; testing it after changes the inference. - Assuming the tool 'knows' your intent. Inference is purely pattern-based — write the exact pattern for the primitive you want.
- Ignoring latch warnings. A reported inferred latch is almost always a bug; fix the assignment rather than suppressing the warning.
8. Engineering insight & continuity
Inference reduces to one rule — storage is created when a value must be remembered — yielding three patterns: a clocked, assign-under-edge process → flip-flop; a combinational process with a missing assignment → unintended latch; a fully-assigned combinational process → pure logic. Control the primitive by controlling whether (and when) the signal is reassigned: clock for registers, default-assign for logic. This is the synthesis view of Modules 6 and 7. Knowing what each pattern infers leads to writing it well — the next lesson, RTL Coding Style for Synthesis, covers the idioms and habits that produce clean, predictable, high-quality hardware.
Standards & specifications
- Governing standard
- IEEE Std 1076 (VHDL)(opens IEEE in a new tab)
Defines the VHDL language — types, the simulation cycle, and the semantics a conforming analyser and simulator must implement. Synthesis restrictions and vendor coding rules are tool behaviour, not language rules.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the VHDL curriculum.
