Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
dd1f8e446This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright 2020 Silicon Labs, Inc.
//
// This file, and derivatives thereof are licensed under the
// Solderpad License, Version 2.0 (the "License").
//
// Use of this file means you agree to the terms and conditions
// of the license and are in full compliance with the License.
//
// You may obtain a copy of the License at:
//
// https://solderpad.org/licenses/SHL-2.0/
//
// Unless required by applicable law or agreed to in writing, software
// and hardware implementations thereof distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED.
//
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
// Engineer: Arjan Bink - arjan.bink@silabs.com //
// //
// Design Name: Prefetcher Controller //
// Project Name: CV32E40P //
// Language: SystemVerilog //
// //
// Description: Prefetch Controller which receives control flow //
// information (req_i, branch_*) from the Fetch stage //
// and based on that performs transactions requests to the //
// bus interface adapter instructions. Prefetching based on //
// incrementing addressed is performed when no new control //
// flow change is requested. New transaction requests are //
// only performed if it can be guaranteed that the fetch FIFO //
// will not overflow (resulting in a maximum of DEPTH //
// outstanding transactions. //
// //
////////////////////////////////////////////////////////////////////////////////
module cv32e40p_prefetch_controller #(
parameter PULP_OBI = 0, // Legacy PULP OBI behavior
parameter COREV_PULP = 1, // PULP ISA Extension (including PULP specific CSRs and hardware loop, excluding cv.elw)
parameter DEPTH = 4, // Prefetch FIFO Depth
parameter FIFO_ADDR_DEPTH = (DEPTH > 1) ? $clog2(DEPTH) : 1 // Do not override this parameter
) (
input logic clk,
input logic rst_n,
// Fetch stage interface
input logic req_i, // Fetch stage requests instructions
input logic branch_i, // Taken branch
input logic [31:0] branch_addr_i, // Taken branch address (only valid when branch_i = 1)
output logic busy_o, // Prefetcher busy
// HW loop signals
input logic hwlp_jump_i,
input logic [31:0] hwlp_target_i,
// Transaction request interface
output logic trans_valid_o, // Transaction request valid (to bus interface adapter)
input logic trans_ready_i, // Transaction request ready (transaction gets accepted when trans_valid_o and trans_ready_i are both 1)
output logic [31:0] trans_addr_o, // Transaction address (only valid when trans_valid_o = 1). No stability requirements.
// Transaction response interface
input logic resp_valid_i, // Note: Consumer is assumed to be 'ready' whenever resp_valid_i = 1
// Fetch interface is ready/valid
input logic fetch_ready_i,
output logic fetch_valid_o,
// FIFO interface
output logic fifo_push_o, // PUSH an instruction into the FIFO
output logic fifo_pop_o, // POP an instruction from the FIFO
output logic fifo_flush_o, // Flush the FIFO
output logic fifo_flush_but_first_o, // Flush the FIFO, but keep the first instruction if present
input logic [FIFO_ADDR_DEPTH:0] fifo_cnt_i, // Number of valid items/words in the prefetch FIFO
input logic fifo_empty_i // FIFO is empty
);
import cv32e40p_pkg::*;
prefetch_state_e state_q, next_state;
logic [FIFO_ADDR_DEPTH:0] cnt_q; // Transaction counter
logic [FIFO_ADDR_DEPTH:0] next_cnt; // Next value for cnt_q
logic count_up; // Increment outstanding transaction count by 1 (can happen at same time as count_down)
logic count_down; // Decrement outstanding transaction count by 1 (can happen at same time as count_up)
logic [FIFO_ADDR_DEPTH:0] flush_cnt_q; // Response flush counter (to flush speculative responses after branch)
logic [FIFO_ADDR_DEPTH:0] next_flush_cnt; // Next value for flush_cnt_q
// Transaction address
logic [31:0] trans_addr_q, trans_addr_incr;
// Word-aligned branch target address
logic [31:0] aligned_branch_addr; // Word aligned branch target address
// FIFO auxiliary signal
logic fifo_valid; // FIFO output valid (if !fifo_empty)
logic [FIFO_ADDR_DEPTH:0] fifo_cnt_masked; // FIFO_cnt signal, masked when we are branching to allow a new memory request in that cycle
// HW loop support signals
logic hwlp_wait_resp_flush; // Trigger for the delayed flush
logic hwlp_flush_after_resp; // Wait for HWLP_END and then flush the wrong granted requests
logic [FIFO_ADDR_DEPTH:0] hwlp_flush_cnt_delayed_q; // The number of outstanding requests to flush when HWLP_END is returned
logic hwlp_flush_resp_delayed; // Actual delayed flush
logic hwlp_flush_resp; // Response flush counter when hwlp occurs
//////////////////////////////////////////////////////////////////////////////
// Prefetch buffer status
//////////////////////////////////////////////////////////////////////////////
// Busy if there are ongoing (or potentially outstanding) transfers
assign busy_o = (cnt_q != 3'b000) || trans_valid_o;
//////////////////////////////////////////////////////////////////////////////
// IF/ID interface
//////////////////////////////////////////////////////////////////////////////
// Fectch valid control. Fetch never valid if jumping or flushing responses.
// Fetch valid if there are instructions in FIFO or there is an incoming
// instruction from memory.
assign fetch_valid_o = (fifo_valid || resp_valid_i) && !(branch_i || (flush_cnt_q > 0));
//////////////////////////////////////////////////////////////////////////////
// Transaction request generation
//
// Assumes that corresponding response is at least 1 cycle after request
//
// - Only request transaction when fetch stage requires fetch (req_i), and
// - make sure that FIFO (cv32e40p_fetch_fifo) never overflows (fifo_cnt_i + cnt_q < DEPTH)
//////////////////////////////////////////////////////////////////////////////
// Prefetcher will only perform word fetches
assign aligned_branch_addr = {branch_addr_i[31:2], 2'b00};
// Increment address (always word fetch)
assign trans_addr_incr = {trans_addr_q[31:2], 2'b00} + 32'd4;
// Transaction request generation
generate
if (PULP_OBI == 0) begin : gen_no_pulp_obi
// OBI compatible (avoids combinatorial path from instr_rvalid_i to instr_req_o).
// Multiple trans_* transactions can be issued (and accepted) before a response
// (resp_*) is received.
assign trans_valid_o = req_i && (fifo_cnt_masked + cnt_q < DEPTH);
end else begin : gen_pulp_obi
// Legacy PULP OBI behavior, i.e. only issue subsequent transaction if preceding transfer
// is about to finish (re-introducing timing critical path from instr_rvalid_i to instr_req_o)
assign trans_valid_o = (cnt_q == 3'b000) ? req_i && (fifo_cnt_masked + cnt_q < DEPTH) :
req_i && (fifo_cnt_masked + cnt_q < DEPTH) && resp_valid_i;
end
endgenerate
// Optimization:
// fifo_cnt is used to understand if we can perform new memory requests
// When branching, we flush both the FIFO and the outstanding requests. Therefore,
// there is surely space for a new request.
// Masking fifo_cnt in this case allows for making a new request when the FIFO
// is not empty and we are jumping, and (fifo_cnt_i + cnt_q == DEPTH)
assign fifo_cnt_masked = (branch_i || hwlp_jump_i) ? '0 : fifo_cnt_i;
// FSM (state_q, next_state) to control OBI A channel signals.
always_comb begin
next_state = state_q;
trans_addr_o = trans_addr_q;
case (state_q)
// Default state (pass on branch target address or transaction with incremented address)
IDLE: begin
begin
if (branch_i) begin
// Jumps must have the highest priority (e.g. an interrupt must
// have higher priority than a HW-loop branch)
trans_addr_o = aligned_branch_addr;
end else if (hwlp_jump_i) begin
trans_addr_o = hwlp_target_i;
end else begin
trans_addr_o = trans_addr_incr;
end
end
if ((branch_i || hwlp_jump_i) && !(trans_valid_o && trans_ready_i)) begin
// Taken branch, but transaction not yet accepted by bus interface adapter.
next_state = BRANCH_WAIT;
end
end // case: IDLE
BRANCH_WAIT: begin
// Replay previous branch target address (trans_addr_q) or new branch address (this can
// occur if for example an interrupt is taken right after a taken jump which did not
// yet have its target address accepted by the bus interface adapter.
trans_addr_o = branch_i ? aligned_branch_addr : trans_addr_q;
if (trans_valid_o && trans_ready_i) begin
// Transaction with branch target address has been accepted. Start regular prefetch again.
next_state = IDLE;
end
end // case: BRANCH_WAIT
endcase
end
//////////////////////////////////////////////////////////////////////////////
// FIFO management
//////////////////////////////////////////////////////////////////////////////
// Pass on response transfer directly to FIFO (which should be ready, otherwise
// the corresponding transfer would not have been requested via trans_valid_o).
// Upon a branch (branch_i) all incoming responses (resp_valid_i) are flushed
// until the flush count is 0 again. (The flush count is initialized with the
// number of outstanding transactions at the time of the branch).
assign fifo_valid = !fifo_empty_i;
assign fifo_push_o = resp_valid_i && (fifo_valid || !fetch_ready_i) && !(branch_i || (flush_cnt_q > 0));
assign fifo_pop_o = fifo_valid && fetch_ready_i;
//////////////////////////////////////////////////////////////////////////////
// Counter (cnt_q, next_cnt) to count number of outstanding OBI transactions
// (maximum = DEPTH)
//
// Counter overflow is prevented by limiting the number of outstanding transactions
// to DEPTH. Counter underflow is prevented by the assumption that resp_valid_i = 1
// will only occur in response to accepted transfer request (as per the OBI protocol).
//////////////////////////////////////////////////////////////////////////////
assign count_up = trans_valid_o && trans_ready_i; // Increment upon accepted transfer request
assign count_down = resp_valid_i; // Decrement upon accepted transfer response
always_comb begin
case ({
count_up, count_down
})
2'b00: begin
next_cnt = cnt_q;
end
2'b01: begin
next_cnt = cnt_q - 1'b1;
end
2'b10: begin
next_cnt = cnt_q + 1'b1;
end
2'b11: begin
next_cnt = cnt_q;
end
endcase
end
generate
if (COREV_PULP) begin : gen_hwlp
// Flush the FIFO if it is not empty and we are hwlp branching.
// If HWLP_END is not going to ID, save it from the flush.
// Don't flush the FIFO if it is empty (maybe we must accept
// HWLP_end from the memory in this cycle)
assign fifo_flush_o = branch_i || (hwlp_jump_i && !fifo_empty_i && fifo_pop_o);
assign fifo_flush_but_first_o = (hwlp_jump_i && !fifo_empty_i && !fifo_pop_o);
//////////////////////////////////////////////////////////////////////////////
// HWLP main resp flush controller
//////////////////////////////////////////////////////////////////////////////
// If HWLP_END-4 is in ID and HWLP_END is being/was returned by the memory
// we can flush all the eventual outstanding requests up to now
assign hwlp_flush_resp = hwlp_jump_i && !(fifo_empty_i && !resp_valid_i);
//////////////////////////////////////////////////////////////////////////////
// HWLP delayed flush controller
//////////////////////////////////////////////////////////////////////////////
// If HWLP_END-4 is in ID and HWLP_END has not been returned yet,
// save the present number of outstanding requests (subtract the HWLP_END one).
// Wait for HWLP_END then flush the saved number of (wrong) outstanding requests
assign hwlp_wait_resp_flush = hwlp_jump_i && (fifo_empty_i && !resp_valid_i);
always_ff @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
hwlp_flush_after_resp <= 1'b0;
hwlp_flush_cnt_delayed_q <= 2'b00;
end else begin
if (branch_i) begin
// Reset the flush request if an interrupt is taken
hwlp_flush_after_resp <= 1'b0;
hwlp_flush_cnt_delayed_q <= 2'b00;
end else begin
if (hwlp_wait_resp_flush) begin
hwlp_flush_after_resp <= 1'b1;
// cnt_q > 0 checked by an assertion
hwlp_flush_cnt_delayed_q <= cnt_q - 1'b1;
end else begin
// Reset the delayed flush request when it's completed
if (hwlp_flush_resp_delayed) begin
hwlp_flush_after_resp <= 1'b0;
hwlp_flush_cnt_delayed_q <= 2'b00;
end
end
end
end
end
// This signal is masked by branch_i in the flush counter process,
// because if an interrupt occurs during a delayed flush, the interrupt
// is served first so the flush should be normal (caused by branch_i)
assign hwlp_flush_resp_delayed = hwlp_flush_after_resp && resp_valid_i;
end else begin : gen_no_hwlp
// Flush the FIFO if it is not empty
assign fifo_flush_o = branch_i;
assign fifo_flush_but_first_o = 1'b0;
assign hwlp_flush_resp = 1'b0;
assign hwlp_wait_resp_flush = 1'b0;
assign hwlp_flush_after_resp = 1'b0;
assign hwlp_flush_cnt_delayed_q = 2'b00;
assign hwlp_flush_resp_delayed = 1'b0;
end
endgenerate
//////////////////////////////////////////////////////////////////////////////
// Counter (flush_cnt_q, next_flush_cnt) to count reseponses to be flushed.
//////////////////////////////////////////////////////////////////////////////
always_comb begin
next_flush_cnt = flush_cnt_q;
// Number of outstanding transfers at time of branch equals the number of
// responses that will need to be flushed (responses already in the FIFO will
// be flushed there)
if (branch_i || hwlp_flush_resp) begin
next_flush_cnt = cnt_q;
if (resp_valid_i && (cnt_q > 0)) begin
next_flush_cnt = cnt_q - 1'b1;
end
end else if (hwlp_flush_resp_delayed) begin
// Delayed flush has a lower priority than the normal flush,
// because HW loops branches have lower priority than
// taken interrupts
next_flush_cnt = hwlp_flush_cnt_delayed_q;
end else if (resp_valid_i && (flush_cnt_q > 0)) begin
next_flush_cnt = flush_cnt_q - 1'b1;
end
end
//////////////////////////////////////////////////////////////////////////////
// Registers
//////////////////////////////////////////////////////////////////////////////
always_ff @(posedge clk, negedge rst_n) begin
if (rst_n == 1'b0) begin
state_q <= IDLE;
cnt_q <= '0;
flush_cnt_q <= '0;
trans_addr_q <= '0;
end else begin
state_q <= next_state;
cnt_q <= next_cnt;
flush_cnt_q <= next_flush_cnt;
if (branch_i || hwlp_jump_i || (trans_valid_o && trans_ready_i)) begin
trans_addr_q <= trans_addr_o;
end
end
end
endmodule // cv32e40p_prefetch_controller