Finite state machines control almost everything in a chip: bus interfaces, protocol engines, power controllers. Their bugs are typically in the corners: a transition that forgets to clear a timer, a state that can never be left, a state that can never be reached. This article lists the standard properties for state machines and uses them on a request controller with a timeout, whose timer is not cleared when a new request starts.
9 min read | Intermediate
What You'll Learn
- Write the standard properties of a state machine: legal states, transitions, outputs, no deadlock
- Check every state for reachability with covers
- Find a timer that is not reinitialized on a transition
- Prove a state machine property with k-induction
Standard FSM Properties
- Legal states: the state register only holds defined encodings. With a 2-bit register and 3 states, the fourth encoding must never appear.
- Legal transitions: each state goes only to its allowed successors, under the right conditions.
- Outputs per state: outputs match the state, such as
busyexactly inWAIT. - No deadlock: the machine cannot get stuck, which in a bounded check means a limit on how long it stays in a waiting state.
- Reachability: every state and every transition can happen, shown with covers.
The Design: a Request Controller
On req, the controller goes to WAIT and waits for ack. If no acknowledge comes within 4 cycles, it goes to ERR until software clears it:
module req_fsm (input clk, input rst, input req, input ack, input clr,
output busy, output err, output reg [1:0] state);
localparam IDLE = 2'd0, WAIT = 2'd1, ERR = 2'd2;
reg [2:0] t;
assign busy = (state == WAIT);
assign err = (state == ERR);
always @(posedge clk)
if (rst) begin
state <= IDLE; t <= 3'd0;
end else case (state)
IDLE: if (req) state <= WAIT; // bug: t is not cleared here
WAIT: if (ack) begin state <= IDLE; end
else if (t == 3'd3) begin state <= ERR; t <= 3'd0; end
else t <= t + 3'd1;
ERR: if (clr) state <= IDLE;
default: state <= IDLE;
endcase
endmodule
The timer t is cleared on reset and on the timeout, but not when an acknowledge ends the wait. The next request then starts with a timer that is already running.
The Properties
a_legal: assert property (@(posedge clk) disable iff (rst) state != 2'd3);
a_timeout: assert property (@(posedge clk) disable iff (rst)
state == IDLE && req ##1 (state == WAIT && !ack) [*4] |=> state == ERR);
a_early: assert property (@(posedge clk) disable iff (rst) $rose(err) |-> $past(state == WAIT, 4));
a_timeout says that four cycles of waiting without an acknowledge lead to ERR. a_early says that ERR is never entered without four cycles of WAIT before it. In the lab form, a helper counter w measures the time spent in WAIT:
`ifdef FORMAL
reg init = 1'b1;
always @(posedge clk) init <= 1'b0;
always @(*) if (init) assume (rst);
// legal states only
always @(*) if (!init) assert (state != 2'd3);
// helper: how long the machine has been waiting
reg [3:0] w = 4'd0;
always @(posedge clk) w <= (rst || state != WAIT) ? 4'd0 : w + 4'd1;
// ERR only after 4 cycles of waiting without ack
always @(posedge clk) if (!init && !$past(rst) && err && !$past(err)) assert ($past(w) == 4'd3);
// never stuck in WAIT: at most 4 cycles
always @(*) if (!init) assert (w <= 4'd4);
`endif
The run fails at step 9:
The first request is acknowledged in its second cycle of waiting, which leaves t at 1. The second request starts with t = 1, so the controller reaches ERR after only 3 cycles of waiting. A slow memory that answers in the fourth cycle would be reported as failed. The fix clears the timer when a request is accepted: IDLE: if (req) begin state <= WAIT; t <= 3'd0; end.
Quick Check
Q: Why does the bug need two requests to show?
Show Answer
After reset, t is 0, so the first request times out correctly. Only a wait that ends with an acknowledge leaves t non-zero, and only the next request suffers from it.
Reachability
For every state, a cover shows that it can be reached, and the trace shows how. In the lab, the meant-to-fail form assert (!err) fails at step 6 on the fixed design: a request in step 1 and four cycles without an acknowledge. A state machine with an unreachable state either has dead logic or a bug that blocks the path to it.
A Proof
The fixed controller passes in prove mode with k-induction at depth 6, and with the PDR engine. A small state machine with a short timer is a typical case where a full proof is cheap, so there is no reason to stop at a bounded result.
Quick Check
Q: The state register has 2 bits and the machine uses 3 states. What should happen if the fourth encoding ever appears, for example after a radiation upset?
Show Answer
The machine should recover, which is what the default: state <= IDLE; branch does. Formal cannot reach that state from reset, so the recovery is checked separately, for example with a start-anywhere run.
Running It in ChipVerify Lab
The example below is the buggy controller with the properties above. It fails at step 9 within the free depth.
Common Mistakes
Mistake #1: Clearing a timer on only some of the transitions
A timer or counter tied to a state must be initialized on every path into that state. Check each transition, or better, clear it on entry.
Mistake #2: Writing properties that copy the case statement
"In WAIT with t == 3, go to ERR" restates the RTL and shares its bug. Describe the time spent in a state with an independent helper, as w does.
Mistake #3: Skipping the reachability covers
An error state that can never be reached makes every property about it vacuous. Cover every state.
Practice Exercise
Challenge: Fix the Timer and Show ERR Is Reachable
Open the example and run it. Fix the controller. Then add a meant-to-fail check that err never goes high, and read the trace it produces.
Hint
The meant-to-fail check is always @(*) if (!init) assert (!err);. Its counterexample is a trace into ERR.
Solution
With the fix, the properties pass. The meant-to-fail check fails at step 6: a request in step 1 moves the controller to WAIT in step 2, no acknowledge comes in steps 2 to 5, and the controller is in ERR in step 6. That is exactly four cycles of waiting, as specified.
Open the ready-made example in the lab and run the formal check from the formal panel. The free tier allows a depth of up to 10 cycles, and PRO allows up to 100.
Summary
- State machines need properties for legal states, legal transitions, outputs, bounded waiting and reachability.
- A helper counter for the time spent in a state is independent of the RTL's own timer and catches its mistakes.
- A timer that was not cleared on a new request made the controller time out after 3 cycles instead of 4.
- Covers, or meant-to-fail checks in the lab, show that every state can be reached.
- Small state machines are easy to prove completely; do it.