WIP adding new test for L2 reg file store
Some checks failed
/ test (pull_request) Failing after 5m30s

This commit is contained in:
Jacob Lifshay 2026-05-06 20:11:50 -07:00
parent 33b5d59507
commit 04e924460a
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
5 changed files with 231623 additions and 161 deletions

View file

@ -20,7 +20,7 @@ use crate::{
CpuConfig, CpuConfigFetchWidth, CpuConfigMaxFetchesInFlight, CpuConfigRobSize, CpuConfig, CpuConfigFetchWidth, CpuConfigMaxFetchesInFlight, CpuConfigRobSize,
PhantomConstCpuConfig, TwiceCpuConfigFetchWidth, PhantomConstCpuConfig, TwiceCpuConfigFetchWidth,
}, },
util::array_vec::ArrayVec, util::{LFSR31, array_vec::ArrayVec},
}; };
use fayalite::{ use fayalite::{
int::{UIntInRange, UIntInRangeInclusive, UIntInRangeInclusiveType, UIntInRangeType}, int::{UIntInRange, UIntInRangeInclusive, UIntInRangeInclusiveType, UIntInRangeType},
@ -2955,33 +2955,9 @@ impl BTBEntry {
} }
} }
#[hdl]
struct LFSR31 {
// MSB is always zero, 32 bits makes it easier to manipulate
state: UInt<32>,
}
impl SimValueDefault for LFSR31 { impl SimValueDefault for LFSR31 {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> { fn sim_value_default(self) -> SimValue<Self> {
#[hdl(sim)] Self::new()
Self { state: 1u32 }
}
}
impl LFSR31 {
fn next(this: &mut SimValue<Self>) -> u32 {
let state = this.state.as_int();
let state = if state == 0 {
1u32
} else {
// a maximal-length 31-bit LFSR
let lsb = ((state >> 30) ^ (state >> 27)) & 1;
let msb = (state << 1) & ((1 << 31) - 1);
lsb | msb
};
*this.state = state.into();
state
} }
} }
@ -2995,7 +2971,7 @@ impl BranchTargetBuffer {
const LOG2_SIZE: usize = 4; const LOG2_SIZE: usize = 4;
const SIZE: usize = 1 << Self::LOG2_SIZE; const SIZE: usize = 1 << Self::LOG2_SIZE;
fn next_index_to_replace(this: &mut SimValue<Self>) -> usize { fn next_index_to_replace(this: &mut SimValue<Self>) -> usize {
LFSR31::next(&mut this.next_index_to_replace_lfsr) as usize % Self::SIZE LFSR31::next_sim(&mut this.next_index_to_replace_lfsr) as usize % Self::SIZE
} }
} }

View file

