Avoiding Accidental Latches in Verilog

What an inferred latch is, the four coding patterns that cause one inside always @(*), and the two habits that prevent every case.

Updated 2026-07-08

An accidental latch is one of the first hardware surprises that catches beginners writing combinational Verilog. The code looks fine, the simulation seems to work, and then a synthesis warning appears: Inferred latch on signal .... This guide explains what that means, why it happens, and the two habits that make it impossible.

What is a latch, and why do you get one by accident?

A latch is a small storage element. It holds its output at the last value it was given until something tells it to change. That is useful when you want memory — but inside a block you intended to be pure combinational logic, it is almost never what you want.

The rule that causes accidental latches is simple: a combinational block must give every output a value on every possible path through the code. If there is any path where an output is not assigned, the synthesizer cannot build plain logic gates — gates always produce an output. To honor "keep the old value on this path," it must add storage. That storage is the latch you did not ask for.

Inside always @(*), the synthesizer reads your code as a description of hardware, not as a program that runs top to bottom. "This output is sometimes not assigned" translates directly into "this output sometimes needs to remember," and remembering means a latch.

The four ways a latch sneaks in

Every accidental latch has the same root cause — an unassigned path — but it shows up in four recognizable shapes.

1. Missing else. An if with no else leaves the output untouched when the condition is false:

always @(*) begin
  if (enable)
    y = data;
  // when enable is 0, y is never assigned -> latch
end

2. Missing default. A case that does not list every possible value and has no default leaves the uncovered values unassigned:

always @(*) begin
  case (sel)
    2'b00: y = a;
    2'b01: y = b;
    // 2'b10 and 2'b11 are unassigned -> latch
  endcase
end

3. Partial case assignment. Some branches assign the output and others forget. The forgetful branches are the unassigned paths.

4. Unassigned branch in a larger block. In a block with several outputs, one path assigns some of them and never touches another. That untouched output latches.

The two habits that prevent all four

You do not need to memorize four fixes. Two habits close every case.

Default-at-top. Assign every output a safe default as the very first thing in the block, before any if or case:

always @(*) begin
  y = 1'b0;        // default at top
  if (enable)
    y = data;      // override only when needed
end

Because y already has a value before any branch runs, there is no path where it is unassigned — so no latch is ever inferred. This is the pattern this course uses from the first always @(*) lesson onward, precisely so latch avoidance becomes a habit rather than a rule you remember under pressure.

Assign every output in every branch. The alternative is to make sure each branch — every if/else arm, every case item plus a default — assigns every output explicitly. This is more verbose but equally correct.

Most designers reach for default-at-top because it is shorter and harder to get wrong: you cannot forget a branch that does not exist yet.

How to react to the warning

When you see Inferred latch on signal X, do not ignore it. Find the block that assigns X, and look for the path where X gets no value. Add a default at the top of that block, or complete the missing branch. Re-check, and the warning disappears — along with the phantom storage it was describing.

Reading and fixing that warning quickly is a real signal of combinational-Verilog fluency. It is the difference between "my simulation passed" and "my hardware does what I meant."