Hello! Welcome to the sixth lesson of our course.
In our last session, we focused on liveness, writing properties with the strong operator to prove that good things—like a grant eventually following a request—do happen. We ensured our designs make forward progress.
Today, we pivot back to safety properties, but with a specific focus. Instead of just checking for correct data, we'll learn how to prove that bad things in the form of unexpected behaviors never happen. This lesson is about hunting down glitches, protocol errors, and invalid FSM sequences. By the end, you'll be able to write precise assertions to detect spurious events and illegal state transitions, a skill that is fundamental to robust formal verification and a frequent topic in interviews.
1. Defining the "Bad Things": Spurious Events and Illegal Transitions
In any complex design, like the arbiters and read-return units you've worked on, there's an implicit set of rules governing behavior. A violation of these rules can lead to catastrophic failure. We can categorize these violations into two main types:
- Illegal State Transition: A finite state machine (FSM) or a protocol moves from a valid state to another state that is not a permitted successor. Think of a traffic light FSM transitioning directly from green to red, skipping yellow.
- Spurious Event: A signal exhibits unexpected behavior that violates protocol rules, even if it doesn't cause an immediate illegal FSM state change. This could be a control signal de-asserting prematurely, a data bus changing when it should be stable, or a signal briefly taking on an unknown ('X') value.
These are not theoretical problems. Formal verification is exceptionally good at finding these bugs, which often hide from simulation.

2. Asserting Against Illegal State Transitions
The most direct way to prevent illegal behavior is to formally codify the legal state transitions of your design's FSMs and forbid anything else.
Let's say an FSM has a state A and can legally transition to B or C. An illegal transition would be moving from A to D. While you could write a property asserting the positive case ((state == A) |-> (next_state == B || next_state == C)), it's often more direct and scalable to assert the negative case—that the illegal transition never happens.
The pattern is antecedent |-> !(illegal_consequence).
// Asserts that if the FSM is in state A, it NEVER transitions to state D on the next cycle.
a_no_A_to_D: assert property (@(posedge clk) (state == A) |-> !(next_state == D));
A common mistake is to misuse the not operator.
SystemVerilog Assertion Linting
This paper on SVA linting highlights a subtle but critical logical error that engineers often make. Understanding this distinction is vital for writing correct negative properties.
Read the short section '7) Property using negated implication' on page 2. Focus on the difference between not (a |-> b) and a |-> !b. The former is almost never what you want.
As the paper explains, not (a |-> b) is equivalent to a && !b, meaning a must be true every cycle and b must be false. The correct expression to forbid a sequence is a |-> !b.
Illegal transitions can also span multiple cycles.

