Hardware Engineering · HDL and Synthesis

Inferred Latches: Why an Incomplete Branch Becomes State

How a combinational block that fails to assign its output on every path forces the synthesis tool to infer a level-sensitive latch, and which coding shapes create that condition.

A combinational block is a truth table with a gate network attached. If the block does not assign its output for some combination of inputs, the truth table has a hole, and a hole in a combinational function has exactly one hardware interpretation: the output holds its previous value. Holding a value requires state, and state in a level-sensitive form is a latch. The synthesis tool does not fail on this code. It builds the latch, reports it as inferred, and continues.

That is the mechanism behind a common synthesis warning. The warning is worth understanding precisely, because the fix is not “add a default” in the way it is usually described; the fix is to make the block assign every output on every path, and defaults are one way to do that and a poor way when they hide an unintended priority structure.

The minimal case

always @(*) begin
  if (sel)
    y = a;
end

When sel is low, nothing assigns y. The tool’s data-flow analysis asks what value y takes for sel = 0, and the only answer consistent with the code is “whatever it had”. The resulting cell is a latch whose enable is sel and whose data input is a: transparent while sel is high, holding while sel is low.

Adding an else removes the state:

always @(*) begin
  if (sel) y = a;
  else     y = b;
end

This is a two-input multiplexer and nothing more. The difference between the two versions is one branch, and the difference in hardware is a multiplexer versus a multiplexer plus a latch plus the enable logic that generates sel. The latch version is also worse on every axis that matters in a synchronous design: it is not scannable in the ordinary way, it has a transparent window that makes timing analysis two-sided, and it breaks the assumption that state changes only at clock edges.

Why latches are a problem rather than a style choice

A flip-flop samples on an edge; its timing is characterized by setup and hold around that edge, and its output changes only after clock-to-Q. A latch is transparent for half a cycle, so a path that enters a latch during the transparent window continues through it, and the arrival time at the next element depends on the clock’s duty cycle and on the path taken through the latch. Timing analysis must check both the opening edge and the closing edge of the window, so every latch path carries a second edge to analyse and the results become sensitive to clock skew in a way edge-triggered logic is not. Scan insertion, the process that chains registers into a test shift register, also has to treat latches specially because they cannot be chained on a single edge.

Some designs use latches deliberately for area or for a time-borrowing path. Those designs treat the latch as a first-class element, instantiate it explicitly, and analyse the transparency window as part of the clocking scheme. An inferred latch is not that; it is a latch nobody chose.

The shapes that infer one

Four patterns account for most accidental latches.

An if without an else on one output of several. The output assigned in the branch but not outside it holds. This includes the case where a block assigns three outputs and one of them appears only inside a nested condition.

A case without a default, or with a default that does not cover every output. A case statement is a priority structure in simulation and a multiplexer in hardware. If some case item assigns y and the default does not, the uncovered encodings hold. This is the most common form because a case over a state register with an enumerated set of states still leaves the encodings not in the enumeration unassigned, and a two-bit state register has four encodings of which the design may use three. The fix depends on the encoding chosen for that register, discussed in state encoding and next-state logic.

A for loop with a conditional assignment. A loop that assigns an array element only when a condition holds infers a latch per element, because each element holds when its condition is false. Loop-generated structures are especially prone to this because the condition is often on the loop index rather than on the data.

A signal assigned in one branch and read in another before assignment. This is a variant of the first pattern and appears in blocks that compute an intermediate value used only on some paths.

/* carry and sum are fully assigned below; valid is not. */
always @(*) begin
  case (op)
    2'b00:   {carry, sum} = a + b;
    2'b01:   {carry, sum} = a - b;
    default: {carry, sum} = 2'b00;   /* carry assigned, sum assigned */
  endcase
  /* valid is assigned only when op == 2'b10, so it latches */
  if (op == 2'b10) valid = 1'b1;
end

Why the default-value idiom is only half a fix

Writing y = 0; at the top of the block before the if makes every path assign y, and the latch disappears. It is a legitimate fix and it is the one most style guides recommend. Its cost is that it changes the logic: the default now competes with the branch assignments, and the tool must build a multiplexer that selects between the default and each branch’s value. That multiplexer is usually free because the branch structure already needed one, but in a large case the default can create an extra level of selection that the designer did not intend.

The idiom’s real danger is that it silences the warning without answering the question the warning asked. If valid was meant to be a registered flag, the default assigns it combinationally and the design now has a combinational path where a register was intended. If valid was meant to hold across cycles, the default destroys the intended behaviour. The warning is a question about intent, and a default answers it by fiat.

The stronger discipline is to assign every output in every branch of the structure, so the code’s shape matches the multiplexer’s shape. Where a default is used, it should be the value that makes the unused encodings harmless, not a value chosen to make the warning go away.

The clocked block that infers a latch instead of a register

A latch can also appear in a block that looks clocked. If the block is sensitive to the clock level rather than its edge, or if the clock appears in the sensitivity list without posedge, the tool sees a level-sensitive process:

always @(clk or d)        /* level-sensitive, not edge-triggered */
  if (clk) q = d;

This is a latch with enable clk, and the tool will say so. The intent was probably a flip-flop with a clock enable, which is written as an edge-triggered block with a conditional non-blocking assignment:

always @(posedge clk)
  if (en) q <= d;

The difference between “if the clock is high, pass the data” and “on the clock’s rising edge, if enabled, capture the data” is the entire difference between a latch and a flip-flop, and it is one keyword in the sensitivity list. The clocked form’s semantics depend on the assignment operator as well, which is why blocking and non-blocking assignment belongs to the same discussion.

A related case is the block that is edge-triggered but does not assign on every path. if (en) q <= d; with no else inside always @(posedge clk) infers a flip-flop with an enable, not a latch: the register’s own feedback supplies the held value, and a flip-flop with feedback through a multiplexer is a register with a clock enable, which is a standard cell. The same missing else in a level-sensitive or combinational block infers a latch. The distinction is entirely about whether the process has an edge in its event control.

Detecting and removing them

Synthesis tools report inferred latches by name and by source line, and most have an option to turn the report into an error. That option is worth enabling for new code: a latch that is intentional can be instantiated explicitly, and everything else reported is a bug.

The lint view is complementary. A combinational block whose output set is not fully assigned on every path is a static property of the code, detectable without synthesis, and most linters check it. The check is also expressible as a rule about the block’s structure: every if has an else that assigns the same outputs, every case has a default that assigns the same outputs, and every loop body assigns unconditionally. A block that satisfies those three rules, at every level including inside the functions it calls, cannot produce a latch, and the rules are local enough to check by reading.

The residual risk is in the constructs that hide a path: a function called from several branches that assigns only some of its outputs, a generate block that conditionally omits an assignment, an ifdef that removes a branch for one configuration. Those need the tool’s report, which is why the report exists and why suppressing it is a bad trade.

References

  1. IEEE Std 1364-2005: IEEE Standard for Verilog Hardware Description Language — IEEE
  2. IEEE Std 1076-2008: IEEE Standard for VHDL Language Reference Manual — IEEE
  3. Reuse Methodology Manual for System-on-a-Chip Designs — Michael Keating and Pierre Bricaud, Kluwer Academic Publishers
  4. Verilog HDL Synthesis: A Practical Primer — J. Bhasker, Star Galaxy Publishing