forked from libre-chip/cpu
2985 lines
101 KiB
Rust
2985 lines
101 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, UnitNum,
|
|
},
|
|
next_pc::CallStackOp,
|
|
register::{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,
|
|
},
|
|
unit::{UnitKind, UnitMOp},
|
|
util::array_vec::ArrayVec,
|
|
};
|
|
use fayalite::{
|
|
bundle::BundleType,
|
|
module::instance_with_loc,
|
|
prelude::*,
|
|
sim::vcd::VcdWriterDecls,
|
|
ty::{OpaqueSimValue, StaticType},
|
|
util::RcWriter,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{
|
|
cell::Cell,
|
|
cmp::Ordering,
|
|
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 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_ble(&mut self, target: InsnsBuilderLabel) {
|
|
let pc = self.pc;
|
|
self.add_insn(Insn::new_lazy(
|
|
4,
|
|
format!("ble {}", 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<_>>(),
|
|
true,
|
|
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<_>>(),
|
|
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() -> MockMemory;
|
|
}
|
|
|
|
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, MockMemory::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, MockMemory::IO_ADDR as i16); // store output
|
|
let done = b.new_defined_label("done");
|
|
b.power_isa_b(done);
|
|
|
|
b.set_pc(0x1000);
|
|
b.define_label(fib);
|
|
b.power_isa_cmpldi(0, 3, 1);
|
|
b.power_isa_ble(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() -> MockMemory {
|
|
MockMemory::new(4, 5, [])
|
|
}
|
|
}
|
|
|
|
#[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 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"),
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn run<C: PhantomConstCpuConfig>(&mut self, op: &SimValue<NextPcPredictorOp<C>>) {
|
|
#[hdl(sim)]
|
|
let NextPcPredictorOp::<_> {
|
|
call_stack_op,
|
|
cond_br_taken,
|
|
config: _,
|
|
} = op;
|
|
#[hdl(sim)]
|
|
match call_stack_op {
|
|
CallStackOp::None => {}
|
|
CallStackOp::Push(return_pc) => self.predict_call(return_pc.as_int()),
|
|
CallStackOp::Pop => {
|
|
self.predict_ret();
|
|
}
|
|
CallStackOp::Unknown => unreachable!(),
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(cond_br_taken) = cond_br_taken {
|
|
// TODO
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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<MOpInstance<MOp>, CpuConfigFetchWidth<C>>,
|
|
retire_br_pred_state: BrPredDebugState,
|
|
canceling: Bool,
|
|
config: C,
|
|
}
|
|
|
|
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<SimValue<MOpInstance<MOp>>>,
|
|
retire_br_pred_state: BrPredState,
|
|
canceling: bool,
|
|
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()),
|
|
retire_br_pred_state: BrPredState::new(),
|
|
canceling: false,
|
|
config,
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<MockNextPcDebugState<C>> {
|
|
let Self {
|
|
random_state: _,
|
|
insns: _,
|
|
next_pc,
|
|
next_mop_index,
|
|
next_br_pred_state,
|
|
next_id,
|
|
fetch_block_id,
|
|
fetch_queue,
|
|
retire_br_pred_state,
|
|
canceling,
|
|
config,
|
|
} = 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())
|
|
.expect("known to fit"),
|
|
retire_br_pred_state: retire_br_pred_state.debug_state(),
|
|
canceling,
|
|
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 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(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]];
|
|
if self.canceling {
|
|
return ty.new_sim(zeroed(MOpInstance[MOp]));
|
|
}
|
|
let peek_size = self.random_state.random_u64(u32::from_le_bytes(*b"pdoi")) as usize
|
|
% (16 * ty.capacity());
|
|
let peek_size = peek_size.min(ty.capacity());
|
|
ty.from_iter_sim(
|
|
zeroed(MOpInstance[MOp]),
|
|
self.fetch_queue.iter().take(peek_size),
|
|
)
|
|
.ok()
|
|
.expect("known to fit")
|
|
}
|
|
fn cancel_and_start_at(&mut self, start_at_pc: u64) {
|
|
let Self {
|
|
random_state: _,
|
|
insns: _,
|
|
next_pc,
|
|
next_mop_index,
|
|
next_br_pred_state,
|
|
next_id: _,
|
|
fetch_block_id: _,
|
|
fetch_queue,
|
|
retire_br_pred_state,
|
|
canceling,
|
|
config: _,
|
|
} = self;
|
|
*next_pc = start_at_pc;
|
|
*next_mop_index = 0;
|
|
*next_br_pred_state = *retire_br_pred_state;
|
|
fetch_queue.clear();
|
|
*canceling = true;
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
if state.canceling {
|
|
#[hdl(sim)]
|
|
HdlSome(())
|
|
} else {
|
|
#[hdl(sim)]
|
|
HdlNone()
|
|
},
|
|
)
|
|
.await;
|
|
sim.write(from_retire.inner.ready, true).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(fetched) = state.fetch_queue.pop_front() else {
|
|
unreachable!();
|
|
};
|
|
println!("fetched: {fetched:?}");
|
|
}
|
|
if sim.read_past_bool(from_retire.inner.ready, cd.clk).await {
|
|
#[hdl(sim)]
|
|
if let HdlSome(retire) = sim.read_past(from_retire.inner.data, cd.clk).await {
|
|
#[hdl(sim)]
|
|
match retire {
|
|
RetireToNextPcInterfaceInner::<_>::CancelAndStartAt(start_at_pc) => {
|
|
state.cancel_and_start_at(start_at_pc.as_int());
|
|
}
|
|
RetireToNextPcInterfaceInner::<_>::RetiredInstructions(retire) => {
|
|
for op in ArrayVec::elements_sim_ref(&retire) {
|
|
state.retire_br_pred_state.run(op);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if sim
|
|
.read_past_bool(post_decode_output.cancel.ready, cd.clk)
|
|
.await
|
|
{
|
|
#[hdl(sim)]
|
|
if let HdlSome(cancel) = sim.read_past(post_decode_output.cancel.data, cd.clk).await
|
|
{
|
|
let () = *cancel;
|
|
assert!(state.canceling);
|
|
state.canceling = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
struct MemoryChunk {
|
|
addr: Option<u64>,
|
|
data: [u8; Self::CHUNK_SIZE],
|
|
}
|
|
|
|
impl Default for MemoryChunk {
|
|
fn default() -> Self {
|
|
Self {
|
|
addr: None,
|
|
data: [0; _],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for MemoryChunk {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Self { addr, data } = self;
|
|
let Some(addr) = addr else {
|
|
return f.write_str("<empty>");
|
|
};
|
|
write!(f, "0x{addr:016x}:")?;
|
|
for (i, &byte) in data.iter().enumerate() {
|
|
if i % 4 == 0 {
|
|
f.write_str(" ")?;
|
|
} else {
|
|
f.write_str(" ")?;
|
|
}
|
|
write!(f, "{byte:02x}")?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MemoryChunk {
|
|
const LOG2_CHUNK_SIZE: u32 = 4;
|
|
const CHUNK_SIZE: usize = 1 << Self::LOG2_CHUNK_SIZE;
|
|
fn byte_addr_to_chunk_addr(byte_addr: u64) -> u64 {
|
|
byte_addr - byte_addr % Self::CHUNK_SIZE as u64
|
|
}
|
|
}
|
|
|
|
#[hdl]
|
|
struct MockCacheLineDebugState {
|
|
chunk_addr: UInt<64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct MockCacheLine {
|
|
chunk_addr: u64,
|
|
}
|
|
|
|
#[hdl(get(|_| 32))]
|
|
type MockMemoryDebugStateMemorySize<C: PhantomConstGet<()>> = DynSize;
|
|
|
|
#[hdl]
|
|
type SimOnlyMemoryChunk = SimOnly<MemoryChunk>;
|
|
|
|
#[hdl(no_static)]
|
|
struct MockMemoryDebugState<C: PhantomConstGet<()> = PhantomConst<()>> {
|
|
wrote_output: Bool,
|
|
memory: ArrayType<SimOnlyMemoryChunk, MockMemoryDebugStateMemorySize<C>>,
|
|
l1_cache: Array<HdlOption<MockCacheLineDebugState>, { MockMemory::CACHE_LINE_COUNT }>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct MockMemory {
|
|
input: u64,
|
|
expected_output: u64,
|
|
wrote_output: bool,
|
|
memory: BTreeMap<u64, SimOnlyValue<MemoryChunk>>,
|
|
l1_cache: [Option<MockCacheLine>; Self::CACHE_LINE_COUNT],
|
|
}
|
|
|
|
struct AddressCantBeSpeculativelyAccessed;
|
|
|
|
enum FillCacheError {
|
|
Uncacheable,
|
|
CacheMiss,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct LoadStoreChunk {
|
|
chunk_addr: u64,
|
|
load_store_range: std::ops::Range<usize>,
|
|
chunk_range: std::ops::Range<usize>,
|
|
}
|
|
|
|
impl LoadStoreChunk {
|
|
fn chunks(mut addr: u64, load_store_len: usize) -> impl Iterator<Item = Self> {
|
|
let mut load_store_start = 0;
|
|
std::iter::from_fn(move || -> Option<Self> {
|
|
if load_store_start == load_store_len {
|
|
return None;
|
|
}
|
|
let load_store_len_left = load_store_len - load_store_start;
|
|
let chunk_addr = MemoryChunk::byte_addr_to_chunk_addr(addr);
|
|
let chunk_start = (addr - chunk_addr) as usize;
|
|
let chunk_len_after_start = MemoryChunk::CHUNK_SIZE - chunk_start;
|
|
let cur_len = chunk_len_after_start.min(load_store_len_left);
|
|
let chunk_end = chunk_start + cur_len;
|
|
assert_ne!(cur_len, 0);
|
|
let next_load_store_start = load_store_start + cur_len;
|
|
let load_store_range = load_store_start..next_load_store_start;
|
|
load_store_start = next_load_store_start;
|
|
addr = addr.wrapping_add(cur_len as u64);
|
|
Some(Self {
|
|
chunk_addr,
|
|
load_store_range,
|
|
chunk_range: chunk_start..chunk_end,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
impl MockMemory {
|
|
const IO_ADDR: u64 = 16u64.wrapping_neg();
|
|
const LOG2_CACHE_LINE_COUNT: usize = 2;
|
|
const CACHE_LINE_COUNT: usize = 1 << Self::LOG2_CACHE_LINE_COUNT;
|
|
fn new<'a, I: IntoIterator<Item = (u64, &'a [u8])>>(
|
|
input: u64,
|
|
expected_output: u64,
|
|
initial_memory: I,
|
|
) -> Self {
|
|
let mut retval = Self {
|
|
input,
|
|
expected_output,
|
|
wrote_output: false,
|
|
memory: BTreeMap::new(),
|
|
l1_cache: [const { None }; _],
|
|
};
|
|
for (addr, bytes) in initial_memory {
|
|
let Ok(()) = retval.store_bytes(addr, bytes, false) else {
|
|
unreachable!();
|
|
};
|
|
}
|
|
retval.cache_flush();
|
|
retval
|
|
}
|
|
fn cache_flush(&mut self) {
|
|
self.l1_cache.fill(None);
|
|
}
|
|
fn is_cacheable_chunk_addr(chunk_addr: u64) -> bool {
|
|
chunk_addr != MemoryChunk::byte_addr_to_chunk_addr(Self::IO_ADDR)
|
|
}
|
|
fn cache_index(chunk_addr: u64) -> usize {
|
|
((chunk_addr >> MemoryChunk::LOG2_CHUNK_SIZE) % Self::CACHE_LINE_COUNT as u64) as usize
|
|
}
|
|
fn check_and_fill_cache(
|
|
&mut self,
|
|
chunk_addr: u64,
|
|
is_speculative: bool,
|
|
) -> Result<(), FillCacheError> {
|
|
let cache_line = &mut self.l1_cache[Self::cache_index(chunk_addr)];
|
|
if cache_line
|
|
.as_ref()
|
|
.is_some_and(|cache_line| cache_line.chunk_addr == chunk_addr)
|
|
{
|
|
return Ok(());
|
|
}
|
|
if is_speculative {
|
|
return Err(FillCacheError::CacheMiss);
|
|
}
|
|
if Self::is_cacheable_chunk_addr(chunk_addr) {
|
|
*cache_line = Some(MockCacheLine { chunk_addr });
|
|
Ok(())
|
|
} else {
|
|
Err(FillCacheError::Uncacheable)
|
|
}
|
|
}
|
|
fn load_bytes(
|
|
&mut self,
|
|
addr: u64,
|
|
bytes: &mut [u8],
|
|
is_speculative: bool,
|
|
) -> Result<(), AddressCantBeSpeculativelyAccessed> {
|
|
for LoadStoreChunk {
|
|
chunk_addr,
|
|
load_store_range,
|
|
chunk_range,
|
|
} in LoadStoreChunk::chunks(addr, bytes.len())
|
|
{
|
|
match self.check_and_fill_cache(chunk_addr, is_speculative) {
|
|
Ok(()) => {
|
|
let src = &self
|
|
.memory
|
|
.get(&chunk_addr)
|
|
.map(|chunk| &chunk.data)
|
|
.unwrap_or(const { &[0u8; MemoryChunk::CHUNK_SIZE] })[chunk_range];
|
|
bytes[load_store_range].copy_from_slice(src);
|
|
}
|
|
Err(FillCacheError::Uncacheable) => {
|
|
assert!(!is_speculative);
|
|
for (chunk_byte_index, byte) in chunk_range.zip(&mut bytes[load_store_range]) {
|
|
let offset = chunk_addr
|
|
.wrapping_add(chunk_byte_index as u64)
|
|
.wrapping_sub(Self::IO_ADDR);
|
|
let value = self.input.to_le_bytes();
|
|
assert!(offset < value.len() as u64);
|
|
*byte = value[offset as usize];
|
|
}
|
|
}
|
|
Err(FillCacheError::CacheMiss) => return Err(AddressCantBeSpeculativelyAccessed),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
fn store_bytes(
|
|
&mut self,
|
|
addr: u64,
|
|
bytes: &[u8],
|
|
is_speculative: bool,
|
|
) -> Result<(), AddressCantBeSpeculativelyAccessed> {
|
|
for LoadStoreChunk {
|
|
chunk_addr,
|
|
load_store_range,
|
|
chunk_range,
|
|
} in LoadStoreChunk::chunks(addr, bytes.len())
|
|
{
|
|
match self.check_and_fill_cache(chunk_addr, is_speculative) {
|
|
Ok(()) => {
|
|
let dest = &mut self
|
|
.memory
|
|
.entry(chunk_addr)
|
|
.or_insert_with(|| {
|
|
SimOnlyValue::new(MemoryChunk {
|
|
addr: Some(chunk_addr),
|
|
data: [0; _],
|
|
})
|
|
})
|
|
.data[chunk_range];
|
|
dest.copy_from_slice(&bytes[load_store_range]);
|
|
}
|
|
Err(FillCacheError::Uncacheable) => {
|
|
assert!(!is_speculative);
|
|
let mut value = [0; _];
|
|
for (chunk_byte_index, byte) in chunk_range.zip(&bytes[load_store_range]) {
|
|
let offset = chunk_addr
|
|
.wrapping_add(chunk_byte_index as u64)
|
|
.wrapping_sub(Self::IO_ADDR);
|
|
assert!(offset < value.len() as u64);
|
|
value[offset as usize] = *byte;
|
|
}
|
|
let value = u64::from_le_bytes(value);
|
|
assert_eq!(
|
|
self.expected_output, value,
|
|
"output doesn't match expected output"
|
|
);
|
|
self.wrote_output = true;
|
|
}
|
|
Err(FillCacheError::CacheMiss) => return Err(AddressCantBeSpeculativelyAccessed),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
fn store<const SIZE_IN_BYTES: usize>(
|
|
&mut self,
|
|
addr: u64,
|
|
value: u64,
|
|
is_speculative: bool,
|
|
) -> Result<(), AddressCantBeSpeculativelyAccessed> {
|
|
self.store_bytes(addr, &value.to_le_bytes(), is_speculative)
|
|
}
|
|
fn load<const SIGNED: bool, const SIZE_IN_BYTES: usize>(
|
|
&mut self,
|
|
addr: u64,
|
|
is_speculative: bool,
|
|
) -> Result<u64, AddressCantBeSpeculativelyAccessed> {
|
|
let mut value = [0u8; _];
|
|
self.load_bytes(addr, &mut value[..SIZE_IN_BYTES], is_speculative)?;
|
|
if SIZE_IN_BYTES < value.len() && SIZE_IN_BYTES > 0 {
|
|
if SIGNED && value[SIZE_IN_BYTES - 1].cast_signed() < 0 {
|
|
value[SIZE_IN_BYTES..].fill(u8::MAX);
|
|
}
|
|
}
|
|
Ok(u64::from_le_bytes(value))
|
|
}
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<MockMemoryDebugState> {
|
|
let Self {
|
|
input: _,
|
|
expected_output: _,
|
|
wrote_output,
|
|
memory,
|
|
l1_cache,
|
|
} = self;
|
|
let ret_ty = MockMemoryDebugState[PhantomConst::default()];
|
|
let empty_chunk = SimOnlyValue::new(MemoryChunk::default());
|
|
#[hdl(sim)]
|
|
MockMemoryDebugState::<_> {
|
|
wrote_output,
|
|
memory: SimValue::from_array_elements(
|
|
ret_ty.memory,
|
|
memory
|
|
.values()
|
|
.cloned()
|
|
.chain(std::iter::repeat(empty_chunk))
|
|
.take(ret_ty.memory.len()),
|
|
),
|
|
l1_cache: l1_cache.clone().map(|v| {
|
|
if let Some(MockCacheLine { chunk_addr }) = v {
|
|
#[hdl(sim)]
|
|
HdlSome(
|
|
#[hdl(sim)]
|
|
MockCacheLineDebugState { chunk_addr },
|
|
)
|
|
} else {
|
|
#[hdl(sim)]
|
|
HdlNone()
|
|
}
|
|
}),
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn run_mop<C: PhantomConstCpuConfig>(
|
|
&mut self,
|
|
mop: &SimValue<MOpInstance<LoadStoreMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>>>>,
|
|
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
|
|
is_speculative: bool,
|
|
) -> Result<SimValue<PRegValue>, AddressCantBeSpeculativelyAccessed> {
|
|
#[hdl(sim)]
|
|
match &mop.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 => {
|
|
self.load::<false, 1>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width16Bit => {
|
|
self.load::<false, 2>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width32Bit => {
|
|
self.load::<false, 4>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width64Bit => {
|
|
self.load::<false, 8>(addr, is_speculative)?
|
|
}
|
|
}
|
|
}
|
|
LoadStoreConversion::SignExt =>
|
|
{
|
|
#[hdl(sim)]
|
|
match width {
|
|
LoadStoreWidth::Width8Bit => {
|
|
self.load::<true, 1>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width16Bit => {
|
|
self.load::<true, 2>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width32Bit => {
|
|
self.load::<true, 4>(addr, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width64Bit => {
|
|
self.load::<true, 8>(addr, is_speculative)?
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Ok(
|
|
#[hdl(sim)]
|
|
PRegValue {
|
|
int_fp: loaded,
|
|
flags: PRegFlags::zeroed_sim(),
|
|
},
|
|
)
|
|
}
|
|
LoadStoreMOp::<_, _>::Store(mop) => {
|
|
#[hdl(sim)]
|
|
let StoreMOp::<_, _> { load_store_common } = mop;
|
|
#[hdl(sim)]
|
|
let LoadStoreCommonMOp::<_, _, _> {
|
|
common: _,
|
|
width,
|
|
conversion,
|
|
} = load_store_common;
|
|
let addr = src_values[0].int_fp.as_int();
|
|
let value = src_values[1].int_fp.as_int();
|
|
#[hdl(sim)]
|
|
match conversion {
|
|
LoadStoreConversion::ZeroExt | LoadStoreConversion::SignExt =>
|
|
{
|
|
#[hdl(sim)]
|
|
match width {
|
|
LoadStoreWidth::Width8Bit => {
|
|
self.store::<1>(addr, value, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width16Bit => {
|
|
self.store::<2>(addr, value, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width32Bit => {
|
|
self.store::<4>(addr, value, is_speculative)?
|
|
}
|
|
LoadStoreWidth::Width64Bit => {
|
|
self.store::<8>(addr, value, is_speculative)?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(PRegValue::zeroed_sim())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[hdl(no_static)]
|
|
struct MockL2RegFileDebugState {}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct MockL2RegFile {}
|
|
|
|
trait MockExecutionStateTrait: Default {
|
|
type DebugState: BundleType;
|
|
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile>;
|
|
fn get_l2_reg_file(&mut self) -> &mut MockL2RegFile {
|
|
self.try_get_l2_reg_file()
|
|
.expect("no MockL2RegFile available in this unit")
|
|
}
|
|
fn debug_state_ty() -> Self::DebugState;
|
|
fn zeroed_debug_state() -> SimValue<Self::DebugState>;
|
|
fn debug_state(&self) -> SimValue<Self::DebugState>;
|
|
#[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],
|
|
) -> 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_compare<C: PhantomConstCpuConfig, SrcCount: KnownSize>(
|
|
&mut self,
|
|
mop: &SimValue<CompareMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>, SrcCount>>,
|
|
src_values: &[SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
|
|
) -> SimValue<PRegValue> {
|
|
#[hdl(sim)]
|
|
let CompareMOp::<_, _, _> {
|
|
common,
|
|
compare_mode,
|
|
} = mop;
|
|
#[hdl(sim)]
|
|
let CommonMOp::<_, _, _, _, _> {
|
|
prefix_pad: _,
|
|
dest: _,
|
|
src: _,
|
|
imm,
|
|
} = common;
|
|
let (lhs, rhs) = match SrcCount::VALUE {
|
|
1 => (
|
|
src_values[0].int_fp.as_int(),
|
|
SimValue::value(imm).cast_to_static::<UInt<64>>().as_int(),
|
|
),
|
|
2 => (src_values[0].int_fp.as_int(), src_values[1].int_fp.as_int()),
|
|
_ => todo!(),
|
|
};
|
|
let ordering_to_result = |v: Ordering| -> SimValue<PRegValue> {
|
|
let mut retval = #[hdl(sim)]
|
|
PRegValue {
|
|
int_fp: 0u64,
|
|
flags: PRegFlags::zeroed_sim(),
|
|
};
|
|
let flags = PRegFlags::view_sim_mut::<PRegFlagsPowerISA>(&mut retval.flags);
|
|
match v {
|
|
Ordering::Less => **flags.cr_lt = true,
|
|
Ordering::Equal => **flags.cr_eq = true,
|
|
Ordering::Greater => **flags.cr_gt = true,
|
|
}
|
|
retval
|
|
};
|
|
#[hdl(sim)]
|
|
match compare_mode {
|
|
CompareMode::U64 => ordering_to_result(u64::cmp(&lhs, &rhs)),
|
|
CompareMode::S64 => ordering_to_result(i64::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::U32 => ordering_to_result(u32::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::S32 => ordering_to_result(i32::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::U16 => ordering_to_result(u16::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::S16 => ordering_to_result(i16::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::U8 => ordering_to_result(u8::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::S8 => ordering_to_result(i8::cmp(&(lhs as _), &(rhs as _))),
|
|
CompareMode::CmpRBOne => todo!(),
|
|
CompareMode::CmpRBTwo => todo!(),
|
|
CompareMode::CmpEqB => todo!(),
|
|
CompareMode::Unknown => unreachable!(),
|
|
}
|
|
}
|
|
#[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>,
|
|
SimValue<NextPcPredictorOp<C>>,
|
|
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_src0 = src.as_ref().get(0).is_some_and(|src0| {
|
|
src0.cast_bits_to(PRegNum[config]) != PRegNum[config].const_zero().into_sim_value()
|
|
});
|
|
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;
|
|
let _ = src2;
|
|
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 cond_br_taken = src0_cond && src2_cond;
|
|
let is_cond = !cond_br_taken || has_src0 || has_src2;
|
|
let next_pc = if cond_br_taken {
|
|
target_pc
|
|
} else {
|
|
fallthrough_pc
|
|
};
|
|
let cancel = if next_pc != predicted_next_pc {
|
|
Some(
|
|
#[hdl(sim)]
|
|
UnitCausedCancel::<C> {
|
|
start_at_pc: next_pc,
|
|
cancel_after_retire: true,
|
|
config,
|
|
},
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
(
|
|
#[hdl(sim)]
|
|
PRegValue {
|
|
int_fp: fallthrough_pc,
|
|
flags: PRegFlags::zeroed_sim(),
|
|
},
|
|
#[hdl(sim)]
|
|
NextPcPredictorOp::<_> {
|
|
call_stack_op: if **is_ret {
|
|
#[hdl(sim)]
|
|
CallStackOp.Pop()
|
|
} else if **is_call {
|
|
#[hdl(sim)]
|
|
CallStackOp.Push(fallthrough_pc)
|
|
} else {
|
|
#[hdl(sim)]
|
|
CallStackOp.None()
|
|
},
|
|
cond_br_taken: if is_cond {
|
|
#[hdl(sim)]
|
|
HdlSome(cond_br_taken)
|
|
} else {
|
|
#[hdl(sim)]
|
|
HdlNone()
|
|
},
|
|
config,
|
|
},
|
|
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>, SimValue<NextPcPredictorOp<C>>)>,
|
|
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());
|
|
let empty_predictor_op = || {
|
|
#[hdl(sim)]
|
|
NextPcPredictorOp::<_> {
|
|
call_stack_op: #[hdl(sim)]
|
|
CallStackOp.None(),
|
|
cond_br_taken: #[hdl(sim)]
|
|
HdlNone(),
|
|
config,
|
|
}
|
|
};
|
|
#[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),
|
|
empty_predictor_op(),
|
|
)),
|
|
None,
|
|
),
|
|
AluBranchMOp::<_, _>::AddSubI(mop) => (
|
|
Some((
|
|
self.run_add_sub(pc.as_int(), mop, src_values),
|
|
empty_predictor_op(),
|
|
)),
|
|
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) => (
|
|
Some((self.run_compare(mop, src_values), empty_predictor_op())),
|
|
None,
|
|
),
|
|
AluBranchMOp::<_, _>::CompareI(mop) => (
|
|
Some((self.run_compare(mop, src_values), empty_predictor_op())),
|
|
None,
|
|
),
|
|
AluBranchMOp::<_, _>::Branch(mop) => {
|
|
let (value, predictor_op, cancel) = self.run_branch(
|
|
id,
|
|
pc.as_int(),
|
|
fallthrough_pc,
|
|
predicted_next_pc.as_int(),
|
|
mop,
|
|
src_values,
|
|
config,
|
|
);
|
|
(Some((value, predictor_op)), cancel)
|
|
}
|
|
AluBranchMOp::<_, _>::BranchI(mop) => {
|
|
let (value, predictor_op, cancel) = self.run_branch(
|
|
id,
|
|
pc.as_int(),
|
|
fallthrough_pc,
|
|
predicted_next_pc.as_int(),
|
|
mop,
|
|
src_values,
|
|
config,
|
|
);
|
|
(Some((value, predictor_op)), cancel)
|
|
}
|
|
AluBranchMOp::<_, _>::ReadSpecial(mop) => {
|
|
todo!("implement ReadSpecial")
|
|
}
|
|
AluBranchMOp::<_, _>::Unknown => unreachable!(),
|
|
}
|
|
}
|
|
UnitMOp::<_, _, _>::LoadStore(mop) => {
|
|
panic!(
|
|
"mock_unit can't execute LoadStore MOp, that needs mock_load_store_unit: {mop:#?}"
|
|
);
|
|
}
|
|
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 MockL2RegFile {
|
|
type DebugState = MockL2RegFileDebugState;
|
|
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
|
|
Some(self)
|
|
}
|
|
fn debug_state_ty() -> Self::DebugState {
|
|
MockL2RegFileDebugState
|
|
}
|
|
fn zeroed_debug_state() -> SimValue<Self::DebugState> {
|
|
zeroed(Self::debug_state_ty())
|
|
}
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<Self::DebugState> {
|
|
let Self {} = self;
|
|
#[hdl(sim)]
|
|
MockL2RegFileDebugState {}
|
|
}
|
|
}
|
|
|
|
impl MockExecutionStateTrait for () {
|
|
type DebugState = ();
|
|
fn try_get_l2_reg_file(&mut self) -> Option<&mut MockL2RegFile> {
|
|
None
|
|
}
|
|
fn debug_state_ty() -> Self::DebugState {
|
|
()
|
|
}
|
|
fn zeroed_debug_state() -> SimValue<Self::DebugState> {
|
|
().into_sim_value()
|
|
}
|
|
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 }>,
|
|
sent_cant_cause_cancel: Bool,
|
|
output_ready: HdlOption<UnitOutputReady<C>>,
|
|
caused_cancel: HdlOption<UnitCausedCancel<C>>,
|
|
config: C,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct MockUnitOp<C: PhantomConstCpuConfig> {
|
|
mop: SimValue<MOpInstance<RenamedMOp<C>>>,
|
|
src_values: [SimValue<PRegValue>; COMMON_MOP_SRC_LEN],
|
|
sent_cant_cause_cancel: bool,
|
|
output_ready: Option<SimValue<UnitOutputReady<C>>>,
|
|
caused_cancel: Option<SimValue<UnitCausedCancel<C>>>,
|
|
config: C,
|
|
}
|
|
|
|
impl<C: PhantomConstCpuConfig> MockUnitOp<C> {
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<MockUnitOpDebugState<C>> {
|
|
let Self {
|
|
mop,
|
|
src_values,
|
|
sent_cant_cause_cancel,
|
|
output_ready,
|
|
caused_cancel,
|
|
config,
|
|
} = self;
|
|
let output_ready_ty = HdlOption[UnitOutputReady[*config]];
|
|
let caused_cancel_ty = HdlOption[UnitCausedCancel[*config]];
|
|
#[hdl(sim)]
|
|
MockUnitOpDebugState::<_> {
|
|
mop,
|
|
src_values,
|
|
sent_cant_cause_cancel,
|
|
output_ready: output_ready.into_sim_value_with_type(output_ready_ty),
|
|
caused_cancel: caused_cancel.into_sim_value_with_type(caused_cancel_ty),
|
|
config,
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn try_run<E: MockExecutionStateTrait>(&mut self, 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);
|
|
assert!(output.is_some() || caused_cancel.is_some());
|
|
println!(
|
|
"try_run: {:#x}: {}",
|
|
self.mop.pc.as_int(),
|
|
fmt::from_fn(|f| MOpDebug::mop_debug(&self.mop.mop, f)),
|
|
);
|
|
println!(
|
|
"<- {:?}",
|
|
self.src_values
|
|
.each_ref()
|
|
.map(PRegValue::debug_fmt::<PRegFlagsPowerISA>),
|
|
);
|
|
self.output_ready = output.map(|(dest_value, predictor_op)| {
|
|
println!(
|
|
"-> {:?}",
|
|
PRegValue::debug_fmt::<PRegFlagsPowerISA>(&dest_value),
|
|
);
|
|
#[hdl(sim)]
|
|
let NextPcPredictorOp::<_> {
|
|
call_stack_op,
|
|
cond_br_taken,
|
|
config: _,
|
|
} = &predictor_op;
|
|
#[hdl(sim)]
|
|
match call_stack_op {
|
|
CallStackOp::None => {}
|
|
CallStackOp::Push(ret_pc) => println!("-> call returning to {ret_pc:?}"),
|
|
CallStackOp::Pop => println!("-> return"),
|
|
CallStackOp::Unknown => unreachable!(),
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(cond_br_taken) = cond_br_taken {
|
|
if **cond_br_taken {
|
|
println!("-> cond br taken");
|
|
} else {
|
|
println!("-> cond br not taken");
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
UnitOutputReady::<_> {
|
|
id: &self.mop.id,
|
|
dest_value,
|
|
predictor_op,
|
|
}
|
|
});
|
|
if let Some(caused_cancel) = &caused_cancel {
|
|
println!("-> cancel; start at {:?}", caused_cancel.start_at_pc);
|
|
}
|
|
self.caused_cancel = caused_cancel;
|
|
}
|
|
}
|
|
|
|
#[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>>> {
|
|
let ret_ty = HdlOption[UnitMOpCantCauseCancel[self.config]];
|
|
for op in self.ops.values() {
|
|
if !op.sent_cant_cause_cancel && op.caused_cancel.is_none() && op.output_ready.is_some()
|
|
{
|
|
return #[hdl(sim)]
|
|
ret_ty.HdlSome(
|
|
#[hdl(sim)]
|
|
UnitMOpCantCauseCancel::<_> {
|
|
id: &op.mop.id,
|
|
config: self.config,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
ret_ty.HdlNone()
|
|
}
|
|
#[hdl]
|
|
fn handle_mop_cant_cause_cancel(
|
|
&mut self,
|
|
cant_cause_cancel: SimValue<UnitMOpCantCauseCancel<C>>,
|
|
) {
|
|
#[hdl(sim)]
|
|
let UnitMOpCantCauseCancel::<_> { id, config: _ } = cant_cause_cancel;
|
|
let Some(op) = self.ops.get_mut(&id) else {
|
|
return;
|
|
};
|
|
op.sent_cant_cause_cancel = true;
|
|
}
|
|
#[hdl]
|
|
fn peek_output_ready_and_finish_cause_cancel(
|
|
&self,
|
|
) -> (
|
|
SimValue<HdlOption<UnitOutputReady<C>>>,
|
|
SimValue<HdlOption<UnitFinishCauseCancel<C>>>,
|
|
) {
|
|
let output_ready_ty = HdlOption[UnitOutputReady[self.config]];
|
|
let finish_cause_cancel_ty = HdlOption[UnitFinishCauseCancel[self.config]];
|
|
let caused_cancel_ty = finish_cause_cancel_ty.HdlSome.caused_cancel;
|
|
for op in self.ops.values() {
|
|
if op.output_ready.is_none() && op.caused_cancel.is_none() {
|
|
continue;
|
|
}
|
|
let output_ready = op.output_ready.to_sim_value_with_type(output_ready_ty);
|
|
let caused_cancel = op.caused_cancel.to_sim_value_with_type(caused_cancel_ty);
|
|
// TODO: add delay
|
|
return (
|
|
output_ready,
|
|
#[hdl(sim)]
|
|
finish_cause_cancel_ty.HdlSome(
|
|
#[hdl(sim)]
|
|
UnitFinishCauseCancel::<_> {
|
|
id: &op.mop.id,
|
|
caused_cancel,
|
|
config: self.config,
|
|
},
|
|
),
|
|
);
|
|
}
|
|
(
|
|
#[hdl(sim)]
|
|
output_ready_ty.HdlNone(),
|
|
#[hdl(sim)]
|
|
finish_cause_cancel_ty.HdlNone(),
|
|
)
|
|
}
|
|
#[hdl]
|
|
fn handle_output_ready_and_finish_cause_cancel(
|
|
&mut self,
|
|
_output_ready: SimValue<HdlOption<UnitOutputReady<C>>>,
|
|
finish_cause_cancel: SimValue<UnitFinishCauseCancel<C>>,
|
|
) {
|
|
self.ops.remove(&finish_cause_cancel.id);
|
|
}
|
|
#[hdl]
|
|
fn handle_inputs_ready(&mut self, inputs_ready: SimValue<UnitInputsReady<C>>) {
|
|
#[hdl(sim)]
|
|
let UnitInputsReady::<_> {
|
|
mop,
|
|
src_values,
|
|
config: _,
|
|
} = inputs_ready;
|
|
assert_eq!(
|
|
UnitNum::index_sim(&MOpTrait::dest_reg_sim_ref(&mop.mop).unit_num),
|
|
Some(self.unit_index),
|
|
);
|
|
let mut op = MockUnitOp {
|
|
mop,
|
|
src_values: SimValue::into_value(src_values),
|
|
sent_cant_cause_cancel: false,
|
|
output_ready: None,
|
|
caused_cancel: None,
|
|
config: self.config,
|
|
};
|
|
op.try_run(&mut self.execution_state);
|
|
self.ops.insert(op.mop.id.clone(), op);
|
|
}
|
|
#[hdl]
|
|
fn handle_mop_is_no_longer_speculative(
|
|
&mut self,
|
|
_is_no_longer_speculative: SimValue<UnitMOpIsNoLongerSpeculative<C>>,
|
|
) {
|
|
}
|
|
fn cancel_all(&mut self) {
|
|
let Self {
|
|
ops,
|
|
execution_state: _,
|
|
config: _,
|
|
unit_index: _,
|
|
} = self;
|
|
ops.clear();
|
|
}
|
|
}
|
|
|
|
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>,
|
|
unit_index: usize,
|
|
) {
|
|
#[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][Bundle::from_canonical(E::debug_state_ty().canonical())],
|
|
);
|
|
m.register_clock_for_past(cd.clk);
|
|
m.extern_module_simulation_fn(
|
|
(cd, from_execute, debug_state, config, unit_index),
|
|
async |args, mut sim| {
|
|
let (cd, from_execute, debug_state, config, unit_index) = args;
|
|
sim.resettable(
|
|
cd,
|
|
async |mut sim| {
|
|
#[hdl]
|
|
let ExecuteToUnitInterface::<_> {
|
|
enqueue,
|
|
inputs_ready: _,
|
|
is_no_longer_speculative: _,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready: _,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
sim.write(enqueue.ready, false).await;
|
|
sim.write(cancel_all.ready, false).await;
|
|
sim.write(
|
|
cant_cause_cancel,
|
|
#[hdl(sim)]
|
|
(cant_cause_cancel.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
sim.write(
|
|
output_ready,
|
|
#[hdl(sim)]
|
|
(output_ready.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
sim.write(
|
|
finish_cause_cancel,
|
|
#[hdl(sim)]
|
|
(finish_cause_cancel.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
sim.write(
|
|
debug_state,
|
|
#[hdl(sim)]
|
|
MockUnitDebugState::<_, _> {
|
|
ops: zeroed(debug_state.ty().ops),
|
|
execution_state: SimValue::into_bundle(E::zeroed_debug_state()),
|
|
config,
|
|
},
|
|
)
|
|
.await;
|
|
},
|
|
|sim, ()| {
|
|
run_fn(
|
|
cd,
|
|
from_execute,
|
|
Expr::from_bundle(Expr::as_bundle(debug_state)),
|
|
E::default(),
|
|
config,
|
|
unit_index,
|
|
sim,
|
|
)
|
|
},
|
|
)
|
|
.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>>,
|
|
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::<_> {
|
|
enqueue,
|
|
inputs_ready: _,
|
|
is_no_longer_speculative: _,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready: _,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
sim.write(enqueue.ready, true).await;
|
|
sim.write(cancel_all.ready, true).await;
|
|
sim.write(cant_cause_cancel, state.peek_cant_cause_cancel())
|
|
.await;
|
|
let (peek_output_ready, peek_finish_cause_cancel) =
|
|
state.peek_output_ready_and_finish_cause_cancel();
|
|
sim.write(output_ready, peek_output_ready).await;
|
|
sim.write(finish_cause_cancel, peek_finish_cause_cancel)
|
|
.await;
|
|
}
|
|
sim.write(debug_state, state.debug_state()).await;
|
|
sim.wait_for_clock_edge(cd.clk).await;
|
|
{
|
|
#[hdl]
|
|
let ExecuteToUnitInterface::<_> {
|
|
enqueue: _, // we ignore enqueues since we don't need to track order for these instructions
|
|
inputs_ready,
|
|
is_no_longer_speculative,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
#[hdl(sim)]
|
|
if let HdlSome(inputs_ready) = sim.read_past(inputs_ready, cd.clk).await {
|
|
state.handle_inputs_ready(inputs_ready);
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(is_no_longer_speculative) =
|
|
sim.read_past(is_no_longer_speculative, cd.clk).await
|
|
{
|
|
state.handle_mop_is_no_longer_speculative(is_no_longer_speculative);
|
|
}
|
|
if sim.read_past_bool(unit_outputs_ready, cd.clk).await {
|
|
#[hdl(sim)]
|
|
if let HdlSome(cant_cause_cancel) =
|
|
sim.read_past(cant_cause_cancel, cd.clk).await
|
|
{
|
|
state.handle_mop_cant_cause_cancel(cant_cause_cancel);
|
|
}
|
|
let output_ready = sim.read_past(output_ready, cd.clk).await;
|
|
#[hdl(sim)]
|
|
if let HdlSome(finish_cause_cancel) =
|
|
sim.read_past(finish_cause_cancel, cd.clk).await
|
|
{
|
|
state.handle_output_ready_and_finish_cause_cancel(
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
);
|
|
} else if let HdlSome(output_ready) = output_ready {
|
|
unreachable!(
|
|
"output_ready should not be HdlSome while finish_cause_cancel is HdlNone: {output_ready:#?}"
|
|
);
|
|
}
|
|
}
|
|
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(no_static)]
|
|
struct MockLoadStoreOpDebugState<C: PhantomConstGet<CpuConfig>> {
|
|
mop: MOpInstance<LoadStoreMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>>>,
|
|
is_speculative: Bool,
|
|
src_values: HdlOption<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
|
dest_value: HdlOption<PRegValue>,
|
|
ran_nonspeculatively: Bool,
|
|
sent_cant_cause_cancel: Bool,
|
|
sent_output_ready: Bool,
|
|
config: C,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct MockLoadStoreOp<C: PhantomConstCpuConfig> {
|
|
mop: SimValue<MOpInstance<LoadStoreMOp<PRegNum<C>, CpuConfigPRegNumWidth<C>>>>,
|
|
is_speculative: bool,
|
|
src_values: Option<[SimValue<PRegValue>; COMMON_MOP_SRC_LEN]>,
|
|
dest_value: Option<SimValue<PRegValue>>,
|
|
ran_nonspeculatively: bool,
|
|
sent_cant_cause_cancel: bool,
|
|
sent_output_ready: bool,
|
|
config: C,
|
|
}
|
|
|
|
impl<C: PhantomConstCpuConfig> MockLoadStoreOp<C> {
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<MockLoadStoreOpDebugState<C>> {
|
|
let Self {
|
|
mop,
|
|
is_speculative,
|
|
src_values,
|
|
dest_value,
|
|
ran_nonspeculatively,
|
|
sent_cant_cause_cancel,
|
|
sent_output_ready,
|
|
config,
|
|
} = self;
|
|
#[hdl(sim)]
|
|
MockLoadStoreOpDebugState::<_> {
|
|
mop,
|
|
is_speculative,
|
|
src_values,
|
|
dest_value,
|
|
ran_nonspeculatively,
|
|
sent_cant_cause_cancel,
|
|
sent_output_ready,
|
|
config,
|
|
}
|
|
}
|
|
fn id(&self) -> &SimValue<MOpId> {
|
|
&self.mop.id
|
|
}
|
|
}
|
|
|
|
#[hdl(no_static)]
|
|
struct MockLoadStoreUnitDebugState<C: PhantomConstGet<CpuConfig>> {
|
|
ops: ArrayVec<MockLoadStoreOpDebugState<C>, CpuConfigMaxUnitMaxInFlight<C>>,
|
|
memory: MockMemoryDebugState,
|
|
speculative_memory: MockMemoryDebugState,
|
|
config: C,
|
|
}
|
|
|
|
struct MockLoadStoreUnitState<C: PhantomConstCpuConfig> {
|
|
ops: VecDeque<MockLoadStoreOp<C>>,
|
|
memory: MockMemory,
|
|
speculative_memory: MockMemory,
|
|
config: C,
|
|
unit_index: usize,
|
|
}
|
|
|
|
impl<C: PhantomConstCpuConfig> MockLoadStoreUnitState<C> {
|
|
fn new(config: C, unit_index: usize, memory: MockMemory) -> Self {
|
|
Self {
|
|
ops: VecDeque::new(),
|
|
memory: memory.clone(),
|
|
speculative_memory: memory,
|
|
config,
|
|
unit_index,
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn debug_state(&self) -> SimValue<MockLoadStoreUnitDebugState<C>> {
|
|
let Self {
|
|
ops,
|
|
memory,
|
|
speculative_memory,
|
|
config,
|
|
unit_index: _,
|
|
} = self;
|
|
let ret_ty = MockLoadStoreUnitDebugState[*config];
|
|
#[hdl(sim)]
|
|
MockLoadStoreUnitDebugState::<_> {
|
|
ops: ret_ty
|
|
.ops
|
|
.from_iter_sim(
|
|
zeroed(ret_ty.ops.element()),
|
|
ops.iter().map(MockLoadStoreOp::debug_state),
|
|
)
|
|
.ok()
|
|
.expect("known to fit"),
|
|
memory: memory.debug_state(),
|
|
speculative_memory: speculative_memory.debug_state(),
|
|
config,
|
|
}
|
|
}
|
|
fn try_op_by_id(&self, id: &SimValue<MOpId>) -> Option<&MockLoadStoreOp<C>> {
|
|
self.ops.iter().find(|op| op.id() == id)
|
|
}
|
|
fn try_op_by_id_mut(&mut self, id: &SimValue<MOpId>) -> Option<&mut MockLoadStoreOp<C>> {
|
|
self.ops.iter_mut().find(|op| op.id() == id)
|
|
}
|
|
#[track_caller]
|
|
fn op_by_id(&self, id: &SimValue<MOpId>) -> &MockLoadStoreOp<C> {
|
|
match self.try_op_by_id(id) {
|
|
Some(retval) => retval,
|
|
None => panic!("can't find MockLoadStoreOp with id: {id:?}"),
|
|
}
|
|
}
|
|
#[track_caller]
|
|
fn op_by_id_mut(&mut self, id: &SimValue<MOpId>) -> &mut MockLoadStoreOp<C> {
|
|
match self.try_op_by_id_mut(id) {
|
|
Some(retval) => retval,
|
|
None => panic!("can't find MockLoadStoreOp with id: {id:?}"),
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn step(&mut self) {
|
|
for op in &mut self.ops {
|
|
let MockLoadStoreOp {
|
|
mop,
|
|
is_speculative,
|
|
src_values,
|
|
dest_value,
|
|
ran_nonspeculatively: _,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
} = op;
|
|
if let Some(src_values) = src_values
|
|
&& dest_value.is_none()
|
|
{
|
|
match self
|
|
.speculative_memory
|
|
.run_mop(mop, src_values, *is_speculative)
|
|
{
|
|
Ok(v) => *dest_value = Some(v),
|
|
Err(AddressCantBeSpeculativelyAccessed {}) => {}
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
match &mop.mop {
|
|
LoadStoreMOp::<_, _>::Load(load_mop) => {
|
|
#[hdl(sim)]
|
|
let LoadMOp::<_, _> { load_store_common } = load_mop;
|
|
// match fields so we remember to update our checks if/when
|
|
// we add new fields to LoadMOp or LoadStoreCommonMOp.
|
|
#[hdl(sim)]
|
|
let LoadStoreCommonMOp::<_, _, _> {
|
|
common: _,
|
|
width: _,
|
|
conversion: _,
|
|
} = load_store_common;
|
|
}
|
|
LoadStoreMOp::<_, _>::Store(store_mop) => {
|
|
#[hdl(sim)]
|
|
let StoreMOp::<_, _> { load_store_common } = store_mop;
|
|
// match fields so we remember to update our checks if/when
|
|
// we add new fields to LoadMOp or LoadStoreCommonMOp.
|
|
#[hdl(sim)]
|
|
let LoadStoreCommonMOp::<_, _, _> {
|
|
common: _,
|
|
width: _,
|
|
conversion: _,
|
|
} = load_store_common;
|
|
if dest_value.is_none() {
|
|
// we can't run following loads yet.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for op in &mut self.ops {
|
|
let MockLoadStoreOp {
|
|
mop,
|
|
is_speculative,
|
|
src_values,
|
|
dest_value,
|
|
ran_nonspeculatively,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
} = op;
|
|
if *is_speculative {
|
|
break;
|
|
}
|
|
if !*ran_nonspeculatively {
|
|
match self
|
|
.memory
|
|
.run_mop(mop, src_values.as_ref().expect("known to be set"), false)
|
|
{
|
|
Ok(expected_dest_value) => {
|
|
assert_eq!(*dest_value, Some(expected_dest_value));
|
|
*ran_nonspeculatively = true;
|
|
}
|
|
Err(AddressCantBeSpeculativelyAccessed {}) => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#[hdl]
|
|
fn handle_enqueue(&mut self, enqueue: SimValue<UnitEnqueue<C>>) {
|
|
#[hdl(sim)]
|
|
let UnitEnqueue::<_> { mop, config: _ } = enqueue;
|
|
#[hdl(sim)]
|
|
let MOpInstance::<_> {
|
|
fetch_block_id,
|
|
id,
|
|
pc,
|
|
predicted_next_pc,
|
|
size_in_bytes,
|
|
is_first_mop_in_insn,
|
|
mop,
|
|
} = mop;
|
|
let mop = #[hdl(sim)]
|
|
match &mop {
|
|
RenamedMOp::<_>::LoadStore(mop) => mop,
|
|
_ => panic!("MockLoadStoreUnitState can only handle LoadStore MOps, got: {mop:#?}"),
|
|
};
|
|
let mop = #[hdl(sim)]
|
|
MOpInstance::<_> {
|
|
fetch_block_id,
|
|
id,
|
|
pc,
|
|
predicted_next_pc,
|
|
size_in_bytes,
|
|
is_first_mop_in_insn,
|
|
mop,
|
|
};
|
|
self.ops.push_back(MockLoadStoreOp {
|
|
mop,
|
|
is_speculative: true,
|
|
src_values: None,
|
|
dest_value: None,
|
|
ran_nonspeculatively: false,
|
|
sent_cant_cause_cancel: false,
|
|
sent_output_ready: false,
|
|
config: self.config,
|
|
});
|
|
}
|
|
#[hdl]
|
|
fn handle_inputs_ready(&mut self, inputs_ready: SimValue<UnitInputsReady<C>>) {
|
|
#[hdl(sim)]
|
|
let UnitInputsReady::<_> {
|
|
mop,
|
|
config: _,
|
|
src_values,
|
|
} = inputs_ready;
|
|
let MockLoadStoreOp {
|
|
mop: _,
|
|
is_speculative: _,
|
|
src_values: op_src_values,
|
|
dest_value: _,
|
|
ran_nonspeculatively: _,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
} = self.op_by_id_mut(&mop.id);
|
|
assert!(op_src_values.is_none());
|
|
*op_src_values = Some(SimValue::into_value(src_values));
|
|
}
|
|
#[hdl]
|
|
fn handle_mop_is_no_longer_speculative(
|
|
&mut self,
|
|
is_no_longer_speculative: SimValue<UnitMOpIsNoLongerSpeculative<C>>,
|
|
) {
|
|
#[hdl(sim)]
|
|
let UnitMOpIsNoLongerSpeculative::<_> { id, config: _ } = is_no_longer_speculative;
|
|
let MockLoadStoreOp {
|
|
mop: _,
|
|
is_speculative,
|
|
src_values: _,
|
|
dest_value: _,
|
|
ran_nonspeculatively: _,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
} = self.op_by_id_mut(&id);
|
|
assert!(*is_speculative);
|
|
*is_speculative = false;
|
|
}
|
|
#[hdl]
|
|
fn peek_mop_cant_cause_cancel(&self) -> SimValue<HdlOption<UnitMOpCantCauseCancel<C>>> {
|
|
let ret_ty = HdlOption[UnitMOpCantCauseCancel[self.config]];
|
|
for MockLoadStoreOp {
|
|
mop,
|
|
is_speculative: _,
|
|
src_values: _,
|
|
dest_value,
|
|
ran_nonspeculatively: _,
|
|
sent_cant_cause_cancel,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
} in &self.ops
|
|
{
|
|
if *sent_cant_cause_cancel {
|
|
continue;
|
|
}
|
|
if dest_value.is_some() {
|
|
return #[hdl(sim)]
|
|
ret_ty.HdlSome(
|
|
#[hdl(sim)]
|
|
UnitMOpCantCauseCancel::<_> {
|
|
id: &mop.id,
|
|
config: self.config,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
ret_ty.HdlNone()
|
|
}
|
|
#[hdl]
|
|
fn handle_mop_cant_cause_cancel(
|
|
&mut self,
|
|
cant_cause_cancel: SimValue<UnitMOpCantCauseCancel<C>>,
|
|
) {
|
|
#[hdl(sim)]
|
|
let UnitMOpCantCauseCancel::<_> { id, config: _ } = cant_cause_cancel;
|
|
let op = self.op_by_id_mut(&id);
|
|
assert!(!op.sent_cant_cause_cancel);
|
|
op.sent_cant_cause_cancel = true;
|
|
}
|
|
#[hdl]
|
|
fn peek_output_ready(&self) -> SimValue<HdlOption<UnitOutputReady<C>>> {
|
|
let ret_ty = HdlOption[UnitOutputReady[self.config]];
|
|
for MockLoadStoreOp {
|
|
mop,
|
|
is_speculative: _,
|
|
src_values: _,
|
|
dest_value,
|
|
ran_nonspeculatively: _,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready,
|
|
config: _,
|
|
} in &self.ops
|
|
{
|
|
if *sent_output_ready {
|
|
continue;
|
|
}
|
|
if let Some(dest_value) = dest_value {
|
|
return #[hdl(sim)]
|
|
ret_ty.HdlSome(
|
|
#[hdl(sim)]
|
|
UnitOutputReady::<_> {
|
|
id: &mop.id,
|
|
dest_value,
|
|
predictor_op: #[hdl(sim)]
|
|
NextPcPredictorOp::<_> {
|
|
call_stack_op: #[hdl(sim)]
|
|
CallStackOp.None(),
|
|
cond_br_taken: #[hdl(sim)]
|
|
HdlNone(),
|
|
config: self.config,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
ret_ty.HdlNone()
|
|
}
|
|
#[hdl]
|
|
fn handle_output_ready(&mut self, output_ready: SimValue<UnitOutputReady<C>>) {
|
|
#[hdl(sim)]
|
|
let UnitOutputReady::<_> {
|
|
id,
|
|
dest_value: _,
|
|
predictor_op: _,
|
|
} = output_ready;
|
|
let op = self.op_by_id_mut(&id);
|
|
assert!(!op.sent_output_ready);
|
|
op.sent_output_ready = true;
|
|
}
|
|
#[hdl]
|
|
fn peek_finish_cause_cancel(&self) -> SimValue<HdlOption<UnitFinishCauseCancel<C>>> {
|
|
let ret_ty = HdlOption[UnitFinishCauseCancel[self.config]];
|
|
if let Some(MockLoadStoreOp {
|
|
mop,
|
|
is_speculative: _,
|
|
src_values: _,
|
|
dest_value: _,
|
|
ran_nonspeculatively,
|
|
sent_cant_cause_cancel: _,
|
|
sent_output_ready: _,
|
|
config: _,
|
|
}) = self.ops.front()
|
|
{
|
|
if *ran_nonspeculatively {
|
|
return #[hdl(sim)]
|
|
ret_ty.HdlSome(
|
|
#[hdl(sim)]
|
|
UnitFinishCauseCancel::<_> {
|
|
id: &mop.id,
|
|
caused_cancel: #[hdl(sim)]
|
|
(ret_ty.HdlSome.caused_cancel).HdlNone(),
|
|
config: self.config,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
ret_ty.HdlNone()
|
|
}
|
|
#[hdl]
|
|
fn handle_finish_cause_cancel(
|
|
&mut self,
|
|
finish_cause_cancel: SimValue<UnitFinishCauseCancel<C>>,
|
|
) {
|
|
#[hdl(sim)]
|
|
let UnitFinishCauseCancel::<_> {
|
|
id,
|
|
caused_cancel: _,
|
|
config: _,
|
|
} = finish_cause_cancel;
|
|
let Some(op) = self.ops.pop_front() else {
|
|
unreachable!();
|
|
};
|
|
assert_eq!(*op.id(), id);
|
|
}
|
|
fn cancel_all(&mut self) {
|
|
let Self {
|
|
ops,
|
|
memory,
|
|
speculative_memory,
|
|
config: _,
|
|
unit_index: _,
|
|
} = self;
|
|
for op in ops.iter() {
|
|
assert!(
|
|
op.is_speculative,
|
|
"can't cancel non-speculative op: {op:#?}"
|
|
);
|
|
}
|
|
ops.clear();
|
|
speculative_memory.clone_from(memory);
|
|
}
|
|
}
|
|
|
|
#[hdl_module(extern)]
|
|
fn mock_load_store_unit<#[hdl(skip)] MI: MakeInsns>(
|
|
config: PhantomConst<CpuConfig>,
|
|
unit_index: usize,
|
|
) {
|
|
assert!(
|
|
config
|
|
.get()
|
|
.units
|
|
.iter()
|
|
.enumerate()
|
|
.all(|(i, unit)| (unit_index == i) == (unit.kind == UnitKind::LoadStore)),
|
|
"unit index {unit_index} must be the load/store unit: {config:#?}",
|
|
);
|
|
#[hdl]
|
|
let cd: ClockDomain = m.input();
|
|
#[hdl]
|
|
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
|
|
m.input(ExecuteToUnitInterface[config]);
|
|
#[hdl]
|
|
let debug_state: MockLoadStoreUnitDebugState<PhantomConst<CpuConfig>> =
|
|
m.output(MockLoadStoreUnitDebugState[config]);
|
|
#[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::<_> {
|
|
enqueue,
|
|
inputs_ready: _,
|
|
is_no_longer_speculative: _,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready: _,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
sim.write(enqueue.ready, false).await;
|
|
sim.write(cancel_all.ready, false).await;
|
|
sim.write(
|
|
cant_cause_cancel,
|
|
#[hdl(sim)]
|
|
(cant_cause_cancel.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
sim.write(
|
|
output_ready,
|
|
#[hdl(sim)]
|
|
(output_ready.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
sim.write(
|
|
finish_cause_cancel,
|
|
#[hdl(sim)]
|
|
(finish_cause_cancel.ty()).HdlNone(),
|
|
)
|
|
.await;
|
|
let state = MI::make_load_store_execution_state();
|
|
sim.write(
|
|
debug_state,
|
|
#[hdl(sim)]
|
|
MockLoadStoreUnitDebugState::<_> {
|
|
ops: zeroed(debug_state.ty().ops),
|
|
memory: state.debug_state(),
|
|
speculative_memory: state.debug_state(),
|
|
config,
|
|
},
|
|
)
|
|
.await;
|
|
sim.write(all_outputs_written, false).await;
|
|
state
|
|
},
|
|
|sim, state| {
|
|
run_fn(
|
|
cd,
|
|
from_execute,
|
|
debug_state,
|
|
all_outputs_written,
|
|
state,
|
|
config,
|
|
unit_index,
|
|
sim,
|
|
)
|
|
},
|
|
)
|
|
.await;
|
|
},
|
|
);
|
|
#[hdl]
|
|
async fn run_fn(
|
|
cd: Expr<ClockDomain>,
|
|
from_execute: Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>,
|
|
debug_state: Expr<MockLoadStoreUnitDebugState<PhantomConst<CpuConfig>>>,
|
|
all_outputs_written: Expr<Bool>,
|
|
memory: MockMemory,
|
|
config: PhantomConst<CpuConfig>,
|
|
unit_index: usize,
|
|
mut sim: ExternModuleSimulationState,
|
|
) {
|
|
let mut state = MockLoadStoreUnitState::new(config, unit_index, memory);
|
|
loop {
|
|
{
|
|
#[hdl]
|
|
let ExecuteToUnitInterface::<_> {
|
|
enqueue,
|
|
inputs_ready: _,
|
|
is_no_longer_speculative: _,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready: _,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
sim.write(enqueue.ready, true).await;
|
|
sim.write(cancel_all.ready, true).await;
|
|
sim.write(cant_cause_cancel, state.peek_mop_cant_cause_cancel())
|
|
.await;
|
|
sim.write(output_ready, state.peek_output_ready()).await;
|
|
sim.write(finish_cause_cancel, state.peek_finish_cause_cancel())
|
|
.await;
|
|
}
|
|
sim.write(debug_state, state.debug_state()).await;
|
|
sim.write(all_outputs_written, state.memory.wrote_output)
|
|
.await;
|
|
sim.wait_for_clock_edge(cd.clk).await;
|
|
{
|
|
#[hdl]
|
|
let ExecuteToUnitInterface::<_> {
|
|
enqueue,
|
|
inputs_ready,
|
|
is_no_longer_speculative,
|
|
cant_cause_cancel,
|
|
output_ready,
|
|
finish_cause_cancel,
|
|
unit_outputs_ready,
|
|
cancel_all,
|
|
config: _,
|
|
} = from_execute;
|
|
if sim.read_past_bool(enqueue.ready, cd.clk).await {
|
|
#[hdl(sim)]
|
|
if let HdlSome(enqueue) = sim.read_past(enqueue.data, cd.clk).await {
|
|
state.handle_enqueue(enqueue);
|
|
}
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(inputs_ready) = sim.read_past(inputs_ready, cd.clk).await {
|
|
state.handle_inputs_ready(inputs_ready);
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(is_no_longer_speculative) =
|
|
sim.read_past(is_no_longer_speculative, cd.clk).await
|
|
{
|
|
state.handle_mop_is_no_longer_speculative(is_no_longer_speculative);
|
|
}
|
|
if sim.read_past_bool(unit_outputs_ready, cd.clk).await {
|
|
#[hdl(sim)]
|
|
if let HdlSome(cant_cause_cancel) =
|
|
sim.read_past(cant_cause_cancel, cd.clk).await
|
|
{
|
|
state.handle_mop_cant_cause_cancel(cant_cause_cancel);
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(output_ready) = sim.read_past(output_ready, cd.clk).await {
|
|
state.handle_output_ready(output_ready);
|
|
}
|
|
#[hdl(sim)]
|
|
if let HdlSome(finish_cause_cancel) =
|
|
sim.read_past(finish_cause_cancel, cd.clk).await
|
|
{
|
|
state.handle_finish_cause_cancel(finish_cause_cancel);
|
|
}
|
|
}
|
|
if sim.read_past_bool(cancel_all.ready, cd.clk).await {
|
|
#[hdl(sim)]
|
|
if let HdlSome(cancel_all) = sim.read_past(cancel_all.data, cd.clk).await {
|
|
let () = *cancel_all;
|
|
state.cancel_all();
|
|
}
|
|
}
|
|
}
|
|
state.step();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[hdl_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()
|
|
{
|
|
if config.get().units[unit_index].kind == UnitKind::LoadStore {
|
|
let mock_unit = instance_with_loc(
|
|
&dut.ty().to_units.unit_field_name(unit_index),
|
|
mock_load_store_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);
|
|
}
|
|
} else {
|
|
let mock_unit_module = if is_the_l2_reg_file_unit(config, unit_index) {
|
|
mock_unit::<MockL2RegFile>(config, unit_index)
|
|
} else {
|
|
mock_unit::<()>(config, unit_index)
|
|
};
|
|
let mock_unit = instance_with_loc(
|
|
&dut.ty().to_units.unit_field_name(unit_index),
|
|
mock_unit_module,
|
|
SourceLocation::caller(),
|
|
);
|
|
connect(mock_unit.cd, cd);
|
|
connect(mock_unit.from_execute, to_unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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..200 {
|
|
sim.advance_time(SimDuration::from_nanos(500));
|
|
println!("clock tick: {cycle}");
|
|
sim.write_clock(sim.io().cd.clk, true);
|
|
sim.advance_time(SimDuration::from_nanos(500));
|
|
sim.write_clock(sim.io().cd.clk, false);
|
|
sim.write_reset(sim.io().cd.rst, false);
|
|
}
|
|
assert!(sim.read_bool(sim.io().all_outputs_written));
|
|
// FIXME: vcd is just whatever rename_execute_retire does now, which isn't known to be correct
|
|
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
|
println!("####### VCD:\n{vcd}\n#######");
|
|
if vcd != include_str!("expected/rename_execute_retire_fibonacci.vcd") {
|
|
panic!();
|
|
}
|
|
}
|