Part 4 · Assertions (SVA) · Intermediate

Reset & X-Checking Drills

Reset-value checks, X-freedom after reset deassertion, deassertion deadlines, bus-known-when-valid, and init-before-first-transaction ordering.

Drill 1 — outputs hold reset values during reset

Problem

Interviewer: 'While reset is asserted, valid must be low and state must be IDLE. Careful — most candidates write a property that can never fire.'

Think it through

  • The trap: disable iff (!rst_n) KILLS attempts while reset is low — adding it here aborts the very check you are writing. Reset-value properties must NOT carry the usual disable clause.

  • Active-low rst_n: the in-reset condition is !rst_n. Plain overlapped implication, no temporal depth.

  • If reset is asynchronous, the assertion still samples at the clock — pin-level async behavior between edges is out of SVA's reach; say so.

systemverilog
// NOTE: deliberately NO disable iff here
a_reset_values: assert property (
  @(posedge clk) !rst_n |-> (!valid && (state == IDLE)));
diagram
PASS trace                          FAIL trace
cycle :  0  1  2  3  4              cycle :  0  1  2  3  4
rst_n :  0  0  0  1  1              rst_n :  0  0  0  1  1
valid :  0  0  0  0  1              valid :  0  1  0  0  1
state :  I  I  I  I  R              state :  I  I  I  I  R

cycles 0-2 in reset: valid low,     valid pops high at cycle 1
state IDLE  PASS                   DURING reset  FAIL at 1.
                                    (with disable iff (!rst_n)
                                    this bug is INVISIBLE)

Common wrong answers

  • disable iff (!rst_n) ... !rst_n |-> ... — the property self-disables; it can never report. The single most common reset-drill error.

  • Checking only at $fell(rst_n) — verifies the entry cycle, not the whole reset duration.

  • Mixing polarities — rst_n |-> reset-values checks during NORMAL operation. Write the polarity down before the property.


Drill 2 — no X on control signals after reset deasserts

Problem

Interviewer: 'Once reset deasserts, the control bundle (valid, ready, state) must never be X or Z. Both the one-shot check at the deassert edge and the continuous version, please.'

systemverilog
// One-shot: first post-reset cycle is clean
a_no_x_at_exit: assert property (
  @(posedge clk)
  $rose(rst_n) |=> !$isunknown({valid, ready, state}));

// Continuous: clean on every out-of-reset cycle
a_no_x_ever: assert property (
  @(posedge clk) disable iff (!rst_n)
  !$isunknown({valid, ready, state}));

Think it through + waveform

  • $isunknown(expr) is true if ANY bit is X or Z — concatenate the bundle and check once.

  • The one-shot uses |=> : at the $rose(rst_n) cycle itself, flops are still emerging from reset; the contract starts the following cycle. Defend the choice.

  • The continuous form uses disable iff legitimately (we WANT it silent during reset) — contrast with Drill 1 where the same clause was fatal.

diagram
FAIL trace
cycle :  0  1  2  3  4
rst_n :  0  0  1  1  1
valid :  0  0  0  X  0     ← un-reset flop wakes up at 3

a_no_x_ever: attempt at cycle 3 sees $isunknown true  FAIL at 3.
Classic root cause: a flop missing from the reset tree, visible
only because the X-check made it loud.

