Video 3 of 4 · ~12 minutes
Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF
“Zero inferred latches” is a line item on virtually every FPGA/ASIC design checklist. Synopsys DC, Xilinx Vivado, and Intel Quartus all emit latch warnings — which senior engineers treat as errors. Static timing analysis tools can't reliably analyze a design with latches, so they break your sign-off flow.
Every Topic 3+ lab checks for latch warnings during make stat. If Yosys reports “inferred latch,” your grader sees it. This video is your insurance policy: three independent techniques, each of which alone prevents the bug.
“I didn't write posedge clk, so there's no memory in my always @(*) block. Whatever I forgot to assign just stays undefined.”
Any signal that must retain a value through some path of your logic needs storage. If your always @(*) doesn't assign the output on some path, the synthesizer infers a level-sensitive latch to hold the prior value. No clock required — the latch watches your condition signal.
// Missing else → latch on y
always @(*) begin
if (sel) y = a;
// when sel=0: y must "hold" its previous value → LATCH
end
// Missing default + missing case → latch
always @(*) begin
case (opcode)
2'b00: y = a;
2'b01: y = b;
// 2'b10 and 2'b11 not assigned → LATCH
endcase
end
always @(*) begin
y = 4'b0; // default FIRST
if (sel) y = a;
// no latch!
end
always @(*) begin
if (sel) y = a;
else y = 4'b0;
end
always @(*) begin
case (opcode)
2'b00: y = a;
2'b01: y = b;
default: y = 4'b0;
endcase
end
if-without-else and missing-case in one line. I put it in every single always @(*) block I write.
Which of these always @(*) blocks will infer a latch?
// Block A
always @(*) begin
if (en) y = a;
else y = 0;
end
// Block B
always @(*) begin
case (sel)
2'b00: y = a;
2'b11: y = b;
endcase
end
// Block C
always @(*) begin
y = 0;
if (en) y = a;
end
You synthesize this code. What exact warning do you expect from Yosys?
always @(*) begin
case (opcode)
2'b00: result = a + b;
2'b01: result = a - b;
endcase
end
Warning: Latch inferred for signal \result\ in process...
Followed by: list of conditions under which the signal is not assigned (opcodes 2'b10 and 2'b11). Some synthesizers also report the estimated latch gate count.
~5 minutes
▸ COMMANDS
# 1. Buggy version (latch warnings):
cd lecture_examples/week1_day03/d03_s3_ex2/
make stat 2>&1 | grep -i 'latch\|infer'
# 2. Fixed version (default + complete case):
cd ../d03_s3_ex3/
make stat 2>&1 | grep -i 'latch\|infer'
# Diff the two source files to see the one-line fixes:
diff ../d03_s3_ex2/day03_ex01_latch_demo.v \
../d03_s3_ex3/day03_ex02_latch_fixed.v
▸ EXPECTED OUTPUT (Yosys 0.47)
# d03_s3_ex2 (buggy):
Latch inferred for signal
`\o_bug1' from process ...
Latch inferred for signal
`\o_bug2' from process ...
SCC pass: Found 8 SCCs ← combo loops!
=== latch_demo ===
SB_LUT4 12
# d03_s3_ex3 (fixed):
(no latch warnings, 0 SCCs)
=== latch_fixed ===
SB_LUT4 8
▸ KEY OBSERVATION — THE BUG IS HIDING
stat shows only SB_LUT4 in both versions — no latch cell appears. The buggy build just has 4 more LUTs (12 vs 8) wired into 8 combinational feedback loops (one per bit of o_bug1/o_bug2). Read the synthesis log for Latch inferred warnings and the SCC count — make stat alone won't save you.
Same module, buggy vs fixed (Yosys 0.47, synth_ice40):
Latch inferred for `\o_bug1'
Latch inferred for `\o_bug2'
SCC pass: Found 8 SCCs
SB_LUT4 12
No DFF, no SB_DFFE, no SB_DLATCH. The latch becomes 12 LUTs wired into 8 combinational feedback loops — one per output bit.
(no latch warnings)
SCC pass: Found 0 SCCs
SB_LUT4 8
Pure combinational. 8 LUTs, no feedback loops, clean STA.
$_DLATCH_P_ through latches_map.v into a mux + inverter, which ABC packs into LUTs with feedback. stat just shows SB_LUT4 — exactly what a combinational design looks like. The bug is hiding inside the LUT fabric.
Latch inferred and check the SCC count. A latch-free combinational module has 0 SCCs; every inferred latch bit adds one feedback loop.
Same Verilog, two silicon targets — radically different gates.
Standard-cell libraries ship a real DLATCH primitive (transmission-gate or NAND-SR internally). Synthesis maps the inferred latch to one characterized cell. STA still flags it — latches break sign-off — but the gate is real and visible in the netlist.
netlist: DLATCH_X1 — one cell, level-sensitive
No latch primitive in the fabric. Yosys lowers $_DLATCH_P_ via latches_map.v into Q = EN ? D : Q — a mux whose own output feeds an input. ABC packs that into an SB_LUT4. The latch is the combinational loop.
netlist: SB_LUT4 + 1 SCC per latched bit
DLATCH shouldn't be there). On iCE40 it disappears into LUT statistics that look identical to a clean combinational design — the only fingerprints left are the “Latch inferred” warning and the non-zero SCC count.
Paste this buggy code and ask AI: “Will this infer a latch on iCE40? If so, give me three different fixes.”
always @(*)
if (enable) out = data;
TASK
Ask AI: latch? What fixes?
BEFORE
Predict: 3 fixes — default at top, explicit else, rewrite with assign.
AFTER
Good AI identifies the latch and suggests all 3. Weak AI may miss the default-at-top pattern.
TAKEAWAY
Latch-hunting is a well-solved problem; AI is actually reliable here. Use it as a second pair of eyes.
① A latch appears when any input combination leaves an output unassigned.
② Three fixes: default at top, complete if/else, case with default.
③ Default at top is universal — use it always.
④ Grep synthesis logs for “latch” every session. Zero tolerance.
🔗 Transfer
Video 4 of 4 · ~6 minutes
▸ WHY THIS MATTERS NEXT
You've seen = (blocking) inside every always @(*) so far. Tomorrow we introduce <= (nonblocking) for sequential logic. Video 4 previews the rule: = for combinational, <= for sequential, never mix. Topic 4 will prove why, but you need the rule in memory before you get there.