forked from libre-chip/cpu
WIP adding memory_interface_adaptor
This commit is contained in:
parent
3080ea4ce2
commit
022ecea3d4
1 changed files with 638 additions and 2 deletions
|
|
@ -2,8 +2,18 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{config::CpuConfig, next_pc::FETCH_BLOCK_ID_WIDTH, util::array_vec::ArrayVec};
|
||||
use fayalite::{prelude::*, util::ready_valid::ReadyValid};
|
||||
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
|
||||
use fayalite::{
|
||||
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;
|
||||
|
||||
|
|
@ -180,6 +190,7 @@ pub struct MemoryOperationFinish<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
|||
#[hdl(no_static)]
|
||||
pub struct MemoryInterface<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
||||
pub start: ReadyValid<MemoryOperationStart<C>>,
|
||||
/// started operations must finish in the same order they started in
|
||||
#[hdl(flip)]
|
||||
pub finish: ReadyValid<MemoryOperationFinish<C>>,
|
||||
/// for debugging
|
||||
|
|
@ -187,3 +198,628 @@ pub struct MemoryInterface<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
|||
pub next_op_ids: HdlOption<ArrayVec<MemoryInterfaceOpId<C>, MemoryInterfaceQueueCapacity<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());
|
||||
let log2_split_count = usize::from(log2_split_count);
|
||||
|
||||
connect(
|
||||
input_interface.next_op_ids,
|
||||
input_interface.ty().next_op_ids.HdlNone(),
|
||||
);
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct Entry<C: PhantomConstGet<MemoryInterfaceConfig>, SplitCount: Size> {
|
||||
addr: UInt<64>,
|
||||
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>,
|
||||
active_split_chunks: ArrayType<Bool, SplitCount>,
|
||||
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, queue.inp.ready);
|
||||
connect(
|
||||
output_interface.start.data,
|
||||
output_interface.ty().start.data.HdlNone(),
|
||||
);
|
||||
|
||||
#[hdl]
|
||||
let prev_incomplete_start_state = wire(incomplete_start_state_reg.ty());
|
||||
|
||||
#[hdl]
|
||||
if let HdlSome(start) = input_interface.start.data {
|
||||
#[hdl]
|
||||
let start_active_split_chunks = wire(Array[Bool][split_count]);
|
||||
|
||||
#[hdl]
|
||||
if start.rw_mask.cast_to_bits().all_zero_bits() {
|
||||
// if rw_mask is all false, set start_active_split_chunks to [true, false, false, false, ...] so we make at least one transaction on output_interface
|
||||
connect(start_active_split_chunks, repeat(false, split_count));
|
||||
connect(start_active_split_chunks[0], true);
|
||||
} else {
|
||||
// otherwise, split rw_mask into chunks of size at most output_config.get().bus_width_in_bytes()
|
||||
for (split_chunk, rw_mask_chunk) in start_active_split_chunks.into_iter().zip(
|
||||
start
|
||||
.rw_mask
|
||||
.chunks(output_config.get().bus_width_in_bytes()),
|
||||
) {
|
||||
connect(split_chunk, rw_mask_chunk.cast_to_bits().any_one_bits());
|
||||
}
|
||||
}
|
||||
|
||||
connect(
|
||||
prev_incomplete_start_state,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
IncompleteStartState::<_, _> {
|
||||
start,
|
||||
active_split_chunks: start_active_split_chunks,
|
||||
split_chunks_left: start_active_split_chunks,
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
connect(
|
||||
prev_incomplete_start_state,
|
||||
prev_incomplete_start_state.ty().HdlNone(),
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
if let HdlSome(_) = incomplete_start_state_reg {
|
||||
connect(input_interface.start.ready, false);
|
||||
connect(prev_incomplete_start_state, incomplete_start_state_reg);
|
||||
}
|
||||
|
||||
connect(
|
||||
incomplete_start_state_reg,
|
||||
incomplete_start_state_reg.ty().HdlNone(),
|
||||
);
|
||||
|
||||
#[hdl]
|
||||
if let HdlSome(incomplete_start_state) = prev_incomplete_start_state {
|
||||
#[hdl]
|
||||
let IncompleteStartState::<_, _> {
|
||||
start,
|
||||
active_split_chunks,
|
||||
split_chunks_left,
|
||||
} = incomplete_start_state;
|
||||
// we know at least one element of split_chunks_left is true
|
||||
#[hdl]
|
||||
let start_split_chunk_selected = wire(UInt[log2_split_count]);
|
||||
|
||||
// use last-connect semantics to get the index of the first element of split_chunks_left that's true
|
||||
for (i, split_chunk_left) in split_chunks_left.into_iter().enumerate().rev() {
|
||||
if i == split_count - 1 {
|
||||
connect_any(start_split_chunk_selected, i);
|
||||
continue;
|
||||
}
|
||||
#[hdl]
|
||||
if split_chunk_left {
|
||||
connect_any(start_split_chunk_selected, i);
|
||||
}
|
||||
}
|
||||
|
||||
let output_data_shift = todo!();
|
||||
|
||||
connect(
|
||||
output_interface.start.data,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
MemoryOperationStart::<_> {
|
||||
kind: start.kind,
|
||||
addr: start.addr
|
||||
+ start_split_chunk_selected * todo!("chunk size")
|
||||
+ todo!("mask addr"),
|
||||
write_data,
|
||||
rw_mask,
|
||||
op_id,
|
||||
config,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct IncompleteFinishState<C: PhantomConstGet<MemoryInterfaceConfig>, SplitCount: Size> {
|
||||
addr: UInt<64>,
|
||||
finish: 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!()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue