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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
      • Handshake Drills
      • Pulse & Stability Drills
      • Ordering & Counting Drills
      • Data & Reset Drills
      • FSM & Arbiter Drills
      • Q&A: Assertion Fundamentals
      • Q&A: Operators & Semantics
      • Q&A: Vacuity, Debug & Formal
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹SVA Interview Drills
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. SVA Interview Drills
  5. Handshake Drills

Part 4 · Assertions (SVA) · Intermediate

Handshake Drills

Drill: ack at an exact latency, req held until ack, and no orphan ack on a pulsed protocol.

On this page

    Why this matters

    A candidate wrote req |-> ##3 ack for a held request and could not explain why it failed three times for one transfer. The antecedent was a level.

    After this lesson you can: write exact-latency, hold-until-ack and orphan-ack checks under time pressure, and prove them against planted bugs.

    The three prompts

    Drill 1. req_a and ack_a: whenever req_a rises, ack_a is high exactly 3 cycles later.

    Drill 2. req_b and ack_b: once req_b is high it stays high until ack_b arrives, and still high in the ack_b cycle.

    Drill 3. req_c and ack_c: req_c is a one-cycle pulse and ack_c comes some cycles later. An ack_c with no request outstanding is a bug.

    Write the three assertions before opening the reveal. Copy the starter into the Code Lab, replace each 1'b1 with your property (add modeling code above it if you need state), and run it on Verilator with assertions on. The grader runs legal traffic, where your assertions must stay silent, then one planted bug per drill, which yours must report. If an answer uses a repetition, or a range followed by more steps, and the grader says it missed its bug, read the tool notes on the sequences pages: Verilator 5.048 misses some failures in those forms.

    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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    // SVA drill starter: write each property (and any modeling code), then run it.
    // SVA drills: handshakes. Each drill has its own signals.
    module tb_sva_handshake;
      logic clk = 0, rst_n = 0;
      always #5 clk = ~clk;
      logic req_a = 0, ack_a = 0;   // drill 1: ack exactly 3 cycles after req rises
      logic req_b = 0, ack_b = 0;   // drill 2: req held until ack, overlapping it
      logic req_c = 0, ack_c = 0;   // drill 3: req is a 1-cycle pulse; no ack without one outstanding
      int f1 = 0, f2 = 0, f3 = 0;
    
      // Your answers: one property per drill, plus any modeling code you need.
      ap_drill1: assert property (@(posedge clk) disable iff (!rst_n)
        1'b1   // replace 1'b1 with your property
      ) else f1++;
      ap_drill2: assert property (@(posedge clk) disable iff (!rst_n)
        1'b1   // replace 1'b1 with your property
      ) else f2++;
      ap_drill3: assert property (@(posedge clk) disable iff (!rst_n)
        1'b1   // replace 1'b1 with your property
      ) else f3++;
    
      // The grader: legal traffic first, then one planted bug per drill.
      task automatic hs_a(int lat);
        @(negedge clk) req_a = 1;
        @(negedge clk) req_a = 0;
        repeat (lat - 1) @(negedge clk);
        ack_a = 1; @(negedge clk) ack_a = 0;
        repeat (3) @(negedge clk);
      endtask
      task automatic hs_b(bit early_drop);
        @(negedge clk) req_b = 1;
        repeat (2) @(negedge clk);
        if (early_drop) begin req_b = 0; @(negedge clk); end
        ack_b = 1; @(negedge clk) ack_b = 0; req_b = 0;
        repeat (3) @(negedge clk);
      endtask
      task automatic hs_c(bit extra_ack);
        @(negedge clk) req_c = 1;
        @(negedge clk) req_c = 0;
        @(negedge clk) ack_c = 1;
        @(negedge clk) ack_c = 0;
        if (extra_ack) begin @(negedge clk) ack_c = 1; @(negedge clk) ack_c = 0; end
        repeat (3) @(negedge clk);
      endtask
      function automatic int verdict(string name, int clean, int caught);
        if (clean != 0) begin $display("%s: wrong, it fired %0d times on legal traffic (too strict)", name, clean); return 1; end
        if (caught == 0) begin $display("%s: wrong, it missed its planted bug (too loose)", name); return 1; end
        $display("%s: ok", name);
        return 0;
      endfunction
    
      initial begin
        int c1, c2, c3, wrong;
        repeat (2) @(negedge clk);
        rst_n = 1;
        repeat (3) hs_a(3);
        repeat (2) hs_b(0);
        repeat (2) hs_c(0);
        c1 = f1; c2 = f2; c3 = f3;
        hs_a(2);                       // planted: ack one cycle early
        hs_b(1);                       // planted: req dropped before ack
        hs_c(1);                       // planted: an ack nobody asked for
        wrong = verdict("drill 1, ack exactly 3 cycles later", c1, f1 - c1)
              + verdict("drill 2, req held until ack", c2, f2 - c2)
              + verdict("drill 3, no orphan ack", c3, f3 - c3);
        if (wrong != 0) $fatal(1, "FAIL: %0d of 3 drills wrong", wrong);
        $display("PASS: all 3 drills right");
        $finish;
      end
    endmodule
    Predict first Before you write drill 3: what goes wrong with ack_c |-> req_c?

    On a pulsed protocol req_c is already low when the legal ack arrives, so the assertion fires on good traffic. The pins no longer hold the protocol state; a flop of modeling code has to remember the outstanding request.

    Check yourself Reveal one set of answers (open only after your own run)

    One set of answers, run through the same grader:

    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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // SVA drills: handshakes. Each drill has its own signals.
    module tb_sva_handshake;
      logic clk = 0, rst_n = 0;
      always #5 clk = ~clk;
      logic req_a = 0, ack_a = 0;   // drill 1: ack exactly 3 cycles after req rises
      logic req_b = 0, ack_b = 0;   // drill 2: req held until ack, overlapping it
      logic req_c = 0, ack_c = 0;   // drill 3: req is a 1-cycle pulse; no ack without one outstanding
      int f1 = 0, f2 = 0, f3 = 0;
    
      // Your answers: one property per drill, plus any modeling code you need.
      ap_drill1: assert property (@(posedge clk) disable iff (!rst_n)
        $rose(req_a) |-> ##3 ack_a                                    // ANSWER
      ) else f1++;
      ap_drill2: assert property (@(posedge clk) disable iff (!rst_n)
        (req_b && !ack_b) |=> req_b                                   // ANSWER
      ) else f2++;
      logic outstanding = 0;                                          // ANSWER
      always @(posedge clk)                                           // ANSWER
        if (req_c) outstanding <= 1; else if (ack_c) outstanding <= 0; // ANSWER
      ap_drill3: assert property (@(posedge clk) disable iff (!rst_n)
        ack_c |-> outstanding || req_c                                // ANSWER
      ) else f3++;
    
      // The grader: legal traffic first, then one planted bug per drill.
      task automatic hs_a(int lat);
        @(negedge clk) req_a = 1;
        @(negedge clk) req_a = 0;
        repeat (lat - 1) @(negedge clk);
        ack_a = 1; @(negedge clk) ack_a = 0;
        repeat (3) @(negedge clk);
      endtask
      task automatic hs_b(bit early_drop);
        @(negedge clk) req_b = 1;
        repeat (2) @(negedge clk);
        if (early_drop) begin req_b = 0; @(negedge clk); end
        ack_b = 1; @(negedge clk) ack_b = 0; req_b = 0;
        repeat (3) @(negedge clk);
      endtask
      task automatic hs_c(bit extra_ack);
        @(negedge clk) req_c = 1;
        @(negedge clk) req_c = 0;
        @(negedge clk) ack_c = 1;
        @(negedge clk) ack_c = 0;
        if (extra_ack) begin @(negedge clk) ack_c = 1; @(negedge clk) ack_c = 0; end
        repeat (3) @(negedge clk);
      endtask
      function automatic int verdict(string name, int clean, int caught);
        if (clean != 0) begin $display("%s: wrong, it fired %0d times on legal traffic (too strict)", name, clean); return 1; end
        if (caught == 0) begin $display("%s: wrong, it missed its planted bug (too loose)", name); return 1; end
        $display("%s: ok", name);
        return 0;
      endfunction
    
      initial begin
        int c1, c2, c3, wrong;
        repeat (2) @(negedge clk);
        rst_n = 1;
        repeat (3) hs_a(3);
        repeat (2) hs_b(0);
        repeat (2) hs_c(0);
        c1 = f1; c2 = f2; c3 = f3;
        hs_a(2);                       // planted: ack one cycle early
        hs_b(1);                       // planted: req dropped before ack
        hs_c(1);                       // planted: an ack nobody asked for
        wrong = verdict("drill 1, ack exactly 3 cycles later", c1, f1 - c1)
              + verdict("drill 2, req held until ack", c2, f2 - c2)
              + verdict("drill 3, no orphan ack", c3, f3 - c3);
        if (wrong != 0) $fatal(1, "FAIL: %0d of 3 drills wrong", wrong);
        $display("PASS: all 3 drills right");
        $finish;
      end
    endmodule
    Terminal
    drill 1, ack exactly 3 cycles later: ok
    drill 2, req held until ack: ok
    drill 3, no orphan ack: ok
    PASS: all 3 drills right

    Why these answers

    “Whenever req rises” is an edge, so $rose(req_a): a level antecedent would start a fresh attempt every cycle req_a stays high. “Exactly 3 cycles later” is the fixed delay ##3; a range would accept early acks. And $rose(req_a) |=> ##3 ack_a counts the delay twice, checking cycle T+4.

    For drill 2, (req_b && !ack_b) |=> req_b says each cycle without the ack is followed by one with req_b still high, which carries req_b into the ack cycle. $rose(req_b) |-> req_b until_with ack_b says the same in one sentence. Neither demands that the ack ever comes: that is a separate bounded check.

    Drill 3 needs state because the pins forget. A flop sets on the request and clears on the ack, and the property is one line on top. Modeling code next to an assertion is normal, and saying so is a senior signal.

    Common pitfall

    Stacking |=> with ##N counts the delay twice: |=> already moves one cycle, so $rose(req) |=> ##3 ack checks T+4. Count every delay on a drawn trace before declaring done.

    Interview

    Likely follow-up: “req may stay high for several cycles, and up to 2 requests may be outstanding.” Say that pure sequence operators stop scaling here, reach for a counter in modeling code, and explain why.

    Remember

    • Edge antecedent for one obligation per request.

    • |=> plus ##N counts the delay twice.

    • When the pins forget the protocol, a flop of modeling code remembers it.

    Rule of thumb: write the property, then break it on a drawn trace before saying done.

    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.

    The starter was run with every property left as 1'b1, and its grader reported all three drills wrong; the revealed answers passed the same grader.

    Related topics

    • Pulse & Stability Drills

    Previous

    133 / 259

    Next: Pulse & Stability Drills

    Up next

    Up nextPulse & Stability Drills

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Ordering & Counting Drills
    • Data & Reset Drills
    • FSM & Arbiter Drills