Liveness, Fairness, Deadlock
Read a liveness failure as a loop, add the fairness the environment really gives, run the bounded substitute, and find a deadlock as a safety failure.
- Lesson
- F3.7
- Time
- 15 min
- Runs on
- SymbiYosys
- Check
- 4 questions
An arbiter's "every request is eventually granted" passed in live mode on the first try. The job also held the block's safety assertions, and the liveness flow had quietly turned them into assumptions, one of which pruned the path where the third requester starved. Run alone, with fairness on ready and nothing else, the property failed with a loop of eleven states.
A prefix from reset to a request, then a loop the design can repeat forever. gnt is 0 in every state of the loop, so the promised grant never comes.
Around the loop pending stays 1 and gnt stays 0. No finite step fails; the failure is the loop itself, and only a liveness engine can return it.
Read the states and transitions
- reset: initial, depth 0.
- req seen: reachable, depth 1.
- pending, ready 0: bad, depth 2.
- pending, ready 0: bad, depth 3.
- pending, ready 0: bad, depth 4.
- gnt: unreachable, depth 0.
- reset → req seen.
- req seen → pending, ready 0.
- pending, ready 0 → pending, ready 0.
- pending, ready 0 → pending, ready 0.
- pending, ready 0 → pending, ready 0.
Illustration. A drawing, not an engine witness. The state labelled gnt is never entered on this path.
Fairness: The Stall Always Ends
Measured with smtbmc yices at depth 20. The window is steps 2 to 9; ready is low in all 8 of them, so no design could grant.
A liveness property (F1.5) fails on a loop, not at a step. Most such failures on a correct design are the environment's: the solver holds ready low forever, and no design can grant. A fairness constraint is a liveness assumption on the environment: assume property (@(posedge clk) s_eventually ready); says the stall always ends, without saying when.
Liveness without fairness fails on almost everything. Fairness that is never checked hides almost everything, which is why a live job needs a sanity run (below). The editor here runs the bounded forms; liveness itself is drawn, not run.
| Bounded run | Measured result | Time |
|---|---|---|
req |-> ##[1:8] gnt, no fairness | FAIL at reported step 10 | 689 ms |
plus !ready |-> ##[1:4] ready | BMC PASS to depth 20 | 730 ms |
plus $fell(ready) |-> ##[1:4] ready instead | FAIL at reported step 10 | 731 ms |
| Grant needs two ready cycles running, bounded fairness | FAIL at reported step 10 | 733 ms |
| No fairness, clocked immediate form | FAIL at reported step 10 | 516 ms |
hs_live; mode bmc, depth 20, smtbmc yices, reset assumed in step 0. The range comes from A2.1.
The $fell form fails because ready can be low from reset: it never falls, so the assumption never starts. The two-cycle bug fails under fair stalls too, because ready may rise for one cycle at a time. Bounded response is weaker than liveness, since it invents a deadline the spec may not state, and far cheaper.
Three Ways to Make No Progress
Read the diagram as text
- Deadlock: one stuck state, repeated
- Livelock: a loop of legal states: retry, back off, retry
- Starvation: a loop in which one requester is never served
- Safety, in bmc: !(sa == 1 && sb == 1): a finite trace
- Liveness: req |-> s_eventually gnt: a lasso
- Liveness plus fairness: or a round-robin fix
- Deadlock → Safety, in bmc
- Livelock → Liveness
- Starvation → Liveness plus fairness
Only deadlock is reached at a finite step. Livelock and starvation have no bad state, only a bad loop.
Measured at depth 20. own1 = 1 (A) and own2 = 2 (B) from step 2 on: a single state the pair can never leave. With both machines taking lock 1 first: BMC PASS to depth 20.
A deadlock is a safety property in disguise: one stuck state, reached at a finite step, so bmc finds it. Livelock and starvation have no bad state; every state on the loop is legal and only the loop is wrong (A3.1 Weak and strong).
Three Rules for a Live Job
| Rule | Why |
|---|---|
One s_eventually assertion per job, and no safety assertions in it | SBY's live mode turns asserts into assumptions. Prove safety in its own task first, on the same design and environment. |
| One obligation per job | The liveness engine reads only the first obligation. Two properties need two jobs. |
A sanity job: assert property (@(posedge clk) s_eventually 1'b0); must FAIL | If it passes, the fairness left no infinite path, and every liveness proof under it was about nothing. |
Never put s_eventually in a bmc or prove task | Plain SBY drops liveness checks in those modes and passes. |
The traps come from the pinned SBY flow; commercial tools have the same traps under other names.
Written out, the live job is assert property (@(posedge clk) req |-> s_eventually gnt); with assume property (@(posedge clk) s_eventually ready);, alone, plus the sanity job. When a liveness proof will not converge, the bounded form above is the common substitute; record the deadline you chose as a reviewed assumption.
Asked in Interviews
| Question | Answer |
|---|---|
| How do you write "every request is eventually granted"? | req |-> s_eventually gnt in a live job, alone, with the fairness the environment really gives and a sanity job showing a fair path exists. |
| What is a fairness constraint? | A liveness assumption on the environment: a stall always ends. |
| Deadlock, livelock, starvation? | A stuck state (safety, finite trace); a loop of legal states with no progress (liveness); a loop in which one party is never served (liveness, usually arbitration). |
Try It
Liveness, as IEEE SVA writes it
assert property (@(posedge clk) req |-> s_eventually gnt);
assume property (@(posedge clk) s_eventually ready);
// sanity job, must FAIL:
assert property (@(posedge clk) s_eventually 1'b0);
A live job: no depth, a loop as the counterexample.
The bounded substitute this editor runs
assume property (@(posedge clk) disable iff (rst) !ready |-> ##[1:4] ready);
assert property (@(posedge clk) disable iff (rst) req |-> ##[1:8] gnt);
Safety: a finite window and a finite stall.
[options]
mode bmc
depth 20
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv props.sv
prep -top hs_live
[files]
design.sv
props.svRun without fairness
Run as it is: FAIL at reported step 10. Count the window steps with
readylow.Add bounded fairness
Add the
!ready |-> ##[1:4] readyassumption as the first line of props.sv: BMC PASS to depth 20.Try the $fell form
Write it as
assume property (@(posedge clk) disable iff (rst) $fell(ready) |-> ##[1:4] ready);with the registeredready_q. Why does it not help?Ask the spec
What deadline does the spec actually state for the grant? What did the bounded form add?
Find the deadlock
Run
two_fsmas safety: FAIL at reported step 3. Then make both machines take lock 1 first.
design.sv
props.sv
Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.
Inputs are free unless constrained. Run to find a counterexample or check a proof.
Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.
SBY summary and log
Check Yourself
Remember
- A liveness counterexample is a lasso: a prefix and a loop in which the promised event never comes.
- Fairness is a liveness assumption on the environment; without it, liveness fails on almost everything.
- A live job holds one obligation and no safety asserts, and a sanity job must show a fair path exists.
- Deadlock is safety and bmc finds it; bounded response is the cheap substitute for liveness, with a deadline the spec may not state.