@ -17,7 +17,7 @@ use crate::{
register::PRegValue, register::PRegValue,
rename_execute_retire::to_unit_interfaces::ExecuteToUnitInterfaces, rename_execute_retire::to_unit_interfaces::ExecuteToUnitInterfaces,
unit::{UnitKind, UnitMOp}, unit::{UnitKind, UnitMOp},
util::array_vec::ArrayVec, util::{LFSR31, array_vec::ArrayVec},
}; };
use fayalite::{ use fayalite::{
int::UIntInRangeInclusiveType, int::UIntInRangeInclusiveType,
@ -227,6 +227,7 @@ impl<C: PhantomConstCpuConfig> SimValueDefault for RenameExecuteRetireDebugState
next_pc_canceling, next_pc_canceling,
unit_canceling, unit_canceling,
l1_reg_file, l1_reg_file,
lfsr: _,
per_insn_timeline, per_insn_timeline,
} = self; } = self;
let empty_string = SimOnlyValue::new(String::new()); let empty_string = SimOnlyValue::new(String::new());
@ -239,6 +240,7 @@ impl<C: PhantomConstCpuConfig> SimValueDefault for RenameExecuteRetireDebugState
next_pc_canceling: zeroed(next_pc_canceling), next_pc_canceling: zeroed(next_pc_canceling),
unit_canceling: zeroed(unit_canceling), unit_canceling: zeroed(unit_canceling),
l1_reg_file: zeroed(l1_reg_file), l1_reg_file: zeroed(l1_reg_file),
lfsr: LFSR31::new(),
per_insn_timeline: SimValue::from_array_elements( per_insn_timeline: SimValue::from_array_elements(
per_insn_timeline, per_insn_timeline,
(0..per_insn_timeline.len()).map(|_| empty_string.clone()), (0..per_insn_timeline.len()).map(|_| empty_string.clone()),
@ -570,7 +572,7 @@ struct RobEntryDebugState<C: PhantomConstGet<CpuConfig>> {
mop: MOpInstance<RenamedMOp<C>>, mop: MOpInstance<RenamedMOp<C>>,
mop_in_unit_state: SimOnlyMOpInUnitState, mop_in_unit_state: SimOnlyMOpInUnitState,
is_speculative: Bool, is_speculative: Bool,
finished: HdlOption<NextPcPredictorOp<C>>, output: HdlOption<NextPcPredictorOp<C>>,
caused_cancel: HdlOption<UnitCausedCancel<C>>, caused_cancel: HdlOption<UnitCausedCancel<C>>,
} }
@ -581,7 +583,7 @@ impl<C: PhantomConstCpuConfig> SimValueDefault for RobEntryDebugState<C> {
mop, mop,
mop_in_unit_state: _, mop_in_unit_state: _,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = self; } = self;
#[hdl(sim)] #[hdl(sim)]
@ -589,8 +591,8 @@ impl<C: PhantomConstCpuConfig> SimValueDefault for RobEntryDebugState<C> {
mop: zeroed(mop), mop: zeroed(mop),
mop_in_unit_state: SimOnlyValue::default(), mop_in_unit_state: SimOnlyValue::default(),
is_speculative: false, is_speculative: false,
finished: #[hdl(sim)] output: #[hdl(sim)]
finished.HdlNone(), output.HdlNone(),
caused_cancel: #[hdl(sim)] caused_cancel: #[hdl(sim)]
caused_cancel.HdlNone(), caused_cancel.HdlNone(),
} }
@ -602,7 +604,7 @@ struct RobEntry<C: PhantomConstCpuConfig> {
mop: SimValue<MOpInstance<RenamedMOp<C>>>, mop: SimValue<MOpInstance<RenamedMOp<C>>>,
mop_in_unit_state: MOpInUnitState, mop_in_unit_state: MOpInUnitState,
is_speculative: bool, is_speculative: bool,
finished: Option<SimValue<NextPcPredictorOp<C>>>, output: Option<SimValue<NextPcPredictorOp<C>>>,
caused_cancel: Option<SimValue<UnitCausedCancel<C>>>, caused_cancel: Option<SimValue<UnitCausedCancel<C>>>,
} }
@ -612,7 +614,7 @@ impl<C: PhantomConstCpuConfig> RobEntry<C> {
mop, mop,
mop_in_unit_state: MOpInUnitState::NotYetEnqueued, mop_in_unit_state: MOpInUnitState::NotYetEnqueued,
is_speculative: true, is_speculative: true,
finished: None, output: None,
caused_cancel: None, caused_cancel: None,
} }
} }
@ -637,7 +639,7 @@ impl<C: PhantomConstCpuConfig> RobEntry<C> {
mop, mop,
mop_in_unit_state, mop_in_unit_state,
is_speculative, is_speculative,
finished, output,
caused_cancel, caused_cancel,
} = self; } = self;
let ret_ty = RobEntryDebugState[config]; let ret_ty = RobEntryDebugState[config];
@ -646,7 +648,7 @@ impl<C: PhantomConstCpuConfig> RobEntry<C> {
mop, mop,
mop_in_unit_state: SimOnlyValue::new(*mop_in_unit_state), mop_in_unit_state: SimOnlyValue::new(*mop_in_unit_state),
is_speculative, is_speculative,
finished: finished.into_sim_value_with_type(ret_ty.finished), output: output.into_sim_value_with_type(ret_ty.output),
caused_cancel: caused_cancel.into_sim_value_with_type(ret_ty.caused_cancel), caused_cancel: caused_cancel.into_sim_value_with_type(ret_ty.caused_cancel),
} }
} }
@ -978,9 +980,15 @@ const SimOnlyString: SimOnlyString = SimOnlyString::TYPE;
#[hdl(get(|c| c.rob_size.get().next_power_of_two()))] #[hdl(get(|c| c.rob_size.get().next_power_of_two()))]
type PerInsnTimelineLen<C: PhantomConstGet<CpuConfig>> = DynSize; type PerInsnTimelineLen<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl]
struct RenameDelayedEntry {
mop: MOpInstance<MOp>,
started_l2_store: Bool,
}
#[hdl(no_static)] #[hdl(no_static)]
pub struct RenameExecuteRetireDebugState<C: PhantomConstGet<CpuConfig>> { pub struct RenameExecuteRetireDebugState<C: PhantomConstGet<CpuConfig>> {
rename_delayed: ArrayVec<MOpInstance<MOp>, TwiceCpuConfigFetchWidth<C>>, rename_delayed: ArrayVec<RenameDelayedEntry, TwiceCpuConfigFetchWidth<C>>,
rename_table: RenameTableDebugState<C>, rename_table: RenameTableDebugState<C>,
retire_rename_table: RenameTableDebugState<C>, retire_rename_table: RenameTableDebugState<C>,
rob: ReorderBufferDebugState<C>, rob: ReorderBufferDebugState<C>,
@ -990,18 +998,20 @@ pub struct RenameExecuteRetireDebugState<C: PhantomConstGet<CpuConfig>> {
ArrayType<HdlOption<PRegValue>, CpuConfig2PowOutRegNumWidth<C>>, ArrayType<HdlOption<PRegValue>, CpuConfig2PowOutRegNumWidth<C>>,
CpuConfigUnitCount<C>, CpuConfigUnitCount<C>,
>, >,
lfsr: LFSR31,
per_insn_timeline: ArrayType<SimOnlyString, PerInsnTimelineLen<C>>, per_insn_timeline: ArrayType<SimOnlyString, PerInsnTimelineLen<C>>,
} }
#[derive(Debug)] #[derive(Debug)]
struct RenameExecuteRetireState<C: PhantomConstCpuConfig> { struct RenameExecuteRetireState<C: PhantomConstCpuConfig> {
rename_delayed: VecDeque<SimValue<MOpInstance<MOp>>>, rename_delayed: VecDeque<SimValue<RenameDelayedEntry>>,
rename_table: RenameTable<C>, rename_table: RenameTable<C>,
retire_rename_table: RenameTable<C>, retire_rename_table: RenameTable<C>,
rob: ReorderBuffer<C>, rob: ReorderBuffer<C>,
next_pc_canceling: Option<NextPcCancelingState>, next_pc_canceling: Option<NextPcCancelingState>,
unit_canceling: Box<[bool]>, unit_canceling: Box<[bool]>,
l1_reg_file: Box<[Box<[Option<SimValue<PRegValue>>]>]>, l1_reg_file: Box<[Box<[Option<SimValue<PRegValue>>]>]>,
lfsr: SimValue<LFSR31>,
l2_reg_file_unit_index: usize, l2_reg_file_unit_index: usize,
config: C, config: C,
} }
@ -1020,6 +1030,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
CpuConfigUnitCount[config] CpuConfigUnitCount[config]
] ]
.into_boxed_slice(), .into_boxed_slice(),
lfsr: LFSR31::new(),
l2_reg_file_unit_index: config l2_reg_file_unit_index: config
.get() .get()
.units .units
@ -1044,8 +1055,8 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
let masked_id = rob.mop.id.as_int() as usize & mask; let masked_id = rob.mop.id.as_int() as usize & mask;
**retval[masked_id] = fmt::from_fn(|f| { **retval[masked_id] = fmt::from_fn(|f| {
f.write_str(rob.mop_in_unit_state.debug_str())?; f.write_str(rob.mop_in_unit_state.debug_str())?;
if rob.finished.is_some() { if rob.output.is_some() {
f.write_str("(finished)")?; f.write_str("(output)")?;
} }
if rob.caused_cancel.is_some() { if rob.caused_cancel.is_some() {
f.write_str("(caused cancel)")?; f.write_str("(caused cancel)")?;
@ -1079,16 +1090,17 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
state_for_debug: Expr<RenameExecuteRetireDebugState<C>>, state_for_debug: Expr<RenameExecuteRetireDebugState<C>>,
) { ) {
let Self { let Self {
ref rename_delayed, rename_delayed,
ref rename_table, rename_table,
ref retire_rename_table, retire_rename_table,
ref rob, rob,
ref next_pc_canceling, next_pc_canceling,
ref unit_canceling, unit_canceling,
ref l1_reg_file, l1_reg_file,
lfsr,
l2_reg_file_unit_index: _, l2_reg_file_unit_index: _,
config: _, config: _,
} = *self; } = self;
sim.write( sim.write(
state_for_debug, state_for_debug,
#[hdl(sim)] #[hdl(sim)]
@ -1109,6 +1121,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
SimValue::from_array_elements(state_for_debug.ty().l1_reg_file.element(), v) SimValue::from_array_elements(state_for_debug.ty().l1_reg_file.element(), v)
}), }),
), ),
lfsr,
per_insn_timeline: self.per_insn_timeline(), per_insn_timeline: self.per_insn_timeline(),
}, },
) )
@ -1133,7 +1146,10 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
.HdlSome .HdlSome
.from_iter_sim( .from_iter_sim(
zeroed(MOpInstance[MOp]), zeroed(MOpInstance[MOp]),
self.rename_delayed.iter().chain(self.rob.unrenamed()), self.rename_delayed
.iter()
.map(|v| &v.mop)
.chain(self.rob.unrenamed()),
) )
.ok() .ok()
.expect("known to fit"), .expect("known to fit"),
@ -1242,13 +1258,33 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
#[hdl] #[hdl]
fn try_rename( fn try_rename(
&mut self, &mut self,
insn: SimValue<MOpInstance<MOp>>, entry: SimValue<RenameDelayedEntry>,
) -> Result<(), SimValue<MOpInstance<MOp>>> { ) -> Result<(), SimValue<RenameDelayedEntry>> {
#[hdl(sim)]
let RenameDelayedEntry {
mop: insn,
started_l2_store,
} = entry;
println!("try_rename: insn: {insn:?}");
if self.rob.unrenamed_len() >= self.config.get().rob_size.get() { if self.rob.unrenamed_len() >= self.config.get().rob_size.get() {
return Err(insn); println!("try_rename: unrenamed_len >= rob_size");
return Err(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store,
},
);
} }
if self.rob.renamed_len() >= self.config.get().rob_size.get() { if self.rob.renamed_len() >= self.config.get().rob_size.get() {
return Err(insn); println!("try_rename: renamed_len >= rob_size");
return Err(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store,
},
);
} }
let unit_kind = UnitMOp::kind_sim(&insn.mop); let unit_kind = UnitMOp::kind_sim(&insn.mop);
#[hdl(sim)] #[hdl(sim)]
@ -1323,15 +1359,30 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
); );
}; };
if space_available == 0 { if space_available == 0 {
return Err(insn); println!("try_rename: space_available = 0");
return Err(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store,
},
);
} }
let Some(out_reg_num) = out_reg_num else { let Some(out_reg_num) = out_reg_num else {
return if self.space_available_for_unit(self.l2_reg_file_unit_index) > 0 println!("try_rename: out_reg_num = None");
return if !*started_l2_store
&& self.space_available_for_unit(self.l2_reg_file_unit_index) > 0
&& let Some(l2_reg_index) = self.find_free_l2_reg() && let Some(l2_reg_index) = self.find_free_l2_reg()
{ {
todo!("maybe start a L2 register file store"); todo!("maybe start a L2 register file store");
} else { } else {
Err(insn) Err(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store,
},
)
}; };
}; };
let out_reg_num_sim = UnitOutRegNum[self.config].new_sim(out_reg_num); let out_reg_num_sim = UnitOutRegNum[self.config].new_sim(out_reg_num);
@ -1408,7 +1459,14 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
); );
Ok(()) Ok(())
} else { } else {
Err(insn) println!("try_rename: l2 reg file has no space and/or has no free output regs");
Err(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store,
},
)
}; };
} }
let mop = UnitMOp::with_transformed_move_op_sim( let mop = UnitMOp::with_transformed_move_op_sim(
@ -1567,15 +1625,15 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = rob; } = rob;
assert!(finished.is_none()); assert!(output.is_none());
assert!(caused_cancel.is_none()); assert!(caused_cancel.is_none());
let l1_reg = &mut self.l1_reg_file[unit_index][out_reg_index]; let l1_reg = &mut self.l1_reg_file[unit_index][out_reg_index];
assert!(l1_reg.is_none()); assert!(l1_reg.is_none());
*l1_reg = Some(dest_value); *l1_reg = Some(dest_value);
*finished = Some(predictor_op); *output = Some(predictor_op);
*mop_in_unit_state = mop_in_unit_state *mop_in_unit_state = mop_in_unit_state
.after_output_ready() .after_output_ready()
.expect("should be valid state for output to become ready"); .expect("should be valid state for output to become ready");
@ -1596,7 +1654,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = self.rob.renamed_by_id_mut(&id); } = self.rob.renamed_by_id_mut(&id);
assert!(caused_cancel.is_none()); assert!(caused_cancel.is_none());
@ -1616,7 +1674,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
panic!( panic!(
"MOp {id:?} made an invalid attempt to finish/cause a cancel:\n\ "MOp {id:?} made an invalid attempt to finish/cause a cancel:\n\
mop_in_unit_state={mop_in_unit_state:?}\n\ mop_in_unit_state={mop_in_unit_state:?}\n\
finished={finished:?}\n\ output={output:?}\n\
caused_cancel={caused_cancel:?}" caused_cancel={caused_cancel:?}"
); );
} }
@ -1635,13 +1693,20 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
) )
} }
} }
#[hdl]
fn handle_from_post_decode(&mut self, insns: &[SimValue<MOpInstance<MOp>>]) { fn handle_from_post_decode(&mut self, insns: &[SimValue<MOpInstance<MOp>>]) {
if insns.is_empty() { if self.is_canceling() {
assert!(insns.is_empty());
return; return;
} }
assert!(!self.is_canceling());
for insn in insns { for insn in insns {
self.rename_delayed.push_back(insn.clone()); self.rename_delayed.push_back(
#[hdl(sim)]
RenameDelayedEntry {
mop: insn,
started_l2_store: false,
},
);
} }
for _ in 0..CpuConfigFetchWidth[self.config] { for _ in 0..CpuConfigFetchWidth[self.config] {
let Some(insn) = self.rename_delayed.pop_front() else { let Some(insn) = self.rename_delayed.pop_front() else {
@ -1666,6 +1731,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
next_pc_canceling, next_pc_canceling,
unit_canceling: _, unit_canceling: _,
l1_reg_file: _, l1_reg_file: _,
lfsr: _,
l2_reg_file_unit_index: _, l2_reg_file_unit_index: _,
config: _, config: _,
} = self; } = self;
@ -1695,7 +1761,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
mop: _, mop: _,
mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel, mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = renamed_entry } = renamed_entry
{ {
@ -1703,7 +1769,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
// only the part before the cancel needs to be ready // only the part before the cancel needs to be ready
break; break;
} }
assert!(finished.is_some()); assert!(output.is_some());
} else { } else {
// group isn't ready // group isn't ready
return retval; return retval;
@ -1740,7 +1806,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
config: self.config, config: self.config,
}; };
for renamed in renamed_entries { for renamed in renamed_entries {
let Some(finished) = &renamed.finished else { let Some(output) = &renamed.output else {
unreachable!(); unreachable!();
}; };
#[hdl(sim)] #[hdl(sim)]
@ -1748,7 +1814,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
call_stack_op, call_stack_op,
cond_br_taken, cond_br_taken,
config: _, config: _,
} = finished; } = output;
#[hdl(sim)] #[hdl(sim)]
if let CallStackOp::None = &unrenamed_op.call_stack_op { if let CallStackOp::None = &unrenamed_op.call_stack_op {
unrenamed_op.call_stack_op = call_stack_op.clone(); unrenamed_op.call_stack_op = call_stack_op.clone();
@ -1826,7 +1892,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished: _, output: _,
caused_cancel, caused_cancel,
} in renamed_entries } in renamed_entries
{ {
@ -1879,7 +1945,7 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
mop: _, mop: _,
mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel, mop_in_unit_state: MOpInUnitState::FinishedAndOrCausedCancel,
is_speculative: _, is_speculative: _,
finished: _, output: _,
caused_cancel: Some(caused_cancel), caused_cancel: Some(caused_cancel),
}) = first_renamed }) = first_renamed
&& !*caused_cancel.cancel_after_retire && !*caused_cancel.cancel_after_retire
@ -1989,10 +2055,10 @@ async fn rename_execute_retire_run(
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = state.rob.renamed_by_id_mut(&enqueue.mop.id); } = state.rob.renamed_by_id_mut(&enqueue.mop.id);
assert!(finished.is_none()); assert!(output.is_none());
assert!(caused_cancel.is_none()); assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state *mop_in_unit_state = mop_in_unit_state
.after_enqueue() .after_enqueue()
@ -2006,10 +2072,10 @@ async fn rename_execute_retire_run(
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output,
caused_cancel, caused_cancel,
} = state.rob.renamed_by_id_mut(&inputs_ready.mop.id); } = state.rob.renamed_by_id_mut(&inputs_ready.mop.id);
assert!(finished.is_none()); assert!(output.is_none());
assert!(caused_cancel.is_none()); assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state *mop_in_unit_state = mop_in_unit_state
.with_inputs_ready() .with_inputs_ready()
@ -2024,11 +2090,9 @@ async fn rename_execute_retire_run(
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output: _,
caused_cancel, caused_cancel: _,
} = state.rob.renamed_by_id_mut(&is_no_longer_speculative.id); } = state.rob.renamed_by_id_mut(&is_no_longer_speculative.id);
assert!(finished.is_none());
assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state *mop_in_unit_state = mop_in_unit_state
.without_speculative() .without_speculative()
.expect("UnitMOpIsNoLongerSpeculative is known to be valid"); .expect("UnitMOpIsNoLongerSpeculative is known to be valid");
@ -2043,10 +2107,9 @@ async fn rename_execute_retire_run(
mop: _, mop: _,
mop_in_unit_state, mop_in_unit_state,
is_speculative: _, is_speculative: _,
finished, output: _,
caused_cancel, caused_cancel,
} = state.rob.renamed_by_id_mut(&id); } = state.rob.renamed_by_id_mut(&id);
assert!(finished.is_none());
assert!(caused_cancel.is_none()); assert!(caused_cancel.is_none());
*mop_in_unit_state = mop_in_unit_state *mop_in_unit_state = mop_in_unit_state
.with_cant_cause_cancel() .with_cant_cause_cancel()

