A latch is what happens when a branch does not say what to do
Lesson 5's rule for combinational logic was: every signal a procedural block assigns needs a value for every possible path through the block. An if with no else, or a case that does not cover every input combination, breaks that rule in a specific, easy-to-miss way: for the input values that fall through unhandled, the only thing the tool can honestly do is keep the signal's previous value โ which is exactly what a latch does. A synthesis tool reading an incomplete conditional does not raise an error; it infers a latch to hold that stale value, silently, because nothing in the code forbids it.
module bad_latch (input wire sel, input wire a, input wire b, output reg y);
always @* begin
if (sel)
y = a;
// no else -- when sel is 0, y must "remember" its old value
end
endmodule
Running this exact module through Yosys's synthesis flow produces a real warning, not a silent pass:
Warning: Latch inferred for signal `\bad_latch.\y' from
process `\bad_latch.$proc$bad_latch.v:2$1'
and the resulting netlist contains one cell: $_DLATCH_P_ โ an actual level-sensitive latch, not the plain combinational logic the module was presumably meant to describe. Adding the missing branch is the entire fix, and it changes what gets synthesized completely:
module good_mux (input wire sel, input wire a, input wire b, output reg y);
always @* begin
if (sel)
y = a;
else
y = b;
end
endmodule
Synthesizing this version produces no warning at all, and the resulting netlist contains one cell: $_MUX_ โ a plain multiplexer, with no latch anywhere in the design. The two modules differ by exactly one else clause; the hardware they produce differs by an entire storage element.
Reading a real synthesis latch warning
The warning text above follows a pattern worth being able to read on sight, since it is close to what every major synthesis tool prints, Yosys included: it names the exact signal the latch was inferred for (\bad_latch.\y) and the exact process that caused it (the always block at a specific source location). When a synthesis log reports an unintended latch, the fix is almost never to rewrite the logic from scratch โ it is to find the specific branch of the specific if or case the warning names and ask what value the named signal should take on the path that falls through unhandled.
The same rule extends naturally to a case statement: a case with a default branch covering every value the selector could ever take is complete, the same way good_mux's else made the if complete; a case missing a branch for some reachable value, with no default to catch it, infers a latch for exactly the same reason an incomplete if does. A default branch is cheap insurance even when every case value looks covered by hand โ it is also what keeps the block complete if a signal's width is widened later and a value that used to be unreachable becomes reachable.
casez and casex: useful, and a real hazard
casez and casex are variants of case built for a situation a plain case cannot handle: matching a pattern against only some of a selector's bits, treating the rest as don't-care โ the kind of priority logic a priority encoder needs, where a higher-priority bit being set should match regardless of what the lower bits are doing. casez treats z in the case item (written ? or z) as a wildcard; casex treats both x and z in the case item as a wildcard.
The hazard is specific to casex, and it is real enough that most style guides ban the construct outright: casex treats an x in the signal being switched on, not just in the case items, as a wildcard too โ which means a genuinely unknown bit on a real signal can silently match a pattern instead of correctly reporting "unknown."
sel=1xx casex=1 (treats x as don't-care -- MASKS a real unknown) plain-case=0 (correctly unknown)
sel=001 casex=3 plain-case=3 (both agree on a real, known value)
With sel genuinely unknown in its lower two bits (1xx), casex confidently matches the 3'b1?? pattern and reports 1 โ exactly the wrong outcome for a testbench meant to catch unknowns, since it hides the very problem the test was trying to surface. A plain case on the same input correctly falls through every listed pattern (none of them equal 1xx literally) into default, reporting 0 as a visible "this did not match anything," which is the honest answer. casez alone โ don't-care only in the case items, never in the signal being tested โ covers the legitimate priority-encoder use case without this hazard, and is the only one of the two this course uses.
Common mistakes
The most common mistake is adding a default branch that assigns nothing, written as an empty default: ; out of habit โ this is still an incomplete assignment for the signal in question and still infers a latch, since "do nothing" is not the same as "assign a defined value." The second is assuming a full case on a selector automatically means every output signal in that block is covered โ if the block assigns two different signals and only one of them appears in every branch, the other one still infers its own latch, independently. The third is reaching for casex out of habit when casez was what was actually needed โ the two look nearly identical on the page and behave identically for every input except a genuinely unknown one, which is exactly the input a test is least likely to try on purpose and most likely to see for free from an uninitialized signal.
Interview questions
Why does a missing else in combinational logic infer a latch? For the input case where no branch applies, the only defined behavior a tool can give the signal is to hold its previous value, which is exactly what a latch does. Adding the missing branch (or a default in a case) removes the undefined path and the latch along with it.
What does a synthesis tool's latch warning actually tell you? It names the specific signal and the specific procedural block where a path through the logic left that signal unassigned. The fix is to find the named branch and add the value that signal should take there โ not to restructure the block from scratch.
Why is casex considered risky compared to casez? casex treats an x on the signal being switched on as a wildcard, not just in the case items โ a genuinely unknown input bit can then silently match a pattern instead of correctly producing an unknown result, hiding exactly the condition a testbench most needs to catch. casez only treats don't-cares in the case items as wildcards and does not have this hazard.
Key takeaways
- An
ifwith noelse, or acasemissing a branch (and nodefault), leaves a signal undefined on some input โ synthesis fills the gap with a latch, confirmed here by a real Yosys warning and a$_DLATCH_P_cell. - Adding the missing branch is the fix, and it is a complete fix: the same module, with an
elseadded, synthesizes to a plain$_MUX_with no warning at all. - A synthesis latch warning names the exact signal and exact process โ read it to find the specific unhandled branch, rather than rewriting the block.
- Every signal a block assigns needs its own complete coverage; a
casethat is complete for one output signal does not automatically cover a second signal assigned in the same block. casextreatsxin the tested signal itself as a wildcard, which can mask a genuinely unknown input;casezonly wildcards the case items and does not have this hazard.