`include "VX_cache_define.vh"

module VX_miss_resrv #(
    parameter CACHE_ID          = 0,
    parameter BANK_ID           = 0, 
    
    // Number of Word requests per cycle
    parameter NUM_REQS          = 1, 
    
    // Size of line inside a bank in bytes
    parameter CACHE_LINE_SIZE   = 1, 
    // Number of banks
    parameter NUM_BANKS         = 1,
    // Number of ports per banks
    parameter NUM_PORTS         = 1,
    // Size of a word in bytes
    parameter WORD_SIZE         = 1, 
    // Miss Reserv Queue Knob
    parameter MSHR_SIZE         = 1,
    // core request tag size
    parameter CORE_TAG_WIDTH    = 1,

    parameter MSHR_ADDR_WIDTH   = $clog2(MSHR_SIZE)
) (
    input wire clk,
    input wire reset,

`ifdef DBG_CACHE_REQ_INFO
`IGNORE_UNUSED_BEGIN
    input wire[31:0]                    deq_debug_pc,
    input wire[`NW_BITS-1:0]            deq_debug_wid,
    input wire[31:0]                    lkp_debug_pc,
    input wire[`NW_BITS-1:0]            lkp_debug_wid,
    input wire[31:0]                    rel_debug_pc,
    input wire[`NW_BITS-1:0]            rel_debug_wid,
