Difficulty: Medium · Puzzle 5 of 6 · Topic: Deadlock Scenario Puzzles: Objection and Phase Stalls
Two agents deadlock rarely under high parallel load. The failure disappears if either agent runs alone.
semaphore cfg_sem = new(1);
semaphore bus_sem = new(1);
task agent_a();
cfg_sem.get(1);
#1ns;
bus_sem.get(1);
bus_sem.put(1);
cfg_sem.put(1);
endtask
task agent_b();
bus_sem.get(1);
#1ns;
cfg_sem.get(1);
cfg_sem.put(1);
bus_sem.put(1);
endtaskClassic AB-BA acquisition ordering bug.
1) Capture semaphore acquisition timeline from both agents on failing seed.
2) Confirm agent_a holds cfg_sem waiting bus_sem while agent_b holds bus_sem waiting cfg_sem.
3) Enforce one global lock order across all users (for example cfg then bus everywhere).
4) Optionally merge locks if independent parallelism is not needed.
5) Add lock-order assertions in debug builds.Answer: Bug: lock inversion causes circular wait (AB-BA deadlock). Fix: enforce consistent semaphore acquisition order across all threads.
Checked by running the code in Verilator 5; the printed output supports the answer above. Plain-SV semaphore model of AB-BA lock inversion: opposite acquire order deadlocks, one common order finishes.
Random seed changes only shift timing; they do not remove the structural deadlock risk.
If lock ordering cannot be unified, what timeout/retry strategy would you choose and why? Model answer: Acquire the second lock with try_get; if it fails, release the first lock, back off for a random delay and retry, so a circular wait can never persist. Add a watchdog that logs who holds each lock; timeouts alone only detect the deadlock, they do not break it.
First I list which thread holds which semaphore and which one it is waiting for, because a hang that depends on timing looks like a circular wait. The root cause is lock inversion, where one thread takes A then B and another takes B then A, so each waits on the other. Changing seeds only shifts the timing and does not remove the structural risk. I confirm it by drawing the wait cycle, and the fix is a consistent acquisition order across all threads.