Difficulty: Medium · Puzzle 5 of 6 · Topic: Race Condition Puzzles
Compute the final value of hits and explain why four increments produce exactly that value.
module tb;
int hits = 0;
task automatic add_hit();
int snap;
snap = hits;
#0 hits = snap + 1;
endtask
initial begin
fork
repeat (2) add_hit();
repeat (2) add_hit();
join
$display("[%0t] hits=%0d", $time, hits);
end
endmoduleRead-modify-write is not atomic across concurrent threads, and #0 yields in between.
1) Both threads start in Active. Each reads snap=0 and yields on #0.
2) In Inactive each thread writes hits=1 and starts its second add_hit. The second snapshot is taken right after the thread's own first write, with no yield in between, so both second snapshots see hits=1.
3) Both threads yield again; both then write hits=2. Final value: 2.
4) The result does not depend on the order in which the LRM lets the two inactive resumptions run, so the answer is exactly 2, never 3 or 4.
5) A correct counter needs mutual exclusion: wrap snap/write in sem.get(1)/sem.put(1).Answer: hits=2 at t=0 in every legal ordering (verified in Icarus and Verilator). Two of the four increments are lost.
Checked by running the code in Verilator 5 and Icarus Verilog 13; the printed output supports the answer above. Two of the four increments are lost, every time. Icarus gives the same hits=2, so the lost updates are not a tool quirk.
Answering 'typically 2' or 'sometimes 4' is wrong: the lost updates are deterministic here. Four would only appear if #0 were treated as a no-op (some tools approximate it), or if a semaphore serialised the updates.
Show a semaphore-based rewrite that guarantees hits=4. Model answer: semaphore s = new(1); task automatic add_hit(); int snap; s.get(1); snap = hits; #0 hits = snap + 1; s.put(1); endtask. Each read-modify-write is now a critical section, so hits=4.
The answer is exactly 2, and two of the four increments are lost. Each thread reads the counter and yields on #0, then both write 1, and their second increments also read 1 because nothing yields between a thread's write and its next read, so both write 2. This does not depend on the order of the two inactive resumptions, so it is not a toss-up. The trap is saying typically 2 or sometimes 4; the fix is a semaphore around the read-modify-write, and I would check the lock is released on every path.