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. Valid/Ready Handshake

Part 4 · Assertions (SVA) · Intermediate

Valid/Ready Handshake

Valid/ready handshake assertions: stability, payload hold, X-safety, bounded liveness and covers.

On this page

    Why this matters

    A DMA engine dropped one word in ten thousand under back-pressure. The producer changed its data during a stall, and no rule watched the stalled cycle.

    After this lesson you can: write and wire a valid/ready checker: hold, payload stability, X-safety, bounded acceptance and traffic covers.

    The handshake contract

    A transfer happens on any cycle where valid and ready are both high. Once valid rises, the producer holds valid and the payload steady until ready accepts.

    The top two rows make the offer; the multi-bit row carries the data word.

    • Slot 0: idle: valid 0, ready 1, no offer.

    • Slot 1: valid rises with data 5; ready 0, so the offer waits.

    • Slot 2: stall continues; valid and data hold their values.

    • Slot 3: ready rises; data 5 is accepted.

    • Slot 4: valid drops; the bus idles for one cycle.

    • Slot 5: back-to-back traffic starts: data 6 accepted at once.

    • Slot 6: data 7 accepted; the throughput case the covers must record.

    Rule

    Assertion shape

    Why

    Valid holds

    (valid && !ready) |-> ##1 valid

    Retraction voids an unaccepted offer.

    Payload holds

    (valid && !ready) |-> ##1 $stable(data)

    Mutation during stall corrupts the accepted word.

    X-safety

    valid |-> !$isunknown(data)

    Unknown bits propagate into every consumer.

    Bounded acceptance

    valid |-> ##[0:MAX_STALL] ready

    An offer must be taken within a known bound.

    Traffic covers

    Back-to-back; stall then accept

    Covers prove the checker saw real traffic.

    The checker module

    The cover writes ready == 1'b0 inside the repetition: Verilator 5.048 rejects a unary ! inside a sequence, 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.

    vr_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
    module vr_checker #(
      parameter int  DW          = 32,
      parameter int  MAX_STALL   = 64
    )(
      input logic           clk,
      input logic           rst_n,
      input logic           valid,
      input logic           ready,
      input logic [DW-1:0]  data,
      input logic           last
    );
      // Every rule reports through fail(): it counts and prints, and the run goes on.
      // (Verilator stops at the first $error, so a counting report keeps one bench
      // able to plant several bugs. Production checkers often do the same.)
      int n_fail = 0, n_b2b = 0, n_stall = 0;
      function automatic void fail(string msg);
        n_fail++;
        $display("[%0t] vr_checker: %s", $time, msg);
      endfunction
    
      // R1: valid holds until accepted (covers retraction too)
      a_valid_stable: assert property (@(posedge clk) disable iff (!rst_n)
        (valid && !ready) |-> ##1 valid)
        else fail("valid retracted before ready");
    
      // R2: payload frozen while the offer is pending
      a_payload_stable: assert property (@(posedge clk) disable iff (!rst_n)
        (valid && !ready) |-> ##1 ($stable(data) && $stable(last)))
        else fail("payload changed during stall");
    
      // R3: no X on payload while it is being offered (fires on 4-state simulators only)
      a_no_x: assert property (@(posedge clk) disable iff (!rst_n)
        valid |-> (!$isunknown(data) && !$isunknown(last)))
        else fail("X on payload while valid");
    
      // R4: bounded liveness, accepted within MAX_STALL cycles
      a_accepted: assert property (@(posedge clk) disable iff (!rst_n)
        valid |-> ##[0:MAX_STALL] ready)
        else fail("transfer not accepted within bound");
    
      // Cover the scenarios that prove the checker saw real traffic
      c_b2b:   cover property (@(posedge clk)
        (valid && ready) ##1 (valid && ready)) n_b2b++;
      c_stall: cover property (@(posedge clk)
        (valid && ready == 1'b0) [*3] ##1 (valid && ready)) n_stall++;
    endmodule
    
    Terminal
    legal traffic: 0 failures; covers: back-to-back 1, stall-then-accept 1
    [165] vr_checker: valid retracted before ready
    [185] vr_checker: payload changed during stall
    after one retraction and one payload change: 2 failures
    [275] vr_checker: transfer not accepted within bound
    [285] vr_checker: transfer not accepted within bound
    [295] vr_checker: transfer not accepted within bound
    after a 9-cycle stall against a bound of 6: 5 failures (3 reports for one late offer)
    PASS: legal traffic passed with both covers hit, and the checker reported each planted bug

    The producer-side dependency rule is not checkable from the wires. A producer that waits for ready before raising valid makes traces that also read as legal, so the checker covers valid rising first and relies on the bounded rule to flag the deadlock symptom.

    Simulation uses the bounded window form. The unbounded s_eventually form belongs in formal tools, where it is a genuine liveness obligation.

    Common mistakes

    Common pitfall

    Asserting $stable(data) in the same cycle valid rises compares against the pre-offer value and misfires. Phrase stall rules as (valid && !ready) |-> ##1 ... so the obligation lands on the next sample.

    Interview

    “Write the SVA for a valid/ready channel.”

    • Rules first: valid hold, payload hold, X-safety, bounded acceptance, two covers.

    • Nuance: the ready-dependency is not black-box checkable; cover valid before ready instead.

    • Form: bounded ##[0:N] in simulation, s_eventually in formal.

    Check yourself valid rises with new data on the same cycle. Which rule fires, and why is silence correct?

    None fires. The hold rules compare a stalled cycle against the next sample, so the first offered value is never compared against pre-offer garbage.

    Check yourself ready never rises after valid. Which assertion reports, and when?

    The bounded acceptance rule reports exactly MAX_STALL cycles after the offer. The unbounded form would only surface at end of test.

    What the run shows

    Legal traffic, including the diagram's stall and a back-to-back run, gives no reports and hits both covers. A retraction and a payload change each give one report, from the rule that owns them. The late offer gives three: R4's antecedent is the level valid, so each stalled cycle opened its own window, and the first three ran out of time. That is the level-antecedent cost from the Implication lessons, seen on a real checker.

    R3 never fires on Verilator, which is 2-state: an X on data becomes 0 or 1 before any check sees it. Keep R3; it earns its place on a 4-state simulator.

    Remember

    • Stall rules start from valid && !ready and look one tick ahead.

    • A level antecedent reports one late offer several times.

    • Covers prove the checker saw back-to-back and stalled traffic.

    Rule of thumb: a checker is done when legal traffic is clean, its covers hit, and every planted bug is reported.

    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

    119 / 259

    Next: Req/Ack + Pulse Discipline

    Up next

    Up nextReq/Ack + Pulse Discipline

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • FIFO Rulesets
    • FSM Rulesets
    • Pipelines, Stall, Credits
    clkvalidreadydata05067
    A valid/ready offer stalled for two cycles, then back-to-back transfers