Common Verilog Mistakes (Latches, Sensitivity Lists, Races)
Most Verilog bugs are not syntax errors — the code compiles, simulates, and then behaves differently in hardware. Here are the mistakes that cost beginners the most time, each with the fix.
1. Incomplete sensitivity list
Listing only some signals in always @(a) makes the simulator miss updates — and infers a latch. Always use always @(*) for combinational logic so the tool tracks every input.
2. Latch from a missing else / default
If a combinational always block does not assign an output on every path, Verilog must remember the old value — a latch. The example below shows the leak and the fix (assign a default first):
module latch_demo (input [1:0] sel, input a, b, output reg y_bad, output reg y_good);
// BAD: no default -> y_bad holds its value when sel==2 or 3 (latch inferred)
always @(*) case (sel)
2'd0: y_bad = a;
2'd1: y_bad = b;
endcase
// GOOD: default assignment on every path -> pure combinational
always @(*) begin
y_good = 1'b0; // default first
case (sel)
2'd0: y_good = a;
2'd1: y_good = b;
endcase
end
endmodule
module tb;
reg [1:0] sel; reg a,b; wire y_bad, y_good;
latch_demo dut(.sel(sel), .a(a), .b(b), .y_bad(y_bad), .y_good(y_good));
initial begin
a=1; b=0; sel=2'd0; #1 $display("sel=0 y_bad=%b y_good=%b", y_bad, y_good);
sel=2'd2; #1 $display("sel=2 y_bad=%b y_good=%b (y_bad kept old value!)", y_bad, y_good);
$finish;
end
endmodulesel=0 y_bad=1 y_good=1 sel=2 y_bad=1 y_good=0 (y_bad kept old value!)
3. Blocking assignment in clocked logic
Using = inside always @(posedge clk) creates races and sim-vs-synthesis mismatch. Always use <= there — see blocking vs non-blocking.
4. Width mismatches that silently truncate
Assigning a 9-bit sum to an 8-bit reg drops the carry with no warning. Size your nets for the widest intermediate value, and watch for the implicit extension in a + b.
5. Multiple drivers on one net
Two assigns (or two always blocks) writing the same wire produce x. One net, one driver.
▶ Try it in the ChipXpert Lab
Run the latch demo in the lab and watch y_bad keep its old value at sel=2. Add the default assignment and re-simulate to see the latch disappear.
Check yourself
How do you spot an accidental latch in combinational logic?
Synthesis/lint warns ‘inferred latch’, and in simulation the output keeps its previous value on an unassigned path. The fix is to assign every output a default at the top of the always @(*) block, or cover every branch with a full case/else.
Why does <code>always @(a or b)</code> risk a bug when the logic also reads <code>c</code>?
The block will not re-evaluate when c changes, so the output goes stale and a latch may be inferred. always @(*) builds the sensitivity list automatically from everything the block reads, eliminating the class of bug.
Share your question in comments or talk to our mentor team for batch guidance.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses