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
      • RDC Is CDC Plus Reset Asymmetry
      • Reset Isolation Pattern
      • Quiescent State Before Reset Release
      • RDC Signoff Checklist
      • Recovery, Removal and Verification Focus
    • CDC Verification
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Reset Domain Crossing
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. Reset Domain Crossing
  5. RDC Is CDC Plus Reset Asymmetry

Part 6 · Reset & CDC · Intermediate

RDC Is CDC Plus Reset Asymmetry

A four-phase handshake under 1,500 random resets applied to one side, the other, or both.

On this page

    Why this matters

    A crossing that is correct as a CDC design can still fail when a reset hits only one side. In the lesson's experiment a source-only reset delivered 271 stale zero words, and a destination-only reset delivered 3 duplicates.

    After this lesson you can: name the three kinds of reset damage at a crossing and explain why a structural CDC check does not find them.

    An experiment

    The four-phase request/acknowledge handshake moves a running word sequence between domains. A reset event arrives every 100 to 400 ns on the destination only, the source only, or both.

    The bench tracks accepted words in an epoch-tagged queue, explicitly cancels pending words at reset, and drains the final epoch. Single-sided modes intentionally report protocol damage; the coordinated-reset mode requires exact accounting.

    Three kinds of damage

    Kind

    Mechanism

    Reset-value leakage

    A flop in reset drives its reset value across the boundary, which the other side may take for real data (the 271 zeros).

    Protocol mismatch

    One side forgets the state of the handshake while the other remembers it (the 3 duplicates).

    Asynchronous reset edge

    The reset assertion itself changes outputs at an arbitrary time, so even a single-bit control signal can change inside the receiving flop's window.

    A duplicate, cycle by cycle

    Protocol mismatch is the easiest to see. The destination is reset while the source still holds req high, so after reset it treats the old request as a new one.

    Each slot is one clock cycle, numbered from 0, showing the values just after the edge that starts it. For a short picture both sides share one 10 ns clock and the two-flop synchronisers are ideal flops; the protocol logic is the listing's.

    • Slots 0 to 2: the source has sent word 0x05 and holds req high while the request passes through the two synchroniser flops.

    • Slot 3: the destination captures 0x05. ack and out_valid go high: this is the real delivery.

    • Slot 4: rst_d_n is low. The destination forgets the transfer: ack and out_data return to 0, while req is still high.

    • Slot 6: out of reset, the destination still sees the old request (its synchronised copy req_sync is high, although req itself fell at this edge) with ack low, so it captures 0x05 again. The source counted one transfer; the consumer received two.

    Common pitfall

    None of these show up in a CDC check that only looks at synchroniser structure. The crossing in this experiment is correct as a CDC design; it fails only when the reset behaviour is added.

    That is why RDC needs its own analysis.

    Check yourself Which of the three kinds of damage does a data register reset value cause?

    Reset-value leakage: the reset value of the data register is visible across the crossing and can be captured as a valid word. The fix is to not reset data buses (reset only control), or to mark them invalid while in reset.

    Testbench

    The handshake with random resets on either side or both.

    meta_ff.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
    `timescale 1ns/1ps
    
    module meta_ff #(parameter real TCQ = 0.25, TSU = 0.3, THD = 0.15) (input logic clk, d, output logic q);
      realtime t_d = -100.0, t_clk = -100.0;
      initial q = 1'b0;
      always @(d) begin
        t_d = $realtime;
        if ($realtime - t_clk < THD) q <= #TCQ $urandom_range(0, 1);
      end
      always @(posedge clk) begin
        t_clk = $realtime;
        if ($realtime - t_d < TSU) q <= #TCQ $urandom_range(0, 1);
        else                       q <= #TCQ d;
      end
    endmodule
    
    module sync2 (input logic clk, d, output logic q);
      logic q1;
      meta_ff f1 (.clk, .d,      .q(q1));
      meta_ff f2 (.clk, .d(q1),  .q);
    endmodule
    
    // ---------------- 4-phase request/acknowledge transfer of one word ----------------
    module hs_src #(parameter int W = 8) (
      input  logic         clk, rst_n,
      input  logic         send_valid,        // producer offers a word
      output logic         send_ready,        // high only when a new word can be accepted
      input  logic [W-1:0] send_data,
      output logic         req,               // request to the destination
      output logic [W-1:0] data,              // held STABLE while req is high
      input  logic         ack_sync           // acknowledge, already synchronised into this domain
    );
      typedef enum logic [1:0] {IDLE, WAIT_ACK, WAIT_NACK} state_t;
      state_t state;
      assign send_ready = (state == IDLE);
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin state <= IDLE; req <= 1'b0; data <= '0; end
        else unique case (state)
          IDLE:      if (send_valid) begin data <= send_data; req <= 1'b1; state <= WAIT_ACK; end
          WAIT_ACK:  if (ack_sync)   begin req <= 1'b0; state <= WAIT_NACK; end     // destination has the data
          WAIT_NACK: if (!ack_sync)  state <= IDLE;                                 // destination released: ready again
          default:   state <= IDLE;
        endcase
    endmodule
    
    module hs_dst #(parameter int W = 8) (
      input  logic         clk, rst_n,
      input  logic         req_sync,          // request, already synchronised into this domain
      input  logic [W-1:0] data,              // crosses WITHOUT a synchroniser: legal because it is stable while req is high
      output logic         ack,
      output logic         out_valid,         // one-cycle pulse with new data
      output logic [W-1:0] out_data
    );
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin ack <= 1'b0; out_valid <= 1'b0; out_data <= '0; end
        else begin
          out_valid <= 1'b0;
          if (req_sync && !ack) begin ack <= 1'b1; out_data <= data; out_valid <= 1'b1; end   // capture, then acknowledge
          else if (!req_sync)   ack <= 1'b0;
        end
    endmodule
    
    module hs_cdc #(parameter int W = 8) (
      input  logic clk_s, rst_s_n, clk_d, rst_d_n,
      input  logic send_valid, input logic [W-1:0] send_data, output logic send_ready,
      output logic out_valid, output logic [W-1:0] out_data
    );
      logic req, ack, req_sync, ack_sync;
      logic [W-1:0] data;
      hs_src #(W) s (.clk(clk_s), .rst_n(rst_s_n), .send_valid, .send_ready, .send_data, .req, .data, .ack_sync);
      sync2 u_req (.clk(clk_d), .d(req), .q(req_sync));       // request crosses to the destination clock
      hs_dst #(W) d (.clk(clk_d), .rst_n(rst_d_n), .req_sync, .data, .ack, .out_valid, .out_data);
      sync2 u_ack (.clk(clk_s), .d(ack), .q(ack_sync));       // acknowledge crosses back
    endmodule
    

    Reset applied to

    Words delivered

    Duplicates

    Stale words

    Why

    Destination only

    4,532

    3

    0

    After reset the destination sees req still high from the interrupted transfer and captures the same word again

    Source only

    4,587

    2

    271

    The source's data register resets to 0 while req is still being seen: the destination captures the reset value 0 as a real word

    Both together

    4,197

    0

    0

    Both protocol states restart together; the word in flight is cancelled

    In the coordinated-reset mode (MODE=2) the bench requires accepted = accounted deliveries + explicit cancellations, no unexpected delivery and a final drain: accepted=4606, delivered=4197, cancelled=409, errors=0. MODE=0/1 report diagnostics only, because their damage is intentional. Queue epochs belong to this testbench oracle; a real exactly-once protocol needs transmitted transaction identity, replay and deduplication.

    Remember

    • Reset-value leakage: a flop in reset drives its reset value across, and the other side takes it for data.

    • Protocol mismatch: one side forgets the handshake state while the other remembers it, which gives duplicates.

    • The asynchronous reset edge itself can change even a single-bit control signal inside the receiver's window.

    Rule of thumb: do not reset data buses that cross; reset control, and mark data invalid while in reset.

    How this was checked

    The results table is from the listing above, run with Icarus Verilog 13.0 (2026-10-16), default seed, one run per MODE. MODE=2 prints ACCOUNT accepted=4606 delivered=4197 cancelled=409 errors=0, then PASS. MODE=0 and MODE=1 keep diagnostic RESULT output because their damage is intentional.

    The original eight-bit modular monitor checks duplicates and backward jumps only when the true sequence gap is less than 128. It cannot establish transaction conservation across arbitrary reset epochs or distinguish wraparound from a large gap, so the revised bench adds an epoch-tagged queue.

    Keep going

    • Practice: Debugging metastability from asynchronous reset deassertion

    • Code lab: Reset domain crossing

    • Interview: run a mock interview on this topic

    Related topics

    • Reset isolation pattern

    Previous

    177 / 350

    Next: Reset Isolation Pattern

    Up next

    Up nextReset Isolation Pattern

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Quiescent State Before Reset Release
    • RDC Signoff Checklist
    • Recovery, Removal and Verification Focus
    clkrst_d_nreqackout_validout_data0505
    The lesson's handshake with a destination-only reset mid-transfer: word 0x05 is captured in slot 3, the reset clears the destination in slot 4, and the same word is captured again in slot 6