WIP: adding test::decode_and_run_single_insn

This commit is contained in:
Jacob Lifshay 2026-05-29 01:32:18 -07:00
parent 7fc205e583
commit 93598dc6d1
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
3 changed files with 482 additions and 0 deletions

View file

@ -11,5 +11,6 @@ pub mod powerisa_instructions_xml;
pub mod reg_alloc;
pub mod register;
pub mod rename_execute_retire;
pub mod test;
pub mod unit;
pub mod util;

4
crates/cpu/src/test.rs Normal file
View file

@ -0,0 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod decode_and_run_single_insn;

View file

@ -0,0 +1,477 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{CpuConfig, CpuConfigUnitCount, PhantomConstCpuConfig},
instruction::{MOp, MOpRegNum, MOpTrait, MoveRegMOp, PRegNum, UnitNum, UnitOutRegNum},
next_pc::FETCH_BLOCK_ID_WIDTH,
register::PRegValue,
rename_execute_retire::{
ExecuteToUnitInterface, GlobalState, MOpId, MOpInstance, NextPcPredictorOp, RenamedMOp,
to_unit_interfaces::ExecuteToUnitInterfaces,
},
unit::{HdlUnitKind, UnitMOp},
util::array_vec::ArrayVec,
};
use fayalite::{
bundle::BundleType,
int::UIntInRangeType,
intern::{Intern, Interned},
module::wire_with_loc,
prelude::*,
util::{prefix_sum::reduce, ready_valid::ReadyValid},
};
/// make arrays dynamically-sized to avoid putting large types on the stack
#[hdl(get(|c| 1 << MOpRegNum::WIDTH))]
type MOpRegCount<C: PhantomConstGet<CpuConfig>> = DynSize;
#[hdl(cmp_eq, no_static)]
pub struct Registers<C: PhantomConstGet<CpuConfig>> {
pub regs: ArrayType<TraceAsString<PRegValue>, MOpRegCount<C>>,
pub config: C,
}
impl<C: PhantomConstCpuConfig> Registers<C> {
#[hdl]
pub fn zeroed(self) -> Expr<Self> {
#[hdl]
Self {
regs: repeat(PRegValue::zeroed().into_trace_as_string(), self.regs.len()),
config: self.config,
}
}
#[hdl]
pub fn zeroed_sim(self) -> SimValue<Self> {
#[hdl(sim)]
Self {
regs: vec![PRegValue::zeroed_sim().into_trace_as_string(); self.regs.len()],
config: self.config,
}
}
}
#[hdl(no_static)]
pub struct DecodeOneInsnOutput<MaxMOpCount: Size> {
pub mops: ArrayVec<TraceAsString<MOp>, MaxMOpCount>,
pub size_in_bytes: UInt<4>,
pub is_illegal: Bool,
}
pub trait DecodeOneInsn: BundleType {
type Input: Type;
fn input_ty(self) -> Self::Input;
type MaxMOpCount: Size;
fn max_mop_count(self) -> <Self::MaxMOpCount as Size>::SizeType;
fn connect_io(
this: Expr<Self>,
input: Expr<Self::Input>,
output: Expr<DecodeOneInsnOutput<Self::MaxMOpCount>>,
);
}
pub type DecodeOneInsnInput<T> = <T as DecodeOneInsn>::Input;
#[doc(hidden)]
#[expect(non_upper_case_globals)]
pub const DecodeOneInsnInput: DecodeOneInsnInputWithoutGenerics =
DecodeOneInsnInputWithoutGenerics {};
#[doc(hidden)]
pub struct DecodeOneInsnInputWithoutGenerics {}
impl<T: DecodeOneInsn> std::ops::Index<T> for DecodeOneInsnInputWithoutGenerics {
type Output = DecodeOneInsnInput<T>;
fn index(&self, index: T) -> &Self::Output {
Interned::into_inner(index.input_ty().intern_sized())
}
}
pub type DecodeOneInsnMaxMOpCount<T> = <T as DecodeOneInsn>::MaxMOpCount;
#[doc(hidden)]
#[expect(non_upper_case_globals)]
pub const DecodeOneInsnMaxMOpCount: DecodeOneInsnMaxMOpCountWithoutGenerics =
DecodeOneInsnMaxMOpCountWithoutGenerics {};
#[doc(hidden)]
pub struct DecodeOneInsnMaxMOpCountWithoutGenerics {}
impl<T: DecodeOneInsn> std::ops::Index<T> for DecodeOneInsnMaxMOpCountWithoutGenerics {
type Output = <T::MaxMOpCount as Size>::SizeType;
fn index(&self, index: T) -> &Self::Output {
Interned::into_inner(index.max_mop_count().intern_sized())
}
}
#[hdl(no_static)]
pub struct DecodeAndRunSingleInsnInput<C: PhantomConstGet<CpuConfig>, I> {
pub decoder_input: I,
pub fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
pub first_id: MOpId,
pub pc: UInt<64>,
pub predicted_next_pc: UInt<64>,
pub regs: Registers<C>,
pub config: C,
}
#[hdl(no_static)]
pub struct DecodeAndRunSingleInsnOutput<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
pub regs: Registers<C>,
pub cancel_and_start_at: HdlOption<UInt<64>>,
pub retired_insns: ArrayVec<NextPcPredictorOp<C>, MaxMOpCount>,
pub config: C,
}
#[hdl(no_static)]
struct UnrenamedMOpState<C: PhantomConstGet<CpuConfig>> {
unrenamed: MOpInstance<MOp>,
renamed: HdlOption<TraceAsString<RenamedMOp<C>>>,
}
#[hdl(no_static)]
enum Error {
NoUnitsOfKind(HdlUnitKind),
}
#[hdl(no_static)]
struct CancelState<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
cancel_index: UIntInRangeType<ConstUsize<0>, MaxMOpCount>,
canceling_units: ArrayType<Bool, CpuConfigUnitCount<C>>,
config: C,
}
#[hdl(no_static)]
enum RunState<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
RunningMOp(UIntInRangeType<ConstUsize<0>, MaxMOpCount>),
CancelingUnits(CancelState<C, MaxMOpCount>),
Finished,
Error(TraceAsString<Error>),
}
#[hdl(no_static)]
struct DecodeAndRunSingleInsnState<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
mops: ArrayVec<UnrenamedMOpState<C>, MaxMOpCount>,
regs: Registers<C>,
run_state: RunState<C, MaxMOpCount>,
config: C,
}
#[hdl(no_static)]
enum StepOutput<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
Finished(DecodeAndRunSingleInsnOutput<C, MaxMOpCount>),
Step(DecodeAndRunSingleInsnState<C, MaxMOpCount>),
Error(TraceAsString<Error>),
}
impl<C: PhantomConstCpuConfig, MaxMOpCount: Size> DecodeAndRunSingleInsnState<C, MaxMOpCount> {
#[hdl]
fn new(
input: Expr<DecodeAndRunSingleInsnInput<C, impl Type>>,
decoder_output: Expr<DecodeOneInsnOutput<MaxMOpCount>>,
config: C,
max_mop_count: MaxMOpCount::SizeType,
) -> Expr<Self> {
#[hdl]
let new_state = wire(DecodeAndRunSingleInsnState[config][max_mop_count]);
#[hdl]
let Self {
mops,
regs,
run_state,
config: _,
} = new_state;
connect(run_state, run_state.ty().Finished());
connect(
mops,
ArrayVec::map(
decoder_output.mops,
mops.ty().element(),
|index, decoded_mop| {
if index == 0 {
connect(
run_state,
#[hdl(sim)]
(run_state.ty()).RunningMOp(0usize),
);
}
let retval = wire_with_loc(
&format!("mops_{index}"),
SourceLocation::caller(),
UnrenamedMOpState[config],
);
#[hdl]
let UnrenamedMOpState::<_> { unrenamed, renamed } = retval;
#[hdl]
let MOpInstance::<_> {
fetch_block_id,
id,
pc,
predicted_next_pc,
size_in_bytes,
is_first_mop_in_insn,
is_last_mop_in_insn,
mop,
} = unrenamed;
connect(fetch_block_id, input.fetch_block_id);
connect(id, (input.first_id + index).cast_to_static::<MOpId>());
connect(pc, input.pc);
connect(predicted_next_pc, input.predicted_next_pc);
connect(size_in_bytes, decoder_output.size_in_bytes);
connect(is_first_mop_in_insn, index == 0);
connect(
is_last_mop_in_insn,
ArrayVec::len(decoder_output.mops).cmp_eq(index + 1),
);
connect(mop, decoded_mop);
#[hdl]
let renamed_untransformed_move = wire(
HdlOption[TraceAsString[UnitMOp[PRegNum[config]][PRegNum[config]]
[MoveRegMOp[PRegNum[config]][PRegNum[config]]]]],
);
#[hdl]
if let MOp::TransformedMove(_) = *mop {
connect(
renamed_untransformed_move,
renamed_untransformed_move.ty().HdlNone(),
);
} else {
let unit_kind = MOp::kind(*mop);
let available_units = config.get().available_units_for_kind(unit_kind);
let chosen_unit = reduce(
available_units.into_iter().enumerate().map(
|(unit_index, available_unit)| {
#[hdl]
let chosen_unit = wire(UnitNum[config]);
connect(chosen_unit, chosen_unit.ty().const_zero());
#[hdl]
if available_unit {
connect(
chosen_unit,
chosen_unit.ty().from_index(unit_index),
);
}
chosen_unit
},
),
|a, b| {
#[hdl]
let chosen_unit = wire(UnitNum[config]);
connect(chosen_unit, a);
#[hdl]
if a.cmp_eq(chosen_unit.ty().const_zero()) {
connect(chosen_unit, b);
}
chosen_unit
},
)
.unwrap_or(UnitNum[config].const_zero());
#[hdl]
if chosen_unit.cmp_eq(chosen_unit.ty().const_zero()) {
connect(
run_state,
run_state
.ty()
.Error(Error.NoUnitsOfKind(unit_kind).to_trace_as_string()),
);
}
let unit_out_reg_num_ty = UnitOutRegNum[config];
connect(
renamed_untransformed_move,
HdlSome(
MOpTrait::map_regs(
*mop,
#[hdl]
PRegNum::<_> {
unit_num: chosen_unit,
unit_out_reg: #[hdl]
UnitOutRegNum::<_> {
value: !unit_out_reg_num_ty.value.zero(),
config,
},
},
PRegNum[config],
&mut |src_reg, index| {
#[hdl]
let renamed_src_reg = wire(PRegNum[config]);
#[hdl]
if src_reg.cmp_eq(MOpRegNum::const_zero()) {
connect(
renamed_src_reg,
renamed_src_reg.ty().const_zero(),
);
} else {
connect(
renamed_src_reg,
#[hdl]
PRegNum::<_> {
unit_num: chosen_unit,
unit_out_reg: unit_out_reg_num_ty
.new_sim(index),
},
);
}
renamed_src_reg
},
)
.to_trace_as_string(),
),
);
}
connect(renamed, renamed.ty().HdlNone());
#[hdl]
if let HdlSome(renamed_untransformed_move) = renamed_untransformed_move {
let v = UnitMOp::try_with_transformed_move_op(
*renamed_untransformed_move,
renamed.ty().HdlSome.inner_ty().TransformedMove,
|dest, _src| {
connect(dest, dest.ty().HdlNone());
},
);
#[hdl]
if let HdlSome(v) = v {
connect(renamed, HdlSome(v.to_trace_as_string()));
}
}
retval
},
),
);
connect(regs, input.regs);
new_state
}
#[hdl]
fn step(
this: Expr<Self>,
to_units: Expr<ExecuteToUnitInterfaces<C>>,
) -> Expr<StepOutput<C, MaxMOpCount>> {
#[hdl]
let Self {
mops,
regs,
run_state,
config,
} = this;
let config = config.ty();
let max_mop_count = MaxMOpCount::from_usize(mops.ty().capacity());
#[hdl]
let stepped = wire(StepOutput[config][max_mop_count]);
#[hdl]
match run_state {
RunState::<_, _>::RunningMOp(cur_mop_index) => {
todo!();
}
RunState::<_, _>::CancelingUnits(cancel_state) => {
todo!();
}
RunState::<_, _>::Finished => {
todo!();
}
RunState::<_, _>::Error(error) => {
connect(stepped, stepped.ty().Error(error));
}
}
stepped
}
}
#[hdl_module]
pub fn decode_and_run_single_insn<C: Type + PhantomConstCpuConfig, D: Type + DecodeOneInsn>(
config: C,
decoder: Interned<Module<D>>,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input: ReadyValid<DecodeAndRunSingleInsnInput<C, DecodeOneInsnInput<D>>> = m.input(
ReadyValid[DecodeAndRunSingleInsnInput[config][DecodeOneInsnInput[decoder.io_ty()]]],
);
#[hdl]
let global_state: GlobalState = m.input();
#[hdl]
let output: ReadyValid<DecodeAndRunSingleInsnOutput<C, DecodeOneInsnMaxMOpCount<D>>> = m
.output(
ReadyValid
[DecodeAndRunSingleInsnOutput[config][DecodeOneInsnMaxMOpCount[decoder.io_ty()]]],
);
#[hdl]
let to_units: ExecuteToUnitInterfaces<C> = m.output(ExecuteToUnitInterfaces[config]);
#[hdl]
let error: Bool = m.output();
for to_unit in ExecuteToUnitInterfaces::unit_fields(to_units) {
#[hdl]
let ExecuteToUnitInterface::<_> {
global_state: global_state_,
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel: _,
output_ready: _,
finish_cause_cancel: _,
unit_outputs_ready,
cancel_all,
config: _,
} = to_unit;
connect(global_state_, global_state);
connect(enqueue.data, enqueue.ty().data.HdlNone());
connect(inputs_ready, inputs_ready.ty().HdlNone());
connect(
is_no_longer_speculative,
is_no_longer_speculative.ty().HdlNone(),
);
connect(unit_outputs_ready, false);
connect(cancel_all.data, HdlNone());
}
connect(error, false);
#[hdl]
let decoder = instance(decoder);
#[hdl]
let decoder_input = wire(decoder.ty().input_ty());
connect(decoder_input, decoder_input.ty().uninit());
#[hdl]
let decoder_output = wire(DecodeOneInsnOutput[decoder.ty().max_mop_count()]);
D::connect_io(decoder, decoder_input, decoder_output);
#[hdl]
let state_reg = reg_builder().clock_domain(cd).reset(
HdlOption[DecodeAndRunSingleInsnState[config][decoder.ty().max_mop_count()]].HdlNone(),
);
#[hdl]
if let HdlSome(state) = state_reg {
connect(input.ready, false);
let step = DecodeAndRunSingleInsnState::step(state, to_units);
#[hdl]
match step {
StepOutput::<_, _>::Finished(v) => {
connect(output.data, HdlSome(v));
#[hdl]
if output.ready {
connect(state_reg, state_reg.ty().HdlNone());
}
}
StepOutput::<_, _>::Step(state) => {
connect(state_reg, HdlSome(state));
}
StepOutput::<_, _>::Error(_) => {
connect(error, true);
}
}
} else {
connect(input.ready, true);
connect(output.data, output.ty().data.HdlNone());
#[hdl]
if let HdlSome(input) = input.data {
connect(
state_reg,
HdlSome(DecodeAndRunSingleInsnState::new(
input,
decoder_output,
config,
decoder.ty().max_mop_count(),
)),
);
}
}
}