View file

@ -280,3 +280,33 @@ impl<T: Type, N: Size> Rotate<usize> for SimValue<ArrayType<T, N>> {
retval retval
} }
} }
#[hdl]
pub struct LFSR31 {
// MSB is always zero, 32 bits makes it easier to manipulate
state: UInt<32>,
}
impl LFSR31 {
#[hdl]
pub fn new() -> SimValue<Self> {
#[hdl(sim)]
Self { state: 1u32 }
}
}
impl LFSR31 {
pub fn next_sim(this: &mut SimValue<Self>) -> u32 {
let state = this.state.as_int();
let state = if state == 0 {
1u32
} else {
// a maximal-length 31-bit LFSR
let lsb = ((state >> 30) ^ (state >> 27)) & 1;
let msb = (state << 1) & ((1 << 31) - 1);
lsb | msb
};
*this.state = state.into();
state
}
}

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,10 @@ use cpu::{
}, },
instruction::{ instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp, AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp,
CompareMOp, CompareMode, ConditionMode, L2RegisterFileMOp, LoadMOp, LoadStoreCommonMOp, CompareMOp, CompareMode, ConditionMode, L2RegNum, L2RegisterFileMOp, LoadMOp,
LoadStoreConversion, LoadStoreMOp, LoadStoreWidth, MOp, MOpDestReg, MOpRegNum, MOpTrait, LoadStoreCommonMOp, LoadStoreConversion, LoadStoreMOp, LoadStoreWidth, MOp, MOpDestReg,
MoveRegMOp, OutputIntegerMode, PRegNum, StoreMOp, UnitNum, MOpRegNum, MOpTrait, MoveRegMOp, OutputIntegerMode, PRegNum, ReadL2RegMOp, StoreMOp,
UnitNum, WriteL2RegMOp,
}, },
next_pc::CallStackOp, next_pc::CallStackOp,
register::{PRegFlags, PRegFlagsPowerISA, PRegValue}, register::{PRegFlags, PRegFlagsPowerISA, PRegValue},
@ -291,7 +292,34 @@ impl InsnsBuilder {
.cast_to_static::<SInt<_>>(), .cast_to_static::<SInt<_>>(),
true, true,
ConditionMode.SGt(), ConditionMode.SGt(),
true,
true,
false, false,
false,
)]
},
));
}
fn power_isa_beq(&mut self, target: InsnsBuilderLabel) {
let pc = self.pc;
self.add_insn(Insn::new_lazy(
4,
format!("beq {}", self.labels[target.0].name),
move |labels| {
[BranchMOp::branch_cond_ctr(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_cr_reg_imm(0),
MOpRegNum::const_zero(),
MOpRegNum::const_zero(),
],
labels[target.0]
.pc()
.wrapping_sub(pc)
.cast_to_static::<SInt<_>>(),
false,
ConditionMode.Eq(),
true,
true, true,
false, false,
false, false,
@ -843,6 +871,7 @@ fn mock_next_pc<#[hdl(skip)] MI: MakeInsns>(config: PhantomConst<CpuConfig>) {
state: Cell::new(0), state: Cell::new(0),
}; };
let insns = MI::make_insns(); let insns = MI::make_insns();
dbg!(&insns);
sim.resettable( sim.resettable(
cd, cd,
async |mut sim| { async |mut sim| {
@ -1272,7 +1301,12 @@ impl MockMemory {
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN], src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
is_speculative: bool, is_speculative: bool,
) -> Result<SimValue<PRegValue>, AddressCantBeSpeculativelyAccessed> { ) -> Result<SimValue<PRegValue>, AddressCantBeSpeculativelyAccessed> {
#[hdl(sim)] println!("MockMemory::run_mop: {:#x}: {:?}", mop.pc.as_int(), mop.mop);
println!(
"<- {}{src_values:?}",
if is_speculative { "(speculative) " } else { "" },
);
let retval = #[hdl(sim)]
match &mop.mop { match &mop.mop {
LoadStoreMOp::<_, _>::Load(mop) => { LoadStoreMOp::<_, _>::Load(mop) => {
#[hdl(sim)] #[hdl(sim)]
@ -1323,13 +1357,11 @@ impl MockMemory {
} }
} }
}; };
Ok(
#[hdl(sim)] #[hdl(sim)]
PRegValue { PRegValue {
int_fp: loaded, int_fp: loaded,
flags: PRegFlags::zeroed_sim(), flags: PRegFlags::zeroed_sim(),
}, }
)
} }
LoadStoreMOp::<_, _>::Store(mop) => { LoadStoreMOp::<_, _>::Store(mop) => {
#[hdl(sim)] #[hdl(sim)]
@ -1363,25 +1395,16 @@ impl MockMemory {
} }
} }
} }
Ok(PRegValue::zeroed_sim()) PRegValue::zeroed_sim()
}
} }
};
println!("-> {retval:?}");
Ok(retval)
} }
} }
#[hdl(no_static)]
struct MockL2RegFileDebugState {}
#[derive(Debug, Default)]
struct MockL2RegFile {}
trait MockExecutionStateTrait: Default { trait MockExecutionStateTrait: Default {
type DebugState: BundleType; type DebugState: BundleType;
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile>;
fn get_l2_reg_file(&mut self) -> &mut MockL2RegFile {
self.try_get_l2_reg_file()
.expect("no MockL2RegFile available in this unit")
}
fn debug_state_ty() -> Self::DebugState; fn debug_state_ty() -> Self::DebugState;
fn zeroed_debug_state() -> SimValue<Self::DebugState>; fn zeroed_debug_state() -> SimValue<Self::DebugState>;
fn debug_state(&self) -> SimValue<Self::DebugState>; fn debug_state(&self) -> SimValue<Self::DebugState>;
@ -1732,46 +1755,17 @@ trait MockExecutionStateTrait: Default {
); );
} }
UnitMOp::<_, _, _>::TransformedMove(mop) => { UnitMOp::<_, _, _>::TransformedMove(mop) => {
let l2_reg_file = self.get_l2_reg_file(); panic!(
#[hdl(sim)] "mock_unit can't execute L2RegisterFile MOp, that needs mock_l2_reg_file_unit: {mop:#?}"
match mop { );
L2RegisterFileMOp::<_, _>::ReadL2Reg(mop) => {
todo!("implement ReadL2Reg")
}
L2RegisterFileMOp::<_, _>::WriteL2Reg(mop) => {
todo!("implement WriteL2Reg")
}
}
} }
UnitMOp::<_, _, _>::Unknown => unreachable!(), UnitMOp::<_, _, _>::Unknown => unreachable!(),
} }
} }
} }
impl MockExecutionStateTrait for MockL2RegFile {
type DebugState = MockL2RegFileDebugState;
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
Some(self)
}
fn debug_state_ty() -> Self::DebugState {
MockL2RegFileDebugState
}
fn zeroed_debug_state() -> SimValue<Self::DebugState> {
zeroed(Self::debug_state_ty())
}
#[hdl]
fn debug_state(&self) -> SimValue<Self::DebugState> {
let Self {} = self;
#[hdl(sim)]
MockL2RegFileDebugState {}
}
}
impl MockExecutionStateTrait for () { impl MockExecutionStateTrait for () {
type DebugState = (); type DebugState = ();
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
None
}
fn debug_state_ty() -> Self::DebugState { fn debug_state_ty() -> Self::DebugState {
() ()
} }
@ -2228,6 +2222,542 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
} }
} }
#[hdl(no_static)]
struct MockL2RegFileOpDebugState<C: PhantomConstGet<CpuConfig>> {
mop: MOpInstance<L2RegisterFileMOp<PRegNum<C>, PRegNum<C>>>,
src_values: HdlOption<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
dest_value: HdlOption<PRegValue>,
sent_cant_cause_cancel: Bool,
sent_output_ready: Bool,
config: C,
}
#[derive(Debug)]
struct MockL2RegFileOp<C: PhantomConstCpuConfig> {
mop: SimValue<MOpInstance<L2RegisterFileMOp<PRegNum<C>, PRegNum<C>>>>,
src_values: Option<[SimValue<PRegValue>; COMMON_MOP_SRC_LEN]>,
dest_value: Option<SimValue<PRegValue>>,
sent_cant_cause_cancel: bool,
sent_output_ready: bool,
config: C,
}
impl<C: PhantomConstCpuConfig> MockL2RegFileOp<C> {
#[hdl]
fn debug_state(&self) -> SimValue<MockL2RegFileOpDebugState<C>> {
let Self {
mop,
src_values,
dest_value,
sent_cant_cause_cancel,
sent_output_ready,
config,
} = self;
#[hdl(sim)]
MockL2RegFileOpDebugState::<_> {
mop,
src_values,
dest_value,
sent_cant_cause_cancel,
sent_output_ready,
config,
}
}
fn id(&self) -> &SimValue<MOpId> {
&self.mop.id
}
}
#[hdl(get(|_| L2RegNum.l2_reg_count()))]
type L2RegFileSize<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(no_static)]
struct MockL2RegFileUnitDebugState<C: PhantomConstGet<CpuConfig>> {
ops: ArrayVec<MockL2RegFileOpDebugState<C>, CpuConfigMaxUnitMaxInFlight<C>>,
l2_regs: ArrayType<PRegValue, L2RegFileSize<C>>,
config: C,
}
struct MockL2RegFileUnitState<C: PhantomConstCpuConfig> {
ops: VecDeque<MockL2RegFileOp<C>>,
l2_regs: Box<[SimValue<PRegValue>]>,
config: C,
}
impl<C: PhantomConstCpuConfig> MockL2RegFileUnitState<C> {
fn new(config: C) -> Self {
Self {
ops: VecDeque::new(),
l2_regs: vec![PRegValue::zeroed_sim(); L2RegFileSize[config]].into_boxed_slice(),
config,
}
}
#[hdl]
fn debug_state(&self) -> SimValue<MockL2RegFileUnitDebugState<C>> {
let Self {
ops,
l2_regs,
config,
} = self;
let ret_ty = MockL2RegFileUnitDebugState[*config];
#[hdl(sim)]
MockL2RegFileUnitDebugState::<_> {
ops: ret_ty
.ops
.from_iter_sim(
zeroed(ret_ty.ops.element()),
ops.iter().map(MockL2RegFileOp::debug_state),
)
.ok()
.expect("known to fit"),
l2_regs,
config,
}
}
fn try_op_by_id(&self, id: &SimValue<MOpId>) -> Option<&MockL2RegFileOp<C>> {
self.ops.iter().find(|op| op.id() == id)
}
fn try_op_by_id_mut(&mut self, id: &SimValue<MOpId>) -> Option<&mut MockL2RegFileOp<C>> {
self.ops.iter_mut().find(|op| op.id() == id)
}
#[track_caller]
fn op_by_id(&self, id: &SimValue<MOpId>) -> &MockL2RegFileOp<C> {
match self.try_op_by_id(id) {
Some(retval) => retval,
None => panic!("can't find MockL2RegFileOp with id: {id:?}"),
}
}
#[track_caller]
fn op_by_id_mut(&mut self, id: &SimValue<MOpId>) -> &mut MockL2RegFileOp<C> {
match self.try_op_by_id_mut(id) {
Some(retval) => retval,
None => panic!("can't find MockL2RegFileOp with id: {id:?}"),
}
}
#[hdl]
fn step(&mut self) {
for op in &mut self.ops {
let MockL2RegFileOp {
mop,
src_values,
dest_value,
sent_cant_cause_cancel: _,
sent_output_ready: _,
config: _,
} = op;
#[hdl(sim)]
match &mop.mop {
L2RegisterFileMOp::<_, _>::ReadL2Reg(mop) => {
#[hdl(sim)]
let ReadL2RegMOp::<_, _> { common } = mop;
if src_values.is_some() && dest_value.is_none() {
*dest_value = Some(self.l2_regs[L2RegNum::value_sim(&common.imm)].clone());
}
}
L2RegisterFileMOp::<_, _>::WriteL2Reg(mop) => {
#[hdl(sim)]
let WriteL2RegMOp::<_, _> { common } = mop;
if let Some(src_values) = src_values
&& dest_value.is_none()
{
self.l2_regs[L2RegNum::value_sim(&common.imm)] = src_values[0].clone();
*dest_value = Some(PRegValue::zeroed_sim());
}
if dest_value.is_none() {
// we can't run following reads yet.
break;
}
}
}
}
}
#[hdl]
fn handle_enqueue(&mut self, enqueue: SimValue<UnitEnqueue<C>>) {
#[hdl(sim)]
let UnitEnqueue::<_> { mop, config: _ } = enqueue;
#[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,
} = mop;
let mop = #[hdl(sim)]
match &mop {
RenamedMOp::<_>::TransformedMove(mop) => mop,
_ => {
panic!("MockL2RegFileUnitState can only handle L2RegisterFile MOps, got: {mop:#?}");
}
};
let mop = #[hdl(sim)]
MOpInstance::<_> {
fetch_block_id,
id,
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop,
};
self.ops.push_back(MockL2RegFileOp {
mop,
src_values: None,
dest_value: None,
sent_cant_cause_cancel: false,
sent_output_ready: false,
config: self.config,
});
}
#[hdl]
fn handle_inputs_ready(&mut self, inputs_ready: SimValue<UnitInputsReady<C>>) {
#[hdl(sim)]
let UnitInputsReady::<_> {
mop,
config: _,
src_values,
} = inputs_ready;
let MockL2RegFileOp {
mop: _,
src_values: op_src_values,
dest_value: _,
sent_cant_cause_cancel: _,
sent_output_ready: _,
config: _,
} = self.op_by_id_mut(&mop.id);
assert!(op_src_values.is_none());
*op_src_values = Some(SimValue::into_value(src_values));
}
#[hdl]
fn handle_mop_is_no_longer_speculative(
&mut self,
is_no_longer_speculative: SimValue<UnitMOpIsNoLongerSpeculative<C>>,
) {
#[hdl(sim)]
let UnitMOpIsNoLongerSpeculative::<_> { id, config: _ } = is_no_longer_speculative;
let MockL2RegFileOp {
mop: _,
src_values: _,
dest_value: _,
sent_cant_cause_cancel: _,
sent_output_ready: _,
config: _,
} = self.op_by_id_mut(&id);
}
#[hdl]
fn peek_mop_cant_cause_cancel(&self) -> SimValue<HdlOption<UnitMOpCantCauseCancel<C>>> {
let ret_ty = HdlOption[UnitMOpCantCauseCancel[self.config]];
for MockL2RegFileOp {
mop,
src_values: _,
dest_value: _,
sent_cant_cause_cancel,
sent_output_ready: _,
config: _,
} in &self.ops
{
if *sent_cant_cause_cancel {
continue;
}
// L2 reg file ops can never cause cancels
return #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitMOpCantCauseCancel::<_> {
id: &mop.id,
config: self.config,
},
);
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn handle_mop_cant_cause_cancel(
&mut self,
cant_cause_cancel: SimValue<UnitMOpCantCauseCancel<C>>,
) {
#[hdl(sim)]
let UnitMOpCantCauseCancel::<_> { id, config: _ } = cant_cause_cancel;
let op = self.op_by_id_mut(&id);
assert!(!op.sent_cant_cause_cancel);
op.sent_cant_cause_cancel = true;
}
#[hdl]
fn peek_output_ready(&self) -> SimValue<HdlOption<UnitOutputReady<C>>> {
let ret_ty = HdlOption[UnitOutputReady[self.config]];
for MockL2RegFileOp {
mop,
src_values: _,
dest_value,
sent_cant_cause_cancel: _,
sent_output_ready,
config: _,
} in &self.ops
{
if *sent_output_ready {
continue;
}
if let Some(dest_value) = dest_value {
return #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitOutputReady::<_> {
id: &mop.id,
dest_value,
predictor_op: #[hdl(sim)]
NextPcPredictorOp::<_> {
call_stack_op: #[hdl(sim)]
CallStackOp.None(),
cond_br_taken: #[hdl(sim)]
HdlNone(),
config: self.config,
},
},
);
}
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn handle_output_ready(&mut self, output_ready: SimValue<UnitOutputReady<C>>) {
#[hdl(sim)]
let UnitOutputReady::<_> {
id,
dest_value: _,
predictor_op: _,
} = output_ready;
let op = self.op_by_id_mut(&id);
assert!(!op.sent_output_ready);
op.sent_output_ready = true;
}
#[hdl]
fn peek_finish_cause_cancel(&self) -> SimValue<HdlOption<UnitFinishCauseCancel<C>>> {
let ret_ty = HdlOption[UnitFinishCauseCancel[self.config]];
if let Some(MockL2RegFileOp {
mop,
src_values: _,
dest_value,
sent_cant_cause_cancel: _,
sent_output_ready: _,
config: _,
}) = self.ops.front()
{
if dest_value.is_some() {
return #[hdl(sim)]
ret_ty.HdlSome(
#[hdl(sim)]
UnitFinishCauseCancel::<_> {
id: &mop.id,
caused_cancel: #[hdl(sim)]
(ret_ty.HdlSome.caused_cancel).HdlNone(),
config: self.config,
},
);
}
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn handle_finish_cause_cancel(
&mut self,
finish_cause_cancel: SimValue<UnitFinishCauseCancel<C>>,
) {
#[hdl(sim)]
let UnitFinishCauseCancel::<_> {
id,
caused_cancel: _,
config: _,
} = finish_cause_cancel;
let Some(op) = self.ops.pop_front() else {
unreachable!();
};
assert_eq!(*op.id(), id);
}
fn cancel_all(&mut self) {
let Self {
ops,
l2_regs: _,
config: _,
} = self;
ops.clear();
}
}
#[hdl_module(extern)]
fn mock_l2_reg_file_unit(config: PhantomConst<CpuConfig>, unit_index: usize) {
assert!(
config
.get()
.units
.iter()
.enumerate()
.all(|(i, unit)| (unit_index == i) == (unit.kind == UnitKind::TransformedMove)),
"unit index {unit_index} must be the only L2 register file unit: {config:#?}",
);
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
m.input(ExecuteToUnitInterface[config]);
#[hdl]
let debug_state: MockL2RegFileUnitDebugState<PhantomConst<CpuConfig>> =
m.output(MockL2RegFileUnitDebugState[config]);
m.register_clock_for_past(cd.clk);
m.extern_module_simulation_fn(
(cd, from_execute, debug_state, config),
async |args, mut sim| {
let (cd, from_execute, debug_state, config) = args;
sim.resettable(
cd,
async |mut sim| {
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready: _,
is_no_longer_speculative: _,
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready: _,
cancel_all,
config: _,
} = from_execute;
sim.write(enqueue.ready, false).await;
sim.write(cancel_all.ready, false).await;
sim.write(
cant_cause_cancel,
#[hdl(sim)]
(cant_cause_cancel.ty()).HdlNone(),
)
.await;
sim.write(
output_ready,
#[hdl(sim)]
(output_ready.ty()).HdlNone(),
)
.await;
sim.write(
finish_cause_cancel,
#[hdl(sim)]
(finish_cause_cancel.ty()).HdlNone(),
)
.await;
sim.write(
debug_state,
#[hdl(sim)]
MockL2RegFileUnitDebugState::<_> {
ops: zeroed(debug_state.ty().ops),
l2_regs: zeroed(debug_state.ty().l2_regs),
config,
},
)
.await;
},
|sim, ()| run_fn(cd, from_execute, debug_state, config, sim),
)
.await;
},
);
#[hdl]
async fn run_fn(
cd: Expr<ClockDomain>,
from_execute: Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>,
debug_state: Expr<MockL2RegFileUnitDebugState<PhantomConst<CpuConfig>>>,
config: PhantomConst<CpuConfig>,
mut sim: ExternModuleSimulationState,
) {
let mut state = MockL2RegFileUnitState::new(config);
loop {
{
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready: _,
is_no_longer_speculative: _,
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready: _,
cancel_all,
config: _,
} = from_execute;
sim.write(enqueue.ready, true).await;
sim.write(cancel_all.ready, true).await;
sim.write(cant_cause_cancel, state.peek_mop_cant_cause_cancel())
.await;
sim.write(output_ready, state.peek_output_ready()).await;
sim.write(finish_cause_cancel, state.peek_finish_cause_cancel())
.await;
}
sim.write(debug_state, state.debug_state()).await;
sim.wait_for_clock_edge(cd.clk).await;
{
#[hdl]
let ExecuteToUnitInterface::<_> {
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready,
cancel_all,
config: _,
} = from_execute;
if sim.read_past_bool(enqueue.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(enqueue) = sim.read_past(enqueue.data, cd.clk).await {
state.handle_enqueue(enqueue);
}
}
#[hdl(sim)]
if let HdlSome(inputs_ready) = sim.read_past(inputs_ready, cd.clk).await {
state.handle_inputs_ready(inputs_ready);
}
#[hdl(sim)]
if let HdlSome(is_no_longer_speculative) =
sim.read_past(is_no_longer_speculative, cd.clk).await
{
state.handle_mop_is_no_longer_speculative(is_no_longer_speculative);
}
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
{
state.handle_mop_cant_cause_cancel(cant_cause_cancel);
}
#[hdl(sim)]
if let HdlSome(output_ready) = sim.read_past(output_ready, cd.clk).await {
state.handle_output_ready(output_ready);
}
#[hdl(sim)]
if let HdlSome(finish_cause_cancel) =
sim.read_past(finish_cause_cancel, cd.clk).await
{
state.handle_finish_cause_cancel(finish_cause_cancel);
}
}
if sim.read_past_bool(cancel_all.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(cancel_all) = sim.read_past(cancel_all.data, cd.clk).await {
let () = *cancel_all;
state.cancel_all();
}
}
}
state.step();
}
}
}
#[hdl(no_static)] #[hdl(no_static)]
struct MockLoadStoreOpDebugState<C: PhantomConstGet<CpuConfig>> { struct MockLoadStoreOpDebugState<C: PhantomConstGet<CpuConfig>> {
mop: MOpInstance<LoadStoreMOp<PRegNum<C>, PRegNum<C>>>, mop: MOpInstance<LoadStoreMOp<PRegNum<C>, PRegNum<C>>>,
@ -2897,7 +3427,8 @@ fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(config: Phanto
.into_iter() .into_iter()
.enumerate() .enumerate()
{ {
if config.get().units[unit_index].kind == UnitKind::LoadStore { match config.get().units[unit_index].kind {
UnitKind::LoadStore => {
let mock_unit = instance_with_loc( let mock_unit = instance_with_loc(
&dut.ty().to_units.unit_field_name(unit_index), &dut.ty().to_units.unit_field_name(unit_index),
mock_load_store_unit::<MI>(config, unit_index), mock_load_store_unit::<MI>(config, unit_index),
@ -2909,20 +3440,26 @@ fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(config: Phanto
if !mock_unit.all_outputs_written { if !mock_unit.all_outputs_written {
connect(all_outputs_written, false); connect(all_outputs_written, false);
} }
} else { }
let mock_unit_module = if is_the_l2_reg_file_unit(config, unit_index) { UnitKind::TransformedMove => {
mock_unit::<MockL2RegFile>(config, unit_index)
} else {
mock_unit::<()>(config, unit_index)
};
let mock_unit = instance_with_loc( let mock_unit = instance_with_loc(
&dut.ty().to_units.unit_field_name(unit_index), &dut.ty().to_units.unit_field_name(unit_index),
mock_unit_module, mock_l2_reg_file_unit(config, unit_index),
SourceLocation::caller(), SourceLocation::caller(),
); );
connect(mock_unit.cd, cd); connect(mock_unit.cd, cd);
connect(mock_unit.from_execute, to_unit); connect(mock_unit.from_execute, to_unit);
} }
UnitKind::AluBranch => {
let mock_unit = instance_with_loc(
&dut.ty().to_units.unit_field_name(unit_index),
mock_unit::<()>(config, unit_index),
SourceLocation::caller(),
);
connect(mock_unit.cd, cd);
connect(mock_unit.from_execute, to_unit);
}
}
} }
} }
@ -2977,3 +3514,135 @@ fn test_rename_execute_retire_fibonacci() {
panic!(); panic!();
} }
} }
struct SlowLoopInsns;
impl SlowLoopInsns {
const CONSTANTS_ADDR: u64 = 0x4000;
const CONSTANTS_COUNT: usize = 16;
const CONSTANTS_STEP: usize = 8;
const LOG2_RESULT_FACTOR: u32 = 2;
}
impl MakeInsns for SlowLoopInsns {
fn make_insns() -> Insns {
let mut b = InsnsBuilder::new();
let slow_loop = b.new_label("slow_loop");
b.power_isa_ld(3, 0, MockMemory::IO_ADDR as i16); // load input
b.power_isa_addi(1, 0, 0x4000); // setup stack pointer
b.power_isa_bl(slow_loop);
b.power_isa_std(3, 0, MockMemory::IO_ADDR as i16); // store output
let done = b.new_defined_label("done");
b.power_isa_b(done);
b.set_pc(0x1000);
b.define_label(slow_loop);
b.power_isa_addi(4, 0, 0); // clear sum
let loop_header = b.new_defined_label("loop_header");
b.power_isa_cmpldi(0, 3, 0);
let loop_done = b.new_label("loop_done");
b.power_isa_beq(loop_done); // if input == 0 goto loop_done
// long sequence of loads to provoke L2 register file store
let start_reg = 5;
assert!(
start_reg + Self::CONSTANTS_COUNT <= 32,
"too many constants to load them all into PowerISA GPRs",
);
for i in 0..Self::CONSTANTS_COUNT {
b.power_isa_ld(
start_reg + i,
0,
(Self::CONSTANTS_ADDR + (Self::CONSTANTS_STEP * i) as u64) as i16,
);
}
for i in 0..Self::CONSTANTS_COUNT {
b.power_isa_add(4, 4, start_reg + i);
}
b.power_isa_addi(3, 3, -1);
b.power_isa_b(loop_header);
b.define_label(loop_done);
b.power_isa_mr(3, 4);
for _ in 0..Self::LOG2_RESULT_FACTOR {
b.power_isa_add(3, 3, 3);
}
b.power_isa_blr(); // return
b.build()
}
fn make_load_store_execution_state() -> MockMemory {
let expected = 0x0123_4567_89AB_CDEF_u64;
let constants: [[u8; Self::CONSTANTS_STEP]; Self::CONSTANTS_COUNT] =
std::array::from_fn(|i| {
let start_bit_index = i * 64 / Self::CONSTANTS_COUNT;
let end_bit_index = (i + 1) * 64 / Self::CONSTANTS_COUNT;
let start_bit = 1u64.unbounded_shl(start_bit_index as u32);
let end_bit = 1u64.unbounded_shl(end_bit_index as u32);
let mask = end_bit.wrapping_sub(start_bit);
(expected & mask).to_le_bytes()
});
let loop_count = 4;
MockMemory::new(
loop_count,
expected * loop_count * 2u64.pow(Self::LOG2_RESULT_FACTOR),
[(Self::CONSTANTS_ADDR, constants.as_flattened())],
)
}
}
#[hdl]
#[test]
fn test_rename_execute_retire_slow_loop() {
let _n = SourceLocation::normalize_files_for_tests();
let mut config = CpuConfig::new(
vec![
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::LoadStore),
UnitConfig::new(UnitKind::TransformedMove),
],
NonZeroUsize::new(20).unwrap(),
);
config.fetch_width = NonZeroUsize::new(4).unwrap();
let m = rename_execute_retire_test_harness::<SlowLoopInsns>(PhantomConst::new_sized(config));
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
for cycle in 0..200 {
sim.advance_time(SimDuration::from_nanos(500));
println!("clock tick: {cycle}");
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert!(sim.read_bool(sim.io().all_outputs_written));
// FIXME: vcd is just whatever rename_execute_retire does now, which isn't known to be correct
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/rename_execute_retire_slow_loop.vcd") {
panic!();
}
}