Difficulty: Easy · Puzzle 3 of 6 · Topic: Semaphore & Mailbox Puzzles
No deadlock occurs, but simulation appears frozen at t=0. Explain root cause and proper fix.
module tb;
mailbox #(int) mb = new();
int item;
initial begin
forever begin
if (!mb.try_get(item)) begin
// no delay, no event wait
end
end
end
endmoduleA non-blocking API still needs timing control in polling loops.
1) try_get returns immediately when mailbox empty.
2) Loop has no blocking statement or delay.
3) The loop never suspends, so the process never gives control back to the scheduler: simulation time can never advance past t=0 and no other process is guaranteed to run. It is always stuck, not 'often' (Verilator hung until killed; even a #5 print in another initial never appeared).
4) This is a zero-delay livelock, not a deadlock: the process keeps running, but time is frozen.
5) Fix: use a blocking mb.get(item), or poll with #1 / @(posedge clk), or wait on an event raised by the producer.Answer: This is a zero-delay busy spin (livelock), not a protocol deadlock: the loop has no timing control, so time never advances past t=0 and other processes do not get to run. Switch to a blocking mb.get, or poll with #1 or @(posedge clk).
Checked by running the code in Verilator 5; the printed output supports the answer above. A try_get loop with no timing control keeps time at 0 while it spins (bounded here so the run ends). Adding #1 to the poll lets time advance and the item is picked up at t=3.
Non-blocking call does not imply low-overhead loop by itself.
What backoff policy would you use to avoid both starvation and performance collapse? Model answer: prefer a blocking get so no polling is needed. If polling is required, use a fixed small poll such as #1 or @(posedge clk), or exponential backoff capped at a few clocks (1, 2, 4, then stay at 4) that resets after a successful get, so latency stays bounded and the simulator is not woken every time step.
This is not a deadlock, it is a zero-delay livelock. The try_get returns at once on an empty mailbox and the loop has no timing control, so it never gives the scheduler a chance, time never leaves zero and other processes never run. It always hangs, not sometimes. The trap is believing a non-blocking call makes a cheap loop. The fix is a blocking get, or poll with a delay or a clock edge, or wait on an event, and I would check there is no other tight loop.