blob: 09bb06c294d2bf2ad3d46c329e316c64eebd71a7 [file] [log] [blame]
/**
* Fetch Fifo for 32 bit memory interface
*
* input port: send address and data to the FIFO
* clear_i clears the FIFO for the following cycle, including any new request
*/
module brq_ifu_fifo #(
parameter int unsigned NUM_REQS = 2
) (
input logic clk_i,
input logic rst_ni,
// control signals
input logic clear_i, // clears the contents of the FIFO
output logic [NUM_REQS-1:0] busy_o,
// input port
input logic in_valid_i,
input logic [31:0] in_addr_i,
input logic [31:0] in_rdata_i,
input logic in_err_i,
// output port
output logic out_valid_o,
input logic out_ready_i,
output logic [31:0] out_addr_o,
output logic [31:0] out_addr_next_o,
output logic [31:0] out_rdata_o,
output logic out_err_o,
output logic out_err_plus2_o
);
localparam int unsigned DEPTH = NUM_REQS+1;
// index 0 is used for output
logic [DEPTH-1:0] [31:0] rdata_d, rdata_q;
logic [DEPTH-1:0] err_d, err_q;
logic [DEPTH-1:0] valid_d, valid_q;
logic [DEPTH-1:0] lowest_free_entry;
logic [DEPTH-1:0] valid_pushed, valid_popped;
logic [DEPTH-1:0] entry_en;
logic pop_fifo;
logic [31:0] rdata, rdata_unaligned;
logic err, err_unaligned, err_plus2;
logic valid, valid_unaligned;
logic aligned_is_compressed, unaligned_is_compressed;
logic addr_incr_two;
logic [31:1] instr_addr_next;
logic [31:1] instr_addr_d, instr_addr_q;
logic instr_addr_en;
logic unused_addr_in;
/////////////////
// Output port //
/////////////////
assign rdata = valid_q[0] ? rdata_q[0] : in_rdata_i;
assign err = valid_q[0] ? err_q[0] : in_err_i;
assign valid = valid_q[0] | in_valid_i;
// The FIFO contains word aligned memory fetches, but the instructions contained in each entry
// might be half-word aligned (due to compressed instructions)
// e.g.
// | 31 16 | 15 0 |
// FIFO entry 0 | Instr 1 [15:0] | Instr 0 [15:0] |
// FIFO entry 1 | Instr 2 [15:0] | Instr 1 [31:16] |
//
// The FIFO also has a direct bypass path, so a complete instruction might be made up of data
// from the FIFO and new incoming data.
//
// Construct the output data for an unaligned instruction
assign rdata_unaligned = valid_q[1] ? {rdata_q[1][15:0], rdata[31:16]} :
{in_rdata_i[15:0], rdata[31:16]};
// If entry[1] is valid, an error can come from entry[0] or entry[1], unless the
// instruction in entry[0] is compressed (entry[1] is a new instruction)
// If entry[1] is not valid, and entry[0] is, an error can come from entry[0] or the incoming
// data, unless the instruction in entry[0] is compressed
// If entry[0] is not valid, the error must come from the incoming data
assign err_unaligned = valid_q[1] ? ((err_q[1] & ~unaligned_is_compressed) | err_q[0]) :
((valid_q[0] & err_q[0]) |
(in_err_i & (~valid_q[0] | ~unaligned_is_compressed)));
// Record when an error is caused by the second half of an unaligned 32bit instruction.
// Only needs to be correct when unaligned and if err_unaligned is set
assign err_plus2 = valid_q[1] ? (err_q[1] & ~err_q[0]) :
(in_err_i & valid_q[0] & ~err_q[0]);
// An uncompressed unaligned instruction is only valid if both parts are available
assign valid_unaligned = valid_q[1] ? 1'b1 :
(valid_q[0] & in_valid_i);
// If there is an error, rdata is unknown
assign unaligned_is_compressed = (rdata[17:16] != 2'b11) & ~err;
assign aligned_is_compressed = (rdata[ 1: 0] != 2'b11) & ~err;
////////////////////////////////////////
// Instruction aligner (if unaligned) //
////////////////////////////////////////
always_comb begin
if (out_addr_o[1]) begin
// unaligned case
out_rdata_o = rdata_unaligned;
out_err_o = err_unaligned;
out_err_plus2_o = err_plus2;
if (unaligned_is_compressed) begin
out_valid_o = valid;
end else begin
out_valid_o = valid_unaligned;
end
end else begin
// aligned case
out_rdata_o = rdata;
out_err_o = err;
out_err_plus2_o = 1'b0;
out_valid_o = valid;
end
end
/////////////////////////
// Instruction address //
/////////////////////////
// Update the address on branches and every time an instruction is driven
assign instr_addr_en = clear_i | (out_ready_i & out_valid_o);
// Increment the address by two every time a compressed instruction is popped
assign addr_incr_two = instr_addr_q[1] ? unaligned_is_compressed :
aligned_is_compressed;
assign instr_addr_next = (instr_addr_q[31:1] +
// Increment address by 4 or 2
{29'd0,~addr_incr_two,addr_incr_two});
assign instr_addr_d = clear_i ? in_addr_i[31:1] :
instr_addr_next;
always_ff @(posedge clk_i) begin
if (instr_addr_en) begin
instr_addr_q <= instr_addr_d;
end
end
// Output both PC of current instruction and instruction following. PC of instruction following is
// required for the branch predictor. It's used to fetch the instruction following a branch that
// was not-taken but (mis)predicted taken.
assign out_addr_next_o = {instr_addr_next, 1'b0};
assign out_addr_o = {instr_addr_q, 1'b0};
// The LSB of the address is unused, since all addresses are halfword aligned
assign unused_addr_in = in_addr_i[0];
/////////////////
// FIFO status //
/////////////////
// Indicate the fill level of fifo-entries. This is used to determine when a new request can be
// made on the bus. The prefetch buffer only needs to know about the upper entries which overlap
// with NUM_REQS.
assign busy_o = valid_q[DEPTH-1:DEPTH-NUM_REQS];
/////////////////////
// FIFO management //
/////////////////////
// Since an entry can contain unaligned instructions, popping an entry can leave the entry valid
assign pop_fifo = out_ready_i & out_valid_o & (~aligned_is_compressed | out_addr_o[1]);
for (genvar i = 0; i < (DEPTH - 1); i++) begin : g_fifo_next
// Calculate lowest free entry (write pointer)
if (i == 0) begin : g_ent0
assign lowest_free_entry[i] = ~valid_q[i];
end else begin : g_ent_others
assign lowest_free_entry[i] = ~valid_q[i] & valid_q[i-1];
end
// An entry is set when an incoming request chooses the lowest available entry
assign valid_pushed[i] = (in_valid_i & lowest_free_entry[i]) |
valid_q[i];
// Popping the FIFO shifts all entries down
assign valid_popped[i] = pop_fifo ? valid_pushed[i+1] : valid_pushed[i];
// All entries are wiped out on a clear
assign valid_d[i] = valid_popped[i] & ~clear_i;
// data flops are enabled if there is new data to shift into it, or
assign entry_en[i] = (valid_pushed[i+1] & pop_fifo) |
// a new request is incoming and this is the lowest free entry
(in_valid_i & lowest_free_entry[i] & ~pop_fifo);
// take the next entry or the incoming data
assign rdata_d[i] = valid_q[i+1] ? rdata_q[i+1] : in_rdata_i;
assign err_d [i] = valid_q[i+1] ? err_q [i+1] : in_err_i;
end
// The top entry is similar but with simpler muxing
assign lowest_free_entry[DEPTH-1] = ~valid_q[DEPTH-1] & valid_q[DEPTH-2];
assign valid_pushed [DEPTH-1] = valid_q[DEPTH-1] | (in_valid_i & lowest_free_entry[DEPTH-1]);
assign valid_popped [DEPTH-1] = pop_fifo ? 1'b0 : valid_pushed[DEPTH-1];
assign valid_d [DEPTH-1] = valid_popped[DEPTH-1] & ~clear_i;
assign entry_en[DEPTH-1] = in_valid_i & lowest_free_entry[DEPTH-1];
assign rdata_d [DEPTH-1] = in_rdata_i;
assign err_d [DEPTH-1] = in_err_i;
////////////////////
// FIFO registers //
////////////////////
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
valid_q <= '0;
end else begin
valid_q <= valid_d;
end
end
for (genvar i = 0; i < DEPTH; i++) begin : g_fifo_regs
always_ff @(posedge clk_i) begin
if (entry_en[i]) begin
rdata_q[i] <= rdata_d[i];
err_q[i] <= err_d[i];
end
end
end
endmodule