mirror of
https://github.com/lowRISC/ibex.git
synced 2025-06-28 17:24:11 -04:00
- The prefetch buffer needs to know when space is available in the fetch FIFO to accept a new external request. - This change updates that logic to look at what is in the FIFO and what is outstanding on the bus to decide when space is available rather than always assuming the maximum number of requests are outstanding. - This improves the usage efficiency of the FIFO and fixes #574 Signed-off-by: Tom Roberts <tomroberts@lowrisc.org>
243 lines
8.9 KiB
Systemverilog
243 lines
8.9 KiB
Systemverilog
// Copyright lowRISC contributors.
|
|
// Copyright 2018 ETH Zurich and University of Bologna, see also CREDITS.md.
|
|
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
`include "prim_assert.sv"
|
|
|
|
module ibex_fetch_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_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_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_d = clear_i ? in_addr_i[31:1] :
|
|
(instr_addr_q[31:1] +
|
|
// Increment address by 4 or 2
|
|
{29'd0,~addr_incr_two,addr_incr_two});
|
|
|
|
always_ff @(posedge clk_i) begin
|
|
if (instr_addr_en) begin
|
|
instr_addr_q <= instr_addr_d;
|
|
end
|
|
end
|
|
|
|
assign out_addr_o[31:1] = instr_addr_q[31:1];
|
|
assign out_addr_o[0] = 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
|
|
|
|
////////////////
|
|
// Assertions //
|
|
////////////////
|
|
|
|
// Must not push and pop simultaneously when FIFO full.
|
|
`ASSERT(IbexFetchFifoPushPopFull,
|
|
(in_valid_i && pop_fifo) |-> (!valid_q[DEPTH-1] || clear_i))
|
|
|
|
// Must not push to FIFO when full.
|
|
`ASSERT(IbexFetchFifoPushFull,
|
|
(in_valid_i) |-> (!valid_q[DEPTH-1] || clear_i))
|
|
|
|
endmodule
|