Hello! In our last lesson, we saw how the procedural always @(*) block is a powerful tool for modeling complex combinational logic. You learned the three key rules for using it correctly: use the always @(*) sensitivity list, declare outputs as reg, and use blocking assignments (=). We also hinted that fully specifying all conditions in if and case statements is critical.
Today, we're going to dive deep into why that is so important. We will explore one of the most common pitfalls in RTL design: the unintended latch. By the end of this lesson, you will be able to explain what causes these latches to be created and, more importantly, how to write your Verilog code to prevent them. This is a crucial skill for creating predictable and reliable hardware.
1. What is a Latch and Why is it a Problem?
In digital logic, we have two fundamental types of memory elements: flip-flops and latches.
- Flip-flops, as we will see soon, are edge-triggered. They change their stored value only at the precise moment of a clock's rising or falling edge. This synchronous behavior is the bedrock of modern digital design.
- Latches, on the other hand, are level-sensitive. They are "transparent," meaning the output changes to follow the input as long as an enable signal is active (e.g., high). When the enable signal goes inactive, the latch "closes" and holds the last value it saw.
While latches have their uses, unintentionally creating one in a synchronous design is almost always a bug. Synthesis tools will warn you about them, and for good reason. Because they are not controlled by a clock edge, they can cause timing problems that are very difficult to analyze and debug.
The following video from nandland provides a great high-level explanation of what a latch is and why you should be careful to avoid creating them by accident.
This video introduces the concept of a latch in FPGAs and why they are generally avoided in synchronous design.
Watch the first three minutes of the video, from the beginning up to the point where he transitions to the code examples (What is a latch?). Pay close attention to the reasons given for why designers avoid them.
2. How Unintended Latches are Created
The central rule for describing combinational logic is: the output must be defined for every possible combination of inputs.
When you write an always @(*) block, the synthesis tool tries to build purely combinational logic (gates only). However, if you write code where an output is not assigned a value under certain conditions, the tool has to make a decision. The Verilog language specifies that if a variable is not assigned a new value, it should keep its old one. To implement this "memory" behavior, the synthesis tool has no choice but to infer a latch.
Let's explore the two most common ways this happens.
How to Avoid Latch Inference in Verilog?
This article from VLSIFacts gives a concise and clear explanation of latch inference.
Read the first two sections, "What is Latch Inference?" and "Why Does Latch Inference Happen?". These sections will reinforce the core concept and show you the most common cause: an if statement without an else branch.
As the article explains, the most frequent cause is an incomplete if-else statement. Consider this code:
// This code will infer a latch
always @(*) begin
if (sel == 1'b1) begin
q = d;
end
// Problem: What is 'q' when sel is 0? It's not defined!
// The tool infers a latch to hold the previous value of 'q'.
end
The same problem occurs with case statements. If you don't list all possible values for the case variable and you don't provide a default statement, you create the same situation: for some input values, the output is not assigned.
The image below provides a perfect visual example. It shows Verilog code for a multiplexer using a case statement that is missing a condition. The synthesis tool correctly infers the multiplexer but also adds a D-latch at its output to hold the value for the unspecified sel input.

3. How to Avoid Latches: Two Golden Rules
Now that you can spot the problem, let's focus on the solutions. Preventing unintended latches is straightforward if you follow two simple coding practices.
Method 1: Fully Specify All Branches
The most direct way to avoid a latch is to ensure that every output signal is explicitly assigned a value in every possible branch of your if or case statements.
How to Avoid Latch Inference in Verilog?
This section of the same VLSIFacts article shows the solution.
Now, read the section "How to Avoid Latch Inference: Assign All Outputs in All Branches". It shows the corrected code for the if-else example.
Applying this to our previous examples:
Corrected if-else:
// No latch: 'q' is assigned in both the 'if' and 'else' branches.
always @(*) begin
if (sel == 1'b1) begin
q = d;
end else begin
q = 0; // Or some other default value
end
end
Corrected case:
// No latch: 'default' handles all unspecified cases.
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
// Missing 2'b11 is now covered by the default case.
default: y = 0;
endcase
end
Method 2: Assign a Default Value at the Start
A second, often cleaner, method is to assign a default value to all outputs at the very beginning of the always block. This guarantees that your outputs always have a defined value, even if a subsequent if or case statement doesn't cover all possibilities.
How to Avoid Latch Inference in Verilog?
This final section introduces the second, very effective method.
Read the section titled "Alternative Approach: Use Default Assignments". This is a very common and robust coding style in industry.
Here is what that style looks like:
// No latch: 'q' is given a default value at the start.
always @(*) begin
q = 0; // Default assignment
if (sel == 1'b1) begin
q = d; // 'q' is overridden only when sel is 1.
end
// If sel is 0, 'q' keeps its default value of 0. No latch needed.
end
This approach is particularly useful in complex logic where nesting if-else statements can become messy. By setting a default upfront, you simplify the conditional logic that follows.
The nandland video you watched earlier also demonstrates this fix for an incomplete case statement.
This segment shows a practical example of fixing an incomplete case statement that would otherwise infer a latch.
Watch the part of the video covering the Verilog example. Notice how adding the else (equivalent to a default case) resolves the incomplete assignment and prevents the latch.
4. Your Turn: Find and Fix the Latch
Let's put this knowledge into practice. Below is the Verilog code for a 2:1 multiplexer. It contains a bug that will cause a synthesis tool to infer a latch.
// File: mux_2_to_1_buggy.v
// A 2:1 multiplexer with a bug that infers a latch.
module mux_2_to_1_buggy (
input a, b,
input sel,
output reg y
);
always @(*) begin
if (sel == 1'b1) begin
y = a;
end
end
endmodule
Your Task:
- Identify the bug: Explain in one sentence why this code will infer a latch.
- Fix the bug (Method 1): Rewrite the
alwaysblock using a completeif-elsestatement to prevent the latch. - Fix the bug (Method 2): Rewrite the
alwaysblock again, this time using the default assignment method.
Take a moment to write down your answers before checking the solutions below.
Solution
-
The Bug: The code will infer a latch because the output
yis not assigned a value whenselis0. -
Fix with
if-else:always @(*) begin if (sel == 1'b1) begin y = a; end else begin y = b; end end -
Fix with Default Assignment:
always @(*) begin y = b; // Assign default value when sel=0 if (sel == 1'b1) begin y = a; // Override if sel=1 end end
Conclusion
You have now tackled one of the most fundamental concepts for writing good synthesizable Verilog. Understanding how and why unintended latches are created will save you countless hours of debugging down the road.
Key Takeaways:
- An unintended latch is inferred in combinational logic when an output is not assigned a value for all possible input conditions.
- This happens because the synthesis tool must create a memory element to "hold" the output's previous value.
- Latches are generally bugs in synchronous designs and can lead to timing failures.
- You can avoid them using two main techniques:
- Fully specify all branches in
if-elseandcasestatements. - Assign a default value to all outputs at the beginning of an
always @(*)block.
- Fully specify all branches in
In our next lesson, we will continue practicing our combinational logic skills by learning how to build a parameterized N-bit adder. This will introduce you to the concept of creating flexible and reusable hardware modules, a key principle in modern chip design.