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