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
      • Valid/Ready Handshake
      • Req/Ack + Pulse Discipline
      • FIFO Rulesets
      • FSM Rulesets
      • Pipelines, Stall, Credits
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Protocol Assertion Cookbook
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Protocol Assertion Cookbook
  5. Req/Ack + Pulse Discipline

Part 4 · Assertions (SVA) · Intermediate

Req/Ack + Pulse Discipline

Req/ack assertions: hold, bounded ack window, one pulse per request and spacing.

On this page

    Why this matters

    A missed-ack report led a team to the responder for a day. The first report in the log, a request dropped early by the requester, was the real cause.

    After this lesson you can: check a req/ack protocol phase by phase: hold, window, spurious ack, pulse width, release and spacing.

    The req/ack contract

    The requester raises req and holds it. The responder answers with a one-cycle ack inside a bounded window.

    After the ack, req falls and the bus stays quiet for a gap. Each rule polices one phase of that shape.

    Watch req hold until ack, then fall; the quiet slots delimit the gap.

    • Slot 0: idle: req 0, ack 0.

    • Slot 1: req rises; the hold obligation begins.

    • Slot 2: req held; the window is still open.

    • Slot 3: ack pulses; latency is 2 cycles.

    • Slot 4: req released in the next cycle.

    • Slot 5: quiet; first gap cycle.

    • Slot 6: quiet; second gap cycle.

    • Slot 7: the next request may rise.

    The checker module

    Inside a sequence the checker writes x == 1'b0 instead of !x: Verilator 5.048 rejects a unary ! after ##, although IEEE 1800 allows it.

    The listing is one file you can paste into the Code Lab: the checker first, then a small design and a bench that runs legal traffic before planting one bug at a time. Every rule reports through fail(), which counts and prints the report and lets the run go on; Verilator 5.048 stops at the first $error, and production checkers often count the same way.

    req_ack_checker.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
    module req_ack_checker #(
      parameter int ACK_MIN = 1,
      parameter int ACK_MAX = 8,
      parameter int GAP     = 2,
      parameter int REL     = 1
    )(
      input logic clk, rst_n,
      input logic req,
      input logic ack
    );
      int n_fail = 0, n_min = 0, n_max = 0;
      function automatic void fail(string msg);
        n_fail++;
        $display("[%0t] req_ack_checker: %s", $time, msg);
      endfunction
    
      // R1: req held until ack
      a_req_hold: assert property (@(posedge clk) disable iff (!rst_n)
        (req && !ack) |-> ##1 req)
        else fail("req dropped before ack");
    
      // R2: ack arrives inside the window after the request rises
      a_ack_window: assert property (@(posedge clk) disable iff (!rst_n)
        $rose(req) |-> ##[ACK_MIN:ACK_MAX] ack)
        else fail("ack missed window");
    
      // R3: no ack without a pending request
      a_no_spurious_ack: assert property (@(posedge clk) disable iff (!rst_n)
        ack |-> req)
        else fail("ack with no pending req");
    
      // R4: ack is a single-cycle pulse
      a_ack_pulse: assert property (@(posedge clk) disable iff (!rst_n)
        ack |-> ##1 (ack == 1'b0))
        else fail("ack wider than one cycle");
    
      // R5: pulse-ack release within REL cycles
      a_req_release: assert property (@(posedge clk) disable iff (!rst_n)
        (req && ack) |-> ##[1:REL] (req == 1'b0))
        else fail("req not released after ack");
    
      // R6: the quiet gap starts the cycle after the fall
      a_req_spacing: assert property (@(posedge clk) disable iff (!rst_n)
        $fell(req) |-> ##1 (req == 1'b0) [*GAP])
        else fail("re-request inside gap");
    
      c_min_lat: cover property (@(posedge clk)
        $rose(req) ##ACK_MIN ack) n_min++;
      c_max_lat: cover property (@(posedge clk)
        $rose(req) ##ACK_MAX ack) n_max++;
    endmodule
    
    Terminal
    3 legal transactions: 0 new report(s)
    covers: minimum latency hit 1, maximum latency hit 1
    [245] req_ack_checker: req dropped before ack
    [275] req_ack_checker: ack missed window
    req dropped after one cycle (hold, then the window echo): 2 new report(s)
    [345] req_ack_checker: ack missed window
    ack after 6 cycles against a window of 4: 1 new report(s)
    [415] req_ack_checker: ack with no pending req
    ack with no request: 1 new report(s)
    [495] req_ack_checker: re-request inside gap
    re-request after 1 quiet cycle against a gap of 2: 1 new report(s)
    PASS: legal traffic hit both covers, and each planted bug was reported by the rule that owns it

    The level form req |-> ##[1:N] ack spawns one window thread per held cycle. The edge form $rose(req) spawns exactly one thread per transaction.

    In a failure cascade the earliest report is the root cause. An early req drop trips the hold rule first, and the window rule echoes later.

    Pulse discipline

    Three widths hide under one name. Exact-one is a rise followed by a fall; minimum holds for N cycles; maximum falls within N cycles.

    The band form composes a floor with a ceiling. All four are clocked-sample rules, blind to sub-cycle glitches.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    // a_exact1: assert property (@(posedge clk) disable iff (!rst_n)
    //   $rose(kick) |-> ##1 !kick);
    // a_min: assert property (@(posedge clk) disable iff (!rst_n)
    //   $rose(p) |-> p[*MIN]);
    // a_max: assert property (@(posedge clk) disable iff (!rst_n)
    //   $rose(p) |-> ##[1:MAX] !p);
    // a_band: assert property (@(posedge clk) disable iff (!rst_n)
    //   $rose(p) |-> (p[*MIN] ##[1:MAX-MIN+1] !p));

    Common mistakes

    Common pitfall

    Writing the window as req |-> ##[1:N] ack with a level antecedent spawns one overlapping thread per held cycle and duplicates failures. Use $rose(req) for exactly one thread per transaction.

    Interview

    “req must be acked within N cycles.”

    • Core: $rose(req) |-> ##[1:N] ack.

    • Follow-ups: hold, spurious, pulse, release, spacing.

    • Variants: four-phase level ack; per-tag tracking for many outstanding requests.

    Check yourself An ack arrives while req is low. Which rule fires?

    The no-spurious rule. The window rule alone cannot see it, since it only watches cycles after a rise.

    Check yourself The protocol uses four-phase level ack. Which two rules must change?

    The no-spurious-ack rule and the single-cycle pulse rule. Four-phase ack rises after req rises, stays high until after req falls, then falls. So ack is wider than one cycle and is still high while req is low, and both rules fail on legal traffic. Replace them with “ack holds until req falls” and “ack falls after req falls”. The release rule still holds.

    What the run shows

    Three legal transactions give no reports and hit both latency covers. A request dropped after one cycle gives two reports in order: the hold rule first, then the window rule four cycles later. The first report is the root cause, and the second is its echo. A late ack, an ack with no request and a re-request inside the gap each give one report, from the rule that owns that phase.

    Remember

    • Anchor the window on $rose(req): one thread per transaction.

    • In a failure cascade, the earliest report is the root cause.

    • Four-phase protocols change the spurious and pulse rules.

    Rule of thumb: read the first report before reading any other.

    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.

    Previous

    120 / 259

    Next: FIFO Rulesets

    Up next

    Up nextFIFO Rulesets

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • FSM Rulesets
    • Pipelines, Stall, Credits
    clkreqack
    A req/ack transaction inside window [1:8] with a 2-cycle gap before the next rise