Skip to content
VLSI Mentor

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 onthe rising edge (samples d)latch: en=0 → q_latch HOLDS its last value (level-sensitive)latch: en=0 → q_latch HOLDSits last value(level-sensitive)logic: y_logic follows inputs continuously (no storage)logic: y_logic followsinputs continuously (nostorage)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.

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.