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.