WIP: Add Load/Store Unit #16

Draft
programmerjake wants to merge 4 commits from programmerjake/cpu:load_store into master
13 changed files with 70817 additions and 71 deletions

4
.cargo/config.toml Normal file
View file

@ -0,0 +1,4 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
[build]
rustflags = ["-Csymbol-mangling-version=v0", "-Cforce-frame-pointers=yes"]

View file

@ -19,6 +19,7 @@ use std::{
borrow::Cow, borrow::Cow,
fmt, fmt,
marker::PhantomData, marker::PhantomData,
num::NonZeroU64,
ops::{ControlFlow, Range}, ops::{ControlFlow, Range},
}; };
@ -2983,6 +2984,19 @@ pub enum LoadStoreWidth {
Width64Bit, Width64Bit,
} }
impl LoadStoreWidth {
#[hdl]
pub fn access_byte_size_sim(this: &<Self as Type>::SimValue) -> NonZeroU64 {
#[hdl(sim)]
match this {
Self::Width8Bit => const { NonZeroU64::new(1).unwrap() },
Self::Width16Bit => const { NonZeroU64::new(2).unwrap() },
Self::Width32Bit => const { NonZeroU64::new(4).unwrap() },
Self::Width64Bit => const { NonZeroU64::new(8).unwrap() },
}
}
}
#[hdl(cmp_eq)] #[hdl(cmp_eq)]
pub enum LoadStoreConversion { pub enum LoadStoreConversion {
ZeroExt, ZeroExt,

View file

@ -127,8 +127,47 @@ impl AddressRange {
}, },
} }
} }
pub const fn is_overlapping(self, other: Self) -> bool {
self.contains(other.start().0) || other.contains(self.start().0)
}
} }
const _: () = {
const N: u64 = 4;
const fn is_overlapping_reference(range1: AddressRange, range2: AddressRange) -> bool {
// we just need to check values < N since all starts/ends are < N
let mut v = 0;
while v < N {
if range1.contains(v) && range2.contains(v) {
return true;
}
v += 1;
}
false
}
let mut start1 = 0;
while start1 < N {
let mut end1 = 0;
while end1 < N {
let mut start2 = 0;
while start2 < N {
let mut end2 = 0;
while end2 < N {
let range1 = AddressRange::from_wrapping_range_inclusive(start1..=end1);
let range2 = AddressRange::from_wrapping_range_inclusive(start2..=end2);
assert!(
range1.is_overlapping(range2) == is_overlapping_reference(range1, range2)
);
end2 += 1;
}
start2 += 1;
}
end1 += 1;
}
start1 += 1;
}
};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
#[non_exhaustive] #[non_exhaustive]
pub struct MemoryInterfaceConfig { pub struct MemoryInterfaceConfig {

View file

@ -203,6 +203,16 @@ pub struct UnitMOpCantCauseCancel<C: PhantomConstGet<CpuConfig>> {
pub config: C, pub config: C,
} }
/// This tells the Unit that if [`Self::if_cant_cause_cancel_id`] can no longer cause a cancel
/// (either via [`UnitMOpCantCauseCancel`] or [`UnitFinishCauseCancel`] without a cancel),
/// then [`Self::then_no_longer_speculative_id`] will no longer be speculative.
#[hdl(no_static)]
pub struct UnitMOpIsNoLongerSpeculativePrediction<C: PhantomConstGet<CpuConfig>> {
pub if_cant_cause_cancel_id: MOpId,
pub then_no_longer_speculative_id: MOpId,
pub config: C,
}
/// Interface from the Rename/Execute/Retire control logic to a single Unit. /// Interface from the Rename/Execute/Retire control logic to a single Unit.
/// ///
/// ## State diagram for a single &micro;Op in a Unit /// ## State diagram for a single &micro;Op in a Unit
@ -223,6 +233,8 @@ pub struct ExecuteToUnitInterface<C: PhantomConstGet<CpuConfig>> {
pub inputs_ready: HdlOption<UnitInputsReady<C>>, pub inputs_ready: HdlOption<UnitInputsReady<C>>,
/// if [`Self::unit_outputs_ready`] is `false`, then this is always [`HdlNone`] /// if [`Self::unit_outputs_ready`] is `false`, then this is always [`HdlNone`]
pub is_no_longer_speculative: HdlOption<UnitMOpIsNoLongerSpeculative<C>>, pub is_no_longer_speculative: HdlOption<UnitMOpIsNoLongerSpeculative<C>>,
/// if [`Self::unit_outputs_ready`] is `false`, then this is always [`HdlNone`]
pub is_no_longer_speculative_prediction: HdlOption<UnitMOpIsNoLongerSpeculativePrediction<C>>,
/// this uses [`Self::unit_outputs_ready`] as a shared ready flag /// this uses [`Self::unit_outputs_ready`] as a shared ready flag
#[hdl(flip)] #[hdl(flip)]
pub cant_cause_cancel: HdlOption<UnitMOpCantCauseCancel<C>>, pub cant_cause_cancel: HdlOption<UnitMOpCantCauseCancel<C>>,
@ -1263,31 +1275,62 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
fn get_unit_mop_is_no_longer_speculative( fn get_unit_mop_is_no_longer_speculative(
&self, &self,
unit_index: usize, unit_index: usize,
) -> SimValue<HdlOption<UnitMOpIsNoLongerSpeculative<C>>> { ) -> Option<SimValue<UnitMOpIsNoLongerSpeculative<C>>> {
let ret_ty = HdlOption[UnitMOpIsNoLongerSpeculative[self.config]];
if self.is_canceling() { if self.is_canceling() {
let retval = #[hdl(sim)] return None;
ret_ty.HdlNone();
return retval; // separate variable to work around rust-analyzer parse error
} }
for rob in self.rob.renamed() { for rob in self.rob.renamed() {
if rob.unit_index == unit_index if rob.unit_index == unit_index
&& !rob.is_speculative && !rob.is_speculative
&& let Some(_) = rob.mop_in_unit_state.without_speculative() && let Some(_) = rob.mop_in_unit_state.without_speculative()
{ {
let retval = #[hdl(sim)] return Some(
ret_ty.HdlSome(
#[hdl(sim)] #[hdl(sim)]
UnitMOpIsNoLongerSpeculative::<_> { UnitMOpIsNoLongerSpeculative::<_> {
id: &rob.mop.id, id: &rob.mop.id,
config: self.config, config: self.config,
}, },
); );
return retval; // separate variable to work around rust-analyzer parse error
} }
} }
#[hdl(sim)] None
ret_ty.HdlNone() }
#[hdl]
fn get_unit_mop_is_no_longer_speculative_prediction(
&self,
unit_index: usize,
) -> Option<SimValue<UnitMOpIsNoLongerSpeculativePrediction<C>>> {
if self.is_canceling() {
return None;
}
let mut renamed_iter = self.rob.renamed();
let if_cant_cause_cancel_id = loop {
let Some(renamed) = renamed_iter.next() else {
return None;
};
if renamed.can_cause_cancel() {
if renamed.unit_index != unit_index {
return None;
}
break &renamed.mop.id;
}
};
for renamed in renamed_iter {
if renamed.unit_index == unit_index {
return Some(
#[hdl(sim)]
UnitMOpIsNoLongerSpeculativePrediction::<_> {
if_cant_cause_cancel_id,
then_no_longer_speculative_id: &renamed.mop.id,
config: self.config,
},
);
}
if renamed.can_cause_cancel() {
break;
}
}
None
} }
#[hdl] #[hdl]
fn unit_output_ready(&mut self, output_ready: SimValue<UnitOutputReady<C>>) { fn unit_output_ready(&mut self, output_ready: SimValue<UnitOutputReady<C>>) {
@ -1618,21 +1661,8 @@ impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
}; };
} }
for renamed in self.rob.renamed_mut() { for renamed in self.rob.renamed_mut() {
let can_cause_cancel = match renamed.mop_in_unit_state {
MOpInUnitState::NotYetEnqueued => true,
MOpInUnitState::InputsNotReadySpeculative { can_cause_cancel } => can_cause_cancel,
MOpInUnitState::InputsReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::OutputReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::FinishedAndOrCausedCancel => renamed.caused_cancel.is_some(),
};
renamed.is_speculative = false; renamed.is_speculative = false;
if can_cause_cancel { if renamed.can_cause_cancel() {
break; break;
} }
} }
@ -1719,6 +1749,7 @@ async fn rename_execute_retire_run(
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative_prediction,
cant_cause_cancel: _, cant_cause_cancel: _,
output_ready: _, output_ready: _,
finish_cause_cancel: _, finish_cause_cancel: _,
@ -1747,6 +1778,11 @@ async fn rename_execute_retire_run(
state.get_unit_mop_is_no_longer_speculative(unit_index), state.get_unit_mop_is_no_longer_speculative(unit_index),
) )
.await; .await;
sim.write(
is_no_longer_speculative_prediction,
state.get_unit_mop_is_no_longer_speculative_prediction(unit_index),
)
.await;
sim.write(unit_outputs_ready, !is_canceling).await; sim.write(unit_outputs_ready, !is_canceling).await;
} }
sim.wait_for_clock_edge(cd.clk).await; sim.wait_for_clock_edge(cd.clk).await;
@ -1767,6 +1803,7 @@ async fn rename_execute_retire_run(
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative_prediction: _, // no action necessary
cant_cause_cancel, cant_cause_cancel,
output_ready, output_ready,
finish_cause_cancel, finish_cause_cancel,
@ -1959,6 +1996,7 @@ pub fn rename_execute_retire(config: PhantomConst<CpuConfig>) {
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative_prediction,
cant_cause_cancel: _, cant_cause_cancel: _,
output_ready: _, output_ready: _,
finish_cause_cancel: _, finish_cause_cancel: _,
@ -1992,6 +2030,12 @@ pub fn rename_execute_retire(config: PhantomConst<CpuConfig>) {
(is_no_longer_speculative.ty()).HdlNone(), (is_no_longer_speculative.ty()).HdlNone(),
) )
.await; .await;
sim.write(
is_no_longer_speculative_prediction,
#[hdl(sim)]
(is_no_longer_speculative_prediction.ty()).HdlNone(),
)
.await;
sim.write(unit_outputs_ready, false).await; sim.write(unit_outputs_ready, false).await;
} }
}, },

