From a63d2990efe9fab05f9eb45f8bf5cb2a6f0ecf39 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 29 May 2026 01:32:18 -0700 Subject: [PATCH] WIP: adding test::decode_and_run_single_insn --- crates/cpu/src/lib.rs | 1 + .../to_unit_interfaces.rs | 42 +- crates/cpu/src/test.rs | 4 + .../src/test/decode_and_run_single_insn.rs | 614 ++++++++++++++++++ 4 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 crates/cpu/src/test.rs create mode 100644 crates/cpu/src/test/decode_and_run_single_insn.rs diff --git a/crates/cpu/src/lib.rs b/crates/cpu/src/lib.rs index 2ba775e..ef17595 100644 --- a/crates/cpu/src/lib.rs +++ b/crates/cpu/src/lib.rs @@ -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; diff --git a/crates/cpu/src/rename_execute_retire/to_unit_interfaces.rs b/crates/cpu/src/rename_execute_retire/to_unit_interfaces.rs index d3ec39a..0fd362e 100644 --- a/crates/cpu/src/rename_execute_retire/to_unit_interfaces.rs +++ b/crates/cpu/src/rename_execute_retire/to_unit_interfaces.rs @@ -1,11 +1,15 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // 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::{ bundle::{BundleField, BundleType, NoBuilder}, expr::ops::FieldAccess, intern::{Intern, Interned, Memoize}, + module::{connect_with_loc, wire_with_loc}, prelude::*, ty::{ OpaqueSimValue, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, @@ -102,6 +106,42 @@ impl> ExecuteToUnitInterfaces { } MyMemoize(PhantomData).get_owned(this.to_expr()) } + #[track_caller] + pub fn unit_fields_as_array_wire_with_loc( + this: impl ToExpr, + wire_name: &str, + source_location: SourceLocation, + array_wire_is_dest: bool, + ) -> Expr, CpuConfigUnitCount>> { + 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, + wire_name: &str, + array_wire_is_dest: bool, + ) -> Expr, CpuConfigUnitCount>> { + Self::unit_fields_as_array_wire_with_loc( + this, + wire_name, + SourceLocation::caller(), + array_wire_is_dest, + ) + } } #[doc(hidden)] diff --git a/crates/cpu/src/test.rs b/crates/cpu/src/test.rs new file mode 100644 index 0000000..011c7dd --- /dev/null +++ b/crates/cpu/src/test.rs @@ -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; diff --git a/crates/cpu/src/test/decode_and_run_single_insn.rs b/crates/cpu/src/test/decode_and_run_single_insn.rs new file mode 100644 index 0000000..c44d301 --- /dev/null +++ b/crates/cpu/src/test/decode_and_run_single_insn.rs @@ -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> = DynSize; + +#[hdl(cmp_eq, no_static)] +pub struct Registers> { + pub regs: ArrayType, MOpRegCount>, + pub config: C, +} + +impl Registers { + #[hdl] + pub fn zeroed(self) -> Expr { + #[hdl] + Self { + regs: repeat(PRegValue::zeroed().into_trace_as_string(), self.regs.len()), + config: self.config, + } + } + #[hdl] + pub fn zeroed_sim(self) -> SimValue { + #[hdl(sim)] + Self { + regs: vec![PRegValue::zeroed_sim().into_trace_as_string(); self.regs.len()], + config: self.config, + } + } +} + +#[hdl(no_static)] +pub struct DecodeOneInsnOutput { + pub mops: ArrayVec, 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) -> ::SizeType; + fn connect_io( + this: Expr, + input: Expr, + output: Expr>, + ); +} + +pub type DecodeOneInsnInput = ::Input; +#[doc(hidden)] +#[expect(non_upper_case_globals)] +pub const DecodeOneInsnInput: DecodeOneInsnInputWithoutGenerics = + DecodeOneInsnInputWithoutGenerics {}; +#[doc(hidden)] +pub struct DecodeOneInsnInputWithoutGenerics {} + +impl std::ops::Index for DecodeOneInsnInputWithoutGenerics { + type Output = DecodeOneInsnInput; + + fn index(&self, index: T) -> &Self::Output { + Interned::into_inner(index.input_ty().intern_sized()) + } +} + +pub type DecodeOneInsnMaxMOpCount = ::MaxMOpCount; +#[doc(hidden)] +#[expect(non_upper_case_globals)] +pub const DecodeOneInsnMaxMOpCount: DecodeOneInsnMaxMOpCountWithoutGenerics = + DecodeOneInsnMaxMOpCountWithoutGenerics {}; +#[doc(hidden)] +pub struct DecodeOneInsnMaxMOpCountWithoutGenerics {} + +impl std::ops::Index for DecodeOneInsnMaxMOpCountWithoutGenerics { + type Output = ::SizeType; + + fn index(&self, index: T) -> &Self::Output { + Interned::into_inner(index.max_mop_count().intern_sized()) + } +} + +#[hdl(no_static)] +pub struct DecodeAndRunSingleInsnInput, 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, + pub config: C, +} + +#[hdl(no_static)] +pub struct DecodeAndRunSingleInsnOutput, MaxMOpCount: Size> { + pub regs: Registers, + pub cancel_and_start_at: HdlOption>, + pub retired_insns: ArrayVec, MaxMOpCount>, + pub config: C, +} + +#[hdl(no_static)] +struct UnrenamedMOpState> { + unrenamed: MOpInstance, + renamed: HdlOption>>, + unit_num: TraceAsString>, +} + +#[hdl(no_static)] +enum Error { + NoUnitsOfKind(HdlUnitKind), + InconsistentState, +} + +#[hdl(no_static)] +struct CancelState, MaxMOpCount: Size> { + cancel_index: UIntInRangeType, MaxMOpCount>, + canceling_units: ArrayType>, + config: C, +} + +#[hdl] +enum UnitState { + NotYetEnqueued, + InputsNotReadySpeculative, + InputsReady, + OutputReady, + FinishedAndOrCausedCancel, +} + +#[hdl(no_static)] +struct RunMOpState, MaxMOpCount: Size> { + mop_index: UIntInRangeType, MaxMOpCount>, + unit_state: UnitState, + config: C, +} + +#[hdl(no_static)] +enum RunState, MaxMOpCount: Size> { + RunningMOp(RunMOpState), + CancelingUnits(CancelState), + Finished, + Error(TraceAsString), +} + +#[hdl(no_static)] +struct DecodeAndRunSingleInsnState, MaxMOpCount: Size> { + mops: ArrayVec, MaxMOpCount>, + regs: Registers, + run_state: RunState, + config: C, +} + +#[hdl(no_static)] +enum StepOutput, MaxMOpCount: Size> { + Finished(DecodeAndRunSingleInsnOutput), + Step(DecodeAndRunSingleInsnState), + Error(TraceAsString), +} + +#[hdl] +fn connect_to_unit_nop>( + to_unit: Expr>, + global_state: impl ToExpr, +) { + #[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 DecodeAndRunSingleInsnState { + #[hdl] + fn new( + input: Expr>, + decoder_output: Expr>, + config: C, + max_mop_count: MaxMOpCount::SizeType, + ) -> Expr { + #[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::()); + 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, + to_units: Expr>, + global_state: Expr, + ) -> Expr> { + #[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( + config: C, + decoder: Interned>, +) { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let input: ReadyValid>> = m.input( + ReadyValid[DecodeAndRunSingleInsnInput[config][DecodeOneInsnInput[decoder.io_ty()]]], + ); + #[hdl] + let global_state: GlobalState = m.input(); + #[hdl] + let output: ReadyValid>> = m + .output( + ReadyValid + [DecodeAndRunSingleInsnOutput[config][DecodeOneInsnMaxMOpCount[decoder.io_ty()]]], + ); + #[hdl] + let to_units: ExecuteToUnitInterfaces = 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(), + )), + ); + } + } +}