`IGNORE_UNUSED_END
`endif

    // allocate
    input wire                          allocate_valid,
    input wire [`LINE_ADDR_WIDTH-1:0]   allocate_addr,
    input wire [`MSHR_DATA_WIDTH-1:0]   allocate_data,
    output wire [MSHR_ADDR_WIDTH-1:0]   allocate_id,   
    output wire                         allocate_ready,

    // fill
    input wire                          fill_valid,
    input wire [MSHR_ADDR_WIDTH-1:0]    fill_id,
    output wire [`LINE_ADDR_WIDTH-1:0]  fill_addr,

    // lookup
    input wire                          lookup_valid,
    input wire                          lookup_replay,   
    input wire [MSHR_ADDR_WIDTH-1:0]    lookup_id,
    input wire [`LINE_ADDR_WIDTH-1:0]   lookup_addr,
    output wire                         lookup_match,
    
    // dequeue    
    output wire                         dequeue_valid,
    output wire [MSHR_ADDR_WIDTH-1:0]   dequeue_id,
    output wire [`LINE_ADDR_WIDTH-1:0]  dequeue_addr,
    output wire [`MSHR_DATA_WIDTH-1:0]  dequeue_data,
    input wire                          dequeue_ready,

    // release
    input wire                          release_valid,
    input wire [MSHR_ADDR_WIDTH-1:0]    release_id
);
    `UNUSED_PARAM (CACHE_ID)
    `UNUSED_PARAM (BANK_ID)
    
    reg [MSHR_SIZE-1:0][`LINE_ADDR_WIDTH-1:0] addr_table, addr_table_n;
    reg [MSHR_SIZE-1:0] valid_table, valid_table_n;
    reg [MSHR_SIZE-1:0] ready_table, ready_table_n;
    
    reg allocate_rdy_r, allocate_rdy_n;
    reg [MSHR_ADDR_WIDTH-1:0] allocate_id_r, allocate_id_n;
    
    reg dequeue_val_r, dequeue_val_n, dequeue_val_x;
    reg [MSHR_ADDR_WIDTH-1:0] dequeue_id_r, dequeue_id_n, dequeue_id_x;

    reg [MSHR_SIZE-1:0] valid_table_x;
    reg [MSHR_SIZE-1:0] ready_table_x;

    wire [MSHR_SIZE-1:0] addr_matches;
    
    wire allocate_fire = allocate_valid && allocate_ready;
    
    wire dequeue_fire = dequeue_valid && dequeue_ready;

    for (genvar i = 0; i < MSHR_SIZE; ++i) begin
        assign addr_matches[i] = (addr_table[i] == lookup_addr);
    end
    
    always @(*) begin
        valid_table_x = valid_table;
        ready_table_x = ready_table;
        if (dequeue_fire) begin
            valid_table_x[dequeue_id] = 0;
        end
        if (lookup_replay) begin            
            ready_table_x |= addr_matches;
        end
    end

    VX_lzc #(
        .N (MSHR_SIZE)
    ) dequeue_sel (
        .in_i    (valid_table_x & ready_table_x),
        .cnt_o   (dequeue_id_x),
        .valid_o (dequeue_val_x)
    );

    VX_lzc #(
        .N (MSHR_SIZE)
    ) allocate_sel (
        .in_i    (~valid_table_n),
        .cnt_o   (allocate_id_n),
        .valid_o (allocate_rdy_n)
    );

    always @(*) begin
        valid_table_n = valid_table_x;
        ready_table_n = ready_table_x;
        addr_table_n  = addr_table;
        dequeue_val_n = dequeue_val_r;
        dequeue_id_n  = dequeue_id_r;

        if (dequeue_fire) begin
            dequeue_val_n = dequeue_val_x;            
            dequeue_id_n  = dequeue_id_x;
        end

        if (allocate_fire) begin
            valid_table_n[allocate_id] = 1;
            ready_table_n[allocate_id] = 0;
            addr_table_n[allocate_id]  = allocate_addr;
        end

        if (fill_valid) begin
            dequeue_val_n = 1;
            dequeue_id_n  = fill_id;
        end

        if (release_valid) begin
            valid_table_n[release_id] = 0;
        end
    end

    always @(posedge clk) begin
        if (reset) begin
            valid_table    <= 0;
            allocate_rdy_r <= 0;
            dequeue_val_r  <= 0;
        end else begin
            valid_table    <= valid_table_n;
            allocate_rdy_r <= allocate_rdy_n;
            dequeue_val_r  <= dequeue_val_n;      
        end
        ready_table   <= ready_table_n;
        addr_table    <= addr_table_n;                
        dequeue_id_r  <= dequeue_id_n;
        allocate_id_r <= allocate_id_n;

        `ASSERT(!allocate_fire || !valid_table[allocate_id_r], ("runtime error"));        
        `ASSERT(!release_valid || valid_table[release_id], ("runtime error"));
    end
    
    `RUNTIME_ASSERT((!allocate_fire || ~valid_table[allocate_id]), ("%t: *** cache%0d:%0d in-use allocation: addr=%0h, id=%0d", $time, CACHE_ID, BANK_ID, 
        `LINE_TO_BYTE_ADDR(allocate_addr, BANK_ID), allocate_id))
    
    `RUNTIME_ASSERT((!fill_valid || valid_table[fill_id]), ("%t: *** cache%0d:%0d invalid fill: addr=%0h, id=%0d", $time, CACHE_ID, BANK_ID, 
        `LINE_TO_BYTE_ADDR(addr_table[fill_id], BANK_ID), fill_id))

    VX_dp_ram #(
        .DATAW  (`MSHR_DATA_WIDTH),
        .SIZE   (MSHR_SIZE),
        .LUTRAM (1)
    ) entries (
        .clk   (clk),
        .waddr (allocate_id_r),                                
        .raddr (dequeue_id_r),
        .wren  (allocate_valid),
        .wdata (allocate_data),
        .rdata (dequeue_data)
    );

    assign fill_addr = addr_table[fill_id];

    assign allocate_ready = allocate_rdy_r;
    assign allocate_id    = allocate_id_r;

    assign dequeue_valid  = dequeue_val_r;
    assign dequeue_id     = dequeue_id_r;
    assign dequeue_addr   = addr_table[dequeue_id_r];

    wire [MSHR_SIZE-1:0] lookup_entries;
    for (genvar i = 0; i < MSHR_SIZE; ++i) begin
        assign lookup_entries[i] = (i != lookup_id);
    end
    assign lookup_match = |(lookup_entries & valid_table & addr_matches);

    `UNUSED_VAR (lookup_valid)

`ifdef DBG_PRINT_CACHE_MSHR        
    always @(posedge clk) begin
        if (allocate_fire || fill_valid || dequeue_fire || lookup_replay || lookup_valid || release_valid) begin
            if (allocate_fire)
                dpi_trace("%d: cache%0d:%0d mshr-allocate: addr=%0h, id=%0d, wid=%0d, PC=%0h\n", $time, CACHE_ID, BANK_ID,
                    `LINE_TO_BYTE_ADDR(allocate_addr, BANK_ID), allocate_id, deq_debug_wid, deq_debug_pc);
            if (fill_valid)
                dpi_trace("%d: cache%0d:%0d mshr-fill: addr=%0h, id=%0d, addr=%0h\n", $time, CACHE_ID, BANK_ID, 
                    `LINE_TO_BYTE_ADDR(addr_table[fill_id], BANK_ID), fill_id, `LINE_TO_BYTE_ADDR(fill_addr, BANK_ID));
            if (dequeue_fire)
                dpi_trace("%d: cache%0d:%0d mshr-dequeue: addr=%0h, id=%0d, wid=%0d, PC=%0h\n", $time, CACHE_ID, BANK_ID, 
                    `LINE_TO_BYTE_ADDR(dequeue_addr, BANK_ID), dequeue_id_r, deq_debug_wid, deq_debug_pc);      
            if (lookup_replay)
                dpi_trace("%d: cache%0d:%0d mshr-replay: addr=%0h, id=%0d\n", $time, CACHE_ID, BANK_ID, 
                    `LINE_TO_BYTE_ADDR(lookup_addr, BANK_ID), lookup_id);
            if (lookup_valid)
                dpi_trace("%d: cache%0d:%0d mshr-lookup: addr=%0h, id=%0d, match=%b, wid=%0d, PC=%0h\n", $time, CACHE_ID, BANK_ID, 
                    `LINE_TO_BYTE_ADDR(lookup_addr, BANK_ID), lookup_id, lookup_match, lkp_debug_wid, lkp_debug_pc);
            if (release_valid)
                dpi_trace("%d: cache%0d:%0d mshr-release id=%0d, wid=%0d, PC=%0h\n", $time, CACHE_ID, BANK_ID, 
                    release_id, rel_debug_wid, rel_debug_pc);
            dpi_trace("%d: cache%0d:%0d mshr-table", $time, CACHE_ID, BANK_ID);
            for (integer i = 0; i < MSHR_SIZE; ++i) begin
                if (valid_table[i]) begin
                    dpi_trace(" ");                    
                    if (ready_table[i]) 
                        dpi_trace("*");
                    dpi_trace("%0d=%0h", i, `LINE_TO_BYTE_ADDR(addr_table[i], BANK_ID));
                end
            end
            dpi_trace("\n");
        end        
    end
`endif

endmodule