From 3f5dd61e46ec673ce24b0560cc8de1490df846db Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 17 Oct 2025 05:52:56 -0700 Subject: [PATCH] WIP adding Platform --- crates/fayalite-proc-macros-impl/src/lib.rs | 1 + .../fayalite-proc-macros-impl/src/module.rs | 36 +- .../src/module/transform_body.rs | 156 ++- crates/fayalite/src/lib.rs | 2 +- crates/fayalite/src/module.rs | 38 + crates/fayalite/src/platform.rs | 1146 +++++++++++++++++ crates/fayalite/src/target.rs | 202 --- crates/fayalite/tests/module.rs | 53 + crates/fayalite/tests/ui/module.rs | 16 + crates/fayalite/tests/ui/module.stderr | 36 +- 10 files changed, 1468 insertions(+), 218 deletions(-) create mode 100644 crates/fayalite/src/platform.rs delete mode 100644 crates/fayalite/src/target.rs diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index def91eb..13336fa 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -66,6 +66,7 @@ mod kw { } custom_keyword!(__evaluated_cfgs); + custom_keyword!(add_platform_io); custom_keyword!(all); custom_keyword!(any); custom_keyword!(cfg); diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index c7caa16..5628ff9 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -4,7 +4,7 @@ use crate::{ Errors, HdlAttr, PairsIterExt, hdl_type_common::{ParsedGenerics, SplitForImpl}, kw, - module::transform_body::{HdlLet, HdlLetKindIO}, + module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO}, options, }; use proc_macro2::TokenStream; @@ -39,7 +39,7 @@ pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Res if name == "m" { Err(Error::new_spanned( name, - "name conflicts with implicit `m: &mut ModuleBuilder<_>`", + "name conflicts with implicit `m: &ModuleBuilder`", )) } else { Ok(()) @@ -67,7 +67,7 @@ struct ModuleFnModule { vis: Visibility, sig: Signature, block: Box, - struct_generics: ParsedGenerics, + struct_generics: Option, the_struct: TokenStream, } @@ -290,7 +290,7 @@ impl ModuleFn { paren_token, body, } => { - debug_assert!(io.is_empty()); + debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty())); return Ok(Self(ModuleFnImpl::Fn { attrs, config_options: HdlAttr { @@ -322,6 +322,21 @@ impl ModuleFn { body, }, }; + let io = match io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => io, + ModuleIOOrAddPlatformIO::AddPlatformIO => { + return Ok(Self(ModuleFnImpl::Module(ModuleFnModule { + attrs, + config_options, + module_kind: module_kind.unwrap(), + vis, + sig, + block, + struct_generics: None, + the_struct: TokenStream::new(), + }))); + } + }; let (_struct_impl_generics, _struct_type_generics, struct_where_clause) = struct_generics.split_for_impl(); let struct_where_clause: Option = parse_quote! { #struct_where_clause }; @@ -364,7 +379,7 @@ impl ModuleFn { vis, sig, block, - struct_generics, + struct_generics: Some(struct_generics), the_struct, }))) } @@ -433,9 +448,14 @@ impl ModuleFn { ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal }, }; let fn_name = &outer_sig.ident; - let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = - struct_generics.split_for_impl(); - let struct_ty = quote! {#fn_name #struct_type_generics}; + let struct_ty = match struct_generics { + Some(struct_generics) => { + let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = + struct_generics.split_for_impl(); + quote! {#fn_name #struct_type_generics} + } + None => quote! {::fayalite::bundle::Bundle}, + }; body_sig.ident = parse_quote! {__body}; body_sig .inputs diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 6859f69..7b41f5e 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -39,6 +39,7 @@ options! { pub(crate) enum LetFnKind { Input(input), Output(output), + AddPlatformIO(add_platform_io), Instance(instance), RegBuilder(reg_builder), Wire(wire), @@ -216,6 +217,49 @@ impl HdlLetKindToTokens for HdlLetKindInstance { } } +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindAddPlatformIO { + pub(crate) m: kw::m, + pub(crate) dot_token: Token![.], + pub(crate) add_platform_io: kw::add_platform_io, + pub(crate) paren: Paren, + pub(crate) platform_io_builder: Box, +} + +impl ParseTypes for HdlLetKindAddPlatformIO { + fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result { + Ok(input.clone()) + } +} + +impl_fold! { + struct HdlLetKindAddPlatformIO<> { + m: kw::m, + dot_token: Token![.], + add_platform_io: kw::add_platform_io, + paren: Paren, + platform_io_builder: Box, + } +} + +impl HdlLetKindToTokens for HdlLetKindAddPlatformIO { + fn ty_to_tokens(&self, _tokens: &mut TokenStream) {} + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { + m, + dot_token, + add_platform_io, + paren, + platform_io_builder, + } = self; + m.to_tokens(tokens); + dot_token.to_tokens(tokens); + add_platform_io.to_tokens(tokens); + paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens)); + } +} + #[derive(Clone, Debug)] pub(crate) struct RegBuilderClockDomain { pub(crate) dot_token: Token![.], @@ -711,6 +755,7 @@ impl HdlLetKindMemory { #[derive(Clone, Debug)] pub(crate) enum HdlLetKind { IO(HdlLetKindIO), + AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -721,6 +766,7 @@ pub(crate) enum HdlLetKind { impl_fold! { enum HdlLetKind { IO(HdlLetKindIO), + AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -736,6 +782,9 @@ impl, I> ParseTypes> for HdlLetKind { ) -> Result { match input { HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO), + HdlLetKind::AddPlatformIO(input) => { + ParseTypes::parse_types(input, parser).map(HdlLetKind::AddPlatformIO) + } HdlLetKind::Incomplete(input) => { ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete) } @@ -861,6 +910,23 @@ impl HdlLetKindParse for HdlLetKind { ModuleIOKind::Output(output), ) .map(Self::IO), + LetFnKind::AddPlatformIO((add_platform_io,)) => { + if let Some(parsed_ty) = parsed_ty { + return Err(Error::new_spanned( + parsed_ty.1, + "type annotation not allowed for instance", + )); + } + let (m, dot_token) = unwrap_m_dot(m_dot, kind)?; + let paren_contents; + Ok(Self::AddPlatformIO(HdlLetKindAddPlatformIO { + m, + dot_token, + add_platform_io, + paren: parenthesized!(paren_contents in input), + platform_io_builder: paren_contents.call(parse_single_fn_arg)?, + })) + } LetFnKind::Instance((instance,)) => { if let Some(parsed_ty) = parsed_ty { return Err(Error::new_spanned( @@ -936,6 +1002,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn ty_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.ty_to_tokens(tokens), + HdlLetKind::AddPlatformIO(v) => v.ty_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens), HdlLetKind::Instance(v) => v.ty_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens), @@ -947,6 +1014,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn expr_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.expr_to_tokens(tokens), + HdlLetKind::AddPlatformIO(v) => v.expr_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens), HdlLetKind::Instance(v) => v.expr_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens), @@ -1149,7 +1217,7 @@ impl ToTokens for ImplicitName { struct Visitor<'a> { module_kind: Option, errors: Errors, - io: Vec, + io: ModuleIOOrAddPlatformIO, block_depth: usize, parsed_generics: &'a ParsedGenerics, } @@ -1289,7 +1357,81 @@ impl Visitor<'_> { }), semi_token: hdl_let.semi_token, }; - self.io.push(hdl_let); + match &mut self.io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => io.push(hdl_let), + ModuleIOOrAddPlatformIO::AddPlatformIO => { + self.errors.error( + kind, + "can't have other inputs/outputs in a module using m.add_platform_io()", + ); + } + } + let_stmt + } + fn process_hdl_let_add_platform_io( + &mut self, + hdl_let: HdlLet, + ) -> Local { + let HdlLet { + mut attrs, + hdl_attr: _, + let_token, + mut_token, + ref name, + eq_token, + kind: + HdlLetKindAddPlatformIO { + m, + dot_token, + add_platform_io, + paren, + platform_io_builder, + }, + semi_token, + } = hdl_let; + let mut expr = quote! {#m #dot_token #add_platform_io}; + paren.surround(&mut expr, |expr| { + let name_str = ImplicitName { + name, + span: name.span(), + }; + quote_spanned! {name.span()=> + #name_str, #platform_io_builder + } + .to_tokens(expr); + }); + self.require_module(add_platform_io); + attrs.push(parse_quote_spanned! {let_token.span=> + #[allow(unused_variables)] + }); + let let_stmt = Local { + attrs, + let_token, + pat: parse_quote! { #mut_token #name }, + init: Some(LocalInit { + eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token, + }; + match &mut self.io { + ModuleIOOrAddPlatformIO::ModuleIO(io) => { + for io in io { + self.errors.error( + io.kind.kind, + "can't have other inputs/outputs in a module using m.add_platform_io()", + ); + } + } + ModuleIOOrAddPlatformIO::AddPlatformIO => { + self.errors.error( + add_platform_io, + "can't use m.add_platform_io() more than once in a single module", + ); + } + } + self.io = ModuleIOOrAddPlatformIO::AddPlatformIO; let_stmt } fn process_hdl_let_instance(&mut self, hdl_let: HdlLet) -> Local { @@ -1510,6 +1652,7 @@ impl Visitor<'_> { } the_match! { IO => process_hdl_let_io, + AddPlatformIO => process_hdl_let_add_platform_io, Incomplete => process_hdl_let_incomplete, Instance => process_hdl_let_instance, RegBuilder => process_hdl_let_reg_builder, @@ -1753,15 +1896,20 @@ impl Fold for Visitor<'_> { } } +pub(crate) enum ModuleIOOrAddPlatformIO { + ModuleIO(Vec), + AddPlatformIO, +} + pub(crate) fn transform_body( module_kind: Option, mut body: Box, parsed_generics: &ParsedGenerics, -) -> syn::Result<(Box, Vec)> { +) -> syn::Result<(Box, ModuleIOOrAddPlatformIO)> { let mut visitor = Visitor { module_kind, errors: Errors::new(), - io: vec![], + io: ModuleIOOrAddPlatformIO::ModuleIO(vec![]), block_depth: 0, parsed_generics, }; diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index e5323f3..67f2fe5 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -99,12 +99,12 @@ pub mod intern; pub mod memory; pub mod module; pub mod phantom_const; +pub mod platform; pub mod prelude; pub mod reg; pub mod reset; pub mod sim; pub mod source_location; -pub mod target; pub mod testing; pub mod ty; pub mod util; diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index aa7a673..6527043 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -19,6 +19,7 @@ use crate::{ int::{Bool, DynSize, Size}, intern::{Intern, Interned}, memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, + platform::PlatformIOBuilder, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, sim::{ExternModuleSimGenerator, ExternModuleSimulation}, @@ -2119,6 +2120,27 @@ impl ModuleBuilder { self.output_with_loc(implicit_name.0, SourceLocation::caller(), ty) } #[track_caller] + pub fn add_platform_io_with_loc( + &self, + name: &str, + source_location: SourceLocation, + platform_io_builder: PlatformIOBuilder<'_>, + ) -> Expr { + platform_io_builder.add_platform_io(name, source_location, self) + } + #[track_caller] + pub fn add_platform_io( + &self, + implicit_name: ImplicitName<'_>, + platform_io_builder: PlatformIOBuilder<'_>, + ) -> Expr { + self.add_platform_io_with_loc( + implicit_name.0, + SourceLocation::caller(), + platform_io_builder, + ) + } + #[track_caller] pub fn run( name: &str, module_kind: ModuleKind, @@ -2743,6 +2765,22 @@ impl ModuleIO { source_location, } } + pub fn from_canonical(canonical_module_io: ModuleIO) -> Self { + let ModuleIO { + containing_module_name, + bundle_field, + id, + ty, + source_location, + } = canonical_module_io; + Self { + containing_module_name, + bundle_field, + id, + ty: T::from_canonical(ty), + source_location, + } + } pub fn bundle_field(&self) -> BundleField { self.bundle_field } diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs new file mode 100644 index 0000000..903f9cc --- /dev/null +++ b/crates/fayalite/src/platform.rs @@ -0,0 +1,1146 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::{Bundle, BundleField, BundleType}, + expr::{Expr, ExprEnum}, + intern::{Intern, Interned}, + module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc}, + source_location::SourceLocation, + ty::{CanonicalType, Type}, + util::HashMap, +}; +use std::{ + any::{Any, TypeId}, + cmp::Ordering, + collections::{BTreeMap, BTreeSet}, + convert::Infallible, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, + mem, + sync::{Arc, Mutex, MutexGuard, OnceLock}, +}; + +trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug { + fn as_any(&self) -> &dyn Any; + fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); + fn new_peripherals_dyn<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>); + fn source_location_dyn(&self) -> SourceLocation; + #[track_caller] + fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals); +} + +impl DynPlatformTrait for T { + fn as_any(&self) -> &dyn Any { + self + } + + fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool { + other + .as_any() + .downcast_ref::() + .is_some_and(|other| self == other) + } + + fn hash_dyn(&self, mut state: &mut dyn Hasher) { + self.hash(&mut state); + } + + fn new_peripherals_dyn<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>) { + let (peripherals, finished) = self.new_peripherals(builder_factory); + (DynPeripherals(Box::new(peripherals)), finished) + } + + fn source_location_dyn(&self) -> SourceLocation { + self.source_location() + } + + #[track_caller] + fn add_peripherals_in_wrapper_module_dyn( + &self, + m: &ModuleBuilder, + peripherals: DynPeripherals, + ) { + if DynPeripheralsTrait::type_id(&*peripherals.0) != TypeId::of::() { + panic!( + "wrong DynPeripherals value type, expected type: <{}>::Peripherals, got value:\n{peripherals:?}", + std::any::type_name::() + ); + } + let Ok(peripherals) = peripherals.0.into_box_any().downcast() else { + unreachable!(); + }; + self.add_peripherals_in_wrapper_module(m, *peripherals) + } +} + +#[derive(Clone)] +pub struct DynPlatform(Arc); + +impl PartialEq for DynPlatform { + fn eq(&self, other: &Self) -> bool { + DynPlatformTrait::eq_dyn(&*self.0, &*other.0) + } +} + +impl Eq for DynPlatform {} + +impl Hash for DynPlatform { + fn hash(&self, state: &mut H) { + DynPlatformTrait::hash_dyn(&*self.0, state); + } +} + +impl fmt::Debug for DynPlatform { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl DynPlatform { + pub fn new(platform: T) -> Self { + if let Some(platform) = ::downcast_ref::(&platform) { + platform.clone() + } else { + Self(Arc::new(platform)) + } + } +} + +trait DynPeripheralsTrait: fmt::Debug + 'static + Send + Sync { + fn type_id(&self) -> TypeId; + fn into_box_any(self: Box) -> Box; + fn append_peripherals_dyn<'a>( + &'a self, + peripherals: &mut Vec>, + ); +} + +impl DynPeripheralsTrait for T { + fn type_id(&self) -> TypeId { + TypeId::of::() + } + fn into_box_any(self: Box) -> Box { + self + } + fn append_peripherals_dyn<'a>( + &'a self, + peripherals: &mut Vec>, + ) { + self.append_peripherals(peripherals); + } +} + +pub struct DynPeripherals(Box); + +impl fmt::Debug for DynPeripherals { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Peripherals for DynPeripherals { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + self.0.append_peripherals_dyn(peripherals); + } +} + +impl Platform for DynPlatform { + type Peripherals = DynPeripherals; + fn new_peripherals<'a>( + &self, + builder_factory: PeripheralsBuilderFactory<'a>, + ) -> (Self::Peripherals, PeripheralsBuilderFinished<'a>) { + DynPlatformTrait::new_peripherals_dyn(&*self.0, builder_factory) + } + fn source_location(&self) -> SourceLocation { + DynPlatformTrait::source_location_dyn(&*self.0) + } + #[track_caller] + fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { + DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals); + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct PeripheralId { + pub name: Interned, +} + +impl PartialOrd for PeripheralId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PeripheralId { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + Ordering::Equal + } else { + let Self { name } = self; + str::cmp(name, &other.name) + } + } +} + +struct CollectingPeripherals { + conflicts_graph: BTreeMap>, + on_use_state: PeripheralsOnUseState, +} + +pub trait PeripheralsOnUseSharedState: 'static + Send + fmt::Debug { + fn as_any(&mut self) -> &mut dyn Any; +} + +impl PeripheralsOnUseSharedState for T { + fn as_any(&mut self) -> &mut dyn Any { + self + } +} + +type DynPeripheralsOnUse = dyn FnOnce( + &mut dyn PeripheralsOnUseSharedState, + PeripheralRef<'_, CanonicalType>, + Expr, + ) + Send + + 'static; + +struct PeripheralsOnUseState { + shared_state: Box, + main_module_io_fields: Vec, + main_module_io_wires: Vec>, + on_use_functions: BTreeMap>, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum PeripheralAvailability { + Available, + Used, + ConflictsWithUsed(PeripheralId), +} + +impl PeripheralAvailability { + pub fn is_available(self) -> bool { + matches!(self, Self::Available) + } + pub fn is_used(&self) -> bool { + matches!(self, Self::Used) + } +} + +struct PeripheralsStateBuildingModule { + conflicts_graph: Interned>>>, + availabilities: Mutex>, + on_use_state: Mutex, +} + +impl From for PeripheralsStateBuildingModule { + fn from(value: CollectingPeripherals) -> Self { + let CollectingPeripherals { + conflicts_graph, + on_use_state, + } = value; + let conflicts_graph = BTreeMap::from_iter( + conflicts_graph + .into_iter() + .map(|(k, v)| (k, v.intern_sized())), + ) + .intern_sized(); + Self { + conflicts_graph, + availabilities: Mutex::new( + on_use_state + .on_use_functions + .keys() + .map(|&id| (id, PeripheralAvailability::Available)) + .collect(), + ), + on_use_state: Mutex::new(on_use_state), + } + } +} + +struct PeripheralsStateBuildingWrapperModule { + output_module_io: ModuleIO, + output: Option>, +} + +enum PeripheralsStateEnum { + Initial, + CollectingPeripherals(CollectingPeripherals), + BuildingModule, + BuildingWrapperModule(PeripheralsStateBuildingWrapperModule), +} + +struct PeripheralsState { + will_build_wrapper: bool, + state: Mutex, + building_module: OnceLock, +} + +impl PeripheralsState { + fn finish_collecting_peripherals(&self) { + let mut state = self.state.lock().expect("shouldn't be poison"); + let building_module = match mem::replace(&mut *state, PeripheralsStateEnum::BuildingModule) + { + PeripheralsStateEnum::CollectingPeripherals(v) => v.into(), + PeripheralsStateEnum::Initial + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }; + self.building_module.get_or_init(|| building_module); + } +} + +struct PeripheralCommon { + type_id: TypeId, + id: PeripheralId, + is_input: bool, + peripherals_state: Arc, +} + +#[must_use] +pub struct PeripheralsBuilderFactory<'a> { + peripherals_state: Arc, + _phantom: PhantomData &'a ()>, +} + +impl fmt::Debug for PeripheralsBuilderFactory<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PeripheralsBuilderFactory") + .finish_non_exhaustive() + } +} + +impl PeripheralsBuilderFactory<'_> { + fn new(will_build_wrapper: bool) -> Self { + Self { + peripherals_state: Arc::new(PeripheralsState { + will_build_wrapper, + state: Mutex::new(PeripheralsStateEnum::Initial), + building_module: OnceLock::new(), + }), + _phantom: PhantomData, + } + } +} + +impl<'a> PeripheralsBuilderFactory<'a> { + pub fn builder(self) -> PeripheralsBuilder<'a> { + self.builder_with_default_state() + } + pub fn builder_with_default_state( + self, + ) -> PeripheralsBuilder<'a, S> { + self.builder_with_boxed_state(Box::default()) + } + pub fn builder_with_state( + self, + shared_state: S, + ) -> PeripheralsBuilder<'a, S> { + self.builder_with_boxed_state(Box::new(shared_state)) + } + pub fn builder_with_boxed_state( + self, + shared_state: Box, + ) -> PeripheralsBuilder<'a, S> { + let Self { + peripherals_state, + _phantom: PhantomData, + } = self; + match *peripherals_state.state.lock().expect("shouldn't be poison") { + ref mut state @ PeripheralsStateEnum::Initial => { + *state = PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { + conflicts_graph: BTreeMap::new(), + on_use_state: PeripheralsOnUseState { + shared_state, + main_module_io_fields: Vec::new(), + main_module_io_wires: Vec::new(), + on_use_functions: BTreeMap::new(), + }, + }) + } + PeripheralsStateEnum::CollectingPeripherals(_) + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + } + PeripheralsBuilder { + peripherals_state, + _phantom: PhantomData, + } + } +} + +#[must_use] +pub struct PeripheralsBuilder<'a, S: PeripheralsOnUseSharedState = ()> { + peripherals_state: Arc, + _phantom: PhantomData<(Arc, fn(&'a ()) -> &'a ())>, +} + +#[must_use] +pub struct PeripheralsBuilderFinished<'a> { + _private: PhantomData &'a ()>, +} + +impl fmt::Debug for PeripheralsBuilderFinished<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PeripheralsBuilderFinished") + .finish_non_exhaustive() + } +} + +impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { + fn state_enum(&mut self) -> MutexGuard<'_, PeripheralsStateEnum> { + self.peripherals_state + .state + .lock() + .expect("shouldn't be poison") + } + #[track_caller] + pub fn peripheral( + &mut self, + id_name: impl AsRef, + is_input: bool, + ty: T, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, is_input, ty, |_, _, _| {}) + } + #[track_caller] + pub fn peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + is_input: bool, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + let mut state_enum = self.state_enum(); + let PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { + conflicts_graph, + on_use_state: + PeripheralsOnUseState { + shared_state: _, + main_module_io_fields: _, + main_module_io_wires: _, + on_use_functions, + }, + }) = &mut *state_enum + else { + unreachable!(); + }; + let id = PeripheralId { + name: id_name.as_ref().intern(), + }; + let std::collections::btree_map::Entry::Vacant(entry) = conflicts_graph.entry(id) else { + drop(state_enum); // don't poison + panic!("duplicate peripheral: {id:?}"); + }; + entry.insert(BTreeSet::new()); + on_use_functions.insert( + id, + Box::new(move |state, peripheral_ref, wire| { + on_use( + state + .as_any() + .downcast_mut() + .expect("known to be correct type"), + PeripheralRef::from_canonical(peripheral_ref), + Expr::from_canonical(wire), + ) + }), + ); + drop(state_enum); + Peripheral { + ty, + common: PeripheralCommon { + type_id: TypeId::of::(), + id, + is_input, + peripherals_state: self.peripherals_state.clone(), + }, + } + } + #[track_caller] + pub fn input_peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, true, ty, on_use) + } + #[track_caller] + pub fn output_peripheral_with_on_use( + &mut self, + id_name: impl AsRef, + ty: T, + on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, + ) -> Peripheral { + self.peripheral_with_on_use(id_name, false, ty, on_use) + } + #[track_caller] + pub fn input_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { + self.peripheral(id_name, true, ty) + } + #[track_caller] + pub fn output_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { + self.peripheral(id_name, false, ty) + } + #[track_caller] + pub fn add_conflicts(&mut self, conflicts: impl AsRef<[PeripheralId]>) { + let mut state_enum = self.state_enum(); + let PeripheralsStateEnum::CollectingPeripherals(collecting_peripherals) = &mut *state_enum + else { + unreachable!(); + }; + let conflicts = conflicts.as_ref(); + for &id in conflicts { + let Some(conflicts_for_id) = collecting_peripherals.conflicts_graph.get_mut(&id) else { + drop(state_enum); // don't poison + panic!("unknown peripheral: {id:?}"); + }; + conflicts_for_id.extend(conflicts.iter().copied().filter(|&v| v != id)); + } + } + pub fn finish(self) -> PeripheralsBuilderFinished<'a> { + self.peripherals_state.finish_collecting_peripherals(); + PeripheralsBuilderFinished { + _private: PhantomData, + } + } +} + +pub struct Peripheral { + ty: T, + common: PeripheralCommon, +} + +impl Peripheral { + pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { + let Self { ty, ref common } = *self; + PeripheralRef { ty, common } + } + pub fn is_available(&self) -> bool { + self.as_ref().is_available() + } + pub fn is_used(&self) -> bool { + self.as_ref().is_used() + } + pub fn try_into_used(self) -> Result, Self> { + let Some(building_module) = self.common.peripherals_state.building_module.get() else { + return Err(self); + }; + let building_module = building_module + .availabilities + .lock() + .expect("shouldn't be poison"); + match building_module[&self.common.id] { + PeripheralAvailability::Used => {} + PeripheralAvailability::Available | PeripheralAvailability::ConflictsWithUsed(_) => { + drop(building_module); + return Err(self); + } + } + drop(building_module); + let state = self + .common + .peripherals_state + .state + .lock() + .expect("shouldn't be poison"); + let output = match *state { + PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { + unreachable!() + } + PeripheralsStateEnum::BuildingModule => { + drop(state); + return Err(self); + } + PeripheralsStateEnum::BuildingWrapperModule( + PeripheralsStateBuildingWrapperModule { + output: Some(output), + .. + }, + ) => output, + PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }; + drop(state); + let Self { ty, common } = self; + let instance_io_field = Expr::field(output, &common.id.name); + assert_eq!(ty, Expr::ty(instance_io_field)); + Ok(UsedPeripheral { + instance_io_field, + common, + }) + } + pub fn into_used(self) -> Option> { + self.try_into_used().ok() + } +} + +impl fmt::Debug for Peripheral { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ref().debug_fmt("Peripheral", f) + } +} + +pub struct UsedPeripheral { + instance_io_field: Expr, + common: PeripheralCommon, +} + +impl fmt::Debug for UsedPeripheral { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_ref().debug_fmt("UsedPeripheral", f) + } +} + +impl UsedPeripheral { + pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { + let Self { + instance_io_field, + ref common, + } = *self; + PeripheralRef { + ty: Expr::ty(instance_io_field), + common, + } + } + pub fn instance_io_field(&self) -> Expr { + self.instance_io_field + } +} + +#[derive(Copy, Clone)] +pub struct PeripheralRef<'a, T: Type> { + ty: T, + common: &'a PeripheralCommon, +} + +impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt("PeripheralRef", f) + } +} + +#[derive(Debug, Clone)] +pub enum PeripheralUnavailableError { + PeripheralAlreadyUsed { + id: PeripheralId, + }, + PeripheralConflict { + id: PeripheralId, + conflicting_id: PeripheralId, + }, + PeripheralsWillNotBeUsedToBuildWrapper, +} + +impl fmt::Display for PeripheralUnavailableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::PeripheralAlreadyUsed { id } => { + write!(f, "peripherals can only be used once: {id:?}") + } + Self::PeripheralConflict { id, conflicting_id } => { + write!(f, "peripheral {id:?} conflicts with {conflicting_id:?}") + } + Self::PeripheralsWillNotBeUsedToBuildWrapper => { + write!(f, "peripherals will not be used to build wrapper") + } + } + } +} + +impl std::error::Error for PeripheralUnavailableError {} + +impl<'a, T: Type> PeripheralRef<'a, T> { + fn debug_fmt(&self, struct_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + ty, + common: + PeripheralCommon { + type_id: _, + id, + is_input, + peripherals_state: _, + }, + } = self; + f.debug_struct(struct_name) + .field("ty", ty) + .field("id", id) + .field("is_input", is_input) + .field("availability", &self.availability()) + .finish() + } + pub fn ty(&self) -> T { + self.ty + } + pub fn id(&self) -> PeripheralId { + self.common.id + } + pub fn name(&self) -> Interned { + self.id().name + } + pub fn is_input(&self) -> bool { + self.common.is_input + } + pub fn is_output(&self) -> bool { + !self.common.is_input + } + pub fn conflicts_with(&self) -> Interned> { + match self.common.peripherals_state.building_module.get() { + Some(building_module) => building_module.conflicts_graph[&self.common.id], + None => match &*self + .common + .peripherals_state + .state + .lock() + .expect("shouldn't be poison") + { + PeripheralsStateEnum::CollectingPeripherals(v) => { + v.conflicts_graph[&self.common.id].intern() + } + PeripheralsStateEnum::Initial + | PeripheralsStateEnum::BuildingModule + | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), + }, + } + } + pub fn availability(&self) -> PeripheralAvailability { + match self.common.peripherals_state.building_module.get() { + None => PeripheralAvailability::Available, + Some(building_module) => building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id], + } + } + pub fn is_available(&self) -> bool { + match self.common.peripherals_state.building_module.get() { + None => true, + Some(building_module) => match building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id] + { + PeripheralAvailability::Available => true, + PeripheralAvailability::Used | PeripheralAvailability::ConflictsWithUsed(_) => { + false + } + }, + } + } + pub fn is_used(&self) -> bool { + match self.common.peripherals_state.building_module.get() { + None => false, + Some(building_module) => match building_module + .availabilities + .lock() + .expect("shouldn't be poison")[&self.common.id] + { + PeripheralAvailability::Used => true, + PeripheralAvailability::Available + | PeripheralAvailability::ConflictsWithUsed(_) => false, + }, + } + } + pub fn canonical(self) -> PeripheralRef<'a, CanonicalType> { + let Self { ty, common } = self; + PeripheralRef { + ty: ty.canonical(), + common, + } + } + pub fn from_canonical(peripheral_ref: PeripheralRef<'a, CanonicalType>) -> Self { + let PeripheralRef { ty, common } = peripheral_ref; + Self { + ty: T::from_canonical(ty), + common, + } + } + #[track_caller] + pub fn try_use_peripheral(self) -> Result, PeripheralUnavailableError> { + self.try_use_peripheral_with_loc(SourceLocation::caller()) + } + #[track_caller] + pub fn try_use_peripheral_with_loc( + self, + source_location: SourceLocation, + ) -> Result, PeripheralUnavailableError> { + let PeripheralsState { + will_build_wrapper, + ref state, + ref building_module, + } = *self.common.peripherals_state; + if !will_build_wrapper { + return Err(PeripheralUnavailableError::PeripheralsWillNotBeUsedToBuildWrapper); + } + let Some(PeripheralsStateBuildingModule { + conflicts_graph, + availabilities, + on_use_state, + }) = building_module.get() + else { + panic!("can't use peripherals in a module before the PeripheralsBuilder is finished"); + }; + let state = state.lock().expect("shouldn't be poison"); + match *state { + PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { + unreachable!() + } + PeripheralsStateEnum::BuildingModule => {} + PeripheralsStateEnum::BuildingWrapperModule(_) => { + panic!("can't add new peripherals after calling m.add_platform_io()") + } + } + drop(state); + let mut availabilities = availabilities.lock().expect("shouldn't be poison"); + let Some(availability) = availabilities.get_mut(&self.common.id) else { + unreachable!(); + }; + match *availability { + PeripheralAvailability::Available => { + *availability = PeripheralAvailability::Used; + } + PeripheralAvailability::Used => { + return Err(PeripheralUnavailableError::PeripheralAlreadyUsed { + id: self.common.id, + }); + } + PeripheralAvailability::ConflictsWithUsed(conflicting_id) => { + return Err(PeripheralUnavailableError::PeripheralConflict { + id: self.common.id, + conflicting_id, + }); + } + } + for conflict in conflicts_graph[&self.common.id].iter() { + let Some(availability) = availabilities.get_mut(conflict) else { + unreachable!(); + }; + *availability = PeripheralAvailability::ConflictsWithUsed(self.common.id); + } + drop(availabilities); + let wire = wire_with_loc(&self.name(), source_location, self.ty()); + let mut on_use_state = on_use_state.lock().expect("shouldn't be poison"); + let PeripheralsOnUseState { + shared_state, + main_module_io_fields, + main_module_io_wires, + on_use_functions, + } = &mut *on_use_state; + let Some(on_use_function) = on_use_functions.remove(&self.common.id) else { + unreachable!(); + }; + for conflict in conflicts_graph[&self.common.id].iter() { + on_use_functions.remove(conflict); + } + let canonical_wire = Expr::canonical(wire); + main_module_io_wires.push(canonical_wire); + main_module_io_fields.push(BundleField { + name: self.name(), + flipped: self.is_input(), + ty: Expr::ty(canonical_wire), + }); + on_use_function(shared_state, self.canonical(), canonical_wire); + drop(on_use_state); + Ok(wire) + } + #[track_caller] + pub fn use_peripheral_with_loc(self, source_location: SourceLocation) -> Expr { + match self.try_use_peripheral_with_loc(source_location) { + Ok(wire) => wire, + Err(e) => panic!("{e}"), + } + } + #[track_caller] + pub fn use_peripheral(self) -> Expr { + match self.try_use_peripheral() { + Ok(wire) => wire, + Err(e) => panic!("{e}"), + } + } +} + +pub trait Peripherals: 'static + Send + Sync + fmt::Debug { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>); + fn to_peripherals_vec<'a>(&'a self) -> Vec> { + let mut peripherals = Vec::new(); + self.append_peripherals(&mut peripherals); + peripherals + } +} + +impl Peripherals for Peripheral { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + peripherals.push(self.as_ref().canonical()); + } +} + +impl Peripherals for Vec { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +impl Peripherals for Box { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + T::append_peripherals(self, peripherals); + } +} + +impl Peripherals for [T] { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +impl Peripherals for [T; N] { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + for v in self { + v.append_peripherals(peripherals); + } + } +} + +macro_rules! impl_peripherals { + (@impl $(($v:ident: $T:ident),)*) => { + impl<$($T: Peripherals),*> Peripherals for ($($T,)*) { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + #![allow(unused_variables)] + let ($($v,)*) = self; + $(Peripherals::append_peripherals($v, peripherals);)* + } + } + }; + ($($first:tt, $($rest:tt,)*)?) => { + impl_peripherals!(@impl $($first, $($rest,)*)?); + $(impl_peripherals!($($rest,)*);)? + }; +} + +impl_peripherals! { + (v0: T0), + (v1: T1), + (v2: T2), + (v3: T3), + (v4: T4), + (v5: T5), + (v6: T6), + (v7: T7), + (v8: T8), + (v9: T9), + (v10: T10), + (v11: T11), +} + +pub struct PlatformIOBuilder<'a> { + peripherals: Vec>, + peripherals_by_type_id: HashMap>>, + peripherals_by_id: BTreeMap>, + peripherals_state: &'a PeripheralsState, +} + +impl<'a> PlatformIOBuilder<'a> { + pub fn peripherals(&self) -> &[PeripheralRef<'a, CanonicalType>] { + &self.peripherals + } + pub fn peripherals_with_type(&self) -> Vec> { + let Some(peripherals) = self.peripherals_by_type_id.get(&TypeId::of::()) else { + return Vec::new(); + }; + peripherals + .iter() + .map(|&peripheral_ref| PeripheralRef::from_canonical(peripheral_ref)) + .collect() + } + pub fn peripherals_by_id(&self) -> &BTreeMap> { + &self.peripherals_by_id + } + #[track_caller] + pub(crate) fn add_platform_io( + self, + name: &str, + source_location: SourceLocation, + m: &ModuleBuilder, + ) -> Expr { + if !ModuleBuilder::with(|m2| std::ptr::eq(m, m2)) { + panic!("m.add_platform_io() must be called in the same module as m"); + } + let PeripheralsState { + will_build_wrapper: _, + state, + building_module, + } = self.peripherals_state; + let building_module = building_module.get().expect("shouldn't be None"); + let mut on_use_state = building_module + .on_use_state + .lock() + .expect("shouldn't be poison"); + let output_ty = + Bundle::new(mem::take(&mut on_use_state.main_module_io_fields).intern_deref()); + let main_module_wires = mem::take(&mut on_use_state.main_module_io_wires); + drop(on_use_state); + let output = m.output_with_loc(name, source_location, output_ty); + for (field, wire_expr) in output_ty.fields().iter().zip(main_module_wires) { + let ExprEnum::Wire(wire) = *Expr::expr_enum(wire_expr) else { + unreachable!(); + }; + let field_expr = Expr::field(output, &field.name); + if field.flipped { + connect_with_loc(wire, field_expr, wire.source_location()); + } else { + connect_with_loc(field_expr, wire, wire.source_location()); + } + } + let ExprEnum::ModuleIO(output_module_io) = *Expr::expr_enum(output) else { + unreachable!(); + }; + *state.lock().expect("shouldn't be poison") = + PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { + output_module_io, + output: None, + }); + output + } +} + +impl<'a> fmt::Debug for PlatformIOBuilder<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + peripherals, + peripherals_by_type_id: _, + peripherals_by_id: _, + peripherals_state: _, + } = self; + f.debug_struct("PlatformIOBuilder") + .field("peripherals", peripherals) + .finish_non_exhaustive() + } +} + +pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { + type Peripherals: Peripherals; + fn new_peripherals<'builder>( + &self, + builder_factory: PeripheralsBuilderFactory<'builder>, + ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>); + /// gets peripherals that can be used for inspecting them, but not for building a main module + fn get_peripherals(&self) -> Self::Peripherals { + let ( + retval, + PeripheralsBuilderFinished { + _private: PhantomData, + }, + ) = self.new_peripherals(PeripheralsBuilderFactory::new(false)); + retval + } + fn source_location(&self) -> SourceLocation; + fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals); + #[track_caller] + fn try_wrap_main_module< + T: BundleType, + E, + M: AsRef>, + F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> Result, + >( + &self, + make_main_module: F, + ) -> Result>, E> { + let builder_factory = PeripheralsBuilderFactory::new(true); + let peripherals_state = builder_factory.peripherals_state.clone(); + let ( + peripherals, + PeripheralsBuilderFinished { + _private: PhantomData, + }, + ) = self.new_peripherals(builder_factory); + let peripherals_vec = peripherals.to_peripherals_vec(); + let mut peripherals_by_id = BTreeMap::new(); + let mut peripherals_by_type_id = HashMap::<_, Vec<_>>::default(); + for &peripheral in &peripherals_vec { + peripherals_by_id.insert(peripheral.id(), peripheral); + peripherals_by_type_id + .entry(peripheral.common.type_id) + .or_default() + .push(peripheral); + } + let main_module = Module::canonical( + *make_main_module(PlatformIOBuilder { + peripherals: peripherals_vec, + peripherals_by_type_id, + peripherals_by_id, + peripherals_state: &peripherals_state, + })? + .as_ref(), + ); + let state = peripherals_state.state.lock().expect("shouldn't be poison"); + let PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { + output_module_io, + output: _, + }) = *state + else { + drop(state); + panic!( + "you need to call m.add_platform_io() inside the main module you're trying to use peripherals in.\nat: {}", + main_module.source_location() + ); + }; + drop(state); + for module_io in main_module.module_io() { + if module_io.module_io != output_module_io { + panic!( + "when you're using m.add_platform_io(), you can't have any other inputs/outputs.\nat: {}", + module_io.module_io.source_location() + ); + } + } + Ok(ModuleBuilder::run_with_loc( + &main_module.name(), + self.source_location(), + crate::module::ModuleKind::Normal, + |m| { + let instance = + instance_with_loc("main", main_module.intern(), SourceLocation::caller()); + let output_expr = Expr::field(instance, &output_module_io.bundle_field().name); + let mut state = peripherals_state.state.lock().expect("shouldn't be poison"); + let PeripheralsStateEnum::BuildingWrapperModule( + PeripheralsStateBuildingWrapperModule { + output_module_io: _, + output, + }, + ) = &mut *state + else { + unreachable!(); + }; + *output = Some(output_expr); + drop(state); + self.add_peripherals_in_wrapper_module(m, peripherals) + }, + )) + } + #[track_caller] + fn wrap_main_module< + T: BundleType, + M: AsRef>, + F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> M, + >( + &self, + make_main_module: F, + ) -> Interned> { + self.try_wrap_main_module(|p| Ok(make_main_module(p))) + .unwrap_or_else(|e: Infallible| match e {}) + } +} diff --git a/crates/fayalite/src/target.rs b/crates/fayalite/src/target.rs deleted file mode 100644 index 33ffee9..0000000 --- a/crates/fayalite/src/target.rs +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{intern::Interned, util::job_server::AcquiredJob}; -use std::{ - any::Any, - fmt, - iter::FusedIterator, - sync::{Arc, Mutex}, -}; - -pub trait Peripheral: Any + Send + Sync + fmt::Debug {} - -pub trait Tool: Any + Send + Sync + fmt::Debug { - fn name(&self) -> Interned; - fn run(&self, acquired_job: &mut AcquiredJob); -} - -pub trait Target: Any + Send + Sync + fmt::Debug { - fn name(&self) -> Interned; - fn peripherals(&self) -> Interned<[Interned]>; -} - -#[derive(Clone)] -struct TargetsMap(Vec<(Interned, Interned)>); - -impl TargetsMap { - fn sort(&mut self) { - self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2)); - self.0.dedup_by_key(|(k, _)| *k); - } - fn from_unsorted_vec(unsorted_vec: Vec<(Interned, Interned)>) -> Self { - let mut retval = Self(unsorted_vec); - retval.sort(); - retval - } - fn extend_from_unsorted_slice(&mut self, additional: &[(Interned, Interned)]) { - self.0.extend_from_slice(additional); - self.sort(); - } -} - -impl Default for TargetsMap { - fn default() -> Self { - Self::from_unsorted_vec(vec![ - // TODO: add default targets here - ]) - } -} - -fn access_targets>) -> R, R>(f: F) -> R { - static TARGETS: Mutex>> = Mutex::new(None); - let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned"); - f(&mut targets_lock) -} - -pub fn add_targets>>(additional: I) { - // run iterator and target methods outside of lock - let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v))); - access_targets(|targets| { - Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional); - }); -} - -pub fn targets() -> TargetsSnapshot { - access_targets(|targets| match targets { - Some(targets) => TargetsSnapshot { - targets: targets.clone(), - }, - None => { - let new_targets = Arc::::default(); - *targets = Some(new_targets.clone()); - TargetsSnapshot { - targets: new_targets, - } - } - }) -} - -#[derive(Clone)] -pub struct TargetsSnapshot { - targets: Arc, -} - -impl TargetsSnapshot { - pub fn get(&self, key: &str) -> Option> { - let index = self - .targets - .0 - .binary_search_by_key(&key, |(k, _v)| k) - .ok()?; - Some(self.targets.0[index].1) - } - pub fn iter(&self) -> TargetsIter { - self.into_iter() - } - pub fn len(&self) -> usize { - self.targets.0.len() - } -} - -impl fmt::Debug for TargetsSnapshot { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TargetsSnapshot ")?; - f.debug_map().entries(self).finish() - } -} - -impl IntoIterator for &'_ mut TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - self.clone().into_iter() - } -} - -impl IntoIterator for &'_ TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - self.clone().into_iter() - } -} - -impl IntoIterator for TargetsSnapshot { - type Item = (Interned, Interned); - type IntoIter = TargetsIter; - - fn into_iter(self) -> Self::IntoIter { - TargetsIter { - indexes: 0..self.targets.0.len(), - targets: self.targets, - } - } -} - -#[derive(Clone)] -pub struct TargetsIter { - targets: Arc, - indexes: std::ops::Range, -} - -impl fmt::Debug for TargetsIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TargetsIter ")?; - f.debug_map().entries(self.clone()).finish() - } -} - -impl Iterator for TargetsIter { - type Item = (Interned, Interned); - - fn next(&mut self) -> Option { - Some(self.targets.0[self.indexes.next()?]) - } - - fn size_hint(&self) -> (usize, Option) { - self.indexes.size_hint() - } - - fn count(self) -> usize { - self.indexes.len() - } - - fn last(mut self) -> Option { - self.next_back() - } - - fn nth(&mut self, n: usize) -> Option { - Some(self.targets.0[self.indexes.nth(n)?]) - } - - fn fold B>(self, init: B, mut f: F) -> B { - self.indexes - .fold(init, move |retval, index| f(retval, self.targets.0[index])) - } -} - -impl FusedIterator for TargetsIter {} - -impl DoubleEndedIterator for TargetsIter { - fn next_back(&mut self) -> Option { - Some(self.targets.0[self.indexes.next_back()?]) - } - - fn nth_back(&mut self, n: usize) -> Option { - Some(self.targets.0[self.indexes.nth_back(n)?]) - } - - fn rfold B>(self, init: B, mut f: F) -> B { - self.indexes - .rfold(init, move |retval, index| f(retval, self.targets.0[index])) - } -} - -impl ExactSizeIterator for TargetsIter { - fn len(&self) -> usize { - self.indexes.len() - } -} diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index c2dc24e..a039250 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -6,6 +6,7 @@ use fayalite::{ int::{UIntInRange, UIntInRangeInclusive}, intern::Intern, module::transform::simplify_enums::SimplifyEnumsKind, + platform::PlatformIOBuilder, prelude::*, reset::ResetType, ty::StaticType, @@ -4631,3 +4632,55 @@ circuit check_uint_in_range: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_platform_io(platform_io_builder: PlatformIOBuilder<'_>) { + #[hdl] + let io = m.add_platform_io(platform_io_builder); +} + +#[cfg(todo)] +#[test] +fn test_platform_io() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_platform_io(todo!()); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_platform_io.fir": r"FIRRTL version 3.2.0 +circuit check_platform_io: + type Ty0 = {value: UInt<0>, range: {}} + type Ty1 = {value: UInt<1>, range: {}} + type Ty2 = {value: UInt<2>, range: {}} + type Ty3 = {value: UInt<2>, range: {}} + type Ty4 = {value: UInt<3>, range: {}} + type Ty5 = {value: UInt<3>, range: {}} + type Ty6 = {value: UInt<4>, range: {}} + type Ty7 = {value: UInt<0>, range: {}} + type Ty8 = {value: UInt<1>, range: {}} + type Ty9 = {value: UInt<2>, range: {}} + type Ty10 = {value: UInt<2>, range: {}} + type Ty11 = {value: UInt<3>, range: {}} + type Ty12 = {value: UInt<3>, range: {}} + type Ty13 = {value: UInt<4>, range: {}} + type Ty14 = {value: UInt<4>, range: {}} + module check_platform_io: @[module-XXXXXXXXXX.rs 1:1] + input i_0_to_1: Ty0 @[module-XXXXXXXXXX.rs 2:1] + input i_0_to_2: Ty1 @[module-XXXXXXXXXX.rs 3:1] + input i_0_to_3: Ty2 @[module-XXXXXXXXXX.rs 4:1] + input i_0_to_4: Ty3 @[module-XXXXXXXXXX.rs 5:1] + input i_0_to_7: Ty4 @[module-XXXXXXXXXX.rs 6:1] + input i_0_to_8: Ty5 @[module-XXXXXXXXXX.rs 7:1] + input i_0_to_9: Ty6 @[module-XXXXXXXXXX.rs 8:1] + input i_0_through_0: Ty7 @[module-XXXXXXXXXX.rs 9:1] + input i_0_through_1: Ty8 @[module-XXXXXXXXXX.rs 10:1] + input i_0_through_2: Ty9 @[module-XXXXXXXXXX.rs 11:1] + input i_0_through_3: Ty10 @[module-XXXXXXXXXX.rs 12:1] + input i_0_through_4: Ty11 @[module-XXXXXXXXXX.rs 13:1] + input i_0_through_7: Ty12 @[module-XXXXXXXXXX.rs 14:1] + input i_0_through_8: Ty13 @[module-XXXXXXXXXX.rs 15:1] + input i_0_through_9: Ty14 @[module-XXXXXXXXXX.rs 16:1] +", + }; +} diff --git a/crates/fayalite/tests/ui/module.rs b/crates/fayalite/tests/ui/module.rs index 36649aa..ee0f988 100644 --- a/crates/fayalite/tests/ui/module.rs +++ b/crates/fayalite/tests/ui/module.rs @@ -11,4 +11,20 @@ pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { let o: UInt<8> = m.output(); } +#[hdl_module] +pub fn my_module2(platform_io_builder: PlatformIOBuilder<'_>) { + #[hdl] + let a: UInt<8> = m.input(); + #[hdl] + let b: UInt<8> = m.output(); + #[hdl] + let io = m.add_platform_io(platform_io_builder); + #[hdl] + let c: UInt<8> = m.input(); + #[hdl] + let d: UInt<8> = m.output(); + #[hdl] + let io = m.add_platform_io(platform_io_builder); +} + fn main() {} diff --git a/crates/fayalite/tests/ui/module.stderr b/crates/fayalite/tests/ui/module.stderr index efbd1fe..979e76f 100644 --- a/crates/fayalite/tests/ui/module.stderr +++ b/crates/fayalite/tests/ui/module.stderr @@ -1,17 +1,47 @@ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:7:26 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:7:35 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &mut ModuleBuilder<_>` +error: name conflicts with implicit `m: &ModuleBuilder` --> tests/ui/module.rs:9:9 | 9 | let m: UInt<8> = m.input(); | ^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:17:24 + | +17 | let a: UInt<8> = m.input(); + | ^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:19:24 + | +19 | let b: UInt<8> = m.output(); + | ^^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:23:24 + | +23 | let c: UInt<8> = m.input(); + | ^^^^^ + +error: can't have other inputs/outputs in a module using m.add_platform_io() + --> tests/ui/module.rs:25:24 + | +25 | let d: UInt<8> = m.output(); + | ^^^^^^ + +error: can't use m.add_platform_io() more than once in a single module + --> tests/ui/module.rs:27:16 + | +27 | let io = m.add_platform_io(platform_io_builder); + | ^^^^^^^^^^^^^^^