forked from libre-chip/cpu
Compare commits
10 commits
2363e65564
...
a88009a303
| Author | SHA1 | Date | |
|---|---|---|---|
| a88009a303 | |||
| ce8519b2db | |||
| 151683fbda | |||
| e0dc5d486b | |||
| fdf1e97e10 | |||
| bf2cb688c7 | |||
| 3e08a282ec | |||
| 6026df8d7a | |||
| e502dfe574 | |||
| 0d69666b00 |
10 changed files with 459209 additions and 200240 deletions
File diff suppressed because it is too large
Load diff
311
crates/cpu/src/rename_execute_retire/rename_table.rs
Normal file
311
crates/cpu/src/rename_execute_retire/rename_table.rs
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{
|
||||
config::{CpuConfig, CpuConfig2PowOutRegNumWidth, CpuConfigUnitCount, PhantomConstCpuConfig},
|
||||
instruction::{L2RegNum, MOpRegNum, PRegNum, UnitNum, UnitOutRegNum},
|
||||
rename_execute_retire::L2RegFileLen,
|
||||
};
|
||||
use fayalite::{int::UIntInRangeInclusiveType, prelude::*};
|
||||
|
||||
#[hdl(no_static)]
|
||||
pub(crate) struct RenameTableEntry<C: PhantomConstGet<CpuConfig>> {
|
||||
pub(crate) l1: HdlOption<PRegNum<C>>,
|
||||
pub(crate) l2: HdlOption<L2RegNum>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RenameTableEntry<C> {
|
||||
#[hdl]
|
||||
pub(crate) fn const_zero(self) -> SimValue<Self> {
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
l1: #[hdl(sim)]
|
||||
(self.l1).HdlSome(self.l1.HdlSome.const_zero()),
|
||||
l2: #[hdl(sim)]
|
||||
HdlNone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// make arrays dynamically-sized to avoid putting large types on the stack
|
||||
#[hdl(get(|c| 1 << MOpRegNum::WIDTH))]
|
||||
type MOpRegCount<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(no_static)]
|
||||
pub(crate) struct RenameTableDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
entries: ArrayType<TraceAsString<RenameTableEntry<C>>, MOpRegCount<C>>,
|
||||
l1_ref_counts: ArrayType<
|
||||
ArrayType<
|
||||
UIntInRangeInclusiveType<ConstUsize<0>, MOpRegCount<C>>,
|
||||
CpuConfig2PowOutRegNumWidth<C>,
|
||||
>,
|
||||
CpuConfigUnitCount<C>,
|
||||
>,
|
||||
l2_ref_counts:
|
||||
ArrayType<UIntInRangeInclusiveType<ConstUsize<0>, MOpRegCount<C>>, L2RegFileLen<C>>,
|
||||
config: C,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RenameTable<C: PhantomConstCpuConfig> {
|
||||
entries: Box<[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH]>,
|
||||
l1_ref_counts: Box<[Box<[usize]>]>,
|
||||
l2_ref_counts: Box<[usize]>,
|
||||
config: C,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> Clone for RenameTable<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
entries: self.entries.clone(),
|
||||
config: self.config.clone(),
|
||||
l1_ref_counts: self.l1_ref_counts.clone(),
|
||||
l2_ref_counts: self.l2_ref_counts.clone(),
|
||||
}
|
||||
}
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
let Self {
|
||||
entries,
|
||||
l1_ref_counts,
|
||||
l2_ref_counts,
|
||||
config,
|
||||
} = self;
|
||||
entries.clone_from(&source.entries);
|
||||
l1_ref_counts.clone_from(&source.l1_ref_counts);
|
||||
l2_ref_counts.clone_from(&source.l2_ref_counts);
|
||||
*config = source.config;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum RenameTableUpdate<C: PhantomConstCpuConfig> {
|
||||
Write {
|
||||
unrenamed_reg_num: u32,
|
||||
new: SimValue<TraceAsString<RenameTableEntry<C>>>,
|
||||
},
|
||||
UpdateForReadL2Reg {
|
||||
dest: SimValue<PRegNum<C>>,
|
||||
src: SimValue<L2RegNum>,
|
||||
},
|
||||
UpdateForWriteL2Reg {
|
||||
dest: SimValue<L2RegNum>,
|
||||
src: SimValue<PRegNum<C>>,
|
||||
},
|
||||
DropAllL2RegFileOutputs,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RenameTable<C> {
|
||||
pub(crate) fn new(config: C) -> Self {
|
||||
let entries: Box<[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH]> =
|
||||
vec![
|
||||
RenameTableEntry[config].const_zero().into_trace_as_string();
|
||||
1 << MOpRegNum::WIDTH
|
||||
]
|
||||
.try_into()
|
||||
.expect("size is known to match");
|
||||
Self {
|
||||
entries,
|
||||
l1_ref_counts: vec![
|
||||
vec![0; CpuConfig2PowOutRegNumWidth[config]].into_boxed_slice();
|
||||
CpuConfigUnitCount[config]
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
l2_ref_counts: vec![0; L2RegFileLen[config]].into_boxed_slice(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
pub(crate) fn entries(
|
||||
&self,
|
||||
) -> &[SimValue<TraceAsString<RenameTableEntry<C>>>; 1 << MOpRegNum::WIDTH] {
|
||||
&self.entries
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn to_debug_state(&self) -> SimValue<RenameTableDebugState<C>> {
|
||||
let Self {
|
||||
entries,
|
||||
l1_ref_counts,
|
||||
l2_ref_counts,
|
||||
config,
|
||||
} = self;
|
||||
let ty = RenameTableDebugState[*config];
|
||||
#[hdl(sim)]
|
||||
RenameTableDebugState::<_> {
|
||||
entries: entries.to_sim_value_with_type(ty.entries),
|
||||
l1_ref_counts: l1_ref_counts.to_sim_value_with_type(ty.l1_ref_counts),
|
||||
l2_ref_counts: l2_ref_counts.to_sim_value_with_type(ty.l2_ref_counts),
|
||||
config,
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn l1_ref_counts(&self) -> &[Box<[usize]>] {
|
||||
let mut expected = vec![
|
||||
vec![0usize; CpuConfig2PowOutRegNumWidth[self.config]]
|
||||
.into_boxed_slice();
|
||||
CpuConfigUnitCount[self.config]
|
||||
];
|
||||
for entry in self.entries.iter() {
|
||||
#[hdl(sim)]
|
||||
let RenameTableEntry::<_> { l1, l2: _ } = entry.inner();
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l1) = l1 {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
expected[unit_index][UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(*expected, *self.l1_ref_counts);
|
||||
&self.l1_ref_counts
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn l2_ref_counts(&self) -> &[usize] {
|
||||
let mut expected = vec![0usize; L2RegNum.l2_reg_count()];
|
||||
for entry in self.entries.iter() {
|
||||
#[hdl(sim)]
|
||||
let RenameTableEntry::<_> { l1: _, l2 } = entry.inner();
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l2) = l2 {
|
||||
expected[L2RegNum::value_sim(l2)] += 1
|
||||
}
|
||||
}
|
||||
assert_eq!(*expected, *self.l2_ref_counts);
|
||||
&self.l2_ref_counts
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn update<'a>(&mut self, update: &RenameTableUpdate<C>, rename_table_name: &str) {
|
||||
let mut update_entry =
|
||||
|entry: &mut SimValue<TraceAsString<RenameTableEntry<C>>>,
|
||||
new: SimValue<TraceAsString<RenameTableEntry<C>>>| {
|
||||
#[hdl(sim)]
|
||||
let RenameTableEntry::<_> { l1, l2 } = entry.inner();
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l1) = l1 {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
let ref_count = &mut self.l1_ref_counts[unit_index]
|
||||
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
|
||||
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
|
||||
unreachable!("{rename_table_name}: l1 ref count went negative: {l1:?}")
|
||||
});
|
||||
}
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l2) = l2 {
|
||||
let ref_count = &mut self.l2_ref_counts[L2RegNum::value_sim(l2)];
|
||||
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
|
||||
unreachable!("{rename_table_name}: l2 ref count went negative: {l2:?}")
|
||||
});
|
||||
}
|
||||
#[hdl(sim)]
|
||||
let RenameTableEntry::<_> { l1, l2 } = new.inner();
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l1) = l1 {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
let ref_count = &mut self.l1_ref_counts[unit_index]
|
||||
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
|
||||
*ref_count += 1;
|
||||
assert!(
|
||||
*ref_count <= 1 << MOpRegNum::WIDTH,
|
||||
"{rename_table_name}: l1 ref count overflowed: {l1:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l2) = l2 {
|
||||
let ref_count = &mut self.l2_ref_counts[L2RegNum::value_sim(l2)];
|
||||
*ref_count += 1;
|
||||
assert!(
|
||||
*ref_count <= 1 << MOpRegNum::WIDTH,
|
||||
"{rename_table_name}: l2 ref count overflowed: {l2:?}",
|
||||
);
|
||||
}
|
||||
*entry = new;
|
||||
};
|
||||
match update {
|
||||
RenameTableUpdate::Write {
|
||||
unrenamed_reg_num,
|
||||
new,
|
||||
} => {
|
||||
if *unrenamed_reg_num == MOpRegNum::CONST_ZERO_REG_NUM {
|
||||
// writing to const zero reg does nothing
|
||||
return;
|
||||
}
|
||||
println!("{rename_table_name}: Write: {unrenamed_reg_num:#x} <- {new:?}");
|
||||
update_entry(&mut self.entries[*unrenamed_reg_num as usize], new.clone());
|
||||
}
|
||||
RenameTableUpdate::UpdateForReadL2Reg { dest, src } => {
|
||||
let new = #[hdl(sim)]
|
||||
RenameTableEntry::<_> {
|
||||
l1: #[hdl(sim)]
|
||||
(HdlOption[dest.ty()]).HdlSome(dest),
|
||||
l2: #[hdl(sim)]
|
||||
HdlSome(src),
|
||||
};
|
||||
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l2) = &entry.inner().l2 {
|
||||
if L2RegNum::value_sim(l2) == L2RegNum::value_sim(src) {
|
||||
println!(
|
||||
"{rename_table_name}: UpdateForReadL2Reg: {unrenamed_reg_num:#x} \
|
||||
updating from {entry:?} to {new:?}",
|
||||
);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = &entry.inner().l1 {
|
||||
unreachable!("l1 should be HdlNone: {entry:?}");
|
||||
}
|
||||
update_entry(entry, new.to_trace_as_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RenameTableUpdate::UpdateForWriteL2Reg { dest, src } => {
|
||||
let new = #[hdl(sim)]
|
||||
RenameTableEntry::<_> {
|
||||
l1: #[hdl(sim)]
|
||||
(HdlOption[src.ty()]).HdlNone(),
|
||||
l2: #[hdl(sim)]
|
||||
HdlSome(dest),
|
||||
};
|
||||
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(l1) = &entry.inner().l1 {
|
||||
if l1 == src {
|
||||
println!(
|
||||
"{rename_table_name}: UpdateForWriteL2Reg: {unrenamed_reg_num:#x} \
|
||||
updating from {entry:?} to {new:?}",
|
||||
);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = &entry.inner().l2 {
|
||||
unreachable!("l2 should be HdlNone: {entry:?}");
|
||||
}
|
||||
update_entry(entry, new.to_trace_as_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RenameTableUpdate::DropAllL2RegFileOutputs => {
|
||||
for (unrenamed_reg_num, entry) in self.entries.iter_mut().enumerate() {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = &entry.inner().l1 {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = &entry.inner().l2 {
|
||||
let mut new = entry.inner().clone();
|
||||
new.l1 = #[hdl(sim)]
|
||||
(new.l1.ty()).HdlNone();
|
||||
println!(
|
||||
"{rename_table_name}: DropAllL2RegFileOutputs: {unrenamed_reg_num:#x} \
|
||||
updating from {entry:?} to {new:?}",
|
||||
);
|
||||
update_entry(entry, new.to_trace_as_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn used_unit_out_reg_count(&self, unit_index: usize) -> usize {
|
||||
self.l1_ref_counts()[unit_index]
|
||||
.iter()
|
||||
.filter(|ref_count| **ref_count != 0)
|
||||
.count()
|
||||
}
|
||||
}
|
||||
564
crates/cpu/src/rename_execute_retire/reorder_buffer.rs
Normal file
564
crates/cpu/src/rename_execute_retire/reorder_buffer.rs
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{
|
||||
config::{
|
||||
CpuConfig, CpuConfig2PowOutRegNumWidth, CpuConfigRobSize, CpuConfigUnitCount,
|
||||
PhantomConstCpuConfig,
|
||||
},
|
||||
instruction::{
|
||||
COMMON_MOP_SRC_LEN, L2RegNum, L2RegisterFileMOp, MOp, MOpTrait, PRegNum, UnitNum,
|
||||
UnitOutRegNum,
|
||||
},
|
||||
next_pc::SimValueDefault,
|
||||
rename_execute_retire::{
|
||||
L2RegFileLen, MOpId, MOpInUnitState, MOpInstance, NextPcPredictorOp, RenamedMOp,
|
||||
UnitCausedCancel, rename_table::RenameTableUpdate, zeroed,
|
||||
},
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
int::{UIntInRangeInclusiveType, UIntInRangeType},
|
||||
prelude::*,
|
||||
ty::StaticType,
|
||||
};
|
||||
use std::{collections::VecDeque, mem};
|
||||
|
||||
#[hdl]
|
||||
type SimOnlyMOpInUnitState = SimOnly<MOpInUnitState>;
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct RobEntryDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
/// See [`RobEntry::is_register_fence`]
|
||||
is_register_fence: Bool,
|
||||
/// See [`RobEntry::is_register_fence`]
|
||||
done_waiting_for_register_fences: Bool,
|
||||
mop: MOpInstance<RenamedMOp<C>>,
|
||||
unit_index: UIntInRangeType<ConstUsize<0>, CpuConfigUnitCount<C>>,
|
||||
mop_in_unit_state: SimOnlyMOpInUnitState,
|
||||
is_speculative: Bool,
|
||||
all_prior_mops_finished_and_or_caused_cancel: Bool,
|
||||
output: HdlOption<NextPcPredictorOp<C>>,
|
||||
caused_cancel: HdlOption<UnitCausedCancel<C>>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> SimValueDefault for RobEntryDebugState<C> {
|
||||
#[hdl]
|
||||
fn sim_value_default(self) -> SimValue<Self> {
|
||||
let Self {
|
||||
mop,
|
||||
is_register_fence: _,
|
||||
done_waiting_for_register_fences: _,
|
||||
unit_index,
|
||||
mop_in_unit_state: _,
|
||||
is_speculative: _,
|
||||
all_prior_mops_finished_and_or_caused_cancel: _,
|
||||
output,
|
||||
caused_cancel,
|
||||
} = self;
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
mop: zeroed(mop),
|
||||
is_register_fence: false,
|
||||
done_waiting_for_register_fences: false,
|
||||
unit_index: zeroed(unit_index),
|
||||
mop_in_unit_state: SimOnlyValue::default(),
|
||||
is_speculative: false,
|
||||
all_prior_mops_finished_and_or_caused_cancel: false,
|
||||
output: #[hdl(sim)]
|
||||
output.HdlNone(),
|
||||
caused_cancel: #[hdl(sim)]
|
||||
caused_cancel.HdlNone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RobEntry<C: PhantomConstCpuConfig> {
|
||||
/// Block this and all later µOps until all prior µOps are finished and didn't cause a cancel.
|
||||
/// Not to be confused with memory fences.
|
||||
pub(crate) is_register_fence: bool,
|
||||
/// See [`Self::is_register_fence`]
|
||||
pub(crate) done_waiting_for_register_fences: bool,
|
||||
pub(crate) mop: SimValue<MOpInstance<RenamedMOp<C>>>,
|
||||
pub(crate) unit_index: usize,
|
||||
pub(crate) mop_in_unit_state: MOpInUnitState,
|
||||
pub(crate) is_speculative: bool,
|
||||
pub(crate) all_prior_mops_finished_and_or_caused_cancel: bool,
|
||||
pub(crate) output: Option<SimValue<NextPcPredictorOp<C>>>,
|
||||
pub(crate) caused_cancel: Option<SimValue<UnitCausedCancel<C>>>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RobEntry<C> {
|
||||
pub(crate) fn new(
|
||||
mop: SimValue<MOpInstance<RenamedMOp<C>>>,
|
||||
unit_index: usize,
|
||||
is_register_fence: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
mop,
|
||||
is_register_fence,
|
||||
done_waiting_for_register_fences: false,
|
||||
unit_index,
|
||||
mop_in_unit_state: MOpInUnitState::NotYetEnqueued,
|
||||
is_speculative: true,
|
||||
all_prior_mops_finished_and_or_caused_cancel: false,
|
||||
output: None,
|
||||
caused_cancel: None,
|
||||
}
|
||||
}
|
||||
fn dest_reg(&self) -> Option<&SimValue<PRegNum<C>>> {
|
||||
let dest_reg = MOpTrait::dest_reg_sim_ref(self.mop.mop.inner());
|
||||
let unit_index = UnitNum::index_sim(&dest_reg.unit_num)?;
|
||||
assert_eq!(unit_index, self.unit_index);
|
||||
Some(dest_reg)
|
||||
}
|
||||
fn unit_out_reg(&self) -> Option<&SimValue<UnitOutRegNum<C>>> {
|
||||
Some(&self.dest_reg()?.unit_out_reg)
|
||||
}
|
||||
pub(crate) fn unit_out_reg_index(&self) -> Option<usize> {
|
||||
Some(UnitOutRegNum::value_sim(self.unit_out_reg()?))
|
||||
}
|
||||
#[hdl]
|
||||
fn debug_state(&self, config: C) -> SimValue<RobEntryDebugState<C>> {
|
||||
let Self {
|
||||
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;
|
||||
let ret_ty = RobEntryDebugState[config];
|
||||
#[hdl(sim)]
|
||||
RobEntryDebugState::<C> {
|
||||
is_register_fence,
|
||||
done_waiting_for_register_fences,
|
||||
mop,
|
||||
unit_index: unit_index.into_sim_value_with_type(ret_ty.unit_index),
|
||||
mop_in_unit_state: SimOnlyValue::new(*mop_in_unit_state),
|
||||
is_speculative,
|
||||
all_prior_mops_finished_and_or_caused_cancel,
|
||||
output: output.into_sim_value_with_type(ret_ty.output),
|
||||
caused_cancel: caused_cancel.into_sim_value_with_type(ret_ty.caused_cancel),
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
fn for_each_reg<S>(
|
||||
&self,
|
||||
mut shared_state: S,
|
||||
mut l1_reg: impl FnMut(&mut S, &SimValue<PRegNum<C>>),
|
||||
mut l2_reg: impl FnMut(&mut S, &SimValue<L2RegNum>),
|
||||
) {
|
||||
l1_reg(
|
||||
&mut shared_state,
|
||||
MOpTrait::dest_reg_sim_ref(self.mop.mop.inner()),
|
||||
);
|
||||
MOpTrait::for_each_src_reg_sim_ref(self.mop.mop.inner(), &mut |l1, _| {
|
||||
l1_reg(&mut shared_state, l1);
|
||||
});
|
||||
#[hdl(sim)]
|
||||
if let RenamedMOp::<_>::TransformedMove(mop) = self.mop.mop.inner() {
|
||||
#[hdl(sim)]
|
||||
match mop {
|
||||
L2RegisterFileMOp::<_, _>::ReadL2Reg(v) => {
|
||||
l2_reg(&mut shared_state, &v.common.imm);
|
||||
}
|
||||
L2RegisterFileMOp::<_, _>::WriteL2Reg(v) => {
|
||||
l2_reg(&mut shared_state, &v.common.imm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
struct RobEntriesDebugState {
|
||||
unrenamed: MOpInstance<MOp>,
|
||||
/// number of renamed µOps that this unrenamed µOp corresponds to
|
||||
renamed_entries_len: UInt<8>,
|
||||
}
|
||||
|
||||
impl SimValueDefault for RobEntriesDebugState {
|
||||
#[hdl]
|
||||
fn sim_value_default(self) -> SimValue<Self> {
|
||||
let Self {
|
||||
unrenamed,
|
||||
renamed_entries_len,
|
||||
} = self;
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
unrenamed: zeroed(unrenamed),
|
||||
renamed_entries_len: renamed_entries_len.sim_value_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RobEntries<C: PhantomConstCpuConfig> {
|
||||
unrenamed: SimValue<MOpInstance<MOp>>,
|
||||
rename_table_updates: Vec<RenameTableUpdate<C>>,
|
||||
renamed_entries: VecDeque<RobEntry<C>>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RobEntries<C> {
|
||||
#[hdl]
|
||||
fn debug_state(&self) -> SimValue<RobEntriesDebugState> {
|
||||
let Self {
|
||||
unrenamed,
|
||||
rename_table_updates: _,
|
||||
renamed_entries,
|
||||
} = self;
|
||||
#[hdl(sim)]
|
||||
RobEntriesDebugState {
|
||||
unrenamed,
|
||||
renamed_entries_len: u8::try_from(renamed_entries.len())
|
||||
.expect("renamed_entries.len() should fit in u8"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl(get(|c| c.rob_size.get() * (1 + COMMON_MOP_SRC_LEN)))]
|
||||
type L1RegMaxRefCount<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(no_static)]
|
||||
pub(crate) struct ReorderBufferDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
next_renamed_mop_id: MOpId,
|
||||
entries: ArrayVec<RobEntriesDebugState, CpuConfigRobSize<C>>,
|
||||
incomplete_back_entry: HdlOption<RobEntriesDebugState>,
|
||||
renamed: ArrayVec<RobEntryDebugState<C>, CpuConfigRobSize<C>>,
|
||||
l1_reg_ref_counts: ArrayType<
|
||||
ArrayType<
|
||||
UIntInRangeInclusiveType<ConstUsize<0>, L1RegMaxRefCount<C>>,
|
||||
CpuConfig2PowOutRegNumWidth<C>,
|
||||
>,
|
||||
CpuConfigUnitCount<C>,
|
||||
>,
|
||||
l2_reg_ref_counts:
|
||||
ArrayType<UIntInRangeInclusiveType<ConstUsize<0>, CpuConfigRobSize<C>>, L2RegFileLen<C>>,
|
||||
config: C,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> SimValueDefault for ReorderBufferDebugState<C> {
|
||||
#[hdl]
|
||||
fn sim_value_default(self) -> SimValue<Self> {
|
||||
let Self {
|
||||
next_renamed_mop_id,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
renamed,
|
||||
l1_reg_ref_counts,
|
||||
l2_reg_ref_counts,
|
||||
config,
|
||||
} = self;
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
next_renamed_mop_id: next_renamed_mop_id.sim_value_default(),
|
||||
entries: entries.sim_value_default(),
|
||||
incomplete_back_entry: incomplete_back_entry.sim_value_default(),
|
||||
renamed: renamed.sim_value_default(),
|
||||
l1_reg_ref_counts: zeroed(l1_reg_ref_counts),
|
||||
l2_reg_ref_counts: zeroed(l2_reg_ref_counts),
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReorderBuffer<C: PhantomConstCpuConfig> {
|
||||
next_renamed_mop_id: SimValue<MOpId>,
|
||||
entries: VecDeque<RobEntries<C>>,
|
||||
incomplete_back_entry: Option<RobEntries<C>>,
|
||||
l1_reg_ref_counts: Box<[Box<[usize]>]>,
|
||||
l2_reg_ref_counts: Box<[usize]>,
|
||||
config: C,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> ReorderBuffer<C> {
|
||||
pub(crate) fn new(config: C) -> Self {
|
||||
Self {
|
||||
next_renamed_mop_id: MOpId.zero().into_sim_value(),
|
||||
entries: VecDeque::new(),
|
||||
incomplete_back_entry: None,
|
||||
l1_reg_ref_counts: vec![
|
||||
vec![0; CpuConfig2PowOutRegNumWidth[config]].into_boxed_slice();
|
||||
CpuConfigUnitCount[config]
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
l2_reg_ref_counts: vec![0; L2RegNum.l2_reg_count()].into_boxed_slice(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn l1_reg_ref_counts(&self) -> &[Box<[usize]>] {
|
||||
let mut expected = vec![
|
||||
vec![0usize; CpuConfig2PowOutRegNumWidth[self.config]]
|
||||
.into_boxed_slice();
|
||||
CpuConfigUnitCount[self.config]
|
||||
];
|
||||
for entry in self.renamed() {
|
||||
entry.for_each_reg(
|
||||
(),
|
||||
|(), l1| {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
expected[unit_index][UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1
|
||||
}
|
||||
},
|
||||
|(), _l2| {},
|
||||
);
|
||||
}
|
||||
assert_eq!(*expected, *self.l1_reg_ref_counts);
|
||||
&self.l1_reg_ref_counts
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn l2_reg_ref_counts(&self) -> &[usize] {
|
||||
let mut expected = vec![0usize; L2RegNum.l2_reg_count()];
|
||||
for entry in self.renamed() {
|
||||
entry.for_each_reg(
|
||||
(),
|
||||
|(), _l1| {},
|
||||
|(), l2| expected[L2RegNum::value_sim(l2)] += 1,
|
||||
);
|
||||
}
|
||||
assert_eq!(*expected, *self.l2_reg_ref_counts);
|
||||
&self.l2_reg_ref_counts
|
||||
}
|
||||
#[hdl]
|
||||
pub(crate) fn debug_state(&self) -> SimValue<ReorderBufferDebugState<C>> {
|
||||
let Self {
|
||||
next_renamed_mop_id,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
l1_reg_ref_counts,
|
||||
l2_reg_ref_counts,
|
||||
config,
|
||||
} = self;
|
||||
let ty = ReorderBufferDebugState[*config];
|
||||
#[hdl(sim)]
|
||||
ReorderBufferDebugState::<_> {
|
||||
next_renamed_mop_id,
|
||||
entries: ty
|
||||
.entries
|
||||
.from_iter_sim(
|
||||
zeroed(StaticType::TYPE),
|
||||
entries.iter().map(RobEntries::debug_state),
|
||||
)
|
||||
.expect("known to fit"),
|
||||
incomplete_back_entry: incomplete_back_entry.as_ref().map(|v| v.debug_state()),
|
||||
renamed: ty
|
||||
.renamed
|
||||
.from_iter_sim(
|
||||
ty.renamed.element().sim_value_default(),
|
||||
self.renamed().map(|v| v.debug_state(*config)),
|
||||
)
|
||||
.ok()
|
||||
.expect("known to fit"),
|
||||
l1_reg_ref_counts: l1_reg_ref_counts.to_sim_value_with_type(ty.l1_reg_ref_counts),
|
||||
l2_reg_ref_counts: l2_reg_ref_counts.to_sim_value_with_type(ty.l2_reg_ref_counts),
|
||||
config,
|
||||
}
|
||||
}
|
||||
pub(crate) fn unrenamed_len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
pub(crate) fn unrenamed(
|
||||
&self,
|
||||
) -> impl DoubleEndedIterator<Item = &SimValue<MOpInstance<MOp>>> + Clone {
|
||||
self.entries.iter().map(|v| &v.unrenamed)
|
||||
}
|
||||
fn retire_groups_unrenamed_ranges(
|
||||
&self,
|
||||
) -> impl Clone + Iterator<Item = std::ops::Range<usize>> {
|
||||
let mut next_group_start = 0;
|
||||
self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(index, entry)| {
|
||||
if *entry.unrenamed.is_last_mop_in_insn {
|
||||
let group_start = next_group_start;
|
||||
next_group_start = index + 1;
|
||||
Some(group_start..next_group_start)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
pub(crate) fn retire_groups(
|
||||
&self,
|
||||
) -> impl Clone + Iterator<Item: DoubleEndedIterator<Item = &VecDeque<RobEntry<C>>> + Clone>
|
||||
{
|
||||
self.retire_groups_unrenamed_ranges().map(|range| {
|
||||
self.entries
|
||||
.range(range)
|
||||
.map(|entries| &entries.renamed_entries)
|
||||
})
|
||||
}
|
||||
pub(crate) fn renamed_len(&self) -> usize {
|
||||
let Self {
|
||||
next_renamed_mop_id: _,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
l1_reg_ref_counts: _,
|
||||
l2_reg_ref_counts: _,
|
||||
config: _,
|
||||
} = self;
|
||||
entries
|
||||
.iter()
|
||||
.chain(incomplete_back_entry)
|
||||
.map(|entries| entries.renamed_entries.len())
|
||||
.sum()
|
||||
}
|
||||
pub(crate) fn renamed(&self) -> impl DoubleEndedIterator<Item = &RobEntry<C>> + Clone {
|
||||
let Self {
|
||||
next_renamed_mop_id: _,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
l1_reg_ref_counts: _,
|
||||
l2_reg_ref_counts: _,
|
||||
config: _,
|
||||
} = self;
|
||||
entries
|
||||
.iter()
|
||||
.chain(incomplete_back_entry)
|
||||
.flat_map(|entries| &entries.renamed_entries)
|
||||
}
|
||||
pub(crate) fn renamed_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut RobEntry<C>> {
|
||||
let Self {
|
||||
next_renamed_mop_id: _,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
l1_reg_ref_counts: _,
|
||||
l2_reg_ref_counts: _,
|
||||
config: _,
|
||||
} = self;
|
||||
entries
|
||||
.iter_mut()
|
||||
.chain(incomplete_back_entry)
|
||||
.flat_map(|entries| &mut entries.renamed_entries)
|
||||
}
|
||||
fn try_renamed_by_id_mut(&mut self, id: &SimValue<MOpId>) -> Option<&mut RobEntry<C>> {
|
||||
self.renamed_mut().find(|v| v.mop.id == *id)
|
||||
}
|
||||
pub(crate) fn renamed_by_id_mut(&mut self, id: &SimValue<MOpId>) -> &mut RobEntry<C> {
|
||||
match self.try_renamed_by_id_mut(id) {
|
||||
Some(v) => v,
|
||||
None => panic!("MOpId not found: {id:?}"),
|
||||
}
|
||||
}
|
||||
pub(crate) fn renamed_push_back_with_new_id(
|
||||
&mut self,
|
||||
unrenamed: &SimValue<MOpInstance<MOp>>,
|
||||
mut renamed: RobEntry<C>,
|
||||
) -> &SimValue<MOpId> {
|
||||
let replacement_id = self
|
||||
.next_renamed_mop_id
|
||||
.as_int()
|
||||
.wrapping_add(1)
|
||||
.into_sim_value();
|
||||
renamed.mop.id = mem::replace(&mut self.next_renamed_mop_id, replacement_id);
|
||||
println!("renamed_push_back_with_new_id: {:?}", renamed.mop);
|
||||
renamed.for_each_reg(
|
||||
(),
|
||||
|(), l1| {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
self.l1_reg_ref_counts[unit_index]
|
||||
[UnitOutRegNum::value_sim(&l1.unit_out_reg)] += 1;
|
||||
}
|
||||
},
|
||||
|(), l2| self.l2_reg_ref_counts[L2RegNum::value_sim(l2)] += 1,
|
||||
);
|
||||
let renamed_entries = &mut self
|
||||
.incomplete_back_entry
|
||||
.get_or_insert_with(|| RobEntries {
|
||||
unrenamed: unrenamed.clone(),
|
||||
rename_table_updates: Vec::new(),
|
||||
renamed_entries: VecDeque::new(),
|
||||
})
|
||||
.renamed_entries;
|
||||
renamed_entries.push_back(renamed);
|
||||
&renamed_entries.back().expect("just pushed").mop.id
|
||||
}
|
||||
pub(crate) fn has_incomplete_back_entry(&self) -> bool {
|
||||
self.incomplete_back_entry.is_some()
|
||||
}
|
||||
pub(crate) fn finished_unrenamed_push_back(&mut self, unrenamed: &SimValue<MOpInstance<MOp>>) {
|
||||
let entry = self
|
||||
.incomplete_back_entry
|
||||
.take()
|
||||
.unwrap_or_else(|| RobEntries {
|
||||
unrenamed: unrenamed.clone(),
|
||||
rename_table_updates: Vec::new(),
|
||||
renamed_entries: VecDeque::new(),
|
||||
});
|
||||
self.entries.push_back(entry);
|
||||
}
|
||||
pub(crate) fn clear(&mut self) {
|
||||
let Self {
|
||||
next_renamed_mop_id: _,
|
||||
entries,
|
||||
incomplete_back_entry,
|
||||
l1_reg_ref_counts,
|
||||
l2_reg_ref_counts,
|
||||
config: _,
|
||||
} = self;
|
||||
entries.clear();
|
||||
l2_reg_ref_counts.fill(0);
|
||||
for i in l1_reg_ref_counts {
|
||||
i.fill(0);
|
||||
}
|
||||
*incomplete_back_entry = None;
|
||||
}
|
||||
pub(crate) fn unrenamed_back_append_rename_table_update(
|
||||
&mut self,
|
||||
unrenamed: &SimValue<MOpInstance<MOp>>,
|
||||
update: RenameTableUpdate<C>,
|
||||
) {
|
||||
self.incomplete_back_entry
|
||||
.get_or_insert_with(|| RobEntries {
|
||||
unrenamed: unrenamed.clone(),
|
||||
rename_table_updates: Vec::new(),
|
||||
renamed_entries: VecDeque::new(),
|
||||
})
|
||||
.rename_table_updates
|
||||
.push(update);
|
||||
}
|
||||
pub(crate) fn all_mops_are_finished_and_or_caused_cancel(&self) -> bool {
|
||||
self.renamed().next().is_none_or(|entry| {
|
||||
entry.all_prior_mops_finished_and_or_caused_cancel
|
||||
&& entry.mop_in_unit_state.is_finished_and_or_caused_cancel()
|
||||
})
|
||||
}
|
||||
pub(crate) fn entries_pop_front(
|
||||
&mut self,
|
||||
) -> Option<(Vec<RenameTableUpdate<C>>, VecDeque<RobEntry<C>>)> {
|
||||
let RobEntries {
|
||||
unrenamed: _,
|
||||
rename_table_updates,
|
||||
renamed_entries,
|
||||
} = self.entries.pop_front()?;
|
||||
for entry in &renamed_entries {
|
||||
entry.for_each_reg(
|
||||
(),
|
||||
|(), l1| {
|
||||
if let Some(unit_index) = UnitNum::index_sim(&l1.unit_num) {
|
||||
let ref_count = &mut self.l1_reg_ref_counts[unit_index]
|
||||
[UnitOutRegNum::value_sim(&l1.unit_out_reg)];
|
||||
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
|
||||
unreachable!("ReorderBuffer: l1 ref count went negative: {l1:?}")
|
||||
});
|
||||
}
|
||||
},
|
||||
|(), l2| {
|
||||
let ref_count = &mut self.l2_reg_ref_counts[L2RegNum::value_sim(l2)];
|
||||
*ref_count = ref_count.checked_sub(1).unwrap_or_else(|| {
|
||||
unreachable!("ReorderBuffer: l2 ref count went negative: {l2:?}")
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
Some((rename_table_updates, renamed_entries))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use fayalite::{expr::ops::ArrayLiteral, module::wire_with_loc, prelude::*};
|
||||
use std::num::NonZero;
|
||||
use fayalite::{
|
||||
bundle::BundleType, expr::ops::ArrayLiteral, module::wire_with_loc, prelude::*,
|
||||
sim::vcd::VcdWriterDecls, util::RcWriter,
|
||||
};
|
||||
use std::{
|
||||
num::NonZero,
|
||||
panic::Location,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub mod array_vec;
|
||||
pub mod tree_reduce;
|
||||
|
|
@ -310,3 +317,152 @@ impl LFSR31 {
|
|||
state
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CheckedVcdOutput {
|
||||
writer: Option<RcWriter>,
|
||||
expected_path: PathBuf,
|
||||
expected_contents: Result<String, (Option<PathBuf>, std::io::Error)>,
|
||||
location: &'static Location<'static>,
|
||||
}
|
||||
|
||||
impl CheckedVcdOutput {
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn new<T: BundleType>(sim: &mut Simulation<T>, expected_path: PathBuf) -> Self {
|
||||
let writer = RcWriter::default();
|
||||
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
||||
Self {
|
||||
writer: Some(writer),
|
||||
expected_contents: std::fs::read_to_string(&expected_path).map_err(|e| {
|
||||
eprintln!(
|
||||
"error: failed to read expected VCD from: {}",
|
||||
expected_path.display(),
|
||||
);
|
||||
(std::env::current_dir().ok(), e)
|
||||
}),
|
||||
expected_path,
|
||||
location: Location::caller(),
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
#[doc(hidden)]
|
||||
pub fn __checked_vcd_output_macro_helper<T: BundleType>(
|
||||
sim: &mut Simulation<T>,
|
||||
cargo_manifest_dir: &'static str,
|
||||
path: &'static str,
|
||||
) -> Self {
|
||||
Self::new(sim, Path::new(cargo_manifest_dir).join(path))
|
||||
}
|
||||
pub fn with_vcd_output<R>(&self, f: impl FnOnce(&str) -> R) -> R {
|
||||
let Some(writer) = &self.writer else {
|
||||
unreachable!();
|
||||
};
|
||||
writer.clone().borrow(|output| {
|
||||
let Ok(output) = str::from_utf8(output) else {
|
||||
unreachable!("VcdWriter writes valid UTF-8");
|
||||
};
|
||||
f(output)
|
||||
})
|
||||
}
|
||||
#[track_caller]
|
||||
pub fn finish(mut self) {
|
||||
let Ok(()) = self.finish_impl(|msg| panic!("{msg}"));
|
||||
}
|
||||
fn finish_impl<E>(
|
||||
&mut self,
|
||||
error: impl FnOnce(std::fmt::Arguments<'_>) -> E,
|
||||
) -> Result<(), E> {
|
||||
let Self {
|
||||
writer: Some(writer),
|
||||
expected_path,
|
||||
expected_contents,
|
||||
location,
|
||||
} = self
|
||||
else {
|
||||
// already finished
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(vcd) = String::from_utf8(writer.take()) else {
|
||||
unreachable!("VcdWriter writes valid UTF-8");
|
||||
};
|
||||
let expected_path_d = expected_path.display();
|
||||
if expected_contents
|
||||
.as_ref()
|
||||
.is_ok_and(|expected_contents| *expected_contents == vcd)
|
||||
{
|
||||
// avoid written output from being split from threads interleaving writes to stdout
|
||||
let _stdout = std::io::stderr().lock();
|
||||
// use println to get output captured by tests
|
||||
println!("\n{location}: generated VCD matches the expected VCD in {expected_path_d}");
|
||||
return Ok(());
|
||||
}
|
||||
// avoid written output from being split from threads interleaving writes to stderr
|
||||
let _stderr = std::io::stderr().lock();
|
||||
let error = |msg: std::fmt::Arguments<'_>| {
|
||||
// print msg at both beginning and end so it's easier to find when the vcd is huge
|
||||
Err(error(format_args!(
|
||||
"\n{msg}####### VCD:\n{vcd}\n#######\n{msg}"
|
||||
)))
|
||||
};
|
||||
let error = |msg: std::fmt::Arguments<'_>| match &*expected_contents {
|
||||
Ok(_) => error(format_args!(
|
||||
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||
{msg}",
|
||||
)),
|
||||
Err((Some(current_dir), e)) => error(format_args!(
|
||||
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||
error: failed to read: {e}\n\
|
||||
current dir: {current_dir}\n\
|
||||
{msg}",
|
||||
current_dir = current_dir.display(),
|
||||
)),
|
||||
Err((None, e)) => error(format_args!(
|
||||
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||
error: failed to read: {e}\n\
|
||||
{msg}",
|
||||
)),
|
||||
};
|
||||
const OVERWRITE_VAR_NAME: &str = "OVERWRITE_EXPECTED_VCD";
|
||||
const OVERWRITE_VAR_VALUE: &str = "overwrite";
|
||||
match std::env::var_os(OVERWRITE_VAR_NAME) {
|
||||
Some(v) if v == OVERWRITE_VAR_VALUE => match std::fs::write(&expected_path, &vcd) {
|
||||
Ok(()) => error(format_args!(
|
||||
"warning: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- writing the generated VCD to {expected_path_d}\n"
|
||||
)),
|
||||
Err(e) => error(format_args!(
|
||||
"error: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- tried to write the generated VCD to {expected_path_d}\n\
|
||||
error: failed to write: {e}"
|
||||
)),
|
||||
},
|
||||
_ => error(format_args!(
|
||||
"note: rerun the test with the environment variable `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}`\n\
|
||||
to update the expected output to match the generated output.\n"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CheckedVcdOutput {
|
||||
#[track_caller]
|
||||
fn drop(&mut self) {
|
||||
let _ = self.finish_impl(|msg| {
|
||||
if std::thread::panicking() {
|
||||
eprintln!("{msg}"); // use eprintln to get output captured by tests
|
||||
} else {
|
||||
panic!("{msg}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! checked_vcd_output {
|
||||
($sim:expr, $path_relative_to_manifest_dir:expr $(,)?) => {
|
||||
$crate::util::CheckedVcdOutput::__checked_vcd_output_macro_helper(
|
||||
$sim,
|
||||
::std::env!("CARGO_MANIFEST_DIR"),
|
||||
::std::concat!($path_relative_to_manifest_dir),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
75590
crates/cpu/tests/expected/rename_execute_retire_fibonacci_combinatorial.vcd
generated
Normal file
75590
crates/cpu/tests/expected/rename_execute_retire_fibonacci_combinatorial.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
142833
crates/cpu/tests/expected/rename_execute_retire_head_n1.vcd
generated
142833
crates/cpu/tests/expected/rename_execute_retire_head_n1.vcd
generated
File diff suppressed because it is too large
Load diff
184861
crates/cpu/tests/expected/rename_execute_retire_save_restore_gprs.vcd
generated
Normal file
184861
crates/cpu/tests/expected/rename_execute_retire_save_restore_gprs.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
198743
crates/cpu/tests/expected/rename_execute_retire_slow_loop.vcd
generated
198743
crates/cpu/tests/expected/rename_execute_retire_slow_loop.vcd
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::{
|
||||
checked_vcd_output,
|
||||
config::{
|
||||
CpuConfig, CpuConfigFetchWidth, CpuConfigMaxUnitMaxInFlight, PhantomConstCpuConfig,
|
||||
UnitConfig,
|
||||
|
|
@ -15,13 +16,13 @@ use cpu::{
|
|||
ShiftRotateMode, StoreMOp, UnitNum, WriteL2RegMOp,
|
||||
},
|
||||
next_pc::CallStackOp,
|
||||
register::{PRegFlags, PRegFlagsPowerISA, PRegValue},
|
||||
register::{FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegValue},
|
||||
rename_execute_retire::{
|
||||
ExecuteToUnitInterface, MOpId, MOpInstance, NextPcPredictorOp, PostDecodeOutputInterface,
|
||||
RenamedMOp, RetireToNextPcInterface, RetireToNextPcInterfaceInner, UnitCausedCancel,
|
||||
UnitEnqueue, UnitFinishCauseCancel, UnitInputsReady, UnitMOpCantCauseCancel,
|
||||
UnitMOpIsNoLongerSpeculative, UnitOutputReady, rename_execute_retire,
|
||||
to_unit_interfaces::ExecuteToUnitInterfaces,
|
||||
ExecuteToUnitInterface, GlobalState, MOpId, MOpInstance, NextPcPredictorOp,
|
||||
PostDecodeOutputInterface, RenamedMOp, RetireToNextPcInterface,
|
||||
RetireToNextPcInterfaceInner, UnitCausedCancel, UnitEnqueue, UnitFinishCauseCancel,
|
||||
UnitInputsReady, UnitMOpCantCauseCancel, UnitMOpIsNoLongerSpeculative, UnitOutputReady,
|
||||
rename_execute_retire, to_unit_interfaces::ExecuteToUnitInterfaces,
|
||||
},
|
||||
unit::{UnitKind, UnitMOp},
|
||||
util::array_vec::ArrayVec,
|
||||
|
|
@ -30,9 +31,7 @@ use fayalite::{
|
|||
bundle::BundleType,
|
||||
module::instance_with_loc,
|
||||
prelude::*,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
ty::{OpaqueSimValue, StaticType},
|
||||
util::RcWriter,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
@ -1665,10 +1664,18 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl]
|
||||
fn run_add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
||||
&mut self,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
pc: u64,
|
||||
mop: &SimValue<AddSubMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
|
||||
src_values: &[SimValue<TraceAsString<PRegValue>>; COMMON_MOP_SRC_LEN],
|
||||
) -> SimValue<TraceAsString<PRegValue>> {
|
||||
#[hdl(sim)]
|
||||
let GlobalState { flags_mode } = global_state;
|
||||
#[hdl(sim)]
|
||||
match flags_mode {
|
||||
FlagsMode::PowerISA(_) => {}
|
||||
_ => todo!("flags_mode={flags_mode:?}"),
|
||||
}
|
||||
#[hdl(sim)]
|
||||
let AddSubMOp::<_, _, _> {
|
||||
alu_common,
|
||||
|
|
@ -1732,9 +1739,17 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl]
|
||||
fn run_compare<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
||||
&mut self,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
mop: &SimValue<CompareMOp<PRegNum<C>, PRegNum<C>, SrcCount>>,
|
||||
src_values: &[SimValue<TraceAsString<PRegValue>>; COMMON_MOP_SRC_LEN],
|
||||
) -> SimValue<TraceAsString<PRegValue>> {
|
||||
#[hdl(sim)]
|
||||
let GlobalState { flags_mode } = global_state;
|
||||
#[hdl(sim)]
|
||||
match flags_mode {
|
||||
FlagsMode::PowerISA(_) => {}
|
||||
_ => todo!("flags_mode={flags_mode:?}"),
|
||||
}
|
||||
#[hdl(sim)]
|
||||
let CompareMOp::<_, _, _> {
|
||||
common,
|
||||
|
|
@ -1791,9 +1806,17 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl]
|
||||
fn run_shift_rotate<C: PhantomConstCpuConfig>(
|
||||
&mut self,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
mop: &SimValue<ShiftRotateMOp<PRegNum<C>, PRegNum<C>>>,
|
||||
src_values: &[SimValue<TraceAsString<PRegValue>>; COMMON_MOP_SRC_LEN],
|
||||
) -> SimValue<TraceAsString<PRegValue>> {
|
||||
#[hdl(sim)]
|
||||
let GlobalState { flags_mode } = global_state;
|
||||
#[hdl(sim)]
|
||||
match flags_mode {
|
||||
FlagsMode::PowerISA(_) => {}
|
||||
_ => todo!("flags_mode={flags_mode:?}"),
|
||||
}
|
||||
#[hdl(sim)]
|
||||
let ShiftRotateMOp::<_, _> { alu_common, mode } = mop;
|
||||
#[hdl(sim)]
|
||||
|
|
@ -1973,7 +1996,7 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl]
|
||||
fn run_branch<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
||||
&mut self,
|
||||
id: &SimValue<MOpId>,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
pc: u64,
|
||||
fallthrough_pc: u64,
|
||||
predicted_next_pc: u64,
|
||||
|
|
@ -1985,6 +2008,13 @@ trait MockExecutionStateTrait: Default {
|
|||
SimValue<NextPcPredictorOp<C>>,
|
||||
Option<SimValue<UnitCausedCancel<C>>>,
|
||||
) {
|
||||
#[hdl(sim)]
|
||||
let GlobalState { flags_mode } = global_state;
|
||||
#[hdl(sim)]
|
||||
match flags_mode {
|
||||
FlagsMode::PowerISA(_) => {}
|
||||
_ => todo!("flags_mode={flags_mode:?}"),
|
||||
}
|
||||
#[hdl(sim)]
|
||||
let BranchMOp::<_, _, _> {
|
||||
common,
|
||||
|
|
@ -2093,6 +2123,7 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl]
|
||||
fn run_mop<C: PhantomConstCpuConfig>(
|
||||
&mut self,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
mop: &SimValue<MOpInstance<RenamedMOp<C>>>,
|
||||
src_values: &[SimValue<TraceAsString<PRegValue>>; COMMON_MOP_SRC_LEN],
|
||||
config: C,
|
||||
|
|
@ -2106,7 +2137,7 @@ trait MockExecutionStateTrait: Default {
|
|||
#[hdl(sim)]
|
||||
let MOpInstance::<_> {
|
||||
fetch_block_id: _,
|
||||
id,
|
||||
id: _,
|
||||
pc,
|
||||
predicted_next_pc,
|
||||
size_in_bytes,
|
||||
|
|
@ -2135,14 +2166,14 @@ trait MockExecutionStateTrait: Default {
|
|||
match mop {
|
||||
AluBranchMOp::<_, _>::AddSub(mop) => (
|
||||
Some((
|
||||
self.run_add_sub(pc.as_int(), mop, src_values),
|
||||
self.run_add_sub(global_state, pc.as_int(), mop, src_values),
|
||||
empty_predictor_op(),
|
||||
)),
|
||||
None,
|
||||
),
|
||||
AluBranchMOp::<_, _>::AddSubI(mop) => (
|
||||
Some((
|
||||
self.run_add_sub(pc.as_int(), mop, src_values),
|
||||
self.run_add_sub(global_state, pc.as_int(), mop, src_values),
|
||||
empty_predictor_op(),
|
||||
)),
|
||||
None,
|
||||
|
|
@ -2157,20 +2188,29 @@ trait MockExecutionStateTrait: Default {
|
|||
todo!("implement LogicalI")
|
||||
}
|
||||
AluBranchMOp::<_, _>::ShiftRotate(mop) => (
|
||||
Some((self.run_shift_rotate(mop, src_values), empty_predictor_op())),
|
||||
Some((
|
||||
self.run_shift_rotate(global_state, mop, src_values),
|
||||
empty_predictor_op(),
|
||||
)),
|
||||
None,
|
||||
),
|
||||
AluBranchMOp::<_, _>::Compare(mop) => (
|
||||
Some((self.run_compare(mop, src_values), empty_predictor_op())),
|
||||
Some((
|
||||
self.run_compare(global_state, mop, src_values),
|
||||
empty_predictor_op(),
|
||||
)),
|
||||
None,
|
||||
),
|
||||
AluBranchMOp::<_, _>::CompareI(mop) => (
|
||||
Some((self.run_compare(mop, src_values), empty_predictor_op())),
|
||||
Some((
|
||||
self.run_compare(global_state, mop, src_values),
|
||||
empty_predictor_op(),
|
||||
)),
|
||||
None,
|
||||
),
|
||||
AluBranchMOp::<_, _>::Branch(mop) => {
|
||||
let (value, predictor_op, cancel) = self.run_branch(
|
||||
id,
|
||||
global_state,
|
||||
pc.as_int(),
|
||||
fallthrough_pc,
|
||||
predicted_next_pc.as_int(),
|
||||
|
|
@ -2182,7 +2222,7 @@ trait MockExecutionStateTrait: Default {
|
|||
}
|
||||
AluBranchMOp::<_, _>::BranchI(mop) => {
|
||||
let (value, predictor_op, cancel) = self.run_branch(
|
||||
id,
|
||||
global_state,
|
||||
pc.as_int(),
|
||||
fallthrough_pc,
|
||||
predicted_next_pc.as_int(),
|
||||
|
|
@ -2270,12 +2310,16 @@ impl<C: PhantomConstCpuConfig> MockUnitOp<C> {
|
|||
}
|
||||
}
|
||||
#[hdl]
|
||||
fn try_run<E: MockExecutionStateTrait>(&mut self, execution_state: &mut E) {
|
||||
fn try_run<E: MockExecutionStateTrait>(
|
||||
&mut self,
|
||||
global_state: &SimValue<GlobalState>,
|
||||
execution_state: &mut E,
|
||||
) {
|
||||
if self.output_ready.is_some() || self.caused_cancel.is_some() {
|
||||
return;
|
||||
}
|
||||
let (output, caused_cancel) =
|
||||
execution_state.run_mop(&self.mop, &self.src_values, self.config);
|
||||
execution_state.run_mop(global_state, &self.mop, &self.src_values, self.config);
|
||||
assert!(output.is_some() || caused_cancel.is_some());
|
||||
println!("try_run: {:#x}: {:?}", self.mop.pc.as_int(), self.mop.mop);
|
||||
println!("<- {:?}", self.src_values);
|
||||
|
|
@ -2318,6 +2362,7 @@ impl<C: PhantomConstCpuConfig> MockUnitOp<C> {
|
|||
|
||||
#[hdl(no_static)]
|
||||
struct MockUnitDebugState<C: PhantomConstGet<CpuConfig>, E> {
|
||||
global_state: GlobalState,
|
||||
ops: ArrayVec<MockUnitOpDebugState<C>, CpuConfigMaxUnitMaxInFlight<C>>,
|
||||
execution_state: E,
|
||||
config: C,
|
||||
|
|
@ -2325,6 +2370,7 @@ struct MockUnitDebugState<C: PhantomConstGet<CpuConfig>, E> {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct MockUnitState<C: PhantomConstCpuConfig, E> {
|
||||
global_state: SimValue<GlobalState>,
|
||||
ops: BTreeMap<SimValue<MOpId>, MockUnitOp<C>>,
|
||||
execution_state: E,
|
||||
config: C,
|
||||
|
|
@ -2332,8 +2378,14 @@ struct MockUnitState<C: PhantomConstCpuConfig, E> {
|
|||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
||||
fn new(execution_state: E, config: C, unit_index: usize) -> Self {
|
||||
fn new(
|
||||
global_state: SimValue<GlobalState>,
|
||||
execution_state: E,
|
||||
config: C,
|
||||
unit_index: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
global_state,
|
||||
ops: BTreeMap::new(),
|
||||
execution_state,
|
||||
config,
|
||||
|
|
@ -2343,6 +2395,7 @@ impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
|||
#[hdl]
|
||||
fn debug_state(&self) -> SimValue<MockUnitDebugState<C, E::DebugState>> {
|
||||
let Self {
|
||||
global_state,
|
||||
ops,
|
||||
execution_state,
|
||||
config,
|
||||
|
|
@ -2352,6 +2405,7 @@ impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
|||
let ret_ty = MockUnitDebugState[*config][execution_state.ty()];
|
||||
#[hdl(sim)]
|
||||
MockUnitDebugState::<_, _> {
|
||||
global_state,
|
||||
ops: ret_ty
|
||||
.ops
|
||||
.from_iter_sim(
|
||||
|
|
@ -2459,7 +2513,7 @@ impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
|||
caused_cancel: None,
|
||||
config: self.config,
|
||||
};
|
||||
op.try_run(&mut self.execution_state);
|
||||
op.try_run(&self.global_state, &mut self.execution_state);
|
||||
self.ops.insert(op.mop.id.clone(), op);
|
||||
}
|
||||
#[hdl]
|
||||
|
|
@ -2470,6 +2524,7 @@ impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
|||
}
|
||||
fn cancel_all(&mut self) {
|
||||
let Self {
|
||||
global_state: _,
|
||||
ops,
|
||||
execution_state: _,
|
||||
config: _,
|
||||
|
|
@ -2479,24 +2534,6 @@ impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_the_l2_reg_file_unit(config: PhantomConst<CpuConfig>, unit_index: usize) -> bool {
|
||||
let first_unit_index = config
|
||||
.get()
|
||||
.units
|
||||
.iter()
|
||||
.position(|v| v.kind == UnitKind::TransformedMove);
|
||||
let last_unit_index = config
|
||||
.get()
|
||||
.units
|
||||
.iter()
|
||||
.rposition(|v| v.kind == UnitKind::TransformedMove);
|
||||
assert_eq!(
|
||||
first_unit_index, last_unit_index,
|
||||
"multiple L2 reg file units aren't allowed"
|
||||
);
|
||||
Some(unit_index) == first_unit_index
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
||||
config: PhantomConst<CpuConfig>,
|
||||
|
|
@ -2521,6 +2558,7 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
async |mut sim| {
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -2555,6 +2593,7 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
debug_state,
|
||||
#[hdl(sim)]
|
||||
MockUnitDebugState::<_, _> {
|
||||
global_state: zeroed(GlobalState),
|
||||
ops: zeroed(debug_state.ty().ops),
|
||||
execution_state: SimValue::into_bundle(E::zeroed_debug_state()),
|
||||
config,
|
||||
|
|
@ -2587,11 +2626,17 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
unit_index: usize,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
let mut state = MockUnitState::new(execution_state, config, unit_index);
|
||||
let mut state = MockUnitState::new(
|
||||
sim.read(from_execute.global_state).await,
|
||||
execution_state,
|
||||
config,
|
||||
unit_index,
|
||||
);
|
||||
loop {
|
||||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -2617,6 +2662,7 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state,
|
||||
enqueue: _, // we ignore enqueues since we don't need to track order for these instructions
|
||||
inputs_ready,
|
||||
is_no_longer_speculative,
|
||||
|
|
@ -2627,6 +2673,7 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
cancel_all,
|
||||
config: _,
|
||||
} = from_execute;
|
||||
state.global_state = sim.read_past(global_state, cd.clk).await;
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(inputs_ready) = sim.read_past(inputs_ready, cd.clk).await {
|
||||
state.handle_inputs_ready(inputs_ready);
|
||||
|
|
@ -2671,6 +2718,98 @@ fn mock_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_combinational_unit<#[hdl(skip)] E: MockExecutionStateTrait>(
|
||||
config: PhantomConst<CpuConfig>,
|
||||
unit_index: usize,
|
||||
) {
|
||||
#[hdl]
|
||||
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(ExecuteToUnitInterface[config]);
|
||||
m.extern_module_simulation_fn((from_execute, config, unit_index), async |args, mut sim| {
|
||||
let (from_execute, config, unit_index) = args;
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
cant_cause_cancel,
|
||||
output_ready,
|
||||
finish_cause_cancel,
|
||||
unit_outputs_ready: _,
|
||||
cancel_all,
|
||||
config: _,
|
||||
} = from_execute;
|
||||
// initialize all outputs
|
||||
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;
|
||||
run_fn::<E>(from_execute, config, unit_index, sim).await;
|
||||
});
|
||||
#[hdl]
|
||||
async fn run_fn<E: MockExecutionStateTrait>(
|
||||
from_execute: Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>,
|
||||
config: PhantomConst<CpuConfig>,
|
||||
unit_index: usize,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
loop {
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state,
|
||||
enqueue,
|
||||
inputs_ready,
|
||||
is_no_longer_speculative: _, // we don't care about being speculative for these instructions
|
||||
cant_cause_cancel,
|
||||
output_ready,
|
||||
finish_cause_cancel,
|
||||
unit_outputs_ready: _,
|
||||
cancel_all,
|
||||
config: _,
|
||||
} = from_execute;
|
||||
sim.write(enqueue.ready, true).await; // we ignore enqueues since we don't need to track order for these instructions
|
||||
let global_state = sim.read(global_state).await;
|
||||
let mut state = MockUnitState::new(global_state, E::default(), config, unit_index);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(inputs_ready) = sim.read(inputs_ready).await {
|
||||
state.handle_inputs_ready(inputs_ready);
|
||||
}
|
||||
// all outputs are immediately ready, so reporting that instructions can't cause cancels is superfluous
|
||||
sim.write(
|
||||
cant_cause_cancel,
|
||||
#[hdl(sim)]
|
||||
(cant_cause_cancel.ty()).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
let (output_ready_v, finish_cause_cancel_v) =
|
||||
state.peek_output_ready_and_finish_cause_cancel();
|
||||
sim.write(output_ready, output_ready_v).await;
|
||||
sim.write(finish_cause_cancel, finish_cause_cancel_v).await;
|
||||
sim.write(cancel_all.ready, true).await; // this unit is purely combinational so canceling does nothing
|
||||
sim.wait_for_changes([Expr::canonical(inputs_ready)], None)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct MockL2RegFileOpDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
mop: MOpInstance<L2RegisterFileMOp<PRegNum<C>, PRegNum<C>>>,
|
||||
|
|
@ -3071,6 +3210,7 @@ fn mock_l2_reg_file_unit(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
|||
async |mut sim| {
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -3132,6 +3272,7 @@ fn mock_l2_reg_file_unit(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
|||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -3155,6 +3296,7 @@ fn mock_l2_reg_file_unit(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
|||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready,
|
||||
is_no_longer_speculative,
|
||||
|
|
@ -3751,6 +3893,7 @@ fn mock_load_store_unit<#[hdl(skip)] MI: MakeInsns>(
|
|||
async |mut sim| {
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -3830,6 +3973,7 @@ fn mock_load_store_unit<#[hdl(skip)] MI: MakeInsns>(
|
|||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready: _,
|
||||
is_no_longer_speculative: _,
|
||||
|
|
@ -3855,6 +3999,7 @@ fn mock_load_store_unit<#[hdl(skip)] MI: MakeInsns>(
|
|||
{
|
||||
#[hdl]
|
||||
let ExecuteToUnitInterface::<_> {
|
||||
global_state: _,
|
||||
enqueue,
|
||||
inputs_ready,
|
||||
is_no_longer_speculative,
|
||||
|
|
@ -3913,7 +4058,10 @@ fn mock_load_store_unit<#[hdl(skip)] MI: MakeInsns>(
|
|||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(config: PhantomConst<CpuConfig>) {
|
||||
fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(
|
||||
config: PhantomConst<CpuConfig>,
|
||||
alu_branch_is_combinatorial: bool,
|
||||
) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
|
|
@ -3959,13 +4107,22 @@ fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(config: Phanto
|
|||
connect(started_any_l2_reg_file_ops, mock_unit.started_any);
|
||||
}
|
||||
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);
|
||||
if alu_branch_is_combinatorial {
|
||||
let mock_unit = instance_with_loc(
|
||||
&dut.ty().to_units.unit_field_name(unit_index),
|
||||
mock_combinational_unit::<()>(config, unit_index),
|
||||
SourceLocation::caller(),
|
||||
);
|
||||
connect(mock_unit.from_execute, to_unit);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4026,7 +4183,7 @@ impl MakeInsns for FibonacciInsns {
|
|||
|
||||
#[hdl]
|
||||
#[test]
|
||||
fn test_rename_execute_retire_fibonacci() {
|
||||
fn test_rename_execute_retire_fibonacci_non_combinatorial() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let mut config = CpuConfig::new(
|
||||
vec![
|
||||
|
|
@ -4039,24 +4196,15 @@ fn test_rename_execute_retire_fibonacci() {
|
|||
NonZeroUsize::new(20).unwrap(),
|
||||
);
|
||||
config.fetch_width = NonZeroUsize::new(3).unwrap();
|
||||
let m = rename_execute_retire_test_harness::<FibonacciInsns>(PhantomConst::new_sized(config));
|
||||
let m = rename_execute_retire_test_harness::<FibonacciInsns>(
|
||||
PhantomConst::new_sized(config),
|
||||
false,
|
||||
);
|
||||
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),
|
||||
};
|
||||
let _checked_vcd_output = checked_vcd_output!(
|
||||
&mut sim,
|
||||
"tests/expected/rename_execute_retire_fibonacci_non_combinatorial.vcd",
|
||||
);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
for cycle in 0..200 {
|
||||
|
|
@ -4068,12 +4216,41 @@ fn test_rename_execute_retire_fibonacci() {
|
|||
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_fibonacci.vcd") {
|
||||
panic!();
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
#[test]
|
||||
fn test_rename_execute_retire_fibonacci_combinatorial() {
|
||||
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::LoadStore),
|
||||
UnitConfig::new(UnitKind::TransformedMove),
|
||||
],
|
||||
NonZeroUsize::new(20).unwrap(),
|
||||
);
|
||||
config.fetch_width = NonZeroUsize::new(3).unwrap();
|
||||
let m =
|
||||
rename_execute_retire_test_harness::<FibonacciInsns>(PhantomConst::new_sized(config), true);
|
||||
let mut sim = Simulation::new(m);
|
||||
let _checked_vcd_output = checked_vcd_output!(
|
||||
&mut sim,
|
||||
"tests/expected/rename_execute_retire_fibonacci_combinatorial.vcd",
|
||||
);
|
||||
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));
|
||||
}
|
||||
|
||||
struct SlowLoopInsns;
|
||||
|
|
@ -4171,27 +4348,16 @@ fn test_rename_execute_retire_slow_loop() {
|
|||
NonZeroUsize::new(20).unwrap(),
|
||||
);
|
||||
config.fetch_width = NonZeroUsize::new(4).unwrap();
|
||||
let m = rename_execute_retire_test_harness::<SlowLoopInsns>(PhantomConst::new_sized(config));
|
||||
let m =
|
||||
rename_execute_retire_test_harness::<SlowLoopInsns>(PhantomConst::new_sized(config), true);
|
||||
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),
|
||||
};
|
||||
let _checked_vcd_output = checked_vcd_output!(
|
||||
&mut sim,
|
||||
"tests/expected/rename_execute_retire_slow_loop.vcd",
|
||||
);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
for cycle in 0..600 {
|
||||
for cycle in 0..350 {
|
||||
sim.advance_time(SimDuration::from_nanos(500));
|
||||
println!("clock tick: {cycle}");
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
|
|
@ -4202,12 +4368,6 @@ fn test_rename_execute_retire_slow_loop() {
|
|||
assert!(sim.read_bool(sim.io().all_outputs_written));
|
||||
// make sure we're actually testing L2 reg file ops
|
||||
assert!(sim.read_bool(sim.io().started_any_l2_reg_file_ops));
|
||||
// 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!();
|
||||
}
|
||||
}
|
||||
|
||||
/// equivalent of Unix's `head -n1`
|
||||
|
|
@ -4309,24 +4469,11 @@ fn test_rename_execute_retire_head_n1() {
|
|||
NonZeroUsize::new(20).unwrap(),
|
||||
);
|
||||
config.fetch_width = NonZeroUsize::new(2).unwrap();
|
||||
let m = rename_execute_retire_test_harness::<HeadN1Insns>(PhantomConst::new_sized(config));
|
||||
let m =
|
||||
rename_execute_retire_test_harness::<HeadN1Insns>(PhantomConst::new_sized(config), true);
|
||||
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),
|
||||
};
|
||||
let _checked_vcd_output =
|
||||
checked_vcd_output!(&mut sim, "tests/expected/rename_execute_retire_head_n1.vcd");
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
for cycle in 0..300 {
|
||||
|
|
@ -4338,10 +4485,103 @@ fn test_rename_execute_retire_head_n1() {
|
|||
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_head_n1.vcd") {
|
||||
panic!();
|
||||
}
|
||||
|
||||
struct SaveRestoreGprsInsns;
|
||||
|
||||
impl SaveRestoreGprsInsns {
|
||||
const STATE_OFFSET: usize = 16;
|
||||
const MAIN_STACK_FRAME_SIZE: usize = 16 + 32 * 8;
|
||||
const OK: i16 = i16::from_le_bytes(*b"Ok");
|
||||
}
|
||||
|
||||
impl MakeInsns for SaveRestoreGprsInsns {
|
||||
fn make_insns() -> Insns {
|
||||
let mut b = InsnsBuilder::new();
|
||||
|
||||
let load_all = b.new_label("load_all");
|
||||
let store_all = b.new_label("store_all");
|
||||
let main = b.new_label("main");
|
||||
b.power_isa_addi(1, 0, 0x4000); // setup stack pointer
|
||||
b.power_isa_bl(main);
|
||||
b.power_isa_addi(0, 0, Self::OK);
|
||||
b.power_isa_std(0, 0, MockMemory::IO_ADDR as i16);
|
||||
let done = b.new_defined_label("done");
|
||||
b.power_isa_b(done);
|
||||
|
||||
b.set_pc(0x1000);
|
||||
b.define_label(main);
|
||||
b.power_isa_mflr(0);
|
||||
b.power_isa_stdu(0, 1, -(Self::MAIN_STACK_FRAME_SIZE as i16));
|
||||
b.power_isa_addi(0, 0, 2);
|
||||
b.power_isa_mtctr(0);
|
||||
|
||||
let main_loop = b.new_defined_label("main_loop");
|
||||
b.power_isa_addi(3, 1, Self::STATE_OFFSET as i16);
|
||||
b.power_isa_bl(store_all);
|
||||
b.power_isa_addi(3, 1, Self::STATE_OFFSET as i16);
|
||||
b.power_isa_bl(load_all);
|
||||
b.power_isa_bdnz(main_loop);
|
||||
|
||||
b.power_isa_ld(0, 1, 0);
|
||||
b.power_isa_addi(1, 1, Self::MAIN_STACK_FRAME_SIZE as i16);
|
||||
b.power_isa_mtlr(0);
|
||||
b.power_isa_blr();
|
||||
|
||||
b.set_pc(0x1800);
|
||||
b.define_label(store_all);
|
||||
for i in 0..32 {
|
||||
b.power_isa_std(i, 3, i as i16 * 8);
|
||||
}
|
||||
b.power_isa_blr();
|
||||
|
||||
b.set_pc(0x2000);
|
||||
b.define_label(load_all);
|
||||
// load r3 last to avoid overwriting pointer
|
||||
for i in (0..32).filter(|i| *i != 3).chain([3]) {
|
||||
b.power_isa_ld(i, 3, i as i16 * 8);
|
||||
}
|
||||
b.power_isa_blr();
|
||||
|
||||
b.build()
|
||||
}
|
||||
fn make_load_store_execution_state() -> MockMemory {
|
||||
MockMemory::new(vec![], vec![Self::OK as u64], [])
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
#[test]
|
||||
fn test_rename_execute_retire_save_restore_gprs() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let mut config = CpuConfig::new(
|
||||
vec![
|
||||
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(2).unwrap();
|
||||
let m = rename_execute_retire_test_harness::<SaveRestoreGprsInsns>(
|
||||
PhantomConst::new_sized(config),
|
||||
true,
|
||||
);
|
||||
let mut sim = Simulation::new(m);
|
||||
let _checked_vcd_output = checked_vcd_output!(
|
||||
&mut sim,
|
||||
"tests/expected/rename_execute_retire_save_restore_gprs.vcd",
|
||||
);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
for cycle in 0..700 {
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue