`include "VX_define.vh"

module VX_mem_unit # (
    parameter CORE_ID = 0
) (
    `SCOPE_IO_VX_mem_unit

    input wire              clk,
    input wire              reset,
    
`ifdef PERF_ENABLE
    VX_perf_memsys_if       perf_memsys_if,
`endif

    // Core <-> Dcache    
    VX_dcache_core_req_if   dcache_core_req_if,
    VX_dcache_core_rsp_if   dcache_core_rsp_if,
    
    // Core <-> Icache    
    VX_icache_core_req_if   icache_core_req_if,  
    VX_icache_core_rsp_if   icache_core_rsp_if,

    // DRAM
    VX_cache_dram_req_if    dram_req_if,
    VX_cache_dram_rsp_if    dram_rsp_if
);
    
`ifdef PERF_ENABLE
    VX_perf_cache_if perf_icache_if(), perf_dcache_if(), perf_smem_if();
`endif

    VX_cache_dram_req_if #(
        .DRAM_LINE_WIDTH (`DDRAM_LINE_WIDTH),
        .DRAM_ADDR_WIDTH (`DDRAM_ADDR_WIDTH),
        .DRAM_TAG_WIDTH  (`DDRAM_TAG_WIDTH)
    ) dcache_dram_req_if(), icache_dram_req_if();

    VX_cache_dram_rsp_if #(
        .DRAM_LINE_WIDTH (`DDRAM_LINE_WIDTH),
        .DRAM_TAG_WIDTH  (`DDRAM_TAG_WIDTH)
    ) dcache_dram_rsp_if(), icache_dram_rsp_if();

    VX_dcache_core_req_if #(
        .LANES          (`DNUM_REQUESTS), 
        .WORD_SIZE      (`DWORD_SIZE), 
        .CORE_TAG_WIDTH (`DCORE_TAG_WIDTH)
    ) dcache_req_if();

    VX_dcache_core_rsp_if #(
        .LANES          (`DNUM_REQUESTS), 
        .WORD_SIZE      (`DWORD_SIZE), 
        .CORE_TAG_WIDTH (`DCORE_TAG_WIDTH)
    ) dcache_rsp_if();

    VX_dcache_core_req_if #(
        .LANES          (`DNUM_REQUESTS), 
        .WORD_SIZE      (`DWORD_SIZE), 
        .CORE_TAG_WIDTH (`DCORE_TAG_WIDTH)
    ) smem_req_if();

    VX_dcache_core_rsp_if #(
        .LANES          (`DNUM_REQUESTS), 
        .WORD_SIZE      (`DWORD_SIZE), 
        .CORE_TAG_WIDTH (`DCORE_TAG_WIDTH)
    ) smem_rsp_if();

    VX_smem_arb databus_arb (   
        .clk          (clk),
        .reset        (reset),

        .core_req_if  (dcache_core_req_if),
        .cache_req_if (dcache_req_if),
        .smem_req_if  (smem_req_if),

        .cache_rsp_if (dcache_rsp_if),
        .smem_rsp_if  (smem_rsp_if),
        .core_rsp_if  (dcache_core_rsp_if)
    ); 

    wire icache_reset, dcache_reset;   

    VX_reset_relay #(
        .NUM_NODES (2)
    ) reset_relay (
        .clk     (clk),
        .reset   (reset),
        .reset_o ({dcache_reset, icache_reset})
    );

    VX_cache #(
        .CACHE_ID           (`ICACHE_ID),
        .CACHE_SIZE         (`ICACHE_SIZE),
        .CACHE_LINE_SIZE    (`ICACHE_LINE_SIZE),
        .NUM_BANKS          (`INUM_BANKS),
        .WORD_SIZE          (`IWORD_SIZE),
        .NUM_REQS           (1),
        .CREQ_SIZE          (`ICREQ_SIZE),
        .MSHR_SIZE          (`IMSHR_SIZE),
        .DRSQ_SIZE          (`IDRSQ_SIZE),
        .DREQ_SIZE          (`IDREQ_SIZE),
        .WRITE_ENABLE       (0),
        .CORE_TAG_WIDTH     (`ICORE_TAG_WIDTH),
        .CORE_TAG_ID_BITS   (`ICORE_TAG_ID_BITS),
        .DRAM_TAG_WIDTH     (`DDRAM_TAG_WIDTH)
    ) icache (
        `SCOPE_BIND_VX_mem_unit_icache

        .clk                (clk),
        .reset              (icache_reset),

        .flush              (1'b0),

        // Core request
        .core_req_valid     (icache_core_req_if.valid),
        .core_req_rw        (1'b0),
        .core_req_byteen    ({`IWORD_SIZE{1'b1}}),
        .core_req_addr      (icache_core_req_if.addr),
        .core_req_data      ('x),        
        .core_req_tag       (icache_core_req_if.tag),
        .core_req_ready     (icache_core_req_if.ready),

        // Core response
        .core_rsp_valid     (icache_core_rsp_if.valid),
        .core_rsp_data      (icache_core_rsp_if.data),
        .core_rsp_tag       (icache_core_rsp_if.tag),
        .core_rsp_ready     (icache_core_rsp_if.ready),

    `ifdef PERF_ENABLE
        .perf_cache_if      (perf_icache_if),
    `endif

        // DRAM Req
        .dram_req_valid     (icache_dram_req_if.valid),
        .dram_req_rw        (icache_dram_req_if.rw),        
        .dram_req_byteen    (icache_dram_req_if.byteen),        
        .dram_req_addr      (icache_dram_req_if.addr),
        .dram_req_data      (icache_dram_req_if.data),
        .dram_req_tag       (icache_dram_req_if.tag),
        .dram_req_ready     (icache_dram_req_if.ready),        

        // DRAM response
        .dram_rsp_valid     (icache_dram_rsp_if.valid),        
        .dram_rsp_data      (icache_dram_rsp_if.data),
        .dram_rsp_tag       (icache_dram_rsp_if.tag),
        .dram_rsp_ready     (icache_dram_rsp_if.ready)
    );

    VX_cache #(
        .CACHE_ID           (`DCACHE_ID),
        .CACHE_SIZE         (`DCACHE_SIZE),
        .CACHE_LINE_SIZE    (`DCACHE_LINE_SIZE),
        .NUM_BANKS          (`DNUM_BANKS),
        .NUM_PORTS          (`DNUM_PORTS),
        .WORD_SIZE          (`DWORD_SIZE),
        .NUM_REQS           (`DNUM_REQUESTS),
        .CREQ_SIZE          (`DCREQ_SIZE),
        .MSHR_SIZE          (`DMSHR_SIZE),
        .DRSQ_SIZE          (`DDRSQ_SIZE),
        .DREQ_SIZE          (`DDREQ_SIZE),
        .WRITE_ENABLE       (1),
        .CORE_TAG_WIDTH     (`DCORE_TAG_WIDTH),
        .CORE_TAG_ID_BITS   (`DCORE_TAG_ID_BITS),
        .DRAM_TAG_WIDTH     (`DDRAM_TAG_WIDTH)
    ) dcache (
        `SCOPE_BIND_VX_mem_unit_dcache
        
        .clk                (clk),
        .reset              (dcache_reset),

        .flush              (1'b0),

        // Core req
        .core_req_valid     (dcache_req_if.valid),
        .core_req_rw        (dcache_req_if.rw),
        .core_req_byteen    (dcache_req_if.byteen),
        .core_req_addr      (dcache_req_if.addr),
        .core_req_data      (dcache_req_if.data),        
        .core_req_tag       (dcache_req_if.tag),
        .core_req_ready     (dcache_req_if.ready),

        // Core response
        .core_rsp_valid     (dcache_rsp_if.valid),
        .core_rsp_data      (dcache_rsp_if.data),
        .core_rsp_tag       (dcache_rsp_if.tag),
        .core_rsp_ready     (dcache_rsp_if.ready),

    `ifdef PERF_ENABLE
        .perf_cache_if      (perf_dcache_if),
    `endif

        // DRAM request
        .dram_req_valid     (dcache_dram_req_if.valid),
        .dram_req_rw        (dcache_dram_req_if.rw),        
        .dram_req_byteen    (dcache_dram_req_if.byteen),        
        .dram_req_addr      (dcache_dram_req_if.addr),
        .dram_req_data      (dcache_dram_req_if.data),
        .dram_req_tag       (dcache_dram_req_if.tag),
        .dram_req_ready     (dcache_dram_req_if.ready),

        // DRAM response
        .dram_rsp_valid     (dcache_dram_rsp_if.valid),        
        .dram_rsp_data      (dcache_dram_rsp_if.data),
        .dram_rsp_tag       (dcache_dram_rsp_if.tag),
        .dram_rsp_ready     (dcache_dram_rsp_if.ready)
    ); 

    if (`SM_ENABLE) begin

        wire scache_reset;   

        VX_reset_relay reset_relay (
            .clk     (clk),
            .reset   (reset),
            .reset_o (scache_reset)
        );        

        VX_shared_mem #(
            .CACHE_ID           (`SCACHE_ID),
            .CACHE_SIZE         (`SMEM_SIZE),
            .NUM_BANKS          (`SNUM_BANKS),
            .WORD_SIZE          (`SWORD_SIZE),
            .NUM_REQS           (`SNUM_REQUESTS),
            .CREQ_SIZE          (`SCREQ_SIZE),
            .CORE_TAG_WIDTH     (`DCORE_TAG_WIDTH),
            .CORE_TAG_ID_BITS   (`DCORE_TAG_ID_BITS),
            .BANK_ADDR_OFFSET   (`SBANK_ADDR_OFFSET)
        ) smem (            
            .clk                (clk),
            .reset              (scache_reset),

        `ifdef PERF_ENABLE
            .perf_cache_if      (perf_smem_if),
        `endif

            // Core request
            .core_req_valid     (smem_req_if.valid),
            .core_req_rw        (smem_req_if.rw),
            .core_req_byteen    (smem_req_if.byteen),
            .core_req_addr      (smem_req_if.addr),
            .core_req_data      (smem_req_if.data),        
            .core_req_tag       (smem_req_if.tag),
            .core_req_ready     (smem_req_if.ready),

            // Core response
            .core_rsp_valid     (smem_rsp_if.valid),
            .core_rsp_data      (smem_rsp_if.data),
            .core_rsp_tag       (smem_rsp_if.tag),
            .core_rsp_ready     (smem_rsp_if.ready)
        );
    
    end

    VX_mem_arb #(
        .NUM_REQS      (2),
        .DATA_WIDTH    (`DDRAM_LINE_WIDTH),
        .ADDR_WIDTH    (`DDRAM_ADDR_WIDTH),
        .TAG_IN_WIDTH  (`DDRAM_TAG_WIDTH),
        .TAG_OUT_WIDTH (`XDRAM_TAG_WIDTH),
        .BUFFERED_REQ  (1),
        .BUFFERED_RSP  (0)
    ) dram_arb (
        .clk            (clk),
        .reset          (reset),

        // Source request
        .req_valid_in   ({dcache_dram_req_if.valid,   icache_dram_req_if.valid}),
        .req_rw_in      ({dcache_dram_req_if.rw,      icache_dram_req_if.rw}),
        .req_byteen_in  ({dcache_dram_req_if.byteen,  icache_dram_req_if.byteen}),
        .req_addr_in    ({dcache_dram_req_if.addr,    icache_dram_req_if.addr}),
        .req_data_in    ({dcache_dram_req_if.data,    icache_dram_req_if.data}),  
        .req_tag_in     ({dcache_dram_req_if.tag,     icache_dram_req_if.tag}),  
        .req_ready_in   ({dcache_dram_req_if.ready,   icache_dram_req_if.ready}),

        // DRAM request
        .req_valid_out  (dram_req_if.valid),
        .req_rw_out     (dram_req_if.rw),        
        .req_byteen_out (dram_req_if.byteen),        
        .req_addr_out   (dram_req_if.addr),
        .req_data_out   (dram_req_if.data),
        .req_tag_out    (dram_req_if.tag),
        .req_ready_out  (dram_req_if.ready),

        // Source response
        .rsp_valid_out  ({dcache_dram_rsp_if.valid,   icache_dram_rsp_if.valid}),
        .rsp_data_out   ({dcache_dram_rsp_if.data,    icache_dram_rsp_if.data}),
        .rsp_tag_out    ({dcache_dram_rsp_if.tag,     icache_dram_rsp_if.tag}),
        .rsp_ready_out  ({dcache_dram_rsp_if.ready,   icache_dram_rsp_if.ready}),
        
        // DRAM response
        .rsp_valid_in   (dram_rsp_if.valid),
        .rsp_tag_in     (dram_rsp_if.tag),
        .rsp_data_in    (dram_rsp_if.data),
        .rsp_ready_in   (dram_rsp_if.ready)
    );

`ifdef PERF_ENABLE
    
    assign perf_memsys_if.icache_reads       = perf_icache_if.reads;
    assign perf_memsys_if.icache_read_misses = perf_icache_if.read_misses;
    assign perf_memsys_if.icache_pipe_stalls = perf_icache_if.pipe_stalls;
    assign perf_memsys_if.icache_crsp_stalls = perf_icache_if.crsp_stalls;

    assign perf_memsys_if.dcache_reads       = perf_dcache_if.reads;
    assign perf_memsys_if.dcache_writes      = perf_dcache_if.writes;
    assign perf_memsys_if.dcache_read_misses = perf_dcache_if.read_misses;
    assign perf_memsys_if.dcache_write_misses= perf_dcache_if.write_misses;
    assign perf_memsys_if.dcache_bank_stalls = perf_dcache_if.bank_stalls;
    assign perf_memsys_if.dcache_mshr_stalls = perf_dcache_if.mshr_stalls;    
    assign perf_memsys_if.dcache_pipe_stalls = perf_dcache_if.pipe_stalls;
    assign perf_memsys_if.dcache_crsp_stalls = perf_dcache_if.crsp_stalls;

if (`SM_ENABLE) begin
    assign perf_memsys_if.smem_reads         = perf_smem_if.reads;
    assign perf_memsys_if.smem_writes        = perf_smem_if.writes;
    assign perf_memsys_if.smem_bank_stalls   = perf_smem_if.bank_stalls;    
end else begin
    assign perf_memsys_if.smem_reads         = 0;
    assign perf_memsys_if.smem_writes        = 0;
    assign perf_memsys_if.smem_bank_stalls   = 0;
end
    
    reg [43:0] perf_dram_lat_per_cycle;

    always @(posedge clk) begin
        if (reset) begin
            perf_dram_lat_per_cycle <= 0;
        end else begin
            perf_dram_lat_per_cycle <= perf_dram_lat_per_cycle + 
                44'($signed(2'((dram_req_if.valid && !dram_req_if.rw && dram_req_if.ready) && !(dram_rsp_if.valid && dram_rsp_if.ready)) - 
                            2'((dram_rsp_if.valid && dram_rsp_if.ready)                    && !(dram_req_if.valid && !dram_req_if.rw && dram_req_if.ready))));
        end
    end
    
    reg [43:0] perf_dram_reads;
    reg [43:0] perf_dram_writes;
    reg [43:0] perf_dram_lat;
    reg [43:0] perf_dram_stalls;

    always @(posedge clk) begin
        if (reset) begin       
            perf_dram_reads  <= 0;     
            perf_dram_writes <= 0;            
            perf_dram_lat    <= 0;
            perf_dram_stalls <= 0;
        end else begin  
            if (dram_req_if.valid && dram_req_if.ready && !dram_req_if.rw) begin
                perf_dram_reads <= perf_dram_reads + 44'd1;
            end
            if (dram_req_if.valid && dram_req_if.ready && dram_req_if.rw) begin
                perf_dram_writes <= perf_dram_writes + 44'd1;
            end            
            if (dram_req_if.valid && !dram_req_if.ready) begin
                perf_dram_stalls <= perf_dram_stalls + 44'd1;
            end       
            perf_dram_lat <= perf_dram_lat + perf_dram_lat_per_cycle;
        end
    end

    assign perf_memsys_if.dram_reads   = perf_dram_reads;       
    assign perf_memsys_if.dram_writes  = perf_dram_writes;
    assign perf_memsys_if.dram_latency = perf_dram_lat; 
    assign perf_memsys_if.dram_stalls  = perf_dram_stalls;
`endif
    
endmodule
