HomeToolsVerilog Spot the Bug
🐛 RTL CHALLENGE

Verilog Spot the Bug

A snippet appears. The clock is ticking. Can you find the bug before time runs out? 10 rounds of real RTL traps — latches, blocking assignments, width mismatches and more.

The classic RTL bugs every engineer must catch

The game above is built from the bugs that actually bite in real RTL — the ones reviewers hunt for and interviewers love to ask about. Here's a reference for each, with the symptom and the fix. Learn these and you'll catch most of them on sight.

1. Blocking assignment in a sequential block

always @(posedge clk) q = d;

always @(posedge clk) q <= d;

In clocked logic, use non-blocking (<=). All right-hand sides evaluate first, then update together at the edge — modelling real flip-flops. Blocking (=) in sequential code causes simulation race conditions and sim/synthesis mismatch, especially across multiple always blocks.

2. Non-blocking assignment in combinational logic

always @(*) y <= a & b;

always @(*) y = a & b;

The mirror image of bug 1. Combinational blocks should use blocking (=) so values propagate immediately within the block. Non-blocking in comb logic can cause extra simulation deltas and confusing behaviour.

3. Inferred latch — incomplete if

always @(*) if (sel) y = a;  (no else)

✓ add else y = b; or a default y = b; at the top

In combinational logic, if an output isn't assigned on every path, the synthesizer infers a latch to hold the old value. Latches are almost always unintended and cause timing headaches. Assign every output on every branch.

4. Inferred latch — incomplete case

✗ a case with no default that doesn't cover all values

✓ add a default: branch (or default assignments before the case)

Same root cause as #3, in a case statement. Always include a default, or pre-assign outputs above the case so they have a value on every path.

5. Incomplete sensitivity list

always @(a or b) y = a & b & c;  (c missing)

always @(*) y = a & b & c;

Every signal read in a combinational block must trigger it. A missing signal makes simulation disagree with the synthesized hardware. Use always @(*) (or always_comb in SystemVerilog) to include them automatically.

6. Continuous assignment to a reg

reg y; assign y = a & b;

wire y; assign y = a & b;  (or drive y in an always block)

assign drives a net (wire); a reg is driven procedurally inside an always block. Mixing them is a compile error or a sign of confused intent.

7. Procedural signal not declared reg/logic

output y; always @(*) y = ~a;

output reg y;  (or output logic y;)

Anything assigned inside an always block must be a variable (reg in Verilog, logic in SystemVerilog), including module outputs.

8. Missing edge on an asynchronous reset

always @(posedge clk or rst_n)

always @(posedge clk or negedge rst_n)

An asynchronous reset must be edge-sensitive in the sensitivity list. Listing the bare signal is illegal/ambiguous — specify negedge (active-low) or posedge (active-high).

9. Multiple drivers on one net

assign y = a; assign y = b;

✓ one continuous driver per net (use a mux: assign y = s ? a : b;)

Two continuous assignments fighting over one wire produce contention (X) in simulation and are illegal in synthesis. A net needs exactly one driver.

10. Width mismatch & impossible compares

reg [3:0] cnt; if (cnt == 16) ...  (4-bit max is 15)

✓ size signals correctly; watch carries: a 4-bit + 4-bit needs a 5-bit sum

Truncation and impossible comparisons are silent killers. Mind the bit-widths of sums (carry-out), comparisons, and concatenations.

Want to test code for real? Run it in the browser Verilog Simulator or check it with the Verilog Lint tutorial. New to RTL? Start with Digital Electronics and the FSM Designer.

FAQ

Blocking vs non-blocking — the rule?

Non-blocking (<=) for sequential (clocked) logic; blocking (=) for combinational logic. Mixing them up is the #1 RTL bug.

What causes an inferred latch?

An output in combinational logic not assigned on every path (if with no else, or case with no default). Fix by assigning on all paths.

What is an incomplete sensitivity list?

A combinational block that doesn't list every signal it reads, causing sim/synthesis mismatch. Use always @(*).

Related: Verilog Simulator · Verilog Lint · FSM Designer · VLSI