cpu/crates/cpu/src/rename_execute_retire.rs

2025 lines
75 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! Rename/Execute/Retire Control System
//! [#8](https://git.libre-chip.org/libre-chip/grant-tracking/issues/8)
use crate::{
config::{
CpuConfig, CpuConfig2PowOutRegNumWidth, CpuConfigFetchWidth, CpuConfigRobSize,
CpuConfigUnitCount, PhantomConstCpuConfig, TwiceCpuConfigFetchWidth,
},
instruction::{
COMMON_MOP_SRC_LEN, L2RegNum, L2RegisterFileMOp, MOp, MOpDestReg, MOpRegNum, MOpTrait,
PRegNum, ReadL2RegMOp, UnitNum, UnitOutRegNum, WriteL2RegMOp,
},
next_pc::{CallStackOp, SimValueDefault},
register::PRegValue,
rename_execute_retire::{
rename_table::{RenameTable, RenameTableDebugState, RenameTableEntry, RenameTableUpdate},
reorder_buffer::{ReorderBuffer, ReorderBufferDebugState, RobEntries, RobEntry},
to_unit_interfaces::ExecuteToUnitInterfaces,
},
unit::{UnitKind, UnitMOp},
util::{LFSR31, array_vec::ArrayVec},
};
use fayalite::{
int::UIntInRangeInclusiveType,
prelude::*,
ty::{OpaqueSimValue, SimValueDebug, StaticType},
util::ready_valid::ReadyValid,
};
use std::{collections::VecDeque, fmt, num::NonZero};
mod rename_table;
mod reorder_buffer;
pub mod to_unit_interfaces;
pub const MOP_ID_WIDTH: usize = 16;
#[hdl]
pub type MOpId = UInt<{ MOP_ID_WIDTH }>;
#[hdl(custom_debug(sim))]
/// A &micro;Op along with the state needed for this instance of the &micro;Op.
pub struct MOpInstance<MOp> {
pub fetch_block_id: UInt<8>,
pub id: MOpId,
pub pc: UInt<64>,
/// initialized to 0 by decoder, overwritten by `next_pc()`
pub predicted_next_pc: UInt<64>,
pub size_in_bytes: UInt<4>,
/// `true` if this &micro;Op is the first &micro;Op in the ISA-level instruction.
/// In general, a single &micro;Op can't be canceled by itself,
/// it needs to be canceled along with all other &micro;Ops that
/// come from the same ISA-level instruction.
pub is_first_mop_in_insn: Bool,
/// `true` if this &micro;Op is the last &micro;Op in the ISA-level instruction.
/// In general, a single &micro;Op can't be canceled by itself,
/// it needs to be canceled along with all other &micro;Ops that
/// come from the same ISA-level instruction.
pub is_last_mop_in_insn: Bool,
pub mop: TraceAsString<MOp>,
}
impl<MOp: Type> SimValueDebug for MOpInstance<MOp> {
#[hdl]
fn sim_value_debug(
value: &<Self as Type>::SimValue,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
#[hdl(sim)]
let Self {
fetch_block_id,
id,
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop,
} = value;
write!(
f,
"fid={fetch_block_id:?} id={id:?} pc={pc:?} pn_pc={predicted_next_pc:?} sz={size_in_bytes:?} first={is_first_mop_in_insn} last={is_last_mop_in_insn}: {mop:?}"
)
}
}
#[hdl(no_static)]
/// TODO: merge with [`crate::next_pc::PostDecodeOutputInterface`]
pub struct PostDecodeOutputInterface<C: PhantomConstGet<CpuConfig>> {
pub insns: ArrayVec<MOpInstance<MOp>, CpuConfigFetchWidth<C>>,
#[hdl(flip)]
pub ready: UIntInRangeInclusiveType<ConstUsize<0>, CpuConfigFetchWidth<C>>,
/// tells the rename/execute/retire circuit to cancel all non-retired instructions
pub cancel: ReadyValid<()>,
pub config: C,
}
#[hdl(no_static)]
pub struct NextPcPredictorOp<C: PhantomConstGet<CpuConfig>> {
pub call_stack_op: CallStackOp,
/// should be `HdlSome(taken)` for any conditional control-flow instruction
/// with an immediate target that can be predicted as taken/not-taken (branch/call/return).
pub cond_br_taken: HdlOption<Bool>,
pub config: C,
}
#[hdl(no_static)]
/// TODO: merge with [`crate::next_pc::RetireToNextPcInterfaceInner`]
pub enum RetireToNextPcInterfaceInner<C: PhantomConstGet<CpuConfig>> {
CancelAndStartAt(UInt<64>),
RetiredInstructions(ArrayVec<NextPcPredictorOp<C>, CpuConfigFetchWidth<C>>),
}
#[hdl(no_static)]
/// handles updating speculative branch predictor state (e.g. branch histories)
/// when instructions retire, as well as updating state when a
/// branch instruction is mis-speculated.
pub struct RetireToNextPcInterface<C: PhantomConstGet<CpuConfig>> {
pub inner: ReadyValid<RetireToNextPcInterfaceInner<C>>,
/// only for debugging
pub next_insns: HdlOption<ArrayVec<MOpInstance<MOp>, CpuConfigRobSize<C>>>,
}
#[hdl]
pub type RenamedMOp<C: PhantomConstGet<CpuConfig>> =
crate::instruction::RenamedMOp<PRegNum<C>, PRegNum<C>>;
/// Enqueues happen in program order, they are not re-ordered by out-of-order execution.
/// the whole `MOpInstance` is sent again in [`UnitInputsReady`] so Units can just ignore all
/// [`UnitEnqueue`] messages if they don't need to keep track of program order -- so, pure computation
/// instructions.
/// Loads/Stores need to keep track of program order to ensure they properly handle memory dependencies.
#[hdl(no_static)]
pub struct UnitEnqueue<C: PhantomConstGet<CpuConfig>> {
pub mop: MOpInstance<RenamedMOp<C>>,
pub config: C,
}
#[hdl(no_static)]
pub struct UnitInputsReady<C: PhantomConstGet<CpuConfig>> {
/// the whole `MOpInstance` is sent again so Units can just ignore all [`UnitEnqueue`] messages if desired.
pub mop: MOpInstance<RenamedMOp<C>>,
pub src_values: Array<TraceAsString<PRegValue>, { COMMON_MOP_SRC_LEN }>,
pub config: C,
}
#[hdl(no_static)]
pub struct UnitOutputReady<C: PhantomConstGet<CpuConfig>> {
pub id: MOpId,
pub dest_value: TraceAsString<PRegValue>,
pub predictor_op: NextPcPredictorOp<C>,
}
#[hdl(no_static)]
pub struct UnitCausedCancel<C: PhantomConstGet<CpuConfig>> {
pub start_at_pc: UInt<64>,
/// `true` if this instruction should be retired and then cause a cancel
/// (e.g. a branch ran successfully but the next pc was mispredicted so the following instructions
/// needs to be canceled).
/// `false` if this instruction should be canceled without retiring it
/// (e.g. a load ran before a store it should have run after so it needs to retry after the memory
/// is in the right state).
pub cancel_after_retire: Bool,
pub config: C,
}
#[hdl(no_static)]
pub struct UnitFinishCauseCancel<C: PhantomConstGet<CpuConfig>> {
pub id: MOpId,
pub caused_cancel: HdlOption<UnitCausedCancel<C>>,
pub config: C,
}
#[hdl(no_static)]
pub struct UnitMOpIsNoLongerSpeculative<C: PhantomConstGet<CpuConfig>> {
pub id: MOpId,
pub config: C,
}
#[hdl(no_static)]
pub struct UnitMOpCantCauseCancel<C: PhantomConstGet<CpuConfig>> {
pub id: MOpId,
pub config: C,
}
/// Interface from the Rename/Execute/Retire control logic to a single Unit.
///
/// ## State diagram for a single &micro;Op in a Unit
/// Notes:
/// * The diagram ignores `cancel_all`.
/// * Multiple state transitions can happen in a single clock cycle.
/// * Any state marked "Can cause cancel", can immediately finish with [`Self::finish_cause_cancel`] where:
/// * [`UnitCausedCancel::cancel_after_retire`] must be `false` unless there's a "Finish" edge from this state.
/// * [`UnitFinishCauseCancel::caused_cancel`] must be `HdlSome` unless there's a "Finish" edge from this state.
#[doc = simple_mermaid::mermaid!("rename_execute_retire/unit.mermaid")]
#[hdl(no_static)]
pub struct ExecuteToUnitInterface<C: PhantomConstGet<CpuConfig>> {
/// Enqueues happen in program order, they are not re-ordered by out-of-order execution.
pub enqueue: ReadyValid<UnitEnqueue<C>>,
/// if [`Self::unit_outputs_ready`] is `false`, then this is always [`HdlNone`]
pub inputs_ready: HdlOption<UnitInputsReady<C>>,
/// if [`Self::unit_outputs_ready`] is `false`, then this is always [`HdlNone`]
pub is_no_longer_speculative: HdlOption<UnitMOpIsNoLongerSpeculative<C>>,
/// this uses [`Self::unit_outputs_ready`] as a shared ready flag
#[hdl(flip)]
pub cant_cause_cancel: HdlOption<UnitMOpCantCauseCancel<C>>,
/// this uses [`Self::unit_outputs_ready`] as a shared ready flag
#[hdl(flip)]
pub output_ready: HdlOption<UnitOutputReady<C>>,
/// this uses [`Self::unit_outputs_ready`] as a shared ready flag
#[hdl(flip)]
pub finish_cause_cancel: HdlOption<UnitFinishCauseCancel<C>>,
/// ready flag for [`Self::cant_cause_cancel`], [`Self::output_ready`], and [`Self::finish_cause_cancel`]
pub unit_outputs_ready: Bool,
pub cancel_all: ReadyValid<()>,
pub config: C,
}
fn zeroed<T: Type>(ty: T) -> SimValue<T> {
SimValue::from_opaque(
ty,
OpaqueSimValue::from_bits(UInt::new(ty.canonical().bit_width()).zero()),
)
}
impl<C: PhantomConstCpuConfig> SimValueDefault for RenameExecuteRetireDebugState<C> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self {
rename_delayed,
rename_table,
retire_rename_table,
rob,
next_pc_canceling,
unit_canceling,
l1_reg_file,
lfsr: _,
per_insn_timeline,
} = self;
let empty_string = SimOnlyValue::new(String::new());
#[hdl(sim)]
Self {
rename_delayed: zeroed(rename_delayed),
rename_table: zeroed(rename_table),
retire_rename_table: zeroed(retire_rename_table),
rob: rob.sim_value_default(),
next_pc_canceling: zeroed(next_pc_canceling),
unit_canceling: zeroed(unit_canceling),
l1_reg_file: zeroed(l1_reg_file),
lfsr: LFSR31::new(),
per_insn_timeline: SimValue::from_array_elements(
per_insn_timeline,
(0..per_insn_timeline.len()).map(|_| empty_string.clone()),
),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
enum MOpInUnitState {
#[default]
NotYetEnqueued,
InputsNotReadySpeculative {
can_cause_cancel: bool,
},
InputsReady {
speculative: bool,
can_cause_cancel: bool,
},
OutputReady {
speculative: bool,
can_cause_cancel: bool,
},
FinishedAndOrCausedCancel,
}
impl MOpInUnitState {
fn debug_str(self) -> &'static str {
match self {
Self::NotYetEnqueued => "NotYetEnqueued",
Self::InputsNotReadySpeculative { can_cause_cancel } => {
if can_cause_cancel {
"INR_S_C"
} else {
"INR_S"
}
}
Self::InputsReady {
speculative,
can_cause_cancel,
} => {
if speculative {
if can_cause_cancel { "IR_S_C" } else { "IR_S" }
} else {
if can_cause_cancel { "IR_C" } else { "IR" }
}
}
Self::OutputReady {
speculative,
can_cause_cancel,
} => {
if speculative {
if can_cause_cancel { "OR_S_C" } else { "OR_S" }
} else {
if can_cause_cancel { "OR_C" } else { "OR" }
}
}
Self::FinishedAndOrCausedCancel => "F_C",
}
}
#[must_use]
fn after_enqueue(self) -> Option<Self> {
match self {
Self::NotYetEnqueued => Some(Self::InputsNotReadySpeculative {
can_cause_cancel: true,
}),
_ => None,
}
}
#[must_use]
fn after_output_ready(self) -> Option<Self> {
match self {
Self::InputsReady {
speculative,
can_cause_cancel,
} => Some(Self::OutputReady {
speculative,
can_cause_cancel,
}),
_ => None,
}
}
#[must_use]
fn after_finish_cause_cancel(
self,
cancel_after_retire: bool,
cause_cancel: bool,
) -> Option<Self> {
if cause_cancel && !cancel_after_retire {
match self {
Self::NotYetEnqueued => None,
Self::InputsNotReadySpeculative { can_cause_cancel }
| Self::InputsReady {
speculative: _,
can_cause_cancel,
}
| Self::OutputReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel.then_some(Self::FinishedAndOrCausedCancel),
Self::FinishedAndOrCausedCancel => todo!(),
}
} else {
assert!(cause_cancel == cancel_after_retire);
// see if we can eventually retire MOp
match self {
Self::OutputReady {
speculative: _,
can_cause_cancel,
} => {
if cause_cancel && !can_cause_cancel {
None
} else {
Some(Self::FinishedAndOrCausedCancel)
}
}
Self::NotYetEnqueued
| Self::InputsNotReadySpeculative { .. }
| Self::InputsReady { .. }
| Self::FinishedAndOrCausedCancel => None,
}
}
}
#[must_use]
fn with_inputs_ready(self) -> Option<Self> {
match self {
Self::InputsNotReadySpeculative { can_cause_cancel } => Some(Self::InputsReady {
speculative: true,
can_cause_cancel,
}),
_ => None,
}
}
#[must_use]
fn without_speculative(self) -> Option<Self> {
match self {
Self::NotYetEnqueued => None,
Self::InputsNotReadySpeculative { .. } => None,
Self::InputsReady {
speculative,
can_cause_cancel,
} => speculative.then_some(Self::InputsReady {
speculative: false,
can_cause_cancel,
}),
Self::OutputReady {
speculative,
can_cause_cancel,
} => speculative.then_some(Self::OutputReady {
speculative: false,
can_cause_cancel,
}),
Self::FinishedAndOrCausedCancel => None,
}
}
#[must_use]
fn with_cant_cause_cancel(self) -> Option<Self> {
match self {
Self::NotYetEnqueued => None,
Self::InputsNotReadySpeculative { can_cause_cancel } => {
can_cause_cancel.then_some(Self::InputsNotReadySpeculative {
can_cause_cancel: false,
})
}
Self::InputsReady {
speculative,
can_cause_cancel,
} => can_cause_cancel.then_some(Self::InputsReady {
speculative,
can_cause_cancel: false,
}),
Self::OutputReady {
speculative,
can_cause_cancel,
} => can_cause_cancel.then_some(Self::OutputReady {
speculative,
can_cause_cancel: false,
}),
Self::FinishedAndOrCausedCancel => None,
}
}
fn is_finished_and_or_caused_cancel(self) -> bool {
match self {
Self::NotYetEnqueued => false,
Self::InputsNotReadySpeculative { .. } => false,
Self::InputsReady { .. } => false,
Self::OutputReady { .. } => false,
Self::FinishedAndOrCausedCancel => true,
}
}
}
impl fmt::Debug for MOpInUnitState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.debug_str())
}
}
#[hdl(no_static)]
struct NeedSendCancelDebugState<C: PhantomConstGet<CpuConfig>> {
send_to_next_pc: HdlOption<UInt<64>>,
send_to_units: ArrayType<Bool, CpuConfigUnitCount<C>>,
config: C,
}
#[hdl]
enum NextPcCancelingDebugState {
NeedSendCancel(UInt<64>),
NeedReceiveCancel,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum NextPcCancelingState {
NeedSendCancel(u64),
NeedReceiveCancel,
}
impl NextPcCancelingState {
#[hdl]
fn debug_state(this: &Option<Self>) -> SimValue<HdlOption<NextPcCancelingDebugState>> {
match this {
Some(Self::NeedSendCancel(pc)) =>
{
#[hdl(sim)]
HdlSome(
#[hdl(sim)]
NextPcCancelingDebugState.NeedSendCancel(pc),
)
}
Some(Self::NeedReceiveCancel) =>
{
#[hdl(sim)]
HdlSome(
#[hdl(sim)]
NextPcCancelingDebugState.NeedReceiveCancel(),
)
}
None =>
{
#[hdl(sim)]
HdlNone()
}
}
}
}
type SimOnlyString = SimOnly<String>;
#[expect(non_upper_case_globals)]
const SimOnlyString: SimOnlyString = SimOnlyString::TYPE;
#[hdl(get(|c| c.rob_size.get().next_power_of_two()))]
type PerInsnTimelineLen<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(no_static)]
struct RenameDelayedEntry<C: PhantomConstGet<CpuConfig>> {
is_register_fence: Bool,
mop: MOpInstance<MOp>,
chosen_dest: HdlOption<PRegNum<C>>,
}
#[hdl(no_static)]
pub struct RenameExecuteRetireDebugState<C: PhantomConstGet<CpuConfig>> {
rename_delayed: ArrayVec<RenameDelayedEntry<C>, TwiceCpuConfigFetchWidth<C>>,
rename_table: RenameTableDebugState<C>,
retire_rename_table: RenameTableDebugState<C>,
rob: ReorderBufferDebugState<C>,
next_pc_canceling: HdlOption<NextPcCancelingDebugState>,
unit_canceling: ArrayType<Bool, CpuConfigUnitCount<C>>,
l1_reg_file: ArrayType<
ArrayType<HdlOption<TraceAsString<PRegValue>>, CpuConfig2PowOutRegNumWidth<C>>,
CpuConfigUnitCount<C>,
>,
lfsr: LFSR31,
per_insn_timeline: ArrayType<SimOnlyString, PerInsnTimelineLen<C>>,
}
#[derive(Debug)]
struct RenameExecuteRetireState<C: PhantomConstCpuConfig> {
rename_delayed: VecDeque<SimValue<RenameDelayedEntry<C>>>,
rename_table: RenameTable<C>,
retire_rename_table: RenameTable<C>,
rob: ReorderBuffer<C>,
next_pc_canceling: Option<NextPcCancelingState>,
unit_canceling: Box<[bool]>,
l1_reg_file: Box<[Box<[Option<SimValue<TraceAsString<PRegValue>>>]>]>,
lfsr: SimValue<LFSR31>,
l2_reg_file_unit_index: usize,
config: C,
}
impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
fn new(config: C) -> Self {
Self {
rename_delayed: VecDeque::with_capacity(TwiceCpuConfigFetchWidth[config]),
rename_table: RenameTable::new(config),
retire_rename_table: RenameTable::new(config),
rob: ReorderBuffer::new(config),
next_pc_canceling: None,
unit_canceling: vec![false; CpuConfigUnitCount[config]].into_boxed_slice(),
l1_reg_file: vec![
vec![None; CpuConfig2PowOutRegNumWidth[config]].into_boxed_slice();
CpuConfigUnitCount[config]
]
.into_boxed_slice(),
lfsr: LFSR31::new(),
l2_reg_file_unit_index: config
.get()
.units
.iter()
.position(|unit| unit.kind == UnitKind::TransformedMove)
.expect("Unit for L2 register file is missing"),
config,
}
}
fn is_canceling(&self) -> bool {
self.next_pc_canceling.is_some() || self.unit_canceling.iter().any(|v| *v)
}
fn per_insn_timeline(&self) -> SimValue<ArrayType<SimOnlyString, PerInsnTimelineLen<C>>> {
let len = PerInsnTimelineLen[self.config];
let retval_ty = ArrayType[SimOnlyString][len];
assert!(len.is_power_of_two());
let mask = len - 1;
let empty_string = SimOnlyValue::new(String::new());
let mut retval =
SimValue::from_array_elements(retval_ty, (0..len).map(|_| empty_string.clone()));
for RobEntry {
is_register_fence,
done_waiting_for_register_fences,
mop,
unit_index: _,
mop_in_unit_state,
is_speculative,
all_prior_mops_finished_and_or_caused_cancel,
output,
caused_cancel,
} in self.rob.renamed()
{
let masked_id = mop.id.as_int() as usize & mask;
**retval[masked_id] = fmt::from_fn(|f| {
f.write_str(mop_in_unit_state.debug_str())?;
if *is_register_fence {
f.write_str("(rf)")?;
}
if !*done_waiting_for_register_fences {
f.write_str("(wfrf)")?;
}
if *is_speculative {
f.write_str("(s)")?;
}
if *all_prior_mops_finished_and_or_caused_cancel {
f.write_str("(apfc)")?;
}
if output.is_some() {
f.write_str("(output)")?;
}
if caused_cancel.is_some() {
f.write_str("(caused cancel)")?;
}
write!(
f,
": {}{:#x}{}: {:?}",
if *mop.is_first_mop_in_insn { "" } else { ".." },
mop.pc.as_int(),
if *mop.is_last_mop_in_insn { "" } else { ".." },
mop.mop,
)
})
.to_string();
// TODO
}
retval
}
#[hdl]
async fn write_for_debug(
&self,
sim: &mut ExternModuleSimulationState,
state_for_debug: Expr<RenameExecuteRetireDebugState<C>>,
) {
let Self {
rename_delayed,
rename_table,
retire_rename_table,
rob,
next_pc_canceling,
unit_canceling,
l1_reg_file,
lfsr,
l2_reg_file_unit_index: _,
config: _,
} = self;
sim.write(
state_for_debug,
#[hdl(sim)]
RenameExecuteRetireDebugState::<_> {
rename_delayed: state_for_debug
.ty()
.rename_delayed
.from_iter_sim(zeroed(RenameDelayedEntry[self.config]), rename_delayed)
.expect("known to fit"),
rename_table: rename_table.to_debug_state(),
retire_rename_table: retire_rename_table.to_debug_state(),
rob: rob.debug_state(),
next_pc_canceling: NextPcCancelingState::debug_state(next_pc_canceling),
unit_canceling,
l1_reg_file: SimValue::from_array_elements(
state_for_debug.ty().l1_reg_file,
l1_reg_file.iter().map(|v| {
SimValue::from_array_elements(state_for_debug.ty().l1_reg_file.element(), v)
}),
),
lfsr,
per_insn_timeline: self.per_insn_timeline(),
},
)
.await;
}
#[hdl]
async fn write_to_next_pc_next_insns(
&self,
sim: &mut ExternModuleSimulationState,
next_insns: Expr<HdlOption<ArrayVec<MOpInstance<MOp>, CpuConfigRobSize<C>>>>,
) {
sim.write(
next_insns,
if self.is_canceling() {
#[hdl(sim)]
(next_insns.ty()).HdlNone()
} else {
#[hdl(sim)]
(next_insns.ty()).HdlSome(
next_insns
.ty()
.HdlSome
.from_iter_sim(
zeroed(MOpInstance[MOp]),
self.rename_delayed
.iter()
.map(|v| &v.mop)
.chain(self.rob.unrenamed()),
)
.ok()
.expect("known to fit"),
)
},
)
.await;
}
fn space_available_for_unit(&self, unit_index: usize) -> usize {
let mut retval = self.config.get().unit_max_in_flight(unit_index);
for renamed in self.rob.renamed() {
if renamed.unit_index == unit_index
&& !renamed.mop_in_unit_state.is_finished_and_or_caused_cancel()
{
let Some(v) = NonZero::new(retval.get() - 1) else {
return 0;
};
retval = v;
}
}
retval.get()
}
#[hdl]
fn find_free_unit_out_reg(&self, unit_index: usize) -> Option<usize> {
// TODO: replace searching through instructions and rename tables with tracking when regs are free
let mut allocated_regs = vec![false; 1 << self.config.get().out_reg_num_width];
for renamed in self.rob.renamed() {
if renamed.unit_index == unit_index
&& let Some(unit_out_reg_index) = renamed.unit_out_reg_index()
{
allocated_regs[unit_out_reg_index] = true;
}
MOpTrait::for_each_src_reg_sim_ref(renamed.mop.mop.inner(), &mut |src_reg, _index| {
#[hdl(sim)]
let PRegNum::<_> {
unit_num,
unit_out_reg,
} = src_reg;
if Some(unit_index) == UnitNum::index_sim(&unit_num) {
allocated_regs[UnitOutRegNum::value_sim(&unit_out_reg)] = true;
}
});
}
for entry in self
.rename_table
.entries()
.iter()
.chain(self.retire_rename_table.entries().iter())
{
#[hdl(sim)]
if let HdlSome(l1) = &entry.inner().l1 {
if Some(unit_index) == UnitNum::index_sim(&l1.unit_num) {
allocated_regs[UnitOutRegNum::value_sim(&l1.unit_out_reg)] = true;
}
}
}
allocated_regs.iter().position(|v| !v)
}
#[hdl]
fn find_free_l2_reg(&self) -> Option<usize> {
// TODO: replace searching through instructions and rename tables with tracking when regs are free
let mut allocated_regs = vec![false; L2RegNum.l2_reg_count()];
for renamed in self.rob.renamed() {
#[hdl(sim)]
if let RenamedMOp::<_>::TransformedMove(l2_register_file_op) = renamed.mop.mop.inner() {
let l2_reg = #[hdl(sim)]
match l2_register_file_op {
L2RegisterFileMOp::<_, _>::ReadL2Reg(v) => &v.common.imm,
L2RegisterFileMOp::<_, _>::WriteL2Reg(v) => &v.common.imm,
};
allocated_regs[L2RegNum::value_sim(l2_reg)] = true;
}
}
for entry in self
.rename_table
.entries()
.iter()
.chain(self.retire_rename_table.entries().iter())
{
#[hdl(sim)]
if let HdlSome(l2) = &entry.inner().l2 {
allocated_regs[L2RegNum::value_sim(l2)] = true;
}
}
allocated_regs.iter().position(|v| !v)
}
fn add_renamed_with_new_id(
&mut self,
unrenamed: &SimValue<MOpInstance<MOp>>,
renamed: RobEntry<C>,
) -> &SimValue<MOpId> {
self.rob.renamed_push_back_with_new_id(unrenamed, renamed)
}
fn update_rename_table(
&mut self,
unrenamed: &SimValue<MOpInstance<MOp>>,
update: RenameTableUpdate<C>,
) {
self.rename_table.update(&update, "rename_table");
self.rob
.unrenamed_back_append_rename_table_update(unrenamed, update);
}
#[hdl]
fn try_rename(
&mut self,
entry: SimValue<RenameDelayedEntry<C>>,
) -> Result<(), SimValue<RenameDelayedEntry<C>>> {
#[hdl(sim)]
let RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
} = entry;
println!("try_rename: insn: {insn:?}");
if self.rob.unrenamed_len() >= self.config.get().rob_size.get() {
println!("try_rename: unrenamed_len >= rob_size");
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
);
}
if self.rob.renamed_len() >= self.config.get().rob_size.get() {
println!("try_rename: renamed_len >= rob_size");
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
);
}
let unit_kind = UnitMOp::kind_sim(insn.mop.inner());
#[hdl(sim)]
if let MOp::TransformedMove(move_reg_mop) = insn.mop.inner() {
assert!(!*is_register_fence);
let mut src_regs = [MOpRegNum::const_zero_sim()];
MOpTrait::for_each_src_reg_sim_ref(move_reg_mop, &mut |src_reg, index| {
src_regs[index] = src_reg.clone();
});
let [src_reg] = src_regs;
let renamed_reg =
self.rename_table.entries()[MOpRegNum::reg_num_sim(&src_reg) as usize].clone();
println!("moving from {src_reg:?} renamed: {renamed_reg:?}");
let unrenamed_dest_regs =
MOpDestReg::regs_sim(MOpTrait::dest_reg_sim_ref(move_reg_mop));
assert!(self.rob.incomplete_back_entry.is_none());
for unrenamed_reg_num in unrenamed_dest_regs {
self.update_rename_table(
&insn,
RenameTableUpdate::Write {
unrenamed_reg_num,
new: renamed_reg.clone(),
},
);
}
self.rob.finished_unrenamed_push_back(&insn);
return Ok(());
}
#[derive(Clone, Copy)]
struct ChosenUnit {
unit_index: usize,
out_reg_num: Option<usize>,
space_available: usize,
}
impl ChosenUnit {
fn is_better_than(self, other: Self) -> bool {
let Self {
unit_index: _,
out_reg_num,
space_available,
} = self;
if out_reg_num.is_some() != other.out_reg_num.is_some() {
out_reg_num.is_some()
} else {
space_available > other.space_available
}
}
}
let chosen_unit = #[hdl(sim)]
if let HdlSome(chosen_dest) = &chosen_dest {
let unit_index = UnitNum::index_sim(&chosen_dest.unit_num).expect("known to be some");
assert_eq!(self.config.get().units[unit_index].kind, unit_kind);
ChosenUnit {
unit_index,
out_reg_num: Some(UnitOutRegNum::value_sim(&chosen_dest.unit_out_reg)),
space_available: self.space_available_for_unit(unit_index),
}
} else {
let mut chosen_unit = None;
for (unit_index, unit_config) in self.config.get().units.iter().enumerate() {
if unit_config.kind != unit_kind {
continue;
}
let cur_unit = ChosenUnit {
unit_index,
out_reg_num: self.find_free_unit_out_reg(unit_index),
space_available: self.space_available_for_unit(unit_index),
};
let chosen_unit = chosen_unit.get_or_insert(cur_unit);
if cur_unit.is_better_than(*chosen_unit) {
*chosen_unit = cur_unit;
}
}
let Some(chosen_unit) = chosen_unit else {
panic!(
"there are no units of kind: {unit_kind:?}:\n{:?}",
self.config,
);
};
chosen_unit
};
let ChosenUnit {
unit_index,
out_reg_num,
space_available,
} = chosen_unit;
if space_available == 0 {
println!("try_rename: space_available = 0");
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
);
}
let Some(out_reg_num) = out_reg_num else {
let l2_space_available = self.space_available_for_unit(self.l2_reg_file_unit_index);
let used_unit_out_reg_count = self.rename_table.used_unit_out_reg_count(unit_index);
let unit_out_reg_count = 1 << self.config.get().out_reg_num_width;
let threshold_count = (unit_out_reg_count * 3 / 4).clamp(1, unit_out_reg_count);
let free_l2_reg = self.find_free_l2_reg();
println!(
"try_rename: out_reg_num = None \
l2_space_available={l2_space_available} \
used_unit_out_reg_count={used_unit_out_reg_count} \
threshold_count={threshold_count} \
free_l2_reg={free_l2_reg:?}",
);
if l2_space_available > 0
&& used_unit_out_reg_count >= threshold_count
&& let Some(l2_reg_index) = free_l2_reg
{
println!("try_rename: start L2 store");
let reg_to_free = LFSR31::next_sim(&mut self.lfsr) as usize
% (1 << self.config.get().out_reg_num_width);
let reg_to_free = #[hdl(sim)]
PRegNum::<_> {
unit_num: UnitNum[self.config].from_index_sim(unit_index),
unit_out_reg: UnitOutRegNum[self.config].new_sim(reg_to_free),
};
println!("try_rename: picked {reg_to_free:?}");
let mut any_collisions = false;
MOpTrait::for_each_src_reg_sim_ref(insn.mop.inner(), &mut |src_reg, _| {
let renamed =
&self.rename_table.entries()[MOpRegNum::reg_num_sim(&src_reg) as usize];
println!(
"try_rename: checking that mop src reg ({renamed:?}) doesn't conflict with picked reg"
);
#[hdl(sim)]
if let HdlSome(l1) = &renamed.inner().l1 {
if reg_to_free == *l1 {
any_collisions = true;
}
}
});
if any_collisions {
println!(
"try_rename: attempted L2 store collides with one or more mOp input register(s)"
);
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
);
}
let dest = L2RegNum::new_sim(l2_reg_index);
self.update_rename_table(
&insn,
RenameTableUpdate::UpdateForWriteL2Reg {
dest: dest.clone(),
src: reg_to_free.clone(),
},
);
let l2_store_id = self.add_renamed_with_new_id(
&insn,
RobEntry::new(
#[hdl(sim)]
MOpInstance::<_> {
fetch_block_id: insn.fetch_block_id,
id: MOpId.zero(), // filled in by add_renamed_with_new_id
pc: insn.pc,
predicted_next_pc: insn.pc,
size_in_bytes: insn.size_in_bytes,
is_first_mop_in_insn: insn.is_first_mop_in_insn,
is_last_mop_in_insn: insn.is_last_mop_in_insn,
mop: WriteL2RegMOp::write_l2_reg::<RenamedMOp<C>>(
PRegNum[self.config].const_zero(),
repeat(&reg_to_free, ConstUsize::<1>),
dest,
)
.into_trace_as_string(),
},
self.l2_reg_file_unit_index,
*is_register_fence,
),
);
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence: true,
mop: insn,
chosen_dest: #[hdl(sim)]
(chosen_dest.ty()).HdlSome(reg_to_free),
},
);
}
return Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
);
};
let out_reg_num_sim = UnitOutRegNum[self.config].new_sim(out_reg_num);
#[hdl(sim)]
let MOpInstance::<_> {
fetch_block_id,
id: _,
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop,
} = &insn;
let mut needed_load = None;
let unrenamed_dest_regs = MOpDestReg::regs_sim(MOpTrait::dest_reg_sim_ref(mop.inner()));
let renamed_dest_reg = #[hdl(sim)]
PRegNum::<_> {
unit_num: UnitNum[self.config].from_index_sim(unit_index),
unit_out_reg: out_reg_num_sim,
};
let mop = MOpTrait::map_regs_sim(
mop.inner(),
&renamed_dest_reg,
PRegNum[self.config],
&mut |src_reg, index| {
let renamed =
&self.rename_table.entries()[MOpRegNum::reg_num_sim(&src_reg) as usize];
println!("renaming src[{index}] from {src_reg:?} to {renamed:?}");
#[hdl(sim)]
match &renamed.inner().l1 {
HdlSome(l1) => l1.clone(),
HdlNone => {
let l2 = #[hdl(sim)]
if let HdlSome(l2) = &renamed.inner().l2 {
l2
} else {
unreachable!("rename table entry has neither l1 nor l2: {src_reg:?}");
};
needed_load.get_or_insert_with(|| l2.clone());
PRegNum[self.config].const_zero_sim()
}
}
},
);
if let Some(needed_load) = needed_load {
return if let Some(out_reg) = self.find_free_unit_out_reg(self.l2_reg_file_unit_index)
&& self.space_available_for_unit(self.l2_reg_file_unit_index) > 0
{
let dest = #[hdl(sim)]
PRegNum::<_> {
unit_num: UnitNum[self.config].from_index_sim(self.l2_reg_file_unit_index),
unit_out_reg: UnitOutRegNum[self.config].new_sim(out_reg),
};
self.update_rename_table(
&insn,
RenameTableUpdate::UpdateForReadL2Reg {
dest: dest.clone(),
src: needed_load.clone(),
},
);
self.add_renamed_with_new_id(
&insn,
RobEntry::new(
#[hdl(sim)]
MOpInstance::<_> {
fetch_block_id,
id: MOpId.zero(), // filled in by add_renamed_with_new_id
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop: ReadL2RegMOp::read_l2_reg::<RenamedMOp<C>>(
dest,
repeat(PRegNum[self.config].const_zero_sim(), ConstUsize),
needed_load,
)
.into_trace_as_string(),
},
self.l2_reg_file_unit_index,
*is_register_fence,
),
);
println!("try_rename: read l2 reg");
Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence: false,
mop: insn,
chosen_dest,
},
)
} else if self.rob.all_mops_are_finished_and_or_caused_cancel() {
println!("try_rename: dropping all l2 reg file outputs");
self.update_rename_table(&insn, RenameTableUpdate::DropAllL2RegFileOutputs);
Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence: true,
mop: insn,
chosen_dest,
},
)
} else {
println!(
"try_rename: waiting for all mops to finish before dropping all l2 reg file outputs"
);
Err(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence,
mop: insn,
chosen_dest,
},
)
};
}
let mop = UnitMOp::with_transformed_move_op_sim(
mop,
RenamedMOp[self.config].TransformedMove,
|_move_reg| unreachable!(),
)
.into_trace_as_string();
let renamed_dest_reg = #[hdl(sim)]
RenameTableEntry::<_> {
l1: #[hdl(sim)]
(HdlOption[renamed_dest_reg.ty()]).HdlSome(renamed_dest_reg),
l2: #[hdl(sim)]
HdlNone(),
};
for unrenamed_reg_num in unrenamed_dest_regs {
self.update_rename_table(
&insn,
RenameTableUpdate::Write {
unrenamed_reg_num,
new: renamed_dest_reg.to_trace_as_string(),
},
);
}
self.add_renamed_with_new_id(
&insn,
RobEntry::new(
#[hdl(sim)]
MOpInstance::<_> {
fetch_block_id,
id: MOpId.zero(), // filled in by add_renamed_with_new_id
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop,
},
unit_index,
*is_register_fence,
),
);
self.rob.finished_unrenamed_push_back(&insn);
Ok(())
}
#[hdl]
fn get_unit_enqueue(&self, unit_index: usize) -> SimValue<HdlOption<UnitEnqueue<C>>> {
let ret_ty = HdlOption[UnitEnqueue[self.config]];
if self.is_canceling() {
let retval = #[hdl(sim)]
ret_ty.HdlNone();
return retval; // separate variable to work around rust-analyzer parse error
}
for rob in self.rob.renamed() {
if rob.unit_index == unit_index
&& let Some(_) = rob.mop_in_unit_state.after_enqueue()
{
let retval = #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitEnqueue::<_> {
mop: &rob.mop,
config: self.config,
},
);
return retval;
}
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn get_unit_inputs_ready(&self, unit_index: usize) -> SimValue<HdlOption<UnitInputsReady<C>>> {
let ret_ty = HdlOption[UnitInputsReady[self.config]];
if self.is_canceling() {
let retval = #[hdl(sim)]
ret_ty.HdlNone();
return retval; // separate variable to work around rust-analyzer parse error
}
let zero_reg = PRegNum[self.config].const_zero().into_sim_value();
let zero_value = zeroed(TraceAsString[PRegValue]);
for RobEntry {
is_register_fence: _,
done_waiting_for_register_fences,
mop,
unit_index: rob_unit_index,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output: _,
caused_cancel: _,
} in self.rob.renamed()
{
if *done_waiting_for_register_fences
&& *rob_unit_index == unit_index
&& let Some(_) = mop_in_unit_state.with_inputs_ready()
{
let mut src_values: [_; COMMON_MOP_SRC_LEN] =
std::array::from_fn(|_| Some(zero_value.clone()));
MOpTrait::for_each_src_reg_sim_ref(mop.mop.inner(), &mut |src_reg, index| {
#[hdl(sim)]
let PRegNum::<_> {
unit_num,
unit_out_reg,
} = &src_reg;
if let Some(src_unit_index) = UnitNum::index_sim(unit_num) {
src_values[index] = self.l1_reg_file[src_unit_index]
[UnitOutRegNum::value_sim(unit_out_reg)]
.clone();
} else {
assert_eq!(*src_reg, zero_reg);
}
});
if src_values.iter().all(|v| v.is_some()) {
let src_values: [SimValue<_>; 3] = src_values.map(Option::unwrap);
let retval = #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitInputsReady::<_> {
mop,
src_values,
config: self.config,
},
);
return retval;
}
}
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn get_unit_mop_is_no_longer_speculative(
&self,
unit_index: usize,
) -> SimValue<HdlOption<UnitMOpIsNoLongerSpeculative<C>>> {
let ret_ty = HdlOption[UnitMOpIsNoLongerSpeculative[self.config]];
if self.is_canceling() {
let retval = #[hdl(sim)]
ret_ty.HdlNone();
return retval; // separate variable to work around rust-analyzer parse error
}
for rob in self.rob.renamed() {
if rob.unit_index == unit_index
&& !rob.is_speculative
&& let Some(_) = rob.mop_in_unit_state.without_speculative()
{
let retval = #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitMOpIsNoLongerSpeculative::<_> {
id: &rob.mop.id,
config: self.config,
},
);
return retval; // separate variable to work around rust-analyzer parse error
}
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn unit_output_ready(&mut self, output_ready: SimValue<UnitOutputReady<C>>) {
#[hdl(sim)]
let UnitOutputReady::<_> {
id,
dest_value,
predictor_op,
} = output_ready;
assert!(!self.is_canceling());
let rob = self.rob.renamed_by_id_mut(&id);
let out_reg_index = rob.unit_out_reg_index();
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = rob;
assert!(output.is_none());
assert!(caused_cancel.is_none());
if let Some(out_reg_index) = out_reg_index {
let l1_reg = &mut self.l1_reg_file[*unit_index][out_reg_index];
assert!(l1_reg.is_none());
*l1_reg = Some(dest_value);
}
*output = Some(predictor_op);
*mop_in_unit_state = mop_in_unit_state
.after_output_ready()
.expect("should be valid state for output to become ready");
}
#[hdl]
fn unit_finish_cause_cancel(
&mut self,
finish_cause_cancel: SimValue<UnitFinishCauseCancel<C>>,
) {
#[hdl(sim)]
let UnitFinishCauseCancel::<_> {
id,
caused_cancel: unit_caused_cancel,
config: _,
} = finish_cause_cancel;
assert!(!self.is_canceling());
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = self.rob.renamed_by_id_mut(&id);
assert!(caused_cancel.is_none());
let cancel_after_retire;
#[hdl(sim)]
if let HdlSome(unit_caused_cancel) = unit_caused_cancel {
cancel_after_retire = *unit_caused_cancel.cancel_after_retire;
*caused_cancel = Some(unit_caused_cancel);
} else {
cancel_after_retire = false;
}
if let Some(v) = mop_in_unit_state
.after_finish_cause_cancel(cancel_after_retire, caused_cancel.is_some())
{
*mop_in_unit_state = v
} else {
panic!(
"MOp {id:?} made an invalid attempt to finish/cause a cancel:\n\
mop_in_unit_state={mop_in_unit_state:?}\n\
output={output:?}\n\
caused_cancel={caused_cancel:?}"
);
}
}
fn get_from_post_decode_ready(&self) -> usize {
if self.is_canceling() {
0
} else {
TwiceCpuConfigFetchWidth[self.config]
.saturating_sub(self.rename_delayed.len())
.min(CpuConfigFetchWidth[self.config])
.min(
CpuConfigRobSize[self.config]
.saturating_sub(self.rename_delayed.len())
.saturating_sub(self.rob.unrenamed_len()),
)
}
}
#[hdl]
fn handle_from_post_decode(&mut self, insns: &[SimValue<MOpInstance<MOp>>]) {
if self.is_canceling() {
assert!(insns.is_empty());
return;
}
for insn in insns {
self.rename_delayed.push_back(
#[hdl(sim)]
RenameDelayedEntry::<_> {
is_register_fence: false,
mop: insn,
chosen_dest: #[hdl(sim)]
(HdlOption[PRegNum[self.config]]).HdlNone(),
},
);
}
for _ in 0..CpuConfigFetchWidth[self.config] {
let Some(insn) = self.rename_delayed.pop_front() else {
break;
};
match self.try_rename(insn) {
Ok(()) => {}
Err(insn) => {
self.rename_delayed.push_front(insn);
break;
}
}
}
}
#[hdl]
fn finish_receive_cancel_from_post_decode(&mut self) {
let Self {
rename_delayed,
rename_table,
retire_rename_table,
rob,
next_pc_canceling,
unit_canceling: _,
l1_reg_file: _,
lfsr: _,
l2_reg_file_unit_index: _,
config: _,
} = self;
assert_eq!(
*next_pc_canceling,
Some(NextPcCancelingState::NeedReceiveCancel)
);
rename_delayed.clear();
rename_table.clone_from(retire_rename_table);
rob.clear();
*next_pc_canceling = None;
}
#[hdl]
fn finish_send_cancel_to_next_pc(&mut self) {
assert!(matches!(
self.next_pc_canceling,
Some(NextPcCancelingState::NeedSendCancel(_))
));
self.next_pc_canceling = Some(NextPcCancelingState::NeedReceiveCancel);
}
#[hdl]
fn peek_retiring_insns(&self) -> Vec<SimValue<NextPcPredictorOp<C>>> {
let mut retval = Vec::new();
for retire_group in self.rob.retire_groups() {
for renamed_entry in retire_group.clone().flat_map(|v| &v.renamed_entries) {
if let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = renamed_entry
{
if caused_cancel.is_some() {
// only the part before the cancel needs to be ready
break;
}
assert!(output.is_some());
} else {
// group isn't ready
return retval;
}
}
for RobEntries {
unrenamed: _,
rename_table_updates: _,
renamed_entries,
} in retire_group
{
let caused_cancel = renamed_entries.iter().any(|v| v.caused_cancel.is_some());
let caused_cancel_after_retire =
renamed_entries
.iter()
.rev()
.enumerate()
.all(|(rev_index, v)| {
if rev_index == 0 {
v.caused_cancel
.as_ref()
.is_some_and(|v| *v.cancel_after_retire)
} else {
v.caused_cancel.is_none()
}
});
if !caused_cancel || caused_cancel_after_retire {
let mut unrenamed_op = #[hdl(sim)]
NextPcPredictorOp::<_> {
call_stack_op: #[hdl(sim)]
CallStackOp.None(),
cond_br_taken: #[hdl(sim)]
HdlNone(),
config: self.config,
};
for renamed in renamed_entries {
let Some(output) = &renamed.output else {
unreachable!();
};
#[hdl(sim)]
let NextPcPredictorOp::<_> {
call_stack_op,
cond_br_taken,
config: _,
} = output;
#[hdl(sim)]
if let CallStackOp::None = &unrenamed_op.call_stack_op {
unrenamed_op.call_stack_op = call_stack_op.clone();
}
#[hdl(sim)]
if let HdlNone = &unrenamed_op.cond_br_taken {
unrenamed_op.cond_br_taken = cond_br_taken.clone();
}
}
retval.push(unrenamed_op);
if retval.len() >= self.config.get().fetch_width.get() {
return retval;
}
}
if caused_cancel {
return retval;
}
}
}
retval
}
#[hdl]
fn retire_peek(&self) -> SimValue<HdlOption<RetireToNextPcInterfaceInner<C>>> {
let ty = RetireToNextPcInterfaceInner[self.config];
let ret_ty = HdlOption[ty];
let next_pc_predictor_op = NextPcPredictorOp[self.config];
if let Some(NextPcCancelingState::NeedSendCancel(v)) = &self.next_pc_canceling {
#[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
ty.CancelAndStartAt(v),
)
} else if self.is_canceling() {
#[hdl(sim)]
ret_ty.HdlNone()
} else {
let retiring_insns = self.peek_retiring_insns();
if retiring_insns.is_empty() {
#[hdl(sim)]
ret_ty.HdlNone()
} else {
#[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
ty.RetiredInstructions(
ty.RetiredInstructions
.from_iter_sim(zeroed(next_pc_predictor_op), retiring_insns)
.expect("known to fit"),
),
)
}
}
}
#[hdl]
fn retire_one(&mut self, retire: &SimValue<NextPcPredictorOp<C>>) {
assert!(!self.is_canceling());
#[hdl(sim)]
let NextPcPredictorOp::<_> {
call_stack_op: _,
cond_br_taken: _,
config: _,
} = retire;
let Some(RobEntries {
unrenamed: _,
rename_table_updates,
renamed_entries,
}) = self.rob.entries.pop_front()
else {
unreachable!();
};
rename_table_updates
.iter()
.for_each(|v| self.retire_rename_table.update(v, "retire_rename_table"));
for RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output: _,
caused_cancel,
} in renamed_entries
{
assert_eq!(mop_in_unit_state, MOpInUnitState::FinishedAndOrCausedCancel);
if let Some(caused_cancel) = caused_cancel {
assert!(*caused_cancel.cancel_after_retire);
self.start_cancel(caused_cancel);
return;
}
}
}
#[hdl]
fn start_cancel(&mut self, caused_cancel: SimValue<UnitCausedCancel<C>>) {
assert!(!self.is_canceling());
#[hdl(sim)]
let UnitCausedCancel::<_> {
start_at_pc,
cancel_after_retire: _,
config: _,
} = caused_cancel;
self.next_pc_canceling = Some(NextPcCancelingState::NeedSendCancel(start_at_pc.as_int()));
self.unit_canceling.fill(true);
}
#[hdl]
fn step(&mut self) {
if self.is_canceling() {
return;
}
for renamed in self.rob.renamed_mut() {
renamed.all_prior_mops_finished_and_or_caused_cancel = true;
let MOpInUnitState::FinishedAndOrCausedCancel = renamed.mop_in_unit_state else {
break;
};
}
for renamed in self.rob.renamed_mut() {
let can_cause_cancel = match renamed.mop_in_unit_state {
MOpInUnitState::NotYetEnqueued => true,
MOpInUnitState::InputsNotReadySpeculative { can_cause_cancel } => can_cause_cancel,
MOpInUnitState::InputsReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::OutputReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::FinishedAndOrCausedCancel => renamed.caused_cancel.is_some(),
};
renamed.is_speculative = false;
if can_cause_cancel {
break;
}
}
for renamed in self.rob.renamed_mut() {
let RobEntry {
is_register_fence,
done_waiting_for_register_fences,
mop: _,
unit_index,
mop_in_unit_state: _,
is_speculative,
all_prior_mops_finished_and_or_caused_cancel,
output: _,
caused_cancel: _,
} = &renamed;
if *is_register_fence {
if !*all_prior_mops_finished_and_or_caused_cancel || *is_speculative {
break;
}
}
if !*done_waiting_for_register_fences {
if let Some(unit_out_reg_index) = renamed.unit_out_reg_index() {
self.l1_reg_file[*unit_index][unit_out_reg_index] = None;
}
}
renamed.done_waiting_for_register_fences = true;
}
let first_renamed = self.rob.renamed().next();
if let Some(RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output: _,
caused_cancel: Some(caused_cancel),
}) = first_renamed
&& !*caused_cancel.cancel_after_retire
{
let caused_cancel = caused_cancel.clone();
self.start_cancel(caused_cancel);
return;
}
}
}
#[hdl]
async fn rename_execute_retire_run(
mut sim: ExternModuleSimulationState,
cd: Expr<ClockDomain>,
from_post_decode: Expr<PostDecodeOutputInterface<PhantomConst<CpuConfig>>>,
to_next_pc: Expr<RetireToNextPcInterface<PhantomConst<CpuConfig>>>,
to_units: Expr<ExecuteToUnitInterfaces<PhantomConst<CpuConfig>>>,
state_for_debug: Expr<RenameExecuteRetireDebugState<PhantomConst<CpuConfig>>>,
config: PhantomConst<CpuConfig>,
) {
let mut state = RenameExecuteRetireState::new(config);
loop {
state
.write_to_next_pc_next_insns(&mut sim, to_next_pc.next_insns)
.await;
state.write_for_debug(&mut sim, state_for_debug).await;
let from_post_decode_ready = state.get_from_post_decode_ready();
assert!(from_post_decode_ready <= from_post_decode.ty().ready.end());
sim.write(from_post_decode.ready, from_post_decode_ready)
.await;
sim.write(
from_post_decode.cancel.ready,
state.next_pc_canceling == Some(NextPcCancelingState::NeedReceiveCancel),
)
.await;
let retire_peek = state.retire_peek();
sim.write(to_next_pc.inner.data, &retire_peek).await;
let is_canceling = state.is_canceling();
for (unit_index, to_unit) in ExecuteToUnitInterfaces::unit_fields(to_units)
.into_iter()
.enumerate()
{
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel: _,
output_ready: _,
finish_cause_cancel: _,
unit_outputs_ready,
cancel_all,
config: _,
} = to_unit;
sim.write(enqueue.data, state.get_unit_enqueue(unit_index))
.await;
sim.write(inputs_ready, state.get_unit_inputs_ready(unit_index))
.await;
sim.write(
cancel_all.data,
if state.unit_canceling[unit_index] {
#[hdl(sim)]
HdlSome(())
} else {
#[hdl(sim)]
HdlNone()
},
)
.await;
sim.write(
is_no_longer_speculative,
state.get_unit_mop_is_no_longer_speculative(unit_index),
)
.await;
sim.write(unit_outputs_ready, !is_canceling).await;
}
sim.wait_for_clock_edge(cd.clk).await;
let from_post_decode_insns = sim.read_past(from_post_decode.insns, cd.clk).await;
let from_post_decode_insns = ArrayVec::elements_sim_ref(&from_post_decode_insns);
state.handle_from_post_decode(
from_post_decode_insns
.get(..from_post_decode_ready)
.unwrap_or(from_post_decode_insns),
);
for (unit_index, to_unit) in ExecuteToUnitInterfaces::unit_fields(to_units)
.into_iter()
.enumerate()
{
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready,
cancel_all,
config: _,
} = to_unit;
if sim.read_past_bool(enqueue.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(enqueue) = sim.read_past(enqueue.data, cd.clk).await {
assert!(!state.is_canceling());
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = state.rob.renamed_by_id_mut(&enqueue.mop.id);
assert!(output.is_none());
assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state
.after_enqueue()
.expect("UnitEnqueue is known to be valid");
}
}
#[hdl(sim)]
if let HdlSome(inputs_ready) = sim.read_past(inputs_ready, cd.clk).await {
assert!(!state.is_canceling());
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output,
caused_cancel,
} = state.rob.renamed_by_id_mut(&inputs_ready.mop.id);
assert!(*done_waiting_for_register_fences);
assert!(output.is_none());
assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state
.with_inputs_ready()
.expect("UnitInputsReady is known to be valid");
}
#[hdl(sim)]
if let HdlSome(is_no_longer_speculative) =
sim.read_past(is_no_longer_speculative, cd.clk).await
{
assert!(!state.is_canceling());
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output: _,
caused_cancel: _,
} = state.rob.renamed_by_id_mut(&is_no_longer_speculative.id);
*mop_in_unit_state = mop_in_unit_state
.without_speculative()
.expect("UnitMOpIsNoLongerSpeculative is known to be valid");
}
if sim.read_past_bool(unit_outputs_ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(cant_cause_cancel) = sim.read_past(cant_cause_cancel, cd.clk).await {
#[hdl(sim)]
let UnitMOpCantCauseCancel::<_> { id, config: _ } = cant_cause_cancel;
assert!(!state.is_canceling());
let RobEntry {
is_register_fence: _,
done_waiting_for_register_fences: _,
mop: _,
unit_index: _,
mop_in_unit_state,
is_speculative: _,
all_prior_mops_finished_and_or_caused_cancel: _,
output: _,
caused_cancel,
} = state.rob.renamed_by_id_mut(&id);
assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state
.with_cant_cause_cancel()
.expect("UnitMOpCantCauseCancel should be valid");
}
#[hdl(sim)]
if let HdlSome(output_ready) = sim.read_past(output_ready, cd.clk).await {
state.unit_output_ready(output_ready);
}
#[hdl(sim)]
if let HdlSome(finish_cause_cancel) =
sim.read_past(finish_cause_cancel, cd.clk).await
{
state.unit_finish_cause_cancel(finish_cause_cancel);
}
}
if sim.read_past_bool(cancel_all.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(v) = sim.read_past(cancel_all.data, cd.clk).await {
let () = *v;
assert!(state.unit_canceling[unit_index]);
state.unit_canceling[unit_index] = false;
}
}
}
match &mut state.next_pc_canceling {
Some(NextPcCancelingState::NeedReceiveCancel) => {
#[hdl(sim)]
if let HdlSome(_) = sim.read_past(from_post_decode.cancel.data, cd.clk).await {
state.finish_receive_cancel_from_post_decode();
}
}
Some(NextPcCancelingState::NeedSendCancel(_)) => {
if sim.read_past_bool(to_next_pc.inner.ready, cd.clk).await {
state.finish_send_cancel_to_next_pc();
}
}
None => {
if sim.read_past_bool(to_next_pc.inner.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(v) = retire_peek {
let ops =
#[hdl(sim)]
if let RetireToNextPcInterfaceInner::<_>::RetiredInstructions(ops) = v {
ops
} else {
unreachable!()
};
for op in ArrayVec::elements_sim_ref(&ops) {
state.retire_one(op);
}
}
}
}
}
state.step();
}
}
#[hdl_module(extern)]
pub fn rename_execute_retire(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let from_post_decode: PostDecodeOutputInterface<PhantomConst<CpuConfig>> =
m.input(PostDecodeOutputInterface[config]);
#[hdl]
let to_next_pc: RetireToNextPcInterface<PhantomConst<CpuConfig>> =
m.output(RetireToNextPcInterface[config]);
#[hdl]
let to_units: ExecuteToUnitInterfaces<PhantomConst<CpuConfig>> =
m.output(ExecuteToUnitInterfaces[config]);
#[hdl]
let state_for_debug: RenameExecuteRetireDebugState<PhantomConst<CpuConfig>> =
m.output(RenameExecuteRetireDebugState[config]);
m.register_clock_for_past(cd.clk);
m.extern_module_simulation_fn(
(
cd,
from_post_decode,
to_next_pc,
to_units,
state_for_debug,
config,
),
async |args, mut sim| {
let (cd, from_post_decode, to_next_pc, to_units, state_for_debug, config) = args;
sim.write(state_for_debug, state_for_debug.ty().sim_value_default())
.await;
sim.resettable(
cd,
async |mut sim: ExternModuleSimulationState| {
sim.write(from_post_decode.ready, 0usize).await;
sim.write(from_post_decode.cancel.ready, false).await;
sim.write(to_next_pc.inner.data, to_next_pc.ty().inner.data.HdlNone())
.await;
sim.write(to_next_pc.next_insns, to_next_pc.ty().next_insns.HdlNone())
.await;
for to_unit in ExecuteToUnitInterfaces::unit_fields(to_units) {
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel: _,
output_ready: _,
finish_cause_cancel: _,
unit_outputs_ready,
cancel_all,
config: _,
} = to_unit;
sim.write(
enqueue.data,
#[hdl(sim)]
(enqueue.ty().data).HdlNone(),
)
.await;
sim.write(
inputs_ready,
#[hdl(sim)]
(inputs_ready.ty()).HdlNone(),
)
.await;
sim.write(
cancel_all.data,
#[hdl(sim)]
HdlNone(),
)
.await;
sim.write(
is_no_longer_speculative,
#[hdl(sim)]
(is_no_longer_speculative.ty()).HdlNone(),
)
.await;
sim.write(unit_outputs_ready, false).await;
}
},
|sim, ()| {
rename_execute_retire_run(
sim,
cd,
from_post_decode,
to_next_pc,
to_units,
state_for_debug,
config,
)
},
)
.await;
},
);
}