blob: cfcc0506eb0fd796bbb03ee78296eb0713583d10 [file] [log] [blame]
/**
* Prefetcher Buffer for 32 bit memory interface
*
* Prefetch Buffer that caches instructions. This cuts overly long critical
* paths to the instruction cache.
*/
module brq_ifu_prefetch_buffer #(
parameter bit BranchPredictor = 1'b0
) (
input logic clk_i,
input logic rst_ni,
input logic req_i,
input logic branch_i,
input logic branch_spec_i,
input logic predicted_branch_i,
// input logic branch_mispredict_i,
input logic [31:0] addr_i,
input logic ready_i,
output logic valid_o,
output logic [31:0] rdata_o,
output logic [31:0] addr_o,
output logic err_o,
output logic err_plus2_o,
// goes to instruction memory / instruction cache
output logic instr_req_o,
input logic instr_gnt_i,
output logic [31:0] instr_addr_o,
input logic [31:0] instr_rdata_i,
input logic instr_err_i,
input logic instr_pmp_err_i,
input logic instr_rvalid_i,
// Prefetch Buffer Status
output logic busy_o
);
logic branch_mispredict_i;
assign branch_mispredict_i = '0;
localparam int unsigned NUM_REQS = 2;
logic branch_suppress;
logic valid_new_req, valid_req;
logic valid_req_d, valid_req_q;
logic discard_req_d, discard_req_q;
logic gnt_or_pmp_err, rvalid_or_pmp_err;
logic [NUM_REQS-1:0] rdata_outstanding_n, rdata_outstanding_s, rdata_outstanding_q;
logic [NUM_REQS-1:0] branch_discard_n, branch_discard_s, branch_discard_q;
logic [NUM_REQS-1:0] rdata_pmp_err_n, rdata_pmp_err_s, rdata_pmp_err_q;
logic [NUM_REQS-1:0] rdata_outstanding_rev;
logic [31:0] stored_addr_d, stored_addr_q;
logic stored_addr_en;
logic [31:0] fetch_addr_d, fetch_addr_q;
logic fetch_addr_en;
logic [31:0] branch_mispredict_addr;
logic [31:0] instr_addr, instr_addr_w_aligned;
logic instr_or_pmp_err;
logic fifo_valid;
logic [31:0] fifo_addr;
logic fifo_ready;
logic fifo_clear;
logic [NUM_REQS-1:0] fifo_busy;
logic valid_raw;
logic [31:0] addr_next;
logic branch_or_mispredict;
////////////////////////////
// Prefetch buffer status //
////////////////////////////
assign busy_o = (|rdata_outstanding_q) | instr_req_o;
assign branch_or_mispredict = branch_i | branch_mispredict_i;
//////////////////////////////////////////////
// Fetch fifo - consumes addresses and data //
//////////////////////////////////////////////
// Instruction fetch errors are valid on the data phase of a request
// PMP errors are generated in the address phase, and registered into a fake data phase
assign instr_or_pmp_err = instr_err_i | rdata_pmp_err_q[0];
// A branch will invalidate any previously fetched instructions.
// Note that the FENCE.I instruction relies on this flushing behaviour on branch. If it is
// altered the FENCE.I implementation may require changes.
assign fifo_clear = branch_or_mispredict;
// Reversed version of rdata_outstanding_q which can be overlaid with fifo fill state
for (genvar i = 0; i < NUM_REQS; i++) begin : gen_rd_rev
assign rdata_outstanding_rev[i] = rdata_outstanding_q[NUM_REQS-1-i];
end
// The fifo is ready to accept a new request if it is not full - including space reserved for
// requests already outstanding.
// Overlay the fifo fill state with the outstanding requests to see if there is space.
assign fifo_ready = ~&(fifo_busy | rdata_outstanding_rev);
brq_ifu_fifo #(
.NUM_REQS (NUM_REQS)
) fifo_i (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.clear_i ( fifo_clear ),
.busy_o ( fifo_busy ),
.in_valid_i ( fifo_valid ),
.in_addr_i ( fifo_addr ),
.in_rdata_i ( instr_rdata_i ),
.in_err_i ( instr_or_pmp_err ),
.out_valid_o ( valid_raw ),
.out_ready_i ( ready_i ),
.out_rdata_o ( rdata_o ),
.out_addr_o ( addr_o ),
.out_addr_next_o ( addr_next ),
.out_err_o ( err_o ),
.out_err_plus2_o ( err_plus2_o )
);
//////////////
// Requests //
//////////////
// Suppress a new request on a not-taken branch (as the external address will be incorrect)
assign branch_suppress = branch_spec_i & ~branch_i;
// Make a new request any time there is space in the FIFO, and space in the request queue
assign valid_new_req = ~branch_suppress & req_i & (fifo_ready | branch_or_mispredict) &
~rdata_outstanding_q[NUM_REQS-1];
assign valid_req = valid_req_q | valid_new_req;
// If a request address triggers a PMP error, the external bus request is suppressed. We might
// therefore never receive a grant for such a request. The grant is faked in this case to make
// sure the request proceeds and the error is pushed to the FIFO.
assign gnt_or_pmp_err = instr_gnt_i | instr_pmp_err_i;
// As with the grant, the rvalid must be faked for a PMP error, since the request was suppressed.
assign rvalid_or_pmp_err = rdata_outstanding_q[0] & (instr_rvalid_i | rdata_pmp_err_q[0]);
// Hold the request stable for requests that didn't get granted
assign valid_req_d = valid_req & ~gnt_or_pmp_err;
// Record whether an outstanding bus request is cancelled by a branch
assign discard_req_d = valid_req_q & (branch_or_mispredict | discard_req_q);
////////////////
// Fetch addr //
////////////////
// Two addresses are tracked in the prefetch buffer:
// 1. stored_addr_q - This is the address issued on the bus. It stays stable until
// the request is granted.
// 2. fetch_addr_q - This is our next address to fetch from. It is updated on branches to
// capture the new address, and then for each new request issued.
// A third address is tracked in the fetch FIFO itself:
// 3. instr_addr_q - This is the address at the head of the FIFO, efectively our oldest fetched
// address. This address is updated on branches, and does its own increment
// each time the FIFO is popped.
// 1. stored_addr_q
// Only update stored_addr_q for new ungranted requests
assign stored_addr_en = valid_new_req & ~valid_req_q & ~gnt_or_pmp_err;
// Store whatever address was issued on the bus
assign stored_addr_d = instr_addr;
// CPU resets with a branch, so no need to reset these addresses
always_ff @(posedge clk_i) begin
if (stored_addr_en) begin
stored_addr_q <= stored_addr_d;
end
end
if (BranchPredictor) begin : g_branch_predictor
// Where the branch predictor is present record what address followed a predicted branch. If
// that branch is predicted taken but mispredicted (so not-taken) this is used to resume on
// the not-taken code path.
logic [31:0] branch_mispredict_addr_q;
logic branch_mispredict_addr_en;
assign branch_mispredict_addr_en = branch_i & predicted_branch_i;
always_ff @(posedge clk_i) begin
if (branch_mispredict_addr_en) begin
branch_mispredict_addr_q <= addr_next;
end
end
assign branch_mispredict_addr = branch_mispredict_addr_q;
end else begin : g_no_branch_predictor
logic unused_predicted_branch;
logic [31:0] unused_addr_next;
assign unused_predicted_branch = predicted_branch_i;
assign unused_addr_next = addr_next;
assign branch_mispredict_addr = '0;
end
// 2. fetch_addr_q
// Update on a branch or as soon as a request is issued
assign fetch_addr_en = branch_or_mispredict | (valid_new_req & ~valid_req_q);
assign fetch_addr_d = (branch_i ? addr_i :
branch_mispredict_i ? {branch_mispredict_addr[31:2], 2'b00} :
{fetch_addr_q[31:2], 2'b00}) +
// Current address + 4
{{29{1'b0}},(valid_new_req & ~valid_req_q),2'b00};
always_ff @(posedge clk_i) begin
if (fetch_addr_en) begin
fetch_addr_q <= fetch_addr_d;
end
end
// Address mux
assign instr_addr = valid_req_q ? stored_addr_q :
branch_spec_i ? addr_i :
branch_mispredict_i ? branch_mispredict_addr :
fetch_addr_q;
assign instr_addr_w_aligned = {instr_addr[31:2], 2'b00};
///////////////////////////////
// Request outstanding queue //
///////////////////////////////
for (genvar i = 0; i < NUM_REQS; i++) begin : g_outstanding_reqs
// Request 0 (always the oldest outstanding request)
if (i == 0) begin : g_req0
// A request becomes outstanding once granted, and is cleared once the rvalid is received.
// Outstanding requests shift down the queue towards entry 0.
assign rdata_outstanding_n[i] = (valid_req & gnt_or_pmp_err) |
rdata_outstanding_q[i];
// If a branch is received at any point while a request is outstanding, it must be tracked
// to ensure we discard the data once received
assign branch_discard_n[i] = (valid_req & gnt_or_pmp_err & discard_req_d) |
(branch_or_mispredict & rdata_outstanding_q[i]) |
branch_discard_q[i];
// Record whether this request received a PMP error
assign rdata_pmp_err_n[i] = (valid_req & ~rdata_outstanding_q[i] & instr_pmp_err_i) |
rdata_pmp_err_q[i];
end else begin : g_reqtop
// Entries > 0 consider the FIFO fill state to calculate their next state (by checking
// whether the previous entry is valid)
assign rdata_outstanding_n[i] = (valid_req & gnt_or_pmp_err &
rdata_outstanding_q[i-1]) |
rdata_outstanding_q[i];
assign branch_discard_n[i] = (valid_req & gnt_or_pmp_err & discard_req_d &
rdata_outstanding_q[i-1]) |
(branch_or_mispredict & rdata_outstanding_q[i]) |
branch_discard_q[i];
assign rdata_pmp_err_n[i] = (valid_req & ~rdata_outstanding_q[i] & instr_pmp_err_i &
rdata_outstanding_q[i-1]) |
rdata_pmp_err_q[i];
end
end
// Shift the entries down on each instr_rvalid_i
assign rdata_outstanding_s = rvalid_or_pmp_err ? {1'b0,rdata_outstanding_n[NUM_REQS-1:1]} :
rdata_outstanding_n;
assign branch_discard_s = rvalid_or_pmp_err ? {1'b0,branch_discard_n[NUM_REQS-1:1]} :
branch_discard_n;
assign rdata_pmp_err_s = rvalid_or_pmp_err ? {1'b0,rdata_pmp_err_n[NUM_REQS-1:1]} :
rdata_pmp_err_n;
// Push a new entry to the FIFO once complete (and not cancelled by a branch)
assign fifo_valid = rvalid_or_pmp_err & ~branch_discard_q[0];
assign fifo_addr = branch_i ? addr_i : branch_mispredict_addr;
///////////////
// Registers //
///////////////
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
valid_req_q <= 1'b0;
discard_req_q <= 1'b0;
rdata_outstanding_q <= 'b0;
branch_discard_q <= 'b0;
rdata_pmp_err_q <= 'b0;
end else begin
valid_req_q <= valid_req_d;
discard_req_q <= discard_req_d;
rdata_outstanding_q <= rdata_outstanding_s;
branch_discard_q <= branch_discard_s;
rdata_pmp_err_q <= rdata_pmp_err_s;
end
end
/////////////
// Outputs //
/////////////
assign instr_req_o = valid_req;
assign instr_addr_o = instr_addr_w_aligned;
assign valid_o = valid_raw & ~branch_mispredict_i;
endmodule