Capture values before Active and NBA updates.
SystemVerilog Assertions interview preparation
SystemVerilog Assertions (SVA)Concepts and Interview QuestionsYou Should Master
Use this self-contained SVA reference to learn the sampling model, operator semantics, proof controls, waveform reasoning, and reusable protocol checkers before a design verification interview.
Start with the reading method
Read every property as seven explicit decisions.
This is the shortest path from an English requirement to reviewable SVA. If one decision is missing, the property is probably underspecified.Sample one coherent snapshot at every positive clock edge.
Each waveform column is one posedge of clk. Temporal delays count these sampling events, not simulator time and not arbitrary signal transitions between edges.
@(posedge clk)a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] (ack && !$isunknown(ack))
) else $error("ack missing or unknown: ack=%b", $sampled(ack));
c_last_slot: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req) ##1 !ack[*2] ##1 ack
);
Clock. Sampling grid. All eight columns are sampling events. One ##1 delay moves exactly one column to the right.
01 · Sampling modelReason from the sampled clock edge.
Separate simulation updates from assertion sampling, then explain exactly when a concurrent property observes, evaluates, and reports a result.
Run the property using the sampled values.
Execute the concurrent pass or fail action.
disable iff observes current values and can abort between sampled clock events.
Always-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Why assertions
Use SVA as an executable contract, not as decorative checking.
Capture intent precisely
Name the clock, reset policy, trigger, deadline, and required outcome. The property is only as correct as that translation.
Fail near the cause
Internal invariants can expose a corrupt state before the bug propagates to a scoreboard-visible output.
Carry checks with the IP
Interface and block contracts can run in unit simulation, subsystem regression, emulation where supported, and formal harnesses.
Measure exercise separately
Assertion coverage and cover properties show whether attempts and important scenarios occurred. They do not replace data-oriented covergroups.
Two execution models
Choose the assertion form that matches the requirement.
| Form | When it evaluates | Best use | Important caution |
|---|---|---|---|
assert (expr) | When procedural control reaches it | Local combinational or algorithmic invariant | Simple form can observe transient delta-cycle values |
assert #0 / assert final | Deferred within the current time slot | Settled combinational result | Still procedural, not a multi-cycle temporal property |
assert property | On the declared or inherited sampling event | Latency, ordering, stability, pulse, and protocol rules | Uses sampled values and can create overlapping attempts |
always_comb begin
a_grant_shape: assert final ($onehot0(grant))
else $error("grant is not one-hot-or-zero");
end
a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] ack
);
Scheduler model
The sampled value can differ from the waveform value you see later.
Sample
Capture the values used by the property before ordinary Active-region logic and nonblocking assignment updates.
Evaluate
Evaluate sequence matches and property truth using the Preponed samples.
Report
Run pass or fail action blocks. Use $sampled(signal) when the message must show the value that the property evaluated.
Abort
disable iff observes its current expression and can terminate an active attempt between sampling events.
a_state_known: assert property (
@(posedge clk) disable iff (!rst_n)
!$isunknown(state)
) else $error(
"sampled state=%b current state=%b",
$sampled(state), state
);
Function handbook
Use the sampled-value and four-state helpers deliberately.
| Function | Meaning at this sample | Typical contract |
|---|---|---|
$rose(x) | LSB changed from 0/X/Z to 1 | One attempt per rising event |
$fell(x) | LSB changed from 1/X/Z to 0 | Release or completion edge |
$stable(x) | Whole expression equals its prior sample | Payload held under stall |
$changed(x) | Whole expression differs from its prior sample | State or ownership transition |
$past(x, n) | Value sampled n clock events earlier | History and causality |
$sampled(x) | Value captured for this assertion evaluation | Accurate failure message |
$onehot(x) | Exactly one bit is 1 | Exactly one owner |
$onehot0(x) | Zero or one bit is 1 | Idle-capable one-hot grant |
$isunknown(x) | At least one bit is X or Z | Four-state control integrity |
$countones(x) | Number of bits equal to 1 | Population limit |
Deployment
Place each checker where its inputs and ownership are clear.
| Location | Sees | Best for | Ownership risk |
|---|---|---|---|
RTL module | Internal state and datapath | White-box invariants | Tight implementation coupling |
interface | Protocol signals | Reusable interface contract | Clock and modport conventions |
checker + bind | Explicit checker ports | Non-invasive integration | Accidental bind to every module instance |
formal harness | Boundary inputs and state | Assumptions, assertions, covers | Overconstraint |
scoreboard/model | Transactions and history | Identity, ordering, one-to-one accounting | Not a replacement for local assertions |
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
SVA stands for SystemVerilog Assertions. It adds executable Boolean and temporal properties that can check design behavior in simulation and formal analysis, and can record that meaningful scenarios occurred.
Why it works
- Immediate assertions check a condition at a procedural execution point. Concurrent assertions describe sampled behavior across one or more clock events.
- Assertions work best as compact contracts near an interface, block, or bound checker, not as a replacement for scoreboards and end-to-end data models.
- A useful assertion names the requirement, states its clock and reset policy, and reports enough sampled context to reproduce a failure.
Whether you can describe assertions as executable specifications and choose them for temporal control behavior without claiming they replace every verification technique.
Do not define SVA as only a simulator feature. The same property language is also central to formal verification and assertion coverage.
A simple immediate assertion executes like a procedural if statement when control reaches it. A concurrent assertion starts attempts on a declared clocking event and evaluates sampled behavior over time.
Why it works
- Use an immediate assertion for a local procedural invariant whose meaning is tied to that execution point.
- Use a concurrent property for clocked protocols, latency, stability, ordering, pulse width, and other temporal contracts.
- A deferred immediate form such as assert final can avoid reporting a transient combinational delta-cycle value.
Whether you connect assertion type to scheduling semantics instead of treating assert and assert property as interchangeable syntax.
A simple immediate assertion does not automatically sample in Preponed or wait for a clock edge.
always_comb begin
a_onehot_grant: assert final ($onehot0(grant))
else $error("grant is not one-hot-or-zero");
end
a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] ack
);
At its clocking event, a concurrent assertion samples referenced design values in Preponed, evaluates the property in Observed, and schedules its pass or fail action block in Reactive.
Why it works
- This region separation prevents the property from racing ordinary Active and NBA updates on the same clock edge.
- The disable iff expression is different: it uses current values and can asynchronously abort an active attempt.
- Inside a Reactive failure action, use $sampled(signal) when the message must report the value that the property actually evaluated.
- Choose $fatal, $error, $warning, or $info in an action block according to the regression policy; the property semantics do not change with the message severity.
Whether you can explain why a waveform's post-NBA value may differ from the value seen by the concurrent property on that same edge.
Printing a raw signal from the action block and calling it the sampled failure value can produce a misleading diagnostic.
a_known_state: assert property (
@(posedge clk) disable iff (!rst_n)
!$isunknown(state)
) else $error(
"sampled state was %b",
$sampled(state)
);
They compare values sampled on the property clock. $rose and $fell detect a 0-to-1 or 1-to-0 transition of the expression's least significant bit, $stable checks that the sampled expression did not change, and $past(expr, n) returns its sampled value n clocking events earlier.
Why it works
- For a vector transition, write the comparison you actually mean instead of assuming $rose(vector) detects any rising bit.
- $stable(payload) is especially useful in a one-step invariant that is retriggered on every stalled ready-valid edge.
- Guard uses of $past near time zero or reset release until enough sampled history exists; otherwise the default historical value can create a misleading pass or failure.
Whether you connect sampled-value functions to the property clock and can explain their vector and startup edge cases.
$past is not a free-running history database, and $rose on a bus does not mean any bit rose.
| Function | Question answered | Common use |
|---|---|---|
$rose(x) | Did sampled bit 0 change to 1? | One attempt per rising edge |
$fell(x) | Did sampled bit 0 change to 0? | Release or completion edge |
$stable(x) | Is x unchanged from the prior sample? | Payload under backpressure |
$past(x, n) | What was x n samples ago? | Bounded history and causality |
$onehot(expr) requires exactly one asserted bit, $onehot0(expr) permits zero or one, $isunknown(expr) detects any X or Z, and $countones(expr) returns the number of one bits. They turn common structural invariants into readable checks.
Why it works
- Use $onehot0 for an idle-capable grant vector and $onehot when one owner must always be selected.
- Check control and state vectors with !$isunknown(...) when X or Z must be a failure rather than an optimistic don't-care.
- $countones(mask) supports bounded-population rules, but state the legal count range explicitly so the intent is visible.
Whether you know the four-state and population-count helpers well enough to avoid hand-written, error-prone Boolean trees.
$onehot0 does not require any bit to be asserted; confusing it with $onehot weakens the contract.
a_grant_onehot0: assert property (
@(posedge clk) disable iff (!rst_n)
$onehot0(grant)
);
a_state_known: assert property (
@(posedge clk) disable iff (!rst_n)
!$isunknown({state, valid, ready})
);
02 · Assertion anatomyBuild the temporal sentence in layers.
Start with Boolean expressions, compose sequences across sampled clocks, turn them into properties, and finally choose assert, assume, or cover.
req && readyValue nowreq ##[1:3] ackMatch across clocksreq |-> responseConditional obligationassert property (...)Verification job##n · ##[m:n][*] · [=] · [->]and · or · intersectthroughout · within|-> · |=>Always-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Language model
Identify the language layer before explaining an operator.
| Layer | Question it answers | Representative syntax |
|---|---|---|
Boolean expression | What is true on this sample? | req && ready, !$isunknown(state) |
Sequence | What temporal pattern matched? | req ##[1:3] ack, beat[*4] |
Property | What obligation is true or false? | trigger |-> consequent, disable iff |
Directive | What should the tool do? | assert, assume, cover, expect |
Reusable syntax
Name sequences and properties when the timing deserves a name.
sequence s_req_to_ack;
$rose(req) ##[1:3] $rose(ack);
endsequence
property p_req_to_ack;
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] $rose(ack);
endproperty
checker req_ack_checker (
input logic clk, rst_n, req, ack
);
a_req_to_ack: assert property (p_req_to_ack);
endchecker
bind request_unit
req_ack_checker i_req_ack_checker (.*);
Operator atlas
Use this table to translate English timing into SVA.
| Family | Syntax | Core meaning | Typical mistake |
|---|---|---|---|
Delay | ##0, ##n, ##[m:n] | Concatenate sequence elements by sampled clocks | Counting simulator time instead of samples |
Consecutive repeat | [*n], [*m:n] | Repeat a Boolean or sequence on consecutive samples | Forgetting the implicit ##1 between repeats |
Nonconsecutive repeat | [=n], [=m:n] | Count Boolean hits with gaps and optional trailing slack | Expecting the match to end on the last hit |
Goto repeat | [->n], [->m:n] | Count Boolean hits with gaps and end on the final hit | Applying it to an arbitrary sequence |
Sequence choice | or | Either sequence match is sufficient | Assuming only one candidate endpoint exists |
Sequence conjunction | and | Both start in one compound attempt; later endpoint wins | Treating it like Boolean && |
Endpoint conjunction | intersect | Both sequence matches must share an endpoint | Using it when different endpoints are legal |
Span | expr throughout seq | Expression holds over every sample of the selected match | Forgetting the endpoint is included |
Containment | inner within outer | A complete inner match fits inside outer | Confusing containment with implication |
Implication | |->, |=> | Create a conditional property obligation | Claiming |=> serializes attempts |
Endpoint selection | first_match(seq), seq.ended | Select earliest match or observe a named endpoint | Confusing endpoint selection with transaction ownership |
Outcome control | disable iff, accept_on, reject_on | Abort or force an attempt outcome | Treating all three as reset |
Per-attempt state
A property-local variable belongs to one attempt.
- The comma introduces a sequence match item and captures the value when the trigger matches.
- Do not use a standalone assignment as if it were a sequence expression.
- One response can still satisfy multiple overlapping attempts. Use outstanding-ID state or a scoreboard for one-to-one accounting.
- Functions called from assertion expressions should be side-effect-free. Verify tool support for complex dynamic, class, handle, or real-valued constructs.
property p_tagged_response;
logic [ID_W-1:0] saved_id;
@(posedge clk) disable iff (!rst_n)
($rose(req), saved_id = req_id)
|-> ##[1:8] (rsp && rsp_id == saved_id);
endproperty
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
A sequence describes a temporal match. A property turns that match into a Boolean obligation, often with implication, reset, or logical composition. A directive such as assert, assume, or cover tells a tool what to do with the property.
Why it works
- Named sequences improve reuse, but a bare sequence used as a property fails whenever it does not match from a sampled start point.
- Implication is the usual way to say that a consequent matters only after an antecedent match.
- The same property can be asserted in one context, assumed at a formal boundary, or covered to measure reachability.
Whether you understand the semantic layers and avoid writing a bare temporal sequence when the intended requirement is conditional.
The expression err[*2] ##1 alert alone is not a conditional checker. It fails on every start cycle where that complete sequence does not match.
sequence s_two_err;
$rose(err) ##1 err;
endsequence
property p_alert_after_two_err;
@(posedge clk) disable iff (!rst_n)
s_two_err |=> alert;
endproperty
a_alert_after_two_err:
assert property (p_alert_after_two_err);
The ## operator concatenates sampled sequence elements by clock ticks, not simulator time units. ##n selects an exact sampled delay, while ##[m:n] permits a match at any sampled delay in the inclusive range.
Why it works
- ##0 starts the next sequence element at the current sequence endpoint. ##1 advances one sampling event.
- A range can create several possible match endpoints. Draw those endpoints before composing it with and, intersect, throughout, or implication.
- Use a finite range when the specification has a deadline. An unbounded upper limit may need strong semantics or a separate fairness assumption in formal analysis.
Whether you count sampled clock events from the antecedent endpoint instead of counting visual spaces or simulation time.
Do not read ##2 as two nanoseconds unless the property clock itself advances every nanosecond.
| Form | Meaning | Typical use |
|---|---|---|
##0 | Same sampled endpoint | Sequence composition |
##1 | Next sampling event | Next-cycle response |
##[1:3] | Any of the next one to three samples | Bounded latency |
##[1:$] | Some later sample | Unbounded eventual response |
or accepts a match from either operand. Sequence and starts both operands together and may finish when the later one ends. intersect also requires both operands to start together, but it requires the same endpoint. throughout and within constrain one sequence across another sequence's span.
Why it works
- Use intersect when identical endpoints are part of the requirement, not merely because two conditions must both happen.
- expr throughout seq requires expr on every sampled tick occupied by a selected match of seq.
- inner within outer requires a complete inner match to fit inside an outer match.
Whether you reason about start and end points rather than treating temporal and as ordinary Boolean conjunction.
Two sequences can both match and still fail intersect because their endpoints differ.
Sequence operators such as and, or, and intersect combine temporal matches and therefore have start and endpoint semantics. Property-level not, and, or, and if...else combine truth-valued property expressions. The spelling can look similar, but the object being combined is different.
Why it works
- Sequence and permits different endpoints, while sequence intersect requires a common endpoint; property and simply requires both property expressions to succeed.
- A property if...else selects one property branch from a sampled Boolean condition, which is useful when operating modes have different legal behavior.
- Named properties can be reused and may be recursive, but recursive properties are an advanced feature with progress and operator restrictions; use a positive time advance and verify tool support.
Whether you can identify the language layer before explaining an operator and avoid applying Boolean intuition to temporal endpoints.
Do not assume that sequence and, property and, and Boolean && have identical timing or completion semantics.
| Layer | Examples | Primary concern |
|---|---|---|
Boolean | &&, ||, ! | Value on one sampled edge |
Sequence | and, or, intersect | Temporal match and endpoint |
Property | not, and, or, if...else | Truth of an obligation |
Sequences and properties can be declared in modules, interfaces, programs, clocking blocks, packages, or compilation-unit scope where the language permits them. A bound checker attaches verification logic to a module type or instance without editing the RTL, which keeps reusable interface contracts separate from implementation code.
Why it works
- Place white-box assertions near internal invariants when design ownership allows it; place black-box protocol assertions at interfaces or in reusable checker modules.
- Binding a module type applies the checker to every matching instance, while an instance-specific bind targets one hierarchy location.
- Pass signals through explicit checker ports when possible. Uncontrolled hierarchical references make reuse and integration fragile.
Whether you can choose an ownership and deployment model for assertions instead of treating their file location as an afterthought.
bind changes where the checker is instantiated, not the timing semantics of its properties.
checker req_ack_checker (
input logic clk, rst_n, req, ack
);
a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] ack
);
endchecker
bind request_unit
req_ack_checker i_req_ack_checker (.*);
03 · Implication and timingPlace the consequent on the intended edge.
Draw the trace first, mark the antecedent endpoint, and only then choose overlapped or nonoverlapped implication and any explicit delay.
a |-> bOverlapped. b starts at the antecedent endpoint.
a |=> bNonoverlapped. b starts one sample later.
The operator changes one attempt's timing. It does not block a new antecedent from starting another attempt.
Always-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Conditional property
Implication creates an obligation only after an antecedent match.
| Antecedent | Consequent | Property result | Interpretation |
|---|---|---|---|
No match | Not evaluated | Vacuous success | No obligation was created |
Match | Success | Success | The obligation was met |
Match | Failure | Failure | The obligation was violated |
Match | Pending | Pending or end-of-run dependent | A future endpoint is still required |
Edge arithmetic
Start counting from the selected antecedent endpoint.
| Property | For a one-cycle a at sample t | b is checked |
|---|---|---|
a |-> b | Consequent starts at t | At t |
a |=> b | Consequent starts at t+1 | At t+1 |
a |-> ##1 b | Overlapped start plus one delay | At t+1 |
a |=> ##1 b | Nonoverlapped start plus one delay | At t+2 |
a |-> ##[1:3] b | Three legal endpoints | At t+1, t+2, or t+3 |
a_same_cycle: assert property (
@(posedge clk) req |-> ack
);
a_next_cycle: assert property (
@(posedge clk) req |=> ack
);
Candidate matches
Ranges create candidate endpoints, not a single scheduled check.
Bounded candidates
The first matching endpoint can satisfy a simple response property, but composition may preserve multiple candidate matches.
Earliest endpoint
Keep the earliest complete sequence match within one attempt. It does not reserve the response event globally.
Required completion
Make an eventual sequence fail if it cannot complete by the relevant end condition. Prefer a finite deadline when the architecture has one.
Independent attempts
A second trigger starts a second attempt unless a separate admission rule or outstanding-state model forbids it.
Worked trace
Convert the waveform into endpoints before writing code.
| Trigger | Contract | Observed response | Verdict |
|---|---|---|---|
req rises at 5 | exactly +2 | ack rises at 7 | Pass |
req rises at 5 | exactly +2 | ack rises at 6 | Fail: early response |
req rises at 5 | within 1..3 | ack rises at 8 | Pass at latest boundary |
req rises at 5 | within 1..3 | ack rises at 9 | Fail: late response |
req never rises | within 1..3 | ack never rises | Vacuous success; trigger cover is empty |
Interactive timing lab
Move the obligation across the trace.
Select a scenario. The sampled waveform, property, outcome, and explanation update together.
Overlapped implication checks the antecedent endpoint.
req and ack are both sampled high at clock 2. The consequent of req |-> ack begins on that same sampled edge, so this attempt passes.
a_same_cycle: assert property (
@(posedge clk) disable iff (!rst_n)
req |-> ack
);
Pass. req and ack are both sampled high at clock 2. The consequent of req |-> ack begins on that same sampled edge, so this attempt passes.
Waveform casebook
See the operator before memorizing its syntax.
Each column is a sampled positive clock edge. Read left to right: trigger, legal endpoint, then verdict.
Implication
A request receives its response on the next sampled edge.
$rose(req) |=> ackThe request edge at t2 creates an obligation at t3. ack is high there, so the attempt passes.
Pulse width
A pulse that drops on the next sample is too short.
$rose(pulse) |=> pulsepulse rises at t2 but is low at t3. Nonoverlapped implication checks the next sample and exposes the one-cycle-short pulse.
Backpressure
Valid and payload remain stable until ready accepts them.
valid && !ready |=> valid && $stable(data)The transfer stalls at t1 and t2. valid stays asserted and the Boolean predicate data == A stays true until ready rises at t3.
Consecutive repetition
The error condition holds on two adjacent samples.
$rose(err) |-> err[*2]The antecedent starts at t2. Because |-> overlaps, err[*2] consumes t2 and t3 and completes the required consecutive run.
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
Overlapped implication, |->, starts the consequent at the antecedent endpoint. Nonoverlapped implication, |=>, starts the consequent on the sampling event after the antecedent ends.
Why it works
- For a one-cycle antecedent, a |=> b aligns with a |-> ##1 b when both use the same property clock.
- For a multi-cycle antecedent, count from the selected antecedent match endpoint, not from its first cycle.
- An explicit ## delay belongs to the consequent sequence and shifts the actual Boolean test from that starting point.
Whether you can place the consequent exactly on a trace and state the limited case in which two formulations are equivalent.
Do not memorize |-> as current cycle and |=> as next cycle without considering a multi-cycle antecedent endpoint.
| Property | First b sample for one-cycle a | Meaning |
|---|---|---|
a |-> b | Same sampled edge | Overlapped |
a |=> b | One sampled edge later | Nonoverlapped |
a |-> ##1 b | One sampled edge later | Explicit delayed consequent |
No. Nonoverlapped describes where one attempt's consequent starts. Every matching antecedent can still launch another independent attempt on a later clock, even while an earlier attempt is pending.
Why it works
- Use $rose(start) when one attempt should begin per transaction edge instead of once per high cycle.
- To forbid a second request before completion, assert that serialization rule separately or model an outstanding bit or credit count.
- Pipelined protocols should usually allow overlapping attempts and correlate them with IDs, sequence numbers, or explicit state.
Whether you separate temporal alignment inside one attempt from admission control across several attempts.
Replacing |-> with |=> is not a no-overlap solution.
a_done_in_four: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(start) |-> ##[1:4] done
);
a_no_second_start: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(start) |=>
!$rose(start) until_with done
);
An implication succeeds vacuously when its antecedent does not match, because no consequent obligation was created. Add cover properties for the trigger and representative end-to-end matches so a green assertion is not mistaken for exercised behavior.
Why it works
- Vacuity is useful conditional logic, not automatically a bug.
- Cover the antecedent to prove that the requirement was activated in a simulation or is reachable in formal analysis.
- Cover useful boundary responses separately, such as the earliest and latest legal acknowledgement.
Whether you distinguish absence of a failure from evidence that the intended scenario occurred.
A passing request-to-acknowledge assertion proves nothing about response logic if request never rose.
a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] ack
);
c_request: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req)
);
c_latest_ack: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req) ##3 ack
);
04 · Sequence operatorsKnow what repeats, and where the match ends.
Distinguish consecutive, nonconsecutive, and goto repetition, then combine sequences only when their start and endpoint contracts are clear.
b[*3]Consecutiveb[=3]Gaps and trailing slackb[->3]Ends on final bAlways-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Repetition handbook
The operator defines both the hit pattern and the match endpoint.
| Form | Operand | Gaps | Where the match ends |
|---|---|---|---|
b[*3] | Boolean or sequence | No | On the third consecutive repetition |
b[*2:4] | Boolean or sequence | No | On a legal second, third, or fourth repetition |
b[=3] | Boolean expression | Yes | At or after the third hit; trailing non-b slack is allowed |
b[=3:5] | Boolean expression | Yes | After a legal third through fifth hit, with possible trailing slack |
b[->3] | Boolean expression | Yes | Exactly on the third hit |
b[->3:5] | Boolean expression | Yes | On a legal third through fifth hit |
Algebra
Expand repetition when the endpoint is not obvious.
a[*3]
// equivalent to
a ##1 a ##1 a
a ##1 b[*3] ##1 c
// equivalent to
a ##1 b ##1 b ##1 b ##1 c
(a ##2 b)[*3]
// equivalent to
(a ##2 b) ##1 (a ##2 b) ##1 (a ##2 b)
Endpoint contrast
Nonconsecutive repetition can wait after the last hit; goto cannot.
| Sequence | Legal sampled trace after a | Reason |
|---|---|---|
a ##1 b[=3] ##1 c | b · b · b · · c | Three b hits, then trailing non-b slack before c |
a ##1 b[=3] ##1 c | b · b · b b c | Illegal: an extra b occurs in the trailing region |
a ##1 b[->3] ##1 c | b · b · b c | Third b is the repetition endpoint; c is next sample |
a ##1 b[->3] ##1 c | b · b · b · c | Illegal: c is not immediately after the goto endpoint |
Sequence composition
Track start points, spans, and endpoints separately.
| Construct | Start relationship | Endpoint rule | Use |
|---|---|---|---|
s1 or s2 | One compound start | Either legal endpoint | Alternative temporal pattern |
s1 and s2 | Same compound start | Later successful endpoint | Both patterns, different lengths allowed |
s1 intersect s2 | Same compound start | Common endpoint required | Two views of one bounded interval |
expr throughout seq | expr begins with selected seq match | Includes every sample through seq endpoint | Hold condition over a known span |
inner within outer | inner may begin inside outer | Inner must complete before outer ends | Temporal containment |
first_match(seq) | Same as seq | Earliest complete match only | Deterministic endpoint selection |
named_seq.ended | Named sequence's own start | True on its match endpoint | Align another sequence to completion |
a_req_held_to_grant: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |->
req throughout (gnt[->1])
);
Waveform drills
Minimum width and consecutive-run properties need an event trigger.
| Requirement | Preferred property | Trace | Verdict |
|---|---|---|---|
At least 2 sampled clocks high | $rose(p) |=> p | p: 0 1 0 | Fail: low on the next sample |
At least 2 sampled clocks high | $rose(p) |=> p | p: 0 1 1 0 | Pass |
Exactly 2 sampled clocks high | $rose(p) |-> p[*2] ##1 !p | p: 0 1 1 1 0 | Fail: pulse is too long |
Alert after two err samples | ($rose(err) ##1 err) |=> alert | err: 0 1 1; alert next | One obligation per err run |
Alert for every adjacent pair | err[*2] |=> alert | err: 1 1 1 | Two overlapping pair obligations |
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
Consecutive repetition [*] requires adjacent sampled matches. Nonconsecutive repetition [=] counts possibly gapped occurrences and permits trailing nonmatch cycles in the sequence match. Goto repetition [->] also permits gaps, but the final occurrence ends the match.
Why it works
- b[*3] means b is true on three consecutive sampled clocks.
- b[=3] can include false samples between occurrences and after the third occurrence before a following concatenation aligns.
- b[->3] can include false samples between occurrences, but its match endpoint is the third true sample.
Whether you can predict the endpoint that a later ## delay or intersect operator will use.
Calling [=3] and [->3] equivalent loses the trailing-slack distinction.
| Operator | Gaps allowed | Match endpoint |
|---|---|---|
b[*3] | No | Third consecutive b |
b[=3] | Yes | May extend after third b |
b[->3] | Yes | Exactly on third b |
Trigger once on the rising edge and require the signal to remain high on the next sampled clock. Because the signal is already high on the trigger sample, $rose(p) |=> p establishes a minimum width of two sampled clocks.
Why it works
- This property allows the pulse to remain high longer because it does not require a falling edge after the second sample.
- For exactly two sampled clocks, add an explicit low check on the third sample.
- Define width in sampled clocks, not analog time, unless the specification requires a separate real-time checker.
Whether you count the trigger sample and distinguish minimum width from exact width.
An unbounded repetition such as p[*2:$] adds unnecessary match ambiguity when one next-cycle check states the minimum-width rule directly.
a_min_two: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(p) |=> p
);
a_exact_two: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(p) |-> p[*2] ##1 !p
);
Put the two-cycle err sequence in the antecedent, then imply alert on the following cycle. If the rule should fire once per contiguous err run, begin the antecedent with $rose(err).
Why it works
- ($rose(err) ##1 err) |=> alert checks alert one sampled clock after the second err sample.
- err[*2] |=> alert starts a new antecedent match for every adjacent pair in a long high run, which may be the intended stronger rule.
- A bare err[*2] ##1 alert sequence used as a property also fails on every unrelated start cycle, so it is not the conditional rule described.
Whether you can control attempt count and explain how a long run changes the number of obligations.
Do not omit implication when the English requirement begins with whenever or if.
a_alert_once_per_run: assert property (
@(posedge clk) disable iff (!rst_n)
($rose(err) ##1 err) |=> alert
);
expr throughout seq requires expr to remain true on every sampled tick occupied by the selected match of seq. It constrains the complete sequence span, including its endpoint.
Why it works
- The language defines the idea through unbounded consecutive repetition intersected with the sequence match.
- Use throughout when the requirement genuinely spans a known sequence. For ready-valid stability, small next-cycle invariants are often clearer and easier to debug.
- Be explicit about whether the endpoint is included when translating an until-style English requirement.
Whether you can identify the exact span that throughout covers and avoid hiding a simple invariant inside an unbounded sequence.
Do not read throughout as an implication trigger. It constrains a sequence match that must already be part of a property.
first_match(seq) keeps only the earliest complete match when a sequence has several legal endpoints. seq.ended is true on the sampled clock where that named sequence reaches a match endpoint, which lets another sequence align to completion rather than to the start.
Why it works
- A ranged delay such as ##[1:5] can create several candidate endpoints; first_match deliberately selects the earliest one.
- Use .ended when the contract is expressed relative to a named sequence's completion and the start time is not the relevant alignment point.
- Neither construct consumes a transaction event. Two overlapping property attempts can still use the same response unless separate accounting prevents it.
Whether you can distinguish endpoint selection inside one property attempt from ordering and one-to-one matching across attempts.
first_match does not prove FIFO order, reserve the earliest grant for the oldest request, or replace a scoreboard.
| Construct | What it selects | What it does not prove |
|---|---|---|
first_match(seq) | Earliest complete match | Transaction ownership |
seq.ended | The match endpoint cycle | Which attempt owns an event |
intersect | A common endpoint | FIFO ordering |
05 · Reset and meaningful proofMake every pass mean something.
Control attempt lifetime with reset, expose vacuity with coverage, and state the assumptions that separate useful formal proof from an overconstrained result.
Challenge the implementation.
Define a legal environment.
Demonstrate reachability.
Always-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Attempt lifetime
Abort, accept, reject, and block are different outcomes.
| Construct | Condition observation | Effect on active attempt | Typical use |
|---|---|---|---|
disable iff (x) | Asynchronous current value | Abort with no pass or failure | Reset invalidates transaction |
accept_on (x) | Asynchronous current value | Terminate as success | External event makes obligation acceptable |
reject_on (x) | Asynchronous current value | Terminate as failure | External event makes obligation invalid |
sync_accept_on (x) | Sampled on property clock | Terminate as success | Clocked acceptance policy |
sync_reject_on (x) | Sampled on property clock | Terminate as failure | Clocked rejection policy |
expect (property) | Property sampling semantics | Block process until success or failure | Procedural test scenario |
Reset release
Reset abort and post-reset startup are two separate contracts.
- Use |=> when the first protected sample is after the release edge.
- Use |-> only when the release edge itself is part of the protected interval.
- A sampled high reset level is not a release event. Use $rose(rst_n) for active-low reset deassertion.
- Synchronize asynchronous reset release when the design contract requires a clean first active clock.
a_quiet_after_reset: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(rst_n) |=>
(!enable && !data_valid)[*3]
);
Verification roles
Assert, assume, cover, and coverage reports answer different questions.
| Mechanism | Question | What it proves | What it does not prove |
|---|---|---|---|
assert property | Must this always hold? | No allowed trace violates the property in the analysis scope | The property fully represents the specification |
assume property | What may the environment do? | Only assumed-legal traces are explored in formal | The environment will drive that behavior in simulation |
cover property | Can this temporal scenario occur? | At least one trace reaches the scenario | All safety behavior is correct |
assertion coverage | Was the checker exercised? | Attempts, outcomes, and possible vacuity data | Value-space completeness |
covergroup | Which values and crosses occurred? | Bins and sampled combinations | A multi-cycle protocol path |
Meaningful proof
Treat a formal PASS as the end of an audit, not the start of a celebration.
Correct obligation
Review sampled edges, antecedent triggers, endpoints, and overlapping attempts against the written requirement.
Honest assumptions
Assume only behavior guaranteed by the real integration. Prove or review every assumption source.
Useful cover traces
Cover reset release, triggers, earliest and latest legal responses, and states needed to exercise the checker.
Completion policy
Prefer finite deadlines. Justify every unbounded eventuality and fairness assumption in plain language.
c_request_seen: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req)
);
c_latest_legal_ack: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req) ##3 $rose(ack)
);
c_state_path: cover property (
@(posedge clk) disable iff (!rst_n)
state == S0 ##1 state == S1
##1 state == S2
##1 state == S3
);
Diagnostics
Action blocks control regression response, not property truth.
| Task | Typical policy | Simulation effect |
|---|---|---|
$fatal | Cannot continue meaningfully | Report and terminate |
$error | Regression failure | Report error; simulator normally continues |
$warning | Suspicious but tolerated condition | Report warning |
$info | Diagnostic or progress message | Report information |
a_packet_length: assert property (
@(posedge clk) disable iff (!rst_n)
sop |-> length inside {[1:MAX_LEN]}
) begin
packet_checks_passed++;
end else begin
$error(
"bad length=%0d at packet start",
$sampled(length)
);
end
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
disable iff asynchronously disables a property while its expression is true and aborts active attempts without a pass or failure. The disable expression uses current values, not the sampled values used by the property body.
Why it works
- It is an attempt-lifetime control, not merely an antecedent that happens to mention reset.
- If reset asserts while a request-to-response attempt is pending, that attempt is terminated unless the specification says reset must preserve it.
- Reset release semantics still need a design contract. A synchronously released reset can avoid ambiguous first-cycle behavior in the design and its assertions.
Whether you distinguish asynchronous abort from a synchronously sampled reset condition.
Do not claim disable iff pauses an attempt and resumes it after reset.
a_req_ack: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] ack
);
Trigger on the sampled rising edge of active-low reset and use nonoverlapped implication when the quiet interval begins on the following sampled clock. Then repeat the required idle condition for the exact number of protected samples.
Why it works
- $rose(rst_n) |=> (!enable && !data_valid)[*3] protects the first three samples after the release edge.
- Keep disable iff (!rst_n) so attempts are aborted while reset is active, but do not assume it defines the post-reset startup contract.
- Decide whether the release edge itself counts. Choosing |-> instead of |=> moves the first protected sample onto that edge.
Whether you separate reset abort behavior from the temporal contract that begins when reset is released.
A reset disable clause alone does not guarantee any settling or idle cycles after deassertion.
a_quiet_after_reset: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(rst_n) |=>
(!enable && !data_valid)[*3]
);
assert checks a required property. assume constrains legal environment behavior for the analysis context. cover asks whether an interesting property can occur. Formal tools explore all traces allowed by assumptions, while simulation checks only the traces that ran.
Why it works
- At a formal block boundary, assume only behavior guaranteed by the real environment. Overconstraint can make a false design look proven.
- Use cover to test reachability and to find example traces for useful boundary scenarios.
- A clean simulation regression is evidence about those finite tests, not an exhaustive proof over all legal inputs and states.
- cover property measures a temporal scenario; it complements but does not replace covergroups, cross coverage, or the tool's assertion attempt/pass/fail coverage.
Whether you can define the proof boundary and audit assumptions rather than treating a formal PASS badge as self-explanatory.
Never turn an implementation obligation into an assumption simply to make the proof converge.
| Directive | Question answered | Main risk |
|---|---|---|
assert property | Must this always hold? | Wrong or incomplete property |
assume property | What may the environment do? | Overconstraint |
cover property | Can this scenario occur? | Confusing reachability with correctness |
cover property records that a Boolean or temporal sequence completed. Assertion coverage reports whether checker attempts started, passed, failed, or were vacuous. Covergroups sample value bins and crosses. Together they answer reachability, checker exercise, and data-space coverage questions.
Why it works
- Cover an implication antecedent separately when you need evidence that a passing checker was actually activated.
- Use temporal cover properties for protocol paths and latency boundaries, such as earliest and latest legal responses.
- Use covergroups when the requirement is about value combinations, distributions, or crosses rather than one temporal path.
Whether you can build a coverage plan that distinguishes a reachable behavior from a well-exercised checker and from value-space coverage.
A cover property hit proves that one scenario occurred; it does not prove the associated safety requirement is correct on every trace.
c_request_seen: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req)
);
c_latest_legal_ack: cover property (
@(posedge clk) disable iff (!rst_n)
$rose(req) ##3 ack
);
They matter when a property requires an eventual future match. A weak unbounded obligation can remain unfinished at the end of simulation, while formal liveness may need a justified fairness assumption to rule out an environment that postpones progress forever.
Why it works
- Prefer a finite deadline when the architecture defines one. It is easier to debug and usually easier to prove.
- Use strong around an eventual sequence when an unfinished end-of-simulation attempt must count as failure.
- State every fairness assumption in plain language and prove that the real environment actually supplies it.
Whether you separate bounded safety checks from genuine liveness and understand why time horizon and environment fairness affect the result.
Adding strong does not create a realistic deadline, and adding fairness does not repair an environment that can legally stall forever.
a_req_ack_bounded: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |->
strong(1'b1 ##[1:5] ack)
);
disable iff aborts an active property attempt without pass or failure. accept_on and reject_on terminate evaluation by forcing success or failure when their condition becomes true; sync_accept_on and sync_reject_on make that decision on the property's sampling event. An expect statement is a procedural concurrent assertion that blocks its process until the property succeeds or fails.
Why it works
- Use disable iff for reset-style invalidation, when an interrupted transaction should not be counted as either correct or incorrect.
- Use accept_on or reject_on only when the specification explicitly assigns a success or failure outcome to an external abort condition such as a flush.
- Because asynchronous controls observe current values, choose the sync form when the contract requires a sampled decision.
Whether you distinguish discarding an attempt from deliberately completing it with a result, and whether you know that expect has procedural blocking behavior.
Do not replace reset disable with accept_on just because both stop evaluation; one discards the attempt and the other declares success.
a_req_ack_or_flush: assert property (
@(posedge clk) disable iff (!rst_n)
accept_on (flush)
($rose(req) |-> ##[1:3] ack)
);
06 · Protocol patternsDecompose one interface contract at a time.
Check causality, bounded response, stability, exclusivity, and ordering separately so a failure points to one broken rule instead of one dense property.
grant has a requestNo orphan responserequest reaches grantBounded latencyvalid and data holdBackpressure safeone response per IDNo loss or duplicationOne dense property can hide which obligation failed. Small contracts produce better diagnostics and compose cleanly.
Always-visible reference
Learn the mechanism before testing the interview answer.
These notes, tables, examples, and design limits contain the working knowledge for this chapter. The interview questions below are a checkpoint, not the primary explanation.
Protocol method
Split one interface requirement into independent obligations.
No orphan response
Every response must have a legal outstanding cause.
Bounded progress
Each admitted request receives a response within the specified sampled window.
Offer remains valid
Control and payload remain stable while the receiver applies backpressure.
No loss or duplication
Each accepted request is consumed by exactly one matching response.
Recipe 01
Exact two-cycle request and acknowledgement.
a_req_one_cycle: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=> !req
);
a_ack_exact: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##2 $rose(ack)
);
a_ack_one_cycle: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(ack) |=> !ack
);
a_no_second_req: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=>
!$rose(req) until_with $rose(ack)
);
Recipe 02
Every request receives a grant within three sampled cycles.
- Using req instead of $rose(req) launches a new attempt on every high sample.
- Overlapping requests create overlapping attempts.
- One grant event may satisfy several attempts. Add IDs, credits, or outstanding-state accounting when that reuse is illegal.
- The finite upper bound already catches ordinary lateness. strong matters mainly for unfinished or unbounded eventuality semantics.
a_req_granted: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] $rose(gnt)
);
// Use ##[0:3] if same-edge grant is legal.
Recipe 03
Block a second request until the prior grant.
| Operator | Does p include q sample? | Must q eventually occur? |
|---|---|---|
p until q | No | No: weak |
p until_with q | Yes | No: weak |
p s_until q | No | Yes: strong |
p s_until_with q | Yes | Yes: strong |
a_single_outstanding: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=>
!$rose(req) until_with $rose(gnt)
);
Recipe 04
Hold valid and payload while stalled.
- ready may legally be high before valid. That is not an error unless the protocol forbids it.
- After an accepting valid && ready sample, the source may drop valid or present a new back-to-back transfer according to the protocol.
- $rose(valid) is not always one transaction when valid remains high across back-to-back accepted transfers.
- Do not make ready progress part of the source stability property unless the system contract guarantees a sink deadline.
a_hold_valid: assert property (
@(posedge clk) disable iff (!rst_n)
valid && !ready |=> valid
);
a_hold_payload: assert property (
@(posedge clk) disable iff (!rst_n)
valid && !ready |=> $stable(payload)
);
Recipe 05
Allow five stalled samples and fail on the sixth.
| Sampled stall trace | Verdict | Reason |
|---|---|---|
S S S S S R | Pass | Five stalls, resolved on the sixth sample |
S S S S S S | Fail | The sixth consecutive stalled sample is illegal |
S S R | No failure | The five-sample antecedent never completes |
S S S ready=1 | No failure | Acceptance resolves the stall early |
sequence s_stall;
valid && !ready;
endsequence
a_stall_at_most_five: assert property (
@(posedge clk) disable iff (!rst_n)
s_stall[*5] |=> !s_stall
);
Recipe 06
A grant needs a corresponding request model.
| Protocol shape | Useful check | Limitation |
|---|---|---|
req held through gnt | $rose(gnt) |-> req | Only valid when request is still asserted |
one pulsed req, bounded grant | $rose(gnt) |-> recent request history | History does not consume the request |
single outstanding | Track outstanding bit | Cannot represent multiple requests |
K outstanding | Credit or pending counter | Counts do not prove identity |
tagged outstanding | ID set or scoreboard | Must handle reuse and duplicate responses |
Recipe 07
Correlation is not consumption, and first_match is not FIFO.
- first_match chooses the earliest endpoint within one attempt. It does not choose the globally oldest request.
- Unique live IDs can prove identity without requiring FIFO when out-of-order responses are legal.
- A scalar counter can prove balance and bounds, but not which request a response belongs to.
- For unbounded eventual response, strong handles end-of-simulation completion; formal liveness also needs justified fairness.
property p_matching_response;
logic [ID_W-1:0] saved_id;
@(posedge clk) disable iff (!rst_n)
($rose(req), saved_id = req_id)
|-> ##[1:8]
($rose(rsp) && rsp_id == saved_id);
endproperty
// For one-to-one or FIFO behavior:
// 1. push every accepted req_id into a reference queue
// 2. require rsp_id == queue.front() when FIFO is required
// 3. pop exactly once per accepted response
// 4. assert no response when the queue is empty
Interview checkpoint
Explain the rule, then defend its edge cases.
Open a prompt after studying the reference above. A strong answer should name the sampled edge, match endpoint, failure mode, and proof evidence.
Split the contract into an exact response edge, response pulse shape, and admission rule. Separate properties produce clearer failures and let the interface decide whether a new request is legal on the acknowledgement edge.
Why it works
- $rose(req) |=> !req checks that request itself is a one-cycle sampled pulse.
- $rose(req) |-> ##2 $rose(ack) checks an acknowledgement edge exactly two sampled clocks later.
- $rose(ack) |=> !ack checks that acknowledgement is a one-cycle sampled pulse.
- If no request may arrive until acknowledgement, express that policy separately with until or explicit outstanding state and decide whether the endpoint permits a simultaneous new request.
- Add reverse causality or outstanding-state accounting when an unsolicited acknowledgement must also fail.
Whether you decompose latency, pulse width, and serialization instead of assuming one implication proves all three.
A response-timing property alone does not reject an early extra acknowledgement or a second request.
a_req_one_cycle: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=> !req
);
a_ack_exact: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##2 $rose(ack)
);
a_ack_one_cycle: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(ack) |=> !ack
);
a_no_new_req: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=>
!$rose(req) until_with $rose(ack)
);
Trigger once on the request edge and allow a grant on any of the next three sampled edges: $rose(req) |-> ##[1:3] $rose(gnt). Use ##[0:3] if same-edge grant is legal. This checks latency, not transaction ownership.
Why it works
- Using req instead of $rose(req) launches a new attempt on every sampled cycle that request remains high.
- Overlapping requests create independent property attempts, which is correct for a pipelined timing requirement.
- One grant may satisfy several attempts unless an ID, credit, or outstanding-state model prevents event reuse.
- The finite three-cycle upper bound already catches normal lateness. strong is mainly relevant to unfinished or unbounded eventuality semantics.
Whether you can encode the deadline, decide if cycle zero is legal, and distinguish response timing from one-to-one accounting.
Changing |-> to |=> shifts each consequent; it does not prevent overlapping requests.
a_req_granted: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |-> ##[1:3] $rose(gnt)
);
// Use ##[0:3] if same-edge grant is legal.
Assert admission separately from response timing. $rose(req) |=> !$rose(req) until_with $rose(gnt) forbids a new request through and including the grant sample. Endpoint-excluding until permits same-edge readmission. Use a strong-until form when this property must also require grant eventually.
Why it works
- until and until_with are weak: the right-hand event is not required to occur. s_until and s_until_with require eventual completion.
- until excludes the endpoint from the left-hand requirement; until_with includes it.
- Pair admission control with a bounded grant property so serialization does not hide a missing response.
- For up to K outstanding requests, track a pending or credit count and assert its legal bounds instead of forbidding all overlap.
Whether you can separate admission, progress, endpoint policy, and outstanding capacity.
An implication operator alone does not serialize property attempts or consume a grant.
a_single_outstanding: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(req) |=>
!$rose(req) until_with $rose(gnt)
);
| Operator | Left side includes grant sample | Grant required |
|---|---|---|
until | No | No |
until_with | Yes | No |
s_until | No | Yes |
s_until_with | Yes | Yes |
When valid is sampled high and ready low, require valid to remain high and payload to remain stable on the next sampled clock. Repeating this one-step invariant on every stalled edge naturally holds the transaction until acceptance.
Why it works
- The accepting edge is valid && ready. The source must not withdraw or alter the offered transfer before that edge.
- Use case equality or explicit unknown checks when X or Z on control signals must be reported rather than treated optimistically.
- A separate bounded-stall property belongs to the system's forward-progress contract, not the source stability rule.
Whether you distinguish source obligations from sink progress and keep payload stable through the accepting sample.
Checking only valid misses data corruption while stalled. Requiring ready to rise is invalid unless the environment guarantees progress.
a_hold_valid: assert property (
@(posedge clk) disable iff (!rst_n)
valid && !ready |=> valid
);
a_hold_payload: assert property (
@(posedge clk) disable iff (!rst_n)
valid && !ready |=> $stable(payload)
);
Treat valid && !ready as the stall condition. If it is true for five consecutive sampled clocks, require it to be false on the next sample. A shorter stall never completes the five-cycle antecedent, so it creates no failure.
Why it works
- The property checks the progress limit separately from the source's obligation to hold valid and payload stable.
- With a five-cycle maximum, the sixth consecutive stalled sample is the first illegal one.
- Only assert a sink-progress deadline if the architecture guarantees one; otherwise the environment may legally hold ready low forever.
Whether you can count the allowed stalled samples and distinguish bounded progress from data stability.
Triggering only on valid can accidentally count cycles before backpressure begins or ignore a legal early valid withdrawal policy.
sequence s_stalled;
valid && !ready;
endsequence
a_stall_at_most_five: assert property (
@(posedge clk) disable iff (!rst_n)
s_stalled[*5] |=> !s_stalled
);
If request remains asserted through grant, gnt |-> req is enough. If request is a pulse and grant may arrive later, track outstanding state or check a bounded request history. The property must match the protocol's ownership model.
Why it works
- A same-cycle expression cannot remember a one-cycle request that legitimately preceded grant.
- A bounded lookback window proves recent causality but does not consume a request or prevent duplicate grants.
- An outstanding bit, credit counter, or ID-indexed scoreboard can enforce one-to-one accounting when several transactions may be active.
Whether you recognize when a temporal lookback is enough and when the protocol needs explicit state.
gnt |-> req falsely rejects a legal delayed grant when request is only a pulse.
logic [2:0] req_history;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) req_history <= '0;
else req_history <= {req_history[1:0], req};
end
a_grant_has_request: assert property (
@(posedge clk) disable iff (!rst_n)
$rose(gnt) |->
(req || |req_history)
);
A property-local variable can capture a request tag separately for every attempt and require a later matching response. That proves value correlation for each attempt, but it does not consume a response. Reused tags or overlapping attempts can let one response satisfy several obligations, so one-to-one and FIFO order need explicit state or a scoreboard.
Why it works
- Declare the local variable with the exact interface tag type and assign it in a sequence match item.
- Require unique outstanding tags if the temporal property relies on tag identity alone.
- first_match chooses one endpoint within an attempt. It does not reserve that grant for the oldest request or stop another attempt from matching the same event.
Whether you understand attempt-local storage and the boundary between temporal correlation and transaction accounting.
first_match(##[1:$] gnt) does not prove FIFO order for multiple in-flight requests.
typedef logic [ID_W-1:0] tag_t;
property p_tagged_response;
tag_t saved_id;
@(posedge clk) disable iff (!rst_n)
($rose(req), saved_id = req_id)
|-> strong(1'b1 ##[1:8]
(rsp && rsp_id == saved_id));
endproperty
a_tagged_response:
assert property (p_tagged_response);
// Also enforce unique outstanding IDs, or use
// a scoreboard for one-to-one and FIFO order.
Interview synthesis
Trace it. State the contract. Prove the scenario happened.
- 01Draw sampled edges
Mark the antecedent endpoint and every legal response edge.
- 02Separate obligations
Check causality, timing, stability, and accounting independently.
- 03Audit the evidence
Cover the trigger, review reset aborts, and state formal assumptions.
