// Copyright (c) 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:
// - Wolfgang Roenninger <wroennin@iis.ee.ethz.ch>
// - Andreas Kurth <akurth@iis.ee.ethz.ch>

`include "common_cells/registers.svh"
/// Serialize all AXI transactions to a single ID (zero).
module axi_serializer #(
  /// Maximum number of in flight read transactions.
  parameter int unsigned MaxReadTxns  = 32'd0,
  /// Maximum number of in flight write transactions.
  parameter int unsigned MaxWriteTxns = 32'd0,
  /// AXI4+ATOP ID width.
  parameter int unsigned AxiIdWidth   = 32'd0,
  /// AXI4+ATOP request struct definition.
  parameter type         req_t        = logic,
  /// AXI4+ATOP response struct definition.
  parameter type         resp_t       = logic
) (
  /// Clock
  input  logic  clk_i,
  /// Asynchronous reset, active low
  input  logic  rst_ni,
  /// Slave port request
  input  req_t  slv_req_i,
  /// Slave port response
  output resp_t slv_resp_o,
  /// Master port request
  output req_t  mst_req_o,
  /// Master port response
  input  resp_t mst_resp_i
);

  typedef logic [AxiIdWidth-1:0] id_t;
  typedef enum logic [1:0] {
    AtopIdle    = 2'b00,
    AtopDrain   = 2'b01,
    AtopExecute = 2'b10
  } state_e;

  logic   rd_fifo_full, rd_fifo_empty, rd_fifo_push, rd_fifo_pop,
          wr_fifo_full, wr_fifo_empty, wr_fifo_push, wr_fifo_pop;
  id_t    b_id,
          r_id,         ar_id;
  state_e state_q,      state_d;

  always_comb begin
    // Default assignments
    state_d      = state_q;
    rd_fifo_push = 1'b0;
    wr_fifo_push = 1'b0;

    // Default, connect the channels
    mst_req_o  = slv_req_i;
    slv_resp_o = mst_resp_i;

    // Serialize transactions -> tie downstream IDs to zero.
    mst_req_o.aw.id = '0;
    mst_req_o.ar.id = '0;

    // Reflect upstream ID in response.
    ar_id           = slv_req_i.ar.id;
    slv_resp_o.b.id = b_id;
    slv_resp_o.r.id = r_id;

    // Default, cut the AW/AR handshaking
    mst_req_o.ar_valid  = 1'b0;
    slv_resp_o.ar_ready = 1'b0;
    mst_req_o.aw_valid  = 1'b0;
    slv_resp_o.aw_ready = 1'b0;

    unique case (state_q)
      AtopIdle, AtopExecute: begin

        // Wait until the ATOP response(s) have been sent back upstream.
        if (state_q == AtopExecute) begin
          if ((wr_fifo_empty && rd_fifo_empty) || (wr_fifo_pop && rd_fifo_pop) ||
              (wr_fifo_empty && rd_fifo_pop)   || (wr_fifo_pop && rd_fifo_empty)) begin
            state_d = AtopIdle;
          end
        end

        // This part lets new Transactions through, if no ATOP is underway or the last ATOP
        // response has been transmitted.
        if ((state_q == AtopIdle) || (state_d == AtopIdle)) begin
          // Gate AR handshake with ready output of Read FIFO.
          mst_req_o.ar_valid  = slv_req_i.ar_valid  & ~rd_fifo_full;
          slv_resp_o.ar_ready = mst_resp_i.ar_ready & ~rd_fifo_full;
          rd_fifo_push        = mst_req_o.ar_valid  & mst_resp_i.ar_ready;
          if (slv_req_i.aw_valid) begin
            if (slv_req_i.aw.atop[5:4] == axi_pkg::ATOP_NONE) begin
              // Normal operation
              // Gate AW handshake with ready output of Write FIFO.
              mst_req_o.aw_valid  = ~wr_fifo_full;
              slv_resp_o.aw_ready = mst_resp_i.aw_ready & ~wr_fifo_full;
              wr_fifo_push        = mst_req_o.aw_valid  & mst_resp_i.aw_ready;
            end else begin
              // Atomic Operation received, go to drain state, when both channels are ready
              // Wait for finished or no AR beat
              if (!mst_req_o.ar_valid || (mst_req_o.ar_valid && mst_resp_i.ar_ready)) begin
                state_d = AtopDrain;
              end
            end
          end
        end
      end
      AtopDrain: begin
        // Send the ATOP AW when the last open transaction terminates
        if (wr_fifo_empty && rd_fifo_empty) begin
          mst_req_o.aw_valid  = 1'b1;
          slv_resp_o.aw_ready = mst_resp_i.aw_ready;
          wr_fifo_push        = mst_resp_i.aw_ready;
          if (slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP]) begin
            // Overwrite the read ID with the one from AW
            ar_id        = slv_req_i.aw.id;
            rd_fifo_push = mst_resp_i.aw_ready;
          end
          if (mst_resp_i.aw_ready) begin
            state_d = AtopExecute;
          end
        end
      end
      default : /* do nothing */;
    endcase

    // Gate B handshake with empty flag output of Write FIFO.
    slv_resp_o.b_valid = mst_resp_i.b_valid & ~wr_fifo_empty;
    mst_req_o.b_ready  = slv_req_i.b_ready  & ~wr_fifo_empty;

    // Gate R handshake with empty flag output of Read FIFO.
    slv_resp_o.r_valid = mst_resp_i.r_valid & ~rd_fifo_empty;
    mst_req_o.r_ready  = slv_req_i.r_ready  & ~rd_fifo_empty;
  end

  fifo_v3 #(
    .FALL_THROUGH ( 1'b0        ), // No fall-through as response has to come a cycle later anyway
    .DEPTH        ( MaxReadTxns ),
    .dtype        ( id_t        )
  ) i_rd_id_fifo (
    .clk_i,
    .rst_ni,
    .flush_i    ( 1'b0          ),
    .testmode_i ( 1'b0          ),
    .data_i     ( ar_id         ),
    .push_i     ( rd_fifo_push  ),
    .full_o     ( rd_fifo_full  ),
    .data_o     ( r_id          ),
    .empty_o    ( rd_fifo_empty ),
    .pop_i      ( rd_fifo_pop   ),
    .usage_o    ( /*not used*/  )
  );
  // Assign as this condition is needed in FSM
  assign rd_fifo_pop = slv_resp_o.r_valid & slv_req_i.r_ready & slv_resp_o.r.last;

  fifo_v3 #(
    .FALL_THROUGH ( 1'b0         ),
    .DEPTH        ( MaxWriteTxns ),
    .dtype        ( id_t         )
  ) i_wr_id_fifo (
    .clk_i,
    .rst_ni,
    .flush_i    ( 1'b0            ),
    .testmode_i ( 1'b0            ),
    .data_i     ( slv_req_i.aw.id ),
    .push_i     ( wr_fifo_push    ),
    .full_o     ( wr_fifo_full    ),
    .data_o     ( b_id            ),
    .empty_o    ( wr_fifo_empty   ),
    .pop_i      ( wr_fifo_pop     ),
    .usage_o    ( /*not used*/    )
  );
  // Assign as this condition is needed in FSM
  assign wr_fifo_pop = slv_resp_o.b_valid & slv_req_i.b_ready;

  `FFARN(state_q, state_d, AtopIdle, clk_i, rst_ni)

