"Why did I infer a latch?" — the warning, the cause, the two-line fix
It's the most-Googled synthesis warning in FPGA design:
WARNING: [Synth 8-327] inferring latch for variable 'y_reg'
Good news: the fix is mechanical once you see why it happens, and the habit that prevents it costs one line per block. Let's take it apart.
What the tool is trying to tell you
A combinational block (always @* in Verilog, process (all) in VHDL)
promises: outputs are pure functions of current inputs, no memory. The
tool holds you to it. If any path through the block leaves an output
unassigned, the semantics say "keep the old value" — and keeping an old
value requires memory. Level-sensitive memory, in this case: a latch,
transparent while the enabling condition holds.
always @* begin
if (sel)
y = a; // and when sel is 0... y holds. Latch!
end
You didn't ask for storage — you just forgot the else — but the tool
must build what the language semantics demand, so it builds a latch and
files the warning as its protest.
Why a latch is (almost always) unwanted on an FPGA
- Timing analysis gets much harder to pin down. Data races through a transparent latch, so paths aren't cleanly bounded by clock edges the way flip-flop paths are. Static timing on latch paths involves borrowing analysis most flows aren't set up for.
- FPGA fabric doesn't want to build one. The registers in the fabric are edge-triggered; a latch gets implemented via a LUT feedback path or an awkward primitive mode — glitch-prone and unloved by the tools.
- It simulates fine. That's the treacherous part: RTL simulation happily models the latch, so the bug sails through your testbench and surfaces as flaky hardware behavior later. (See it live in our latches vs flip-flops lesson — the deliberate latch wiggles right there on the waveform.)
The three classic causes
1. Incomplete if:
always @* begin
if (sel) y = a; // no else → latch
end
2. Incomplete case:
always @* begin
case (state) // 3 of 4 encodings covered → latch
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase // no default
end
3. The subtle one — assigning only some outputs on some paths:
always @* begin
y = a; // y is fine on every path...
if (mode) z = b; // ...but z latches when mode is 0
end
Multi-output blocks make cause 3 easy to miss in review, which is why the mechanical defense below beats vigilance.
The two-line fix (and habit)
Assign defaults at the top of the block, unconditionally:
always @* begin
y = 1'b0; // defaults first: every output,
z = 1'b0; // every path, guaranteed assigned
y = a;
if (mode) z = b;
end
Later assignments win (that's how blocking assignments in a
combinational block work), so the logic reads naturally and no path can
escape unassigned. For case, add default: even when you think you've
covered everything — encodings change.
In SystemVerilog, write always_comb instead of always @*: the
language then requires pure combinational behavior, upgrading the
warning to an error you can't ship. In VHDL-2008, the same discipline
applies to process (all) — assign every signal on every path.
And if you want the warning before you even open the tools: our
online Verilog linter runs Verilator's LATCH
check (among ~60 others) on pasted code, instantly. The
FSM generator writes latch-free state-machine
boilerplate for you — its default assignment idiom is exactly the habit
above.
When a latch is legitimate
Rarely, and deliberately: some low-power ASIC techniques, time-borrowing
designs, and a few vendor primitives use latches on purpose. If that's
you, you already knew, and you'll instantiate the primitive explicitly
rather than inferring it by accident. For everyone else: defaults first,
default: always, always_comb where you can — and the warning
disappears for good.