WIP adding memory_interface_adaptor

This commit is contained in:
Jacob Lifshay 2026-03-10 21:23:47 -07:00
parent 3080ea4ce2
commit b09fc821fe
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ

View file

@ -2,8 +2,18 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::{config::CpuConfig, next_pc::FETCH_BLOCK_ID_WIDTH, util::array_vec::ArrayVec}; use crate::{config::CpuConfig, next_pc::FETCH_BLOCK_ID_WIDTH, util::array_vec::ArrayVec};
use fayalite::{prelude::*, util::ready_valid::ReadyValid}; use fayalite::{
use std::num::{NonZeroU64, NonZeroUsize, Wrapping}; bundle::BundleType,
expr::Valueless,
int::{UIntInRangeInclusiveType, UIntInRangeType},
intern::{Intern, Interned},
prelude::*,
util::ready_valid::{ReadyValid, queue},
};
use std::{
fmt,
num::{NonZeroU64, NonZeroUsize, Wrapping},
};
pub mod simple_uart; pub mod simple_uart;
@ -187,3 +197,557 @@ pub struct MemoryInterface<C: PhantomConstGet<MemoryInterfaceConfig>> {
pub next_op_ids: HdlOption<ArrayVec<MemoryInterfaceOpId<C>, MemoryInterfaceQueueCapacity<C>>>, pub next_op_ids: HdlOption<ArrayVec<MemoryInterfaceOpId<C>, MemoryInterfaceQueueCapacity<C>>>,
pub config: C, pub config: C,
} }
pub const fn memory_interface_always_error_config(
base_config: MemoryInterfaceConfig,
) -> MemoryInterfaceConfig {
let MemoryInterfaceConfig {
log2_bus_width_in_bytes,
queue_capacity: _,
op_id_width,
address_range: _,
} = base_config;
MemoryInterfaceConfig {
log2_bus_width_in_bytes,
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
op_id_width,
address_range: AddressRange::Full,
}
}
#[hdl_module]
pub fn memory_interface_always_error(config: PhantomConst<MemoryInterfaceConfig>) {
assert_eq!(
*config.get(),
memory_interface_always_error_config(*config.get()),
);
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
connect(
input_interface.next_op_ids,
input_interface.ty().next_op_ids.HdlNone(),
);
connect(input_interface.start.ready, input_interface.finish.ready);
connect(
input_interface.finish.data,
input_interface.ty().finish.data.HdlNone(),
);
#[hdl]
if let HdlSome(_) = input_interface.start.data {
connect(
input_interface.finish.data,
HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind.Error(MemoryOperationErrorKind.Generic()),
read_data: repeat(0u8, MemoryInterfaceBusWidthInBytes[config]),
config,
},
),
);
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct MemoryInterfacesBundleFieldPath(pub Interned<[Interned<str>]>);
impl MemoryInterfacesBundleFieldPath {
pub fn from_slice(path: &[Interned<str>]) -> Self {
Self(path.intern())
}
}
impl fmt::Display for MemoryInterfacesBundleFieldPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
return f.write_str("<empty path>");
}
for (i, name) in self.0.iter().enumerate() {
if i != 0 {
f.write_str(".")?;
}
if name.is_empty() || name.contains(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_')
{
write!(f, "{name:?}")?;
} else {
f.write_str(name)?;
}
}
Ok(())
}
}
impl fmt::Debug for MemoryInterfacesBundleFieldPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct MemoryInterfacesBundleField<
T: ValueType<Type = MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
> {
pub path: MemoryInterfacesBundleFieldPath,
pub value: T,
}
#[derive(Clone, Debug)]
pub struct MemoryInterfacesBundleProperties<
T: ValueType<Type = MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
> {
first_full_interface: Option<usize>,
fields: Vec<MemoryInterfacesBundleField<T>>,
}
impl<T: ValueType<Type = MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>>
MemoryInterfacesBundleProperties<T>
{
pub fn first_full_interface(&self) -> Option<usize> {
self.first_full_interface
}
pub fn fields(&self) -> &[MemoryInterfacesBundleField<T>] {
&self.fields
}
pub fn into_fields(self) -> Vec<MemoryInterfacesBundleField<T>> {
self.fields
}
#[track_caller]
pub fn from_fields(fields: Vec<MemoryInterfacesBundleField<T>>) -> Self {
let mut first_full_interface = None;
for (index, field) in fields.iter().enumerate() {
let ty = field.value.ty();
assert_eq!(
ty, MemoryInterface[ty.config],
"inconsistent field type: {}",
field.path,
);
if let None = first_full_interface
&& let AddressRange::Full = ty.config.get().address_range
{
first_full_interface = Some(index);
}
}
Self {
first_full_interface,
fields,
}
}
#[track_caller]
fn get_fields<
B: ValueType<Type = Bundle, ValueCategory = T::ValueCategory>,
C: ValueType<Type = CanonicalType, ValueCategory = T::ValueCategory>,
>(
fields: &mut Vec<MemoryInterfacesBundleField<T>>,
path_prefix: &mut Vec<Interned<str>>,
bundle: B,
get_field: impl Copy + Fn(&B, Interned<str>) -> C,
interface_from_bundle: impl Copy + Fn(B) -> T,
bundle_from_canonical: impl Copy + Fn(C) -> B,
) {
let bundle_fields = bundle.ty().fields();
if bundle_fields.iter().any(|f| f.flipped)
&& bundle_fields.iter().any(|f| *f.name == *"config")
{
let value = interface_from_bundle(bundle);
fields.push(MemoryInterfacesBundleField {
path: MemoryInterfacesBundleFieldPath::from_slice(path_prefix),
value,
});
} else {
for f in &bundle_fields {
assert!(
!f.flipped,
"field must not have #[hdl(flip)]: {}",
MemoryInterfacesBundleFieldPath::from_slice(path_prefix),
);
let field = get_field(&bundle, f.name);
match field.ty() {
CanonicalType::Bundle(_) => {
path_prefix.push(f.name);
Self::get_fields(
fields,
path_prefix,
bundle_from_canonical(field),
get_field,
interface_from_bundle,
bundle_from_canonical,
);
path_prefix.pop();
}
CanonicalType::PhantomConst(_) => continue,
_ => panic!(
"field type must be either a MemoryInterfacesBundle or a PhantomConst: {}",
MemoryInterfacesBundleFieldPath::from_slice(path_prefix),
),
}
}
}
}
}
/// `Self` is a bundle where either:
/// * `Self` is a [`MemoryInterface<PhantomConst<MemoryInterfaceConfig>>`]
/// * each field is a [`MemoryInterfacesBundle`] or a [`PhantomConst`] and none of the fields have `#[hdl(flip)]`
pub trait MemoryInterfacesBundle: BundleType {
#[track_caller]
fn properties_valueless(
self,
mut path_prefix: Vec<Interned<str>>,
) -> MemoryInterfacesBundleProperties<
Valueless<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
> {
let mut fields = Vec::new();
MemoryInterfacesBundleProperties::get_fields(
&mut fields,
&mut path_prefix,
Valueless::new(Bundle::new(self.fields())),
|&bundle, name| {
Valueless::new(
bundle
.ty()
.field_by_name(name)
.expect("field is known to exist")
.ty,
)
},
|bundle| Valueless::new(MemoryInterface::from_canonical(bundle.ty().canonical())),
|canonical| Valueless::new(Bundle::from_canonical(canonical.ty())),
);
MemoryInterfacesBundleProperties::from_fields(fields)
}
#[track_caller]
fn properties_expr(
this: impl ToExpr<Type = Self>,
mut path_prefix: Vec<Interned<str>>,
) -> MemoryInterfacesBundleProperties<Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>>
{
let mut fields = Vec::new();
MemoryInterfacesBundleProperties::get_fields(
&mut fields,
&mut path_prefix,
Expr::as_bundle(this.to_expr()),
|&bundle, name| Expr::field(bundle, &name),
Expr::from_bundle,
Expr::from_canonical,
);
MemoryInterfacesBundleProperties::from_fields(fields)
}
}
fn memory_interface_resize_adaptor_input_config(
output_config: PhantomConst<MemoryInterfaceConfig>,
input_log2_bus_width_in_bytes: u8,
max_input_queue_capacity: Option<NonZeroUsize>,
) -> PhantomConst<MemoryInterfaceConfig> {
let output_config = *output_config.get();
let MemoryInterfaceConfig {
log2_bus_width_in_bytes: _,
queue_capacity: output_queue_capacity,
op_id_width,
address_range,
} = output_config;
let mut input_config = MemoryInterfaceConfig {
log2_bus_width_in_bytes: input_log2_bus_width_in_bytes,
queue_capacity: output_queue_capacity,
op_id_width,
address_range,
};
if let Some(ratio) =
NonZeroUsize::new(input_config.bus_width_in_bytes() / output_config.bus_width_in_bytes())
{
input_config.queue_capacity = output_queue_capacity.div_ceil(ratio)
}
if let Some(max_input_queue_capacity) = max_input_queue_capacity
&& max_input_queue_capacity < input_config.queue_capacity
{
input_config.queue_capacity = max_input_queue_capacity;
}
PhantomConst::new_sized(input_config)
}
#[hdl_module]
fn memory_interface_resize_adaptor(
input_config: PhantomConst<MemoryInterfaceConfig>,
output_config: PhantomConst<MemoryInterfaceConfig>,
) {
let expected_input_config = memory_interface_resize_adaptor_input_config(
output_config,
input_config.get().log2_bus_width_in_bytes,
Some(input_config.get().queue_capacity),
);
assert_eq!(input_config, expected_input_config);
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[input_config]);
#[hdl]
let output_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[output_config]);
let log2_split_count = output_config
.get()
.log2_bus_width_in_bytes
.saturating_sub(input_config.get().log2_bus_width_in_bytes);
let split_count = 1usize.strict_shl(log2_split_count.into());
connect(
input_interface.next_op_ids,
input_interface.ty().next_op_ids.HdlNone(),
);
#[hdl]
let input_start_active_split_chunks = wire(Array[Bool][split_count]);
// default to [true, false, false, false, ...]
connect(input_start_active_split_chunks, repeat(false, split_count));
connect(input_start_active_split_chunks[0], true);
#[hdl]
if let HdlSome(start) = input_interface.start.data {
#[hdl]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id,
config,
} = start;
// if rw_mask is all false, leave input_start_active_split_chunks as [true, false, false, false, ...] so we make at least one transaction on output_interface
#[hdl]
if rw_mask.cast_to_bits().any_one_bits() {
// otherwise, split rw_mask into chunks of size at most output_config.get().bus_width_in_bytes()
for (split_chunk, rw_mask_chunk) in input_start_active_split_chunks
.into_iter()
.zip(rw_mask.chunks(output_config.get().bus_width_in_bytes()))
{
connect(split_chunk, rw_mask_chunk.cast_to_bits().any_one_bits());
}
}
}
#[hdl(no_static)]
struct Entry<C: PhantomConstGet<MemoryInterfaceConfig>, SplitCount: Size> {
active_split_chunks: ArrayType<Bool, SplitCount>,
config: C,
}
#[hdl]
let queue = instance(queue(
Entry[input_config][split_count],
input_config.get().queue_capacity,
false,
false,
));
connect(queue.cd, cd);
#[hdl(no_static)]
struct IncompleteStartState<C: PhantomConstGet<MemoryInterfaceConfig>, SplitCount: Size> {
start: MemoryOperationStart<C>,
split_chunks_left: ArrayType<Bool, SplitCount>,
}
#[hdl]
let incomplete_start_state_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[IncompleteStartState[input_config][split_count]].HdlNone());
connect(
input_interface.start.ready,
HdlOption::is_none(incomplete_start_state_reg) & queue.inp.ready,
);
#[hdl(no_static)]
struct IncompleteFinishState<C: PhantomConstGet<MemoryInterfaceConfig>, SplitCount: Size> {
start: MemoryOperationFinish<C>,
split_chunks_left: ArrayType<Bool, SplitCount>,
}
#[hdl]
let incomplete_finish_state_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[IncompleteFinishState[input_config][split_count]].HdlNone());
todo!();
}
#[hdl_module]
pub fn memory_interface_adaptor<OutputInterfaces: Type + MemoryInterfacesBundle>(
input_interface_config: PhantomConst<MemoryInterfaceConfig>,
output_interfaces_ty: OutputInterfaces,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[input_interface_config]);
#[hdl]
let output_interfaces: OutputInterfaces = m.output(output_interfaces_ty);
let mut output_interfaces = MemoryInterfacesBundle::properties_expr(
output_interfaces,
vec!["output_interfaces".intern()],
);
if let Some(index) = output_interfaces.first_full_interface() {
assert!(
index == output_interfaces.fields().len() - 1,
"all fields after {path} will never be used since {path} comes before and handles all addresses",
path = output_interfaces.fields[index].path,
);
} else {
#[hdl]
let always_error = instance(memory_interface_always_error(PhantomConst::new_sized(
memory_interface_always_error_config(*input_interface_config.get()),
)));
let mut fields = output_interfaces.into_fields();
fields.push(MemoryInterfacesBundleField {
path: MemoryInterfacesBundleFieldPath::from_slice(&[
"always_error".intern(),
"input_interface".intern(),
]),
value: always_error.input_interface,
});
output_interfaces = MemoryInterfacesBundleProperties::from_fields(fields);
}
fn visit_selected_output_interface(
output_interfaces: &MemoryInterfacesBundleProperties<
Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
>,
selected_output_interface: impl ToExpr<Type = UIntInRangeType<ConstUsize<0>, DynSize>>,
mut visit_selected: impl FnMut(
usize,
MemoryInterfacesBundleField<Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>>,
),
) {
let selected_output_interface = selected_output_interface.to_expr();
let mut else_scope = None;
for (i, &interface) in output_interfaces.fields().iter().enumerate() {
if i == output_interfaces.fields().len() - 1 {
// just else, no else if
visit_selected(i, interface);
} else {
let if_scope = fayalite::module::if_(selected_output_interface.cmp_eq(i));
visit_selected(i, interface);
else_scope = Some(if_scope.else_());
}
}
drop(else_scope);
}
#[hdl(no_static)]
struct Op<C: PhantomConstGet<MemoryInterfaceConfig>, OutputInterfaceCount: Size> {
start: MemoryOperationStart<C>,
output_interface_index: UIntInRangeType<ConstUsize<0>, OutputInterfaceCount>,
started_bytes: UIntInRangeInclusiveType<ConstUsize<0>, MemoryInterfaceBusWidthInBytes<C>>,
finished_bytes: UIntInRangeInclusiveType<ConstUsize<0>, MemoryInterfaceBusWidthInBytes<C>>,
}
let op_ty = Op[input_interface_config][output_interfaces.fields().len()];
let option_op_ty = HdlOption[op_ty];
#[hdl]
let not_started_op_queue = instance(queue(
op_ty,
const { NonZeroUsize::new(2).unwrap() },
false,
true,
));
connect(not_started_op_queue.cd, cd);
connect(
not_started_op_queue.inp.data,
HdlOption::map(input_interface.start.data, |start| {
#[hdl]
let output_interface_index = wire(op_ty.output_interface_index);
connect(
output_interface_index,
(output_interfaces.fields().len() - 1).cast_to(output_interface_index.ty()),
);
#[hdl]
Op::<_, _> {
start,
output_interface_index,
started_bytes: 0u8.cast_to(op_ty.started_bytes),
finished_bytes: 0u8.cast_to(op_ty.finished_bytes),
}
}),
);
connect(input_interface.start.ready, not_started_op_queue.inp.ready);
#[hdl]
let starting_op_reg = reg_builder().clock_domain(cd).reset(option_op_ty.HdlNone());
#[hdl]
let starting_op_in = wire(option_op_ty);
#[hdl]
if let HdlSome(_) = starting_op_reg {
connect(starting_op_in, starting_op_reg);
connect(not_started_op_queue.out.ready, false);
} else {
connect(starting_op_in, not_started_op_queue.out.data);
connect(not_started_op_queue.out.ready, true);
}
for output_interface in output_interfaces.fields() {
connect(
output_interface.value.start.data,
output_interface.value.ty().start.data.HdlNone(),
);
}
#[hdl]
if let HdlSome(starting_op_in) = starting_op_in {
#[hdl]
let Op::<_, _> {
start,
output_interface_index,
started_bytes,
finished_bytes,
} = starting_op_in;
#[hdl]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id,
config,
} = start;
visit_selected_output_interface(
&output_interfaces,
output_interface_index,
|_, output_interface| {
connect(
output_interface.value.start.data,
#[hdl]
MemoryOperationStart::<_> { kind },
);
#[hdl]
if output_interface.value.start {
todo();
}
todo!();
},
);
} else {
connect(starting_op_reg, option_op_ty.HdlNone());
}
#[hdl]
let started_op_queue = instance(queue(
op_ty,
input_interface_config.get().queue_capacity,
false,
false,
));
#[hdl]
let finishing_op_reg = reg_builder().clock_domain(cd).reset(option_op_ty.HdlNone());
todo!()
}