// 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_define.vh"

`TRACING_OFF
module VX_avs_adapter #(    
    parameter DATA_WIDTH    = 1, 
    parameter ADDR_WIDTH    = 1,    
    parameter BURST_WIDTH   = 1,
    parameter NUM_BANKS     = 1, 
    parameter TAG_WIDTH     = 1,
    parameter RD_QUEUE_SIZE = 1,
    parameter OUT_REG_REQ   = 0,
    parameter OUT_REG_RSP   = 0
) (
    input  wire                     clk,
    input  wire                     reset,

    // Memory request
    input  wire                     mem_req_valid,
    input  wire                     mem_req_rw,    
    input  wire [DATA_WIDTH/8-1:0]  mem_req_byteen,    
    input  wire [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,

    // Memory 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,

    // AVS bus
    output wire [DATA_WIDTH-1:0]    avs_writedata [NUM_BANKS],
    input  wire [DATA_WIDTH-1:0]    avs_readdata [NUM_BANKS],
    output wire [ADDR_WIDTH-1:0]    avs_address [NUM_BANKS],
    input  wire                     avs_waitrequest [NUM_BANKS],
    output wire                     avs_write [NUM_BANKS],
    output wire                     avs_read [NUM_BANKS],
    output wire [DATA_WIDTH/8-1:0]  avs_byteenable [NUM_BANKS],
    output wire [BURST_WIDTH-1:0]   avs_burstcount [NUM_BANKS],
    input  wire                     avs_readdatavalid [NUM_BANKS]
);
    localparam DATA_SIZE = DATA_WIDTH/8;
    localparam RD_QUEUE_ADDR_WIDTH = `CLOG2(RD_QUEUE_SIZE+1);
    localparam BANK_ADDRW = `LOG2UP(NUM_BANKS);
    localparam LOG2_NUM_BANKS = `CLOG2(NUM_BANKS);
    localparam BANK_OFFSETW = ADDR_WIDTH - LOG2_NUM_BANKS;

    // Requests handling //////////////////////////////////////////////////////
    
    wire [NUM_BANKS-1:0] req_queue_push, req_queue_pop;
    wire [NUM_BANKS-1:0][TAG_WIDTH-1:0] req_queue_tag_out;
    wire [NUM_BANKS-1:0] req_queue_going_full;
    wire [NUM_BANKS-1:0][RD_QUEUE_ADDR_WIDTH-1:0] req_queue_size;
    wire [BANK_ADDRW-1:0] req_bank_sel;
    wire [BANK_OFFSETW-1:0] req_bank_off;
    wire [NUM_BANKS-1:0] bank_req_ready;

    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

    assign req_bank_off = mem_req_addr[ADDR_WIDTH-1:LOG2_NUM_BANKS];

    for (genvar i = 0; i < NUM_BANKS; ++i) begin        
        assign req_queue_push[i] = mem_req_valid && ~mem_req_rw && bank_req_ready[i] && (req_bank_sel == i);
    end

    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        VX_pending_size #( 
            .SIZE (RD_QUEUE_SIZE)
        ) pending_size (
            .clk   (clk),
            .reset (reset),
            .incr  (req_queue_push[i]),
            .decr  (req_queue_pop[i]),            
            .full  (req_queue_going_full[i]),
            .size  (req_queue_size[i]),
            `UNUSED_PIN (empty)
        ); 
        `UNUSED_VAR (req_queue_size)
        
        VX_fifo_queue #(
            .DATAW (TAG_WIDTH),
            .DEPTH (RD_QUEUE_SIZE)
        ) rd_req_queue (
            .clk      (clk),
            .reset    (reset),
            .push     (req_queue_push[i]),        
            .pop      (req_queue_pop[i]),
            .data_in  (mem_req_tag),
            .data_out (req_queue_tag_out[i]),
            `UNUSED_PIN (empty),
            `UNUSED_PIN (full),
            `UNUSED_PIN (alm_empty),
            `UNUSED_PIN (alm_full),
            `UNUSED_PIN (size)
        );
    end    

    for (genvar i = 0; i < NUM_BANKS; ++i) begin        
        wire                  valid_out;
        wire                  rw_out;
        wire [DATA_SIZE-1:0]  byteen_out;
        wire [BANK_OFFSETW-1:0] addr_out;
        wire [DATA_WIDTH-1:0] data_out;
        wire                  ready_out;

        wire valid_out_w = mem_req_valid && ~req_queue_going_full[i] && (req_bank_sel == i);
        wire ready_out_w;

        VX_elastic_buffer #(
            .DATAW    (1 + DATA_SIZE + BANK_OFFSETW + DATA_WIDTH),
            .SIZE     (`OUT_REG_TO_EB_SIZE(OUT_REG_REQ)),
            .OUT_REG  (`OUT_REG_TO_EB_REG(OUT_REG_REQ))
        ) req_out_buf (
            .clk       (clk),
            .reset     (reset),
            .valid_in  (valid_out_w),
            .ready_in  (ready_out_w),
            .data_in   ({mem_req_rw, mem_req_byteen, req_bank_off, mem_req_data}),
            .data_out  ({rw_out,     byteen_out,     addr_out,     data_out}),
            .valid_out (valid_out),
            .ready_out (ready_out)
        );

        assign avs_read[i]       = valid_out && ~rw_out;
        assign avs_write[i]      = valid_out && rw_out;
        assign avs_address[i]    = ADDR_WIDTH'(addr_out);
        assign avs_byteenable[i] = byteen_out;
        assign avs_writedata[i]  = data_out;
        assign avs_burstcount[i] = BURST_WIDTH'(1);
        assign ready_out         = ~avs_waitrequest[i];

        assign bank_req_ready[i] = ready_out_w && ~req_queue_going_full[i];
    end

    if (NUM_BANKS > 1) begin
        assign mem_req_ready = bank_req_ready[req_bank_sel];
    end else begin
        assign mem_req_ready = bank_req_ready;
    end

    // Responses handling /////////////////////////////////////////////////////

    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;

    wire [NUM_BANKS-1:0][DATA_WIDTH-1:0] rsp_queue_data_out;
    wire [NUM_BANKS-1:0] rsp_queue_empty;

    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        VX_fifo_queue #(
            .DATAW (DATA_WIDTH),
            .DEPTH (RD_QUEUE_SIZE)
        ) rd_rsp_queue (
            .clk      (clk),
            .reset    (reset),
            .push     (avs_readdatavalid[i]),
            .pop      (req_queue_pop[i]),
            .data_in  (avs_readdata[i]),        
            .data_out (rsp_queue_data_out[i]),
            .empty    (rsp_queue_empty[i]),
            `UNUSED_PIN (full),
            `UNUSED_PIN (alm_empty),
            `UNUSED_PIN (alm_full),
            `UNUSED_PIN (size)
        );
    end
    
    for (genvar i = 0; i < NUM_BANKS; ++i) begin
        assign rsp_arb_valid_in[i] = !rsp_queue_empty[i];
        assign rsp_arb_data_in[i]  = {rsp_queue_data_out[i], req_queue_tag_out[i]};
        assign req_queue_pop[i]    = rsp_arb_valid_in[i] && rsp_arb_ready_in[i];
    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