// pragma translate_off
`ifndef VERILATOR
  initial begin: p_assertions
    assert (AxiIdWidth    >= 1) else $fatal(1, "AXI ID width must be at least 1!");
    assert (MaxReadTxns   >= 1)
      else $fatal(1, "Maximum number of read transactions must be >= 1!");
    assert (MaxWriteTxns  >= 1)
      else $fatal(1, "Maximum number of write transactions must be >= 1!");
  end
  default disable iff (~rst_ni);
  aw_lost : assert property( @(posedge clk_i)
      (slv_req_i.aw_valid & slv_resp_o.aw_ready |-> mst_req_o.aw_valid & mst_resp_i.aw_ready))
    else $error("AW beat lost.");
  w_lost  : assert property( @(posedge clk_i)
      (slv_req_i.w_valid & slv_resp_o.w_ready |-> mst_req_o.w_valid & mst_resp_i.w_ready))
    else $error("W beat lost.");
  b_lost  : assert property( @(posedge clk_i)
      (mst_resp_i.b_valid & mst_req_o.b_ready |-> slv_resp_o.b_valid & slv_req_i.b_ready))
    else $error("B beat lost.");
  ar_lost : assert property( @(posedge clk_i)
      (slv_req_i.ar_valid & slv_resp_o.ar_ready |-> mst_req_o.ar_valid & mst_resp_i.ar_ready))
    else $error("AR beat lost.");
  r_lost :  assert property( @(posedge clk_i)
      (mst_resp_i.r_valid & mst_req_o.r_ready |-> slv_resp_o.r_valid & slv_req_i.r_ready))
    else $error("R beat lost.");
`endif
// pragma translate_on
endmodule

