← RTL Design Interviews

Interview Question · Debug

Two Simulators, Two Answers

L1 JuniorL2 Mid-Levelblocking nonblockingracesdebug

The Question

The interviewer asks

The same RTL gives b=0 on Icarus and b=1 on Verilator. The code is in the depth panel. Which simulator has the bug?

log
$ iverilog -g2012 counter_pair.sv && vvp a.out
after one edge: a=1 b=0
$ verilator --binary counter_pair.sv && obj_dir/Vcounter_pair
after one edge: a=1 b=1
Reveal the Model Answer

Neither simulator has a bug. The design has a race. Both blocks wake at the same edge, and the standard lets a simulator run them in any order. Because a is written with =, the reader sees the old or the new value depending on that order. Write a with <=, so every reader at that edge sees the old value in any tool.

always @(posedge clk) a = a + 1;   // block 1 writes a
always @(posedge clk) b = a;       // block 2 reads a
Two Blocks Wake at the Same EdgeArrows show the order of events in one time step. Block 1 writes a; block 2 reads it.
Two blocks wake at the same edgeArrows show the order of events in one time step. Block 1 writes `a`; block 2 reads it. component posedge clk wakes both blocks component Block 1 a = a + 1 component Block 2 b = a component Active region runs the blocks in anyorder component NBA region with <=, a changeshere wakewakethen
  1. Both wake

    The clock edge wakes block 1 and block 2 in the same time step.

  2. Order is free

    The standard lets the simulator run them in any order. With =, a changes at once, so block 2 sees the old or the new value.

  3. The fix

    With <=, the write to a waits for the NBA region. Every reader in the Active region sees the old a.

Read the diagram as text
  • posedge clk: wakes both blocks
  • Block 1: a = a + 1
  • Block 2: b = a
  • Active region: runs the blocks in any order
  • NBA region: with <=, a changes here
  • posedge clk → Block 1: wake
  • posedge clk → Block 2: wake
  • Block 1 → Active region
  • Block 2 → Active region
  • Active region → NBA region: then

Icarus and Verilator may pick different orders. Neither is wrong; the code is.

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Junior)

A good junior answer calls it a race straight away. It points at the blocking write to a and fixes it with <=.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfCompareScale It

  1. Two Simulators, Two Answers
  2. WhyWhy Nonblocking Removes the RaceL1 JuniorL2 Mid-Level

    Why does changing a = a + 1 to a <= a + 1 remove the race, even though the block order is still open?

    Reveal the Model Answer

    With <=, the write to a waits for the NBA region. Every block that wakes at the edge runs before that, in the Active region. So the reader always sees the old a, whichever block runs first. The order still varies, but it no longer changes the result.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Nonblocking fixes every race."

    It fixes races between a write and reads at the same edge. Two blocks that both write one variable still race. The last update wins.

    Source IEEE 1800-2017 §4.7

  3. Scale ItAuditing a Large Code Base for RacesL3 Senior

    You inherit 200 RTL files written over ten years. How do you find and stop races?

    Reveal the Model Answer

    Start with lint: report blocking writes in clocked blocks and variables written by more than one process. Convert clocked blocks to always_ff, so the compiler rejects a second writer. Run regressions on two simulators and treat any difference as a bug. Then make the lint rules a commit gate, so new code stays clean.

    Self-Check

    Tick each key point you covered in your answer.

    What Interviewers Listen For (Senior)

    A good senior answer orders the work by cost. Automatic lint comes first, then conversion to always_ff, then a two-simulator regression. It ends with a commit gate so the cleanup lasts.

    Wrong Altitude

    "Review every file by hand."

    Manual review of 200 files is slow and misses cases. A lint rule finds every blocking write in a clocked block in minutes.

  4. CompareLint vs a Second Simulator for Finding RacesL2 Mid-Level

    Compare lint and running a second simulator as ways to find races like this one.

    Reveal the Model Answer

    Lint reads the code and flags the pattern, such as a blocking write in a clocked block. It works even if no test shows a wrong value. A second simulator finds only races that change a result in the tests you run. Lint is cheaper and more complete for coding races. Two simulators also catch other order-dependent behaviour.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "If both simulators agree, the code has no race."

    Two tools can pick the same order by chance. Agreement is not proof; only the coding rule removes the race.

    Source IEEE 1800-2017 §4.7

  5. What IfA Testbench That Drives at the Clock EdgeL1 JuniorL2 Mid-Level

    Asked if they mention the testbench

    What if the race is between the testbench and the design? The bench writes din = 1 at posedge clk, and a flop samples din there.

    Reveal the Model Answer

    It is the same race. The bench write and the flop read both run in the Active region. So the flop may capture the old or the new din. Drive inputs away from the sampling edge, for example on the falling edge or through a clocking block. Then the design always samples a settled value.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Use <= in the testbench and the problem is gone."

    It does remove this race. But the inputs still change exactly at the edge, and later reads in the bench can race. A clocking block fixes the boundary.

    Source Spear & Tumbush, SystemVerilog for Verification (3rd ed.) ch. 4.3 Stimulus Timing (clocking block default skews)

What People Get Wrong

Common wrong answers and why they fail. Run It shows the wrong belief failing on a real simulator.

Misconception

"Blocks run in the order they appear in the file."

The standard leaves the order open. In the twin the reader is listed first, yet Icarus runs the writer first and b gets the new a.

Run It

The pair with the reader listed first, then the fix with <=.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: after one edge: a=1 b=1 FAIL: blocks do not run in source order; the writer ran first and b got the new a
See the Fix
module tb;
  logic clk = 0;
  int a = 0, b = 0;
  always @(posedge clk) b <= a;      // reads a (listed first this time)
  always @(posedge clk) a <= a + 1;  // writes a
  initial begin
    #5 clk = 1; #1;
    $display("after one edge: a=%0d b=%0d", a, b);
    if (b == 0) $display("PASS: with <= every reader sees the old a, in any block order");
    $finish;
  end
endmodule

Prints: after one edge: a=1 b=0 PASS: with <= every reader sees the old a, in any block order

Tool as Truth

"Verilator is the more accurate simulator, so b=1 is right."

Both results are legal. The value depends on scheduling order, which the standard does not fix. The code is wrong, not either tool.

Source IEEE 1800-2017 §4.7

Delivery

Starts by filing a bug against one of the simulators.

A two-tool disagreement on zero-delay code is most often a race. Check the code for a blocking write that another block reads first.

Learn It, Then Prove It