// 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