use crate::exec::decode::{DecodedInst, decode_inst};
use crate::exec::inst::Inst;
use crate::isa::instruction::InstructionBits;
use crate::isa::reg::RegIdx;
use crate::isa::rvv::VectorConfig;
use crate::uarch::bpred::ControlInst;
use crate::uarch::ctx::StageCtx;
use crate::uarch::pipeline::exception::ExceptionStage;
use crate::uarch::pipeline::latches::{IdExEntry, IfIdEntry};
pub fn decode_stage(
state: &mut StageCtx<'_>,
input: &mut Vec<IfIdEntry>,
output: &mut Vec<IdExEntry>,
has_register_renaming: bool,
vector: VectorConfig,
) -> DecodeOutcome {
let mut consumed_count = 0;
let mut ended_at_vsetvl = false;
let mut redirect = None;
let mut bundle_writes: Vec<(RegIdx, bool)> = Vec::with_capacity(state.config.pipeline.width);
for if_entry in input.iter().take(state.config.pipeline.decode_width()) {
if let Some(trap) = &if_entry.trap {
output.push(IdExEntry {
inst: Inst {
pc: if_entry.pc,
bits: if_entry.inst,
size: if_entry.inst_size,
..Default::default()
},
trap: Some(trap.clone()),
exception_stage: if_entry.exception_stage,
..Default::default()
});
consumed_count += 1;
break;
}
let inst = if_entry.inst;
let DecodedInst { fields: d, ctrl, trap } = decode_inst(inst, if_entry.pc, vector.vtype);
let ex_stage = trap.as_ref().map(|_| ExceptionStage::Decode);
let rs3_idx = inst.rs3();
if !has_register_renaming {
let hazard = ((!d.rs1.is_zero() || ctrl.rs1_fp)
&& bundle_writes.contains(&(d.rs1, ctrl.rs1_fp)))
|| ((!d.rs2.is_zero() || ctrl.rs2_fp)
&& bundle_writes.contains(&(d.rs2, ctrl.rs2_fp)))
|| (ctrl.rs3_fp && bundle_writes.contains(&(rs3_idx, true)));
if hazard {
break;
}
}
if ctrl.reg_write && !d.rd.is_zero() {
bundle_writes.push((d.rd, false));
}
if ctrl.fp_reg_write {
bundle_writes.push((d.rd, true));
}
let rv1 = if ctrl.rs1_fp {
state.hart().regs.read_f(d.rs1)
} else {
state.hart().regs.read(d.rs1)
};
let rv2 = if ctrl.rs2_fp {
state.hart().regs.read_f(d.rs2)
} else {
state.hart().regs.read(d.rs2)
};
let rv3 = if ctrl.rs3_fp { state.hart().regs.read_f(rs3_idx) } else { 0 };
let has_trap = trap.is_some();
let mut decoded = IdExEntry {
inst: Inst {
pc: if_entry.pc,
bits: inst,
size: if_entry.inst_size,
rs1: d.rs1,
rs2: d.rs2,
rs3: rs3_idx,
rd: d.rd,
imm: d.imm,
rv1,
rv2,
rv3,
ctrl,
},
trap,
exception_stage: ex_stage,
pred_taken: if_entry.pred_taken,
pred_target: if_entry.pred_target,
seq: if_entry.seq,
};
if !has_trap {
redirect = check_fetch_prediction(state, &mut decoded);
}
output.push(decoded);
consumed_count += 1;
if has_trap || redirect.is_some() {
break;
}
if ctrl.vec_op.is_config() {
ended_at_vsetvl = true;
break;
}
}
let _ = input.drain(..consumed_count);
DecodeOutcome { ended_at_vsetvl, redirect }
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DecodeOutcome {
pub ended_at_vsetvl: bool,
pub redirect: Option<u64>,
}
fn check_fetch_prediction(state: &mut StageCtx<'_>, entry: &mut IdExEntry) -> Option<u64> {
let size = entry.inst.size.as_u64();
let fallthrough = entry.inst.pc.wrapping_add(size);
let fetched_next = if entry.pred_taken { entry.pred_target } else { fallthrough };
let control = ControlInst::from_encoding(entry.inst.pc, size, entry.inst.bits);
let predictor = &mut state.core_mut().branch_predictor;
let predicted_by_fetch = predictor.is_predicted(entry.seq);
let redirect = match (control, predicted_by_fetch) {
(None, false) => None,
(None, true) => {
predictor.forget(entry.seq, entry.inst.pc);
entry.pred_taken = false;
entry.pred_target = 0;
Some(fallthrough)
}
(Some(control), true) => {
let direct_target = match control {
ControlInst::Branch { target } | ControlInst::Jump { target, .. } => Some(target),
ControlInst::IndirectJump { .. } => None,
};
let must_take = matches!(control, ControlInst::Jump { .. });
let fixed_target = direct_target.filter(|&target| {
(entry.pred_taken && entry.pred_target != target)
|| (must_take && !entry.pred_taken)
});
fixed_target.inspect(|&target| {
predictor.correct_target(entry.seq, target);
entry.pred_taken = true;
entry.pred_target = target;
})
}
(Some(control), false) => {
let (target, squashed_younger) = predictor.discover(entry.seq, entry.inst.pc, control);
entry.pred_taken = target.is_some();
entry.pred_target = target.unwrap_or(0);
let next = target.unwrap_or(fallthrough);
(squashed_younger || next != fetched_next).then_some(next)
}
};
if redirect.is_some() {
state.counter(state.core().stat_paths.bp.decode_redirects).inc();
}
redirect
}