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
      • Choose a Protocol by Traffic Shape
      • 4-Phase Request/Acknowledge
      • Pulse Synchronisers and Event Toggles
      • Mux Recirculation for Multi-Bit Capture
      • CDC Handshake Checklist
    • Reset Domain Crossing
    • CDC Verification
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹CDC Protocols
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. CDC Protocols
  5. 4-Phase Request/Acknowledge

Part 6 · Reset & CDC · Intermediate

4-Phase Request/Acknowledge

One word per round trip with request, acknowledge and return to zero.

On this page

    Why this matters

    A 4-phase handshake moves a word safely with no data synchroniser, at a cost of about 9 source cycles per word. Change the data while req is high and 2,071 of 2,075 words arrive corrupted.

    After this lesson you can: draw the four steps and the waveform, explain why the data needs no synchroniser, and bound data arrival below the earliest capture.

    Four steps per word

    The source holds the data stable while the request is high. The destination captures it and answers with an acknowledge.

    1. Source puts the word on the data bus and raises req. The data stays stable from now on.

    2. Destination sees req (after a synchroniser), captures the data, and raises ack.

    3. Source sees ack (after a synchroniser) and lowers req. The data may change from now on.

    4. Destination sees req low and lowers ack. The source sees ack low and is ready for the next word.

    Sequence diagram of a four-phase request/acknowledge handshake.
    The four phases. Every arrow between the two lifelines is a synchronised single bit.

    Each slot is one source clock cycle, sampled just after its rising edge. The destination clock is slower and unrelated, so every crossing takes a slot or two. This run uses plain two-flop synchronisers, with no metastability outcome.

    • Slot 2: the source loads the word and raises req on the same edge, so data A5 and req appear together.

    • Slots 3 and 4: req_sync rises after the destination synchroniser, and the destination captures the data and raises ack.

    • Slot 5: ack_sync rises in the source domain. The source lowers req on the next edge, in slot 6.

    • Slots 7 to 9: the low req crosses, ack falls, and the low ack crosses back. After slot 9 the source can accept the next word.

    The data lane stays at A5 from slot 2 on, so it is stable the whole time req is high and while the destination reads it.

    Measured behaviour and completion check

    A digital old/new perturbation model on four flops, with offers stopped and a full drain required, moves 2,077 words at about 9 source cycles per word.

    Check

    Result

    Historical fixed-time cutoff

    2,075 delivered / 2,076 accepted; unfinished or lost final word was not distinguished

    Revised required result (executed: PASS, 2,077 words)

    Accepted = delivered; queue empty; source ready and request/ack history idle under a watchdog

    Source clock / destination clock

    10 ns / 7.13 ns, unrelated

    Metastability model on all four synchroniser flops

    Yes (meta_ff)

    Throughput

    About 9 source cycles per word

    Throughput is low by design: each word waits for the request to cross (2 to 3 destination cycles) and the acknowledge to cross back (2 to 3 source cycles).

    The return-to-zero repeats both crossings. The scheme suits control and configuration, not data streams.

    Bundled-data timing contract

    Holding the source register prevents overwrite; it does not bound wire delay. In this listing, req and data launch together, two destination flops synchronise req, and hs_dst consumes the previous synchroniser output on a third edge. With a request launched just before the first sampling edge, the earliest capture is about 2 destination periods after launch. Every data bit, including source clock-to-Q, routing and destination setup, must fit before that earliest capture with margin. A slower control arrival only increases the budget; do not size it from average handshake latency.

    Diagram
    # Illustrative held-data constraint for the 7.13 ns destination period below.
    # Earliest capture budget: 2 * 7.13 = 14.26 ns, less setup and margin.
    # Conservative example: clock-to-Q + data route + setup <= 12.0 ns.
    # Adapt object names and confirm what the tool includes in its path delay.
    set_max_delay 12.0 -datapath_only -from [get_cells s/data_reg*] -to [get_cells d/out_data_reg*]
    # Do not override this bound with clock groups or a false path on the bus.
    # Require each bit in the effective max-delay report; keep req/ack stage-to-stage paths timed.
    Check yourself The request reaches its capture opportunity in 14.26 ns, but one data bit needs 15 ns including setup. Is holding data until ack sufficient?

    No. The destination can capture that bit before it arrives and then acknowledge corrupted data. Bound the data arrival below the earliest capture opportunity, or delay capture with a documented, constrained control pipeline.

    Warning

    A mutation shows why the data must be stable: if the source changes the data bus while req is high (one line changed in hs_src), 2,071 of 2,075 words arrive corrupted. The destination captured at the instant it saw req, but by then the bus had moved on.

    Common pitfall

    Don't skip the return-to-zero. A 2-phase (toggle) handshake is faster but harder to reason about; a 4-phase handshake that skips the second half can confuse the next transfer with the previous one. Keep to the four steps until you have a throughput problem.

    Interview

    Be ready to draw the waveform: req rises, ack rises, req falls, ack falls, and to say why data needs no synchroniser (it is stable from before req rises until after ack rises).

    Check yourself When is it safe for the source to change the data bus?

    After it has seen ack high (the destination has captured the data) and has lowered req. In this design, the source only updates data in IDLE, when both req and ack are low.

    Testbench

    The full handshake with a digital perturbation model and an order-checking testbench. The drain requires progressing clocks, no reset during the run, and an always-accepting pulse consumer.

    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
    

    Remember

    • Only req and ack cross. The data is stable from before req rises until after ack rises.

    • Throughput is low by design: both crossings repeat for the return to zero. Use it for control and configuration, not streams.

    • Every data bit must arrive before the earliest capture, about 2 destination periods after launch. A slow control path only adds budget.

    Rule of thumb: never skip the return-to-zero half of the handshake.

    How this was checked

    Revised bench, run with Icarus Verilog 13.0 (2026-10-16): prints PASS with 2,077 words at about 9 source cycles per word. The original run, with a digital old/new perturbation model on four flops, also averaged about 9 source cycles per word but stopped with one accepted word unaccounted for; the revised source stops offers, preserves a pending offer and requires full drain. The model neither simulates analog resolution nor establishes MTBF.

    Full testbench, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It is finite functional evidence, not a physical reliability guarantee.

    Keep going

    • Practice: Multi-bit bus CDC with a request/acknowledge handshake

    • Practice: Debugging a CDC handshake failure across domains

    • Code lab: CDC handshake

    • Interview: run a mock interview on this topic

    Related topics

    • Pulse synchronisers

    Previous

    173 / 350

    Next: Pulse Synchronisers and Event Toggles

    Up next

    Up nextPulse Synchronisers and Event Toggles

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Mux Recirculation for Multi-Bit Capture
    • CDC Handshake Checklist
    clk_sdata0a5reqreq_syncackack_sync
    One word through the four phases: req and data A5 launch together, req_sync, ack and ack_sync follow, then req falls and the zeros ripple back