// Copyright © 2019-2023
// 
// 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.

`include "VX_platform.vh"

`TRACING_OFF
module VX_axi_adapter #(
    parameter DATA_WIDTH     = 512, 
    parameter ADDR_WIDTH     = 32,
    parameter TAG_WIDTH      = 8,
    parameter NUM_BANKS      = 1, 
    parameter AVS_ADDR_WIDTH = (ADDR_WIDTH - `CLOG2(DATA_WIDTH/8)),
    parameter OUT_REG_RSP    = 0
) (
    input  wire                     clk,
    input  wire                     reset,

    // Vortex request
    input wire                      mem_req_valid,
    input wire                      mem_req_rw,
    input wire [DATA_WIDTH/8-1:0]   mem_req_byteen,
    input wire [AVS_ADDR_WIDTH-1:0] mem_req_addr,
    input wire [DATA_WIDTH-1:0]     mem_req_data,
    input wire [TAG_WIDTH-1:0]      mem_req_tag,
    output wire                     mem_req_ready,

    // Vortex response    
    output wire                     mem_rsp_valid,        
    output wire [DATA_WIDTH-1:0]    mem_rsp_data,
    output wire [TAG_WIDTH-1:0]     mem_rsp_tag,
    input wire                      mem_rsp_ready,

    // AXI write request address channel  
    output wire                     m_axi_awvalid [NUM_BANKS],
    input wire                      m_axi_awready [NUM_BANKS],
    output wire [ADDR_WIDTH-1:0]    m_axi_awaddr [NUM_BANKS],
    output wire [TAG_WIDTH-1:0]     m_axi_awid [NUM_BANKS],
    output wire [7:0]               m_axi_awlen [NUM_BANKS],
    output wire [2:0]               m_axi_awsize [NUM_BANKS],
    output wire [1:0]               m_axi_awburst [NUM_BANKS],
    output wire [1:0]               m_axi_awlock [NUM_BANKS],
    output wire [3:0]               m_axi_awcache [NUM_BANKS],
    output wire [2:0]               m_axi_awprot [NUM_BANKS],
    output wire [3:0]               m_axi_awqos [NUM_BANKS],
    output wire [3:0]               m_axi_awregion [NUM_BANKS],

    // AXI write request data channel   
    output wire                     m_axi_wvalid [NUM_BANKS],
    input wire                      m_axi_wready [NUM_BANKS],
    output wire [DATA_WIDTH-1:0]    m_axi_wdata [NUM_BANKS],
    output wire [DATA_WIDTH/8-1:0]  m_axi_wstrb [NUM_BANKS],
    output wire                     m_axi_wlast [NUM_BANKS],

    // AXI write response channel
    input wire                      m_axi_bvalid [NUM_BANKS],
    output wire                     m_axi_bready [NUM_BANKS],
    input wire [TAG_WIDTH-1:0]      m_axi_bid [NUM_BANKS],
    input wire [1:0]                m_axi_bresp [NUM_BANKS],
    
    // AXI read address channel
    output wire                     m_axi_arvalid [NUM_BANKS],
    input wire                      m_axi_arready [NUM_BANKS],
    output wire [ADDR_WIDTH-1:0]    m_axi_araddr [NUM_BANKS],
    output wire [TAG_WIDTH-1:0]     m_axi_arid [NUM_BANKS],
    output wire [7:0]               m_axi_arlen [NUM_BANKS],
    output wire [2:0]               m_axi_arsize [NUM_BANKS],
    output wire [1:0]               m_axi_arburst [NUM_BANKS], 
    output wire [1:0]               m_axi_arlock [NUM_BANKS],
    output wire [3:0]               m_axi_arcache [NUM_BANKS],
    output wire [2:0]               m_axi_arprot [NUM_BANKS],
    output wire [3:0]               m_axi_arqos [NUM_BANKS],
    output wire [3:0]               m_axi_arregion [NUM_BANKS],
    
    // AXI read response channel
    input wire                      m_axi_rvalid [NUM_BANKS],
    output wire                     m_axi_rready [NUM_BANKS],
    input wire [DATA_WIDTH-1:0]     m_axi_rdata [NUM_BANKS],
    input wire                      m_axi_rlast [NUM_BANKS],
    input wire [TAG_WIDTH-1:0]      m_axi_rid [NUM_BANKS],
    input wire [1:0]                m_axi_rresp [NUM_BANKS]
);  
    localparam AXSIZE = `CLOG2(DATA_WIDTH/8);
    localparam BANK_ADDRW = `LOG2UP(NUM_BANKS);    
    localparam LOG2_NUM_BANKS = `CLOG2(NUM_BANKS);

    wire [BANK_ADDRW-1:0] req_bank_sel;

    if (NUM_BANKS > 1) begin
        assign req_bank_sel = mem_req_addr[BANK_ADDRW-1:0];        
    end else begin
        assign req_bank_sel = '0;
    end

    wire mem_req_fire = mem_req_valid && mem_req_ready;

    reg [NUM_BANKS-1:0] m_axi_aw_ack;
    reg [NUM_BANKS-1:0] m_axi_w_ack;

    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        wire m_axi_aw_fire = m_axi_awvalid[i] && m_axi_awready[i];
        wire m_axi_w_fire = m_axi_wvalid[i] && m_axi_wready[i];  
        always @(posedge clk) begin
            if (reset) begin
                m_axi_aw_ack[i] <= 0;
                m_axi_w_ack[i]  <= 0;
            end else begin			
                if (mem_req_fire && (req_bank_sel == i)) begin
                    m_axi_aw_ack[i] <= 0;
                    m_axi_w_ack[i] <= 0;
                end else begin
                    if (m_axi_aw_fire)
                        m_axi_aw_ack[i] <= 1;
                    if (m_axi_w_fire)
                        m_axi_w_ack[i] <= 1;
                end
            end
        end
    end

    wire axi_write_ready [NUM_BANKS]; 

    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign axi_write_ready[i] = (m_axi_awready[i] || m_axi_aw_ack[i]) 
                                 && (m_axi_wready[i] || m_axi_w_ack[i]);
    end

    // Vortex request ack
    if (NUM_BANKS > 1) begin
        assign mem_req_ready = mem_req_rw ? axi_write_ready[req_bank_sel] : m_axi_arready[req_bank_sel];
    end else begin
        assign mem_req_ready = mem_req_rw ? axi_write_ready[0] : m_axi_arready[0];
    end

    // AXI write request address channel  
    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign m_axi_awvalid[i] = mem_req_valid && mem_req_rw && (req_bank_sel == i) && ~m_axi_aw_ack[i];
        assign m_axi_awaddr[i]  = (ADDR_WIDTH'(mem_req_addr) >> LOG2_NUM_BANKS) << AXSIZE;
        assign m_axi_awid[i]    = mem_req_tag;
        assign m_axi_awlen[i]   = 8'b00000000;    
        assign m_axi_awsize[i]  = 3'(AXSIZE);
        assign m_axi_awburst[i] = 2'b00;    
        assign m_axi_awlock[i]  = 2'b00;    
        assign m_axi_awcache[i] = 4'b0000;
        assign m_axi_awprot[i]  = 3'b000;    
        assign m_axi_awqos[i]   = 4'b0000;
        assign m_axi_awregion[i]= 4'b0000;
    end

    // AXI write request data channel
    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign m_axi_wvalid[i] = mem_req_valid && mem_req_rw && (req_bank_sel == i) && ~m_axi_w_ack[i];
        assign m_axi_wdata[i]  = mem_req_data;
        assign m_axi_wstrb[i]  = mem_req_byteen;
        assign m_axi_wlast[i]  = 1'b1;
    end

    // AXI write response channel (ignore)
    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        `UNUSED_VAR (m_axi_bvalid[i])
        `UNUSED_VAR (m_axi_bid[i])
        `UNUSED_VAR (m_axi_bresp[i])
        assign m_axi_bready[i] = 1'b1;
        `RUNTIME_ASSERT(~m_axi_bvalid[i] || m_axi_bresp[i] == 0, ("%t: *** AXI response error", $time));    
    end

    // AXI read request channel
    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign m_axi_arvalid[i] = mem_req_valid && ~mem_req_rw && (req_bank_sel == i);    
        assign m_axi_araddr[i]  = (ADDR_WIDTH'(mem_req_addr) >> LOG2_NUM_BANKS) << AXSIZE;
        assign m_axi_arid[i]    = mem_req_tag;
        assign m_axi_arlen[i]   = 8'b00000000;
        assign m_axi_arsize[i]  = 3'(AXSIZE);
        assign m_axi_arburst[i] = 2'b00;  
        assign m_axi_arlock[i]  = 2'b00;    
        assign m_axi_arcache[i] = 4'b0000;
        assign m_axi_arprot[i]  = 3'b000;
        assign m_axi_arqos[i]   = 4'b0000;
        assign m_axi_arregion[i]= 4'b0000;
    end

    // AXI read response channel  

    wire [NUM_BANKS-1:0] rsp_arb_valid_in;
    wire [NUM_BANKS-1:0][DATA_WIDTH+TAG_WIDTH-1:0] rsp_arb_data_in;
    wire [NUM_BANKS-1:0] rsp_arb_ready_in;

    `UNUSED_VAR (m_axi_rlast)   

    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign rsp_arb_valid_in[i] = m_axi_rvalid[i];
        assign rsp_arb_data_in[i] = {m_axi_rdata[i], m_axi_rid[i]};
        assign m_axi_rready[i] = rsp_arb_ready_in[i];
        `RUNTIME_ASSERT(~m_axi_rvalid[i] || m_axi_rlast[i] == 1, ("%t: *** AXI response error", $time));
        `RUNTIME_ASSERT(~m_axi_rvalid[i] || m_axi_rresp[i] == 0, ("%t: *** AXI response error", $time));
    end

    VX_stream_arb #(
        .NUM_INPUTS (NUM_BANKS),
        .DATAW      (DATA_WIDTH + TAG_WIDTH),
        .ARBITER    ("R"),
        .OUT_REG    (OUT_REG_RSP)
    ) rsp_arb (
        .clk       (clk),
        .reset     (reset),
        .valid_in  (rsp_arb_valid_in),
        .data_in   (rsp_arb_data_in),
        .ready_in  (rsp_arb_ready_in),
        .data_out  ({mem_rsp_data, mem_rsp_tag}),
        .valid_out (mem_rsp_valid),
        .ready_out (mem_rsp_ready),
        `UNUSED_PIN (sel_out)
    );

endmodule
`TRACING_ON
