module DCache(
    input clk,
    input reset_n,

    // communication with CPU
    input d_readC, d_writeC,
    input [15:0] d_address,
    inout [15:0] d_data,
    output DataCacheBusy,
    
    // communication with Memory
    output reg d_readM, d_writeM,
    output [13:0] d_address_mem,
    inout [63:0] d_data_mem,
    
    // DMA support
    input BR,
    output reg BG
    );
    
    /*
    Latency:
        Read/write hit:              1 cycle
        Read/write miss, no evict:   6 cycles (1 for cache search, 4 for memory read, 1 for cache search)
        Read/write miss, yes evict:  10 cycles (1 for cache search, 4 for cache line eviction, 4 for memory read, 1 for cache search)
    
    Output:
        DataCacheBusy: asserted when the cache is fetching the requested data. 
                       deasserted right before the cycle data is provided, so that the CPU can detect fetch finish at posedge clk.
                       Hence, (the number of cycles DataCacheBusy==1) + 1 is the cache's latency. 
    */
    
    reg [11:0] TagBank [3:0];
    reg Valid [3:0];
    reg Dirty [3:0];
    reg [63:0] DataBank [3:0];
    
    wire [11:0] tag;
    wire [1:0] idx;
    wire [1:0] block;
    wire hit, evict;
    
    reg [2:0] DataCacheCounter;
    
    assign tag = d_address[15:4];
    assign idx = d_address[3:2];
    assign block = d_address[1:0];
    
    assign hit = (TagBank[idx]==tag) && Valid[idx];
    assign evict = (Dirty[idx]);
    
    // Read hit is handled combinationally
    assign d_data = (d_readC && hit) ? DataBank[idx][16*block+:16] : 16'hz;
    
    assign d_address_mem = (BG || (BR && !d_readM && !d_writeM)) ? 14'bz : { tag, idx };
    assign d_data_mem = (d_writeM && evict && !(BG || (BR && !d_readM && !d_writeM))) ? DataBank[idx] : 64'hz;
    
    // Becomes zero when data is fetched into the cache, since hit becomes 1.
    assign DataCacheBusy = (d_readC || d_writeC) && !hit;
    
    // Cache reset
    always @(posedge clk) begin
        if (!reset_n) begin
            { TagBank[3], TagBank[2], TagBank[1], TagBank[0] } <= 0;
            { Valid[3], Valid[2], Valid[1], Valid[0] } <= 0;
            { Dirty[3], Dirty[2], Dirty[1], Dirty[0] } <= 0;
            { DataBank[3], DataBank[2], DataBank[1], DataBank[0] } <= 0;
            d_readM <= 0;
            d_writeM <= 0;
            DataCacheCounter <= 0;
            BG <= 0;
        end
    end
    
    // Immediately remove bus grant when bus request is deasserted
    always @(negedge BR) begin
        BG <= 0;
    end
    
    // Data read/write miss
    always @(posedge clk) begin
        if (reset_n) begin
            //Bus is requested by the DMA, and data memory is not being accessed => start DMA!
            if (BG || (BR && !d_readM && !d_writeM)) begin
                if (BR) begin
                    BG <= 1;
                    d_writeM <= 1'bz;
                end else begin
                    BG <= 0;
                end
            // Data memory access
            end else if ((d_readC || d_writeC) && !hit) begin
                if (evict) begin    // evict cache line to memory. actual evicting logic is located at the next always statement.
                    if (DataCacheCounter == 0) begin
                        d_writeM <= 1;
                        DataCacheCounter <= `LATENCY;
                    end else begin
                        DataCacheCounter <= DataCacheCounter - 1;
                    end
                end else begin      // fetch cache line from memory
                    if (DataCacheCounter == 0) begin
                        d_readM <= 1;
                        DataCacheCounter <= `LATENCY;
                    end else if (DataCacheCounter == 1) begin
                        TagBank[idx] <= tag;
                        Valid[idx] <= 1;
                        Dirty[idx] <= 0;
                        DataBank[idx] <= d_data_mem;
                        d_readM <= 0;
                    end else begin
                        DataCacheCounter <= DataCacheCounter - 1;
                    end
                end
            // Data cache idle
            end else begin
                d_readM <= 0;
                d_writeM <= 0;
                DataCacheCounter <= 0;
            end
        end
    end
    
    // Cache line eviction
    always @(negedge clk) begin
        if (reset_n && !hit && evict && DataCacheCounter == 1) begin
            Dirty[idx] <= 0;    // Dirty bit cleared. Now the evict signal will drop.
            Valid[idx] <= 0;
            d_writeM <= 0;
            DataCacheCounter <= 0;                    
        end
    end
    
    // Data write hit: latch to cache at next posedge
    always @(posedge clk) begin
        if (reset_n) begin
            if (d_writeC && hit) begin
                DataBank[idx][16*block+:16] <= d_data;
                Dirty[idx] <= 1;
            end
        end
    end
    
    
    // For measuring hit ratio    
    integer data_hit_instructions, data_total_instructions;
    
    always @(posedge clk) begin
        if (!reset_n) begin
            data_hit_instructions <= 0;
            data_total_instructions <= 0;
        end
    end
    
    always @(negedge clk) begin
        if ((d_readC || d_writeC) && hit) data_total_instructions <= data_total_instructions + 1;
        if ((d_readC || d_writeC) && hit && DataCacheCounter==0) data_hit_instructions <= data_hit_instructions + 1;
    end
    
endmodule