Day 3 · Procedural Combinational Logic

The Latch Problem

Video 3 of 4 · ~12 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

always @(*)if/else & caseLatch ProblemBlock vs Nonblock

🌍 Where This Lives

In Industry

“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.

In This Course

Every Day 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.

⚠️ When Memory Sneaks In

❌ Wrong Model

“I didn't write posedge clk, so there's no memory in my always @(*) block. Whatever I forgot to assign just stays undefined.”

✓ Right Model

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.

The failure mode: Latches work in simulation. They pass many testbenches. But they create unclocked storage — glitches on the condition signal propagate to the output, and static timing analyzers refuse to sign off. The bug hides until tapeout.

The Recipe for an Inferred Latch

// 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
The rule: If there exists any combination of inputs where your output is not assigned, the synthesizer must infer storage — it has no choice. Your “intent” doesn't matter; the tool can't read minds.

👁️ I Do — Three Ways to Prevent Latches

1. Default at top

always @(*) begin
    y = 4'b0;   // default FIRST
    if (sel) y = a;
    // no latch!
end

2. Complete if/else

always @(*) begin
    if (sel) y = a;
    else     y = 4'b0;
end

3. default in case

always @(*) begin
    case (opcode)
        2'b00: y = a;
        2'b01: y = b;
        default: y = 4'b0;
    endcase
end
My preference: Option 1 (default at top). It's the most defensive — it covers both if-without-else and missing-case in one line. I put it in every single always @(*) block I write.

🤝 We Do — Latch Hunt

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
Answers: A: NO latch (complete if/else). B: YES latch (cases 2'b01 and 2'b10 not covered → y holds). C: NO latch (default at top).

🧪 You Do — Predict the Warning

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
Expected: 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.
Habit: grep your synthesis logs for “latch” or “inferred” every run. One hit = one bug to fix.
▶ LIVE DEMO

Inducing & Fixing a Latch

~5 minutes

▸ COMMANDS

cd labs/week1_day03/ex3_latch_demo/
# 1. Buggy version:
make stat 2>&1 | grep -i 'latch\|infer'
# 2. Fix: add 'default:' line
sed -i 's|endcase|  default: y = 0;\n  endcase|' buggy.v
# 3. Re-synth:
make stat 2>&1 | grep -i 'latch\|infer'

▸ EXPECTED OUTPUT

# BEFORE fix:
Warning: Latch inferred for
  signal `\y' in process
  $proc$.../buggy.v:6$1

# AFTER fix:
(no warnings)
SB_DFF           0
SB_LUT4          2

▸ KEY OBSERVATION

The warning disappears. Cell count drops. The fix is one line of code and saves you hours of timing-closure debugging later.

🔧 What Did the Tool Build?

Same module, buggy vs fixed:

Buggy (latch)

SB_DFFE      4   ← level-sensitive
SB_LUT4      8
Warning: latch inferred

Yosys uses SB_DFFE with level trigger — a faux-latch. Timing-unsafe.

Fixed (no latch)

SB_DFF       0   ← no storage
SB_LUT4      4
(no warnings)

Pure combinational. Smaller area, faster timing, clean STA.

iCE40 note: iCE40 actually has no true latch primitive — Yosys builds latches out of LUTs and enables, which are even worse than real latches. Always fix them.

🤖 Check the Machine

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.

Key Takeaways

 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.

Every output, every path, every time. If you don't assign it, the tool stores it.

🔗 Transfer

Blocking vs. Nonblocking

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. Day 4 will prove why, but you need the rule in memory before you get there.