mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-24 13:57:19 -04:00
944 lines
32 KiB
Systemverilog
944 lines
32 KiB
Systemverilog
// Copyright 2015 ETH Zurich and University of Bologna.
|
|
// Copyright and related rights are licensed under the Solderpad Hardware
|
|
// License, Version 0.51 (the “License”); you may not use this file except in
|
|
// compliance with the License. You may obtain a copy of the License at
|
|
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
|
|
// or agreed to in writing, software, hardware and materials distributed under
|
|
// this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
|
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations under the License.
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Engineer: Matthias Baer - baermatt@student.ethz.ch //
|
|
// //
|
|
// Additional contributions by: //
|
|
// Igor Loi - igor.loi@unibo.it //
|
|
// Andreas Traber - atraber@student.ethz.ch //
|
|
// Sven Stucki - svstucki@student.ethz.ch //
|
|
// //
|
|
// Design Name: Main controller //
|
|
// Project Name: RI5CY //
|
|
// Language: SystemVerilog //
|
|
// //
|
|
// Description: Main CPU controller of the processor //
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
`include "riscv_config.sv"
|
|
|
|
import riscv_defines::*;
|
|
|
|
|
|
module riscv_controller
|
|
#(
|
|
// CONFIG_REGION: RV32E
|
|
`ifdef RV32E
|
|
parameter REG_ADDR_WIDTH = 4
|
|
`else
|
|
parameter REG_ADDR_WIDTH = 5
|
|
`endif // RV32E
|
|
)
|
|
(
|
|
input logic clk,
|
|
input logic rst_n,
|
|
|
|
input logic fetch_enable_i, // Start the decoding
|
|
output logic ctrl_busy_o, // Core is busy processing instructions
|
|
output logic is_decoding_o, // Core is in decoding state
|
|
|
|
// decoder related signals
|
|
output logic deassert_we_o, // deassert write enable for next instruction
|
|
input logic illegal_insn_i, // decoder encountered an invalid instruction
|
|
input logic eret_insn_i, // decoder encountered an eret instruction
|
|
input logic pipe_flush_i, // decoder wants to do a pipe flush
|
|
|
|
input logic rega_used_i, // register A is used
|
|
input logic regb_used_i, // register B is used
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
input logic regc_used_i, // register C is used
|
|
`endif // THREE_PORT_REG_FILE
|
|
|
|
// from IF/ID pipeline
|
|
input logic instr_valid_i, // instruction coming from IF/ID pipeline is valid
|
|
input logic [31:0] instr_rdata_i, // Instruction read from instr memory/cache: (sampled in the if stage)
|
|
|
|
// from prefetcher
|
|
output logic instr_req_o, // Start fetching instructions
|
|
|
|
// to prefetcher
|
|
output logic pc_set_o, // jump to address set by pc_mux
|
|
output logic [2:0] pc_mux_o, // Selector in the Fetch stage to select the rigth PC (normal, jump ...)
|
|
|
|
// LSU
|
|
input logic data_req_ex_i, // data memory access is currently performed in EX stage
|
|
// CONFIG_REGION: ONLY_ALIGNED
|
|
`ifndef ONLY_ALIGNED
|
|
input logic data_misaligned_i,
|
|
`endif // ONLY_ALIGNED
|
|
input logic data_load_event_i,
|
|
|
|
// CONFIG_REGION: MUL_SUPPORT
|
|
`ifdef MUL_SUPPORT
|
|
// from ALU
|
|
input logic mult_multicycle_i, // multiplier is taken multiple cycles and uses op c as storage
|
|
`endif // MUL_SUPPORT
|
|
|
|
// jump/branch signals
|
|
input logic branch_taken_ex_i, // branch taken signal from EX ALU
|
|
input logic [1:0] jump_in_id_i, // jump is being calculated in ALU
|
|
input logic [1:0] jump_in_dec_i, // jump is being calculated in ALU
|
|
|
|
// Exception Controller Signals
|
|
input logic exc_req_i,
|
|
input logic ext_req_i,
|
|
output logic exc_ack_o,
|
|
|
|
output logic exc_save_if_o,
|
|
output logic exc_save_id_o,
|
|
output logic exc_save_takenbranch_o,
|
|
output logic exc_restore_id_o,
|
|
|
|
// Debug Signals
|
|
input logic dbg_req_i, // a trap was hit, so we have to flush EX and WB
|
|
output logic dbg_ack_o, // we stopped and give control to debug now
|
|
|
|
input logic dbg_stall_i, // Pipeline stall is requested
|
|
input logic dbg_jump_req_i, // Change PC to value from debug unit
|
|
|
|
// Forwarding signals from regfile
|
|
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
input logic [(REG_ADDR_WIDTH-1):0] regfile_waddr_ex_i, // FW: write address from EX stage
|
|
`endif // THREE_PORT_REG_FILE
|
|
input logic regfile_we_ex_i, // FW: write enable from EX stage
|
|
input logic [(REG_ADDR_WIDTH-1):0] regfile_waddr_wb_i, // FW: write address from WB stage
|
|
input logic regfile_we_wb_i, // FW: write enable from WB stage
|
|
|
|
input logic [(REG_ADDR_WIDTH-1):0] regfile_alu_waddr_fw_i, // FW: ALU/MUL write address from EX stage
|
|
input logic regfile_alu_we_fw_i, // FW: ALU/MUL write enable from EX stage
|
|
|
|
// forwarding signals
|
|
output logic [1:0] operand_a_fw_mux_sel_o, // regfile ra data selector form ID stage
|
|
output logic [1:0] operand_b_fw_mux_sel_o, // regfile rb data selector form ID stage
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
output logic [1:0] operand_c_fw_mux_sel_o, // regfile rc data selector form ID stage
|
|
`endif // THREE_PORT_REG_FILE
|
|
|
|
// forwarding detection signals
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
input logic reg_d_ex_is_reg_a_i,
|
|
input logic reg_d_ex_is_reg_b_i,
|
|
input logic reg_d_ex_is_reg_c_i,
|
|
input logic reg_d_wb_is_reg_a_i,
|
|
input logic reg_d_wb_is_reg_b_i,
|
|
input logic reg_d_wb_is_reg_c_i,
|
|
input logic reg_d_alu_is_reg_a_i,
|
|
input logic reg_d_alu_is_reg_b_i,
|
|
input logic reg_d_alu_is_reg_c_i,
|
|
`else
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifdef MERGE_ID_EX
|
|
input logic reg_d_wb_is_reg_a_i,
|
|
input logic reg_d_wb_is_reg_b_i,
|
|
`else
|
|
input logic reg_d_wb_is_reg_a_i,
|
|
input logic reg_d_wb_is_reg_b_i,
|
|
input logic reg_d_alu_is_reg_a_i,
|
|
input logic reg_d_alu_is_reg_b_i,
|
|
`endif // MERGE_ID_EX
|
|
`endif // THREE_PORT_REG_FILE
|
|
|
|
|
|
// stall signals
|
|
output logic halt_if_o,
|
|
output logic halt_id_o,
|
|
|
|
// CONFIG_REGION: ONLY_ALIGNED
|
|
`ifndef ONLY_ALIGNED
|
|
output logic misaligned_stall_o,
|
|
`endif // ONLY_ALIGNED
|
|
output logic jr_stall_o,
|
|
output logic load_stall_o,
|
|
|
|
input logic id_ready_i, // ID stage is ready
|
|
|
|
input logic if_valid_i, // IF stage is done
|
|
input logic ex_valid_i, // EX stage is done
|
|
input logic wb_valid_i, // WB stage is done
|
|
|
|
// Performance Counters
|
|
output logic perf_jump_o, // we are executing a jump instruction (j, jr, jal, jalr)
|
|
output logic perf_jr_stall_o, // stall due to jump-register-hazard
|
|
output logic perf_ld_stall_o // stall due to load-use-hazard
|
|
);
|
|
|
|
// FSM state encoding
|
|
// CONFIG_REGION: JUMP_IN_ID
|
|
`ifdef JUMP_IN_ID
|
|
|
|
enum logic [3:0] { RESET, BOOT_SET, SLEEP, FIRST_FETCH,
|
|
DECODE,
|
|
FLUSH_EX, FLUSH_WB,
|
|
DBG_SIGNAL, DBG_SIGNAL_SLEEP, DBG_WAIT, DBG_WAIT_BRANCH, DBG_WAIT_SLEEP } ctrl_fsm_cs, ctrl_fsm_ns;
|
|
|
|
`else
|
|
|
|
enum logic [3:0] { RESET, BOOT_SET, SLEEP, FIRST_FETCH,
|
|
DECODE, WAIT_JUMP_EX,
|
|
FLUSH_EX, FLUSH_WB,
|
|
DBG_SIGNAL, DBG_SIGNAL_SLEEP, DBG_WAIT, DBG_WAIT_BRANCH, DBG_WAIT_SLEEP } ctrl_fsm_cs, ctrl_fsm_ns;
|
|
|
|
`endif
|
|
|
|
logic jump_done, jump_done_q;
|
|
|
|
|
|
`ifndef SYNTHESIS
|
|
// synopsys translate_off
|
|
// make sure we are called later so that we do not generate messages for
|
|
// glitches
|
|
always_ff @(negedge clk)
|
|
begin
|
|
// print warning in case of decoding errors
|
|
if (is_decoding_o && illegal_insn_i) begin
|
|
$display("%t: Illegal instruction (core %0d) at PC 0x%h:", $time, riscv_core.core_id_i,
|
|
riscv_id_stage.pc_id_i);
|
|
end
|
|
end
|
|
// synopsys translate_on
|
|
`endif
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
// ____ ___ ____ _____ ____ ___ _ _ _____ ____ ___ _ _ _____ ____ //
|
|
// / ___/ _ \| _ \| ____| / ___/ _ \| \ | |_ _| _ \ / _ \| | | | | ____| _ \ //
|
|
// | | | | | | |_) | _| | | | | | | \| | | | | |_) | | | | | | | | _| | |_) | //
|
|
// | |__| |_| | _ <| |___ | |__| |_| | |\ | | | | _ <| |_| | |___| |___| |___| _ < //
|
|
// \____\___/|_| \_\_____| \____\___/|_| \_| |_| |_| \_\\___/|_____|_____|_____|_| \_\ //
|
|
// //
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
always_comb
|
|
begin
|
|
// Default values
|
|
instr_req_o = 1'b1;
|
|
|
|
exc_ack_o = 1'b0;
|
|
exc_save_if_o = 1'b0;
|
|
exc_save_id_o = 1'b0;
|
|
exc_save_takenbranch_o = 1'b0;
|
|
exc_restore_id_o = 1'b0;
|
|
|
|
pc_mux_o = PC_BOOT;
|
|
pc_set_o = 1'b0;
|
|
jump_done = jump_done_q;
|
|
|
|
ctrl_fsm_ns = ctrl_fsm_cs;
|
|
|
|
ctrl_busy_o = 1'b1;
|
|
is_decoding_o = 1'b0;
|
|
|
|
halt_if_o = 1'b0;
|
|
halt_id_o = 1'b0;
|
|
dbg_ack_o = 1'b0;
|
|
|
|
unique case (ctrl_fsm_cs)
|
|
// We were just reset, wait for fetch_enable
|
|
RESET:
|
|
begin
|
|
ctrl_busy_o = 1'b0;
|
|
instr_req_o = 1'b0;
|
|
|
|
if (fetch_enable_i == 1'b1)
|
|
ctrl_fsm_ns = BOOT_SET;
|
|
else if (dbg_req_i) begin
|
|
// just go to debug even when we did not yet get a fetch enable
|
|
// this means that the NPC will not be set yet
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
end
|
|
|
|
// copy boot address to instr fetch address
|
|
BOOT_SET:
|
|
begin
|
|
instr_req_o = 1'b1;
|
|
pc_mux_o = PC_BOOT;
|
|
pc_set_o = 1'b1;
|
|
|
|
ctrl_fsm_ns = FIRST_FETCH;
|
|
end
|
|
|
|
// instruction in if_stage is already valid
|
|
SLEEP:
|
|
begin
|
|
// we begin execution when either fetch_enable is high or an
|
|
// interrupt has arrived
|
|
ctrl_busy_o = 1'b0;
|
|
instr_req_o = 1'b0;
|
|
halt_if_o = 1'b1;
|
|
halt_id_o = 1'b1;
|
|
|
|
if (dbg_req_i) begin
|
|
// debug request, now we need to check if we should stay sleeping or
|
|
// go to normal processing later
|
|
if (fetch_enable_i || exc_req_i)
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
else
|
|
ctrl_fsm_ns = DBG_SIGNAL_SLEEP;
|
|
|
|
end else begin
|
|
// no debug request incoming, normal execution flow
|
|
if (fetch_enable_i || exc_req_i)
|
|
begin
|
|
ctrl_fsm_ns = FIRST_FETCH;
|
|
end
|
|
end
|
|
end
|
|
|
|
FIRST_FETCH:
|
|
begin
|
|
// Stall because of IF miss
|
|
if ((id_ready_i == 1'b1) && (dbg_stall_i == 1'b0))
|
|
begin
|
|
ctrl_fsm_ns = DECODE;
|
|
end
|
|
|
|
// handle exceptions
|
|
if (exc_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
|
|
// TODO: This assumes that the pipeline is always flushed before
|
|
// going to sleep.
|
|
exc_save_if_o = 1'b1;
|
|
end
|
|
end
|
|
|
|
DECODE:
|
|
begin
|
|
is_decoding_o = 1'b0;
|
|
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifdef MERGE_ID_EX
|
|
// decode and execute instructions only if the current conditional
|
|
// branch in the EX stage is either not taken, or there is no
|
|
// conditional branch in the EX stage
|
|
|
|
if (instr_valid_i)
|
|
begin // now analyze the current instruction in the ID stage
|
|
is_decoding_o = 1'b1;
|
|
|
|
// handle conditional branches
|
|
if (branch_taken_ex_i & id_ready_i) begin
|
|
// there is a branch in the EX stage that is taken
|
|
pc_mux_o = PC_BRANCH;
|
|
pc_set_o = 1'b1;
|
|
|
|
// if we want to debug, flush the pipeline
|
|
// the current_pc_if will take the value of the next instruction to
|
|
// be executed (NPC)
|
|
if (ext_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_takenbranch_o = 1'b1;
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
if (dbg_req_i)
|
|
begin
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
end
|
|
|
|
// handle unconditional jumps
|
|
// we can jump directly since we know the address already
|
|
// we don't need to worry about conditional branches here as they
|
|
// will be evaluated in the EX stage
|
|
else if ((jump_in_dec_i == BRANCH_JALR || jump_in_dec_i == BRANCH_JAL) & id_ready_i) begin
|
|
pc_mux_o = PC_JUMP;
|
|
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump target
|
|
|
|
end else begin
|
|
// handle exceptions
|
|
if (exc_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_id_o = 1'b1;
|
|
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
end
|
|
|
|
if (eret_insn_i) begin
|
|
pc_mux_o = PC_ERET;
|
|
exc_restore_id_o = 1'b1;
|
|
|
|
if ((~jump_done_q)) begin
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
end
|
|
end
|
|
|
|
// handle WFI instruction, flush pipeline and (potentially) go to
|
|
// sleep
|
|
// also handles eret when the core should go back to sleep
|
|
if (pipe_flush_i || (eret_insn_i && (~fetch_enable_i)))
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
halt_id_o = 1'b1;
|
|
|
|
ctrl_fsm_ns = FLUSH_WB;
|
|
end
|
|
else if (dbg_req_i)
|
|
begin
|
|
// take care of debug
|
|
// branch conditional will be handled in next state
|
|
// halt pipeline immediately
|
|
halt_if_o = 1'b1;
|
|
|
|
// make sure the current instruction has been executed
|
|
// before changing state to non-decode
|
|
if (id_ready_i) begin
|
|
if ((jump_in_id_i == BRANCH_COND) & branch_taken_ex_i & id_ready_i)
|
|
begin
|
|
pc_mux_o = PC_BRANCH;
|
|
pc_set_o = 1'b1;
|
|
end
|
|
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
else if (~instr_valid_i)
|
|
begin
|
|
if (ext_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_if_o = 1'b1;
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
end
|
|
|
|
|
|
|
|
`else // MERGE_ID_EX
|
|
|
|
// decode and execute instructions only if the current conditional
|
|
// branch in the EX stage is either not taken, or there is no
|
|
// conditional branch in the EX stage
|
|
// CONFIG_REGION: SPLITTED_ADDER
|
|
`ifdef SPLITTED_ADDER
|
|
if (instr_valid_i && (~branch_taken_ex_i) && ex_valid_i)
|
|
`else
|
|
if (instr_valid_i && (~branch_taken_ex_i))
|
|
`endif
|
|
begin // now analyze the current instruction in the ID stage
|
|
is_decoding_o = 1'b1;
|
|
|
|
// handle unconditional jumps
|
|
// we can jump directly since we know the address already
|
|
// we don't need to worry about conditional branches here as they
|
|
// will be evaluated in the EX stage
|
|
if (jump_in_dec_i == BRANCH_JALR || jump_in_dec_i == BRANCH_JAL) begin
|
|
// CONFIG_REGION: JUMP_IN_ID
|
|
`ifdef JUMP_IN_ID
|
|
pc_mux_o = PC_JUMP;
|
|
|
|
// if there is a jr stall, wait for it to be gone
|
|
if ((~jr_stall_o) && (~jump_done_q)) begin
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
end
|
|
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump target
|
|
|
|
`else
|
|
|
|
halt_if_o = 1'b1;
|
|
|
|
// if there is a jr stall, wait for it to be gone
|
|
if (id_ready_i && (~jr_stall_o) && (~jump_done_q)) begin
|
|
ctrl_fsm_ns = WAIT_JUMP_EX;
|
|
end
|
|
|
|
`endif
|
|
end else begin
|
|
// handle exceptions
|
|
if (exc_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_id_o = 1'b1;
|
|
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
end
|
|
|
|
if (eret_insn_i) begin
|
|
pc_mux_o = PC_ERET;
|
|
exc_restore_id_o = 1'b1;
|
|
|
|
if ((~jump_done_q)) begin
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
end
|
|
end
|
|
|
|
// handle WFI instruction, flush pipeline and (potentially) go to
|
|
// sleep
|
|
// also handles eret when the core should go back to sleep
|
|
if (pipe_flush_i || (eret_insn_i && (~fetch_enable_i)))
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
halt_id_o = 1'b1;
|
|
|
|
ctrl_fsm_ns = FLUSH_EX;
|
|
end
|
|
else if (dbg_req_i)
|
|
begin
|
|
// take care of debug
|
|
// branch conditional will be handled in next state
|
|
// halt pipeline immediately
|
|
halt_if_o = 1'b1;
|
|
|
|
// make sure the current instruction has been executed
|
|
// before changing state to non-decode
|
|
if (id_ready_i) begin
|
|
if (jump_in_id_i == BRANCH_COND)
|
|
ctrl_fsm_ns = DBG_WAIT_BRANCH;
|
|
else
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end else if (data_load_event_i) begin
|
|
// special case for p.elw
|
|
// If there was a load event (which means p.elw), we go to debug
|
|
// even though we are still blocked
|
|
// we don't have to distuinguish between branch and non-branch,
|
|
// since the p.elw sits in the EX stage
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (~instr_valid_i && (~branch_taken_ex_i))
|
|
begin
|
|
if (ext_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_if_o = 1'b1;
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
end
|
|
|
|
// TODO: make sure this is not done multiple times in a row (RI5CY)!!!
|
|
// maybe with an assertion?
|
|
// handle conditional branches
|
|
// CONFIG_REGION: SPLITTED_ADDER
|
|
`ifdef SPLITTED_ADDER
|
|
if (branch_taken_ex_i & ex_valid_i) begin
|
|
`else
|
|
// CONFIG_REGION: NO_JUMP_ADDER
|
|
`ifdef NO_JUMP_ADDER
|
|
if (branch_taken_ex_i & ex_valid_i) begin
|
|
`else
|
|
if (branch_taken_ex_i) begin
|
|
`endif
|
|
`endif
|
|
// there is a branch in the EX stage that is taken
|
|
pc_mux_o = PC_BRANCH;
|
|
pc_set_o = 1'b1;
|
|
|
|
|
|
is_decoding_o = 1'b0; // we are not decoding the current instruction in the ID stage
|
|
|
|
// if we want to debug, flush the pipeline
|
|
// the current_pc_if will take the value of the next instruction to
|
|
// be executed (NPC)
|
|
if (ext_req_i) begin
|
|
pc_mux_o = PC_EXCEPTION;
|
|
pc_set_o = 1'b1;
|
|
exc_ack_o = 1'b1;
|
|
halt_id_o = 1'b1; // we don't want to propagate this instruction to EX
|
|
exc_save_takenbranch_o = 1'b1;
|
|
// we don't have to change our current state here as the prefetch
|
|
// buffer is automatically invalidated, thus the next instruction
|
|
// that is served to the ID stage is the one of the jump to the
|
|
// exception handler
|
|
end
|
|
if (dbg_req_i)
|
|
begin
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
end
|
|
|
|
`endif // MERGE_ID_EX
|
|
end
|
|
|
|
// CONFIG_REGION: JUMP_IN_ID
|
|
`ifndef JUMP_IN_ID
|
|
|
|
// a jump was in ID and now has to be applied to IF from EX
|
|
WAIT_JUMP_EX:
|
|
begin
|
|
// CONFIG_REGION: SPLITTED_ADDER
|
|
`ifdef SPLITTED_ADDER
|
|
if (ex_valid_i) begin
|
|
pc_mux_o = PC_JUMP;
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
|
|
ctrl_fsm_ns = DECODE;
|
|
end
|
|
else
|
|
halt_if_o = 1'b1;
|
|
`else
|
|
pc_mux_o = PC_JUMP;
|
|
pc_set_o = 1'b1;
|
|
jump_done = 1'b1;
|
|
|
|
ctrl_fsm_ns = DECODE;
|
|
`endif
|
|
end
|
|
|
|
`endif // JUMP_IN_ID
|
|
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifdef MERGE_ID_EX
|
|
// a branch was in ID when a debug trap is hit
|
|
DBG_WAIT_BRANCH:
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
|
|
if (branch_taken_ex_i) begin
|
|
// there is a branch in the EX stage that is taken
|
|
pc_mux_o = PC_BRANCH;
|
|
pc_set_o = 1'b1;
|
|
end
|
|
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end
|
|
`endif
|
|
|
|
// now we can signal to the debugger that our pipeline is empty and it
|
|
// can examine our current state
|
|
DBG_SIGNAL:
|
|
begin
|
|
dbg_ack_o = 1'b1;
|
|
halt_if_o = 1'b1;
|
|
|
|
ctrl_fsm_ns = DBG_WAIT;
|
|
end
|
|
|
|
DBG_SIGNAL_SLEEP:
|
|
begin
|
|
dbg_ack_o = 1'b1;
|
|
halt_if_o = 1'b1;
|
|
|
|
ctrl_fsm_ns = DBG_WAIT_SLEEP;
|
|
end
|
|
|
|
// The Debugger is active in this state
|
|
// we wait until it is done and go back to SLEEP
|
|
DBG_WAIT_SLEEP:
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
|
|
if (dbg_jump_req_i) begin
|
|
pc_mux_o = PC_DBG_NPC;
|
|
pc_set_o = 1'b1;
|
|
ctrl_fsm_ns = DBG_WAIT;
|
|
end
|
|
|
|
if (dbg_stall_i == 1'b0) begin
|
|
ctrl_fsm_ns = SLEEP;
|
|
end
|
|
end
|
|
|
|
// The Debugger is active in this state
|
|
// we wait until it is done and go back to DECODE
|
|
DBG_WAIT:
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
|
|
if (dbg_jump_req_i) begin
|
|
pc_mux_o = PC_DBG_NPC;
|
|
pc_set_o = 1'b1;
|
|
ctrl_fsm_ns = DBG_WAIT;
|
|
end
|
|
|
|
if (dbg_stall_i == 1'b0) begin
|
|
ctrl_fsm_ns = DECODE;
|
|
end
|
|
end
|
|
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifdef MERGE_ID_EX
|
|
// flush the pipeline, insert NOP into EX stage
|
|
FLUSH_EX:
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
halt_id_o = 1'b1;
|
|
|
|
if (ex_valid_i)
|
|
ctrl_fsm_ns = FLUSH_WB;
|
|
end
|
|
`endif
|
|
|
|
// flush the pipeline, insert NOP into EX and WB stage
|
|
FLUSH_WB:
|
|
begin
|
|
halt_if_o = 1'b1;
|
|
halt_id_o = 1'b1;
|
|
|
|
if(fetch_enable_i) begin
|
|
if (dbg_req_i) begin
|
|
ctrl_fsm_ns = DBG_SIGNAL;
|
|
end else begin
|
|
ctrl_fsm_ns = DECODE;
|
|
halt_if_o = 1'b0;
|
|
end
|
|
end else begin
|
|
if (dbg_req_i) begin
|
|
ctrl_fsm_ns = DBG_SIGNAL_SLEEP;
|
|
end else begin
|
|
ctrl_fsm_ns = SLEEP;
|
|
end
|
|
end
|
|
end
|
|
|
|
default: begin
|
|
instr_req_o = 1'b0;
|
|
ctrl_fsm_ns = RESET;
|
|
end
|
|
endcase
|
|
end
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
// ____ _ _ _ ____ _ _ //
|
|
// / ___|| |_ __ _| | | / ___|___ _ __ | |_ _ __ ___ | | //
|
|
// \___ \| __/ _` | | | | | / _ \| '_ \| __| '__/ _ \| | //
|
|
// ___) | || (_| | | | | |__| (_) | | | | |_| | | (_) | | //
|
|
// |____/ \__\__,_|_|_| \____\___/|_| |_|\__|_| \___/|_| //
|
|
// //
|
|
/////////////////////////////////////////////////////////////
|
|
always_comb
|
|
begin
|
|
load_stall_o = 1'b0;
|
|
jr_stall_o = 1'b0;
|
|
deassert_we_o = 1'b0;
|
|
|
|
// deassert WE when the core is not decoding instructions
|
|
if (~is_decoding_o)
|
|
deassert_we_o = 1'b1;
|
|
|
|
// deassert WE in case of illegal instruction
|
|
if (illegal_insn_i)
|
|
deassert_we_o = 1'b1;
|
|
|
|
// Stall because of load operation
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
if ((data_req_ex_i == 1'b1) && (regfile_we_ex_i == 1'b1) &&
|
|
((reg_d_ex_is_reg_a_i == 1'b1) || (reg_d_ex_is_reg_b_i == 1'b1) || (reg_d_ex_is_reg_c_i == 1'b1)) )
|
|
begin
|
|
deassert_we_o = 1'b1;
|
|
load_stall_o = 1'b1;
|
|
end
|
|
`else
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifndef MERGE_ID_EX
|
|
if ((data_req_ex_i == 1'b1) && (regfile_we_ex_i == 1'b1) &&
|
|
((reg_d_alu_is_reg_a_i == 1'b1) || (reg_d_alu_is_reg_b_i == 1'b1)) )
|
|
begin
|
|
deassert_we_o = 1'b1;
|
|
load_stall_o = 1'b1;
|
|
end
|
|
`endif // MERGE_ID_EX
|
|
`endif // THREE_PORT_REG_FILE
|
|
|
|
|
|
// Stall because of jr path
|
|
// - always stall if a result is to be forwarded to the PC
|
|
// we don't care about in which state the ctrl_fsm is as we deassert_we
|
|
// anyway when we are not in DECODE
|
|
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
if ((jump_in_dec_i == BRANCH_JALR) &&
|
|
(((regfile_we_wb_i == 1'b1) && (reg_d_wb_is_reg_a_i == 1'b1)) ||
|
|
((regfile_we_ex_i == 1'b1) && (reg_d_ex_is_reg_a_i == 1'b1)) ||
|
|
((regfile_alu_we_fw_i == 1'b1) && (reg_d_alu_is_reg_a_i == 1'b1))) )
|
|
begin
|
|
jr_stall_o = 1'b1;
|
|
deassert_we_o = 1'b1;
|
|
end
|
|
`else
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifndef MERGE_ID_EX
|
|
if ((jump_in_dec_i == BRANCH_JALR) &&
|
|
(((regfile_we_wb_i == 1'b1) && (reg_d_wb_is_reg_a_i == 1'b1)) ||
|
|
((regfile_we_ex_i == 1'b1) && (reg_d_alu_is_reg_a_i == 1'b1)) ||
|
|
((regfile_alu_we_fw_i == 1'b1) && (reg_d_alu_is_reg_a_i == 1'b1))) )
|
|
begin
|
|
jr_stall_o = 1'b1;
|
|
deassert_we_o = 1'b1;
|
|
end
|
|
`endif // MERGE_ID_EX
|
|
`endif // THREE_PORT_REG_FILE
|
|
end
|
|
|
|
// CONFIG_REGION: ONLY_ALIGNED
|
|
`ifndef ONLY_ALIGNED
|
|
// stall because of misaligned data access
|
|
assign misaligned_stall_o = data_misaligned_i;
|
|
`endif // ONLY_ALIGNED
|
|
|
|
|
|
// Forwarding control unit
|
|
always_comb
|
|
begin
|
|
// default assignements
|
|
operand_a_fw_mux_sel_o = SEL_REGFILE;
|
|
operand_b_fw_mux_sel_o = SEL_REGFILE;
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
operand_c_fw_mux_sel_o = SEL_REGFILE;
|
|
`endif // THREE_PORT_REG_FILE:
|
|
|
|
// Forwarding WB -> ID
|
|
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
if (regfile_we_wb_i == 1'b1)
|
|
begin
|
|
if (reg_d_wb_is_reg_a_i == 1'b1)
|
|
operand_a_fw_mux_sel_o = SEL_FW_WB;
|
|
if (reg_d_wb_is_reg_b_i == 1'b1)
|
|
operand_b_fw_mux_sel_o = SEL_FW_WB;
|
|
if (reg_d_wb_is_reg_c_i == 1'b1)
|
|
operand_c_fw_mux_sel_o = SEL_FW_WB;
|
|
end
|
|
`else
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifndef MERGE_ID_EX
|
|
if (regfile_we_wb_i == 1'b1)
|
|
begin
|
|
if (reg_d_wb_is_reg_a_i == 1'b1)
|
|
operand_a_fw_mux_sel_o = SEL_FW_WB;
|
|
if (reg_d_wb_is_reg_b_i == 1'b1)
|
|
operand_b_fw_mux_sel_o = SEL_FW_WB;
|
|
end
|
|
`endif
|
|
`endif // THREE_PORT_REG_FILE
|
|
|
|
// Forwarding EX -> ID
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifndef MERGE_ID_EX
|
|
if (regfile_alu_we_fw_i == 1'b1)
|
|
begin
|
|
if (reg_d_alu_is_reg_a_i == 1'b1)
|
|
operand_a_fw_mux_sel_o = SEL_FW_EX;
|
|
if (reg_d_alu_is_reg_b_i == 1'b1)
|
|
operand_b_fw_mux_sel_o = SEL_FW_EX;
|
|
// CONFIG_REGION: THREE_PORT_REG_FILE
|
|
`ifdef THREE_PORT_REG_FILE
|
|
if (reg_d_alu_is_reg_c_i == 1'b1)
|
|
operand_c_fw_mux_sel_o = SEL_FW_EX;
|
|
`endif // THREE_PORT_REG_FILE
|
|
end
|
|
`endif // MERGE_ID_EX
|
|
|
|
// CONFIG_REGION: ONLY_ALIGNED
|
|
`ifndef ONLY_ALIGNED
|
|
// for misaligned memory accesses
|
|
if (data_misaligned_i)
|
|
begin
|
|
operand_a_fw_mux_sel_o = SEL_FW_EX;
|
|
operand_b_fw_mux_sel_o = SEL_REGFILE;
|
|
end
|
|
`endif // ONLY_ALIGNED
|
|
|
|
// CONFIG_REGION: MUL_SUPPORT
|
|
`ifdef MUL_SUPPORT
|
|
else if (mult_multicycle_i) begin
|
|
operand_c_fw_mux_sel_o = SEL_FW_EX;
|
|
end
|
|
`endif
|
|
end
|
|
|
|
// update registers
|
|
always_ff @(posedge clk , negedge rst_n)
|
|
begin : UPDATE_REGS
|
|
if ( rst_n == 1'b0 )
|
|
begin
|
|
ctrl_fsm_cs <= RESET;
|
|
jump_done_q <= 1'b0;
|
|
end
|
|
else
|
|
begin
|
|
ctrl_fsm_cs <= ctrl_fsm_ns;
|
|
|
|
// clear when id is valid (no instruction incoming)
|
|
jump_done_q <= jump_done & (~id_ready_i);
|
|
end
|
|
end
|
|
|
|
// Performance Counters
|
|
assign perf_jump_o = (jump_in_id_i == BRANCH_JAL || jump_in_id_i == BRANCH_JALR);
|
|
assign perf_jr_stall_o = jr_stall_o;
|
|
assign perf_ld_stall_o = load_stall_o;
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Assertions
|
|
//----------------------------------------------------------------------------
|
|
|
|
// CONFIG_REGION: MERGE_ID_EX
|
|
`ifdef MERGE_ID_EX
|
|
// make sure that taken branches do not happen back-to-back, as this is not
|
|
// possible without branch prediction in the IF stage
|
|
assert property (
|
|
@(posedge clk) (branch_taken_ex_i) |=> (~branch_taken_ex_i) ) else $warning("Two branches back-to-back are taken");
|
|
`endif // MERGE_ID_EX
|
|
assert property (
|
|
@(posedge clk) (~(dbg_req_i & ext_req_i)) ) else $warning("Both dbg_req_i and ext_req_i are active");
|
|
|
|
endmodule // controller
|