View file

@ -173,6 +173,21 @@ impl<C: PhantomConstCpuConfig> RobEntry<C> {
} }
} }
} }
pub(crate) fn can_cause_cancel(&self) -> bool {
match self.mop_in_unit_state {
MOpInUnitState::NotYetEnqueued => true,
MOpInUnitState::InputsNotReadySpeculative { can_cause_cancel } => can_cause_cancel,
MOpInUnitState::InputsReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::OutputReady {
speculative: _,
can_cause_cancel,
} => can_cause_cancel,
MOpInUnitState::FinishedAndOrCausedCancel => self.caused_cancel.is_some(),
}
}
} }
#[hdl] #[hdl]

View file

@ -204,6 +204,7 @@ fn connect_to_unit_nop<C: Type + PhantomConstGet<CpuConfig>>(
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative_prediction,
cant_cause_cancel: _, cant_cause_cancel: _,
output_ready: _, output_ready: _,
finish_cause_cancel: _, finish_cause_cancel: _,
@ -218,6 +219,10 @@ fn connect_to_unit_nop<C: Type + PhantomConstGet<CpuConfig>>(
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative.ty().HdlNone(), is_no_longer_speculative.ty().HdlNone(),
); );
connect(
is_no_longer_speculative_prediction,
is_no_longer_speculative_prediction.ty().HdlNone(),
);
connect(unit_outputs_ready, false); connect(unit_outputs_ready, false);
connect(cancel_all.data, HdlNone()); connect(cancel_all.data, HdlNone());
} }
@ -506,6 +511,7 @@ impl<C: PhantomConstCpuConfig, MaxMOpCount: Size> DecodeAndRunSingleInsnState<C,
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative, is_no_longer_speculative,
is_no_longer_speculative_prediction,
cant_cause_cancel: _, // we don't track if it can cause a cancel cant_cause_cancel: _, // we don't track if it can cause a cancel
output_ready, output_ready,
finish_cause_cancel, finish_cause_cancel,
@ -513,6 +519,10 @@ impl<C: PhantomConstCpuConfig, MaxMOpCount: Size> DecodeAndRunSingleInsnState<C,
cancel_all, cancel_all,
config: _, config: _,
} = to_unit; } = to_unit;
connect(
is_no_longer_speculative_prediction,
is_no_longer_speculative_prediction.ty().HdlNone(),
);
connect(unit_outputs_ready, true); connect(unit_outputs_ready, true);
connect(cancel_all.data, HdlNone()); connect(cancel_all.data, HdlNone());
#[hdl] #[hdl]

