// 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>
// - Samuel Riedel <sriedel@iis.ee.ethz.ch>

// Description: AXI4-Lite to APB4 bridge
//
// This module has one AXI4-Lite slave port and is capable of generating
// APB4 requests for multiple APB4 slave modules. The address and data widths
// of AXI4-Lite and APB4 have to be the same for both ports and are enforced over assertions.
// The selection of the APB4 slave is handled by the `addr_decode` module from `common_cells`.
// This module will answer with a `axi_pkg::RESP_DECERR` when a AXI4-Lite AW or AR request
// is not in the address map of the module and `no` APB4 request is made.
//
// The type of the APB4 request is required to look like:
//   typedef struct packed {
//     addr_t paddr;   // same as AXI4-Lite
//     prot_t pprot;   // same as AXI4-Lite, specification is the same
//     logic  psel;    // each APB4 slave has its own single-bit psel
//     logic  penable; // enable signal shows second APB4 cycle
//     logic  pwrite;  // write enable
//     data_t pwdata;  // write data, comes from W channel
//     strb_t pstrb;   // write strb, comes from W channel
//   } apb_req_t;
// For every APB4 slave, the `psel` field is only asserted when the decoded address matches that
// slave.  If `psel` is deasserted, the value of the other fields may be undefined.
//
// The type of the APB4 response is required to look like:
//  typedef struct packed {
//    logic  pready;   // slave signals that it is ready
//    data_t prdata;   // read data, connects to R channel
//    logic  pslverr;  // gets translated into either `axi_pkg::RESP_OK` or `axi_pkg::RESP_SLVERR`
//  } apb_resp_t;
// Each connected `apb_resp`, has to be connected to the corresponding port index. The module
// routes the response depending on the `apb_req.psel` bit and `apb_req.pwrite` either to the
// AXI4Lite B channel for writes and to the R channel for reads.

