// Copyright 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.
//
// Author: Florian Zaruba, ETH Zurich
// Date: 12.11.2017
// Description: Handles cache misses.

// --------------
// MISS Handler
// --------------
import ariane_pkg::*;
import std_cache_pkg::*;

module miss_handler #(
    parameter int unsigned NR_PORTS         = 3
)(
    input  logic                                        clk_i,
    input  logic                                        rst_ni,
    input  logic                                        flush_i,      // flush request
    output logic                                        flush_ack_o,  // acknowledge successful flush
    output logic                                        miss_o,
    input  logic                                        busy_i,       // dcache is busy with something
    // Bypass or miss
    input  logic [NR_PORTS-1:0][$bits(miss_req_t)-1:0]  miss_req_i,
    // Bypass handling
    output logic [NR_PORTS-1:0]                         bypass_gnt_o,
    output logic [NR_PORTS-1:0]                         bypass_valid_o,
    output logic [NR_PORTS-1:0][63:0]                   bypass_data_o,

    // AXI port
    output ariane_axi::req_t                            axi_bypass_o,
    input  ariane_axi::resp_t                           axi_bypass_i,

    // Miss handling (~> cacheline refill)
    output logic [NR_PORTS-1:0]                         miss_gnt_o,
    output logic [NR_PORTS-1:0]                         active_serving_o,

    output logic [63:0]                                 critical_word_o,
    output logic                                        critical_word_valid_o,
    output ariane_axi::req_t                            axi_data_o,
    input  ariane_axi::resp_t                           axi_data_i,

    input  logic [NR_PORTS-1:0][55:0]                   mshr_addr_i,
    output logic [NR_PORTS-1:0]                         mshr_addr_matches_o,
    output logic [NR_PORTS-1:0]                         mshr_index_matches_o,
    // AMO
    input  amo_req_t                                    amo_req_i,
    output amo_resp_t                                   amo_resp_o,
    // Port to SRAMs, for refill and eviction
    output logic  [DCACHE_SET_ASSOC-1:0]                req_o,
    output logic  [DCACHE_INDEX_WIDTH-1:0]              addr_o, // address into cache array
    output cache_line_t                                 data_o,
    output cl_be_t                                      be_o,
    input  cache_line_t [DCACHE_SET_ASSOC-1:0]          data_i,
    output logic                                        we_o
);

    // FSM states
    enum logic [3:0] {
        IDLE,               // 0
        FLUSHING,           // 1
        FLUSH,              // 2
        WB_CACHELINE_FLUSH, // 3
        FLUSH_REQ_STATUS,   // 4
        WB_CACHELINE_MISS,  // 5
        WAIT_GNT_SRAM,      // 6
        MISS,               // 7
        REQ_CACHELINE,      // 8
        MISS_REPL,          // 9
        SAVE_CACHELINE,     // A
        INIT,               // B
        AMO_LOAD,           // C
        AMO_SAVE_LOAD,      // D
        AMO_STORE           // E
    } state_d, state_q;

    // Registers
    mshr_t                                  mshr_d, mshr_q;
    logic [DCACHE_INDEX_WIDTH-1:0]          cnt_d, cnt_q;
    logic [DCACHE_SET_ASSOC-1:0]            evict_way_d, evict_way_q;
    // cache line to evict
    cache_line_t                            evict_cl_d, evict_cl_q;

    logic serve_amo_d, serve_amo_q;
    // Request from one FSM
    logic [NR_PORTS-1:0]                    miss_req_valid;
    logic [NR_PORTS-1:0]                    miss_req_bypass;
    logic [NR_PORTS-1:0][63:0]              miss_req_addr;
    logic [NR_PORTS-1:0][63:0]              miss_req_wdata;
    logic [NR_PORTS-1:0]                    miss_req_we;
    logic [NR_PORTS-1:0][7:0]               miss_req_be;
    logic [NR_PORTS-1:0][1:0]               miss_req_size;

    // Cache Line Refill <-> AXI
    logic                                    req_fsm_miss_valid;
    logic [63:0]                             req_fsm_miss_addr;
    logic [DCACHE_LINE_WIDTH-1:0]            req_fsm_miss_wdata;
    logic                                    req_fsm_miss_we;
    logic [(DCACHE_LINE_WIDTH/8)-1:0]        req_fsm_miss_be;
    ariane_axi::ad_req_t                     req_fsm_miss_req;
    logic [1:0]                              req_fsm_miss_size;

    logic                                    gnt_miss_fsm;
    logic                                    valid_miss_fsm;
    logic [(DCACHE_LINE_WIDTH/64)-1:0][63:0] data_miss_fsm;

    // Cache Management <-> LFSR
    logic                                  lfsr_enable;
    logic [DCACHE_SET_ASSOC-1:0]           lfsr_oh;
    logic [$clog2(DCACHE_SET_ASSOC-1)-1:0] lfsr_bin;
    // AMOs
    ariane_pkg::amo_t amo_op;
    logic [63:0] amo_operand_a, amo_operand_b, amo_result_o;

    struct packed {
        logic [63:3] address;
        logic        valid;
    } reservation_d, reservation_q;

    // ------------------------------
    // Cache Management
    // ------------------------------
    always_comb begin : cache_management
        automatic logic [DCACHE_SET_ASSOC-1:0] evict_way, valid_way;

        for (int unsigned i = 0; i < DCACHE_SET_ASSOC; i++) begin
            evict_way[i] = data_i[i].valid & data_i[i].dirty;
            valid_way[i] = data_i[i].valid;
        end
        // ----------------------
        // Default Assignments
        // ----------------------
        // memory array
        req_o  = '0;
        addr_o = '0;
        data_o = '0;
        be_o   = '0;
        we_o   = '0;
        // Cache controller
        miss_gnt_o = '0;
        // LFSR replacement unit
        lfsr_enable = 1'b0;
        // to AXI refill
        req_fsm_miss_valid  = 1'b0;
        req_fsm_miss_addr   = '0;
        req_fsm_miss_wdata  = '0;
        req_fsm_miss_we     = 1'b0;
        req_fsm_miss_be     = '0;
        req_fsm_miss_req    = ariane_axi::CACHE_LINE_REQ;
        req_fsm_miss_size   = 2'b11;
        // core
        flush_ack_o         = 1'b0;
        miss_o              = 1'b0; // to performance counter
        serve_amo_d         = serve_amo_q;
        // --------------------------------
        // Flush and Miss operation
        // --------------------------------
        state_d      = state_q;
        cnt_d        = cnt_q;
        evict_way_d  = evict_way_q;
        evict_cl_d   = evict_cl_q;
        mshr_d       = mshr_q;
        // communicate to the requester which unit we are currently serving
        active_serving_o[mshr_q.id] = mshr_q.valid;
        // AMOs
        amo_resp_o.ack = 1'b0;
        amo_resp_o.result = '0;
        // silence the unit when not used
        amo_op = amo_req_i.amo_op;
        amo_operand_a = '0;
        amo_operand_b = '0;

        reservation_d = reservation_q;
        case (state_q)

            IDLE: begin
                // lowest priority are AMOs, wait until everything else is served before going for the AMOs
                if (amo_req_i.req && !busy_i) begin
                    // 1. Flush the cache
                    if (!serve_amo_q) begin
                        state_d = FLUSH_REQ_STATUS;
                        serve_amo_d = 1'b1;
                    // 2. Do the AMO
                    end else begin
                        state_d = AMO_LOAD;
                        serve_amo_d = 1'b0;
                    end
                end
                // check if we want to flush and can flush e.g.: we are not busy anymore
                // TODO: Check that the busy flag is indeed needed
                if (flush_i && !busy_i) begin
                    state_d = FLUSH_REQ_STATUS;
                    cnt_d = '0;
                end

                // check if one of the state machines missed
                for (int unsigned i = 0; i < NR_PORTS; i++) begin
                    // here comes the refill portion of code
                    if (miss_req_valid[i] && !miss_req_bypass[i]) begin
                        state_d      = MISS;
                        // we are taking another request so don't take the AMO
                        serve_amo_d  = 1'b0;
                        // save to MSHR
                        mshr_d.valid = 1'b1;
                        mshr_d.we    = miss_req_we[i];
                        mshr_d.id    = i;
                        mshr_d.addr  = miss_req_addr[i][DCACHE_TAG_WIDTH+DCACHE_INDEX_WIDTH-1:0];
                        mshr_d.wdata = miss_req_wdata[i];
                        mshr_d.be    = miss_req_be[i];
                        break;
                    end
                end
            end

            //  ~> we missed on the cache
            MISS: begin
                // 1. Check if there is an empty cache-line
                // 2. If not -> evict one
                req_o = '1;
                addr_o = mshr_q.addr[DCACHE_INDEX_WIDTH-1:0];
                state_d = MISS_REPL;
                miss_o = 1'b1;
            end

            // ~> second miss cycle
            MISS_REPL: begin
                // if all are valid we need to evict one, pseudo random from LFSR
                if (&valid_way) begin
                    lfsr_enable = 1'b1;
                    evict_way_d = lfsr_oh;
                    // do we need to write back the cache line?
                    if (data_i[lfsr_bin].dirty) begin
                        state_d = WB_CACHELINE_MISS;
                        evict_cl_d.tag = data_i[lfsr_bin].tag;
                        evict_cl_d.data = data_i[lfsr_bin].data;
                        cnt_d = mshr_q.addr[DCACHE_INDEX_WIDTH-1:0];
                    // no - we can request a cache line now
                    end else
                        state_d = REQ_CACHELINE;
                // we have at least one free way
                end else begin
                    // get victim cache-line by looking for the first non-valid bit
                    evict_way_d = get_victim_cl(~valid_way);
                    state_d = REQ_CACHELINE;
                end
            end

            // ~> we can just load the cache-line, the way is store in evict_way_q
            REQ_CACHELINE: begin
                req_fsm_miss_valid  = 1'b1;
                req_fsm_miss_addr   = mshr_q.addr;

                if (gnt_miss_fsm) begin
                    state_d = SAVE_CACHELINE;
                    miss_gnt_o[mshr_q.id] = 1'b1;
                end
            end

            // ~> replace the cacheline
            SAVE_CACHELINE: begin
                // calculate cacheline offset
                automatic logic [$clog2(DCACHE_LINE_WIDTH)-1:0] cl_offset;
                cl_offset = mshr_q.addr[DCACHE_BYTE_OFFSET-1:3] << 6;
                // we've got a valid response from refill unit
                if (valid_miss_fsm) begin

                    addr_o       = mshr_q.addr[DCACHE_INDEX_WIDTH-1:0];
                    req_o        = evict_way_q;
                    we_o         = 1'b1;
                    be_o         = '1;
                    be_o.vldrty  = evict_way_q;
                    data_o.tag   = mshr_q.addr[DCACHE_TAG_WIDTH+DCACHE_INDEX_WIDTH-1:DCACHE_INDEX_WIDTH];
                    data_o.data  = data_miss_fsm;
                    data_o.valid = 1'b1;
                    data_o.dirty = 1'b0;

                    // is this a write?
                    if (mshr_q.we) begin
                        // Yes, so safe the updated data now
                        for (int i = 0; i < 8; i++) begin
                            // check if we really want to write the corresponding byte
                            if (mshr_q.be[i])
                                data_o.data[(cl_offset + i*8) +: 8] = mshr_q.wdata[i];
                        end
                        // its immediately dirty if we write
                        data_o.dirty = 1'b1;
                    end
                    // reset MSHR
                    mshr_d.valid = 1'b0;
                    // go back to idle
                    state_d = IDLE;
                end
            end

            // ------------------------------
            // Write Back Operation
            // ------------------------------
            // ~> evict a cache line from way saved in evict_way_q
            WB_CACHELINE_FLUSH, WB_CACHELINE_MISS: begin

                req_fsm_miss_valid  = 1'b1;
                req_fsm_miss_addr   = {evict_cl_q.tag, cnt_q[DCACHE_INDEX_WIDTH-1:DCACHE_BYTE_OFFSET], {{DCACHE_BYTE_OFFSET}{1'b0}}};
                req_fsm_miss_be     = '1;
                req_fsm_miss_we     = 1'b1;
                req_fsm_miss_wdata  = evict_cl_q.data;

                // we've got a grant --> this is timing critical, think about it
                if (gnt_miss_fsm) begin
                    // write status array
                    addr_o     = cnt_q;
                    req_o      = 1'b1;
                    we_o       = 1'b1;
                    data_o.valid = INVALIDATE_ON_FLUSH ? 1'b0 : 1'b1;
                    // invalidate
                    be_o.vldrty = evict_way_q;
                    // go back to handling the miss or flushing, depending on where we came from
                    state_d = (state_q == WB_CACHELINE_MISS) ? MISS : FLUSH_REQ_STATUS;
                end
            end

            // ------------------------------
            // Flushing & Initialization
            // ------------------------------
            // ~> make another request to check the same cache-line if there are still some valid entries
            FLUSH_REQ_STATUS: begin
                req_o   = '1;
                addr_o  = cnt_q;
                state_d = FLUSHING;
            end

            FLUSHING: begin
                // this has priority
                // at least one of the cache lines is dirty
                if (|evict_way) begin
                    // evict cache line, look for the first cache-line which is dirty
                    evict_way_d = get_victim_cl(evict_way);
                    evict_cl_d  = data_i[one_hot_to_bin(evict_way)];
                    state_d     = WB_CACHELINE_FLUSH;
                // not dirty ~> increment and continue
                end else begin
                    // increment and re-request
                    cnt_d       = cnt_q + (1'b1 << DCACHE_BYTE_OFFSET);
                    state_d     = FLUSH_REQ_STATUS;
                    addr_o      = cnt_q;
                    req_o       = 1'b1;
                    be_o.vldrty = INVALIDATE_ON_FLUSH ? '1 : '0;
                    we_o        = 1'b1;
                    // finished with flushing operation, go back to idle
                    if (cnt_q[DCACHE_INDEX_WIDTH-1:DCACHE_BYTE_OFFSET] == DCACHE_NUM_WORDS-1) begin
                        // only acknowledge if the flush wasn't triggered by an atomic
                        flush_ack_o = ~serve_amo_q;
                        state_d     = IDLE;
                    end
                end
            end

            // ~> only called after reset
            INIT: begin
                // initialize status array
                addr_o = cnt_q;
                req_o  = 1'b1;
                we_o   = 1'b1;
                // only write the dirty array
                be_o.vldrty = '1;
                cnt_d       = cnt_q + (1'b1 << DCACHE_BYTE_OFFSET);
                // finished initialization
                if (cnt_q[DCACHE_INDEX_WIDTH-1:DCACHE_BYTE_OFFSET] == DCACHE_NUM_WORDS-1)
                    state_d = IDLE;
            end
            // ----------------------
            // AMOs
            // ----------------------
            // TODO(zarubaf) Move this closer to memory
            // ~> we are here because we need to do the AMO, the cache is clean at this point
            // start by executing the load
            AMO_LOAD: begin
                req_fsm_miss_valid = 1'b1;
                // address is in operand a
                req_fsm_miss_addr = amo_req_i.operand_a;
                req_fsm_miss_req = ariane_axi::SINGLE_REQ;
                req_fsm_miss_size = amo_req_i.size;
                // the request has been granted
                if (gnt_miss_fsm) begin
                    state_d = AMO_SAVE_LOAD;
                end
            end
            // save the load value
            AMO_SAVE_LOAD: begin
                if (valid_miss_fsm) begin
                    // we are only concerned about the lower 64-bit
                    mshr_d.wdata = data_miss_fsm[0];
                    state_d = AMO_STORE;
                end
            end
            // and do the store
            AMO_STORE: begin
                automatic logic [63:0] load_data;
                // re-align load data
                load_data = data_align(amo_req_i.operand_a[2:0], mshr_q.wdata);
                // Sign-extend for word operation
                if (amo_req_i.size == 2'b10) begin
                    amo_operand_a = sext32(load_data[31:0]);
                    amo_operand_b = sext32(amo_req_i.operand_b[31:0]);
                end else begin
                    amo_operand_a = load_data;
                    amo_operand_b = amo_req_i.operand_b;
                end

                //  we do not need a store request for load reserved or a failing store conditional
                //  we can bail-out without making any further requests
                if (amo_req_i.amo_op == AMO_LR ||
                   (amo_req_i.amo_op == AMO_SC &&
                   ((reservation_q.valid && reservation_q.address != amo_req_i.operand_a[63:3]) || !reservation_q.valid))) begin
                    req_fsm_miss_valid = 1'b0;
                    state_d = IDLE;
                    amo_resp_o.ack = 1'b1;
                    // write-back the result
                    amo_resp_o.result = amo_operand_a;
                    // we know that the SC failed
                    if (amo_req_i.amo_op == AMO_SC) begin
                        amo_resp_o.result = 1'b1;
                        // also clear the reservation
                        reservation_d.valid = 1'b0;
                    end
                end else begin
                    req_fsm_miss_valid = 1'b1;
                end

                req_fsm_miss_we   = 1'b1;
                req_fsm_miss_req  = ariane_axi::SINGLE_REQ;
                req_fsm_miss_size = amo_req_i.size;
                req_fsm_miss_addr = amo_req_i.operand_a;

                req_fsm_miss_wdata = data_align(amo_req_i.operand_a[2:0], amo_result_o);
                req_fsm_miss_be = be_gen(amo_req_i.operand_a[2:0], amo_req_i.size);

                // place a reservation on the memory
                if (amo_req_i.amo_op == AMO_LR) begin
                    reservation_d.address = amo_req_i.operand_a[63:3];
                    reservation_d.valid = 1'b1;
                end

                // the request is valid or we didn't need to go for another store
                if (valid_miss_fsm) begin
                    state_d = IDLE;
                    amo_resp_o.ack = 1'b1;
                    // write-back the result
                    amo_resp_o.result = amo_operand_a;

                    if (amo_req_i.amo_op == AMO_SC) begin
                        amo_resp_o.result = 1'b0;
                        // An SC must fail if there is a nother SC (to any address) between the LR and the SC in program
                        // order (even to the same address).
                        // in any case destory the reservation
                        reservation_d.valid = 1'b0;
                    end
                end
            end
        endcase
    end

    // check MSHR for aliasing
    always_comb begin

        mshr_addr_matches_o  = 'b0;
        mshr_index_matches_o = 'b0;

        for (int i = 0; i < NR_PORTS; i++) begin
            // check mshr for potential matching of other units, exclude the unit currently being served
            if (mshr_q.valid && mshr_addr_i[i][55:DCACHE_BYTE_OFFSET] == mshr_q.addr[55:DCACHE_BYTE_OFFSET]) begin
                mshr_addr_matches_o[i] = 1'b1;
            end

            // same as previous, but checking only the index
            if (mshr_q.valid && mshr_addr_i[i][DCACHE_INDEX_WIDTH-1:DCACHE_BYTE_OFFSET] == mshr_q.addr[DCACHE_INDEX_WIDTH-1:DCACHE_BYTE_OFFSET]) begin
                mshr_index_matches_o[i] = 1'b1;
            end
        end
    end
    // --------------------
    // Sequential Process
    // --------------------
    always_ff @(posedge clk_i or negedge rst_ni) begin
        if (~rst_ni) begin
            mshr_q        <= '0;
            state_q       <= INIT;
            cnt_q         <= '0;
            evict_way_q   <= '0;
            evict_cl_q    <= '0;
            serve_amo_q   <= 1'b0;
            reservation_q <= '0;
        end else begin
            mshr_q        <= mshr_d;
            state_q       <= state_d;
            cnt_q         <= cnt_d;
            evict_way_q   <= evict_way_d;
            evict_cl_q    <= evict_cl_d;
            serve_amo_q   <= serve_amo_d;
            reservation_q <= reservation_d;
        end
    end

    //pragma translate_off
    `ifndef VERILATOR
    // assert that cache only hits on one way
    assert property (
      @(posedge clk_i) $onehot0(evict_way_q)) else $warning("Evict-way should be one-hot encoded");
    `endif
    //pragma translate_on
    // ----------------------
    // Bypass Arbiter
    // ----------------------
    // Connection Arbiter <-> AXI
    logic                        req_fsm_bypass_valid;
    logic [63:0]                 req_fsm_bypass_addr;
    logic [63:0]                 req_fsm_bypass_wdata;
    logic                        req_fsm_bypass_we;
    logic [7:0]                  req_fsm_bypass_be;
    logic [1:0]                  req_fsm_bypass_size;
    logic                        gnt_bypass_fsm;
    logic                        valid_bypass_fsm;
    logic [63:0]                 data_bypass_fsm;
    logic [$clog2(NR_PORTS)-1:0] id_fsm_bypass;
    logic [3:0]                  id_bypass_fsm;
    logic [3:0]                  gnt_id_bypass_fsm;

    arbiter #(
        .NR_PORTS       ( NR_PORTS                                 ),
        .DATA_WIDTH     ( 64                                       )
    ) i_bypass_arbiter (
        // Master Side
        .data_req_i     ( miss_req_valid & miss_req_bypass         ),
        .address_i      ( miss_req_addr                            ),
        .data_wdata_i   ( miss_req_wdata                           ),
        .data_we_i      ( miss_req_we                              ),
        .data_be_i      ( miss_req_be                              ),
        .data_size_i    ( miss_req_size                            ),
        .data_gnt_o     ( bypass_gnt_o                             ),
        .data_rvalid_o  ( bypass_valid_o                           ),
        .data_rdata_o   ( bypass_data_o                            ),
        // Slave Sid
        .id_i           ( id_bypass_fsm[$clog2(NR_PORTS)-1:0]      ),
        .id_o           ( id_fsm_bypass                            ),
        .gnt_id_i       ( gnt_id_bypass_fsm[$clog2(NR_PORTS)-1:0]  ),
        .address_o      ( req_fsm_bypass_addr                      ),
        .data_wdata_o   ( req_fsm_bypass_wdata                     ),
        .data_req_o     ( req_fsm_bypass_valid                     ),
        .data_we_o      ( req_fsm_bypass_we                        ),
        .data_be_o      ( req_fsm_bypass_be                        ),
        .data_size_o    ( req_fsm_bypass_size                      ),
        .data_gnt_i     ( gnt_bypass_fsm                           ),
        .data_rvalid_i  ( valid_bypass_fsm                         ),
        .data_rdata_i   ( data_bypass_fsm                          ),
        .*
    );

    axi_adapter #(
        .DATA_WIDTH            ( 64                 ),
        .AXI_ID_WIDTH          ( 4                  ),
        .CACHELINE_BYTE_OFFSET ( DCACHE_BYTE_OFFSET )
    ) i_bypass_axi_adapter (
        .clk_i,
        .rst_ni,
        .req_i                 ( req_fsm_bypass_valid   ),
        .type_i                ( ariane_axi::SINGLE_REQ ),
        .gnt_o                 ( gnt_bypass_fsm         ),
        .addr_i                ( req_fsm_bypass_addr    ),
        .we_i                  ( req_fsm_bypass_we      ),
        .wdata_i               ( req_fsm_bypass_wdata   ),
        .be_i                  ( req_fsm_bypass_be      ),
        .size_i                ( req_fsm_bypass_size    ),
        .id_i                  ( {2'b10, id_fsm_bypass} ),
        .valid_o               ( valid_bypass_fsm       ),
        .rdata_o               ( data_bypass_fsm        ),
        .gnt_id_o              ( gnt_id_bypass_fsm      ),
        .id_o                  ( id_bypass_fsm          ),
        .critical_word_o       (                        ), // not used for single requests
        .critical_word_valid_o (                        ), // not used for single requests
        .axi_req_o             ( axi_bypass_o           ),
        .axi_resp_i            ( axi_bypass_i           )
    );

    // ----------------------
    // Cache Line AXI Refill
    // ----------------------
    axi_adapter  #(
        .DATA_WIDTH            ( DCACHE_LINE_WIDTH  ),
        .AXI_ID_WIDTH          ( 4                  ),
        .CACHELINE_BYTE_OFFSET ( DCACHE_BYTE_OFFSET )
    ) i_miss_axi_adapter (
        .clk_i,
        .rst_ni,
        .req_i               ( req_fsm_miss_valid ),
        .type_i              ( req_fsm_miss_req   ),
        .gnt_o               ( gnt_miss_fsm       ),
        .addr_i              ( req_fsm_miss_addr  ),
        .we_i                ( req_fsm_miss_we    ),
        .wdata_i             ( req_fsm_miss_wdata ),
        .be_i                ( req_fsm_miss_be    ),
        .size_i              ( req_fsm_miss_size  ),
        .id_i                ( 4'b1100            ),
        .gnt_id_o            (                    ), // open
        .valid_o             ( valid_miss_fsm     ),
        .rdata_o             ( data_miss_fsm      ),
        .id_o                (                    ),
        .critical_word_o,
        .critical_word_valid_o,
        .axi_req_o           ( axi_data_o         ),
        .axi_resp_i          ( axi_data_i         )
    );

    // -----------------
    // Replacement LFSR
    // -----------------
    lfsr_8bit #(.WIDTH (DCACHE_SET_ASSOC)) i_lfsr (
        .en_i           ( lfsr_enable ),
        .refill_way_oh  ( lfsr_oh     ),
        .refill_way_bin ( lfsr_bin    ),
        .*
    );

    // -----------------
    // AMO ALU
    // -----------------
    amo_alu i_amo_alu (
        .amo_op_i        ( amo_op        ),
        .amo_operand_a_i ( amo_operand_a ),
        .amo_operand_b_i ( amo_operand_b ),
        .amo_result_o    ( amo_result_o  )
    );

    // -----------------
    // Struct Split
    // -----------------
    // Hack as system verilog support in modelsim seems to be buggy here
    always_comb begin
        automatic miss_req_t miss_req;

        for (int unsigned i = 0; i < NR_PORTS; i++) begin
            miss_req =  miss_req_t'(miss_req_i[i]);
            miss_req_valid  [i]  = miss_req.valid;
            miss_req_bypass [i]  = miss_req.bypass;
            miss_req_addr   [i]  = miss_req.addr;
            miss_req_wdata  [i]  = miss_req.wdata;
            miss_req_we     [i]  = miss_req.we;
            miss_req_be     [i]  = miss_req.be;
            miss_req_size   [i]  = miss_req.size;
        end
    end
endmodule

// --------------
// AXI Arbiter
// --------------s
//
// Description: Arbitrates access to AXI refill/bypass
//
module arbiter #(
        parameter int unsigned NR_PORTS   = 3,
        parameter int unsigned DATA_WIDTH = 64
)(
    input  logic                                   clk_i,          // Clock
    input  logic                                   rst_ni,         // Asynchronous reset active low
    // master ports
    input  logic [NR_PORTS-1:0]                    data_req_i,
    input  logic [NR_PORTS-1:0][63:0]              address_i,
    input  logic [NR_PORTS-1:0][DATA_WIDTH-1:0]    data_wdata_i,
    input  logic [NR_PORTS-1:0]                    data_we_i,
    input  logic [NR_PORTS-1:0][DATA_WIDTH/8-1:0]  data_be_i,
    input  logic [NR_PORTS-1:0][1:0]               data_size_i,
    output logic [NR_PORTS-1:0]                    data_gnt_o,
    output logic [NR_PORTS-1:0]                    data_rvalid_o,
    output logic [NR_PORTS-1:0][DATA_WIDTH-1:0]    data_rdata_o,
    // slave port
    input  logic [$clog2(NR_PORTS)-1:0]            id_i,
    output logic [$clog2(NR_PORTS)-1:0]            id_o,
    input  logic [$clog2(NR_PORTS)-1:0]            gnt_id_i,
    output logic                                   data_req_o,
    output logic [63:0]                            address_o,
    output logic [DATA_WIDTH-1:0]                  data_wdata_o,
    output logic                                   data_we_o,
    output logic [DATA_WIDTH/8-1:0]                data_be_o,
    output logic [1:0]                             data_size_o,
    input  logic                                   data_gnt_i,
    input  logic                                   data_rvalid_i,
    input  logic [DATA_WIDTH-1:0]                  data_rdata_i
);

    enum logic [1:0] { IDLE, REQ, SERVING } state_d, state_q;

    struct packed {
        logic [$clog2(NR_PORTS)-1:0] id;
        logic [63:0]                 address;
        logic [63:0]                 data;
        logic [1:0]                  size;
        logic [DATA_WIDTH/8-1:0]     be;
        logic                        we;
    } req_d, req_q;

    always_comb begin
        automatic logic [$clog2(NR_PORTS)-1:0] request_index;
        request_index = 0;

        state_d = state_q;
        req_d   = req_q;
        // request port
        data_req_o                = 1'b0;
        address_o                 = req_q.address;
        data_wdata_o              = req_q.data;
        data_be_o                 = req_q.be;
        data_size_o               = req_q.size;
        data_we_o                 = req_q.we;
        id_o                      = req_q.id;
        data_gnt_o                = '0;
        // read port
        data_rvalid_o             = '0;
        data_rdata_o              = '0;
        data_rdata_o[req_q.id]    = data_rdata_i;

        case (state_q)

            IDLE: begin
                // wait for incoming requests
                for (int unsigned i = 0; i < NR_PORTS; i++) begin
                    if (data_req_i[i] == 1'b1) begin
                        data_req_o    = data_req_i[i];
                        data_gnt_o[i] = data_req_i[i];
                        request_index = i[$bits(request_index)-1:0];
                        // save the request
                        req_d.address = address_i[i];
                        req_d.id = i[$bits(req_q.id)-1:0];
                        req_d.data = data_wdata_i[i];
                        req_d.size = data_size_i[i];
                        req_d.be = data_be_i[i];
                        req_d.we = data_we_i[i];
                        state_d = SERVING;
                        break; // break here as this is a priority select
                    end
                end

                address_o                 = address_i[request_index];
                data_wdata_o              = data_wdata_i[request_index];
                data_be_o                 = data_be_i[request_index];
                data_size_o               = data_size_i[request_index];
                data_we_o                 = data_we_i[request_index];
                id_o                      = request_index;
            end

            SERVING: begin
                data_req_o = 1'b1;
                if (data_rvalid_i) begin
                    data_rvalid_o[req_q.id] = 1'b1;
                    state_d = IDLE;
                end
            end

            default : /* default */;
        endcase
    end

    always_ff @(posedge clk_i or negedge rst_ni) begin
        if (~rst_ni) begin
            state_q <= IDLE;
            req_q   <= '0;
        end else begin
            state_q <= state_d;
            req_q   <= req_d;
        end
    end
    // ------------
    // Assertions
    // ------------

    //pragma translate_off
    `ifndef VERILATOR
    // make sure that we eventually get an rvalid after we received a grant
    assert property (@(posedge clk_i) data_gnt_i |-> ##[1:$] data_rvalid_i )
        else begin $error("There was a grant without a rvalid"); $stop(); end
    // assert that there is no grant without a request
    assert property (@(negedge clk_i) data_gnt_i |-> data_req_o)
        else begin $error("There was a grant without a request."); $stop(); end
    // assert that the address does not contain X when request is sent
    assert property ( @(posedge clk_i) (data_req_o) |-> (!$isunknown(address_o)) )
      else begin $error("address contains X when request is set"); $stop(); end

    `endif
    //pragma translate_on
endmodule