View file

@ -8,6 +8,7 @@ use crate::{
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum, MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
}, },
rename_execute_retire::ExecuteToUnitInterface, rename_execute_retire::ExecuteToUnitInterface,
unit::load_store::LoadStoreToDCacheInterface,
}; };
use fayalite::{ use fayalite::{
bundle::{Bundle, BundleType}, bundle::{Bundle, BundleType},
@ -18,6 +19,7 @@ use serde::{Deserialize, Serialize};
use std::ops::ControlFlow; use std::ops::ControlFlow;
pub mod alu_branch; pub mod alu_branch;
pub mod load_store;
macro_rules! all_units { macro_rules! all_units {
( (
@ -333,7 +335,7 @@ all_units! {
#[create_dyn_unit_fn = |config, unit_index, filter| todo!()] #[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)] #[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)]
TransformedMove(TransformedMoveOp), 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)] #[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)]
LoadStore(LoadStoreMOp<DestReg, SrcReg>), LoadStore(LoadStoreMOp<DestReg, SrcReg>),
} }
@ -387,6 +389,13 @@ impl RenamedMOpFilter for () {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct UnitIO {
pub cd: Option<Expr<ClockDomain>>,
pub from_execute: Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>,
pub to_d_cache: Option<Expr<LoadStoreToDCacheInterface<PhantomConst<CpuConfig>>>>,
}
pub trait UnitTrait: pub trait UnitTrait:
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId 'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
{ {
@ -395,11 +404,7 @@ pub trait UnitTrait:
fn ty(&self) -> Self::Type; fn ty(&self) -> Self::Type;
fn unit_kind(&self) -> UnitKind; fn unit_kind(&self) -> UnitKind;
fn module(&self) -> Interned<Module<Self::Type>>; fn module(&self) -> Interned<Module<Self::Type>>;
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>>; fn io(&self, this: Expr<Self::Type>) -> UnitIO;
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>>;
fn to_dyn(&self) -> DynUnit; fn to_dyn(&self) -> DynUnit;
} }
@ -434,15 +439,8 @@ impl UnitTrait for DynUnit {
self.unit.module() self.unit.module()
} }
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> { fn io(&self, this: Expr<Self::Type>) -> UnitIO {
self.unit.cd(this) self.unit.io(this)
}
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.unit.from_execute(this)
} }
fn to_dyn(&self) -> DynUnit { fn to_dyn(&self) -> DynUnit {
@ -468,15 +466,8 @@ impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T
self.0.module().canonical().intern_sized() self.0.module().canonical().intern_sized()
} }
fn cd(&self, this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> { fn io(&self, this: Expr<Self::Type>) -> UnitIO {
self.0.cd(Expr::from_bundle(this)) self.0.io(Expr::from_bundle(this))
}
fn from_execute(
&self,
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
self.0.from_execute(Expr::from_bundle(this))
} }
fn to_dyn(&self) -> DynUnit { fn to_dyn(&self) -> DynUnit {

View file

@ -18,7 +18,7 @@ use crate::{
ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel, ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel,
UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady, UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady,
}, },
unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitKind, UnitTrait}, unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitIO, UnitKind, UnitTrait},
}; };
use fayalite::{ use fayalite::{
expr::CastToImpl, expr::CastToImpl,
@ -1260,6 +1260,7 @@ pub fn alu_branch(
enqueue, enqueue,
inputs_ready, inputs_ready,
is_no_longer_speculative: _, // we don't care about being speculative for these instructions is_no_longer_speculative: _, // we don't care about being speculative for these instructions
is_no_longer_speculative_prediction: _,
cant_cause_cancel, cant_cause_cancel,
output_ready, output_ready,
finish_cause_cancel, finish_cause_cancel,
@ -1613,15 +1614,12 @@ impl UnitTrait for AluBranch {
self.module self.module
} }
fn cd(&self, _this: Expr<Self::Type>) -> Option<Expr<ClockDomain>> { fn io(&self, this: Expr<Self::Type>) -> UnitIO {
None UnitIO {
} cd: None,
from_execute: this.from_execute,
fn from_execute( to_d_cache: None,
&self, }
this: Expr<Self::Type>,
) -> Expr<ExecuteToUnitInterface<PhantomConst<CpuConfig>>> {
this.from_execute
} }
fn to_dyn(&self) -> DynUnit { fn to_dyn(&self) -> DynUnit {

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,8 @@
// 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 fayalite::{ use fayalite::{expr::ops::ArrayLiteral, module::wire_with_loc, prelude::*};
bundle::BundleType, expr::ops::ArrayLiteral, module::wire_with_loc, prelude::*, use std::num::NonZero;
sim::vcd::VcdWriterDecls, util::RcWriter,
};
use std::{
num::NonZero,
panic::Location,
path::{Path, PathBuf},
};
pub mod array_vec; pub mod array_vec;
pub mod tree_reduce; pub mod tree_reduce;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@ use cpu::{
DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput, DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput,
DecodeOneInsnMaxMOpCount, decode_and_run_single_insn, DecodeOneInsnMaxMOpCount, decode_and_run_single_insn,
}, },
unit::{RenamedMOpFilter, UnitKind, UnitMOp, UnitTrait}, unit::{RenamedMOpFilter, UnitIO, UnitKind, UnitMOp, UnitTrait},
util::array_vec::ArrayVec, util::array_vec::ArrayVec,
}; };
use fayalite::{ use fayalite::{
@ -91,12 +91,20 @@ fn formal_harness(
dyn_unit.module(), dyn_unit.module(),
SourceLocation::caller(), SourceLocation::caller(),
); );
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( connect(
dyn_unit.from_execute(unit), unit_from_execute,
ExecuteToUnitInterfaces::unit_fields(decode_and_run.to_units)[unit_index], ExecuteToUnitInterfaces::unit_fields(decode_and_run.to_units)[unit_index],
); );
if let Some(unit_cd) = dyn_unit.cd(unit) { if let Some(to_d_cache) = to_d_cache {
connect(unit_cd, cd); unimplemented!("to_d_cache for {unit:?}: {to_d_cache:?}");
} }
} }
hdl_assert(cd.clk, !decode_and_run.error, ""); hdl_assert(cd.clk, !decode_and_run.error, "");