Difficulty: Medium · Puzzle 4 of 6 · Topic: Race Condition Puzzles
Predict every line printed. What value does each child of loop A see, which children of loop B finish, and which line order is defined?
`timescale 1ns/1ns
module tb;
int i; // static, shared
process p[3];
initial begin
for (i = 0; i < 3; i++) begin
fork
$display("[%0t] A sees i=%0d", $time, i);
join_none
end
for (int j = 0; j < 3; j++) begin
automatic int k = j; // per-iteration copy
fork
begin
p[k] = process::self();
#5;
$display("[%0t] B finished k=%0d", $time, k);
end
join_none
end
#1;
p[1].kill();
#10;
$finish;
end
endmodulejoin_none children do not start until the parent blocks; they then read variables as they are at that moment.
1) The parent never blocks while looping, so no child of loop A runs until it reaches #1. By then the shared static i is 3, so all three children print i=3 at t=0.
2) In loop B each iteration has its own automatic k, copied from j at spawn time. The three children see k=0, 1, 2 respectively and each stores its handle in p[k] at t=0.
3) The parent wakes at t=1 and kills p[1], which is still inside #5.
4) At t=5 only the surviving children (k=0 and k=2) print B finished. The killed child never prints.
5) The three A lines are identical, so their order is irrelevant; the order of the two B lines at t=5 is not defined by the LRM.Answer: Three lines [0] A sees i=3, then at t=5 [5] B finished k=0 and [5] B finished k=2 (either order). k=1 never finishes because p[1].kill() ran at t=1.
Checked by running the code in Verilator 5; the printed output supports the answer above. Every loop-A child sees the final i=3, and the killed k=1 child never finishes. The order of the two surviving B lines is undefined, so each is checked in its own run.
Reading the loop index inside a fork join_none body is the bug: the body sees the final value. Copy it into an automatic variable declared inside the loop body before the fork.
What happens if you call p[1].kill() before p[1] has been assigned, e.g. at t=0 right after the loop? Model answer: p[1] is still null, so the call dereferences a null handle (runtime error). Wait until the handle is set (wait (p[1] != null)) or have the child register itself first.
Loop A prints i equal to 3 three times at time zero, because the parent does not block until the delay and the static loop variable has already reached 3. Loop B copies the index into an automatic variable, so the children see 0, 1 and 2, and the parent kills the one with index 1 at time one. At five, only k equal to 0 and k equal to 2 print, in an order the LRM does not define. The trap is reading the loop variable inside the fork body; I would declare an automatic copy inside the loop before the fork.