cdc_fifo_2phase.sv 4.5 KB
Newer Older
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
// Copyright 2018 ETH Zurich and University of Bologna.
//
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
// Fabian Schuiki <fschuiki@iis.ee.ethz.ch>

/// A clock domain crossing FIFO, using 2-phase hand shakes.
///
/// This FIFO has its push and pop ports in two separate clock domains. Its size
/// can only be powers of two, which is why its depth is given as 2**LOG_DEPTH.
/// LOG_DEPTH must be at least 1.
///
/// CONSTRAINT: See the constraints for `cdc_2phase`. An additional maximum
/// delay path needs to be specified from fifo_data_q to dst_data_o.
module cdc_fifo_2phase #(
  /// The data type of the payload transported by the FIFO.
  parameter type T = logic,
  /// The FIFO's depth given as 2**LOG_DEPTH.
  parameter int LOG_DEPTH = 3
)(
  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
);

  // Check the invariants.
  //pragma translate_off
  initial begin
    assert(LOG_DEPTH > 0);
  end
  //pragma translate_on

  localparam int PtrWidth = LOG_DEPTH+1;
  typedef logic [PtrWidth-1:0] pointer_t;
  typedef logic [LOG_DEPTH-1:0] index_t;

  localparam pointer_t PtrFull  = (1 << LOG_DEPTH);
  localparam pointer_t PtrEmpty = '0;

  // Allocate the registers for the FIFO memory with its separate write and read
  // ports. The FIFO has the following ports:
  //
  // - write: fifo_widx, fifo_wdata, fifo_write, src_clk_i
  // - read: fifo_ridx, fifo_rdata
  index_t fifo_widx, fifo_ridx;
  logic fifo_write;
  T fifo_wdata, fifo_rdata;
  T fifo_data_q [2**LOG_DEPTH];

  assign fifo_rdata = fifo_data_q[fifo_ridx];

  for (genvar i = 0; i < 2**LOG_DEPTH; i++) begin : g_word
    always_ff @(posedge src_clk_i, negedge src_rst_ni) begin
      if (!src_rst_ni)
        fifo_data_q[i] <= '0;
      else if (fifo_write && fifo_widx == i)
        fifo_data_q[i] <= fifo_wdata;
    end
  end

  // Allocate the read and write pointers in the source and destination domain.
  pointer_t src_wptr_q, dst_wptr, src_rptr, dst_rptr_q;

  always_ff @(posedge src_clk_i, negedge src_rst_ni) begin
    if (!src_rst_ni)
      src_wptr_q <= 0;
    else if (src_valid_i && src_ready_o)
      src_wptr_q <= src_wptr_q + 1;
  end

  always_ff @(posedge dst_clk_i, negedge dst_rst_ni) begin
    if (!dst_rst_ni)
      dst_rptr_q <= 0;
    else if (dst_valid_o && dst_ready_i)
      dst_rptr_q <= dst_rptr_q + 1;
  end

  // The pointers into the FIFO are one bit wider than the actual address into
  // the FIFO. This makes detecting critical states very simple: if all but the
  // topmost bit of rptr and wptr agree, the FIFO is in a critical state. If the
  // topmost bit is equal, the FIFO is empty, otherwise it is full.
  assign src_ready_o = ((src_wptr_q ^ src_rptr) != PtrFull);
  assign dst_valid_o = ((dst_rptr_q ^ dst_wptr) != PtrEmpty);

  // Transport the read and write pointers across the clock domain boundary.
  cdc_2phase #( .T(pointer_t) ) i_cdc_wptr (
    .src_rst_ni  ( src_rst_ni ),
    .src_clk_i   ( src_clk_i  ),
    .src_data_i  ( src_wptr_q ),
    .src_valid_i ( 1'b1       ),
    .src_ready_o (            ),
    .dst_rst_ni  ( dst_rst_ni ),
    .dst_clk_i   ( dst_clk_i  ),
    .dst_data_o  ( dst_wptr   ),
    .dst_valid_o (            ),
    .dst_ready_i ( 1'b1       )
  );

  cdc_2phase #( .T(pointer_t) ) i_cdc_rptr (
    .src_rst_ni  ( dst_rst_ni ),
    .src_clk_i   ( dst_clk_i  ),
    .src_data_i  ( dst_rptr_q ),
    .src_valid_i ( 1'b1       ),
    .src_ready_o (            ),
    .dst_rst_ni  ( src_rst_ni ),
    .dst_clk_i   ( src_clk_i  ),
    .dst_data_o  ( src_rptr   ),
    .dst_valid_o (            ),
    .dst_ready_i ( 1'b1       )
  );

  // Drive the FIFO write and read ports.
  assign fifo_widx  = src_wptr_q;
  assign fifo_wdata = src_data_i;
  assign fifo_write = src_valid_i && src_ready_o;
  assign fifo_ridx  = dst_rptr_q;
  assign dst_data_o = fifo_rdata;

endmodule