Skip to content

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

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

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

clocked process to flip-flop, incomplete combinational to latch, full combinational to logicclocked + rising_edgeremembers between edges→ FLIP-FLOPedge-triggered registercomb, missingassignmentmust HOLD old value→ LATCH (unintended)level-sensitive storagecomb, full assignmentalways assigned→ LOGIC (no storage)gates / mux12
Synthesis infers a primitive from the assignment pattern, governed by one rule: storage is inferred when a value must be remembered. A clocked process that assigns under rising_edge remembers between edges and infers an edge-triggered flip-flop (with an asynchronous reset if reset is tested before the edge). A combinational process that fails to assign a signal on some path forces it to hold its previous value and infers an unintended level-sensitive latch. A combinational process that assigns the signal on every path needs no memory and infers pure logic. This is an inference-rule structure; the waveform below contrasts the three behaviors on the same input.

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 cycles
Same d and enable: edge FF (clocked) vs level latch vs transparent logicFF: q_ff updates only on the rising edge (samples d)FF: q_ff updates only …latch: en=0 → q_latch HOLDS its last value (level-sensitive)latch: en=0 → q_latch …logic: y_logic follows inputs continuously (no storage)logic: y_logic follows…clken11001100d10110100q_ff00011110q_latch10000111y_logic10000100t0t1t2t3t4t5t6t7
Three primitives from the same stimulus. The flip-flop samples d only at the rising edge and holds between edges. The latch is transparent while en=1 and holds its value when en=0 — level-sensitive, the accidental result of an incomplete combinational assignment. The pure logic tracks its inputs continuously with no memory. The difference is entirely in whether (and when) the value is reassigned — exactly what the inference rule keys on.

6. 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.

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