Common wrong answers

  • (valid !== 1'bx) — element-by-element !== checks miss Z and get verbose; $isunknown on the concatenation is the idiom.

  • Checking X during reset too — many designs legally float pre-reset; scoping to post-reset keeps signal-to-noise high.

  • Relying on implicit X-propagation through comparisons elsewhere — X == anything is X, which makes ordinary assertions VACUOUSLY pass, not fail. Explicit $isunknown checks are the only reliable X net.


Drill 3 — busy deasserts within N cycles of reset assertion

Problem

Interviewer: 'When reset asserts mid-operation, the busy flag must clear within 4 cycles. Note which clock domain and which disable clause you use.'

systemverilog
// Triggered BY reset assertion — so again, no disable iff (!rst_n)
a_busy_clears: assert property (
  @(posedge clk)
  $fell(rst_n) |-> ##[0:4] !busy);
diagram
PASS trace                          FAIL trace
cycle :  0  1  2  3  4  5           cycle :  0  1  2  3  4  5
rst_n :  1  0  0  0  0  0           rst_n :  1  0  0  0  0  0
busy  :  1  1  1  0  0  0           busy  :  1  1  1  1  1  1

fall@1; busy low by cycle 3         busy still high at 5 = fall+4
(within fall+4)  PASS              window [0:4] exhausted  FAIL.

Think it through + common wrong answers

  • The property RUNS during reset, so disable iff (!rst_n) would abort it instantly — same lesson as Drill 1, now with a temporal window.

  • ##[0:4] includes the assert-cycle itself: a design that clears busy combinationally on reset passes at offset 0.

  • Wrong: $rose(rst) on an active-low rst_n — polarity slips are endemic in this family; one trace drawing catches them.

  • If the clock can stop during reset (gated clocks), a clocked property cannot count — flag the assumption that clk keeps running.


Drill 4 — bus must be known when valid

Problem

Interviewer: 'Whenever valid is high, the 64-bit data bus must contain no X or Z bits. When valid is low we don't care. One property.'

systemverilog
a_data_known: assert property (
  @(posedge clk) disable iff (!rst_n)
  valid |-> !$isunknown(data));

Think it through + common wrong answers

  • Qualification IS the spec: unqualified !$isunknown(data) fails on every idle cycle of a bus that legally floats between beats.

  • This pattern (valid-qualified known-ness) is the cheapest, highest-yield X-check in any interface bind file — say that; it shows methodology awareness.

  • Wrong: valid |-> (data !== 64'bx) — !== against all-X only detects the ALL-X pattern... actually data !== 64'bx is true unless every bit is X; one X bit among knowns slips through. $isunknown is per-bit. This subtlety is a favorite follow-up.

  • Wrong: ^data === 1'bx as the check — works (XOR-reduce goes X if any bit is X/Z), but inverted logic and Z-handling subtleties make it a worse answer than the purpose-built function.


Drill 5 — first transaction only after init done

Problem

Interviewer: 'After reset deasserts, the block runs an init sequence and raises init_done. No valid may appear before init_done. Give me the invariant form AND the sequence form, and say which you'd ship.'

systemverilog
// Form 1: invariant — ship this one
a_no_valid_pre_init: assert property (
  @(posedge clk) disable iff (!rst_n)
  !init_done |-> !valid);

// Form 2: sequence form anchored at reset exit
property p_valid_waits;
  @(posedge clk)
  $rose(rst_n) |-> (!valid) until_with init_done;
endproperty
a_valid_waits: assert property (p_valid_waits);
diagram
FAIL trace (premature valid)
cycle    :  0  1  2  3  4  5
rst_n    :  0  1  1  1  1  1
init_done:  0  0  0  0  1  1
valid    :  0  0  1  0  0  1

Form 1: cycle 2 has !init_done && valid  FAIL at 2.
Form 2: window opens at rise@1; !valid must hold through
        cycle 4 (until_with) — violated at 2  FAIL at 2.
valid at 5 (after init_done) is legal in both forms.

Think it through + common wrong answers

  • Both forms catch the bug, but Form 1 is stateless, re-arms automatically if init_done ever drops (re-init), and survives mid-sim resets without anchoring logic. Prefer invariants whenever the spec allows.

  • Form 2's until_with assumes valid may legally rise IN the init_done cycle; if not, use until — extract that detail from the spec aloud.

  • Wrong: $rose(rst_n) |-> ##[1:$] init_done ##1 valid — over-constrains: demands a valid eventually appears, which the spec never required, and (weakly) checks nothing about EARLY valids.

Key takeaways

  • Properties that CHECK reset behavior must not carry disable iff on that same reset — the clause silently kills them.

  • $isunknown over a concatenation is the X-check idiom; !== comparisons against all-X literals miss partial-X buses.

  • Prefer stateless invariants (!init_done |-> !valid) over anchored sequences when both express the spec — they re-arm for free.

Common pitfalls

  • disable iff (!rst_n) on an in-reset check — the most common reset-family error in interviews and in real bind files.

  • X never fails ordinary comparisons — X == X is X, implications go vacuous; only explicit $isunknown makes X loud.

  • Polarity slips between rst, rst_n, $rose, $fell — draw the trace before writing the edge function.

  • Assuming the clock runs during reset — gated-clock designs make clocked reset checks blind; state the assumption.