Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
      • Cycle Delays: ##N and ##[M:N]
      • Repetition: [*], [->], [=]
      • Composition: and, or, intersect, throughout, within
      • first_match() & Sequence Endpoints
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Sequences: Temporal Building Blocks
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Sequences: Temporal Building Blocks
  5. first_match() & Sequence Endpoints
On this page

    Part 4 of 9, lesson 9 of 41Part 4 of 9/Assertions (SVA)/Lesson 9 of 41Intermediate

    first_match() & Sequence Endpoints

    Cutting thread explosion with first_match and reusing sequence endpoints with triggered and matched.

    A done-after-ack check passed although done followed the wrong ack. Without first_match, any ack in the window satisfied it.

    After this lesson you can: count the threads a sequence forks, keep only the earliest match, and reuse a sequence's endpoint as an event.

    Ranges multiply threads

    Every ranged operator multiplies match threads. Two nested ranges multiply them together, so counting threads is a basic review skill.

    Level antecedents make it worse. An attempt every cycle into a wide range holds hundreds of threads alive at once.

    Construct

    Threads per attempt

    Note

    ##[1:8]

    8

    One per delay

    [*2:5]

    4

    One per run length

    ##[1:4] then ##[1:4]

    16

    Ranges multiply

    Level antecedent over 100 cycles

    Up to 1600

    Attempts multiply too

    Check yourself How many live threads does one attempt of req ##[1:4] ack ##[1:4] done hold?

    Sixteen. Four ack choices times four done choices, before any retriggering multiplies further.

    Keep the earliest match

    first_match(s) keeps only the earliest-ending match of s from one attempt. The moment any thread completes, its siblings die.

    Ties survive together. Threads ending on the same earliest tick all pass the pruning, so first_match prunes by end time rather than to a single thread.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // snippet
    sequence s_any_ack;   req ##[1:8] ack;              endsequence
    sequence s_first_ack; first_match(req ##[1:8] ack); endsequence
    
    property p_done_after_first_ack;
      // edge-anchored in real checkers; see antecedent-design
      @(posedge clk) $rose(req) |->
        first_match(req ##[1:8] ack) ##1 done;
    endproperty
    // Without first_match, done after ANY ack-thread endpoint passes.

    Both ack ticks match the plain sequence, and only the first survives pruning. A ##1 done suffix would then time from slot 3 alone.

    • Slot 0: idle, req, ack, and the endpoint flag all 0.

    • Slot 1: req rises, and the window opens.

    • Slot 2: quiet tick inside the window.

    • Slot 3: first ack, where both the plain scan and the pruned scan report an endpoint.

    • Slot 4: quiet tick.

    • Slot 5: second ack, where only the plain scan reports again.

    • Slot 6: both low again.

    first_match changes meaning, not just speed, whenever the endpoint drives downstream timing. Wrapping a consequent just to silence multiple-match warnings can bless an endpoint the specification never meant.

    Check yourself Two threads end on the same earliest tick under first_match. How many survive?

    Both. Same-tick ties all survive, since pruning compares end times rather than picking one thread.

    Endpoints as events

    A named sequence exposes its endpoint as a boolean. s.triggered is true in any time step where an evaluation of s reaches its end, and other properties can consume it as an event.

    s.matched is the multi-clock variant. It carries the endpoint across to the next tick of a destination clock instead of the same time step.

    Both listings below are not run in this course's checks. Verilator 5.048 has no .triggered or .matched, and it rejects a clock inside a sequence declaration.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // not-run: Verilator 5.048 has no .triggered
    sequence s_wr_burst;            // endpoint = last beat of a write burst
      wr_start ##1 wr_beat [->4];
    endsequence
    
    // .triggered pulses at EVERY endpoint of a multi-match sequence.
    // For the earliest endpoint only, wrap first_match in a named
    // sequence (s_first; first_match(s); endsequence) and use s_first.triggered.
    property p_resp_after_burst;
      @(posedge clk) s_wr_burst.triggered |-> ##2 wr_resp;
    endproperty
    design.sv
    1
    2
    3
    4
    5
    6
    // not-run: Verilator 5.048 has no .matched and rejects a clock inside a sequence declaration
    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
    property p_cdc;
      @(posedge clk_b) start_b |-> s_pair;
    endproperty

    Form

    Endpoint meaning

    Use

    s.triggered

    True where s ends, same time step

    Same-clock event reuse

    s.matched

    Endpoint carried to the next destination tick

    Cross-clock hand-off

    s_first.triggered, where s_first is first_match(s)

    Earliest endpoint of each attempt

    One event tick per attempt

    Likely questions: “What does first_match kill?” and “triggered versus matched?”

    • Siblings die: threads of the same attempt, at the first completed end tick, with ties surviving.

    • Same step versus next tick: triggered reports in the ending step, and matched persists across clocks.

    • Count on demand: multiply the ranges to estimate threads per attempt.

    Check yourself A ranged sequence feeds .triggered into a downstream check, and the sequence has three endpoints. How many times does the event fire?

    Three, once per endpoint. For one tick per attempt, declare a named sequence around first_match(s) and use its .triggered. first_match(s).triggered is not legal, because .triggered needs a named sequence instance.

    Testbench

    The bench opens 15 windows, each with acks at +2 and +5. In 10 of them done follows the first ack; in 5, planted, it follows only the second. The rule, done after the first ack, is checked twice: with first_match as written above, and with a flag set by modeling code on the first ack.

    Predict first A cover property on $rose(req) ##[1:8] ack sees two acks in each window. Does it count 15 or 30?

    15. A cover property counts once per attempt that matched, however many endpoints the attempt had. Counting every endpoint is what cover sequence is for, and Verilator 5.048 ignores cover sequence.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    module tb_first_match;
      logic clk = 0, req = 0, ack = 0, done = 0;
      always #5 clk = ~clk;
      int any_hits = 0, fm_hits = 0, fm_fails = 0, flag_fails = 0;
    
      // Covers: an ack somewhere in the window, and the first ack followed by done.
      cp_any: cover property (@(posedge clk) $rose(req) ##[1:8] ack) any_hits++;
      cp_fm:  cover property (@(posedge clk) $rose(req) ##0 first_match(##[1:8] ack) ##1 done) fm_hits++;
      // The rule: done must follow the FIRST ack.
      ap_fm: assert property (@(posedge clk) $rose(req) |-> first_match(##[1:8] ack) ##1 done) else fm_fails++;
      // Modeling code marks the first ack; a one-step property checks done.
      logic waiting = 0;
      always @(posedge clk)
        if ($rose(req)) waiting <= 1;
        else if (ack) waiting <= 0;
      wire first_ack = waiting && ack;
      ap_flag: assert property (@(posedge clk) first_ack |=> done) else flag_fails++;
    
      // A window with acks at offsets a1 and a2 after req, and done at offset d.
      task automatic window(int a1, int a2, int d);
        @(negedge clk) req = 1;
        @(negedge clk) req = 0;
        for (int c = 1; c <= 9; c++) begin
          ack = (c == a1 || c == a2);
          done = (c == d);
          @(negedge clk);
        end
        ack = 0; done = 0;
        repeat (3) @(negedge clk);
      endtask
    
      initial begin
        repeat (2) @(negedge clk);
        for (int t = 0; t < 10; t++) window(2, 5, 3);   // done follows the first ack
        for (int t = 0; t < 5; t++)  window(2, 5, 6);   // planted: done follows only the second ack
        $display("15 windows, each with acks at +2 and +5: cp_any %0d, cp_fm %0d (10 expected)", any_hits, fm_hits);
        $display("5 windows with done after the second ack: first_match assert %0d failures, flag version %0d", fm_fails, flag_fails);
        if (any_hits != 15 || flag_fails != 5) $fatal(1, "FAIL: any %0d flag %0d", any_hits, flag_fails);
        $display("PASS: the cover counted one match per window, and the first-ack flag caught all 5 planted bugs");
        $finish;
      end
    endmodule
    Terminal
    15 windows, each with acks at +2 and +5: cp_any 15, cp_fm 0 (10 expected)
    5 windows with done after the second ack: first_match assert 0 failures, flag version 5
    PASS: the cover counted one match per window, and the first-ack flag caught all 5 planted bugs

    On Verilator 5.048, first_match(...) followed by more sequence matched nothing, as an assert or a cover, so the first_match check reported 0 failures and its cover 0 hits. The flag version is four lines of modeling code and a one-step property, and it caught all 5. That pattern, a few flops that name the event plus a simple property on top, is the most portable assertion style there is.

    • Ranges multiply threads; level antecedents multiply attempts.

    • first_match keeps the earliest end time; ties survive.

    • A flag set by modeling code is the portable first-event marker.

    Rule of thumb: when downstream timing hangs off an endpoint, name which endpoint.

    How this was checked

    Run with Verilator 5.048 (--binary --timing --assert, plus --coverage when a listing has a cover property) on 2026-10-18; every bench printed the PASS line shown. Verilator stops at the first $error or failing assertion without an action block, so the benches count failures in the action block. Icarus 13 has no concurrent assertions.

    first_match(...) followed by more sequence matched nothing on Verilator 5.048, as an assert (0 of 5 planted bugs) or a cover; the modeling-code flag caught 5 of 5. cover sequence was ignored with a COVERIGN warning.

    Related topics

    • Sequence pattern drills

    • Antecedent design

    Previous

    111 / 275

    Next: Implication and Property Operators

    Up next

    Up nextImplication and Property Operators

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Cycle Delays: ##N and ##[M:N]
    • Repetition: [*], [->], [=]
    clkreqackfirst_end
    Two acks in one window: the full scan keeps two endpoints, earliest-only keeps one