Difficulty: Medium · Puzzle 2 of 6 · Topic: Semaphore & Mailbox Puzzles
The start order is fixed explicitly in this version. Explain exactly why depth matters, where progress stops, and what mb.num() is when the simulation ends.
module tb;
semaphore lock = new(1);
mailbox #(int) mb = new(1);
task producer();
lock.get(1);
$display("[%0t] P holds lock, putting 100", $time);
mb.put(100); // blocks: mailbox is full
lock.put(1);
$display("[%0t] P done", $time);
endtask
task consumer();
int v;
lock.get(1); // waits for lock
mb.get(v);
lock.put(1);
$display("[%0t] C got %0d", $time, v);
endtask
initial begin
mb.put(99); // fills the 1-deep mailbox
fork producer(); join_none
#1; // producer is now blocked inside put
fork consumer(); join_none
#50;
$display("[%0t] end: mb.num()=%0d", $time, mb.num());
$finish;
end
endmoduleA bounded put blocks when the mailbox is full, exactly like get on empty.
1) mb.put(99) fills the depth-1 mailbox.
2) At t=0 the producer takes the lock and blocks in mb.put(100) because the mailbox is full.
3) At t=1 the consumer starts and blocks on lock.get: the producer owns the lock.
4) Only the consumer could free space (by mb.get), but it needs the lock the producer holds: circular wait.
5) Output: '[0] P holds lock, putting 100' and '[51] end: mb.num()=1'. No 'P done' and no 'C got'. With the explicit #1 the order is deterministic (verified in Verilator).Answer: It hangs: P holds the lock blocked on put, C blocked on the lock. The output is '[0] P holds lock, putting 100' and '[51] end: mb.num()=1'.
Checked by running the code in Verilator 5; the printed output supports the answer above. The producer holds the lock while blocked on a full mailbox, so the consumer never runs and mb.num() stays 1.
The original form of this puzzle depended on undefined start order of initial blocks and fork children; make the order explicit before claiming a deadlock. And bounded put is as blocking as get.
Would increasing mailbox depth to 2 truly fix correctness or only mask the bug? Model answer: it only masks it: with room for 100 the producer finishes, but any burst larger than the new depth recreates the same lock-held-while-blocked cycle. The real fix is not holding the lock across the put.
It hangs, and the depth is the reason. The producer fills the depth-one mailbox, takes the lock and blocks on the second put, and the consumer, which is the only one that could free space, is stuck on the lock. So nothing progresses, the end line shows one item still in the mailbox, and the circular wait is the lock plus the full mailbox. The trap is thinking a put cannot block, and I would make the start order explicit before claiming the deadlock. I would check the depth and who holds what when the timeout fires.