`include "common_cells/registers.svh"

module axi_lite_to_apb #(
  parameter int unsigned NoApbSlaves = 32'd1,  // Number of connected APB slaves
  parameter int unsigned NoRules     = 32'd1,  // Number of APB address rules
  parameter int unsigned AddrWidth   = 32'd32, // Address width
  parameter int unsigned DataWidth   = 32'd32, // Data width
  parameter bit PipelineRequest      = 1'b0,   // Pipeline request path
  parameter bit PipelineResponse     = 1'b0,   // Pipeline response path
  parameter type      axi_lite_req_t = logic,  // AXI4-Lite request struct
  parameter type     axi_lite_resp_t = logic,  // AXI4-Lite response sruct
  parameter type           apb_req_t = logic,  // APB4 request struct
  parameter type          apb_resp_t = logic,  // APB4 response struct
  parameter type              rule_t = logic   // Address Decoder rule from `common_cells`
) (
  input  logic                        clk_i,     // Clock
  input  logic                        rst_ni,    // Asynchronous reset active low
  // AXI LITE slave port
  input  axi_lite_req_t               axi_lite_req_i,
  output axi_lite_resp_t              axi_lite_resp_o,
  // APB master port
  output apb_req_t  [NoApbSlaves-1:0] apb_req_o,
  input  apb_resp_t [NoApbSlaves-1:0] apb_resp_i,
  // APB Slave Address Map
  input  rule_t     [NoRules-1:0]     addr_map_i
);
  localparam logic RD = 1'b0; // Encode index of a read request
  localparam logic WR = 1'b1; // Encode index of a write request
  localparam int unsigned SelIdxWidth = (NoApbSlaves > 32'd1) ? $clog2(NoApbSlaves) : 32'd1;
  typedef logic [AddrWidth-1:0]   addr_t;    // AXI4-Lite, APB4 and rule_t addr width
  typedef logic [DataWidth-1:0]   data_t;    // AXI4-Lite and APB4 data width
  typedef logic [DataWidth/8-1:0] strb_t;    // AXI4-Lite and APB4 strb width
  typedef logic [SelIdxWidth-1:0] sel_idx_t; // Selection index from addr_decode

  typedef struct packed {
    addr_t          addr;
    axi_pkg::prot_t prot; // prot has the exact same bit mapping in AXI4-Lite as In APB v2.0
    data_t          data;
    strb_t          strb;
    logic           write;
  } int_req_t; // request type generated by the read and write channel, internal
  typedef struct packed {
    data_t          data; // read data from APB
    axi_pkg::resp_t resp; // response bit from APB
  } int_resp_t; // internal
  typedef enum logic {
    Setup  = 1'b0, // APB in Idle or Setup
    Access = 1'b1  // APB in Access
  } apb_state_e;


  // Signals from AXI4-Lite slave to arbitration tree
  int_req_t [1:0] axi_req;
  logic [1:0]     axi_req_valid, axi_req_ready;

  // Signals from response spill registers
  axi_pkg::resp_t axi_bresp;
  logic           axi_bresp_valid, axi_bresp_ready;
  int_resp_t      axi_rresp;
  logic           axi_rresp_valid, axi_rresp_ready;

  // -----------------------------------------------------------------------------------------------
  // AXI4-Lite slave
  // -----------------------------------------------------------------------------------------------
  // read request
  assign axi_req[RD] = '{
    addr:  axi_lite_req_i.ar.addr,
    prot:  axi_lite_req_i.ar.prot,
    data:  '0,
    strb:  '0,
    write: RD
  };
  assign axi_req_valid[RD] = axi_lite_req_i.ar_valid;
  // write request
  assign axi_req[WR] = '{
    addr:  axi_lite_req_i.aw.addr,
    prot:  axi_lite_req_i.aw.prot,
    data:  axi_lite_req_i.w.data,
    strb:  axi_lite_req_i.w.strb,
    write: WR
  };
  assign axi_req_valid[WR] = axi_lite_req_i.aw_valid & axi_lite_req_i.w_valid;
  assign axi_lite_resp_o = '{
    aw_ready: axi_req_valid[WR] & axi_req_ready[WR],        // if AXI AW & W valid & tree gnt_o[WR]
    w_ready:  axi_req_valid[WR] & axi_req_ready[WR],        // if AXI AW & W valid & tree gnt_o[WR]
    b:       '{resp: axi_bresp},                            // from spill reg
    b_valid:  axi_bresp_valid,                              // from spill reg
    ar_ready: axi_req_valid[RD] & axi_req_ready[RD],        // if AXI AR valid and tree gnt[RD]
    r:       '{data: axi_rresp.data, resp: axi_rresp.resp}, // from spill reg
    r_valid:  axi_rresp_valid                               // from spill reg
  };
  // -----------------------------------------------------------------------------------------------
  // Arbitration between write and read plus spill register for request and response
  // -----------------------------------------------------------------------------------------------
  int_req_t       arb_req,                          apb_req;
  logic           arb_req_valid,   arb_req_ready,   apb_req_valid,   apb_req_ready;
  axi_pkg::resp_t apb_wresp;
  logic           apb_wresp_valid, apb_wresp_ready;
  int_resp_t      apb_rresp;
  logic           apb_rresp_valid, apb_rresp_ready;

  rr_arb_tree #(
    .NumIn    ( 32'd2     ),
    .DataType ( int_req_t ),
    .ExtPrio  ( 1'b0      ),
    .AxiVldRdy( 1'b1      ),
    .LockIn   ( 1'b1      )
  ) i_req_arb (
    .clk_i,
    .rst_ni,
    .flush_i ( '0            ),
    .rr_i    ( '0            ),
    .req_i   ( axi_req_valid ),
    .gnt_o   ( axi_req_ready ),
    .data_i  ( axi_req       ),
    .gnt_i   ( arb_req_ready ),
    .req_o   ( arb_req_valid ),
    .data_o  ( arb_req       ),
    .idx_o   ( /*not used*/  )
  );

  if (PipelineRequest) begin : gen_req_spill
    spill_register #(
      .T      ( int_req_t ),
      .Bypass ( 1'b0      )
    ) i_req_spill (
      .clk_i,
      .rst_ni,
      .valid_i ( arb_req_valid ),
      .ready_o ( arb_req_ready ),
      .data_i  ( arb_req       ),
      .valid_o ( apb_req_valid ),
      .ready_i ( apb_req_ready ),
      .data_o  ( apb_req       )
    );
  end else begin : gen_req_ft_reg
    fall_through_register #(
      .T  ( int_req_t )
    ) i_req_ft_reg (
      .clk_i,
      .rst_ni,
      .clr_i      ( 1'b0          ),
      .testmode_i ( 1'b0          ),
      .valid_i    ( arb_req_valid ),
      .ready_o    ( arb_req_ready ),
      .data_i     ( arb_req       ),
      .valid_o    ( apb_req_valid ),
      .ready_i    ( apb_req_ready ),
      .data_o     ( apb_req       )
    );
  end

  if (PipelineResponse) begin : gen_resp_spill
    spill_register #(
      .T      ( axi_pkg::resp_t ),
      .Bypass ( 1'b0            )
    ) i_write_resp_spill (
      .clk_i,
      .rst_ni,
      .valid_i ( apb_wresp_valid        ),
      .ready_o ( apb_wresp_ready        ),
      .data_i  ( apb_wresp              ),
      .valid_o ( axi_bresp_valid        ),
      .ready_i ( axi_lite_req_i.b_ready ),
      .data_o  ( axi_bresp              )
    );
    spill_register #(
      .T      ( int_resp_t  ),
      .Bypass ( 1'b0        )
    ) i_read_resp_spill (
      .clk_i,
      .rst_ni,
      .valid_i ( apb_rresp_valid        ),
      .ready_o ( apb_rresp_ready        ),
      .data_i  ( apb_rresp              ),
      .valid_o ( axi_rresp_valid        ),
      .ready_i ( axi_lite_req_i.r_ready ),
      .data_o  ( axi_rresp              )
    );
  end else begin : gen_resp_ft_reg
    fall_through_register #(
      .T  ( axi_pkg::resp_t )
    ) i_write_resp_ft_reg (
      .clk_i,
      .rst_ni,
      .clr_i      ( 1'b0                    ),
      .testmode_i ( 1'b0                    ),
      .valid_i    ( apb_wresp_valid         ),
      .ready_o    ( apb_wresp_ready         ),
      .data_i     ( apb_wresp               ),
      .valid_o    ( axi_bresp_valid         ),
      .ready_i    ( axi_lite_req_i.b_ready  ),
      .data_o     ( axi_bresp               )
    );
    fall_through_register #(
      .T  ( int_resp_t )
    ) i_read_resp_ft_reg (
      .clk_i,
      .rst_ni,
      .clr_i      ( 1'b0                    ),
      .testmode_i ( 1'b0                    ),
      .valid_i    ( apb_rresp_valid         ),
      .ready_o    ( apb_rresp_ready         ),
      .data_i     ( apb_rresp               ),
      .valid_o    ( axi_rresp_valid         ),
      .ready_i    ( axi_lite_req_i.r_ready  ),
      .data_o     ( axi_rresp               )
    );
  end

  // -----------------------------------------------------------------------------------------------
  // APB master FSM
  // -----------------------------------------------------------------------------------------------
  // APB access state machine
  apb_state_e apb_state_q, apb_state_d;
  logic       apb_update;
  // output of address decoder to determine PSELx signal
  logic     apb_dec_valid;
  sel_idx_t apb_sel_idx;
  addr_decode #(
    .NoIndices( NoApbSlaves ),
    .NoRules  ( NoRules     ),
    .addr_t   ( addr_t      ),
    .rule_t   ( rule_t      )
  ) i_apb_decode (
    .addr_i           ( apb_req.addr  ),
    .addr_map_i       ( addr_map_i    ),
    .idx_o            ( apb_sel_idx   ),
    .dec_valid_o      ( apb_dec_valid ), // when not valid -> decode error
    .dec_error_o      ( /*not used*/  ),
    .en_default_idx_i ( '0            ),
    .default_idx_i    ( '0            )
  );

  always_comb begin
    // default assignments
    apb_state_d     = apb_state_q;
    apb_update      = 1'b0;
    apb_req_o       = '0;
    apb_req_ready   = 1'b0;
    // response defaults to the two response spill registers
    apb_wresp       = axi_pkg::RESP_SLVERR;
    apb_wresp_valid = 1'b0;
    apb_rresp       = '{data: data_t'(32'hDEA110C8), resp: axi_pkg::RESP_SLVERR};
    apb_rresp_valid = 1'b0;

    unique case (apb_state_q)
      Setup: begin
        // `Idle` and `Setup` steps
        // can check here for readiness, because the response goes into spill_registers
        if (apb_req_valid && apb_wresp_ready && apb_rresp_ready) begin
          if (apb_dec_valid) begin
            // `Setup` step
            // set the request output
            apb_req_o[apb_sel_idx] = '{
              paddr:   apb_req.addr,
              pprot:   apb_req.prot,
              psel:    1'b1,
              penable: 1'b0,
              pwrite:  apb_req.write,
              pwdata:  apb_req.data,
              pstrb:   apb_req.strb
            };
            apb_state_d = Access;
            apb_update  = 1'b1;
          end else begin
            // decode error, generate error and do not generate APB request, pop it
            apb_req_ready = 1'b1;
            if (apb_req.write) begin
              apb_wresp       = axi_pkg::RESP_DECERR;
              apb_wresp_valid = 1'b1;
            end else begin
              apb_rresp.resp  = axi_pkg::RESP_DECERR;
              apb_rresp_valid = 1'b1;
            end
          end
        end
      end
      Access: begin
        // `Access` step
        apb_req_o[apb_sel_idx] = '{
          paddr:   apb_req.addr,
          pprot:   apb_req.prot,
          psel:    1'b1,
          penable: 1'b1,
          pwrite:  apb_req.write,
          pwdata:  apb_req.data,
          pstrb:   apb_req.strb
        };
        if (apb_resp_i[apb_sel_idx].pready) begin
          // transfer, pop the request, generate response and update state
          apb_req_ready = 1'b1;
          // we are only in this state if the response spill registers are ready anyway
          if (apb_req.write) begin
            apb_wresp       = apb_resp_i[apb_sel_idx].pslverr ?
                                  axi_pkg::RESP_SLVERR : axi_pkg::RESP_OKAY;
            apb_wresp_valid = 1'b1;
          end else begin
            apb_rresp.data  = apb_resp_i[apb_sel_idx].prdata;
            apb_rresp.resp  = apb_resp_i[apb_sel_idx].pslverr ?
                                  axi_pkg::RESP_SLVERR : axi_pkg::RESP_OKAY;
            apb_rresp_valid = 1'b1;
          end
          apb_state_d = Setup;
          apb_update  = 1'b1;
        end
      end
      default: /* do nothing */ ;
    endcase
  end

  `FFLARN(apb_state_q, apb_state_d, apb_update, Setup, clk_i, rst_ni)

  // parameter check
  // pragma translate_off
  `ifndef VERILATOR
  initial begin : check_params
    addr_width:  assert ($bits(axi_lite_req_i.aw.addr ) == $bits(apb_req_o[0].paddr)) else
      $fatal(1, $sformatf("AXI4-Lite and APB address width not equal"));
    wdata_width: assert ($bits(axi_lite_req_i.w.data ) == $bits(apb_req_o[0].pwdata)) else
      $fatal(1, $sformatf("AXI4-Lite and APB write data width not equal"));
    strb_width:  assert ($bits(axi_lite_req_i.w.strb ) == $bits(apb_req_o[0].pstrb)) else
      $fatal(1, $sformatf("AXI4-Lite and APB strobe width not equal"));
    rdata_width: assert ($bits(axi_lite_resp_o.r.data ) == $bits(apb_resp_i[0].prdata)) else
      $fatal(1, $sformatf("AXI4-Lite and APB read data width not equal"));
    sel_width:   assert ($bits(apb_req_o[0].psel) == 32'd1) else
      $fatal(1, $sformatf("APB psel signal has to have a width of 1'b1"));
  end
  `endif
  // pragma translate_on
