Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
    • Combinational Timing Overview
    • Muxes, Decoders and Encoders
    • Arithmetic Blocks
    • Comparators and Shifters
    • Tri-State and Shared Buses
    • Latch Inference
      • Why Accidental Latches Happen
      • The Complete-Assignment Pattern
      • Intentional Latches
    • Arithmetic Datapaths
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Latch Inference
  1. Topics
  2. RTL Design
  3. Combinational Design
  4. Latch Inference
  5. Why Accidental Latches Happen

Part 2 · Combinational · Intermediate

Why Accidental Latches Happen

Four coding gaps that infer a latch, with the synthesizer message each one produces.

On this page

    Why this matters

    A latch from a missing else or case branch passes functional simulation, because the test rarely visits the uncovered case. It then shows up as a surprise cell in synthesis, timing and scan.

    After this lesson you can: spot the four classic causes of an accidental latch and explain why holding a value needs storage.

    The four classic causes

    Four classic causes cover most accidental latches. Each leaves an output unassigned on some path, so the old value must be held.

    Cause 1: if without else

    Missing else leaves one path unassigned. When en is 0, nothing drives y.

    latch_if.sv
    1
    2
    3
    4
    5
    6
    // yosys: latch
    module latch_if (input logic en, a, output logic y);
      always_comb begin
        if (en) y = a;            // when en is 0, y is not assigned -> must hold
      end
    endmodule

    The synthesizer states the problem directly when you use always_comb:

    Diagram
    ERROR: Latch inferred for signal `\latch_if.\y' from always_comb process `\latch_if.$proc$...'.

    That message comes from the open-source Yosys synthesizer, which treats a latch inside always_comb as an error. Commercial tools usually report it as a warning (for example “latch inferred”).

    Plain always @(*) carries no such intent. Yosys still logs that it inferred a latch, but it carries on and the synthesis succeeds; whether a commercial tool treats the same code as a warning, an error or an info message depends on the tool and its configuration. Success is not a clean report. always_comb adds an intent check that turns the latch into a diagnostic you can act on, which is the reason to prefer it.

    The bottom row, y, holds its old value while en is low.

    • Slot 1: before any input moves, y is unknown (x).

    • Slot 2: en rises with a at 1, so y becomes 1.

    • Slot 3: en drops to 0 while a changes to 0, but y holds 1.

    • Slots 4-5: a toggles twice while en stays 0, and y still holds 1.

    • Slots 6-7: en rises again with a at 0, so y follows down to 0.

    That held 1 is the symptom: simulation passes, but the netlist holds a memory cell.

    Cause 2: case that does not cover every value

    Missing select value is the same bug in a mux. Select 3 has no branch, so the output must hold.

    latch_case.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // yosys: latch
    module latch_case (input logic [1:0] sel, input logic a, b, c, output logic y);
      always_comb begin
        case (sel)
          2'd0: y = a;
          2'd1: y = b;
          2'd2: y = c;
          // sel == 3 is missing and there is no default -> y holds its value
        endcase
      end
    endmodule

    Cause 3: one output assigned on fewer paths than another

    Partly assigned output hides inside a healthy block. Only y grows a latch here.

    latch_partial.sv
    1
    2
    3
    4
    5
    6
    7
    // yosys: latch
    module latch_partial (input logic sel, a, b, output logic x, y);
      always_comb begin
        x = a;                    // x is always assigned
        if (sel) y = b;           // y is not: latch on y only
      end
    endmodule

    The third case is the sneakiest: the block looks assigned because x is, and a quick read misses that y is not.

    Cause 4: assignment inside a loop that may not run

    Conditional assign inside a loop is the same gap in disguise. Zero set bits means zero assignments.

    latch_loop.sv
    1
    2
    3
    4
    5
    6
    7
    // yosys: latch
    module latch_loop (input logic [3:0] sel, input logic [7:0] d, output logic [7:0] y);
      always_comb begin
        for (int i = 0; i < 4; i++)
          if (sel[i]) y = d;        // if sel == 0, nothing assigns y, so y holds its old value
      end
    endmodule

    The loop always runs 4 times, but the assignment inside it is conditional. When sel is zero, no pass assigns y.

    So y must keep its old value, and that keeping needs a latch. Fix it by giving y a default value before the loop.

    Causes at a glance

    Cause

    Symptom

    Fix

    if without else

    Output holds when the condition is false

    Add else, or assign a default first

    case without default

    Output holds for uncovered select values

    Add default, or assign a default first

    Output assigned on some branches only

    That one output holds

    Assign every output on every path

    Assignment inside a loop that may not run

    Output holds if the loop never assigns

    Initialize before the loop

    Common mistakes

    Check yourself An always_comb block assigns y only when en is 1. Now en is 0. What does y hold, and what does synthesis build?

    It holds its old value. The missing path must remember, so synthesis builds a latch.

    Check yourself A loop assigns y only when some bit of sel is set. sel is zero. What does y hold?

    Its old value. No pass assigns it, so the value must be held, and holding needs a latch.

    Check yourself In a 3-input case over a 2-bit select that lists values 0, 1 and 2 only, why is a latch inferred even though “the select can never be 3”?

    The tool cannot know that. It must produce correct behaviour for every possible select value, including 3, and with no assignment for 3 the output has to hold. Cover 3 with a default (or assign a default before the case): that is the complete-assignment fix. A separate assertion can flag a select value that should never occur. unique case is a different promise: it says 3 never happens, so synthesis may treat it as a don't-care, and it belongs only with an explicit assumption that you also check.

    A likely interview question is why an if without else infers a latch.

    • Missing path leaves the output unassigned for some input.

    • Holding the old value needs storage, so synthesis builds a latch.

    • Avoid it with a default assignment first, and write always_comb so the tool reports it.

    Common pitfall

    A latch often passes functional simulation, because your testbench rarely visits the uncovered case. The bug shows up in lint, in the synthesis log, or in timing and test as a surprise latch cell. Read the synthesis log for latch messages every time.

    Testbench

    One testbench shows the symptom: the output keeps its old value.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    module tb_latch_symptom;
      timeunit 1ns; timeprecision 1ps;
      logic en = 0, a = 0, y;
      always @* if (en) y = a;               // the buggy pattern (always @* so the simulator accepts it)
      int errors = 0;
    
      initial begin
        #1 en = 1; a = 1; #1;
        if (y !== 1'b1) begin                // follows a while enabled
          $display("ERROR: en=1 a=1 but y=%b", y);
          errors++;
        end
        en = 0; #1;
        a = 0; #1;                           // en is low: y should ignore a...
        if (y !== 1'b1) begin                // ...so it HOLDS 1 (storage!), not 0
          $display("ERROR: en=0 and y=%b, expected the held 1", y);
          errors++;
        end
        if (errors != 0) $fatal(1, "FAIL: tb_latch_symptom errors=%0d", errors);
        $display("PASS: the output remembers its last value, which is the symptom of a latch");
        $finish;
      end
    endmodule

    Remember

    • Any path that leaves an output unassigned makes the output hold, and holding needs a latch.

    • Four causes: if without else, a case that misses a value, an output assigned on fewer paths than another, and a conditional assignment in a loop.

    • always_comb turns the latch into a diagnostic; plain always @(*) does not.

    Rule of thumb: if a path does not assign the output, the hardware must remember it.

    Keep going

    • Interview: run a mock interview on this topic

    Related topics

    • The complete-assignment pattern

    Previous

    54 / 350

    Next: The Complete-Assignment Pattern

    Up next

    Up nextThe Complete-Assignment Pattern

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Intentional Latches
    clkenay
    The output holds its old value while disabled