Compare commits

..

10 commits

10 changed files with 459209 additions and 200240 deletions

File diff suppressed because it is too large Load diff

View 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()
}
}

View 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 &micro;Ops that this unrenamed &micro;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))
}
}

View file

@ -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),
)
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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));
}