Part 4 · Assertions (SVA) · Intermediate
first_match() & Sequence Endpoints
Thread explosion from multiple matches, first_match pruning, and .triggered/.matched endpoint detection.
The multiple-match problem
Every ranged operator multiplies match threads: ##[1:8] forks 8, [*2:5] forks 4, and nesting multiplies them. Multiple matches are not an error — but they bite in two places. First, performance : each live thread is simulator state, and a level antecedent retriggering into a wide range can hold thousands of threads alive. Second, semantics : when a sequence's endpoint feeds something else (a consequent suffix, a .triggered test, a not), every match thread propagates, and the property must hold for the threads you did not intend.
THREAD EXPLOSION — req ##[1:4] ack ##[1:4] done (one attempt)
attempt tick T
├─ ack@T+1 ─┬─ done@T+2
│ ├─ done@T+3
│ ├─ done@T+4
│ └─ done@T+5
├─ ack@T+2 ─┬─ done@T+3 ... (4 threads)
├─ ack@T+3 ─┬─ ... (4 threads)
└─ ack@T+4 ─┬─ ... (4 threads)
= 16 live threads PER ATTEMPT
A level antecedent attempting every cycle for 100 cycles
→ up to 1600 concurrent threads from one assertion.first_match: pruning to the earliest completion
first_match(s) matches only the earliest-ending match of s from a given attempt; the moment any thread of s completes, all other threads of that attempt are killed. If several threads end on the same earliest tick, those ties all survive — first_match prunes by end time, not to a single thread. Use it when you mean "the first time this pattern completes" and when a multi-match sequence's endpoint must be unique for downstream logic.
// Without first_match: every ack in the window spawns a thread
sequence s_any_ack; req ##[1:8] ack; endsequence
// With first_match: only the EARLIEST ack ends the sequence
sequence s_first_ack; first_match(req ##[1:8] ack); endsequence
// Endpoint-sensitive use: 'done' must follow the FIRST ack
property p_done_after_first_ack;
@(posedge clk) $rose(req) |->
first_match(req ##[1:8] ack) ##1 done;
endproperty
a_fa: assert property (p_done_after_first_ack);
// Without first_match, done after ANY ack-thread endpoint passes —
// a later spurious ack could legitimize a late done.WAVEFORM — first_match(req ##[1:8] ack) (attempt @2)
clk : 1 2 3 4 5 6 7 8
req : 0 1 0 0 0 0 0 0
ack : 0 0 0 1 0 1 0 0
A M1 m2
plain sequence : matches at tick 4 AND tick 6 (two endpoints)
first_match : tick 4 thread completes first → MATCH @4,
all sibling threads killed → m2 never reported
Effect on '##1 done' suffix with done only at tick 7:
plain: endpoint @6 exists → done@7 → PASS (maybe unintended)
first_match: endpoint @4 only → done@5? no → FAIL (strict)Sequence endpoints: .triggered and .matched
A named sequence exposes its endpoint as a boolean: s.triggered is true in any time step where some evaluation of s reaches its endpoint in that step (sampled, usable in expressions, including disable iff conditions and other sequences). s.matched serves the same endpoint-detection purpose for multi-clocked contexts: it persists the endpoint across to the next tick of a destination clock, making it the clock-domain-crossing flavor. Endpoint methods let one sequence act as an event for another property without re-describing the pattern.
sequence s_wr_burst; // endpoint = last beat of a write burst
wr_start ##1 wr_beat [->4];
endsequence
// Use the endpoint inside another property (same clock): .triggered
property p_resp_after_burst;
@(posedge clk) s_wr_burst.triggered |-> ##2 wr_resp;
endproperty
a_resp: assert property (p_resp_after_burst);
// Cross-clock endpoint hand-off: .matched
sequence s_src; @(posedge clk_a) val_a ##1 done_a; endsequence
sequence s_pair; @(posedge clk_b) s_src.matched ##1 done_b; endsequence
a_cdc: assert property (@(posedge clk_b) start_b |-> s_pair);WAVEFORM — s_wr_burst.triggered as an event
clk : 1 2 3 4 5 6 7 8
wr_start : 0 1 0 0 0 0 0 0
wr_beat : 0 0 1 1 0 1 0 0
A b1 b2 b4*
.triggered: 0 0 0 0 0 1 0 0
E
E = endpoint tick: the [->4] completes at tick 6 → s.triggered
is 1 exactly there, 0 everywhere else.
The downstream property treats E as a one-tick event:
s.triggered |-> ##2 wr_resp → wr_resp required @8.Interview angle and review
Interview angle
"What does first_match actually kill?" — sibling threads of the SAME attempt, at the first completed end tick; ties at that tick all survive. "It picks one random match" is the wrong answer.
"When is first_match required, not just an optimization?" — when the sequence endpoint feeds downstream timing (suffix elements, .triggered consumers) and only the earliest completion is meaningful.
"triggered vs matched?" — both detect endpoints; .triggered is the general same-timestep form, .matched persists across clocks for multi-clock sequence pairing.
"Estimate the threads of req ##[1:4] ack ##[1:4] done." — 16 per attempt; multiply the ranges. Interviewers use this to test whether you think in threads at all.
Key takeaways
Ranged operators multiply match threads; nested ranges multiply multiplicatively — count them.
first_match prunes an attempt to its earliest-ending match(es) and kills the sibling threads.
first_match changes meaning, not just speed, whenever the sequence endpoint drives downstream timing.
s.triggered turns a sequence endpoint into a boolean event; s.matched is the multi-clock variant.
Common pitfalls
Wrapping first_match around a consequent just to silence multiple-match warnings — verify the earliest endpoint is really the spec's intent.
Believing first_match always yields exactly one thread — same-tick ties all survive the pruning.
Using .triggered on a sequence with ranged operators and assuming a unique fire tick — it pulses at EVERY endpoint unless first_match'd.
Using .triggered across clock domains where .matched is required — endpoint sampled in the wrong domain's time step.
Ignoring thread cost of level-retriggered wide ranges — assertion-dominated simulation slowdowns that profile as "mysterious".