// Copyright 2020 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.

// Authors:
// - Andreas Kurth <akurth@iis.ee.ethz.ch>

/// `stream_to_mem`: Allows to use memories with flow control (`valid`/`ready`) for requests but without flow
/// control for output data to be used in streams.
`include "common_cells/registers.svh"
module stream_to_mem #(
  /// Memory request payload type, usually write enable, write data, etc.
  parameter type         mem_req_t  = logic,
  /// Memory response payload type, usually read data
  parameter type         mem_resp_t = logic,
  /// Number of buffered responses (fall-through, thus no additional latency).  This defines the
  /// maximum number of outstanding requests on the memory interface. If the attached memory
  /// responds in the same cycle a request is applied, this MUST be 0. If the attached memory
  /// responds at least one cycle after a request, this MUST be >= 1 and should be equal to the
  /// response latency of the memory to saturate bandwidth.
  parameter int unsigned BufDepth   = 32'd1
) (
  /// Clock
  input  logic      clk_i,
  /// Asynchronous reset, active low
  input  logic      rst_ni,
  /// Request stream interface, payload
  input  mem_req_t  req_i,
  /// Request stream interface, payload is valid for transfer
  input  logic      req_valid_i,
  /// Request stream interface, payload can be accepted
  output logic      req_ready_o,
  /// Response stream interface, payload
  output mem_resp_t resp_o,
  /// Response stream interface, payload is valid for transfer
  output logic      resp_valid_o,
  /// Response stream interface, payload can be accepted
  input  logic      resp_ready_i,
  /// Memory request interface, payload
  output mem_req_t  mem_req_o,
  /// Memory request interface, payload is valid for transfer
  output logic      mem_req_valid_o,
  /// Memory request interface, payload can be accepted
  input  logic      mem_req_ready_i,
  /// Memory response interface, payload
  input  mem_resp_t mem_resp_i,
  /// Memory response interface, payload is valid
  input  logic      mem_resp_valid_i
);

  typedef logic [$clog2(BufDepth+1):0] cnt_t;

  cnt_t cnt_d, cnt_q;
  logic buf_ready,
        req_ready;

  if (BufDepth > 0) begin : gen_buf
    // Count number of outstanding requests.
    always_comb begin
      cnt_d = cnt_q;
      if (req_valid_i && req_ready_o) begin
        cnt_d++;
      end
      if (resp_valid_o && resp_ready_i) begin
        cnt_d--;
      end
    end

    // Can issue another request if the counter is not at its limit or a response is delivered in
    // the current cycle.
    assign req_ready = (cnt_q < BufDepth) | (resp_valid_o & resp_ready_i);

    // Control request and memory request interface handshakes.
    assign req_ready_o = mem_req_ready_i & req_ready;
    assign mem_req_valid_o = req_valid_i & req_ready;

    // Buffer responses.
    stream_fifo #(
      .FALL_THROUGH ( 1'b1       ),
      .DEPTH        ( BufDepth   ),
      .T            ( mem_resp_t )
    ) i_resp_buf (
      .clk_i,
      .rst_ni,
      .flush_i    ( 1'b0             ),
      .testmode_i ( 1'b0             ),
      .data_i     ( mem_resp_i       ),
      .valid_i    ( mem_resp_valid_i ),
      .ready_o    ( buf_ready        ),
      .data_o     ( resp_o           ),
      .valid_o    ( resp_valid_o     ),
      .ready_i    ( resp_ready_i     ),
      .usage_o    ( /* unused */     )
    );

    // Register
    `FFARN(cnt_q, cnt_d, '0, clk_i, rst_ni)

  end else begin : gen_no_buf
    // Control request, memory request, and response interface handshakes.
    assign mem_req_valid_o = req_valid_i;
    assign resp_valid_o    = mem_req_valid_o & mem_req_ready_i & mem_resp_valid_i;
    assign req_ready_o     = resp_ready_i    & resp_valid_o;

    // Forward responses.
    assign resp_o = mem_resp_i;
  end

  // Forward requests.
  assign mem_req_o = req_i;

// Assertions
// pragma translate_off
`ifndef VERILATOR
  if (BufDepth > 0) begin : gen_buf_asserts
    assert property (@(posedge clk_i) mem_resp_valid_i |-> buf_ready)
      else $error("Memory response lost!");
    assert property (@(posedge clk_i) cnt_q == '0 |=> cnt_q != '1)
      else $error("Counter underflowed!");
    assert property (@(posedge clk_i) cnt_q == BufDepth |=> cnt_q != BufDepth + 1)
      else $error("Counter overflowed!");
  end else begin : gen_no_buf_asserts
    assume property (@(posedge clk_i) mem_req_valid_o & mem_req_ready_i |-> mem_resp_valid_i)
      else $error("Without BufDepth = 0, the memory must respond in the same cycle!");
  end
`endif
// pragma translate_on
endmodule