FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Common CellsのCDC実装詳細解析 (3. 4相ハンドシェイクCDC)

https://github.com/pulp-platform/common_cells/blob/master/src/cdc_4phase.sv

基本設計方針

cdc_4phase.svは、4相ハンドシェイクプロトコルを使用してクロックドメイン間でデータを転送するモジュールである。 4相ハンドシェイクでは、req信号とack信号がアサート/デアサートのサイクルを繰り返す。

インタフェース定義

module cdc_4phase #(
  parameter type T = logic,
  parameter bit DECOUPLED = 1'b1,
  parameter bit SEND_RESET_MSG = 1'b0,
  parameter T RESET_MSG = T'('0)
)(
  // ソース側の信号
  input  logic src_rst_ni,
  input  logic src_clk_i,
  input  T     src_data_i,
  input  logic src_valid_i,
  output logic src_ready_o,
  
  // デスティネーション側の実装
  input  logic dst_rst_ni,
  input  logic dst_clk_i,
  output T     dst_data_o,
  output logic dst_valid_o,
  input  logic dst_ready_i
);

パラメータ定義

  • DECOUPLED: デカップルモード(有効な場合、ハンドシェイク完了前に次のデータを受け入れる)
  • SEND_RESET_MSG: リセット時にメッセージを送信するかどうか
  • RESET_MSG: リセット時に送信するメッセージ

4相ハンドシェイクの動作原理

4相ハンドシェイクでは、以下の4つのフェーズを繰り返す:

  1. IDLE: req = 0, ack = 0 の状態。
  2. WAIT_ACK_ASSERT: req = 1の状態。 ack = 0を待つ。
  3. WAIT_ACK_DEASSERT: req = 0 ack = 1を待つ。
  4. IDLEに戻る: req = 0, ack = 0

ソース側の実装

module cdc_4phase_src #(
  parameter type T = logic,
  parameter int unsigned SYNC_STAGES = 2,
  parameter bit DECOUPLED = 1'b1,
  parameter bit SEND_RESET_MSG = 1'b0,
  parameter T RESET_MSG = T'('0)
)(
  input  logic rst_ni,
  input  logic clk_i,
  input  T     data_i,
  input  logic valid_i,
  output logic ready_o,
  output logic async_req_o,
  input  logic async_ack_i,
  output T     async_data_o
);
  (* dont_touch = "true" *)
  logic  req_src_d, req_src_q;
  (* dont_touch = "true" *)
  T data_src_d, data_src_q;
  (* dont_touch = "true" *)
  logic  ack_synced;
  typedef enum logic[1:0] {IDLE, WAIT_ACK_ASSERT, WAIT_ACK_DEASSERT} state_e;
  state_e state_d, state_q;
  // Synchronize the async ACK  sync #(
    .STAGES(SYNC_STAGES)
  ) i_sync(
    .clk_i,
    .rst_ni,
    .serial_i( async_ack_i ),
    .serial_o( ack_synced  )
  );
 
  // FSM for the 4-phase handshake
  always_comb begin
    state_d    = state_q;
    req_src_d  = 1'b0;
    data_src_d = data_src_q;
    ready_o    = 1'b0;
    case (state_q)
      IDLE: begin
        // If decoupling is disabled, defer assertion of ready until the
        // handshake with the dst is completed
        if (DECOUPLED) begin
          ready_o = 1'b1;
        end else begin
          ready_o = 1'b0;
        end
        // Sample a new item when the valid signal is asserted.
        if (valid_i) begin
          data_src_d = data_i;
          req_src_d  = 1'b1;
          state_d = WAIT_ACK_ASSERT;
        end
      end
      WAIT_ACK_ASSERT: begin
        req_src_d = 1'b1;
        if (ack_synced == 1'b1) begin
          req_src_d = 1'b0;
          state_d   = WAIT_ACK_DEASSERT;
        end
      end
      WAIT_ACK_DEASSERT: begin
        if (ack_synced == 1'b0) begin
          state_d = IDLE;
          if (!DECOUPLED) begin
            ready_o = 1'b1;
          end
        end
      end
      default: begin
        state_d = IDLE;
      end
    endcase
  end

ソース側のFSMは、3つの状態を持つ:

  • IDLE: 新しいデータを待つ
  • WAIT_ACK_ASSERT: ackがアサートされるのを待つ
  • WAIT_ACK_DEASSERT: ackがデアサートされるのを待つ

DECOUPLEDモードが有効な場合、ハンドシェイクが完了する前に次のデータを受け入れることができる。

デスティネーション側の実装

module cdc_4phase_dst #(
  parameter type T = logic,
  parameter int unsigned SYNC_STAGES = 2,
  parameter bit DECOUPLED = 1)(
  input  logic rst_ni,
  input  logic clk_i,
  output T     data_o,
  output logic valid_o,
  input  logic ready_i,
  input  logic async_req_i,
  output logic async_ack_o,
  input  T     async_data_i
);
  (* dont_touch = "true" *)
  logic  ack_dst_d, ack_dst_q;
  (* dont_touch = "true" *)
  logic  req_synced;
  logic  data_valid;
  logic  output_ready;
  typedef enum logic[1:0] {IDLE, WAIT_DOWNSTREAM_ACK, WAIT_REQ_DEASSERT} state_e;
  state_e state_d, state_q;
  //Synchronize the request  sync #(
    .STAGES(SYNC_STAGES)
  ) i_sync(
    .clk_i,
    .rst_ni,
    .serial_i( async_req_i ),
    .serial_o( req_synced  )
  );
  // FSM for the 4-phase handshake
  always_comb begin
    state_d    = state_q;
    data_valid = 1'b0;
    ack_dst_d  = 1'b0;

    case (state_q)
      IDLE: begin
        // Sample the data upon a new request and transition to the next state
        if (req_synced == 1'b1) begin
          data_valid = 1'b1;
          if (output_ready == 1'b1) begin
            state_d = WAIT_REQ_DEASSERT;
          end else begin
            state_d = WAIT_DOWNSTREAM_ACK;
          end
        end
      end

      WAIT_DOWNSTREAM_ACK: begin
        data_valid       = 1'b1;
        if (output_ready == 1'b1) begin
          state_d    = WAIT_REQ_DEASSERT;
          ack_dst_d  = 1'b1;
        end
      end

      WAIT_REQ_DEASSERT: begin
        ack_dst_d = 1'b1;
        if (req_synced == 1'b0) begin
          ack_dst_d = 1'b0;
          state_d   = IDLE;
        end
      end

      default: begin
        state_d = IDLE;
      end
    endcase
  end

デスティネーション側のFSMは、3つの状態を持つ:

  • IDLE: 新しいリクエストを待つ
  • WAIT_DOWNSTREAM_ACK: 下流回路がデータを受け取るのを待つ
  • WAIT_REQ_DEASSERT: reqがデアサートされるのを待つ

DECOUPLEDモードが有効な場合、spill_registerを使用して出力をデカップルする。