The property shown is:$rose(current_st == State2) |-> not((current_st == State4) ##1 (current_st == State6))
This assertion states: "At the cycle where the FSM enters State2, it is forbidden for the FSM to then be in State4 on some future cycle, and then State6 on the very next cycle." This prevents a specific illegal multi-step pathway.
3. Catching Protocol Violations and Spurious Signals
Beyond simple FSMs, most hardware communication is governed by protocols. A ready/valid handshake is one of the most fundamental. Violating its rules creates spurious events that can break the system.
This article provides an excellent primer on the ready/valid protocol and its rules. Understanding these rules is the first step to verifying them.
Please read the text up to and including 'Figure 2: Ready/Valid Link State'. Pay close attention to the two AMBA rules and the state transition diagram. This diagram visually defines the 'legal' moves, and any deviation is an illegal transition.
Now that you're familiar with the protocol rules, let's write assertions to enforce them. We want to catch events like valid de-asserting too early or data changing when it should be held stable.
The same article provides clear SVA examples for these protocol checks. We'll focus on checking for 'X' values and ensuring signal stability.
Please read the subsections 'Always Check for Xes on Control Signals' and 'Protocol Violations'. Focus on understanding the logic behind the RV_Tx_ValidDataStable property.
Let's dissect the key properties from the reading:
Checking for Unknowns ('X')
A signal taking on an 'X' value is a classic spurious event, often caused by uninitialized logic or bus contention. Using $isunknown is a simple but powerful safety net.
// From the article: Data should never be unknown when valid is high.
ASSERT(RV_Tx_NeverDataUnknown, read_valid |-> !$isunknown(read_data), clock, reset);
Interview Tip: A sharp follow-up question might be about the difference between == and === when checking for unknowns. Remember that (my_signal == 1'bx) will always evaluate to 1'bx (unknown), not true. To check if a signal is 'X', you must use the case equality operator: (my_signal === 1'bx). Asserting !$isunknown(my_signal) is the most robust approach.
Ensuring Signal Stability
The AMBA rule states: "Once VALID is asserted it must remain asserted until the handshake occurs." This is a stability requirement. The property RV_Tx_ValidDataStable captures this beautifully:
// If we are waiting for the receiver (`!read_ready && read_valid`)...
// ...then on the next cycle, `valid` must remain high and `data` must be stable.
ASSERT(RV_Tx_ValidDataStable,
(!read_ready && read_valid) |=> (read_valid && $stable(read_data)),
clock, reset);
The $stable() system task is your primary tool here. It returns true if the expression's value is the same in the current cycle as it was in the previous cycle. This elegantly checks that a signal isn't changing when it's supposed to be held constant—a direct check against a spurious event.
Conclusion
Today, we built a crucial part of our verification toolkit: writing safety properties to catch illegal and unexpected behavior. While liveness properties prove your design does something, these properties prove it doesn't do something wrong. A robust formal testbench needs both.
Key Takeaways:
- Illegal Transitions vs. Spurious Events: An illegal transition is a violation of a state machine's defined flow. A spurious event is a broader protocol violation, such as a signal changing value when it should be stable or becoming unknown.
- The Negative Check Pattern: Forbidding bad behavior with
antecedent |-> !(illegal_consequence)is often more effective than listing all legal behaviors. - Key System Tasks:
$stable(signal)is essential for checking that signals are not changing across cycles.$isunknown(signal)is your first line of defense against uninitialized logic and 'X' propagation.
- Protocol is King: For any interface, understanding its protocol rules (like ready/valid) is the prerequisite for writing meaningful assertions to detect violations.
In our next lesson, we will tackle multi-clock domain assertions. Clock domain crossings (CDCs) are a notorious source of spurious events and metastability issues, and formal methods are an excellent way to verify them.
🧠Interview & Practice Drill
Time to put these concepts into practice.
🎤 Interview Drill Section
-
Rapid Fire Theory:
- You see
assert property (@(posedge clk) (state == BUSY) |-> $stable(data_bus));. What kind of bug is this property designed to catch? - An FSM can go from
IDLEtoSTATE_A. It should never go directly fromIDLEtoSTATE_B. Which is a better property, and why?(state == IDLE) |-> (next_state == STATE_A)(state == IDLE) |-> !(next_state == STATE_B)
- What is the semantic difference between
$stable(sig)andsig == $past(sig)? (Hint: Think about the first cycle and how 'X' values are handled). - How do you write an assertion to check that a signal
my_sighas not become 'X'?
- You see
-
Resume Deep Dive Question:
- "Looking at your Multi-Client Arbiter project, let's assume it had a fixed-priority scheme. An illegal state would be granting to a lower-priority client while a higher-priority client is requesting. How would you write an SVA property to detect this specific illegal grant event? Describe the antecedent and the consequent."
-
Whiteboard Problem:
- A simple FSM controls a memory read operation. Its states are
IDLE,REQ,WAIT,DONE.- Legal transitions:
IDLE->REQ(whenstartis high). REQ->WAIT(unconditionally on the next cycle).WAIT->DONE(whenmem_readyis high).DONE->IDLE(unconditionally on the next cycle).
- Legal transitions:
- Task: Write two SVA properties to check for illegal transitions:
- One that ensures the FSM never goes from
IDLEtoWAIT. - One that ensures the FSM never leaves the
WAITstate unlessmem_readyis asserted.
- One that ensures the FSM never goes from
- A simple FSM controls a memory read operation. Its states are
🧩 Coding Exercises
Assume a default clock @(posedge clk).
-
Arbiter Spurious Grant: An arbiter has inputs
req[3:0]and outputsgnt[3:0]. A grantgnt[i]should only ever be asserted if the corresponding requestreq[i]is asserted in the same cycle. Write a propertya_no_spurious_grantthat asserts that for anyifrom 0 to 3, ifgnt[i]is high, thenreq[i]must also be high. (Hint: Aforloop inside agenerateblock is a clean way to instantiate this four times). -
Ready/Valid Protocol Violation: Based on the ready/valid protocol we studied, one optional rule is that once the receiver asserts
ready(whenvalidis low), it must keepreadyasserted until a transfer occurs (validgoes high). Write a propertya_ready_is_stablethat enforces this rule. -
One-Hot FSM Check: A state machine uses a 4-bit one-hot encoding for its state register
fsm_state[3:0]. An illegal state is any value that is not one-hot (e.g.,4'b0000,4'b0011). Write a concurrent assertiona_state_is_onehotthat continuously checks thatfsm_stateis always a one-hot value. Use the system function$onehot(fsm_state).