From bc2bcf4434f1531ccc8e1d6f4f59d9770444fc93 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 12 Jul 2026 18:44:03 -0700 Subject: [PATCH 1/2] switch UnitTrait to using UnitIO instead of separate methods for each IO port --- crates/cpu/src/unit.rs | 34 ++++++++++--------------------- crates/cpu/src/unit/alu_branch.rs | 16 ++++++--------- crates/cpu/tests/units_formal.rs | 10 ++++++--- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/crates/cpu/src/unit.rs b/crates/cpu/src/unit.rs index ce024a4..d7442a3 100644 --- a/crates/cpu/src/unit.rs +++ b/crates/cpu/src/unit.rs @@ -387,6 +387,12 @@ impl RenamedMOpFilter for () { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct UnitIO { + pub cd: Option>, + pub from_execute: Expr>>, +} + pub trait UnitTrait: 'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId { @@ -395,11 +401,7 @@ pub trait UnitTrait: fn ty(&self) -> Self::Type; fn unit_kind(&self) -> UnitKind; fn module(&self) -> Interned>; - fn cd(&self, this: Expr) -> Option>; - fn from_execute( - &self, - this: Expr, - ) -> Expr>>; + fn io(&self, this: Expr) -> UnitIO; fn to_dyn(&self) -> DynUnit; } @@ -434,15 +436,8 @@ impl UnitTrait for DynUnit { self.unit.module() } - fn cd(&self, this: Expr) -> Option> { - self.unit.cd(this) - } - - fn from_execute( - &self, - this: Expr, - ) -> Expr>> { - self.unit.from_execute(this) + fn io(&self, this: Expr) -> UnitIO { + self.unit.io(this) } fn to_dyn(&self) -> DynUnit { @@ -468,15 +463,8 @@ impl UnitTrait for DynUnitWrapper) -> Option> { - self.0.cd(Expr::from_bundle(this)) - } - - fn from_execute( - &self, - this: Expr, - ) -> Expr>> { - self.0.from_execute(Expr::from_bundle(this)) + fn io(&self, this: Expr) -> UnitIO { + self.0.io(Expr::from_bundle(this)) } fn to_dyn(&self) -> DynUnit { diff --git a/crates/cpu/src/unit/alu_branch.rs b/crates/cpu/src/unit/alu_branch.rs index 9ad3f4b..52bd4ab 100644 --- a/crates/cpu/src/unit/alu_branch.rs +++ b/crates/cpu/src/unit/alu_branch.rs @@ -18,7 +18,7 @@ use crate::{ ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel, UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady, }, - unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitKind, UnitTrait}, + unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitIO, UnitKind, UnitTrait}, }; use fayalite::{ expr::CastToImpl, @@ -1613,15 +1613,11 @@ impl UnitTrait for AluBranch { self.module } - fn cd(&self, _this: Expr) -> Option> { - None - } - - fn from_execute( - &self, - this: Expr, - ) -> Expr>> { - this.from_execute + fn io(&self, this: Expr) -> UnitIO { + UnitIO { + cd: None, + from_execute: this.from_execute, + } } fn to_dyn(&self) -> DynUnit { diff --git a/crates/cpu/tests/units_formal.rs b/crates/cpu/tests/units_formal.rs index 47ad415..fa3397d 100644 --- a/crates/cpu/tests/units_formal.rs +++ b/crates/cpu/tests/units_formal.rs @@ -14,7 +14,7 @@ use cpu::{ DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput, DecodeOneInsnMaxMOpCount, decode_and_run_single_insn, }, - unit::{RenamedMOpFilter, UnitKind, UnitMOp, UnitTrait}, + unit::{RenamedMOpFilter, UnitIO, UnitKind, UnitMOp, UnitTrait}, util::array_vec::ArrayVec, }; use fayalite::{ @@ -91,11 +91,15 @@ fn formal_harness( dyn_unit.module(), SourceLocation::caller(), ); + let UnitIO { + cd: unit_cd, + from_execute: unit_from_execute, + } = dyn_unit.io(unit); connect( - dyn_unit.from_execute(unit), + unit_from_execute, ExecuteToUnitInterfaces::unit_fields(decode_and_run.to_units)[unit_index], ); - if let Some(unit_cd) = dyn_unit.cd(unit) { + if let Some(unit_cd) = unit_cd { connect(unit_cd, cd); } } From f1f39187e431f4bec5756153b49fb7cbec6a1ef3 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 10 Jul 2026 19:16:13 -0700 Subject: [PATCH 2/2] WIP: adding load/store unit --- crates/cpu/src/unit.rs | 5 +- crates/cpu/src/unit/alu_branch.rs | 1 + crates/cpu/src/unit/load_store.rs | 237 ++++++++++++++++++++++++++++++ crates/cpu/tests/units_formal.rs | 8 +- 4 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 crates/cpu/src/unit/load_store.rs diff --git a/crates/cpu/src/unit.rs b/crates/cpu/src/unit.rs index d7442a3..4864125 100644 --- a/crates/cpu/src/unit.rs +++ b/crates/cpu/src/unit.rs @@ -8,6 +8,7 @@ use crate::{ MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum, }, rename_execute_retire::ExecuteToUnitInterface, + unit::load_store::LoadStoreToDCacheInterface, }; use fayalite::{ bundle::{Bundle, BundleType}, @@ -18,6 +19,7 @@ use serde::{Deserialize, Serialize}; use std::ops::ControlFlow; pub mod alu_branch; +pub mod load_store; macro_rules! all_units { ( @@ -333,7 +335,7 @@ all_units! { #[create_dyn_unit_fn = |config, unit_index, filter| todo!()] #[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)] TransformedMove(TransformedMoveOp), - #[create_dyn_unit_fn = |config, unit_index, filter| todo!()] + #[create_dyn_unit_fn = |config, unit_index, filter| load_store::LoadStore::new(config, unit_index, filter).to_dyn()] #[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)] LoadStore(LoadStoreMOp), } @@ -391,6 +393,7 @@ impl RenamedMOpFilter for () { pub struct UnitIO { pub cd: Option>, pub from_execute: Expr>>, + pub to_d_cache: Option>>>, } pub trait UnitTrait: diff --git a/crates/cpu/src/unit/alu_branch.rs b/crates/cpu/src/unit/alu_branch.rs index 52bd4ab..4af688a 100644 --- a/crates/cpu/src/unit/alu_branch.rs +++ b/crates/cpu/src/unit/alu_branch.rs @@ -1617,6 +1617,7 @@ impl UnitTrait for AluBranch { UnitIO { cd: None, from_execute: this.from_execute, + to_d_cache: None, } } diff --git a/crates/cpu/src/unit/load_store.rs b/crates/cpu/src/unit/load_store.rs new file mode 100644 index 0000000..89c8157 --- /dev/null +++ b/crates/cpu/src/unit/load_store.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + config::CpuConfig, + instruction::LoadStoreMOp, + main_memory_and_io::MemoryOperationErrorKind, + next_pc::CallStackOp, + register::PRegValue, + rename_execute_retire::{ + ExecuteToUnitInterface, NextPcPredictorOp, RenamedMOp, UnitCausedCancel, + UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady, + }, + unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitIO, UnitKind, UnitTrait}, +}; +use fayalite::{intern::Interned, prelude::*, util::ready_valid::ReadyValid}; +use serde::{Deserialize, Serialize}; + +#[hdl] +pub struct DCacheOpKindLoad { + /// if this is `false`, then if the address refers to a valid cache line, that cache line must have any dirty data + /// written back to memory and then be set to invalid before performing the load. + pub is_cacheable: Bool, + /// if this is `true`, then the load must only look in the cache (must not propagate to the L2/L3 cache or to + /// memory), must not cause any earlier operations' timing to change because this load exists, and must not change + /// any cache state that remains after all operations finish, including LRU state, random number generators, + /// predictive prefetching state, etc. + /// Speculative loads that miss the cache must return [`DCacheFinishStatus::NotFound`]. + pub is_speculative: Bool, +} + +#[hdl] +pub struct DCacheOpKindStore { + /// if this is `false`, then if the address refers to a valid cache line, that cache line must have any dirty data + /// written back to memory and then be set to invalid before performing the store. + pub is_cacheable: Bool, +} + +#[hdl] +pub enum DCacheOpKind { + Load(DCacheOpKindLoad), + Store(DCacheOpKindStore), +} + +#[hdl] +pub struct DCacheStart> { + pub kind: DCacheOpKind, + /// unaligned addresses may cause a load/store to cross a cache-line boundary + pub address: UInt<64>, + pub data_and_mask: Array>, 8>, + pub config: C, +} + +#[hdl] +pub enum DCacheFinishStatus { + /// The operation finished successfully + Success, + /// A speculative load missed the cache, so data was not returned. + NotFound, + MemoryError(MemoryOperationErrorKind), +} + +#[hdl] +pub struct DCacheFinish> { + pub status: DCacheFinishStatus, + pub data: Array, 8>, + pub config: C, +} + +#[hdl] +pub struct LoadStoreToDCacheInterface> { + pub start: ReadyValid>, + #[hdl(flip)] + pub finish: ReadyValid>, + pub config: C, +} + +#[hdl] +async fn load_store_impl( + config: PhantomConst, + unit_index: usize, + filter: LoadStoreFilter, + cd: Expr, + from_execute: Expr>>, + to_d_cache: Expr>>, + mut sim: ExternModuleSimulationState, +) { + todo!() +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct LoadStoreFilter { + load: bool, + store: bool, +} + +impl LoadStoreFilter { + fn new( + from_execute: ExecuteToUnitInterface>, + filter: &mut impl RenamedMOpFilter, + ) -> Self { + let LoadStoreMOp { Load, Store } = from_execute + .inputs_ready + .HdlSome + .mop + .mop + .inner_ty() + .LoadStore; + Self { + load: filter.should_include_ty(Load), + store: filter.should_include_ty(Store), + } + } +} + +#[hdl_module(extern)] +pub fn load_store( + config: PhantomConst, + unit_index: usize, + filter: &mut impl RenamedMOpFilter, +) { + #[hdl] + let cd: ClockDomain = m.input(); + + #[hdl] + let from_execute: ExecuteToUnitInterface> = + m.input(ExecuteToUnitInterface[config]); + + #[hdl] + let to_d_cache: LoadStoreToDCacheInterface> = + m.output(LoadStoreToDCacheInterface[config]); + + let filter = LoadStoreFilter::new(from_execute.ty(), filter); + + assert_eq!(config.get().units[unit_index].kind, UnitKind::LoadStore); + + m.register_clock_for_past(cd.clk); + + m.extern_module_simulation_fn( + (config, unit_index, filter, cd, from_execute, to_d_cache), + async |(config, unit_index, filter, cd, from_execute, to_d_cache), mut sim| { + sim.resettable( + cd, + async |mut sim| { + #[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: _, + } = from_execute; + sim.write(enqueue.ready, false).await; + sim.write(cant_cause_cancel, cant_cause_cancel.ty().HdlNone()) + .await; + sim.write(output_ready, output_ready.ty().HdlNone()).await; + sim.write(finish_cause_cancel, finish_cause_cancel.ty().HdlNone()) + .await; + sim.write(cancel_all.ready, false).await; + #[hdl] + let LoadStoreToDCacheInterface::<_> { + start, + finish, + config: _, + } = to_d_cache; + sim.write(start.data, start.ty().data.HdlNone()).await; + sim.write(finish.ready, false).await; + }, + async |sim, ()| { + load_store_impl( + config, + unit_index, + filter, + cd, + from_execute, + to_d_cache, + sim, + ) + .await + }, + ) + .await + }, + ); +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct LoadStore { + config: PhantomConst, + module: Interned>, +} + +impl LoadStore { + pub fn new( + config: PhantomConst, + unit_index: usize, + filter: &mut impl RenamedMOpFilter, + ) -> Self { + Self { + config, + module: load_store(config, unit_index, filter), + } + } +} + +impl UnitTrait for LoadStore { + type Type = load_store; + + fn ty(&self) -> Self::Type { + self.module.io_ty() + } + + fn unit_kind(&self) -> UnitKind { + UnitKind::LoadStore + } + + fn module(&self) -> Interned> { + self.module + } + + fn io(&self, this: Expr) -> UnitIO { + UnitIO { + cd: Some(this.cd), + from_execute: this.from_execute, + to_d_cache: Some(this.to_d_cache), + } + } + + fn to_dyn(&self) -> DynUnit { + DynUnitWrapper(*self).to_dyn() + } +} diff --git a/crates/cpu/tests/units_formal.rs b/crates/cpu/tests/units_formal.rs index fa3397d..2a818b8 100644 --- a/crates/cpu/tests/units_formal.rs +++ b/crates/cpu/tests/units_formal.rs @@ -94,13 +94,17 @@ fn formal_harness( let UnitIO { cd: unit_cd, from_execute: unit_from_execute, + to_d_cache, } = dyn_unit.io(unit); + if let Some(unit_cd) = unit_cd { + connect(unit_cd, cd); + } connect( unit_from_execute, ExecuteToUnitInterfaces::unit_fields(decode_and_run.to_units)[unit_index], ); - if let Some(unit_cd) = unit_cd { - connect(unit_cd, cd); + if let Some(to_d_cache) = to_d_cache { + unimplemented!("to_d_cache for {unit:?}: {to_d_cache:?}"); } } hdl_assert(cd.clk, !decode_and_run.error, "");