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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Logic Synthesis Glossary
  • Logic Synthesis Cheatsheet
  • Synthesis Foundations14
    • Synthesis Flow Overview
    • Synthesizable RTL and Inference
      • Combinational Blocks Infer Logic
      • Priority Chains versus Parallel Selects
      • Latch Inference from Incomplete Assignment
      • Flops, Tri-States and Unrolled Loops
      • What Is Not Synthesizable
    • Reading Netlists and Reports
  • Logic Optimisation and Mapping20
  • Constraints and Timing15
  • Sequential, FSM and Memory19
  • Datapath Synthesis14
  • Area, Power and Test14
  • Verification, ECO and Handoff15
  • Flows and Debugging15
  • Interview and Judgment11
Loading lesson

←Synthesizable RTL and Inference
  1. Topics
  2. Logic Synthesis
  3. Synthesis Foundations
  4. Synthesizable RTL and Inference
  5. Latch Inference from Incomplete Assignment
On this page

    Part 1 of 9, lesson 8 of 14Part 1 of 9/Synthesis Foundations/Lesson 8 of 14Intermediate

    Latch Inference from Incomplete Assignment

    Incomplete assignment infers a latch, and default assignment removes it.

    The latch is the most common synthesis surprise: simulation holds a value because the variable simply was not reassigned, while synthesis must build a memory cell to do the same. The netlist then carries state the designer never drew.

    After this lesson you can: name the three one-line fixes that remove a latch, and show the $dlatch count falling to zero in the recorded runs.

    How a latch is inferred

    A latch holds the old value while its enable is low, and passes the input through while the enable is high. A synthesis tool infers one 8-bit $dlatch (8 latch bits after mapping) whenever some path through a combinational block skips an output, because holding the old value is the only hardware that reproduces the simulation behaviour of leaving a variable untouched. The rule covers every shape of gap: a missing else, a case without a default, a default assigned to only some of several outputs. Width does not matter to the rule, only coverage does: an 8-bit bus skipped on one path needs eight held bits, which the tool packs into one $dlatch cell carrying all eight.

    This lesson runs five variants of one 8-bit gated buffer through proc and records the $dlatch count of each: the bare if without an else, three classic fixes, and an always_comb version with complete assignment. The table below is the answer key, read from the recorded netlists rather than asserted by hand. Predict first, then check yourself against it.

    Predict first Five variants, one question each: which ones infer a latch? The bare if without an else, the default-first fix, the added-else fix, the case-with-default fix, and the always_comb version with complete assignment.

    Only the bare variant infers storage: it records 1 $dlatch cell with no select cells, while the default-first, added-else, case-default and always_comb variants record 0, 0, 0 and 0 latches respectively. Every fix closes the skipped path, so the hold requirement vanishes.

    The run: one latch, three fixes, one SystemVerilog check

    The script reads each variant on its own with read_verilog -sv, which is the Yosys front end that also accepts SystemVerilog keywords such as always_comb. Each phase selects its top, runs proc, and records stat plus the JSON netlist. The bare variant asserts exactly one $dlatch with no flop; every fixed variant asserts no $dlatch with at least one select cell. One PASS line covers all five phases, so a passing run certifies the before and every after in a single stroke.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    read_verilog -sv <<EOV
    module latch_bad (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @* begin
        if (en)
          q = d;
      end
    endmodule
    EOV
    hierarchy -check -top latch_bad
    proc
    tee -o stat_bad.txt stat
    write_json net_bad.json
    select -assert-count 1 t:$dlatch
    select -assert-none t:$dff
    design -reset
    read_verilog -sv <<EOV
    module fix_default (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @* begin
        q = 8'd0;
        if (en)
          q = d;
      end
    endmodule
    EOV
    hierarchy -check -top fix_default
    proc
    tee -o stat_fix_default.txt stat
    write_json net_fix_default.json
    select -assert-none t:$dlatch
    select -assert-min 1 t:$mux t:$pmux %u
    design -reset
    read_verilog -sv <<EOV
    module fix_else (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @* begin
        if (en)
          q = d;
        else
          q = 8'd0;
      end
    endmodule
    EOV
    hierarchy -check -top fix_else
    proc
    tee -o stat_fix_else.txt stat
    write_json net_fix_else.json
    select -assert-none t:$dlatch
    select -assert-min 1 t:$mux t:$pmux %u
    design -reset
    read_verilog -sv <<EOV
    module fix_case (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always @* begin
        case (en)
          1'b1: q = d;
          default: q = 8'd0;
        endcase
      end
    endmodule
    EOV
    hierarchy -check -top fix_case
    proc
    tee -o stat_fix_case.txt stat
    write_json net_fix_case.json
    select -assert-none t:$dlatch
    select -assert-min 1 t:$mux t:$pmux %u
    design -reset
    read_verilog -sv <<EOV
    module fix_sv (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always_comb begin
        q = 8'd0;
        if (en)
          q = d;
      end
    endmodule
    EOV
    hierarchy -check -top fix_sv
    proc
    tee -o stat_sv.txt stat
    write_json net_sv.json
    select -assert-none t:$dlatch
    select -assert-min 1 t:$mux t:$pmux %u
    log PASS: bare branch infers one latch and every fix removes it

    Variant

    $dlatch cells (recorded)

    Select cells (recorded)

    Verdict

    Bare if, no else

    1

    0

    Latch: enable low must hold q.

    Default assigned first

    0

    1

    Mux: every path assigns q.

    Missing else added

    0

    1

    Mux: every path assigns q.

    Case with default

    0

    1

    Mux: every value assigns q.

    always_comb, complete

    0

    1

    Mux: SystemVerilog, same rule.

    Read the first row against the other four. The bare variant is the only one with a skipped path, and it is the only one with a latch; the four fixes differ in syntax but agree in coverage, and the fixed variants agree in hardware. That agreement is the point of the table: the tool does not grade your style, it checks your coverage, and every complete coverage compiles to the same select shape.

    The bare variant as recorded: 1 highlighted $dlatch cell feeding the 8-bit output q, with 0 select cells beside it. The fixed variants replace the hold with one select cell.

    The always_comb version the checker accepts

    SystemVerilog offers always_comb for exactly this intent: combinational logic with no storage. The fix_sv variant assigns a default first and completes the branch inside always_comb, and the recorded run treats it like its always @* twin, with no latch and the same select shape. Two details matter for everyday use. First, Yosys reads it only with the SystemVerilog front end, hence read_verilog -sv in the script; the plain Verilog reader would reject the keyword. Second, several simulators and lint flows warn when an always_comb block can hold a value, which turns the latch from a silent netlist surprise into a compile-time message. Prefer always_comb for new combinational blocks, and read any warning on it as the tool pointing at the exact path you skipped.

    latch_sv.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module latch_sv (
      input wire en,
      input wire [7:0] d,
      output logic [7:0] q
    );
      always_comb begin
        q = 8'd0;
        if (en)
          q = d;
      end
    endmodule

    Break it: put the gap in always_comb

    Take the bare variant and change only its block header to always_comb, keeping the missing else. Run the listing below by hand: Yosys stops inside proc with the error "Latch inferred for signal latch_acomb.q from always_comb process", naming the signal and the block (hand-observed message, exact cell prefix aside). The course runner treats any ERROR line as a failed block and allows no logger override, so this refusal cannot ship as a recorded block; the listing stays hand-run. SystemVerilog reserves always_comb for gap-free combinational logic, so the tool refuses to build the memory instead of inferring it silently. That refusal is the feature: the construct that hides the latch in always @* becomes a compile error here, pointing at the exact path you skipped. Deleting any fixed variant's safety line and re-elaborating reproduces the bare row the other way: the symptom is a signal that keeps its old value after its inputs changed, and the fix is the missing assignment, never a constraint.

    yosys
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # not-run: Yosys stops this variant with an ERROR line, which fails a recorded block and allows no logger override, so run this listing by hand
    read_verilog -sv <<EOV
    module latch_acomb (
      input wire en,
      input wire [7:0] d,
      output reg [7:0] q
    );
      always_comb begin
        if (en)
          q = d;
      end
    endmodule
    EOV
    hierarchy -check -top latch_acomb
    proc
    log PASS: always_comb with a skipped path is rejected

    Common mistakes

    An unintended latch rarely announces itself in simulation. It looks like a signal that keeps its old value after its inputs changed, and the netlist shows $dlatch where a $mux was expected. A second trap is fixing one output of several: a default that assigns q but not a sibling valid flag leaves the sibling latched, so confirm every output on every path.

    What infers a latch, and how do you remove it?

    Weak answer: a missing else makes a latch, and you fix it somehow.

    Strong answer: any skipped path in a combinational block infers $dlatch; the bare 8-bit variant records one 8-bit $dlatch (8 latch bits after mapping) with no select logic beside it, and each of the three fixes plus the always_comb version records zero, because every fix makes each path assign the output. The figure shows the hold directly: the highlighted latch alone on the path to q.

    Follow-up: the netlist shows a latch but the RTL looks complete. Answer: check for a second output assigned in only some branches, or a case selector wider than the listed values, then confirm with the per-variant proc counts.

    Check yourself An if without an else drives an 8-bit bus in a combinational block. How many latches does the tool infer?

    One 8-bit $dlatch (8 latch bits after mapping): the recorded bare netlist shows 1 latch cell. Each bit holds its old value on the skipped path, packed into a single recorded cell.

    Check yourself The same block gains q = 0 before the if. What changes in the netlist, and by how much?

    Every path now assigns the output, so the latch count falls from 1 to 0 while 1 select cell carries the choice. The memory has nothing left to remember.

    • Any skipped path in a combinational block infers one 8-bit $dlatch (8 latch bits after mapping).

    • A default-first assignment, a missing else, or a case default each close the gap.

    • always_comb with complete assignment passes the same checks as always @*; with a gap the tool errors instead of inferring.

    Rule of thumb: close every path before the block ends.

    How this was checked

    Every count on this page comes from one Yosys 0.70+post run recorded by the course checker: read_verilog -sv reads the latch plus three fixed variants plus an always_comb version, each runs proc, and stat plus write_json are recorded per variant. The single figure is the recorded bare-variant netlist, which holds one 8-bit $dlatch (8 latch bits after mapping) and no select cells; the hold behaviour is described from that figure, not from a waveform. The always_comb refusal stays a hand-run listing with its observed message quoted, because the runner fails any block that prints an ERROR line and allows no logger override.

    • Interview: run a mock interview on this topic

    Related topics

    • Combinational Blocks Infer Logic

    • Simulation Synthesis Mismatch

    • Latch Inference

    Previous

    10 / 139

    Next: Flops, Tri-States and Unrolled Loops

    Up next

    Up nextFlops, Tri-States and Unrolled Loops

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Combinational Blocks Infer Logic
    • What Is Not Synthesizable