Ready-to-run snippets

Code Examples

Ready-to-run Verilog, SystemVerilog, and UVM examples. Click any to open it live in the IDE.

← All examples

Clock Domain Crossing with a Handshake

Verilog Bus Protocols

Words cross from a 10 ns to a 14 ns clock domain with a four-phase handshake and two-flip-flop synchronizers.

Open in IDE
design.v
// Passing words from clock domain A to clock domain B with a four-phase handshake.
// req crosses from A to B and ack crosses back from B to A, each through a
// two-flip-flop synchronizer. The data bus is held steady while req is high,
// so B can sample it safely once it sees the synchronized req.

module sync2 (
  input      clk,
  input      rst,
  input      d,
  output reg q
);
  reg meta;                        // first stage: may go metastable in real silicon

  always @(posedge clk) begin
    if (rst) begin
      meta <= 1'b0;
      q    <= 1'b0;
    end
    else begin
      meta <= d;
      q    <= meta;
    end
  end
endmodule

module cdc_sender (
  input            clk_a,
  input            rst,
  input            go,             // a new word is waiting in din
  input      [7:0] din,
  input            ack_a,          // ack, already synchronized into domain A
  output reg       req,
  output reg [7:0] data,
  output           busy
);
  assign busy = req || ack_a;      // a word is in flight

  always @(posedge clk_a) begin
    if (rst) begin
      req <= 1'b0;
    end
    else if (go && !busy) begin    // phase 1: data first, then request
      data <= din;
      req  <= 1'b1;
    end
    else if (req && ack_a) begin   // phase 3: release
      req <= 1'b0;
    end
  end
endmodule

module cdc_receiver (
  input            clk_b,
  input            rst,
  input            req_b,          // req, already synchronized into domain B
  input      [7:0] data,           // stable while req is high
  output reg       ack,
  output reg [7:0] got,
  output reg       got_valid
);
  always @(posedge clk_b) begin
    if (rst) begin
      ack       <= 1'b0;
      got_valid <= 1'b0;
    end
    else begin
      got_valid <= 1'b0;
      if (req_b && !ack) begin     // phase 2: sample the data, acknowledge
        got       <= data;
        got_valid <= 1'b1;
        ack       <= 1'b1;
      end
      else if (!req_b && ack) begin  // phase 4: release
        ack <= 1'b0;
      end
    end
  end
endmodule

module cdc_link (
  input        clk_a,
  input        clk_b,
  input        rst,
  input        go,
  input  [7:0] din,
  output       busy,
  output [7:0] got,
  output       got_valid
);
  wire       req, ack, req_b, ack_a;
  wire [7:0] data;

  cdc_sender u_tx (
    .clk_a (clk_a),
    .rst   (rst),
    .go    (go),
    .din   (din),
    .ack_a (ack_a),
    .req   (req),
    .data  (data),
    .busy  (busy)
  );

  sync2 u_req_sync (
    .clk (clk_b),
    .rst (rst),
    .d   (req),
    .q   (req_b)
  );

  cdc_receiver u_rx (
    .clk_b     (clk_b),
    .rst       (rst),
    .req_b     (req_b),
    .data      (data),
    .ack       (ack),
    .got       (got),
    .got_valid (got_valid)
  );

  sync2 u_ack_sync (
    .clk (clk_a),
    .rst (rst),
    .d   (ack),
    .q   (ack_a)
  );
endmodule
testbench.v
// Domain A runs at 100 MHz (10 ns), domain B at about 71 MHz (14 ns). Three words cross.
module tb;
  reg        clk_a = 0;
  reg        clk_b = 0;
  reg        rst   = 1;
  reg        go    = 0;
  reg  [7:0] din   = 8'h00;
  wire       busy;
  wire [7:0] got;
  wire       got_valid;
  integer    k;

  cdc_link dut (
    .clk_a     (clk_a),
    .clk_b     (clk_b),
    .rst       (rst),
    .go        (go),
    .din       (din),
    .busy      (busy),
    .got       (got),
    .got_valid (got_valid)
  );

  always #5 clk_a = ~clk_a;
  always #7 clk_b = ~clk_b;

  always @(posedge clk_b) begin
    if (got_valid)
      $display("%0t  domain B received %h", $time, got);
  end

  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, tb);
    #30 rst = 0;
    for (k = 0; k < 3; k = k + 1) begin
      @(negedge clk_a);
      while (busy)
        @(negedge clk_a);
      din = 8'hC0 + k;
      go  = 1;
      $display("%0t  domain A sends    %h", $time, din);
      @(negedge clk_a);
      go = 0;
    end
    #300 $finish;
  end
endmodule
Admin mode — edit and delete controls are visible on each row. Use the IDE to add new snippets via File → Save as Snippet.