snitch_icache_handler.sv 11.4 KB
Newer Older
sakundu committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
// Copyright 2020 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51

// Fabian Schuiki <fschuiki@iis.ee.ethz.ch>

module snitch_icache_handler #(
    parameter snitch_icache_pkg::config_t CFG = '0
)(
    input  logic clk_i,
    input  logic rst_ni,

    input  logic [CFG.FETCH_AW-1:0]      in_req_addr_i,
    input  logic [CFG.ID_WIDTH_REQ-1:0]  in_req_id_i,
    input  logic [CFG.SET_ALIGN-1:0]     in_req_set_i,
    input  logic                         in_req_hit_i,
    input  logic [CFG.LINE_WIDTH-1:0]    in_req_data_i,
    input  logic                         in_req_error_i,
    input  logic                         in_req_valid_i,
    output logic                         in_req_ready_o,

    output logic [CFG.LINE_WIDTH-1:0]    in_rsp_data_o,
    output logic                         in_rsp_error_o,
    output logic [CFG.ID_WIDTH_RESP-1:0] in_rsp_id_o,
    output logic                         in_rsp_valid_o,
    input  logic                         in_rsp_ready_i,

    output logic [CFG.COUNT_ALIGN-1:0]   write_addr_o,
    output logic [CFG.SET_ALIGN-1:0]     write_set_o,
    output logic [CFG.LINE_WIDTH-1:0]    write_data_o,
    output logic [CFG.TAG_WIDTH-1:0]     write_tag_o,
    output logic                         write_error_o,
    output logic                         write_valid_o,
    input  logic                         write_ready_i,

    output logic [CFG.FETCH_AW-1:0]      out_req_addr_o,
    output logic [CFG.PENDING_IW-1:0]    out_req_id_o,
    output logic                         out_req_valid_o,
    input  logic                         out_req_ready_i,

    input  logic [CFG.LINE_WIDTH-1:0]    out_rsp_data_i,
    input  logic                         out_rsp_error_i,
    input  logic [CFG.PENDING_IW-1:0]    out_rsp_id_i,
    input  logic                         out_rsp_valid_i,
    output logic                         out_rsp_ready_o
);

    `ifndef SYNTHESIS
    initial assert(CFG != '0);
    `endif

    // The table of pending refills holds the metadata of all refills that are
    // currently in flight. The table has a push and a pop interfaces. The push
    // interface is used to mark entries as valid and update the mask of request
    // IDs that the refill will serve. The pop interface is used to read a value
    // from the table and clear its valid flag.
    typedef struct packed {
        logic valid;
        logic [CFG.FETCH_AW-1:0] addr;
        logic [CFG.ID_WIDTH_RESP-1:0] idmask; // mask of incoming ids
    } pending_t;
    pending_t pending_q [CFG.PENDING_COUNT];
    logic [CFG.PENDING_COUNT-1:0] pending_clr;
    logic [CFG.PENDING_COUNT-1:0] pending_set;

    logic [CFG.PENDING_IW-1:0]    push_index;
    logic                         push_init;   // reset the idmask instead of or'ing
    logic [CFG.FETCH_AW-1:0]      push_addr;
    logic [CFG.ID_WIDTH_RESP-1:0] push_idmask;
    logic                         push_enable;

    logic [CFG.PENDING_IW-1:0]    pop_index;
    logic [CFG.FETCH_AW-1:0]      pop_addr;
    logic [CFG.ID_WIDTH_RESP-1:0] pop_idmask;
    logic                         pop_enable;

    for (genvar i = 0; i < CFG.PENDING_COUNT; i++) begin : g_pending_row
        always_ff @(posedge clk_i, negedge rst_ni) begin
            if (!rst_ni)
                pending_q[i].valid <= 0;
            else if (pending_set[i] || pending_clr[i])
                pending_q[i].valid <= pending_set[i] && ~pending_clr[i];
        end

        always_ff @(posedge clk_i, negedge rst_ni) begin
            if (!rst_ni) begin
                pending_q[i].addr <= '0;
                pending_q[i].idmask <= '0;
            end else if (pending_set[i]) begin
                pending_q[i].addr <= push_addr;
                pending_q[i].idmask <= push_init ? push_idmask : push_idmask | pending_q[i].idmask;
            end
        end
    end

    // The bypass logic ensures that if a table entry is pushed and popped at
    // the same time, the pop is updated with the push information and the push
    // discarded.
    always_comb begin : p_pushpop_bypass
        pending_set = push_enable ? 'b1 << push_index : '0;
        pending_clr = pop_enable ? 'b1 << pop_index : '0;
        pop_addr = pending_q[pop_index].addr;
        pop_idmask = pending_q[pop_index].idmask;
        if (push_enable && pop_enable && push_index == pop_index) begin
            pop_addr = push_addr;
            pop_idmask |= push_idmask;
        end
    end

    // Determine the first available entry in the pending table, if any is free.
    logic [CFG.PENDING_COUNT-1:0] free_entries;
    logic free;
    logic [CFG.PENDING_IW-1:0] free_id;

    always_comb begin : p_free_id
        for (int i = 0; i < CFG.PENDING_COUNT; i++)
            free_entries[i] = ~pending_q[i].valid;
        free = |free_entries;
    end

    lzc #(.WIDTH(CFG.PENDING_COUNT)) i_lzc_free (
        .in_i    ( free_entries ),
        .cnt_o   ( free_id      ),
        .empty_o (              )
    );

    // Determine if the address of the incoming request coincides with any of
    // the entries in the pending table.
    logic [CFG.PENDING_COUNT-1:0] pending_matches;
    logic pending;
    logic [CFG.PENDING_IW-1:0] pending_id;

    always_comb begin : p_pending_id
        for (int i = 0; i < CFG.PENDING_COUNT; i++)
            pending_matches[i] = pending_q[i].valid && pending_q[i].addr == in_req_addr_i;
        pending = |pending_matches;
    end

    lzc #(.WIDTH(CFG.PENDING_COUNT)) i_lzc_pending (
        .in_i    ( pending_matches ),
        .cnt_o   ( pending_id      ),
        .empty_o (                 )
    );

    // The miss handler checks if the access into the cache was a hit. If yes,
    // the data is forwarded to the response handler. Otherwise the table of
    // pending refills is consulted to check if any refills are currently in
    // progress which cover the request. If not, a new refill request is issued
    // and the next free entry in the table allocated. Otherwise the existing
    // table entry is updated.
    logic [CFG.ID_WIDTH_RESP-1:0] hit_id;
    logic [CFG.LINE_WIDTH-1:0]    hit_data;
    logic                         hit_error;
    logic                         hit_valid;
    logic                         hit_ready;

    always_comb begin : p_miss_handler
        hit_valid = 0;
        hit_id    = 'b1 << in_req_id_i;
        hit_data  = in_req_data_i;
        hit_error = in_req_error_i;

        push_index  = free_id;
        push_init   = 0;
        push_addr   = in_req_addr_i;
        push_idmask = 'b1 << in_req_id_i;
        push_enable = 0;

        in_req_ready_o  = 1;

        out_req_addr_o  = in_req_addr_i;
        out_req_id_o    = free_id;
        out_req_valid_o = 0;

        if (in_req_valid_i) begin
            // The cache lookup was a hit.
            if (in_req_hit_i) begin
                hit_valid = 1;
                in_req_ready_o = hit_ready;

            // The cache lookup was a miss, but there is already a pending
            // refill that covers the line.
            end else if (pending) begin
                push_index  = pending_id;
                push_enable = 1;

            // The cache lookup was a miss, there is no pending refill, but
            // there are available entries in the table.
            end else if (free) begin
                out_req_addr_o  = in_req_addr_i;
                out_req_id_o    = free_id;
                out_req_valid_o = 1;
                in_req_ready_o  = out_req_ready_i;
                push_index      = free_id;
                push_init       = 1;
                push_enable     = out_req_ready_i;

            // The cache lookup was a miss, there is no pending refill, and
            // there is no room in the table for a new refill at the moment.
            end else begin
                in_req_ready_o = 0;
            end
        end
    end

    // The cache line eviction LFSR is responsible for picking a cache line for
    // replacement at random. Note that we assume that the entire cache is full,
    // so no empty cache lines are available. This is the common case since we
    // do not support flushing of the cache.
    logic [CFG.SET_ALIGN-1:0] evict_index;
    logic evict_enable;

    snitch_icache_lfsr #(CFG.SET_ALIGN) i_evict_lfsr (
        .clk_i    ( clk_i        ),
        .rst_ni   ( rst_ni       ),
        .value_o  ( evict_index  ),
        .enable_i ( evict_enable )
    );

    // The response handler deals with incoming refill responses. It queries and
    // clears the corresponding entry in the pending table, stores the data in
    // the cache via the `write` port, and returns the data to the appropriate
    // fetch ports via the `in_rsp` port. It also mixes the data of a cache hit
    // into the response stream.
    logic write_served_q;
    logic in_rsp_served_q;
    logic rsp_valid, rsp_ready;

    struct packed {
        logic sel;
        logic lock;
    } arb_q, arb_d;

    always_ff @(posedge clk_i, negedge rst_ni) begin
        if (!rst_ni)
            arb_q <= '0;
        else
            arb_q <= arb_d;
    end

    always_comb begin : p_response_handler
        pop_index  = out_rsp_id_i;
        pop_enable = 0;

        write_addr_o  = pop_addr >> CFG.LINE_ALIGN;
        write_set_o   = evict_index;
        write_data_o  = out_rsp_data_i;
        write_tag_o   = pop_addr >> (CFG.LINE_ALIGN + CFG.COUNT_ALIGN);
        write_error_o = out_rsp_error_i;
        write_valid_o = 0;

        in_rsp_data_o  = out_rsp_data_i;
        in_rsp_error_o = out_rsp_error_i;
        in_rsp_id_o    = pop_idmask;
        in_rsp_valid_o = 0;

        hit_ready       = 1;
        out_rsp_ready_o = 1;
        evict_enable    = 0;
        rsp_valid       = 0;
        rsp_ready       = 1;

        arb_d = arb_q;
        if (!arb_q.lock) begin
            if (hit_valid) begin
                arb_d.sel  = 0;
                arb_d.lock = 1;
            end else if (out_rsp_valid_i) begin
                arb_d.sel  = 1;
                arb_d.lock = 1;
            end else begin
                arb_d.sel  = 0;
                arb_d.lock = 0;
            end
        end

        // Cache hit data is pending.
        if (arb_d.sel == 0) begin
            if (hit_valid) begin
                out_rsp_ready_o = 0;
                in_rsp_data_o   = hit_data;
                in_rsp_error_o  = 0;
                in_rsp_id_o     = hit_id;
                in_rsp_valid_o  = 1;
                hit_ready = in_rsp_ready_i;
            end else hit_ready = 1;
            if (hit_ready) arb_d.lock = 0;

        // No cache hit is pending, but response data is available.
        end else if (arb_d.sel == 1) begin
            if (out_rsp_valid_i) begin
                rsp_valid       = 1;
                rsp_ready       = (in_rsp_ready_i || in_rsp_served_q) && (write_ready_i || write_served_q);
                write_valid_o   = 1 && ~write_served_q;
                in_rsp_valid_o  = 1 && ~in_rsp_served_q;
                pop_enable      = rsp_ready;
                out_rsp_ready_o = rsp_ready;
                evict_enable    = rsp_ready;
            end else rsp_ready = 1;
            if (rsp_ready) arb_d.lock = 0;
        end
    end

    always_ff @(posedge clk_i, negedge rst_ni) begin
        if (!rst_ni) begin
            write_served_q <= 0;
            in_rsp_served_q <= 0;
        end else begin
            write_served_q <= rsp_valid & ~rsp_ready & (write_served_q | write_ready_i);
            in_rsp_served_q <= rsp_valid & ~rsp_ready & (in_rsp_served_q | in_rsp_ready_i);
        end
    end

endmodule