endmodule

`include "axi/typedef.svh"
`include "axi/assign.svh"

module axi_lite_to_apb_intf #(
  parameter int unsigned NoApbSlaves = 32'd1,  // Number of connected APB slaves
  parameter int unsigned NoRules     = 32'd1,  // Number of APB address rules
  parameter int unsigned AddrWidth   = 32'd32, // Address width
  parameter int unsigned DataWidth   = 32'd32, // Data width
  parameter bit PipelineRequest      = 1'b0,   // Pipeline request path
  parameter bit PipelineResponse     = 1'b0,   // Pipeline response path
  parameter type         rule_t      = logic,  // Address Decoder rule from `common_cells`
  // DEPENDENT PARAMERETS, DO NOT OVERWRITE!
  parameter type              addr_t = logic [AddrWidth-1:0],
  parameter type              data_t = logic [DataWidth-1:0],
  parameter type              strb_t = logic [DataWidth/8-1:0],
  parameter type              sel_t  = logic [NoApbSlaves-1:0]
) (
  input  logic                    clk_i,     // Clock
  input  logic                    rst_ni,    // Asynchronous reset active low
  // AXI LITE slave port
  AXI_LITE.Slave                  slv,
  // APB master port
  output addr_t                   paddr_o,
  output logic  [2:0]             pprot_o,
  output sel_t                    pselx_o,
  output logic                    penable_o,
  output logic                    pwrite_o,
  output data_t                   pwdata_o,
  output strb_t                   pstrb_o,
  input  logic  [NoApbSlaves-1:0] pready_i,
  input  data_t [NoApbSlaves-1:0] prdata_i,
  input         [NoApbSlaves-1:0] pslverr_i,
  // APB Slave Address Map
  input  rule_t [NoRules-1:0]     addr_map_i
);
  localparam int unsigned SelIdxWidth = NoApbSlaves > 1 ? $clog2(NoApbSlaves) : 1;

  typedef struct packed {
    addr_t          paddr;   // same as AXI4-Lite
    axi_pkg::prot_t pprot;   // same as AXI4-Lite, specification is the same
    logic           psel;    // onehot, one psel line per connected APB4 slave
    logic           penable; // enable signal shows second APB4 cycle
    logic           pwrite;  // write enable
    data_t          pwdata;  // write data, comes from W channel
    strb_t          pstrb;   // write strb, comes from W channel
  } apb_req_t;

  typedef struct packed {
    logic  pready;   // slave signals that it is ready
    data_t prdata;   // read data, connects to R channel
    logic  pslverr;  // gets translated into either `axi_pkg::RESP_OK` or `axi_pkg::RESP_SLVERR`
  } apb_resp_t;

  `AXI_LITE_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t)
  `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t)
  `AXI_LITE_TYPEDEF_B_CHAN_T(b_chan_t)
  `AXI_LITE_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t)
  `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_t, data_t)
  `AXI_LITE_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t)
  `AXI_LITE_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t)

  axi_req_t                     axi_req;
  axi_resp_t                    axi_resp;
  apb_req_t   [NoApbSlaves-1:0] apb_req;
  apb_resp_t  [NoApbSlaves-1:0] apb_resp;
  logic       [SelIdxWidth-1:0] apb_sel;

  `AXI_LITE_ASSIGN_TO_REQ(axi_req, slv)
  `AXI_LITE_ASSIGN_FROM_RESP(slv, axi_resp)

  onehot_to_bin #(
    .ONEHOT_WIDTH ( NoApbSlaves )
  ) i_onehot_to_bin (
    .onehot ( pselx_o ),
    .bin    ( apb_sel )
  );

  assign paddr_o   = apb_req[apb_sel].paddr;
  assign pprot_o   = apb_req[apb_sel].pprot;
  assign penable_o = apb_req[apb_sel].penable;
  assign pwrite_o  = apb_req[apb_sel].pwrite;
  assign pwdata_o  = apb_req[apb_sel].pwdata;
  assign pstrb_o   = apb_req[apb_sel].pstrb;
  for (genvar i = 0; i < NoApbSlaves; i++) begin : gen_apb_resp_assign
    assign pselx_o[i]          = apb_req[i].psel;
    assign apb_resp[i].pready  = pready_i[i];
    assign apb_resp[i].prdata  = prdata_i[i];
    assign apb_resp[i].pslverr = pslverr_i[i];
  end

  axi_lite_to_apb #(
    .NoApbSlaves      ( NoApbSlaves       ),
    .NoRules          ( NoRules           ),
    .AddrWidth        ( AddrWidth         ),
    .DataWidth        ( DataWidth         ),
    .PipelineRequest  ( PipelineRequest   ),
    .PipelineResponse ( PipelineResponse  ),
    .axi_lite_req_t   ( axi_req_t         ),
    .axi_lite_resp_t  ( axi_resp_t        ),
    .apb_req_t        ( apb_req_t         ),
    .apb_resp_t       ( apb_resp_t        ),
    .rule_t           ( rule_t            )
  ) i_axi_lite_to_apb (
    .clk_i,     // Clock
    .rst_ni,    // Asynchronous reset active low
    // AXI LITE slave port
    .axi_lite_req_i  ( axi_req  ),
    .axi_lite_resp_o ( axi_resp ),
    // APB master port
    .apb_req_o       ( apb_req  ),
    .apb_resp_i      ( apb_resp ),
    // APB Slave Address Map
    .addr_map_i
  );
endmodule