`include "axi/typedef.svh"
`include "axi/assign.svh"
/// Serialize all AXI transactions to a single ID (zero), interface version.
module axi_serializer_intf #(
  /// AXI4+ATOP ID width.
  parameter int unsigned AXI_ID_WIDTH   = 32'd0,
  /// AXI4+ATOP address width.
  parameter int unsigned AXI_ADDR_WIDTH = 32'd0,
  /// AXI4+ATOP data width.
  parameter int unsigned AXI_DATA_WIDTH = 32'd0,
  /// AXI4+ATOP user width.
  parameter int unsigned AXI_USER_WIDTH = 32'd0,
  /// Maximum number of in flight read transactions.
  parameter int unsigned MAX_READ_TXNS  = 32'd0,
  /// Maximum number of in flight write transactions.
  parameter int unsigned MAX_WRITE_TXNS = 32'd0
) (
  /// Clock
  input  logic    clk_i,
  /// Asynchronous reset, active low
  input  logic    rst_ni,
  /// AXI4+ATOP Slave modport
  AXI_BUS.Slave   slv,
  /// AXI4+ATOP Master modport
  AXI_BUS.Master  mst
);

  typedef logic [AXI_ID_WIDTH    -1:0] id_t;
  typedef logic [AXI_ADDR_WIDTH  -1:0] addr_t;
  typedef logic [AXI_DATA_WIDTH  -1:0] data_t;
  typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t;
  typedef logic [AXI_USER_WIDTH  -1:0] user_t;
  `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t)
  `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t)
  `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t)
  `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t)
  `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t)
  `AXI_TYPEDEF_REQ_T(req_t, aw_chan_t, w_chan_t, ar_chan_t)
  `AXI_TYPEDEF_RESP_T(resp_t, b_chan_t, r_chan_t)
  req_t  slv_req,  mst_req;
  resp_t slv_resp, mst_resp;
  `AXI_ASSIGN_TO_REQ(slv_req, slv)
  `AXI_ASSIGN_FROM_RESP(slv, slv_resp)
  `AXI_ASSIGN_FROM_REQ(mst, mst_req)
  `AXI_ASSIGN_TO_RESP(mst_resp, mst)

  axi_serializer #(
    .MaxReadTxns  ( MAX_READ_TXNS  ),
    .MaxWriteTxns ( MAX_WRITE_TXNS ),
    .AxiIdWidth   ( AXI_ID_WIDTH   ),
    .req_t        ( req_t          ),
    .resp_t       ( resp_t         )
  ) i_axi_serializer (
    .clk_i,
    .rst_ni,
    .slv_req_i  ( slv_req  ),
    .slv_resp_o ( slv_resp ),
    .mst_req_o  ( mst_req  ),
    .mst_resp_i ( mst_resp )
  );

// pragma translate_off
`ifndef VERILATOR
  initial begin: p_assertions
    assert (AXI_ADDR_WIDTH  >= 1) else $fatal(1, "AXI address width must be at least 1!");
    assert (AXI_DATA_WIDTH  >= 1) else $fatal(1, "AXI data width must be at least 1!");
    assert (AXI_ID_WIDTH    >= 1) else $fatal(1, "AXI ID width must be at least 1!");
    assert (AXI_USER_WIDTH  >= 1) else $fatal(1, "AXI user width must be at least 1!");
    assert (MAX_READ_TXNS   >= 1)
      else $fatal(1, "Maximum number of read transactions must be >= 1!");
    assert (MAX_WRITE_TXNS  >= 1)
      else $fatal(1, "Maximum number of write transactions must be >= 1!");
  end
`endif
// pragma translate_on
endmodule