// Copyright 2020 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51

// Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch>
// Description: Load Store Unit (can handle `NumOutstandingLoads` outstanding loads) and
//              optionally NaNBox if used in a floating-point setting.
//              It supports out-of-order memory responses via metadata linking with IDs.

module snitch_lsu
  import cf_math_pkg::idx_width;
#(
  parameter type tag_t                       = logic [4:0],
  parameter int unsigned NumOutstandingLoads = 1,
  parameter bit NaNBox                       = 0,
  // Dependent parameters. DO NOT CHANGE.
  localparam int unsigned IdWidth = idx_width(NumOutstandingLoads)
) (
  input  logic               clk_i,
  input  logic               rst_i,
  // request channel
  input  tag_t               lsu_qtag_i,
  input  logic               lsu_qwrite,
  input  logic               lsu_qsigned,
  input  logic [31:0]        lsu_qaddr_i,
  input  logic [31:0]        lsu_qdata_i,
  input  logic [1:0]         lsu_qsize_i,
  input  logic [3:0]         lsu_qamo_i,
  input  logic               lsu_qvalid_i,
  output logic               lsu_qready_o,
  // response channel
  output logic [31:0]        lsu_pdata_o,
  output tag_t               lsu_ptag_o,
  output logic               lsu_perror_o,
  output logic               lsu_pvalid_o,
  input  logic               lsu_pready_i,
  // Memory Interface Channel
  output logic [31:0]        data_qaddr_o,
  output logic               data_qwrite_o,
  output logic [3:0]         data_qamo_o,
  output logic [31:0]        data_qdata_o,
  output logic [3:0]         data_qstrb_o,
  output logic [IdWidth-1:0] data_qid_o,
  output logic               data_qvalid_o,
  input  logic               data_qready_i,
  input  logic [31:0]        data_pdata_i,
  input  logic               data_perror_i,
  input  logic [IdWidth-1:0] data_pid_i,
  input  logic               data_pvalid_i,
  output logic               data_pready_o
);

  // ----------------
  // TYPEDEFS
  // ----------------

  typedef logic [IdWidth-1:0] meta_id_t;

  typedef struct packed {
    tag_t       tag;
    logic       sign_ext;
    logic [1:0] offset;
    logic [1:0] size;
  } metadata_t;

  // ----------------
  // SIGNALS
  // ----------------

  // ID Table
  logic      [NumOutstandingLoads-1:0] id_available_d, id_available_q;
  metadata_t [NumOutstandingLoads-1:0] metadata_d, metadata_q;
  metadata_t                           req_metadata;
  metadata_t                           resp_metadata;
  meta_id_t                            req_id;
  meta_id_t                            resp_id;
  logic                                id_table_push;
  logic                                id_table_pop;
  logic                                id_table_full;

  // Response
  logic [31:0] ld_result;

  // ----------------
  // ID TABLE
  // ----------------
  // Track ID availability and store metadata
  always_comb begin
    // Default
    id_available_d = id_available_q;
    metadata_d     = metadata_q;

    // Take ID and store metadata
    if (id_table_push) begin
      id_available_d[req_id] = 1'b0;
      metadata_d[req_id]     = req_metadata;
    end

    // Free ID
    if (id_table_pop) begin
      id_available_d[resp_id] = 1'b1;
    end
  end

  assign req_metadata = '{
    tag:      lsu_qtag_i,
    sign_ext: lsu_qsigned,
    offset:   lsu_qaddr_i[1:0],
    size:     lsu_qsize_i
  };

  assign resp_metadata = metadata_q[resp_id];

  // Search available ID for request
  lzc #(
    .WIDTH ( NumOutstandingLoads )
  ) i_req_id (
    .in_i   ( id_available_q ),
    .cnt_o  ( req_id         ),
    .empty_o( id_table_full  )
  );

  // Pop if response accepted
  assign id_table_pop = data_pvalid_i & data_pready_o;

  // Push if load request accepted
  assign id_table_push = ~lsu_qwrite & data_qready_i & data_qvalid_o;

  // ----------------
  // REQUEST
  // ----------------
  // only make a request when we got a valid request and if it is a load
  // also check that we can actually store the necessary information to process
  // it in the upcoming cycle(s).
  assign data_qvalid_o = (lsu_qvalid_i) & (lsu_qwrite | ~id_table_full);
  assign data_qwrite_o = lsu_qwrite;
  assign data_qaddr_o  = {lsu_qaddr_i[31:2], 2'b0};
  assign data_qamo_o   = lsu_qamo_i;
  assign data_qid_o    = req_id;
  // generate byte enable mask
  always_comb begin
    unique case (lsu_qsize_i)
      2'b00: data_qstrb_o = (4'b1 << lsu_qaddr_i[1:0]);
      2'b01: data_qstrb_o = (4'b11 << lsu_qaddr_i[1:0]);
      2'b10: data_qstrb_o = '1;
      default: data_qstrb_o = '0;
    endcase
  end

  // re-align write data
  /* verilator lint_off WIDTH */
  always_comb begin
    unique case (lsu_qaddr_i[1:0])
      2'b00: data_qdata_o = lsu_qdata_i;
      2'b01: data_qdata_o = {lsu_qdata_i[23:0], lsu_qdata_i[31:24]};
      2'b10: data_qdata_o = {lsu_qdata_i[15:0], lsu_qdata_i[31:16]};
      2'b11: data_qdata_o = {lsu_qdata_i[ 7:0], lsu_qdata_i[31: 8]};
      default: data_qdata_o = lsu_qdata_i;
    endcase
  end
  /* verilator lint_on WIDTH */

  // the interface didn't accept our request yet
  assign lsu_qready_o = ~(data_qvalid_o & ~data_qready_i) & ~id_table_full;

  // ----------------
  // RESPONSE
  // ----------------
  // Return Path
  // shift the load data back by offset bytes
  logic [31:0] shifted_data;
  assign shifted_data = data_pdata_i >> {resp_metadata.offset, 3'b000};
  always_comb begin
    unique case (resp_metadata.size)
      2'b00: ld_result = {{24{shifted_data[ 7] & resp_metadata.sign_ext}}, shifted_data[7:0]};
      2'b01: ld_result = {{16{shifted_data[15] & resp_metadata.sign_ext}}, shifted_data[15:0]};
      2'b10: ld_result = shifted_data;
      default: ld_result = shifted_data;
    endcase
  end

  assign resp_id       = data_pid_i;
  assign lsu_perror_o  = data_perror_i;
  assign lsu_pdata_o   = ld_result;
  assign lsu_ptag_o    = resp_metadata.tag;
  assign lsu_pvalid_o  = data_pvalid_i;
  assign data_pready_o = lsu_pready_i;

  // ----------------
  // SEQUENTIAL
  // ----------------
  always_ff @(posedge clk_i or posedge rst_i) begin
    if (rst_i) begin
      id_available_q <= '1;
      metadata_q     <= 'b0;
    end else begin
      id_available_q <= id_available_d;
      metadata_q     <= metadata_d;
    end
  end

  // ----------------
  // ASSERTIONS
  // ----------------
  // Check for unsupported parameters
  if (NumOutstandingLoads == 0)
    $error(1, "[snitch_lsu] NumOutstandingLoads cannot be 0.");

  // pragma translate_off
  `ifndef VERILATOR
    invalid_req_id : assert property(
      @(posedge clk_i) disable iff (rst_i) (!(id_table_push & ~id_available_q[req_id])))
      else $fatal (1, "Request ID is not available.");
  `endif

  `ifndef VERILATOR
    invalid_resp_id : assert property(
      @(posedge clk_i) disable iff (rst_i) (!(id_table_pop & id_available_q[resp_id])))
      else $fatal (1, "Response ID does not match with valid metadata.");
  `endif
  // pragma translate_on

endmodule