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

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
    • Reset Architecture
    • Metastability and Synchronisation
    • Asynchronous FIFO
    • CDC Protocols
    • Reset Domain Crossing
    • CDC Verification
      • Rules
      • Simulation
      • Assertion Patterns
      • FIFO Tests
      • Signoff
      • Reading a CDC Report
      • A CDC Mini Project
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹CDC Verification
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. CDC Verification
  5. Assertion Patterns

Part 6 · Reset & CDC · Intermediate

Assertion Patterns

Handshake assertions and a source-clock Gray-pointer check with an explicit binding contract.

On this page

    Why this matters

    A request that drops before its ack, or a Gray pointer that moves two bits, is a protocol bug no structural CDC check sees. Four small properties catch these, and a source-side Gray check applied to destination samples fires falsely.

    After this lesson you can: write the four CDC protocol properties (request holds, data stable, ack needs request, Gray one bit) and bind each to the right clock.

    Four protocol properties

    Every crossing carries protocol rules. Requests hold until acknowledged, and data stays stable under request.

    Acknowledgements answer requests, and source Gray pointers move one bit per step. The listing below drives a legal Gray sequence in which 5 destination samples legally skip Gray steps; a mutation that makes the source Gray pointer jump two bits fires a_gray_one_bit.

    Warning

    Bind the Gray check to the registered source pointer and its source clock/reset, once for each FIFO direction. A slow destination may skip intermediate Gray values: 000 -> 001 -> 011 at the source can appear as 000 -> 011 at the destination, changing two observed bits legally. Never apply the source one-bit-step assertion to successive destination samples. The handshake examples below share one clock for illustration; in a real crossing bind source req/data with ack_sync to the source clock, and destination ack with req_sync to the destination clock. Raw asynchronous controls must not be mixed into a single-clock checker.

    Each slot is one source clock cycle (5 ns); clk is the destination clock at half the rate. src_bits and dst_bits count the bits that changed since the previous value in each domain.

    • Slots 0 to 7: gray_src steps 000, 001, 011, 010, 110, 111, 101, 100, and from slot 1 on src_bits is always 1. The source property holds.

    • Slot 3: the destination samples 011 after 000. It never saw 001, so two bits changed and dst_bits is 2.

    • Slots 5 and 7: every later sample also changes two bits (110, then 101). A one-bit check on dst_q would fire on every sample of a correct design.

    Violation driven

    Property that fired

    Message

    req drops before ack

    a_req_holds

    Assertion failed in …dut.a_req_holds

    Data bus changes while req is high

    a_data_stable

    Assertion failed in …dut.a_data_stable

    ack rises with no preceding req

    a_ack_needs_req

    Assertion failed in …dut.a_ack_needs_req

    Gray pointer changes two bits at once

    a_gray_one_bit

    Assertion failed in …dut.a_gray_one_bit

    More patterns

    Verilator supports the common SVA subset: implication, $past, $stable, $rose and fixed delays. Older Verilator releases did not support ranged delays such as ##[1:5]; Verilator 5.048 runs them with --timing --assert. Check your tool version.

    Keep checks in the simple subset so they run in every assertion-capable flow (Verilator with --assert, commercial simulators, formal).

    Check

    SVA sketch

    Pulse spacing at least N cycles

    pulse |=> !pulse [*N-1] (needs sequence repetition; or a counter)

    One-hot select

    $onehot0(sel) (or $onehot(sel) when exactly one must be set)

    FIFO never overflows

    wfull |-> !winc_accepted

    Valid stays until ready

    valid && !ready |=> valid && $stable(data)

    Reset leaves protocol idle

    $fell(rst_n) |=> !req && !ack

    Common mistakes

    Tip

    Bind the checker module to the design with bind so the assertions live in a verification file, not in the RTL. Use the same checker in simulation and in formal runs.

    Common pitfall

    default disable iff (!rst_n) stops the properties from firing during reset. If your reset behaviour is itself a protocol (as in RDC), write explicit properties for the reset and keep them enabled.

    Check yourself Why does a_data_stable check $past(req) as well as req?

    To avoid flagging the very first cycle of the request, in which the data may legitimately change from the previous value. The rule only applies once the request has been high for a cycle.

    Testbench

    The checker module plus a testbench driving a legal handshake and a legal Gray sequence. Run it with Verilator (verilator --binary --timing --assert); Icarus cannot parse concurrent assertions.

    cdc_checks.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
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    // not-run: concurrent assertions and default clocking need Verilator (verilator --binary --timing --assert); Icarus 13 cannot parse them
    // Handshake signals are local to clk. The Gray pointer has its own source clock/reset.
    module cdc_checks (
      input logic        clk, rst_n,
      input logic        req, ack,
      input logic [7:0]  data,
      input logic        src_clk, src_rst_n,
      input logic [4:0]  gray_src
    );
      default clocking cb @(posedge clk); endclocking
      default disable iff (!rst_n);
    
      // 1. A request stays high until it is acknowledged.
      a_req_holds: assert property (req && !ack |=> req);
    
      // 2. The data bus is stable for as long as req is high.
      a_data_stable: assert property (req && $past(req) |-> $stable(data));
    
      // 3. An acknowledge only appears in answer to a request.
      a_ack_needs_req: assert property ($rose(ack) |-> $past(req));
    
      gray_source_checks g (.src_clk, .src_rst_n, .gray_src);
    endmodule
    
    // Separate clocking scope prevents the handshake clock from sampling the source pointer.
    module gray_source_checks (
      input logic src_clk, src_rst_n,
      input logic [4:0] gray_src
    );
      default clocking cb @(posedge src_clk); endclocking
      default disable iff (!src_rst_n);
      logic src_past_valid;
      always_ff @(posedge src_clk or negedge src_rst_n)
        if (!src_rst_n) src_past_valid <= 1'b0;
        else           src_past_valid <= 1'b1;
      a_gray_one_bit: assert property (
        src_past_valid |-> !$isunknown(gray_src) &&
          $countones(gray_src ^ $past(gray_src)) <= 1);
    endmodule
    
    module tb_cdc_checks;
      logic clk = 0, rst_n = 0, req = 0, ack = 0;
      logic [7:0] data = 0;
      logic src_clk = 0, src_rst_n = 0;
      logic [4:0] src_bin = 0, gray_src = 0, dst_g1 = 0, dst_g2 = 0, dst_prev = 0;
      int skipped_steps = 0;
      cdc_checks dut (.*);
      always #5 clk = ~clk;
      always #1 src_clk = ~src_clk;
      initial begin
        repeat (2) @(negedge src_clk);
        src_rst_n = 1;
      end
      always_ff @(posedge src_clk)
        if (!src_rst_n) begin src_bin <= '0; gray_src <= '0; end
        else begin
          src_bin <= src_bin + 5'd1;
          gray_src <= (src_bin + 5'd1) ^ ((src_bin + 5'd1) >> 1);
        end
      always_ff @(posedge clk)
        if (!rst_n) begin dst_g1 <= '0; dst_g2 <= '0; dst_prev <= '0; end
        else begin
          dst_g1 <= gray_src; dst_g2 <= dst_g1; dst_prev <= dst_g2;
          if ($countones(dst_g2 ^ dst_prev) > 1) skipped_steps <= skipped_steps + 1;
        end
    
      initial begin
        repeat (2) @(negedge clk);
        rst_n = 1;
        // one legal handshake
        @(negedge clk) begin req = 1; data = 8'h12; end
        repeat (2) @(negedge clk);
        ack = 1;
        @(negedge clk) req = 0;
        @(negedge clk) ack = 0;
        // Source steps remain legal even when the slower destination skips values.
        repeat (3) @(negedge clk);
        if (skipped_steps == 0) $fatal(1, "test did not exercise skipped destination Gray values");
        $display("PASS: source Gray steps satisfy the property; %0d destination samples legally skip steps", skipped_steps);
        $finish;
      end
    endmodule

    The directed example requires observed multi-bit destination jumps while the source property remains legal. The ideal destination flops here illustrate sampling scope.

    Remember

    • Bind the Gray one-bit check to the registered source pointer and source clock, once for each FIFO direction.

    • A slow destination can skip Gray values: 000 to 011 is a legal two-bit change there. Never apply the one-step check to destination samples.

    • Stay in the simple SVA subset (implication, $past, $stable, $rose, fixed delays) so the checks run in every assertion-capable flow.

    How this was checked

    Revised listing, run with Verilator 5.048 (--binary --timing --assert, 2026-10-16): prints PASS with 5 destination samples that legally skip Gray steps. A mutation that makes the source Gray pointer jump two bits fires a_gray_one_bit.

    The handshake rows in the violation table come from an earlier version of the bench and are not re-run against the revised checker. Re-run the source two-bit-jump mutation against gray_src to confirm detection. The ideal destination flops illustrate sampling scope, not physical CDC signoff.

    Keep going

    • Practice: Inline RTL checkers: X/Z detection and handshake liveness

    • Interview: run a mock interview on this topic

    Related topics

    • FIFO tests

    Previous

    184 / 350

    Next: FIFO Tests

    Up next

    Up nextFIFO Tests

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Rules
    • Signoff
    • Reading a CDC Report
    src_clkclkgray_src000001011010110111101100src_bits01dst_q000011110101dst_bits02
    A 3-bit Gray pointer on a 5 ns source clock sampled by a 10 ns destination clock: each source step changes one bit, each destination sample changes two