// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 Western Digital Corporation or it's affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the 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.
//********************************************************************************
// $Id$
//
// Owner:
// Function: AHB to AXI4 Bridge
// Comments:
//
//********************************************************************************
module ahb_to_axi4
import el2_pkg::*;
#(
   TAG = 1,
   `include "el2_param.vh"
)
//   ,TAG  = 1)
(
   input                   clk,
   input                   rst_l,
   input                   scan_mode,
   input                   bus_clk_en,
   input                   clk_override,

   // AXI signals
   // AXI Write Channels
   output logic            axi_awvalid,
   input  logic            axi_awready,
   output logic [TAG-1:0]  axi_awid,
   output logic [31:0]     axi_awaddr,
   output logic [2:0]      axi_awsize,
   output logic [2:0]      axi_awprot,
   output logic [7:0]      axi_awlen,
   output logic [1:0]      axi_awburst,

   output logic            axi_wvalid,
   input  logic            axi_wready,
   output logic [63:0]     axi_wdata,
   output logic [7:0]      axi_wstrb,
   output logic            axi_wlast,

   input  logic            axi_bvalid,
   output logic            axi_bready,
   input  logic [1:0]      axi_bresp,
   input  logic [TAG-1:0]  axi_bid,

   // AXI Read Channels
   output logic            axi_arvalid,
   input  logic            axi_arready,
   output logic [TAG-1:0]  axi_arid,
   output logic [31:0]     axi_araddr,
   output logic [2:0]      axi_arsize,
   output logic [2:0]      axi_arprot,
   output logic [7:0]      axi_arlen,
   output logic [1:0]      axi_arburst,

   input  logic            axi_rvalid,
   output logic            axi_rready,
   input  logic [TAG-1:0]  axi_rid,
   input  logic [63:0]     axi_rdata,
   input  logic [1:0]      axi_rresp,

   // AHB-Lite signals
   input logic [31:0]      ahb_haddr,     // ahb bus address
   input logic [2:0]       ahb_hburst,    // tied to 0
   input logic             ahb_hmastlock, // tied to 0
   input logic [3:0]       ahb_hprot,     // tied to 4'b0011
   input logic [2:0]       ahb_hsize,     // size of bus transaction (possible values 0,1,2,3)
   input logic [1:0]       ahb_htrans,    // Transaction type (possible values 0,2 only right now)
   input logic             ahb_hwrite,    // ahb bus write
   input logic [63:0]      ahb_hwdata,    // ahb bus write data
   input logic             ahb_hsel,      // this slave was selected
   input logic             ahb_hreadyin,  // previous hready was accepted or not

   output logic [63:0]      ahb_hrdata,      // ahb bus read data
   output logic             ahb_hreadyout,   // slave ready to accept transaction
   output logic             ahb_hresp        // slave response (high indicates erro)

);

   logic [7:0]       master_wstrb;

 typedef enum logic [1:0] {   IDLE   = 2'b00,    // Nothing in the buffer. No commands yet recieved
                              WR     = 2'b01,    // Write Command recieved
                              RD     = 2'b10,    // Read Command recieved
                              PEND   = 2'b11     // Waiting on Read Data from core
                            } state_t;
   state_t      buf_state, buf_nxtstate;
   logic        buf_state_en;

   // Buffer signals (one entry buffer)
   logic                    buf_read_error_in, buf_read_error;
   logic [63:0]             buf_rdata;

   logic                    ahb_hready;
   logic                    ahb_hready_q;
   logic [1:0]              ahb_htrans_in, ahb_htrans_q;
   logic [2:0]              ahb_hsize_q;
   logic                    ahb_hwrite_q;
   logic [31:0]             ahb_haddr_q;
   logic [63:0]             ahb_hwdata_q;
   logic                    ahb_hresp_q;

    //Miscellaneous signals
   logic                    ahb_addr_in_dccm, ahb_addr_in_iccm, ahb_addr_in_pic;
   logic                    ahb_addr_in_dccm_region_nc, ahb_addr_in_iccm_region_nc, ahb_addr_in_pic_region_nc;
   // signals needed for the read data coming back from the core and to block any further commands as AHB is a blocking bus
   logic                    buf_rdata_en;

   logic                    ahb_bus_addr_clk_en, buf_rdata_clk_en;
   logic                    ahb_clk, ahb_addr_clk, buf_rdata_clk;
   // Command buffer is the holding station where we convert to AXI and send to core
   logic                    cmdbuf_wr_en, cmdbuf_rst;
   logic                    cmdbuf_full;
   logic                    cmdbuf_vld, cmdbuf_write;
   logic [1:0]              cmdbuf_size;
   logic [7:0]              cmdbuf_wstrb;
   logic [31:0]             cmdbuf_addr;
   logic [63:0]             cmdbuf_wdata;

   logic                    bus_clk;

// FSM to control the bus states and when to block the hready and load the command buffer
   always_comb begin
      buf_nxtstate      = IDLE;
      buf_state_en      = 1'b0;
      buf_rdata_en      = 1'b0;              // signal to load the buffer when the core sends read data back
      buf_read_error_in = 1'b0;              // signal indicating that an error came back with the read from the core
      cmdbuf_wr_en      = 1'b0;              // all clear from the gasket to load the buffer with the command for reads, command/dat for writes
      case (buf_state)
         IDLE: begin  // No commands recieved
                  buf_nxtstate      = ahb_hwrite ? WR : RD;
                  buf_state_en      = ahb_hready & ahb_htrans[1] & ahb_hsel;                 // only transition on a valid hrtans
          end
         WR: begin // Write command recieved last cycle
                  buf_nxtstate      = (ahb_hresp | (ahb_htrans[1:0] == 2'b0) | ~ahb_hsel) ? IDLE : ahb_hwrite  ? WR : RD;
                  buf_state_en      = (~cmdbuf_full | ahb_hresp) ;
                  cmdbuf_wr_en      = ~cmdbuf_full & ~(ahb_hresp | ((ahb_htrans[1:0] == 2'b01) & ahb_hsel));   // Dont send command to the buffer in case of an error or when the master is not ready with the data now.
         end
         RD: begin // Read command recieved last cycle.
                 buf_nxtstate      = ahb_hresp ? IDLE :PEND;                                       // If error go to idle, else wait for read data
                 buf_state_en      = (~cmdbuf_full | ahb_hresp);                                   // only when command can go, or if its an error
                 cmdbuf_wr_en      = ~ahb_hresp & ~cmdbuf_full;                                    // send command only when no error
         end
         PEND: begin // Read Command has been sent. Waiting on Data.
                 buf_nxtstate      = IDLE;                                                          // go back for next command and present data next cycle
                 buf_state_en      = axi_rvalid & ~cmdbuf_write;                                    // read data is back
                 buf_rdata_en      = buf_state_en;                                                  // buffer the read data coming back from core
                 buf_read_error_in = buf_state_en & |axi_rresp[1:0];                                // buffer error flag if return has Error ( ECC )
         end
     endcase
   end // always_comb begin

    rvdffs #($bits(state_t)) state_reg (.*, .din(buf_nxtstate), .dout({buf_state}), .en(buf_state_en), .clk(ahb_clk));

   assign master_wstrb[7:0]   = ({8{ahb_hsize_q[2:0] == 3'b0}}  & (8'b1    << ahb_haddr_q[2:0])) |
                                ({8{ahb_hsize_q[2:0] == 3'b1}}  & (8'b11   << ahb_haddr_q[2:0])) |
                                ({8{ahb_hsize_q[2:0] == 3'b10}} & (8'b1111 << ahb_haddr_q[2:0])) |
                                ({8{ahb_hsize_q[2:0] == 3'b11}} & 8'b1111_1111);

   // AHB signals
   assign ahb_hreadyout       = ahb_hresp ? (ahb_hresp_q & ~ahb_hready_q) :
                                         ((~cmdbuf_full | (buf_state == IDLE)) & ~(buf_state == RD | buf_state == PEND)  & ~buf_read_error);

   assign ahb_hready          = ahb_hreadyout & ahb_hreadyin;
   assign ahb_htrans_in[1:0]  = {2{ahb_hsel}} & ahb_htrans[1:0];
   assign ahb_hrdata[63:0]    = buf_rdata[63:0];
   assign ahb_hresp        = ((ahb_htrans_q[1:0] != 2'b0) & (buf_state != IDLE)  &

                             ((~(ahb_addr_in_dccm | ahb_addr_in_iccm)) |                                                                                   // request not for ICCM or DCCM
                             ((ahb_addr_in_iccm | (ahb_addr_in_dccm &  ahb_hwrite_q)) & ~((ahb_hsize_q[1:0] == 2'b10) | (ahb_hsize_q[1:0] == 2'b11))) |    // ICCM Rd/Wr OR DCCM Wr not the right size
                             ((ahb_hsize_q[2:0] == 3'h1) & ahb_haddr_q[0])   |                                                                             // HW size but unaligned
                             ((ahb_hsize_q[2:0] == 3'h2) & (|ahb_haddr_q[1:0])) |                                                                          // W size but unaligned
                             ((ahb_hsize_q[2:0] == 3'h3) & (|ahb_haddr_q[2:0])))) |                                                                        // DW size but unaligned
                             buf_read_error |                                                                                                              // Read ECC error
                             (ahb_hresp_q & ~ahb_hready_q);

   // Buffer signals - needed for the read data and ECC error response
   rvdff  #(.WIDTH(64)) buf_rdata_ff     (.din(axi_rdata[63:0]),  .dout(buf_rdata[63:0]), .clk(buf_rdata_clk), .*);
   rvdff  #(.WIDTH(1))  buf_read_error_ff(.din(buf_read_error_in),  .dout(buf_read_error),  .clk(ahb_clk),       .*);          // buf_read_error will be high only one cycle

   // All the Master signals are captured before presenting it to the command buffer. We check for Hresp before sending it to the cmd buffer.
   rvdff  #(.WIDTH(1))    hresp_ff  (.din(ahb_hresp),          .dout(ahb_hresp_q),       .clk(ahb_clk),      .*);
   rvdff  #(.WIDTH(1))    hready_ff (.din(ahb_hready),         .dout(ahb_hready_q),      .clk(ahb_clk),      .*);
   rvdff  #(.WIDTH(2))    htrans_ff (.din(ahb_htrans_in[1:0]), .dout(ahb_htrans_q[1:0]), .clk(ahb_clk),      .*);
   rvdff  #(.WIDTH(3))    hsize_ff  (.din(ahb_hsize[2:0]),     .dout(ahb_hsize_q[2:0]),  .clk(ahb_addr_clk), .*);
   rvdff  #(.WIDTH(1))    hwrite_ff (.din(ahb_hwrite),         .dout(ahb_hwrite_q),      .clk(ahb_addr_clk), .*);
   rvdff  #(.WIDTH(32))   haddr_ff  (.din(ahb_haddr[31:0]),    .dout(ahb_haddr_q[31:0]), .clk(ahb_addr_clk), .*);

   // Clock header logic
   assign ahb_bus_addr_clk_en = bus_clk_en & (ahb_hready & ahb_htrans[1]);
   assign buf_rdata_clk_en    = bus_clk_en & buf_rdata_en;

   rvclkhdr ahb_cgc       (.en(bus_clk_en),          .l1clk(ahb_clk),       .*);
   rvclkhdr ahb_addr_cgc  (.en(ahb_bus_addr_clk_en), .l1clk(ahb_addr_clk),  .*);
   rvclkhdr buf_rdata_cgc (.en(buf_rdata_clk_en),    .l1clk(buf_rdata_clk), .*);

   // Address check  dccm
   rvrangecheck #(.CCM_SADR(pt.DCCM_SADR),
                  .CCM_SIZE(pt.DCCM_SIZE)) addr_dccm_rangecheck (
      .addr(ahb_haddr_q[31:0]),
      .in_range(ahb_addr_in_dccm),
      .in_region(ahb_addr_in_dccm_region_nc)
   );

   // Address check  iccm
   if (pt.ICCM_ENABLE == 1) begin: GenICCM
      rvrangecheck #(.CCM_SADR(pt.ICCM_SADR),
                     .CCM_SIZE(pt.ICCM_SIZE)) addr_iccm_rangecheck (
         .addr(ahb_haddr_q[31:0]),
         .in_range(ahb_addr_in_iccm),
         .in_region(ahb_addr_in_iccm_region_nc)
      );
   end else begin: GenNoICCM
      assign ahb_addr_in_iccm = '0;
      assign ahb_addr_in_iccm_region_nc = '0;
   end

   // PIC memory address check
   rvrangecheck #(.CCM_SADR(pt.PIC_BASE_ADDR),
                  .CCM_SIZE(pt.PIC_SIZE)) addr_pic_rangecheck (
      .addr(ahb_haddr_q[31:0]),
      .in_range(ahb_addr_in_pic),
      .in_region(ahb_addr_in_pic_region_nc)
   );

   // Command Buffer - Holding for the commands to be sent for the AXI. It will be converted to the AXI signals.
   assign cmdbuf_rst         = (((axi_awvalid & axi_awready) | (axi_arvalid & axi_arready)) & ~cmdbuf_wr_en) | (ahb_hresp & ~cmdbuf_write);
   assign cmdbuf_full        = (cmdbuf_vld & ~((axi_awvalid & axi_awready) | (axi_arvalid & axi_arready)));

   rvdffsc #(.WIDTH(1))   cmdbuf_vldff      (.din(1'b1),                .dout(cmdbuf_vld),          .en(cmdbuf_wr_en),   .clear(cmdbuf_rst),      .clk(bus_clk), .*);
   rvdffs  #(.WIDTH(1))   cmdbuf_writeff    (.din(ahb_hwrite_q),        .dout(cmdbuf_write),        .en(cmdbuf_wr_en),   .clk(bus_clk), .*);
   rvdffs  #(.WIDTH(2))   cmdbuf_sizeff     (.din(ahb_hsize_q[1:0]),    .dout(cmdbuf_size[1:0]),    .en(cmdbuf_wr_en),   .clk(bus_clk), .*);
   rvdffs  #(.WIDTH(8))   cmdbuf_wstrbff    (.din(master_wstrb[7:0]),   .dout(cmdbuf_wstrb[7:0]),   .en(cmdbuf_wr_en),   .clk(bus_clk), .*);
   rvdffe  #(.WIDTH(32))  cmdbuf_addrff     (.din(ahb_haddr_q[31:0]),   .dout(cmdbuf_addr[31:0]),   .en(cmdbuf_wr_en),   .clk(bus_clk), .*);
   rvdffe  #(.WIDTH(64))  cmdbuf_wdataff    (.din(ahb_hwdata[63:0]),    .dout(cmdbuf_wdata[63:0]),  .en(cmdbuf_wr_en),   .clk(bus_clk), .*);

   // AXI Write Command Channel
   assign axi_awvalid           = cmdbuf_vld & cmdbuf_write;
   assign axi_awid[TAG-1:0]     = '0;
   assign axi_awaddr[31:0]      = cmdbuf_addr[31:0];
   assign axi_awsize[2:0]       = {1'b0, cmdbuf_size[1:0]};
   assign axi_awprot[2:0]       = 3'b0;
   assign axi_awlen[7:0]        = '0;
   assign axi_awburst[1:0]      = 2'b01;
   // AXI Write Data Channel - This is tied to the command channel as we only write the command buffer once we have the data.
   assign axi_wvalid            = cmdbuf_vld & cmdbuf_write;
   assign axi_wdata[63:0]       = cmdbuf_wdata[63:0];
   assign axi_wstrb[7:0]        = cmdbuf_wstrb[7:0];
   assign axi_wlast             = 1'b1;
  // AXI Write Response - Always ready. AHB does not require a write response.
   assign axi_bready            = 1'b1;
   // AXI Read Channels
   assign axi_arvalid           = cmdbuf_vld & ~cmdbuf_write;
   assign axi_arid[TAG-1:0]     = '0;
   assign axi_araddr[31:0]      = cmdbuf_addr[31:0];
   assign axi_arsize[2:0]       = {1'b0, cmdbuf_size[1:0]};
   assign axi_arprot            = 3'b0;
   assign axi_arlen[7:0]        = '0;
   assign axi_arburst[1:0]      = 2'b01;
   // AXI Read Response Channel - Always ready as AHB reads are blocking and the the buffer is available for the read coming back always.
   assign axi_rready            = 1'b1;

   // Clock header logic
   rvclkhdr bus_cgc        (.en(bus_clk_en),       .l1clk(bus_clk),       .*);

`ifdef ASSERT_ON
   property ahb_error_protocol;
      @(posedge ahb_clk) (ahb_hready & ahb_hresp) |-> (~$past(ahb_hready) & $past(ahb_hresp));
   endproperty
   assert_ahb_error_protocol: assert property (ahb_error_protocol) else
      $display("Bus Error with hReady isn't preceded with Bus Error without hready");

`endif

endmodule // ahb_to_axi4