// Copyright 2014-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.
//
// Igor Loi <igor.loi@unibo.it>
// Davide Rossi <davide.rossi@unibo.it>
// Florian Zaruba <zarubaf@iis.ee.ethz.ch>

`define OKAY   2'b00
`define EXOKAY 2'b01
`define SLVERR 2'b10
`define DECERR 2'b11

module axi2apb
#(
    parameter AXI4_ADDRESS_WIDTH = 32,
    parameter AXI4_RDATA_WIDTH   = 32,
    parameter AXI4_WDATA_WIDTH   = 32,
    parameter AXI4_ID_WIDTH      = 16,
    parameter AXI4_USER_WIDTH    = 10,
    parameter AXI_NUMBYTES       = AXI4_WDATA_WIDTH/8,

    parameter BUFF_DEPTH_SLAVE   = 4,
    parameter APB_ADDR_WIDTH     = 32
)
(
    input logic                           ACLK,
    input logic                           ARESETn,
    input logic                           test_en_i,

    input  logic [AXI4_ID_WIDTH-1:0]      AWID_i,
    input  logic [AXI4_ADDRESS_WIDTH-1:0] AWADDR_i,
    input  logic [ 7:0]                   AWLEN_i,
    input  logic [ 2:0]                   AWSIZE_i,
    input  logic [ 1:0]                   AWBURST_i,
    input  logic                          AWLOCK_i,
    input  logic [ 3:0]                   AWCACHE_i,
    input  logic [ 2:0]                   AWPROT_i,
    input  logic [ 3:0]                   AWREGION_i,
    input  logic [ AXI4_USER_WIDTH-1:0]   AWUSER_i,
    input  logic [ 3:0]                   AWQOS_i,
    input  logic                          AWVALID_i,
    output logic                          AWREADY_o,

    input  logic [AXI4_WDATA_WIDTH-1:0]   WDATA_i,
    input  logic [AXI_NUMBYTES-1:0]       WSTRB_i,
    input  logic                          WLAST_i,
    input  logic [AXI4_USER_WIDTH-1:0]    WUSER_i,
    input  logic                          WVALID_i,
    output logic                          WREADY_o,

    output logic   [AXI4_ID_WIDTH-1:0]    BID_o,
    output logic   [ 1:0]                 BRESP_o,
    output logic                          BVALID_o,
    output logic   [AXI4_USER_WIDTH-1:0]  BUSER_o,
    input  logic                          BREADY_i,

    input  logic [AXI4_ID_WIDTH-1:0]      ARID_i,
    input  logic [AXI4_ADDRESS_WIDTH-1:0] ARADDR_i,
    input  logic [ 7:0]                   ARLEN_i,
    input  logic [ 2:0]                   ARSIZE_i,
    input  logic [ 1:0]                   ARBURST_i,
    input  logic                          ARLOCK_i,
    input  logic [ 3:0]                   ARCACHE_i,
    input  logic [ 2:0]                   ARPROT_i,
    input  logic [ 3:0]                   ARREGION_i,
    input  logic [ AXI4_USER_WIDTH-1:0]   ARUSER_i,
    input  logic [ 3:0]                   ARQOS_i,
    input  logic                          ARVALID_i,
    output logic                          ARREADY_o,

    output  logic [AXI4_ID_WIDTH-1:0]     RID_o,
    output  logic [AXI4_RDATA_WIDTH-1:0]  RDATA_o,
    output  logic [ 1:0]                  RRESP_o,
    output  logic                         RLAST_o,
    output  logic [AXI4_USER_WIDTH-1:0]   RUSER_o,
    output  logic                         RVALID_o,
    input   logic                         RREADY_i,

    output logic                          PENABLE,
    output logic                          PWRITE,
    output logic [APB_ADDR_WIDTH-1:0]     PADDR,
    output logic                          PSEL,
    output logic [AXI4_WDATA_WIDTH-1:0]   PWDATA,
    input  logic [AXI4_RDATA_WIDTH-1:0]   PRDATA,
    input  logic                          PREADY,
    input  logic                          PSLVERR
);

    // --------------------
    // AXI write address bus
    // --------------------
    logic [AXI4_ID_WIDTH-1:0]       AWID;
    logic [AXI4_ADDRESS_WIDTH-1:0]  AWADDR;
    logic [ 7:0]                    AWLEN;
    logic [ 2:0]                    AWSIZE;
    logic [ 1:0]                    AWBURST;
    logic                           AWLOCK;
    logic [ 3:0]                    AWCACHE;
    logic [ 2:0]                    AWPROT;
    logic [ 3:0]                    AWREGION;
    logic [ AXI4_USER_WIDTH-1:0]    AWUSER;
    logic [ 3:0]                    AWQOS;
    logic                           AWVALID;
    logic                           AWREADY;
    // --------------------
    // AXI write data bus
    // --------------------
    logic [AXI4_WDATA_WIDTH-1:0]    WDATA;  // from FIFO
    logic [AXI_NUMBYTES-1:0]        WSTRB;  // from FIFO
    logic                           WLAST;  // from FIFO
    logic [AXI4_USER_WIDTH-1:0]     WUSER;  // from FIFO
    logic                           WVALID; // from FIFO
    logic                           WREADY; // TO FIFO
    // --------------------
    // AXI write response bus
    // --------------------
    logic   [AXI4_ID_WIDTH-1:0]     BID;
    logic   [ 1:0]                  BRESP;
    logic                           BVALID;
    logic   [AXI4_USER_WIDTH-1:0]   BUSER;
    logic                           BREADY;
    // --------------------
    // AXI read address bus
    // --------------------
    logic [AXI4_ID_WIDTH-1:0]       ARID;
    logic [AXI4_ADDRESS_WIDTH-1:0]  ARADDR;
    logic [ 7:0]                    ARLEN;
    logic [ 2:0]                    ARSIZE;
    logic [ 1:0]                    ARBURST;
    logic                           ARLOCK;
    logic [ 3:0]                    ARCACHE;
    logic [ 2:0]                    ARPROT;
    logic [ 3:0]                    ARREGION;
    logic [ AXI4_USER_WIDTH-1:0]    ARUSER;
    logic [ 3:0]                    ARQOS;
    logic                           ARVALID;
    logic                           ARREADY;
    // --------------------
    // AXI read data bus
    // --------------------
    logic [AXI4_ID_WIDTH-1:0]       RID;
    logic [AXI4_RDATA_WIDTH-1:0]    RDATA;
    logic [ 1:0]                    RRESP;
    logic                           RLAST;
    logic [AXI4_USER_WIDTH-1:0]     RUSER;
    logic                           RVALID;
    logic                           RREADY;

  enum logic [2:0] { IDLE,
                     DONE_SINGLE_RD,
                     WAIT_W_PREADY,
                     WAIT_R_PREADY,
                     SEND_B_RESP
                    } CS, NS;

  logic [AXI4_ADDRESS_WIDTH-1:0] address;
  logic sample_RDATA;

  logic [AXI4_RDATA_WIDTH-1:0] RDATA_Q;

  logic read_req;
  logic write_req;

  assign PENABLE = write_req | read_req;
  assign PWRITE  = write_req;
  assign PADDR   = address[APB_ADDR_WIDTH-1:0];
  assign PWDATA  = WDATA;
  assign PSEL    = 1'b1;

   // AXI WRITE ADDRESS CHANNEL BUFFER
   axi_aw_buffer #(
       .ID_WIDTH     ( AXI4_ID_WIDTH      ),
       .ADDR_WIDTH   ( AXI4_ADDRESS_WIDTH ),
       .USER_WIDTH   ( AXI4_USER_WIDTH    ),
       .BUFFER_DEPTH ( BUFF_DEPTH_SLAVE   )
   ) slave_aw_buffer_i (
      .clk_i           ( ACLK        ),
      .rst_ni          ( ARESETn     ),
      .test_en_i       ( test_en_i   ),

      .slave_valid_i   ( AWVALID_i   ),
      .slave_addr_i    ( AWADDR_i    ),
      .slave_prot_i    ( AWPROT_i    ),
      .slave_region_i  ( AWREGION_i  ),
      .slave_len_i     ( AWLEN_i     ),
      .slave_size_i    ( AWSIZE_i    ),
      .slave_burst_i   ( AWBURST_i   ),
      .slave_lock_i    ( AWLOCK_i    ),
      .slave_cache_i   ( AWCACHE_i   ),
      .slave_qos_i     ( AWQOS_i     ),
      .slave_id_i      ( AWID_i      ),
      .slave_user_i    ( AWUSER_i    ),
      .slave_ready_o   ( AWREADY_o   ),

      .master_valid_o  ( AWVALID     ),
      .master_addr_o   ( AWADDR      ),
      .master_prot_o   ( AWPROT      ),
      .master_region_o ( AWREGION    ),
      .master_len_o    ( AWLEN       ),
      .master_size_o   ( AWSIZE      ),
      .master_burst_o  ( AWBURST     ),
      .master_lock_o   ( AWLOCK      ),
      .master_cache_o  ( AWCACHE     ),
      .master_qos_o    ( AWQOS       ),
      .master_id_o     ( AWID        ),
      .master_user_o   ( AWUSER      ),
      .master_ready_i  ( AWREADY     )
   );

   // AXI WRITE ADDRESS CHANNEL BUFFER
   axi_ar_buffer #(
       .ID_WIDTH     ( AXI4_ID_WIDTH      ),
       .ADDR_WIDTH   ( AXI4_ADDRESS_WIDTH ),
       .USER_WIDTH   ( AXI4_USER_WIDTH    ),
       .BUFFER_DEPTH ( BUFF_DEPTH_SLAVE   )
   ) slave_ar_buffer_i (
      .clk_i           ( ACLK       ),
      .rst_ni          ( ARESETn    ),
      .test_en_i       ( test_en_i  ),

      .slave_valid_i   ( ARVALID_i  ),
      .slave_addr_i    ( ARADDR_i   ),
      .slave_prot_i    ( ARPROT_i   ),
      .slave_region_i  ( ARREGION_i ),
      .slave_len_i     ( ARLEN_i    ),
      .slave_size_i    ( ARSIZE_i   ),
      .slave_burst_i   ( ARBURST_i  ),
      .slave_lock_i    ( ARLOCK_i   ),
      .slave_cache_i   ( ARCACHE_i  ),
      .slave_qos_i     ( ARQOS_i    ),
      .slave_id_i      ( ARID_i     ),
      .slave_user_i    ( ARUSER_i   ),
      .slave_ready_o   ( ARREADY_o  ),

      .master_valid_o  ( ARVALID    ),
      .master_addr_o   ( ARADDR     ),
      .master_prot_o   ( ARPROT     ),
      .master_region_o ( ARREGION   ),
      .master_len_o    ( ARLEN      ),
      .master_size_o   ( ARSIZE     ),
      .master_burst_o  ( ARBURST    ),
      .master_lock_o   ( ARLOCK     ),
      .master_cache_o  ( ARCACHE    ),
      .master_qos_o    ( ARQOS      ),
      .master_id_o     ( ARID       ),
      .master_user_o   ( ARUSER     ),
      .master_ready_i  ( ARREADY    )
   );


   axi_w_buffer #(
       .DATA_WIDTH(AXI4_WDATA_WIDTH),
       .USER_WIDTH(AXI4_USER_WIDTH),
       .BUFFER_DEPTH(BUFF_DEPTH_SLAVE)
   ) slave_w_buffer_i (
        .clk_i          ( ACLK      ),
        .rst_ni         ( ARESETn   ),
        .test_en_i      ( test_en_i ),

        .slave_valid_i  ( WVALID_i  ),
        .slave_data_i   ( WDATA_i   ),
        .slave_strb_i   ( WSTRB_i   ),
        .slave_user_i   ( WUSER_i   ),
        .slave_last_i   ( WLAST_i   ),
        .slave_ready_o  ( WREADY_o  ),

        .master_valid_o ( WVALID    ),
        .master_data_o  ( WDATA     ),
        .master_strb_o  ( WSTRB     ),
        .master_user_o  ( WUSER     ),
        .master_last_o  ( WLAST     ),
        .master_ready_i ( WREADY    )
    );

   axi_r_buffer #(
        .ID_WIDTH     ( AXI4_ID_WIDTH    ),
        .DATA_WIDTH   ( AXI4_RDATA_WIDTH ),
        .USER_WIDTH   ( AXI4_USER_WIDTH  ),
        .BUFFER_DEPTH ( BUFF_DEPTH_SLAVE )
   ) slave_r_buffer_i (
        .clk_i          ( ACLK       ),
        .rst_ni         ( ARESETn    ),
        .test_en_i      ( test_en_i  ),

        .slave_valid_i  ( RVALID     ),
        .slave_data_i   ( RDATA      ),
        .slave_resp_i   ( RRESP      ),
        .slave_user_i   ( RUSER      ),
        .slave_id_i     ( RID        ),
        .slave_last_i   ( RLAST      ),
        .slave_ready_o  ( RREADY     ),

        .master_valid_o ( RVALID_o   ),
        .master_data_o  ( RDATA_o    ),
        .master_resp_o  ( RRESP_o    ),
        .master_user_o  ( RUSER_o    ),
        .master_id_o    ( RID_o      ),
        .master_last_o  ( RLAST_o    ),
        .master_ready_i ( RREADY_i   )
   );

   axi_b_buffer #(
        .ID_WIDTH(AXI4_ID_WIDTH),
        .USER_WIDTH(AXI4_USER_WIDTH),
        .BUFFER_DEPTH(BUFF_DEPTH_SLAVE)
   ) slave_b_buffer (
        .clk_i          ( ACLK      ),
        .rst_ni         ( ARESETn   ),
        .test_en_i      ( test_en_i ),

        .slave_valid_i  ( BVALID    ),
        .slave_resp_i   ( BRESP     ),
        .slave_id_i     ( BID       ),
        .slave_user_i   ( BUSER     ),
        .slave_ready_o  ( BREADY    ),

        .master_valid_o ( BVALID_o  ),
        .master_resp_o  ( BRESP_o   ),
        .master_id_o    ( BID_o     ),
        .master_user_o  ( BUSER_o   ),
        .master_ready_i ( BREADY_i  )
    );

    always_comb begin

      read_req     = 1'b0;
      write_req    = 1'b0;
      address      = '0;

      sample_RDATA = 1'b0;

      ARREADY      = 1'b0;
      AWREADY      = 1'b0;
      WREADY       = 1'b0;

      BVALID       = 1'b0;
      BRESP        = `OKAY;
      BID          = AWID;
      BUSER        = AWUSER;

      RVALID       = 1'b0;
      RLAST        = 1'b0;
      RID          = ARID;
      RUSER        = ARUSER;
      RRESP        = `OKAY;
      RDATA        = RDATA_Q;

      case(CS)

        WAIT_R_PREADY: begin
            read_req     = 1'b1;
            address      = ARADDR[APB_ADDR_WIDTH  - 1 : 0];
            sample_RDATA = PREADY;

            if (PREADY == 1'b1) begin // APB is READY --> RDATA is AVAILABLE
               NS = DONE_SINGLE_RD;
            end
        end

        WAIT_W_PREADY: begin
            write_req   = 1'b1;
            address     = AWADDR[APB_ADDR_WIDTH - 1:0];
            // There is a Pending WRITE!!
            if (PREADY == 1'b1) begin // APB is READY --> WDATA is LAtched
                NS = SEND_B_RESP;
            end
        end

        IDLE: begin
            if (ARVALID == 1'b1) begin
                read_req     = 1'b1;
                address      = ARADDR[APB_ADDR_WIDTH - 1:0];;
                sample_RDATA = PREADY;

                if(PREADY == 1'b1) begin // APB is READY --> RDATA is AVAILABLE
                    NS   = DONE_SINGLE_RD;
                end else begin // APB not ready
                    NS = WAIT_R_PREADY;
                end
            end else begin
                if (AWVALID) begin
                    address =  AWADDR[APB_ADDR_WIDTH - 1:0];
                    if (WVALID) begin
                        write_req = 1'b1;

                        // There is a Pending WRITE!!
                        if (PREADY == 1'b1) begin// APB is READY --> WDATA is LAtched
                            NS = SEND_B_RESP;
                        end else begin // APB not READY
                           NS = WAIT_W_PREADY;
                        end
                    end else begin // GOT ADDRESS WRITE, not DATA
                        write_req       = 1'b0;
                        address         = '0;
                        NS              = IDLE;
                    end
                end
            end
        end

        SEND_B_RESP: begin

            BVALID   = 1'b1;
            address  = '0;

            if (BREADY) begin
                NS      = IDLE;
                AWREADY = 1'b1;
                WREADY  = 1'b1;
            end
        end

        DONE_SINGLE_RD: begin

            RVALID    = 1'b1;
            RLAST     = 1;
            address   = '0;

            if (RREADY) begin // ready to send back the rdata
                NS = IDLE;
                ARREADY = 1'b1;
            end
        end

        default: NS = IDLE;

      endcase
    end

    always_ff @(posedge ACLK, negedge ARESETn) begin
        if (ARESETn == 1'b0) begin
            CS      <= IDLE;
            RDATA_Q <= '0;
        end else begin
            CS      <= NS;

            if (sample_RDATA)
                RDATA_Q <= PRDATA;
        end
    end

endmodule