Digital Design / Module 3: Verilog that means hardware
16. Accidental latches
Stop a very common synthesis surprise.
See it move
The idea
Combinational logic has no memory, so its output must be defined for every possible input. If your code leaves a signal unassigned on some path, it has to keep its old value. Keeping a value requires storage, so the tool builds a .
always @(*) begin
if (en) q = d; // what happens when en is 0?
endNothing in this block says what q is when en is 0. So it must hold, and that is a latch. You probably wanted a mux or a flop.
Why it is a problem
Latches are transparent while enabled, which complicates timing analysis, and an unintended one usually signals a logic bug. Simulation may look fine because nothing exercised the hold path, and the surprise arrives only at synthesis.
Two fixes
Give a default at the top of the block. This is the most reliable habit:
always @(*) begin
y = 1'b0; // default on every path
case (s)
2'b00: y = a;
2'b01: y = b;
endcase
endOr make sure every branch assigns every output, with else and default everywhere. With several outputs in a large block, the defaults method is much harder to get wrong.
Spotting them
Read the synthesis log: tools report inferred latches. Using always_comb and a tool turns this into an early warning instead of a late surprise.
Common mistake
Adding default to the case but forgetting that a block has several outputs. Each output needs a value on every path. If y is assigned in every branch but z in only one, z still becomes a latch.
Key ideas
- Missing assignments on a path imply memory.
- Assign defaults at the top of the block.
- Complete case or a default closes the gap.
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
always @(*) begin
y = 1'b0;Why it failed
Two values of s leave y unassigned, so y has to remember its value, and remembering takes a latch. The fix is a default at the top of the block, so that every path assigns y. Later lines can then overrule it.
Interview questions
coreHow is a latch inferred by accident?
A combinational block leaves a signal unassigned on some path.
basicIs always @(*) if (en) q = d; a flop, a latch or a gate?
It is a latch. When en is 0 the code says nothing, so q must keep its value, which needs storage. There is no clock edge, so the storage is level-sensitive: transparent while en is 1 and holding while en is 0.
basicName two ways to stop an always @(*) block from inferring a latch.
First, assign a default value to every output at the top of the block, so every path has an answer. Second, make every branch assign every output, with an else for each if and a default for each case. Defaults at the top are the more reliable habit when a block has several outputs.
coreThis block has a default for y and a default case branch. Is it free of latches?
always @(*) begin
y = 1'b0;
case (s)
2'b00: begin y = a; z = 1'b1; end
2'b01: y = b;
default: y = 1'b0;
endcase
endNo. y is covered, but z is only assigned in the 2'b00 branch. For any other select value z must keep its old value, so a latch is inferred for z. Add z = 1'b0; (or a suitable value) at the top of the block next to y's default.
coreA case on a 2-bit select lists all four values, assigns the output in each, and has no default. Does synthesis infer a latch?
Typically no. Every combination of 0s and 1s is covered, so the output is defined on every path that real hardware can take. In simulation, an x on the select matches no branch and the output holds its old value, so adding a default is still good practice; it also protects the code if the select is widened later.
coreIs always @(posedge clk) if (en) q <= d; a latch, since q is not assigned when en is 0?
No, it is a flop with an enable. In a clocked block every signal holds between edges anyway, so leaving q unassigned when en is 0 simply means the flop keeps its value. That is built with a mux in front of the flop, or an enable flop cell. The latch rule applies only to combinational blocks.
coreA designer silences a latch warning by writing else y = y;. Does that fix it?
No. Assigning y to itself still means y keeps its old value when that branch is taken, which still needs storage, so it is still a latch; it has only been made explicit. The fix is to give y a real value on that path, such as 0 or another input.
advancedWhy do unintended latches make timing analysis harder than flops do?
A flop captures only at an edge, so every path starts and ends at a clean point in time. A latch is transparent while enabled, so a path can pass straight through it and borrow time from the next stage, and the tool must analyse paths across the latch. An unintended latch also often has a gating signal that was never designed as a clock, so its timing may not be properly constrained.
advancedIs assign q = en ? d : q; a latch?
assign q = en ? d : q;
Yes, in effect. When en is 0 the output feeds back to itself, so it holds its value, and simulation shows it holding. It is built as a combinational loop through a mux, not as a proper latch cell, so timing tools see a loop and lint tools typically flag it. If storage is wanted, describe it explicitly as a latch or, better, as a flop.