WIP: adding test::decode_and_run_single_insn

This commit is contained in:
Jacob Lifshay 2026-05-29 01:32:18 -07:00
parent 29622be160
commit c8a4c576d3
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
4 changed files with 660 additions and 1 deletions

View file

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

View file

@ -1,11 +1,15 @@
// SPDX-License-Identifier: LGPL-3.0-or-later // SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::{config::CpuConfig, rename_execute_retire::ExecuteToUnitInterface}; use crate::{
config::{CpuConfig, CpuConfigUnitCount},
rename_execute_retire::ExecuteToUnitInterface,
};
use fayalite::{ use fayalite::{
bundle::{BundleField, BundleType, NoBuilder}, bundle::{BundleField, BundleType, NoBuilder},
expr::ops::FieldAccess, expr::ops::FieldAccess,
intern::{Intern, Interned, Memoize}, intern::{Intern, Interned, Memoize},
module::{connect_with_loc, wire_with_loc},
prelude::*, prelude::*,
ty::{ ty::{
OpaqueSimValue, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, OpaqueSimValue, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
@ -102,6 +106,42 @@ impl<C: Type + PhantomConstGet<CpuConfig>> ExecuteToUnitInterfaces<C> {
} }
MyMemoize(PhantomData).get_owned(this.to_expr()) MyMemoize(PhantomData).get_owned(this.to_expr())
} }
#[track_caller]
pub fn unit_fields_as_array_wire_with_loc(
this: impl ToExpr<Type = Self>,
wire_name: &str,
source_location: SourceLocation,
array_wire_is_dest: bool,
) -> Expr<ArrayType<ExecuteToUnitInterface<C>, CpuConfigUnitCount<C>>> {
let this = this.to_expr();
let config = this.ty().config;
let array_wire = wire_with_loc(
wire_name,
source_location,
ArrayType[ExecuteToUnitInterface[config]][CpuConfigUnitCount[config]],
);
for (unit_field, array_element) in Self::unit_fields(this).into_iter().zip(array_wire) {
if array_wire_is_dest {
connect_with_loc(array_element, unit_field, source_location);
} else {
connect_with_loc(unit_field, array_element, source_location);
}
}
array_wire
}
#[track_caller]
pub fn unit_fields_as_array_wire(
this: impl ToExpr<Type = Self>,
wire_name: &str,
array_wire_is_dest: bool,
) -> Expr<ArrayType<ExecuteToUnitInterface<C>, CpuConfigUnitCount<C>>> {
Self::unit_fields_as_array_wire_with_loc(
this,
wire_name,
SourceLocation::caller(),
array_wire_is_dest,
)
}
} }
#[doc(hidden)] #[doc(hidden)]

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,614 @@
// 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,
UnitEnqueue, 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>>>,
unit_num: TraceAsString<UnitNum<C>>,
}
#[hdl(no_static)]
enum Error {
NoUnitsOfKind(HdlUnitKind),
InconsistentState,
}
#[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]
enum UnitState {
NotYetEnqueued,
InputsNotReadySpeculative,
InputsReady,
OutputReady,
FinishedAndOrCausedCancel,
}
#[hdl(no_static)]
struct RunMOpState<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
mop_index: UIntInRangeType<ConstUsize<0>, MaxMOpCount>,
unit_state: UnitState,
config: C,
}
#[hdl(no_static)]
enum RunState<C: PhantomConstGet<CpuConfig>, MaxMOpCount: Size> {
RunningMOp(RunMOpState<C, 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>),
}
#[hdl]
fn connect_to_unit_nop<C: Type + PhantomConstGet<CpuConfig>>(
to_unit: Expr<ExecuteToUnitInterface<C>>,
global_state: impl ToExpr<Type = GlobalState>,
) {
#[hdl]
let ExecuteToUnitInterface::<_> {
global_state: to_unit_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(to_unit_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());
}
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,
run_state.ty().RunningMOp(
#[hdl]
RunMOpState::<_, _> {
mop_index: 0usize.cast_to(run_state.ty().RunningMOp.mop_index),
unit_state: UnitState.NotYetEnqueued(),
config,
},
),
);
}
let retval = wire_with_loc(
&format!("mops_{index}"),
SourceLocation::caller(),
UnrenamedMOpState[config],
);
#[hdl]
let UnrenamedMOpState::<_> {
unrenamed,
renamed,
unit_num,
} = 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());
connect(*unit_num, unit_num.ty().inner_ty().const_zero());
#[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()));
connect(*unit_num, MOpTrait::dest_reg(v).unit_num);
}
}
retval
},
),
);
connect(regs, input.regs);
new_state
}
#[hdl]
fn step(
this: Expr<Self>,
to_units: Expr<ExecuteToUnitInterfaces<C>>,
global_state: Expr<GlobalState>,
) -> 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]);
let to_units_as_array = ExecuteToUnitInterfaces::unit_fields_as_array_wire(
to_units,
"to_units_as_array",
false,
);
for to_unit in to_units_as_array {
connect_to_unit_nop(to_unit, global_state);
}
#[hdl]
match run_state {
RunState::<_, _>::RunningMOp(run_mop_state) => {
#[hdl]
let stepped_step = wire(stepped.ty().Step);
connect(stepped, stepped.ty().Step(stepped_step));
#[hdl]
let RunMOpState::<_, _> {
mop_index,
unit_state,
config: _,
} = run_mop_state;
let mop_index = mop_index.cast_to(UInt[mop_index.ty().bit_width()]);
#[hdl]
let mop = wire(mops.ty().element());
connect(mop, mops[mop_index]);
#[hdl]
let UnrenamedMOpState::<_> {
unrenamed,
renamed,
unit_num,
} = mop;
connect(stepped_step.mops, mops);
#[hdl]
if let HdlSome(unit_index) = UnitNum::as_index(*unit_num) {
#[hdl]
let to_unit = wire(to_units_as_array.ty().element());
connect(to_units_as_array[unit_index], to_unit);
connect_to_unit_nop(to_unit, global_state);
#[hdl]
let ExecuteToUnitInterface::<_> {
global_state: _,
enqueue,
inputs_ready,
is_no_longer_speculative,
cant_cause_cancel,
output_ready,
finish_cause_cancel,
unit_outputs_ready,
cancel_all,
config: _,
} = to_unit;
#[hdl]
match unit_state {
UnitState::NotYetEnqueued => {
#[hdl]
if let HdlSome(renamed) = renamed {
let mop = #[hdl]
MOpInstance::<_> {
fetch_block_id: unrenamed.fetch_block_id,
id: unrenamed.id,
pc: unrenamed.pc,
predicted_next_pc: unrenamed.predicted_next_pc,
size_in_bytes: unrenamed.size_in_bytes,
is_first_mop_in_insn: unrenamed.is_first_mop_in_insn,
is_last_mop_in_insn: unrenamed.is_last_mop_in_insn,
mop: renamed,
};
connect(
enqueue.data,
HdlSome(
#[hdl]
UnitEnqueue::<_> { mop, config },
),
);
#[hdl]
if enqueue.ready {
todo!();
}
} else {
connect(
stepped,
stepped
.ty()
.Error(Error.InconsistentState().to_trace_as_string()),
);
}
todo!();
}
UnitState::InputsNotReadySpeculative => {
todo!();
}
UnitState::InputsReady => {
todo!();
}
UnitState::OutputReady => {
todo!();
}
UnitState::FinishedAndOrCausedCancel => {
todo!();
}
}
} else {
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();
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, global_state);
#[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 {
for to_unit in ExecuteToUnitInterfaces::unit_fields(to_units) {
connect_to_unit_nop(to_unit, global_state);
}
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(),
)),
);
}
}
}