cpu/crates/cpu/tests/rename_execute_retire.rs

1812 lines
60 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
config::{
CpuConfig, CpuConfigFetchWidth, CpuConfigMaxUnitMaxInFlight, CpuConfigPRegNumWidth,
PhantomConstCpuConfig, UnitConfig,
},
instruction::{
AddSubMOp, AluBranchMOp, AluCommonMOp, BranchMOp, COMMON_MOP_SRC_LEN, CommonMOp,
CompareMOp, CompareMode, ConditionMode, L2RegisterFileMOp, LoadMOp, LoadStoreCommonMOp,
LoadStoreConversion, LoadStoreMOp, LoadStoreWidth, MOp, MOpDebug, MOpDestReg, MOpRegNum,
MOpTrait, MoveRegMOp, OutputIntegerMode, PRegNum, StoreMOp,
},
register::{PRegFlags, PRegFlagsPowerISA, PRegValue},
rename_execute_retire::{
ExecuteToUnitInterface, MOpId, MOpInstance, PostDecodeOutputInterface, RenamedMOp,
RetireToNextPcInterface, UnitCausedCancel, UnitFinished, UnitFinishedSuccessfully,
UnitMOpCantCauseCancel, UnitMOpIsNoLongerSpeculative, UnitStart, rename_execute_retire,
to_unit_interfaces::ExecuteToUnitInterfaces,
},
unit::{UnitKind, UnitMOp},
util::array_vec::ArrayVec,
};
use fayalite::{
int::IntType,
intern::Intern,
module::instance_with_loc,
prelude::*,
sim::vcd::VcdWriterDecls,
ty::{OpaqueSimValue, StaticType},
util::RcWriter,
};
use std::{
cell::Cell,
collections::{BTreeMap, VecDeque},
fmt::{self, Write},
mem,
num::NonZeroUsize,
u8,
};
fn zeroed<T: Type>(ty: T) -> SimValue<T> {
SimValue::from_opaque(
ty,
OpaqueSimValue::from_bits(UInt::new(ty.canonical().bit_width()).zero()),
)
}
fn signed_hex(v: impl Into<i64>) -> impl fmt::Display {
let v = v.into();
fmt::from_fn(move |f| {
if v < 0 {
f.write_char('-')?;
}
write!(f, "{:#x}", v.unsigned_abs())
})
}
struct RandomState {
state: Cell<u64>,
}
impl RandomState {
fn random_u64(&self, key: u32) -> u64 {
let state = self.state.replace(self.state.get().wrapping_add(1));
// make a pseudo-random number deterministically based on state and key
let mut random = state
.wrapping_add(1)
.wrapping_mul(0x39FF446D8BFB75BB) // random prime
.rotate_left(32)
.wrapping_mul(0x73161B54984B1C21) // random prime
.rotate_right(60);
random ^= key as u64;
random
.wrapping_mul(0x39FF446D8BFB75BB) // random prime
.rotate_left(32)
.wrapping_mul(0x73161B54984B1C21) // random prime
.rotate_right(60)
}
}
const START_PC: u64 = 0x0; // match microwatt's reset pc
#[derive(Clone, Debug)]
struct Insn<MOps = Vec<SimValue<self::MOp>>> {
size_in_bytes: u8,
power_isa: String,
mops: MOps,
}
enum LazyMOps {
MOps(Vec<SimValue<self::MOp>>),
Lazy(Box<dyn FnOnce(&[InsnsBuilderLabelState]) -> Vec<SimValue<self::MOp>>>),
}
impl Insn<LazyMOps> {
fn new<I: IntoIterator<Item: ToSimValue<Type = MOp>>>(
size_in_bytes: u8,
power_isa: String,
mops: I,
) -> Self {
Self {
size_in_bytes,
power_isa,
mops: LazyMOps::MOps(mops.into_iter().map(|mop| mop.into_sim_value()).collect()),
}
}
fn new_lazy<I: IntoIterator<Item: ToSimValue<Type = MOp>>>(
size_in_bytes: u8,
power_isa: String,
lazy_mops: impl FnOnce(&[InsnsBuilderLabelState]) -> I + 'static,
) -> Self {
Self {
size_in_bytes,
power_isa,
mops: LazyMOps::Lazy(Box::new(|labels| {
lazy_mops(labels)
.into_iter()
.map(|mop| mop.into_sim_value())
.collect()
})),
}
}
}
struct Insns {
insns: BTreeMap<u64, Insn>,
}
impl Insns {
fn get_mop_mut(&mut self, pc: u64, mop_index: usize) -> Option<&mut SimValue<MOp>> {
self.insns.get_mut(&pc)?.mops.get_mut(mop_index)
}
}
impl fmt::Debug for Insns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(
self.insns
.iter()
.map(|(k, v)| (fmt::from_fn(move |f| write!(f, "{k:#x}")), v)),
)
.finish()
}
}
struct InsnsBuilderLabelState {
name: &'static str,
pc: Option<u64>,
location: &'static std::panic::Location<'static>,
}
impl InsnsBuilderLabelState {
fn pc(&self) -> u64 {
match self.pc {
Some(pc) => pc,
None => panic!("label not defined {}: at: {}", self.name, self.location),
}
}
}
#[derive(Clone, Copy, Debug)]
struct InsnsBuilderLabel(usize);
struct InsnsBuilder {
labels: Vec<InsnsBuilderLabelState>,
insns: BTreeMap<u64, Insn<LazyMOps>>,
pc: u64,
}
impl InsnsBuilder {
fn new() -> Self {
Self {
labels: Vec::new(),
insns: BTreeMap::new(),
pc: START_PC,
}
}
#[track_caller]
fn new_label(&mut self, name: &'static str) -> InsnsBuilderLabel {
let retval = InsnsBuilderLabel(self.labels.len());
self.labels.push(InsnsBuilderLabelState {
pc: None,
name,
location: std::panic::Location::caller(),
});
retval
}
#[track_caller]
fn define_label(&mut self, label: InsnsBuilderLabel) {
let label_state = &mut self.labels[label.0];
if label_state.pc.is_some() {
panic!("label already defined at: {}", label_state.location);
}
label_state.pc = Some(self.pc);
label_state.location = std::panic::Location::caller();
}
#[track_caller]
fn new_defined_label(&mut self, name: &'static str) -> InsnsBuilderLabel {
let label = self.new_label(name);
self.define_label(label);
label
}
#[track_caller]
fn add_insn(&mut self, insn: Insn<LazyMOps>) {
let pc = self.pc;
let next_pc = pc.wrapping_add(insn.size_in_bytes as u64);
if self.insns.insert(self.pc, insn).is_some() {
panic!("instruction already stored at {pc:#x}");
}
self.pc = next_pc;
}
fn build(self) -> Insns {
Insns {
insns: self
.insns
.into_iter()
.map(|(pc, insn)| {
let Insn {
size_in_bytes,
power_isa,
mops,
} = insn;
(
pc,
Insn {
size_in_bytes,
power_isa,
mops: match mops {
LazyMOps::MOps(mops) => mops,
LazyMOps::Lazy(f) => f(&self.labels),
},
},
)
})
.collect(),
}
}
fn set_pc(&mut self, pc: u64) {
self.pc = pc;
}
fn power_isa_b(&mut self, target: InsnsBuilderLabel) {
let pc = self.pc;
self.add_insn(Insn::new_lazy(
4,
format!("b {}", self.labels[target.0].name),
move |labels| {
[BranchMOp::branch_i(
MOpDestReg::new([], []),
MOpRegNum::const_zero().value,
labels[target.0]
.pc()
.wrapping_sub(pc)
.cast_to_static::<SInt<_>>(),
true,
false,
false,
)]
},
));
}
fn power_isa_blr(&mut self) {
self.add_insn(Insn::new(
4,
format!("blr"),
[BranchMOp::branch_i(
MOpDestReg::new([], []),
MOpRegNum::power_isa_lr_reg().value,
0i8.cast_to_static::<SInt<_>>(),
false,
false,
true,
)],
));
}
fn power_isa_bgt(&mut self, target: InsnsBuilderLabel) {
let pc = self.pc;
self.add_insn(Insn::new_lazy(
4,
format!("bgt {}", self.labels[target.0].name),
move |labels| {
[BranchMOp::branch_cond_ctr(
MOpDestReg::new([], []),
[
MOpRegNum::power_isa_cr_reg_imm(0).value,
MOpRegNum::const_zero().value,
MOpRegNum::const_zero().value,
],
labels[target.0]
.pc()
.wrapping_sub(pc)
.cast_to_static::<SInt<_>>(),
false,
ConditionMode.SGt(),
false,
true,
false,
false,
)]
},
));
}
fn power_isa_bl(&mut self, target: InsnsBuilderLabel) {
let pc = self.pc;
self.add_insn(Insn::new_lazy(
4,
format!("bl {}", self.labels[target.0].name),
move |labels| {
[BranchMOp::branch_i(
MOpDestReg::new([MOpRegNum::power_isa_lr_reg()], []),
MOpRegNum::const_zero().value,
labels[target.0]
.pc()
.wrapping_sub(pc)
.cast_to_static::<SInt<_>>(),
true,
true,
false,
)]
},
));
}
fn power_isa_add(&mut self, dest: usize, src1: usize, src2: usize) {
self.add_insn(Insn::new(
4,
format!("add {dest}, {src1}, {src2}"),
[AddSubMOp::add_sub(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(dest)], &[]),
[
MOpRegNum::power_isa_gpr_reg_imm(src1).value,
MOpRegNum::power_isa_gpr_reg_imm(src2).value,
MOpRegNum::const_zero().value,
],
0i8.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
)],
));
}
fn power_isa_addi(&mut self, dest: usize, src: usize, imm: i16) {
self.add_insn(Insn::new(
4,
format!("addi {dest}, {src}, {}", signed_hex(imm)),
[AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(dest)], &[]),
[
MOpRegNum::power_isa_gpr_or_zero_reg_imm(src).value,
MOpRegNum::const_zero().value,
],
imm.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
)],
));
}
fn power_isa_cmpldi(&mut self, dest: usize, src: usize, imm: u16) {
self.add_insn(Insn::new(
4,
format!("cmpldi {dest}, {src}, {imm:#x}"),
[CompareMOp::compare_i(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_cr_reg_num(dest)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(src).value],
imm.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
CompareMode.U64(),
)],
));
}
fn power_isa_ld(&mut self, dest: usize, src: usize, disp: i16) {
self.add_insn(Insn::new(
4,
format!("ld {dest}, {}({src})", signed_hex(disp)),
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
MOpRegNum::power_isa_gpr_or_zero_reg_imm(src).value,
MOpRegNum::const_zero().value,
],
disp.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
LoadMOp::load(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(dest)], &[]),
[MOpRegNum::power_isa_temp_reg().value],
LoadStoreWidth.Width64Bit(),
LoadStoreConversion.ZeroExt(),
),
],
));
}
fn power_isa_std(&mut self, value_src: usize, addr_src: usize, disp: i16) {
self.add_insn(Insn::new(
4,
format!("std {value_src}, {}({addr_src})", signed_hex(disp)),
[
AddSubMOp::add_sub_i(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_TEMP_REG_NUM], &[]),
[
MOpRegNum::power_isa_gpr_or_zero_reg_imm(addr_src).value,
MOpRegNum::const_zero().value,
],
disp.cast_to_static::<SInt<_>>(),
OutputIntegerMode.Full64(),
false,
false,
false,
false,
),
StoreMOp::store(
MOpDestReg::new_sim(&[], &[]),
[
MOpRegNum::power_isa_temp_reg().value,
MOpRegNum::power_isa_gpr_reg_imm(value_src).value,
],
LoadStoreWidth.Width64Bit(),
LoadStoreConversion.ZeroExt(),
),
],
));
}
fn power_isa_mflr(&mut self, dest: usize) {
self.add_insn(Insn::new(
4,
format!("mflr {dest}"),
[MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(dest)], &[]),
[MOpRegNum::power_isa_lr_reg().value],
0i8.cast_to_static::<SInt<_>>(),
)],
));
}
fn power_isa_mtlr(&mut self, src: usize) {
self.add_insn(Insn::new(
4,
format!("mtlr {src}"),
[MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::POWER_ISA_LR_REG_NUM], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(src).value],
0i8.cast_to_static::<SInt<_>>(),
)],
));
}
fn power_isa_mr(&mut self, dest: usize, src: usize) {
self.add_insn(Insn::new(
4,
format!("mr {dest}, {src}"),
[MoveRegMOp::move_reg(
MOpDestReg::new_sim(&[MOpRegNum::power_isa_gpr_reg_num(dest)], &[]),
[MOpRegNum::power_isa_gpr_reg_imm(src).value],
0i8.cast_to_static::<SInt<_>>(),
)],
));
}
}
trait MakeInsns {
fn make_insns() -> Insns;
fn make_load_store_execution_state() -> MockLoadStoreState;
}
struct FibonacciInsns;
impl MakeInsns for FibonacciInsns {
fn make_insns() -> Insns {
let mut b = InsnsBuilder::new();
let fib = b.new_label("fib");
let fib_small = b.new_label("fib_small");
b.power_isa_ld(3, 0, MockLoadStoreState::IO_ADDR as i16); // load input
b.power_isa_addi(1, 0, 0x4000); // setup stack pointer
b.power_isa_bl(fib);
b.power_isa_std(3, 0, MockLoadStoreState::IO_ADDR as i16); // store output
let done = b.new_defined_label("done");
b.power_isa_b(done);
b.set_pc(0x1000);
b.define_label(fib);
b.power_isa_cmpldi(0, 3, 1);
b.power_isa_bgt(fib_small); // if input > 1 goto fib_small
// push stack frame
b.power_isa_mflr(0);
b.power_isa_addi(1, 1, -16);
b.power_isa_std(0, 1, 0);
b.power_isa_std(30, 1, 8);
b.power_isa_addi(30, 3, -2);
b.power_isa_addi(3, 3, -1);
b.power_isa_bl(fib); // fib(input - 1)
b.power_isa_mr(2, 3);
b.power_isa_mr(3, 30);
b.power_isa_mr(30, 2);
b.power_isa_bl(fib); // fib(input - 2)
b.power_isa_add(3, 3, 30); // set output to fib(input - 1) + fib(input - 2)
// pop stack frame and return
b.power_isa_ld(0, 1, 0);
b.power_isa_ld(30, 1, 8);
b.power_isa_addi(1, 1, 16);
b.power_isa_mtlr(0);
b.power_isa_blr();
b.define_label(fib_small);
b.power_isa_addi(3, 0, 1); // set output to 1
b.power_isa_blr(); // return
b.build()
}
fn make_load_store_execution_state() -> MockLoadStoreState {
MockLoadStoreState::new(5, 8, [])
}
}
#[hdl(no_static)]
struct MockNextPcDebugState<C: PhantomConstGet<CpuConfig>> {
next_pc: UInt<64>,
next_mop_index: UInt<8>,
next_br_pred_state: BrPredDebugState,
next_id: UInt<64>,
fetch_block_id: UInt<64>,
fetch_queue: ArrayVec<(BrPredDebugState, MOpInstance<MOp>), CpuConfigFetchWidth<C>>,
config: C,
}
#[hdl]
struct BrPredDebugState {
call_stack: ArrayVec<UInt<64>, ConstUsize<{ BrPredState::CALL_STACK_CAPACITY }>>,
}
#[derive(Copy, Clone)]
struct BrPredState {
call_stack: [u64; Self::CALL_STACK_CAPACITY],
call_stack_len: usize,
}
impl BrPredState {}
impl fmt::Debug for BrPredState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
call_stack,
call_stack_len,
} = self;
f.debug_struct("BrPredState")
.field(
"call_stack",
&fmt::from_fn(|f| {
let mut debug_list = f.debug_list();
for pc in &call_stack[..*call_stack_len] {
debug_list.entry(&fmt::from_fn(|f| write!(f, "{pc:#x}")));
}
debug_list.finish()
}),
)
.finish()
}
}
impl BrPredState {
const CALL_STACK_CAPACITY: usize = 8;
fn new() -> Self {
Self {
call_stack: [0; _],
call_stack_len: 0,
}
}
fn predict_call(&mut self, return_pc: u64) {
if self.call_stack_len == Self::CALL_STACK_CAPACITY {
self.call_stack.copy_within(1.., 0);
*self.call_stack.last_mut().expect("known to be non-zero") = return_pc;
} else {
self.call_stack[self.call_stack_len] = return_pc;
self.call_stack_len += 1;
}
}
fn predict_ret(&mut self) -> Option<u64> {
if self.call_stack_len > 0 {
self.call_stack_len -= 1;
Some(mem::replace(&mut self.call_stack[self.call_stack_len], 0))
} else {
None
}
}
fn predict_branch<SrcCount: KnownSize>(
&mut self,
mop: &SimValue<BranchMOp<MOpDestReg, ConstUsize<{ MOpRegNum::WIDTH }>, SrcCount>>,
branch_pc: u64,
fallthrough_pc: u64,
) -> u64 {
if *mop.is_ret {
if let Some(retval) = self.predict_ret() {
return retval;
}
}
let mut src_regs = [MOpRegNum::CONST_ZERO_REG_NUM; 3];
MOpTrait::for_each_src_reg_sim_ref(mop, &mut |reg, index| {
src_regs[index] = reg.cast_to_static::<UInt<32>>().as_int();
});
if src_regs[1] != MOpRegNum::CONST_ZERO_REG_NUM {
// indirect branch -- this test doesn't implement predicting them, so just use the fallthrough_pc
return fallthrough_pc;
}
let target_pc = (*mop.common.imm)
.cast_to_static::<UInt<64>>()
.as_int()
.wrapping_add(if *mop.pc_relative { branch_pc } else { 0 });
if *mop.is_call {
self.predict_call(fallthrough_pc);
}
if src_regs[0] != MOpRegNum::CONST_ZERO_REG_NUM
|| src_regs[2] != MOpRegNum::CONST_ZERO_REG_NUM
{
// conditional branch -- this test just predicts taken for backward branches and not taken for forward branches
if target_pc < fallthrough_pc {
target_pc
} else {
fallthrough_pc
}
} else {
target_pc
}
}
#[hdl]
fn debug_state(&self) -> SimValue<BrPredDebugState> {
let Self {
call_stack,
call_stack_len,
} = self;
#[hdl(sim)]
BrPredDebugState {
call_stack: BrPredDebugState
.call_stack
.from_iter_sim(0u64, &call_stack[..*call_stack_len])
.expect("known to fit"),
}
}
}
struct MockNextPcState<'a, C: PhantomConstCpuConfig> {
random_state: &'a RandomState,
insns: &'a Insns,
next_pc: u64,
next_mop_index: usize,
next_br_pred_state: BrPredState,
next_id: u64,
fetch_block_id: u64,
fetch_queue: VecDeque<(BrPredState, SimValue<MOpInstance<MOp>>)>,
config: C,
}
impl<'a, C: PhantomConstCpuConfig> MockNextPcState<'a, C> {
fn new(random_state: &'a RandomState, insns: &'a Insns, config: C) -> Self {
Self {
random_state,
insns,
next_pc: START_PC,
next_mop_index: 0,
next_br_pred_state: BrPredState::new(),
next_id: 0,
fetch_block_id: 0,
fetch_queue: VecDeque::with_capacity(config.get().fetch_width.get()),
config,
}
}
#[hdl]
fn debug_state(&self) -> SimValue<MockNextPcDebugState<C>> {
let Self {
random_state: _,
insns: _,
config,
next_pc,
next_mop_index,
next_br_pred_state,
next_id,
fetch_block_id,
fetch_queue,
} = self;
let ty = MockNextPcDebugState[*config];
#[hdl(sim)]
MockNextPcDebugState::<_> {
next_pc,
next_mop_index: *next_mop_index as u8,
next_br_pred_state: next_br_pred_state.debug_state(),
next_id,
fetch_block_id,
fetch_queue: ty
.fetch_queue
.from_iter_sim(
zeroed(StaticType::TYPE),
fetch_queue
.iter()
.map(|(br_pred_state, mop)| (br_pred_state.debug_state(), mop)),
)
.expect("known to fit"),
config,
}
}
#[hdl]
fn try_push_fetch_queue(&mut self) -> Result<(), ()> {
if self.fetch_queue.len() >= self.config.get().fetch_width.get() {
return Err(());
}
let insn = self.insns.insns.get(&self.next_pc).ok_or(())?;
let fallthrough_pc = self.next_pc.wrapping_add(insn.size_in_bytes.into());
let is_last_mop_index = self.next_mop_index + 1 >= insn.mops.len();
let mut predicted_next_pc = if is_last_mop_index {
fallthrough_pc
} else {
self.next_pc
};
let prev_br_pred_state = self.next_br_pred_state;
let mop = &insn.mops[self.next_mop_index];
#[hdl(sim)]
if let MOp::AluBranch(mop) = mop {
#[hdl(sim)]
match mop {
AluBranchMOp::<_, _>::Branch(mop) => {
predicted_next_pc =
self.next_br_pred_state
.predict_branch(mop, self.next_pc, fallthrough_pc);
}
AluBranchMOp::<_, _>::BranchI(mop) => {
predicted_next_pc =
self.next_br_pred_state
.predict_branch(mop, self.next_pc, fallthrough_pc);
}
_ => {}
}
}
let mop = #[hdl(sim)]
MOpInstance::<_> {
fetch_block_id: self.fetch_block_id.cast_to_static::<UInt<_>>(),
id: self.next_id.cast_to_static::<UInt<_>>(),
pc: self.next_pc,
predicted_next_pc,
size_in_bytes: insn.size_in_bytes.cast_to_static::<UInt<_>>(),
is_first_mop_in_insn: self.next_mop_index == 0,
mop,
};
println!("pushed to fetch queue: {mop:?}");
self.fetch_queue.push_back((prev_br_pred_state, mop));
if is_last_mop_index {
self.next_mop_index = 0;
self.next_pc = predicted_next_pc;
} else {
self.next_mop_index += 1;
}
Ok(())
}
fn peek_post_decode_output_insns(
&self,
) -> SimValue<ArrayVec<MOpInstance<MOp>, CpuConfigFetchWidth<C>>> {
let ty = ArrayVec[MOpInstance[MOp]][CpuConfigFetchWidth[self.config]];
let peek_size = self.random_state.random_u64(u32::from_le_bytes(*b"pdoi")) as usize
% (ty.capacity() + 1);
ty.from_iter_sim(
zeroed(MOpInstance[MOp]),
self.fetch_queue.iter().map(|(_, v)| v).take(peek_size),
)
.ok()
.expect("known to fit")
}
}
#[hdl_module(extern)]
fn mock_next_pc<#[hdl(skip)] MI: MakeInsns>(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let post_decode_output: PostDecodeOutputInterface<PhantomConst<CpuConfig>> =
m.output(PostDecodeOutputInterface[config]);
#[hdl]
let from_retire: RetireToNextPcInterface<PhantomConst<CpuConfig>> =
m.input(RetireToNextPcInterface[config]);
#[hdl]
let debug_state: MockNextPcDebugState<PhantomConst<CpuConfig>> =
m.output(MockNextPcDebugState[config]);
m.register_clock_for_past(cd.clk);
m.extern_module_simulation_fn(
(cd, post_decode_output, from_retire, debug_state),
|args, mut sim| async move {
let (cd, post_decode_output, from_retire, debug_state) = args;
// intentionally have a different sequence each time we're reset
let random_state = RandomState {
state: Cell::new(0),
};
let insns = MI::make_insns();
sim.resettable(
cd,
async |mut sim| {
sim.write(
post_decode_output.insns,
post_decode_output
.ty()
.insns
.new_sim(zeroed(StaticType::TYPE)),
)
.await;
sim.write(
post_decode_output.cancel.data,
#[hdl(sim)]
HdlNone(),
)
.await;
sim.write(from_retire.inner.ready, false).await;
},
|sim, ()| {
run_fn(
cd,
post_decode_output,
from_retire,
debug_state,
&random_state,
&insns,
sim,
)
},
)
.await;
},
);
#[hdl]
async fn run_fn(
cd: Expr<ClockDomain>,
post_decode_output: Expr<PostDecodeOutputInterface<PhantomConst<CpuConfig>>>,
from_retire: Expr<RetireToNextPcInterface<PhantomConst<CpuConfig>>>,
debug_state: Expr<MockNextPcDebugState<PhantomConst<CpuConfig>>>,
random_state: &RandomState,
insns: &Insns,
mut sim: ExternModuleSimulationState,
) {
let config = post_decode_output.ty().config;
let mut state = MockNextPcState::new(random_state, insns, config);
loop {
while state.try_push_fetch_queue().is_ok() {}
state.fetch_block_id += 1;
let post_decode_output_insns = state.peek_post_decode_output_insns();
sim.write(post_decode_output.insns, &post_decode_output_insns)
.await;
sim.write(
post_decode_output.cancel.data,
#[hdl(sim)]
HdlNone(),
)
.await;
sim.write(from_retire.inner.ready, false).await;
sim.write(debug_state, state.debug_state()).await;
sim.wait_for_clock_edge(cd.clk).await;
let ready = *sim.read_past(post_decode_output.ready, cd.clk).await;
for _ in ArrayVec::elements_sim_ref(&post_decode_output_insns)
.iter()
.take(ready)
{
let Some((prev_br_pred_state, fetched)) = state.fetch_queue.pop_front() else {
unreachable!();
};
println!("fetched: {fetched:?}\nprev_br_pred_state: {prev_br_pred_state:?}");
}
}
}
}
#[hdl(no_static)]
struct MockLoadStoreDebugState {}
#[derive(Debug)]
struct MockLoadStoreState {
input: u64,
expected_output: u64,
wrote_output: bool,
memory: BTreeMap<u64, u8>,
}
impl MockLoadStoreState {
const IO_ADDR: u64 = 16u64.wrapping_neg();
fn new<'a, I: IntoIterator<Item = (u64, &'a [u8])>>(
input: u64,
expected_output: u64,
initial_memory: I,
) -> Self {
let mut memory = BTreeMap::new();
for (addr, bytes) in initial_memory {
memory.extend(
bytes
.as_ref()
.iter()
.enumerate()
.map(|(index, byte)| (addr.wrapping_add(index as u64), *byte)),
);
}
Self {
input,
expected_output,
wrote_output: false,
memory,
}
}
fn load<const SIGNED: bool, const SIZE_IN_BYTES: usize>(&self, mut addr: u64) -> u64 {
if addr == Self::IO_ADDR {
assert_eq!(SIZE_IN_BYTES, 8);
return self.input;
}
let mut value = 0u64.to_le_bytes();
let mut last_byte = 0;
for i in 0..SIZE_IN_BYTES {
let byte = self.memory.get(&addr).copied().unwrap_or(0);
last_byte = byte;
value[i] = byte;
addr = addr.wrapping_add(1);
}
if SIGNED && last_byte.cast_signed() < 0 {
value[SIZE_IN_BYTES..].fill(u8::MAX);
}
u64::from_le_bytes(value)
}
}
#[hdl(no_static)]
struct MockL2RegFileDebugState {}
#[derive(Debug)]
struct MockL2RegFile {}
impl MockL2RegFile {
fn new() -> Self {
Self {}
}
}
trait MockExecutionStateTrait {
type DebugState: Type;
fn try_get_load_store(&mut self) -> Option<&mut MockLoadStoreState>;
fn get_load_store(&mut self) -> &mut MockLoadStoreState {
self.try_get_load_store()
.expect("no MockLoadStoreState available in this unit")
}
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile>;
fn get_l2_reg_file(&mut self) -> &mut MockL2RegFile {
self.try_get_l2_reg_file()
.expect("no MockL2RegFile available in this unit")
}
fn debug_state_ty(&self) -> Self::DebugState;
fn debug_state(&self) -> SimValue<Self::DebugState>;
fn all_outputs_written(&self) -> bool {
true
}
#[hdl]
fn run_add_sub<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
&mut self,
pc: u64,
mop: &SimValue<AddSubMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>, SrcCount>>,
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
config: C,
) -> SimValue<PRegValue> {
#[hdl(sim)]
let AddSubMOp::<_, _, _> {
alu_common,
invert_src0,
src1_is_carry_in,
invert_carry_in,
add_pc,
} = mop;
#[hdl(sim)]
let AluCommonMOp::<_, _, _, _> {
common,
output_integer_mode,
} = alu_common;
#[hdl(sim)]
let CommonMOp::<_, _, _, _, _> {
prefix_pad: _,
dest: _,
src: _,
imm,
} = common;
if **invert_src0 {
todo!("invert_src0");
}
if **src1_is_carry_in {
todo!("src1_is_carry_in");
}
if **invert_carry_in {
todo!("invert_carry_in");
}
let pc_or_zero = if **add_pc { pc } else { 0 };
let [src0, src1, src2] = src_values;
let int_fp = src0
.int_fp
.as_int()
.wrapping_add(src1.int_fp.as_int())
.wrapping_add(src2.int_fp.as_int())
.wrapping_add(SimValue::value(imm).cast_to_static::<UInt<64>>().as_int())
.wrapping_add(pc_or_zero);
let int_fp = #[hdl(sim)]
match output_integer_mode {
OutputIntegerMode::Full64 => int_fp,
OutputIntegerMode::DupLow32 => {
let v = int_fp as u32 as u64;
v | (v << 32)
}
OutputIntegerMode::ZeroExt32 => int_fp as u32 as u64,
OutputIntegerMode::SignExt32 => int_fp as i32 as u64,
OutputIntegerMode::ZeroExt16 => int_fp as u16 as u64,
OutputIntegerMode::SignExt16 => int_fp as i16 as u64,
OutputIntegerMode::ZeroExt8 => int_fp as u8 as u64,
OutputIntegerMode::SignExt8 => int_fp as i8 as u64,
};
#[hdl(sim)]
PRegValue {
int_fp,
flags: PRegFlags::zeroed_sim(), // TODO: compute flags
}
}
#[hdl]
fn run_branch<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
&mut self,
id: &SimValue<MOpId>,
pc: u64,
fallthrough_pc: u64,
predicted_next_pc: u64,
mop: &SimValue<BranchMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>, SrcCount>>,
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
config: C,
) -> (SimValue<PRegValue>, Option<SimValue<UnitCausedCancel<C>>>) {
#[hdl(sim)]
let BranchMOp::<_, _, _> {
common,
invert_src0_cond,
src0_cond_mode,
invert_src2_eq_zero,
pc_relative,
is_call,
is_ret,
} = mop;
#[hdl(sim)]
let CommonMOp::<_, _, _, _, _> {
prefix_pad: _,
dest: _,
src,
imm,
} = common;
let [src0, src1, src2] = src_values;
let has_src2 = src.as_ref().get(2).is_some_and(|src2| {
src2.cast_bits_to(PRegNum[config]) != PRegNum[config].const_zero().into_sim_value()
});
let src2_cond = if has_src2 {
let _ = invert_src2_eq_zero;
todo!("has_src2");
} else {
true
};
let src0_flags = PRegFlags::view_sim_ref::<PRegFlagsPowerISA>(&src0.flags);
let src0_cond = #[hdl(sim)]
match src0_cond_mode {
ConditionMode::Eq => **src0_flags.cr_eq,
ConditionMode::ULt => **src0_flags.cr_lt,
ConditionMode::UGt => **src0_flags.cr_gt,
ConditionMode::SLt => **src0_flags.cr_lt,
ConditionMode::SGt => **src0_flags.cr_gt,
ConditionMode::Sign => todo!(),
ConditionMode::Overflow => **src0_flags.so,
ConditionMode::Parity => todo!(),
};
let src0_cond = src0_cond ^ **invert_src0_cond;
let pc_or_zero = if **pc_relative { pc } else { 0 };
let target_pc = src1
.int_fp
.as_int()
.wrapping_add(SimValue::value(imm).cast_to_static::<UInt<64>>().as_int())
.wrapping_add(pc_or_zero);
let next_pc = if src0_cond && src2_cond {
target_pc
} else {
fallthrough_pc
};
let cancel = if next_pc != predicted_next_pc {
Some(
#[hdl(sim)]
UnitCausedCancel::<C> {
id,
start_at_pc: next_pc,
config,
},
)
} else {
None
};
(
#[hdl(sim)]
PRegValue {
int_fp: fallthrough_pc,
flags: PRegFlags::zeroed_sim(),
},
cancel,
)
}
#[hdl]
fn run_mop<C: PhantomConstCpuConfig>(
&mut self,
mop: &SimValue<MOpInstance<RenamedMOp<C>>>,
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
config: C,
) -> (
Option<SimValue<PRegValue>>,
Option<SimValue<UnitCausedCancel<C>>>,
) {
#[hdl(sim)]
let MOpInstance::<_> {
fetch_block_id: _,
id,
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
mop,
} = mop;
let fallthrough_pc = pc
.as_int()
.wrapping_add(size_in_bytes.cast_to_static::<UInt<64>>().as_int());
#[hdl(sim)]
match mop {
UnitMOp::<_, _, _>::AluBranch(mop) =>
{
#[hdl(sim)]
match mop {
AluBranchMOp::<_, _>::AddSub(mop) => (
Some(self.run_add_sub(pc.as_int(), mop, src_values, config)),
None,
),
AluBranchMOp::<_, _>::AddSubI(mop) => (
Some(self.run_add_sub(pc.as_int(), mop, src_values, config)),
None,
),
AluBranchMOp::<_, _>::LogicalFlags(mop) => {
todo!("implement LogicalFlags")
}
AluBranchMOp::<_, _>::Logical(mop) => {
todo!("implement Logical")
}
AluBranchMOp::<_, _>::LogicalI(mop) => {
todo!("implement LogicalI")
}
AluBranchMOp::<_, _>::ShiftRotate(mop) => {
todo!("implement ShiftRotate")
}
AluBranchMOp::<_, _>::Compare(mop) => {
todo!("implement Compare")
}
AluBranchMOp::<_, _>::CompareI(mop) => {
todo!("implement CompareI")
}
AluBranchMOp::<_, _>::Branch(mop) => {
let (value, cancel) = self.run_branch(
id,
pc.as_int(),
fallthrough_pc,
predicted_next_pc.as_int(),
mop,
src_values,
config,
);
(Some(value), cancel)
}
AluBranchMOp::<_, _>::BranchI(mop) => {
let (value, cancel) = self.run_branch(
id,
pc.as_int(),
fallthrough_pc,
predicted_next_pc.as_int(),
mop,
src_values,
config,
);
(Some(value), cancel)
}
AluBranchMOp::<_, _>::ReadSpecial(mop) => {
todo!("implement ReadSpecial")
}
AluBranchMOp::<_, _>::Unknown => unreachable!(),
}
}
UnitMOp::<_, _, _>::LoadStore(mop) => {
let state = self.get_load_store();
#[hdl(sim)]
match mop {
LoadStoreMOp::<_, _>::Load(mop) => {
#[hdl(sim)]
let LoadMOp::<_, _> { load_store_common } = mop;
#[hdl(sim)]
let LoadStoreCommonMOp::<_, _, _> {
common: _,
width,
conversion,
} = load_store_common;
let addr = src_values[0].int_fp.as_int();
let loaded = #[hdl(sim)]
match conversion {
LoadStoreConversion::ZeroExt =>
{
#[hdl(sim)]
match width {
LoadStoreWidth::Width8Bit => state.load::<false, 1>(addr),
LoadStoreWidth::Width16Bit => state.load::<false, 2>(addr),
LoadStoreWidth::Width32Bit => state.load::<false, 4>(addr),
LoadStoreWidth::Width64Bit => state.load::<false, 8>(addr),
}
}
LoadStoreConversion::SignExt =>
{
#[hdl(sim)]
match width {
LoadStoreWidth::Width8Bit => state.load::<true, 1>(addr),
LoadStoreWidth::Width16Bit => state.load::<true, 2>(addr),
LoadStoreWidth::Width32Bit => state.load::<true, 4>(addr),
LoadStoreWidth::Width64Bit => state.load::<true, 8>(addr),
}
}
};
(
Some(
#[hdl(sim)]
PRegValue {
int_fp: loaded,
flags: PRegFlags::zeroed_sim(),
},
),
None,
)
}
LoadStoreMOp::<_, _>::Store(mop) => {
todo!("implement store")
}
}
}
UnitMOp::<_, _, _>::TransformedMove(mop) => {
let l2_reg_file = self.get_l2_reg_file();
#[hdl(sim)]
match mop {
L2RegisterFileMOp::<_, _>::ReadL2Reg(mop) => {
todo!("implement ReadL2Reg")
}
L2RegisterFileMOp::<_, _>::WriteL2Reg(mop) => {
todo!("implement WriteL2Reg")
}
}
}
UnitMOp::<_, _, _>::Unknown => unreachable!(),
}
}
}
impl MockExecutionStateTrait for MockLoadStoreState {
type DebugState = MockLoadStoreDebugState;
fn try_get_load_store(&mut self) -> Option<&mut MockLoadStoreState> {
Some(self)
}
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
None
}
fn debug_state_ty(&self) -> Self::DebugState {
MockLoadStoreDebugState
}
#[hdl]
fn debug_state(&self) -> SimValue<Self::DebugState> {
let Self {
input: _,
expected_output: _,
wrote_output: _,
memory: _,
} = self;
#[hdl(sim)]
MockLoadStoreDebugState {}
}
fn all_outputs_written(&self) -> bool {
self.wrote_output
}
}
impl MockExecutionStateTrait for MockL2RegFile {
type DebugState = MockL2RegFileDebugState;
fn try_get_load_store(&mut self) -> Option<&mut MockLoadStoreState> {
None
}
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
Some(self)
}
fn debug_state_ty(&self) -> Self::DebugState {
MockL2RegFileDebugState
}
#[hdl]
fn debug_state(&self) -> SimValue<Self::DebugState> {
let Self {} = self;
#[hdl(sim)]
MockL2RegFileDebugState {}
}
}
impl MockExecutionStateTrait for () {
type DebugState = ();
fn try_get_load_store(&mut self) -> Option<&mut MockLoadStoreState> {
None
}
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
None
}
fn debug_state_ty(&self) -> Self::DebugState {
()
}
fn debug_state(&self) -> SimValue<Self::DebugState> {
().into_sim_value()
}
}
#[hdl(no_static)]
struct MockUnitOpDebugState<C: PhantomConstGet<CpuConfig>> {
mop: MOpInstance<RenamedMOp<C>>,
src_values: Array<PRegValue, { COMMON_MOP_SRC_LEN }>,
finished: UnitFinished<C>,
config: C,
}
#[derive(Debug)]
struct MockUnitOp<C: PhantomConstCpuConfig> {
mop: SimValue<MOpInstance<RenamedMOp<C>>>,
src_values: [SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
finished: SimValue<UnitFinished<C>>,
config: C,
}
impl<C: PhantomConstCpuConfig> MockUnitOp<C> {
#[hdl]
fn debug_state(&self) -> SimValue<MockUnitOpDebugState<C>> {
let Self {
mop,
src_values,
finished,
config,
} = self;
#[hdl(sim)]
MockUnitOpDebugState::<_> {
mop,
src_values,
finished,
config,
}
}
}
#[hdl(no_static)]
struct MockUnitDebugState<C: PhantomConstGet<CpuConfig>, E> {
ops: ArrayVec<MockUnitOpDebugState<C>, CpuConfigMaxUnitMaxInFlight<C>>,
execution_state: E,
config: C,
}
#[derive(Debug)]
struct MockUnitState<C: PhantomConstCpuConfig, E> {
ops: BTreeMap<SimValue<MOpId>, MockUnitOp<C>>,
execution_state: E,
config: C,
unit_index: usize,
}
impl<C: PhantomConstCpuConfig, E: MockExecutionStateTrait> MockUnitState<C, E> {
fn new(execution_state: E, config: C, unit_index: usize) -> Self {
Self {
ops: BTreeMap::new(),
execution_state,
config,
unit_index,
}
}
#[hdl]
fn debug_state(&self) -> SimValue<MockUnitDebugState<C, E::DebugState>> {
let Self {
ops,
execution_state,
config,
unit_index: _,
} = self;
let execution_state = execution_state.debug_state();
let ret_ty = MockUnitDebugState[*config][execution_state.ty()];
#[hdl(sim)]
MockUnitDebugState::<_, _> {
ops: ret_ty
.ops
.from_iter_sim(
zeroed(ret_ty.ops.element()),
ops.values().map(MockUnitOp::debug_state),
)
.expect("known to fit"),
execution_state,
config,
}
}
#[hdl]
fn peek_cant_cause_cancel(&self) -> SimValue<HdlOption<UnitMOpCantCauseCancel<C>>> {
// TODO
#[hdl(sim)]
(HdlOption[UnitMOpCantCauseCancel[self.config]]).HdlNone()
}
#[hdl]
fn handle_mop_cant_cause_cancel(
&mut self,
cant_cause_cancel: SimValue<UnitMOpCantCauseCancel<C>>,
) {
todo!()
}
#[hdl]
fn peek_finished(&self) -> SimValue<HdlOption<UnitFinished<C>>> {
let ret_ty = HdlOption[UnitFinished[self.config]];
for op in self.ops.values() {
// TODO: add delay
let retval = #[hdl(sim)]
ret_ty.HdlSome(&op.finished);
return retval;
}
#[hdl(sim)]
ret_ty.HdlNone()
}
#[hdl]
fn handle_finished(&mut self, finished: SimValue<UnitFinished<C>>) {
self.ops.remove(&finished.id);
}
#[hdl]
fn handle_start(&mut self, start: SimValue<UnitStart<C>>) {
#[hdl(sim)]
let UnitStart::<_> {
mop,
src_values,
config: _,
} = start;
println!(
"unit {}: started executing: {:?}",
self.unit_index,
fmt::from_fn(|f| MOpDebug::mop_debug(&mop.mop, f))
);
let src_values = SimValue::into_value(src_values);
let (dest_value, caused_cancel) =
self.execution_state.run_mop(&mop, &src_values, self.config);
let finished_ty = UnitFinished[self.config];
let finished = #[hdl(sim)]
UnitFinished::<_> {
id: mop.id.clone(),
finished_successfully: if let Some(dest_value) = dest_value {
#[hdl(sim)]
(finished_ty.finished_successfully).HdlSome(
#[hdl(sim)]
UnitFinishedSuccessfully::<_> {
dest: &MOpTrait::dest_reg_sim_ref(&mop.mop).unit_out_reg,
dest_value,
},
)
} else {
#[hdl(sim)]
(finished_ty.finished_successfully).HdlNone()
},
caused_cancel: if let Some(caused_cancel) = caused_cancel {
#[hdl(sim)]
(finished_ty.caused_cancel).HdlSome(caused_cancel)
} else {
#[hdl(sim)]
(finished_ty.caused_cancel).HdlNone()
},
config: self.config,
};
self.ops.insert(
mop.id.clone(),
MockUnitOp {
mop,
src_values,
finished,
config: self.config,
},
);
}
#[hdl]
fn handle_mop_is_no_longer_speculative(
&mut self,
is_no_longer_speculative: SimValue<UnitMOpIsNoLongerSpeculative<C>>,
) {
// TODO
}
fn cancel_all(&mut self) {
// TODO
}
}
fn is_the_load_store_unit(config: PhantomConst<CpuConfig>, unit_index: usize) -> bool {
let load_store_unit_index = config
.get()
.units
.iter()
.position(|v| v.kind == UnitKind::LoadStore);
let load_store_last_unit_index = config
.get()
.units
.iter()
.rposition(|v| v.kind == UnitKind::LoadStore);
if load_store_unit_index != load_store_last_unit_index {
todo!("multiple LoadStore units aren't implemented");
}
Some(unit_index) == load_store_unit_index
}
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)] MI: MakeInsns>(config: PhantomConst<CpuConfig>, unit_index: usize) {
let execution_state_ty = if is_the_load_store_unit(config, unit_index) {
Bundle::from_canonical(MockLoadStoreDebugState.canonical())
} else if is_the_l2_reg_file_unit(config, unit_index) {
Bundle::from_canonical(MockL2RegFileDebugState.canonical())
} else {
Bundle::from_canonical(().canonical())
};
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
m.input(ExecuteToUnitInterface[config]);
#[hdl]
let debug_state: MockUnitDebugState<PhantomConst<CpuConfig>, Bundle> =
m.output(MockUnitDebugState[config][execution_state_ty]);
#[hdl]
let all_outputs_written: Bool = m.output();
m.register_clock_for_past(cd.clk);
m.extern_module_simulation_fn(
(
cd,
from_execute,
debug_state,
all_outputs_written,
config,
unit_index,
),
async |args, mut sim| {
let (cd, from_execute, debug_state, all_outputs_written, config, unit_index) = args;
sim.resettable(
cd,
async |mut sim| {
#[hdl]
let ExecuteToUnitInterface::<_> {
start,
cancel_all,
is_no_longer_speculative,
cant_cause_cancel,
finished,
config: _,
} = from_execute;
sim.write(start.ready, false).await;
sim.write(cancel_all.ready, false).await;
sim.write(is_no_longer_speculative.ready, false).await;
sim.write(
cant_cause_cancel.data,
#[hdl(sim)]
(cant_cause_cancel.ty().data).HdlNone(),
)
.await;
sim.write(
finished.data,
#[hdl(sim)]
(finished.ty().data).HdlNone(),
)
.await;
sim.write(debug_state, zeroed(debug_state.ty())).await;
},
async |sim, ()| {
if is_the_load_store_unit(config, unit_index) {
run_fn(
cd,
from_execute,
Expr::from_bundle(Expr::as_bundle(debug_state)),
all_outputs_written,
MI::make_load_store_execution_state(),
config,
unit_index,
sim,
)
.await
} else if is_the_l2_reg_file_unit(config, unit_index) {
run_fn(
cd,
from_execute,
Expr::from_bundle(Expr::as_bundle(debug_state)),
all_outputs_written,
MockL2RegFile::new(),
config,
unit_index,
sim,
)
.await
} else {
run_fn(
cd,
from_execute,
Expr::from_bundle(Expr::as_bundle(debug_state)),
all_outputs_written,
(),
config,
unit_index,
sim,
)
.await
}
},
)
.await;
},
);
#[hdl]
async fn run_fn<E: MockExecutionStateTrait>(
cd: Expr<ClockDomain>,
from_execute: Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>,
debug_state: Expr<MockUnitDebugState<PhantomConst<CpuConfig>, E::DebugState>>,
all_outputs_written: Expr<Bool>,
execution_state: E,
config: PhantomConst<CpuConfig>,
unit_index: usize,
mut sim: ExternModuleSimulationState,
) {
let mut state = MockUnitState::new(execution_state, config, unit_index);
loop {
{
#[hdl]
let ExecuteToUnitInterface::<_> {
start,
cancel_all,
is_no_longer_speculative,
cant_cause_cancel,
finished,
config: _,
} = from_execute;
sim.write(start.ready, true).await;
sim.write(cancel_all.ready, true).await;
sim.write(is_no_longer_speculative.ready, true).await;
sim.write(cant_cause_cancel.data, state.peek_cant_cause_cancel())
.await;
sim.write(finished.data, state.peek_finished()).await;
}
sim.write(debug_state, state.debug_state()).await;
sim.write(
all_outputs_written,
state.execution_state.all_outputs_written(),
)
.await;
sim.wait_for_clock_edge(cd.clk).await;
{
#[hdl]
let ExecuteToUnitInterface::<_> {
start,
cancel_all,
is_no_longer_speculative,
cant_cause_cancel,
finished,
config: _,
} = from_execute;
if sim.read_past_bool(start.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(start) = sim.read_past(start.data, cd.clk).await {
state.handle_start(start);
}
}
if sim
.read_past_bool(is_no_longer_speculative.ready, cd.clk)
.await
{
#[hdl(sim)]
if let HdlSome(is_no_longer_speculative) =
sim.read_past(is_no_longer_speculative.data, cd.clk).await
{
state.handle_mop_is_no_longer_speculative(is_no_longer_speculative);
}
}
if sim.read_past_bool(cant_cause_cancel.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(cant_cause_cancel) =
sim.read_past(cant_cause_cancel.data, cd.clk).await
{
state.handle_mop_cant_cause_cancel(cant_cause_cancel);
}
}
if sim.read_past_bool(finished.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(finished) = sim.read_past(finished.data, cd.clk).await {
state.handle_finished(finished);
}
}
if sim.read_past_bool(cancel_all.ready, cd.clk).await {
#[hdl(sim)]
if let HdlSome(cancel_all) = sim.read_past(cancel_all.data, cd.clk).await {
let () = *cancel_all;
state.cancel_all();
}
}
}
}
}
}
#[hdl_module]
fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let all_outputs_written: Bool = m.output();
#[hdl]
let next_pc = instance(mock_next_pc::<MI>(config));
connect(next_pc.cd, cd);
#[hdl]
let dut = instance(rename_execute_retire(config));
connect(dut.cd, cd);
connect(dut.from_post_decode, next_pc.post_decode_output);
connect(next_pc.from_retire, dut.to_next_pc);
connect(all_outputs_written, true);
for (unit_index, to_unit) in ExecuteToUnitInterfaces::unit_fields(dut.to_units)
.into_iter()
.enumerate()
{
let mock_unit = instance_with_loc(
&dut.ty().to_units.unit_field_name(unit_index),
mock_unit::<MI>(config, unit_index),
SourceLocation::caller(),
);
connect(mock_unit.cd, cd);
connect(mock_unit.from_execute, to_unit);
#[hdl]
if !mock_unit.all_outputs_written {
connect(all_outputs_written, false);
}
}
}
#[hdl]
#[test]
fn test_rename_execute_retire_fibonacci() {
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));
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
for cycle in 0..50 {
sim.advance_time(SimDuration::from_nanos(500));
println!("clock tick: {cycle}");
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert!(sim.read_bool(sim.io().all_outputs_written));
// FIXME: vcd is just whatever rename_execute_retire does now, which isn't known to be correct
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/rename_execute_retire_fibonacci.vcd") {
panic!();
}
}