From 094c77e26ecd495f667d00eba6744d59793b1175 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 02:13:10 -0700 Subject: [PATCH 01/10] add #[hdl(get(|v| ...))] type GetStuff> = MyType or DynSize; --- .../src/hdl_bundle.rs | 5 + .../fayalite-proc-macros-impl/src/hdl_enum.rs | 5 + .../src/hdl_type_alias.rs | 556 ++++++++++++++++-- .../src/hdl_type_common.rs | 2 + crates/fayalite-proc-macros-impl/src/lib.rs | 1 + crates/fayalite/src/phantom_const.rs | 68 +++ crates/fayalite/src/prelude.rs | 2 +- crates/fayalite/tests/hdl_types.rs | 19 +- 8 files changed, 600 insertions(+), 58 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 09189bd9..e8dc51bf 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -87,7 +87,11 @@ impl ParsedBundle { no_static: _, no_runtime_generics: _, cmp_eq: _, + ref get, } = options.body; + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on structs"); + } let mut fields = match fields { syn::Fields::Named(fields) => fields, syn::Fields::Unnamed(fields) => { @@ -445,6 +449,7 @@ impl ToTokens for ParsedBundle { no_static, no_runtime_generics, cmp_eq, + get: _, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 47a5df10..885cf871 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -159,10 +159,14 @@ impl ParsedEnum { no_static: _, no_runtime_generics: _, cmp_eq, + ref get, } = options.body; if let Some((cmp_eq,)) = cmp_eq { errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); } + if let Some((get, ..)) = get { + errors.error(get, "#[hdl(get(...))] is not allowed on enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -225,6 +229,7 @@ impl ToTokens for ParsedEnum { no_static, no_runtime_generics, cmp_eq: _, // TODO: implement cmp_eq for enums + get: _, } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index d4a035bd..82353669 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -4,28 +4,353 @@ use crate::{ Errors, HdlAttr, hdl_type_common::{ ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, - get_target, + WrappedInConst, common_derives, get_target, known_items, }, kw, }; use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned}; +use quote::{ToTokens, format_ident, quote_spanned}; +use syn::{ + AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam, + Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound, + TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility, + parse_quote_spanned, punctuated::Pair, token::Paren, +}; #[derive(Clone, Debug)] -pub(crate) struct ParsedTypeAlias { - pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, - pub(crate) vis: Visibility, - pub(crate) type_token: Token![type], - pub(crate) ident: Ident, - pub(crate) generics: MaybeParsed, - pub(crate) eq_token: Token![=], - pub(crate) ty: MaybeParsed, - pub(crate) semi_token: Token![;], +pub(crate) struct PhantomConstGetBound { + pub(crate) phantom_const_get: known_items::PhantomConstGet, + pub(crate) colon2_token: Option, + pub(crate) lt_token: Token![<], + pub(crate) ty: Type, + pub(crate) comma_token: Option, + pub(crate) gt_token: Token![>], +} + +impl From for Path { + fn from(value: PhantomConstGetBound) -> Self { + let PhantomConstGetBound { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = value; + let mut path = phantom_const_get.path; + path.segments.last_mut().expect("known to exist").arguments = + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), + gt_token, + }); + path + } +} + +impl From for TraitBound { + fn from(value: PhantomConstGetBound) -> Self { + let path = Path::from(value); + TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + } + } +} + +impl From for TypeParamBound { + fn from(value: PhantomConstGetBound) -> Self { + TraitBound::from(value).into() + } +} + +impl PhantomConstGetBound { + fn parse_opt(bound: TypeParamBound) -> Option { + let TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + }) = bound + else { + return None; + }; + let Ok(( + phantom_const_get, + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }), + )) = known_items::PhantomConstGet::parse_path_with_arguments(path) + else { + return None; + }; + let mut args = args.into_pairs(); + let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else { + return None; + }; + let None = args.next() else { + return None; + }; + Some(Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorTypeParam { + attrs: Vec, + ident: Ident, + colon_token: Token![:], + phantom_const_get_bound: PhantomConstGetBound, + plus_token: Option, +} + +impl From for TypeParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + let PhantomConstAccessorTypeParam { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + } = value; + TypeParam { + attrs, + ident, + colon_token: Some(colon_token), + bounds: FromIterator::from_iter([Pair::new( + phantom_const_get_bound.into(), + plus_token, + )]), + eq_token: None, + default: None, + } + } +} + +impl From for GenericParam { + fn from(value: PhantomConstAccessorTypeParam) -> Self { + TypeParam::from(value).into() + } +} + +impl PhantomConstAccessorTypeParam { + fn parse_opt(generic_param: GenericParam) -> Option { + let GenericParam::Type(TypeParam { + attrs, + ident, + colon_token, + bounds, + eq_token: None, + default: None, + }) = generic_param + else { + return None; + }; + let colon_token = colon_token.unwrap_or(Token![:](ident.span())); + let mut bounds = bounds.into_pairs(); + let (bound, plus_token) = bounds.next()?.into_tuple(); + let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?; + let None = bounds.next() else { + return None; + }; + Some(Self { + attrs, + ident, + colon_token, + phantom_const_get_bound, + plus_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstAccessorGenerics { + lt_token: Token![<], + type_param: PhantomConstAccessorTypeParam, + comma_token: Option, + gt_token: Token![>], +} + +impl From for Generics { + fn from(value: PhantomConstAccessorGenerics) -> Self { + let PhantomConstAccessorGenerics { + lt_token, + type_param, + comma_token, + gt_token, + } = value; + Generics { + lt_token: Some(lt_token), + params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]), + gt_token: Some(gt_token), + where_clause: None, + } + } +} + +impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics { + fn from(value: &'a PhantomConstAccessorGenerics) -> Self { + value.clone().into() + } +} + +impl PhantomConstAccessorGenerics { + fn parse_opt(generics: Generics) -> Option { + let Generics { + lt_token, + params, + gt_token, + where_clause: None, + } = generics + else { + return None; + }; + let mut params = params.into_pairs(); + let (generic_param, comma_token) = params.next()?.into_tuple(); + let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?; + let span = type_param.ident.span(); + let lt_token = lt_token.unwrap_or(Token![<](span)); + let gt_token = gt_token.unwrap_or(Token![>](span)); + let None = params.next() else { + return None; + }; + Some(Self { + lt_token, + type_param, + comma_token, + gt_token, + }) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum ParsedTypeAlias { + TypeAlias { + attrs: Vec, + options: HdlAttr, + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: MaybeParsed, + eq_token: Token![=], + ty: MaybeParsed, + semi_token: Token![;], + }, + PhantomConstAccessor { + attrs: Vec, + options: HdlAttr, + get: (kw::get, Paren, Expr), + vis: Visibility, + type_token: Token![type], + ident: Ident, + generics: PhantomConstAccessorGenerics, + eq_token: Token![=], + ty: Type, + ty_is_dyn_size: Option, + semi_token: Token![;], + }, } impl ParsedTypeAlias { + fn ty_is_dyn_size(ty: &Type) -> Option { + match ty { + Type::Group(TypeGroup { + group_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Paren(TypeParen { + paren_token: _, + elem, + }) => Self::ty_is_dyn_size(elem), + Type::Path(syn::TypePath { qself: None, path }) => { + known_items::DynSize::parse_path(path.clone()).ok() + } + _ => None, + } + } + fn parse_phantom_const_accessor( + item: ItemType, + mut errors: Errors, + options: HdlAttr, + get: (kw::get, Paren, Expr), + ) -> syn::Result { + let ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } = item; + let ItemOptions { + outline_generated: _, + ref target, + custom_bounds, + no_static, + no_runtime_generics, + cmp_eq, + get: _, + } = options.body; + if let Some((no_static,)) = no_static { + errors.error(no_static, "no_static is not valid on type aliases"); + } + if let Some((target, ..)) = target { + errors.error( + target, + "target is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((no_runtime_generics,)) = no_runtime_generics { + errors.error( + no_runtime_generics, + "no_runtime_generics is not implemented on PhantomConstGet type aliases", + ); + } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } + if let Some((custom_bounds,)) = custom_bounds { + errors.error( + custom_bounds, + "custom_bounds is not implemented on PhantomConstGet type aliases", + ); + } + let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else { + errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter> = RetType;"); + errors.finish()?; + unreachable!(); + }; + errors.finish()?; + let ty_is_dyn_size = Self::ty_is_dyn_size(&ty); + Ok(Self::PhantomConstAccessor { + attrs, + options, + get, + vis, + type_token, + ident, + generics, + eq_token, + ty: *ty, + ty_is_dyn_size, + semi_token, + }) + } fn parse(item: ItemType) -> syn::Result { let ItemType { mut attrs, @@ -51,7 +376,25 @@ impl ParsedTypeAlias { no_static, no_runtime_generics: _, cmp_eq, + ref mut get, } = options.body; + if let Some(get) = get.take() { + return Self::parse_phantom_const_accessor( + ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + }, + errors, + options, + get, + ); + } if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } @@ -67,7 +410,7 @@ impl ParsedTypeAlias { }; let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors); errors.finish()?; - Ok(Self { + Ok(Self::TypeAlias { attrs, options, vis, @@ -83,54 +426,155 @@ impl ParsedTypeAlias { impl ToTokens for ParsedTypeAlias { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - attrs, - options, - vis, - type_token, - ident, - generics, - eq_token, - ty, - semi_token, - } = self; - let ItemOptions { - outline_generated: _, - target, - custom_bounds: _, - no_static: _, - no_runtime_generics, - cmp_eq: _, - } = &options.body; - let target = get_target(target, ident); - let mut type_attrs = attrs.clone(); - type_attrs.push(parse_quote_spanned! {ident.span()=> - #[allow(type_alias_bounds)] - }); - ItemType { - attrs: type_attrs, - vis: vis.clone(), - type_token: *type_token, - ident: ident.clone(), - generics: generics.into(), - eq_token: *eq_token, - ty: Box::new(ty.clone().into()), - semi_token: *semi_token, - } - .to_tokens(tokens); - if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = - (generics, ty, no_runtime_generics) - { - generics.make_runtime_generics(tokens, vis, ident, &target, |context| { - ty.make_hdl_type_expr(context) - }) + match self { + Self::TypeAlias { + attrs, + options, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target, + custom_bounds: _, + no_static: _, + no_runtime_generics, + cmp_eq: _, + get: _, + } = &options.body; + let target = get_target(target, ident); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {ident.span()=> + #[allow(type_alias_bounds)] + }); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: generics.into(), + eq_token: *eq_token, + ty: Box::new(ty.clone().into()), + semi_token: *semi_token, + } + .to_tokens(tokens); + if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) = + (generics, ty, no_runtime_generics) + { + generics.make_runtime_generics(tokens, vis, ident, &target, |context| { + ty.make_hdl_type_expr(context) + }) + } + } + Self::PhantomConstAccessor { + attrs, + options, + get: (_get_kw, _get_paren, get_expr), + vis, + type_token, + ident, + generics, + eq_token, + ty, + ty_is_dyn_size, + semi_token, + } => { + let ItemOptions { + outline_generated: _, + target: _, + custom_bounds: _, + no_static: _, + no_runtime_generics: _, + cmp_eq: _, + get: _, + } = &options.body; + let span = ident.span(); + let mut type_attrs = attrs.clone(); + type_attrs.push(parse_quote_spanned! {span=> + #[allow(type_alias_bounds)] + }); + let type_param_ident = &generics.type_param.ident; + let syn_generics = Generics::from(generics); + ItemType { + attrs: type_attrs, + vis: vis.clone(), + type_token: *type_token, + ident: ident.clone(), + generics: syn_generics.clone(), + eq_token: *eq_token, + ty: parse_quote_spanned! {span=> + <#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type + }, + semi_token: *semi_token, + } + .to_tokens(tokens); + let generics_accumulation_ident = + format_ident!("__{}__GenericsAccumulation", ident); + ItemStruct { + attrs: vec![ + common_derives(span), + parse_quote_spanned! {span=> + #[allow(non_camel_case_types)] + }, + ], + vis: vis.clone(), + struct_token: Token![struct](span), + ident: generics_accumulation_ident.clone(), + generics: Generics::default(), + fields: Fields::Unnamed(parse_quote_spanned! {span=> + (()) + }), + semi_token: Some(Token![;](span)), + } + .to_tokens(tokens); + quote_spanned! {span=> + #[allow(non_upper_case_globals, dead_code)] + #vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(()); + } + .to_tokens(tokens); + let mut wrapped_in_const = WrappedInConst::new(tokens, span); + let tokens = wrapped_in_const.inner(); + let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl(); + let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty; + let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size { + known_items::usize(ty_is_dyn_size.span).to_token_stream() + } else { + ty.to_token_stream() + }; + quote_spanned! {span=> + #[allow(non_upper_case_globals)] + #[automatically_derived] + impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident> + for #generics_accumulation_ident + #where_clause + { + type Output = #index_output; + + fn index(&self, __param: #type_param_ident) -> &Self::Output { + ::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>( + __param, + #get_expr, + ) + } + } + } + .to_tokens(tokens); + } } } } pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result { let item = ParsedTypeAlias::parse(item)?; - let outline_generated = item.options.body.outline_generated; + let outline_generated = match &item { + ParsedTypeAlias::TypeAlias { options, .. } + | ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated, + }; let mut contents = item.to_token_stream(); if outline_generated.is_some() { contents = crate::outline_generated(contents, "hdl-type-alias-"); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 1206f11c..73acc395 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -27,6 +27,7 @@ crate::options! { NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), CmpEq(cmp_eq), + Get(get, Expr), } } @@ -2045,6 +2046,7 @@ pub(crate) mod known_items { impl_known_item!(::fayalite::int::Size); impl_known_item!(::fayalite::int::UInt); impl_known_item!(::fayalite::int::UIntType); + impl_known_item!(::fayalite::phantom_const::PhantomConstGet); impl_known_item!(::fayalite::reset::ResetType); impl_known_item!(::fayalite::ty::CanonicalType); impl_known_item!(::fayalite::ty::StaticType); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 13336fa2..13ec7a2e 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -76,6 +76,7 @@ mod kw { custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); + custom_keyword!(get); custom_keyword!(hdl); custom_keyword!(hdl_module); custom_keyword!(incomplete_wire); diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index b8520560..9f251665 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -415,3 +415,71 @@ impl ToSimValueWithType for Phanto SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self)) } } + +mod sealed { + pub trait Sealed {} +} + +pub trait PhantomConstGet: sealed::Sealed { + fn get(&self) -> Interned; +} + +impl>> + sealed::Sealed for This +{ +} + +impl>> + PhantomConstGet for This +{ + fn get(&self) -> Interned { + This::Target::get(&**self) + } +} + +macro_rules! impl_phantom_const_get { + ( + impl PhantomConstGet<$T:ident> for $ty:ty { + fn $get:ident(&$get_self:ident) -> _ $get_body:block + } + ) => { + impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {} + + impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty { + fn $get(&$get_self) -> Interned<$T> $get_body + } + }; +} + +impl_phantom_const_get! { + impl PhantomConstGet for PhantomConst { + fn get(&self) -> _ { + PhantomConst::get(*self) + } + } +} + +impl_phantom_const_get! { + impl PhantomConstGet for Expr> { + fn get(&self) -> _ { + PhantomConst::get(Expr::ty(*self)) + } + } +} + +#[doc(hidden)] +pub trait ReturnSelfUnchanged { + type Type: ?Sized; +} + +impl ReturnSelfUnchanged for This { + type Type = This; +} + +#[doc(hidden)] +pub fn type_alias_phantom_const_get_helper( + param: impl PhantomConstGet, + get: impl FnOnce(Interned) -> R, +) -> &'static R { + Interned::into_inner(get(param.get()).intern_sized()) +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5bb4b772..4cc173e8 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -27,7 +27,7 @@ pub use crate::{ Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, }, - phantom_const::PhantomConst, + phantom_const::{PhantomConst, PhantomConstGet}, platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals}, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 8802fd4b..8742bb0b 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -4,7 +4,6 @@ use fayalite::{ bundle::BundleType, enum_::EnumType, int::{BoolOrIntType, IntType}, - phantom_const::PhantomConst, prelude::*, ty::StaticType, }; @@ -197,3 +196,21 @@ check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>); + +#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +pub struct MyPhantomConstInner { + pub a: usize, + pub b: UInt, +} + +#[hdl(outline_generated, get(|v| v.a))] +pub type GetA> = DynSize; + +#[hdl(outline_generated, get(|v| v.b))] +pub type GetB> = UInt; + +#[hdl(outline_generated, no_static)] +pub struct MyTypeWithPhantomConstParameter> { + pub a: ArrayType>, + pub b: HdlOption>, +} From 4b24a886413da316e71bf8f61ce091bbde73b1ab Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 26 Oct 2025 03:01:26 -0700 Subject: [PATCH 02/10] add docs for #[hdl] and particularly for #[hdl] type aliases --- crates/fayalite/src/lib.rs | 129 +++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 7998e6f6..98849a67 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -86,6 +86,135 @@ macro_rules! __cfg_expansion_helper { pub use fayalite_proc_macros::hdl_module; #[doc(inline)] +/// The `#[hdl]` attribute is supported on several different kinds of [Rust Items](https://doc.rust-lang.org/reference/items.html): +/// +/// # Functions and Methods +/// Enable's the stuff that you can use inside a [module's body](crate::_docs::modules::module_bodies), +/// but without being a module or changing the function's signature. +/// The only exception is that you can't use stuff that requires the automatically-provided `m` variable. +/// +/// # Structs +// TODO: expand on struct docs +/// e.g.: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct OtherStruct {} +/// #[hdl] +/// pub struct MyStruct { +/// #[hdl(flip)] +/// pub a: UInt<5>, +/// pub b: Bool, +/// #[hdl(flip)] +/// pub c: OtherStruct, +/// } +/// ``` +/// +/// # Enums +// TODO: expand on enum docs +/// e.g.: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct MyStruct {} +/// #[hdl] +/// pub enum MyEnum { +/// A(UInt<3>), +/// B, +/// C(MyStruct), +/// } +/// ``` +/// +/// # Type Aliases +/// +/// There's three different ways you can create a type alias: +/// +/// # Normal Type Alias +/// +/// This works exactly how you'd expect: +/// ``` +/// # use fayalite::prelude::*; +/// # #[hdl] +/// # pub struct MyStruct { +/// # v: T, +/// # } +/// #[hdl] +/// pub type MyType = MyStruct; +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// +/// let ty = MyType[UInt[3]]; +/// assert_eq!(ty, MyStruct[UInt[3]]); +/// ``` +/// +/// # Type Alias that gets a [`Type`] from a [`PhantomConst`] +/// +/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types. +/// +/// ``` +/// # use fayalite::{intern::Intern, prelude::*}; +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// pub struct Config { +/// pub foo: usize, +/// pub bar: Bundle, +/// } +/// +/// // the expression inside `get` is called with `Interned` and returns `Array` +/// #[hdl(get(|config| Array[config.bar][config.foo]))] +/// pub type GetMyArray> = Array; +/// +/// // you can then use it in other types: +/// +/// #[hdl(no_static)] +/// pub struct WrapMyArray> { +/// pub my_array: GetMyArray

, +/// } +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// let bar = Bundle::new(Default::default()); +/// let config = PhantomConst::new(Config { foo: 12, bar }.intern_sized()); +/// let ty = WrapMyArray[config]; +/// assert_eq!(ty.my_array, Array[bar][12]); +/// ``` +/// +/// # Type Alias that gets a [`Size`] from a [`PhantomConst`] +/// +/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types. +/// +/// ``` +/// # use fayalite::{intern::Intern, prelude::*}; +/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// # pub struct ConfigItem {} +/// # impl ConfigItem { +/// # pub fn new() -> Self { +/// # Self {} +/// # } +/// # } +/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] +/// pub struct Config { +/// pub items: Vec, +/// } +/// +/// // the expression inside `get` is called with `Interned` and returns `usize` (not DynSize) +/// #[hdl(get(|config| config.items.len()))] +/// pub type GetItemsLen> = DynSize; // must be DynSize +/// +/// // you can then use it in other types: +/// +/// #[hdl(no_static)] +/// pub struct FlagPerItem> { +/// pub flags: ArrayType>, +/// } +/// +/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: +/// let config = PhantomConst::new(Config { items: vec![ConfigItem::new(); 5] }.intern_sized()); +/// let ty = FlagPerItem[config]; +/// assert_eq!(ty.flags, Array[Bool][5]); +/// ``` +/// +/// [`PhantomConst`]: crate::phantom_const::PhantomConst +/// [`Size`]: crate::int::Size +/// [`Type`]: crate::ty::Type pub use fayalite_proc_macros::hdl; pub use bitvec; From 0b821787408e940d30799a525743f25e64d1c6ae Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 27 Oct 2025 20:08:22 -0700 Subject: [PATCH 03/10] add PhantomConstGet to the known Type bounds for #[hdl] struct/enum --- .../src/hdl_type_alias.rs | 106 +------- .../src/hdl_type_common.rs | 256 ++++++++++++++++-- crates/fayalite/src/lib.rs | 4 +- crates/fayalite/tests/hdl_types.rs | 2 +- 4 files changed, 251 insertions(+), 117 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index 82353669..0fa2222d 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -3,111 +3,19 @@ use crate::{ Errors, HdlAttr, hdl_type_common::{ - ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser, - WrappedInConst, common_derives, get_target, known_items, + ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, + PhantomConstGetBound, TypesParser, WrappedInConst, common_derives, get_target, known_items, }, kw, }; use proc_macro2::TokenStream; use quote::{ToTokens, format_ident, quote_spanned}; use syn::{ - AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam, - Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound, - TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility, - parse_quote_spanned, punctuated::Pair, token::Paren, + Attribute, Expr, Fields, GenericParam, Generics, Ident, ItemStruct, ItemType, Token, Type, + TypeGroup, TypeParam, TypeParen, Visibility, parse_quote_spanned, punctuated::Pair, + token::Paren, }; -#[derive(Clone, Debug)] -pub(crate) struct PhantomConstGetBound { - pub(crate) phantom_const_get: known_items::PhantomConstGet, - pub(crate) colon2_token: Option, - pub(crate) lt_token: Token![<], - pub(crate) ty: Type, - pub(crate) comma_token: Option, - pub(crate) gt_token: Token![>], -} - -impl From for Path { - fn from(value: PhantomConstGetBound) -> Self { - let PhantomConstGetBound { - phantom_const_get, - colon2_token, - lt_token, - ty, - comma_token, - gt_token, - } = value; - let mut path = phantom_const_get.path; - path.segments.last_mut().expect("known to exist").arguments = - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - colon2_token, - lt_token, - args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), - gt_token, - }); - path - } -} - -impl From for TraitBound { - fn from(value: PhantomConstGetBound) -> Self { - let path = Path::from(value); - TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path, - } - } -} - -impl From for TypeParamBound { - fn from(value: PhantomConstGetBound) -> Self { - TraitBound::from(value).into() - } -} - -impl PhantomConstGetBound { - fn parse_opt(bound: TypeParamBound) -> Option { - let TypeParamBound::Trait(TraitBound { - paren_token: None, - modifier: TraitBoundModifier::None, - lifetimes: None, - path, - }) = bound - else { - return None; - }; - let Ok(( - phantom_const_get, - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - colon2_token, - lt_token, - args, - gt_token, - }), - )) = known_items::PhantomConstGet::parse_path_with_arguments(path) - else { - return None; - }; - let mut args = args.into_pairs(); - let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else { - return None; - }; - let None = args.next() else { - return None; - }; - Some(Self { - phantom_const_get, - colon2_token, - lt_token, - ty, - comma_token, - gt_token, - }) - } -} - #[derive(Clone, Debug)] pub(crate) struct PhantomConstAccessorTypeParam { attrs: Vec, @@ -162,7 +70,9 @@ impl PhantomConstAccessorTypeParam { let colon_token = colon_token.unwrap_or(Token![:](ident.span())); let mut bounds = bounds.into_pairs(); let (bound, plus_token) = bounds.next()?.into_tuple(); - let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?; + let phantom_const_get_bound = PhantomConstGetBound::parse_type_param_bound(bound) + .ok()? + .ok()?; let None = bounds.next() else { return None; }; diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 73acc395..f5b353e0 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -8,9 +8,9 @@ use syn::{ AngleBracketedGenericArguments, Attribute, Block, ConstParam, Expr, ExprBlock, ExprGroup, ExprIndex, ExprParen, ExprPath, ExprTuple, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, GenericArgument, GenericParam, Generics, Ident, ImplGenerics, Index, ItemStruct, - Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, Turbofish, Type, - TypeGenerics, TypeGroup, TypeParam, TypeParen, TypePath, TypeTuple, Visibility, WhereClause, - WherePredicate, + Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, TraitBound, Turbofish, + Type, TypeGenerics, TypeGroup, TypeParam, TypeParamBound, TypeParen, TypePath, TypeTuple, + Visibility, WhereClause, WherePredicate, parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::{Pair, Punctuated}, @@ -2065,6 +2065,174 @@ pub(crate) mod known_items { ); } +#[derive(Clone, Debug)] +pub(crate) struct PhantomConstGetBound { + pub(crate) phantom_const_get: known_items::PhantomConstGet, + pub(crate) colon2_token: Option, + pub(crate) lt_token: Token![<], + pub(crate) ty: Type, + pub(crate) comma_token: Option, + pub(crate) gt_token: Token![>], +} + +impl PhantomConstGetBound { + pub(crate) fn parse_path_with_arguments(path: Path) -> syn::Result> { + match known_items::PhantomConstGet::parse_path_with_arguments(path) { + Ok((phantom_const_get, arguments)) => { + Self::parse_path_and_arguments(phantom_const_get, arguments).map(Ok) + } + Err(path) => Ok(Err(path)), + } + } + pub(crate) fn parse_path_and_arguments( + phantom_const_get: known_items::PhantomConstGet, + arguments: PathArguments, + ) -> syn::Result { + let error = |arguments: PathArguments, message: &str| { + let mut path = phantom_const_get.path.clone(); + path.segments.last_mut().expect("known to exist").arguments = arguments; + syn::Error::new_spanned(path, message) + }; + match arguments { + PathArguments::None => Err(error(arguments, "missing generics for PhantomConstGet")), + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }) => { + let error = |args: Punctuated, message| { + error( + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + }), + message, + ) + }; + let mut args = args.into_pairs().peekable(); + let Some((generic_argument, comma_token)) = args.next().map(Pair::into_tuple) + else { + return Err(error( + Default::default(), + "PhantomConstGet takes a type argument but no generic arguments were supplied", + )); + }; + if args.peek().is_some() { + return Err(error( + [Pair::new(generic_argument, comma_token)] + .into_iter() + .chain(args) + .collect(), + "PhantomConstGet takes a single type argument but too many generic arguments were supplied", + )); + }; + let GenericArgument::Type(ty) = generic_argument else { + return Err(error( + Punctuated::from_iter([Pair::new(generic_argument, comma_token)]), + "PhantomConstGet requires a type argument", + )); + }; + Ok(Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + }) + } + PathArguments::Parenthesized(_) => Err(error( + arguments, + "parenthetical generics are not valid for PhantomConstGet", + )), + } + } + pub(crate) fn parse_type_param_bound( + bound: TypeParamBound, + ) -> syn::Result> { + let TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + }) = bound + else { + return Ok(Err(bound)); + }; + Ok(match Self::parse_path_with_arguments(path)? { + Ok(v) => Ok(v), + Err(path) => Err(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + })), + }) + } +} + +impl ToTokens for PhantomConstGetBound { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = self; + phantom_const_get.to_tokens(tokens); + colon2_token.to_tokens(tokens); + lt_token.to_tokens(tokens); + ty.to_tokens(tokens); + comma_token.to_tokens(tokens); + gt_token.to_tokens(tokens); + } +} + +impl From for Path { + fn from(value: PhantomConstGetBound) -> Self { + let PhantomConstGetBound { + phantom_const_get, + colon2_token, + lt_token, + ty, + comma_token, + gt_token, + } = value; + let mut path = phantom_const_get.path; + path.segments.last_mut().expect("known to exist").arguments = + PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token, + lt_token, + args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]), + gt_token, + }); + path + } +} + +impl From for TraitBound { + fn from(value: PhantomConstGetBound) -> Self { + let path = Path::from(value); + TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + } + } +} + +impl From for TypeParamBound { + fn from(value: PhantomConstGetBound) -> Self { + TraitBound::from(value).into() + } +} + macro_rules! impl_bounds { ( #[struct = $struct_type:ident] @@ -2072,6 +2240,10 @@ macro_rules! impl_bounds { $( $Variant:ident, )* + $( + #[has_body] + $VariantHasBody:ident($variant_has_body_ty:ty), + )* $( #[unknown] $Unknown:ident, @@ -2081,6 +2253,7 @@ macro_rules! impl_bounds { #[derive(Clone, Debug)] $vis enum $enum_type { $($Variant(known_items::$Variant),)* + $($VariantHasBody($variant_has_body_ty),)* $($Unknown(syn::TypeParamBound),)? } @@ -2090,31 +2263,42 @@ macro_rules! impl_bounds { } })* + $(impl From<$variant_has_body_ty> for $enum_type { + fn from(v: $variant_has_body_ty) -> Self { + Self::$VariantHasBody(v) + } + })* + impl ToTokens for $enum_type { fn to_tokens(&self, tokens: &mut TokenStream) { match self { $(Self::$Variant(v) => v.to_tokens(tokens),)* + $(Self::$VariantHasBody(v) => v.to_tokens(tokens),)* $(Self::$Unknown(v) => v.to_tokens(tokens),)? } } } impl $enum_type { - $vis fn parse_path(path: Path) -> Result { + $vis fn parse_path_with_arguments(path: Path) -> syn::Result> { #![allow(unreachable_code)] $(let path = match known_items::$Variant::parse_path(path) { - Ok(v) => return Ok(Self::$Variant(v)), + Ok(v) => return Ok(Ok(Self::$Variant(v))), Err(path) => path, };)* - $(return Ok(Self::$Unknown(syn::TraitBound { + $(let path = match <$variant_has_body_ty>::parse_path_with_arguments(path)? { + Ok(v) => return Ok(Ok(Self::$VariantHasBody(v))), + Err(path) => path, + };)* + $(return Ok(Ok(Self::$Unknown(syn::TraitBound { paren_token: None, modifier: syn::TraitBoundModifier::None, lifetimes: None, path, - }.into()));)? - Err(path) + }.into())));)? + Ok(Err(path)) } - $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result { + $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> syn::Result> { #![allow(unreachable_code)] if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound { if let syn::TraitBound { @@ -2123,24 +2307,24 @@ macro_rules! impl_bounds { lifetimes: None, path: _, } = trait_bound { - match Self::parse_path(trait_bound.path) { - Ok(retval) => return Ok(retval), + match Self::parse_path_with_arguments(trait_bound.path)? { + Ok(retval) => return Ok(Ok(retval)), Err(path) => trait_bound.path = path, } } type_param_bound = trait_bound.into(); } - $(return Ok(Self::$Unknown(type_param_bound));)? - Err(type_param_bound) + $(return Ok(Ok(Self::$Unknown(type_param_bound)));)? + Ok(Err(type_param_bound)) } } impl Parse for $enum_type { fn parse(input: ParseStream) -> syn::Result { - Self::parse_type_param_bound(input.parse()?) + Self::parse_type_param_bound(input.parse()?)? .map_err(|type_param_bound| syn::Error::new_spanned( type_param_bound, - format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), + format_args!("expected one of: {}", [$(stringify!($Variant),)* $(stringify!($VariantHasBody)),*].join(", ")), )) } } @@ -2149,6 +2333,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct $struct_type { $($vis $Variant: Option,)* + $($vis $VariantHasBody: Option<$variant_has_body_ty>,)* $($vis $Unknown: Vec,)? } @@ -2161,6 +2346,11 @@ macro_rules! impl_bounds { separator = Some(::default()); v.to_tokens(tokens); })* + $(if let Some(v) = &self.$VariantHasBody { + separator.to_tokens(tokens); + separator = Some(::default()); + v.to_tokens(tokens); + })* $(for v in &self.$Unknown { separator.to_tokens(tokens); separator = Some(::default()); @@ -2174,6 +2364,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct Iter { $($Variant: Option,)* + $($VariantHasBody: Option<$variant_has_body_ty>,)* $($Unknown: std::vec::IntoIter,)? } @@ -2184,6 +2375,7 @@ macro_rules! impl_bounds { fn into_iter(self) -> Self::IntoIter { Iter { $($Variant: self.$Variant,)* + $($VariantHasBody: self.$VariantHasBody,)* $($Unknown: self.$Unknown.into_iter(),)? } } @@ -2198,6 +2390,11 @@ macro_rules! impl_bounds { return Some($enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$VariantHasBody.take() { + return Some($enum_type::$VariantHasBody(value)); + } + )* $( if let Some(value) = self.$Unknown.next() { return Some($enum_type::$Unknown(value)); @@ -2213,6 +2410,11 @@ macro_rules! impl_bounds { init = f(init, $enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$VariantHasBody.take() { + init = f(init, $enum_type::$VariantHasBody(value)); + } + )* $( if let Some(value) = self.$Unknown.next() { init = f(init, $enum_type::$Unknown(value)); @@ -2229,6 +2431,9 @@ macro_rules! impl_bounds { $($enum_type::$Variant(v) => { self.$Variant = Some(v); })* + $($enum_type::$VariantHasBody(v) => { + self.$VariantHasBody = Some(v); + })* $($enum_type::$Unknown(v) => { self.$Unknown.push(v); })? @@ -2250,6 +2455,9 @@ macro_rules! impl_bounds { $(if let Some(v) = v.$Variant { self.$Variant = Some(v); })* + $(if let Some(v) = v.$VariantHasBody { + self.$VariantHasBody = Some(v); + })* $(self.$Unknown.extend(v.$Unknown);)* }); } @@ -2304,6 +2512,8 @@ impl_bounds! { Size, StaticType, Type, + #[has_body] + PhantomConstGet(PhantomConstGetBound), #[unknown] Unknown, } @@ -2319,6 +2529,8 @@ impl_bounds! { ResetType, StaticType, Type, + #[has_body] + PhantomConstGet(PhantomConstGetBound), #[unknown] Unknown, } @@ -2334,6 +2546,7 @@ impl From for ParsedBound { ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v), + ParsedTypeBound::PhantomConstGet(v) => ParsedBound::PhantomConstGet(v), ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v), } } @@ -2349,6 +2562,7 @@ impl From for ParsedBounds { ResetType, StaticType, Type, + PhantomConstGet, Unknown, } = value; Self { @@ -2361,6 +2575,7 @@ impl From for ParsedBounds { Size: None, StaticType, Type, + PhantomConstGet, Unknown, } } @@ -2397,6 +2612,10 @@ impl ParsedTypeBound { ParsedTypeBound::Type(known_items::Type(span)), ]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), + Self::PhantomConstGet(v) => ParsedTypeBounds::from_iter([ + ParsedTypeBound::from(v), + ParsedTypeBound::Type(known_items::Type(span)), + ]), Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]), } } @@ -2432,6 +2651,7 @@ impl From for ParsedBounds { Size, StaticType: None, Type: None, + PhantomConstGet: None, Unknown: vec![], } } @@ -2534,6 +2754,9 @@ impl ParsedBound { Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), + Self::PhantomConstGet(v) => { + ParsedBoundCategory::Type(ParsedTypeBound::PhantomConstGet(v)) + } Self::Unknown(v) => ParsedBoundCategory::Unknown(v), } } @@ -3419,7 +3642,8 @@ impl ParsedGenerics { | ParsedTypeBound::BundleType(_) | ParsedTypeBound::EnumType(_) | ParsedTypeBound::IntType(_) - | ParsedTypeBound::ResetType(_) => { + | ParsedTypeBound::ResetType(_) + | ParsedTypeBound::PhantomConstGet(_) => { errors.error(bound, "bounds on mask types are not implemented"); } ParsedTypeBound::StaticType(bound) => { diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 98849a67..96ee1f71 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -166,7 +166,7 @@ pub use fayalite_proc_macros::hdl_module; /// // you can then use it in other types: /// /// #[hdl(no_static)] -/// pub struct WrapMyArray> { +/// pub struct WrapMyArray> { /// pub my_array: GetMyArray

, /// } /// @@ -202,7 +202,7 @@ pub use fayalite_proc_macros::hdl_module; /// // you can then use it in other types: /// /// #[hdl(no_static)] -/// pub struct FlagPerItem> { +/// pub struct FlagPerItem> { /// pub flags: ArrayType>, /// } /// diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 8742bb0b..148cb642 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -210,7 +210,7 @@ pub type GetA> = DynSize; pub type GetB> = UInt; #[hdl(outline_generated, no_static)] -pub struct MyTypeWithPhantomConstParameter> { +pub struct MyTypeWithPhantomConstParameter> { pub a: ArrayType>, pub b: HdlOption>, } From 0be9f9ce2329fd455a40f87e70c51a3ab672cc40 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 27 Oct 2025 22:57:12 -0700 Subject: [PATCH 04/10] fix JobGraph::run to not busy-wait --- crates/fayalite/src/build/graph.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index d81b282f..bed88295 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -703,8 +703,12 @@ impl JobGraph { } let mut running_jobs = HashMap::default(); let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel(); + let mut next_finished_job = None; loop { - while let Some(finished_job) = finished_jobs_receiver.try_recv().ok() { + if let Some(finished_job) = next_finished_job + .take() + .or_else(|| finished_jobs_receiver.try_recv().ok()) + { let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job) else { unreachable!(); @@ -736,6 +740,7 @@ impl JobGraph { } } } + continue; } if let Some(WaitingJobState { job_node_id, @@ -791,12 +796,15 @@ impl JobGraph { .expect("failed to spawn thread for job"), }, ); + continue; } if running_jobs.is_empty() { assert!(item_name_to_waiting_jobs_map.is_empty()); assert!(ready_jobs.is_empty()); return Ok(()); } + // nothing to do yet, block to avoid busy waiting + next_finished_job = finished_jobs_receiver.recv().ok(); } }) } From c11a1743f97b5219725d123d876effe717b633f2 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 30 Oct 2025 21:14:05 -0700 Subject: [PATCH 05/10] add sim.fork_join() and fix Simulator to handle running futures with arbitrary wakers --- crates/fayalite/src/sim.rs | 1245 +++++++++----- crates/fayalite/tests/sim.rs | 82 + .../fayalite/tests/sim/expected/array_rw.txt | 8 +- .../expected/conditional_assignment_last.txt | 8 +- .../tests/sim/expected/connect_const.txt | 8 +- .../sim/expected/connect_const_reset.txt | 8 +- .../tests/sim/expected/counter_async.txt | 12 +- .../tests/sim/expected/counter_async.vcd | 64 +- .../tests/sim/expected/counter_sync.txt | 12 +- .../tests/sim/expected/counter_sync.vcd | 66 +- .../tests/sim/expected/duplicate_names.txt | 8 +- crates/fayalite/tests/sim/expected/enums.txt | 10 +- .../tests/sim/expected/extern_module.txt | 15 +- .../tests/sim/expected/extern_module.vcd | 3 +- .../tests/sim/expected/extern_module2.txt | 155 +- .../tests/sim/expected/extern_module2.vcd | 3 +- .../tests/sim/expected/many_memories.txt | 8 +- .../tests/sim/expected/many_memories.vcd | 48 +- .../fayalite/tests/sim/expected/memories.txt | 8 +- .../fayalite/tests/sim/expected/memories.vcd | 14 +- .../fayalite/tests/sim/expected/memories2.txt | 8 +- .../fayalite/tests/sim/expected/memories2.vcd | 12 +- .../fayalite/tests/sim/expected/memories3.txt | 8 +- .../fayalite/tests/sim/expected/memories3.vcd | 120 +- crates/fayalite/tests/sim/expected/mod1.txt | 8 +- .../tests/sim/expected/ripple_counter.txt | 445 +++-- .../tests/sim/expected/ripple_counter.vcd | 624 ++++--- .../tests/sim/expected/shift_register.txt | 10 +- .../tests/sim/expected/shift_register.vcd | 12 +- .../tests/sim/expected/sim_fork_join.txt | 523 ++++++ .../tests/sim/expected/sim_fork_join.vcd | 1467 +++++++++++++++++ .../tests/sim/expected/sim_only_connects.txt | 300 ++-- .../tests/sim/expected/sim_only_connects.vcd | 17 +- 33 files changed, 4083 insertions(+), 1256 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 44030c14..35da3367 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -48,7 +48,7 @@ use num_traits::{Signed, Zero}; use std::{ any::Any, borrow::Cow, - cell::RefCell, + cell::{Cell, RefCell}, collections::BTreeMap, fmt, future::{Future, IntoFuture}, @@ -57,8 +57,9 @@ use std::{ pin::Pin, ptr, rc::Rc, - sync::Arc, + sync::{Arc, Mutex}, task::Poll, + usize, }; pub mod compiler; @@ -1262,149 +1263,11 @@ impl SimulationModuleState { } } -#[derive(Copy, Clone, Debug)] -enum WaitTarget { - Settle, - Instant(SimInstant), - Change { key: ChangeKey, value: ChangeValue }, -} - -#[derive(Clone)] -struct EarliestWaitTargets { - settle: bool, - instant: Option, - changes: HashMap, SimValue>, -} - -impl fmt::Debug for EarliestWaitTargets { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_set().entries(self.iter()).finish() - } -} - -impl Default for EarliestWaitTargets { - fn default() -> Self { - Self { - settle: false, - instant: None, - changes: HashMap::default(), - } - } -} - -impl EarliestWaitTargets { - fn settle() -> Self { - Self { - settle: true, - instant: None, - changes: HashMap::default(), - } - } - fn instant(instant: SimInstant) -> Self { - Self { - settle: false, - instant: Some(instant), - changes: HashMap::default(), - } - } - fn len(&self) -> usize { - self.settle as usize + self.instant.is_some() as usize + self.changes.len() - } - fn is_empty(&self) -> bool { - self.len() == 0 - } - fn clear(&mut self) { - let Self { - settle, - instant, - changes, - } = self; - *settle = false; - *instant = None; - changes.clear(); - } - fn insert( - &mut self, - value: impl std::borrow::Borrow, ChangeValue>>, - ) where - ChangeValue: std::borrow::Borrow>, - { - let value = value.borrow(); - match value { - WaitTarget::Settle => self.settle = true, - WaitTarget::Instant(instant) => { - if self.instant.is_none_or(|v| v > *instant) { - self.instant = Some(*instant); - } - } - WaitTarget::Change { key, value } => { - self.changes - .entry(*key) - .or_insert_with(|| value.borrow().clone()); - } - } - } - fn convert_earlier_instants_to_settle(&mut self, instant: SimInstant) { - if self.instant.is_some_and(|v| v <= instant) { - self.settle = true; - self.instant = None; - } - } - fn iter<'a>( - &'a self, - ) -> impl Clone - + Iterator, &'a SimValue>> - + 'a { - self.settle - .then_some(WaitTarget::Settle) - .into_iter() - .chain(self.instant.map(|instant| WaitTarget::Instant(instant))) - .chain( - self.changes - .iter() - .map(|(&key, value)| WaitTarget::Change { key, value }), - ) - } -} - -impl>> - Extend, ChangeValue>> for EarliestWaitTargets -{ - fn extend, ChangeValue>>>( - &mut self, - iter: T, - ) { - iter.into_iter().for_each(|v| self.insert(v)) - } -} - -impl<'a, ChangeValue: std::borrow::Borrow>> - Extend<&'a WaitTarget, ChangeValue>> for EarliestWaitTargets -{ - fn extend, ChangeValue>>>( - &mut self, - iter: T, - ) { - iter.into_iter().for_each(|v| self.insert(v)) - } -} - -impl FromIterator for EarliestWaitTargets -where - Self: Extend, -{ - fn from_iter>(iter: T) -> Self { - let mut retval = Self::default(); - retval.extend(iter); - retval - } -} - struct SimulationExternModuleState { module_state: SimulationModuleState, sim: ExternModuleSimulation, running_generator: Option + 'static>>>, - wait_targets: EarliestWaitTargets, + waker: Arc, } impl fmt::Debug for SimulationExternModuleState { @@ -1413,7 +1276,7 @@ impl fmt::Debug for SimulationExternModuleState { module_state, sim, running_generator, - wait_targets, + waker: _, } = self; f.debug_struct("SimulationExternModuleState") .field("module_state", module_state) @@ -1422,7 +1285,6 @@ impl fmt::Debug for SimulationExternModuleState { "running_generator", &running_generator.as_ref().map(|_| DebugAsDisplay("...")), ) - .field("wait_targets", wait_targets) .finish() } } @@ -1489,28 +1351,255 @@ impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { } } -struct GeneratorWaker; +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum EventKind { + State, + ExternModule(usize), +} -impl std::task::Wake for GeneratorWaker { - fn wake(self: Arc) { - panic!("can't await other kinds of futures in function passed to ExternalModuleSimulation"); +impl PartialOrd for EventKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } -#[derive(Default)] -struct ReadyToRunSet { - state_ready_to_run: bool, - extern_modules_ready_to_run: Vec, +impl Ord for EventKind { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match (self, other) { + (Self::State, Self::State) => std::cmp::Ordering::Equal, + (Self::State, Self::ExternModule(_)) => std::cmp::Ordering::Less, + (Self::ExternModule(_), Self::State) => std::cmp::Ordering::Greater, + (Self::ExternModule(this), Self::ExternModule(other)) => this.cmp(other), + } + } } -impl ReadyToRunSet { - fn clear(&mut self) { +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +struct Event { + instant: SimInstant, + kind: EventKind, +} + +impl PartialOrd for Event { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Event { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let Self { instant, kind } = other; + self.instant.cmp(instant).then(self.kind.cmp(kind)) + } +} + +struct HashableWaker(std::task::Waker); + +impl Clone for HashableWaker { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + + fn clone_from(&mut self, source: &Self) { + self.0.clone_from(&source.0); + } +} + +impl PartialEq for HashableWaker { + fn eq(&self, other: &Self) -> bool { + self.0.will_wake(&other.0) + } +} + +impl Eq for HashableWaker {} + +impl Hash for HashableWaker { + fn hash(&self, state: &mut H) { + self.0.data().hash(state); + let vtable: *const std::task::RawWakerVTable = self.0.vtable(); + vtable.hash(state); + } +} + +struct EventQueueData { + instant: SimInstant, + events: BTreeMap>, +} + +impl fmt::Debug for EventQueueData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct EventsDebug<'a>(&'a BTreeMap>); + impl fmt::Debug for EventsDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug_map = f.debug_map(); + for (k, v) in self.0 { + debug_map.entry(&format_args!("{k:?}"), &v.len()); + } + debug_map.finish() + } + } + let Self { instant, events } = self; + f.debug_struct("EventQueueData") + .field("instant", instant) + .field("events", &EventsDebug(events)) + .finish() + } +} + +struct EventQueueFirstEntry<'a>( + std::collections::btree_map::OccupiedEntry<'a, Event, HashSet>, +); + +impl<'a> EventQueueFirstEntry<'a> { + fn key(&self) -> &Event { + self.0.key() + } + fn remove(self) -> HashSet { + self.0.remove() + } +} + +impl EventQueueData { + fn new(instant: SimInstant) -> Self { + Self { + instant, + events: BTreeMap::new(), + } + } + fn instant(&self) -> SimInstant { + self.instant + } + fn set_instant(&mut self, instant: SimInstant) { + self.instant = instant; + } + fn add_event(&mut self, mut event: Event, waker: Option) { + event.instant = event.instant.max(self.instant); + self.events + .entry(event) + .or_default() + .extend(waker.map(HashableWaker)); + } + fn add_event_for_now(&mut self, kind: EventKind, waker: Option) { + self.add_event( + Event { + instant: self.instant, + kind, + }, + waker, + ); + } + fn first_entry(&mut self) -> Option> { + self.events.first_entry().map(EventQueueFirstEntry) + } + fn peek_first_event(&self) -> Option { + Some(*self.events.first_key_value()?.0) + } + fn peek_first_event_for_now(&self) -> Option { + self.peek_first_event() + .filter(|event| event.instant <= self.instant) + } +} + +struct EventQueue(Mutex); + +impl fmt::Debug for EventQueue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("EventQueue(")?; + if let Ok(data) = self.0.try_lock() { + (*data).fmt(f)?; + } else { + f.write_str("")?; + } + f.write_str(")") + } +} + +impl EventQueue { + fn new(data: EventQueueData) -> Self { + Self(Mutex::new(data)) + } + fn lock(&self) -> std::sync::MutexGuard<'_, EventQueueData> { + self.0.lock().expect("not poisoned") + } + fn add_event_for_now(&self, kind: EventKind, waker: Option) { + self.lock().add_event_for_now(kind, waker); + } + fn peek_first_event_for_now(&self) -> Option { + self.lock().peek_first_event_for_now() + } +} + +struct ExternModuleGeneratorWaker { + event_queue: std::sync::Weak, + module_index: usize, +} + +impl std::task::Wake for ExternModuleGeneratorWaker { + fn wake(self: Arc) { + self.wake_by_ref(); + } + fn wake_by_ref(self: &Arc) { + if let Some(event_queue) = self.event_queue.upgrade() { + event_queue.add_event_for_now(EventKind::ExternModule(self.module_index), None); + } + } +} + +struct SensitivitySet { + debug_id: u64, + compiled_values: HashSet>>, + values: Vec<( + Rc>, + Rc>, + )>, + waker: RefCell, + changed: Cell, +} + +struct SensitivitySetJustId<'a>(&'a SensitivitySet); + +impl fmt::Debug for SensitivitySetJustId<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.debug_fmt(true, f) + } +} + +impl fmt::Debug for SensitivitySet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt(false, f) + } +} + +impl SensitivitySet { + fn debug_fmt(&self, just_id: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { - state_ready_to_run, - extern_modules_ready_to_run, + debug_id, + compiled_values: _, + values, + waker: _, + changed, } = self; - *state_ready_to_run = false; - extern_modules_ready_to_run.clear(); + struct DebugValues<'a>( + &'a [( + Rc>, + Rc>, + )], + ); + impl<'a> fmt::Debug for DebugValues<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.0.iter().map(|(k, v)| (k, v))) + .finish() + } + } + let mut debug_struct = f.debug_struct("SensitivitySet"); + debug_struct.field("id", debug_id); + if !just_id { + debug_struct + .field("values", &DebugValues(values)) + .field("changed", changed); + } + debug_struct.finish_non_exhaustive() } } @@ -1519,15 +1608,22 @@ struct SimulationImpl { io: Expr, main_module: SimulationModuleState, extern_modules: Box<[SimulationExternModuleState]>, - state_ready_to_run: bool, trace_decls: TraceModule, traces: SimTraces]>>, trace_memories: BTreeMap, TraceMem>, trace_writers: Vec>, - instant: SimInstant, clocks_triggered: Interned<[StatePartIndex]>, breakpoints: Option, - generator_waker: std::task::Waker, + event_queue: Arc, + next_sensitivity_set_debug_id: u64, + waiting_sensitivity_sets_by_compiled_value: HashMap< + Rc>, + ( + Rc>, + HashMap<*const SensitivitySet, Rc>, + ), + >, + waiting_sensitivity_sets_by_address: HashMap<*const SensitivitySet, Rc>, } impl fmt::Debug for SimulationImpl { @@ -1536,6 +1632,70 @@ impl fmt::Debug for SimulationImpl { } } +struct DebugSensitivitySetsByAddress<'a> { + sensitivity_sets: &'a HashMap<*const SensitivitySet, Rc>, + just_id: bool, +} + +impl<'a> fmt::Debug for DebugSensitivitySetsByAddress<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + sensitivity_sets, + just_id, + } = *self; + let mut values: Vec<_> = sensitivity_sets.values().collect(); + values.sort_by_key(|v| v.debug_id); + if just_id { + f.debug_set() + .entries( + values + .iter() + .map(|sensitivity_set| SensitivitySetJustId(sensitivity_set)), + ) + .finish() + } else { + f.debug_set().entries(values).finish() + } + } +} + +struct DebugSensitivitySetsByCompiledValue<'a>( + &'a HashMap< + Rc>, + ( + Rc>, + HashMap<*const SensitivitySet, Rc>, + ), + >, +); + +impl<'a> fmt::Debug for DebugSensitivitySetsByCompiledValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut values: Vec<_> = self + .0 + .iter() + .map(|(compiled_value, (sim_value, sensitivity_sets))| { + ( + DebugAsDisplay(if f.alternate() { + format!("{compiled_value:#?}") + } else { + format!("{compiled_value:?}") + }), + ( + sim_value, + DebugSensitivitySetsByAddress { + sensitivity_sets, + just_id: true, + }, + ), + ) + }) + .collect(); + values.sort_by(|l, r| l.0.0.cmp(&r.0.0)); + f.debug_map().entries(values).finish() + } +} + impl SimulationImpl { fn debug_fmt(&self, io: Option<&dyn fmt::Debug>, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { @@ -1543,38 +1703,58 @@ impl SimulationImpl { io: self_io, main_module, extern_modules, - state_ready_to_run, trace_decls, traces, trace_memories, trace_writers, - instant, clocks_triggered, breakpoints: _, - generator_waker: _, + event_queue, + next_sensitivity_set_debug_id: _, + waiting_sensitivity_sets_by_compiled_value, + waiting_sensitivity_sets_by_address, } = self; f.debug_struct("Simulation") .field("state", state) .field("io", io.unwrap_or(self_io)) .field("main_module", main_module) .field("extern_modules", extern_modules) - .field("state_ready_to_run", state_ready_to_run) .field("trace_decls", trace_decls) .field("traces", traces) .field("trace_memories", trace_memories) .field("trace_writers", trace_writers) - .field("instant", instant) .field("clocks_triggered", clocks_triggered) + .field("event_queue", event_queue) + .field( + "waiting_sensitivity_sets_by_address", + &DebugSensitivitySetsByAddress { + sensitivity_sets: waiting_sensitivity_sets_by_address, + just_id: false, + }, + ) + .field( + "waiting_sensitivity_sets_by_compiled_value", + &DebugSensitivitySetsByCompiledValue(waiting_sensitivity_sets_by_compiled_value), + ) .finish_non_exhaustive() } fn new(compiled: Compiled) -> Self { let io_target = Target::from(compiled.io); - let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( - |&CompiledExternModule { - module_io_targets, - module_io, - simulation, - }| { + let mut event_queue = EventQueueData::new(SimInstant::START); + event_queue.add_event_for_now(EventKind::State, None); + for module_index in 0..compiled.extern_modules.len() { + event_queue.add_event_for_now(EventKind::ExternModule(module_index), None); + } + let event_queue = Arc::new(EventQueue::new(event_queue)); + let extern_modules = Box::from_iter(compiled.extern_modules.iter().enumerate().map( + |( + module_index, + &CompiledExternModule { + module_io_targets, + module_io, + simulation, + }, + )| { SimulationExternModuleState { module_state: SimulationModuleState::new( module_io_targets @@ -1584,7 +1764,10 @@ impl SimulationImpl { ), sim: simulation, running_generator: None, - wait_targets: EarliestWaitTargets::settle(), + waker: Arc::new(ExternModuleGeneratorWaker { + event_queue: Arc::downgrade(&event_queue), + module_index, + }), } }, )); @@ -1609,7 +1792,6 @@ impl SimulationImpl { }), ), extern_modules, - state_ready_to_run: true, trace_decls: compiled.base_module.trace_decls, traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( |&SimTrace { @@ -1624,10 +1806,12 @@ impl SimulationImpl { ))), trace_memories: BTreeMap::from_iter(compiled.trace_memories.iter().copied()), trace_writers: vec![], - instant: SimInstant::START, clocks_triggered: compiled.clocks_triggered, breakpoints: None, - generator_waker: Arc::new(GeneratorWaker).into(), + event_queue, + next_sensitivity_set_debug_id: 0, + waiting_sensitivity_sets_by_compiled_value: HashMap::default(), + waiting_sensitivity_sets_by_address: HashMap::default(), } } fn write_traces( @@ -1782,137 +1966,170 @@ impl SimulationImpl { } #[track_caller] fn advance_time(this_ref: &Rc>, duration: SimDuration) { - let run_target = this_ref.borrow().instant + duration; - Self::run_until(this_ref, run_target); + Self::run_until(this_ref, &mut |instant| instant.checked_add(duration)); } - /// clears `targets` - #[must_use] - fn yield_wait<'a>( - this: Rc>, - module_index: usize, - targets: &'a mut EarliestWaitTargets, - ) -> impl Future + 'a { - struct MyGenerator<'a> { - sim: Rc>, - yielded_at_all: bool, - module_index: usize, - targets: &'a mut EarliestWaitTargets, + fn wake_after_change(&mut self, sensitivity_set: Rc) { + let None = self + .waiting_sensitivity_sets_by_address + .insert(Rc::as_ptr(&sensitivity_set), sensitivity_set.clone()) + else { + // already added + return; + }; + let SensitivitySet { + debug_id: _, + compiled_values: _, + values, + waker: _, + changed: _, + } = &*sensitivity_set; + for (compiled_value, sim_value) in values { + self.waiting_sensitivity_sets_by_compiled_value + .entry(compiled_value.clone()) + .or_insert_with(|| (sim_value.clone(), HashMap::default())) + .1 + .insert(Rc::as_ptr(&sensitivity_set), sensitivity_set.clone()); } - impl Future for MyGenerator<'_> { - type Output = (); - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll { - let this = &mut *self; - let mut sim = this.sim.borrow_mut(); - let sim = &mut *sim; - assert!( - cx.waker().will_wake(&sim.generator_waker), - "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation" - ); - this.targets.convert_earlier_instants_to_settle(sim.instant); - if this.targets.is_empty() { - this.targets.settle = true; + } + fn cancel_wake_after_change(&mut self, sensitivity_set: &Rc) { + let Some(_) = self + .waiting_sensitivity_sets_by_address + .remove(&Rc::as_ptr(&sensitivity_set)) + else { + return; + }; + let SensitivitySet { + debug_id: _, + compiled_values: _, + values, + waker: _, + changed: _, + } = &**sensitivity_set; + for (compiled_value, _) in values { + if let hashbrown::hash_map::Entry::Occupied(mut entry) = self + .waiting_sensitivity_sets_by_compiled_value + .entry(compiled_value.clone()) + { + entry.get_mut().1.remove(&Rc::as_ptr(&sensitivity_set)); + if entry.get().1.is_empty() { + entry.remove(); } - if this.targets.settle { - if this.yielded_at_all { - this.targets.clear(); - return Poll::Ready(()); - } - } - sim.extern_modules[this.module_index] - .wait_targets - .extend(this.targets.iter()); - this.targets.clear(); - this.yielded_at_all = true; - Poll::Pending } } - MyGenerator { - sim: this, - yielded_at_all: false, - module_index, - targets, - } + } + fn try_wake_at_instant( + &mut self, + module_index: usize, + instant: &mut dyn FnMut(SimInstant) -> Option, + waker: std::task::Waker, + ) -> Result<(), std::task::Waker> { + let mut event_queue = self.event_queue.lock(); + let Some(instant) = instant(event_queue.instant()) else { + return Err(waker); + }; + event_queue.add_event( + Event { + instant, + kind: EventKind::ExternModule(module_index), + }, + Some(waker), + ); + Ok(()) } async fn yield_advance_time_or_settle( - this: Rc>, + this: &RefCell, module_index: usize, duration: Option, ) { - let mut targets = duration.map_or(EarliestWaitTargets::settle(), |duration| { - EarliestWaitTargets::instant(this.borrow().instant + duration) - }); - Self::yield_wait(this, module_index, &mut targets).await; + let mut target_instant = None; + std::future::poll_fn(|cx| { + match this.borrow_mut().try_wake_at_instant( + module_index, + &mut |instant| match target_instant { + Some(target_instant) => { + // already waited at least once + if instant < target_instant { + Some(target_instant) + } else { + None + } + } + None => { + target_instant = instant.checked_add(duration.unwrap_or(SimDuration::ZERO)); + target_instant + } + }, + cx.waker().clone(), + ) { + Ok(()) => Poll::Pending, + Err(_waker) => { + if target_instant.is_none() { + // don't panic in try_wait_at_instant to avoid poisoning the lock + panic!("SimInstant overflow"); + } + Poll::Ready(()) + } + } + }) + .await } - fn is_extern_module_ready_to_run(&mut self, module_index: usize) -> Option { - let module = &self.extern_modules[module_index]; - let mut retval = None; - for wait_target in module.wait_targets.iter() { - retval = match (wait_target, retval) { - (WaitTarget::Settle, _) => Some(self.instant), - (WaitTarget::Instant(instant), _) if instant <= self.instant => Some(self.instant), - (WaitTarget::Instant(instant), None) => Some(instant), - (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), - (WaitTarget::Change { key, value }, retval) => { - if Self::value_changed(&mut self.state, key, SimValue::opaque(value)) { - Some(self.instant) - } else { - retval + async fn yield_wait_for_changes( + this: &RefCell, + module_index: usize, + sensitivity_set: SensitivitySet, + timeout: Option, + ) { + if let Some(timeout) = timeout { + if timeout == SimDuration::ZERO { + return Self::yield_advance_time_or_settle(this, module_index, None).await; + } + } + let sensitivity_set = Rc::new(sensitivity_set); + let mut timeout_instant = None; + std::future::poll_fn(|cx| { + if sensitivity_set.changed.get() { + return Poll::Ready(()); + } + sensitivity_set.waker.borrow_mut().clone_from(cx.waker()); + let mut this = this.borrow_mut(); + this.wake_after_change(sensitivity_set.clone()); + if let Some(timeout) = timeout { + match this.try_wake_at_instant( + module_index, + &mut |instant| match timeout_instant { + Some(timeout_instant) => { + if instant < timeout_instant { + Some(timeout_instant) + } else { + None + } + } + None => { + timeout_instant = instant.checked_add(timeout); + timeout_instant + } + }, + cx.waker().clone(), + ) { + Ok(()) => Poll::Pending, + Err(_waker) => { + if timeout_instant.is_none() { + // don't panic in try_wait_at_instant to avoid poisoning the lock + panic!("SimInstant overflow"); + } + Poll::Ready(()) } } - }; - if retval == Some(self.instant) { - break; - } - } - retval - } - fn get_ready_to_run_set(&mut self, ready_to_run_set: &mut ReadyToRunSet) -> Option { - ready_to_run_set.clear(); - let mut retval = None; - if self.state_ready_to_run { - ready_to_run_set.state_ready_to_run = true; - retval = Some(self.instant); - } - for module_index in 0..self.extern_modules.len() { - let Some(instant) = self.is_extern_module_ready_to_run(module_index) else { - continue; - }; - if let Some(retval) = &mut retval { - match instant.cmp(retval) { - std::cmp::Ordering::Less => ready_to_run_set.clear(), - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => continue, - } } else { - retval = Some(instant); + Poll::Pending } - ready_to_run_set - .extern_modules_ready_to_run - .push(module_index); - } - retval - } - fn set_instant_no_sim(&mut self, instant: SimInstant) { - self.instant = instant; - self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { - match &mut trace_writer_state { - TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), - TraceWriterState::Running(trace_writer) => { - trace_writer.change_time_to(this.instant)?; - } - TraceWriterState::Errored(_) => {} - } - Ok(trace_writer_state) - }); + }) + .await; + this.borrow_mut().cancel_wake_after_change(&sensitivity_set); } #[must_use] #[track_caller] - fn run_state_settle_cycle(&mut self) -> bool { - self.state_ready_to_run = false; + fn run_state_settle_cycle(&mut self) { self.state.setup_call(0); if self.breakpoints.is_some() { loop { @@ -1943,138 +2160,197 @@ impl SimulationImpl { .iter() .any(|i| self.state.small_slots[*i] != 0) { - self.state_ready_to_run = true; - true - } else { - false + self.event_queue.add_event_for_now(EventKind::State, None); } } #[track_caller] - fn run_extern_modules_cycle( + fn run_extern_module(this_ref: &Rc>, module_index: usize) { + let mut this = this_ref.borrow_mut(); + let extern_module = &mut this.extern_modules[module_index]; + let waker = std::task::Waker::from(extern_module.waker.clone()); + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + return; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(&waker)) + { + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); + } + this.extern_modules[module_index].running_generator = generator; + } + fn check_waiting_sensitivity_sets(&mut self) { + if let Some(Event { + instant: _, + kind: EventKind::State, + }) = self.event_queue.peek_first_event_for_now() + { + return; + } + let mut triggered = HashMap::<*const SensitivitySet, Rc>::default(); + for (compiled_value, (sim_value, sensitivity_sets)) in + &self.waiting_sensitivity_sets_by_compiled_value + { + if Self::value_changed( + &mut self.state, + **compiled_value, + SimValue::opaque(sim_value), + ) { + triggered.extend(sensitivity_sets.iter().map(|(&k, v)| (k, v.clone()))); + } + } + for sensitivity_set in triggered.into_values() { + sensitivity_set.changed.set(true); + sensitivity_set.waker.borrow().wake_by_ref(); + self.cancel_wake_after_change(&sensitivity_set); + } + } + fn write_traces_after_event(&mut self) { + if let Some(Event { + instant: _, + kind: EventKind::State, + }) = self.event_queue.peek_first_event_for_now() + { + return; + } + if self.main_module.did_initial_settle { + self.read_traces::(); + } else { + self.read_traces::(); + } + self.state.memory_write_log.sort_unstable(); + self.state.memory_write_log.dedup(); + self.main_module.did_initial_settle = true; + self.for_each_trace_writer_storing_error(|this, trace_writer_state| { + Ok(match trace_writer_state { + TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( + this.init_trace_writer(trace_writer_decls.write_decls( + this.trace_decls, + this.traces.0.len(), + this.trace_memories.len(), + )?)?, + ), + TraceWriterState::Init(trace_writer) => { + TraceWriterState::Running(this.init_trace_writer(trace_writer)?) + } + TraceWriterState::Running(trace_writer) => { + TraceWriterState::Running(this.update_trace_writer(trace_writer)?) + } + TraceWriterState::Errored(e) => TraceWriterState::Errored(e), + }) + }); + self.state.memory_write_log.clear(); + } + fn write_traces_change_time_to(&mut self, new_instant: SimInstant) { + self.for_each_trace_writer_storing_error(|_this, mut trace_writer_state| { + match &mut trace_writer_state { + TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), + TraceWriterState::Running(trace_writer) => { + trace_writer.change_time_to(new_instant)?; + } + TraceWriterState::Errored(_) => {} + } + Ok(trace_writer_state) + }); + } + #[track_caller] + fn run_until( this_ref: &Rc>, - generator_waker: &std::task::Waker, - extern_modules_ready_to_run: &[usize], + run_target: &mut dyn FnMut(SimInstant) -> Option, ) { let mut this = this_ref.borrow_mut(); - for module_index in extern_modules_ready_to_run.iter().copied() { - let extern_module = &mut this.extern_modules[module_index]; - extern_module.wait_targets.clear(); - let mut generator = if !extern_module.module_state.did_initial_settle { - let sim = extern_module.sim; - drop(this); - Box::into_pin(sim.run(ExternModuleSimulationState { - sim_impl: this_ref.clone(), - module_index, - wait_for_changes_wait_targets: EarliestWaitTargets::default(), - })) - } else if let Some(generator) = extern_module.running_generator.take() { - drop(this); - generator - } else { - continue; - }; - let generator = match generator - .as_mut() - .poll(&mut std::task::Context::from_waker(generator_waker)) - { - Poll::Ready(()) => None, - Poll::Pending => Some(generator), - }; - this = this_ref.borrow_mut(); - this.extern_modules[module_index] - .module_state - .did_initial_settle = true; - if !this.extern_modules[module_index] - .module_state - .uninitialized_ios - .is_empty() - { - panic!( - "extern module didn't initialize all outputs before \ - waiting, settling, or reading any inputs: {}", - this.extern_modules[module_index].sim.source_location - ); - } - this.extern_modules[module_index].running_generator = generator; - } - } - /// clears `targets` - #[track_caller] - fn run_until(this_ref: &Rc>, run_target: SimInstant) { - let mut this = this_ref.borrow_mut(); - let mut ready_to_run_set = ReadyToRunSet::default(); - let generator_waker = this.generator_waker.clone(); assert!( this.main_module.uninitialized_ios.is_empty(), "didn't initialize all inputs", ); - let run_target = run_target.max(this.instant); + this.check_waiting_sensitivity_sets(); + let mut event_queue = this.event_queue.lock(); + let Some(run_target) = run_target(event_queue.instant()) else { + drop(event_queue); + panic!("SimInstant overflowed"); + }; + let run_target = run_target.max(event_queue.instant()); let mut settle_cycle = 0; - let mut run_extern_modules = true; loop { - assert!(settle_cycle < 100000, "settle(): took too many steps"); + if settle_cycle >= 100000 { + drop(event_queue); + panic!("settle(): took too many steps"); + } settle_cycle += 1; - let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) { - Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, - _ => break, + let event_queue_instant = event_queue.instant(); + let Some(first_entry) = event_queue.first_entry() else { + let changed_time = event_queue_instant != run_target; + event_queue.set_instant(run_target); + drop(event_queue); + if changed_time { + this.write_traces_change_time_to(run_target); + } + return; }; - if next_wait_target > this.instant { - settle_cycle = 0; - this.set_instant_no_sim(next_wait_target); - } - if run_extern_modules { - drop(this); - Self::run_extern_modules_cycle( - this_ref, - &generator_waker, - &ready_to_run_set.extern_modules_ready_to_run, - ); - this = this_ref.borrow_mut(); - } - if ready_to_run_set.state_ready_to_run { - if this.run_state_settle_cycle() { - // wait for clocks to settle before running extern modules again - run_extern_modules = false; - } else { - run_extern_modules = true; + let Event { + instant: event_instant, + kind: event_kind, + } = *first_entry.key(); + if event_instant <= event_queue_instant { + let wakers = first_entry.remove(); + drop(event_queue); + for HashableWaker(waker) in wakers { + waker.wake(); + } + match event_kind { + EventKind::State => this.run_state_settle_cycle(), + EventKind::ExternModule(module_index) => { + drop(this); + Self::run_extern_module(this_ref, module_index); + this = this_ref.borrow_mut(); + } + } + this.write_traces_after_event(); + this.check_waiting_sensitivity_sets(); + } else { + let new_instant = event_instant.min(run_target); + let changed_time = event_queue_instant != new_instant; + event_queue.set_instant(new_instant); + drop(event_queue); + if changed_time { + this.write_traces_change_time_to(new_instant); + } + if event_instant > run_target { + return; } } - if this.main_module.did_initial_settle { - this.read_traces::(); - } else { - this.read_traces::(); - } - this.state.memory_write_log.sort_unstable(); - this.state.memory_write_log.dedup(); - this.main_module.did_initial_settle = true; - this.for_each_trace_writer_storing_error(|this, trace_writer_state| { - Ok(match trace_writer_state { - TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( - this.init_trace_writer(trace_writer_decls.write_decls( - this.trace_decls, - this.traces.0.len(), - this.trace_memories.len(), - )?)?, - ), - TraceWriterState::Init(trace_writer) => { - TraceWriterState::Running(this.init_trace_writer(trace_writer)?) - } - TraceWriterState::Running(trace_writer) => { - TraceWriterState::Running(this.update_trace_writer(trace_writer)?) - } - TraceWriterState::Errored(e) => TraceWriterState::Errored(e), - }) - }); - this.state.memory_write_log.clear(); - } - if run_target > this.instant { - this.set_instant_no_sim(run_target); + event_queue = this.event_queue.lock(); } } #[track_caller] fn settle(this_ref: &Rc>) { - let run_target = this_ref.borrow().instant; - Self::run_until(this_ref, run_target); + Self::run_until(this_ref, &mut Some); } fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { match which_module { @@ -2106,7 +2382,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); match compiled_value.range.len().as_single() { Some(TypeLenSingle::SmallSlot) => { self.state.small_slots[compiled_value.range.small_slots.start] = value as _; @@ -2138,7 +2414,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(Expr::canonical(io), which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); let value: BigInt = value.into(); match compiled_value.range.len().as_single() { Some(TypeLenSingle::SmallSlot) => { @@ -2378,7 +2654,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.state_ready_to_run = true; + self.event_queue.add_event_for_now(EventKind::State, None); assert_eq!(Expr::ty(io), SimValue::ty(value)); Self::read_write_sim_value_helper( &mut self.state, @@ -2426,7 +2702,7 @@ impl SimulationImpl { } } async fn yield_settle_if_needed( - this_ref: &Rc>, + this_ref: &RefCell, module_index: usize, v: MaybeNeedsSettle, ) -> O @@ -2435,7 +2711,7 @@ impl SimulationImpl { { match v { MaybeNeedsSettle::NeedsSettle(v) => { - Self::yield_advance_time_or_settle(this_ref.clone(), module_index, None).await; + Self::yield_advance_time_or_settle(this_ref, module_index, None).await; v.call(&mut this_ref.borrow_mut().state) } MaybeNeedsSettle::NoSettleNeeded(v) => v, @@ -2786,7 +3062,6 @@ impl Simulation { pub struct ExternModuleSimulationState { sim_impl: Rc>, module_index: usize, - wait_for_changes_wait_targets: EarliestWaitTargets, } impl fmt::Debug for ExternModuleSimulationState { @@ -2794,7 +3069,6 @@ impl fmt::Debug for ExternModuleSimulationState { let Self { sim_impl: _, module_index, - wait_for_changes_wait_targets: _, } = self; f.debug_struct("ExternModuleSimulationState") .field("sim_impl", &DebugAsDisplay("...")) @@ -2805,12 +3079,11 @@ impl fmt::Debug for ExternModuleSimulationState { impl ExternModuleSimulationState { pub async fn settle(&mut self) { - SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) - .await + SimulationImpl::yield_advance_time_or_settle(&self.sim_impl, self.module_index, None).await } pub async fn advance_time(&mut self, duration: SimDuration) { SimulationImpl::yield_advance_time_or_settle( - self.sim_impl.clone(), + &self.sim_impl, self.module_index, Some(duration), ) @@ -2821,7 +3094,17 @@ impl ExternModuleSimulationState { iter: I, timeout: Option, ) { - self.wait_for_changes_wait_targets.clear(); + let mut sim_impl = self.sim_impl.borrow_mut(); + let mut sensitivity_set = SensitivitySet { + debug_id: sim_impl.next_sensitivity_set_debug_id, + compiled_values: HashSet::default(), + values: Vec::new(), + waker: RefCell::new(std::task::Waker::noop().clone()), + changed: Cell::new(false), + }; + sim_impl.next_sensitivity_set_debug_id = + sim_impl.next_sensitivity_set_debug_id.wrapping_add(1); + drop(sim_impl); let which_module = WhichModule::Extern { module_index: self.module_index, }; @@ -2829,19 +3112,18 @@ impl ExternModuleSimulationState { let io = Expr::canonical(io.to_expr()); let (key, value) = self.sim_impl.borrow_mut().read(io, which_module); let value = self.settle_if_needed(value).await; - self.wait_for_changes_wait_targets - .insert(WaitTarget::Change { key, value }); + let key = Rc::new(key); + if sensitivity_set.compiled_values.insert(key.clone()) { + sensitivity_set.values.push((key, Rc::new(value))); + } } - if let Some(timeout) = timeout { - self.wait_for_changes_wait_targets.instant = - Some(self.sim_impl.borrow().instant + timeout); - } - SimulationImpl::yield_wait( - self.sim_impl.clone(), + SimulationImpl::yield_wait_for_changes( + &self.sim_impl, self.module_index, - &mut self.wait_for_changes_wait_targets, + sensitivity_set, + timeout, ) - .await; + .await } pub async fn wait_for_clock_edge(&mut self, clk: impl ToExpr) { let clk = clk.to_expr(); @@ -2858,6 +3140,19 @@ impl ExternModuleSimulationState { { SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await } + pub async fn fork_join(&mut self, futures: F) -> F::Output { + F::fork_join(futures, self).await + } + fn forked_state(&self) -> Self { + let Self { + ref sim_impl, + module_index, + } = *self; + Self { + sim_impl: sim_impl.clone(), + module_index, + } + } impl_simulation_methods!( async_await = (async, await), track_caller = (), @@ -2865,6 +3160,114 @@ impl ExternModuleSimulationState { ); } +struct ForkJoinImpl<'a> { + futures: Vec + 'a>>>, +} + +impl Future for ForkJoinImpl<'_> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let Self { futures } = self.get_mut(); + futures.retain_mut(|future| future.as_mut().poll(cx).is_pending()); + if futures.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +pub trait ForkJoin { + type Output; + #[allow(async_fn_in_trait, reason = "no auto traits needed")] + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output; +} + +impl O, O, const N: usize> ForkJoin for [F; N] { + type Output = [O; N]; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + let mut temp = [const { None }; N]; + ForkJoinImpl { + futures: this + .into_iter() + .zip(&mut temp) + .map(|(future, temp)| -> Pin + '_>> { + Box::pin(async { *temp = Some(future(sim_state.forked_state()).await) }) + }) + .collect(), + } + .await; + temp.map(|output| output.expect("set to Some above")) + } +} + +impl O, O> ForkJoin for Vec { + type Output = Vec; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + let mut temp = Vec::with_capacity(this.len()); + for _ in 0..this.len() { + temp.push(None); + } + ForkJoinImpl { + futures: this + .into_iter() + .zip(&mut temp) + .map(|(future, temp)| -> Pin + '_>> { + Box::pin(async { *temp = Some(future(sim_state.forked_state()).await) }) + }) + .collect(), + } + .await; + temp.into_iter() + .map(|output| output.expect("set to Some above")) + .collect() + } +} + +impl O, O> ForkJoin for Box<[F]> { + type Output = Box<[O]>; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + Vec::fork_join(this.into(), sim_state) + .await + .into_boxed_slice() + } +} + +impl ForkJoin for Box { + type Output = Box; + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + Box::new(T::fork_join(*this, sim_state).await) + } +} + +macro_rules! impl_fork_join_tuples { + (@impl $(($f:ident: $F:ident => $o:ident: $O:ident)),*) => { + impl<$($F: AsyncFnOnce(ExternModuleSimulationState) -> $O, $O,)*> ForkJoin for ($($F,)*) { + type Output = ($($O,)*); + async fn fork_join(this: Self, sim_state: &mut ExternModuleSimulationState) -> Self::Output { + #![allow(unused_variables)] + let ($($f,)*) = this; + $(let mut $o = None;)* + ForkJoinImpl { + futures: vec![ + $(Box::pin(async { $o = Some($f(sim_state.forked_state()).await) }),)* + ], + }.await; + ($($o.expect("set to Some above"),)*) + } + } + }; + (($($first:tt)*) $(, $rest:tt)* $(,)?) => { + impl_fork_join_tuples!(@impl ($($first)*) $(, $rest)*); + impl_fork_join_tuples!($($rest),*); + }; + () => { + impl_fork_join_tuples!(@impl); + }; +} + +impl_fork_join_tuples!((f0: F0 => o0: O0), (f1: F1 => o1: O1), (f2: F2 => o2: O2), (f3: F3 => o3: O3), (f4: F4 => o4: O4), (f5: F5 => o5: O5), (f6: F6 => o6: O6), (f7: F7 => o7: O7), (f8: F8 => o8: O8), (f9: F9 => o9: O9), (f10: F10 => o10: O10), (f11: F11 => o11: O11),); + pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt::Debug { fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a; } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 873978a9..b055ffa0 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2026,3 +2026,85 @@ fn test_sim_only_connects() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn sim_fork_join() +where + ConstUsize: KnownSize, +{ + #[hdl] + let clocks: Array = m.input(); + #[hdl] + let outputs: Array, N> = m.output(); + m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { + sim.write(outputs, [0u8; N]).await; + loop { + sim.fork_join( + clocks + .into_iter() + .zip(outputs) + .map(|(clock, output)| { + move |mut sim: ExternModuleSimulationState| async move { + sim.wait_for_clock_edge(clock).await; + let v = sim + .read_bool_or_int(output) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(output, 1u8.wrapping_add(v)).await; + } + }) + .collect::>(), + ) + .await; + } + }); +} + +#[test] +fn test_sim_fork_join() { + let _n = SourceLocation::normalize_files_for_tests(); + const N: usize = 3; + let mut sim = Simulation::new(sim_fork_join::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().clocks, [false; N]); + let mut clocks_triggered = [false; N]; + let mut expected = [0u8; N]; + for i0 in 0..N { + for i1 in 0..N { + for i2 in 0..N { + for i3 in 0..N { + let indexes = [i0, i1, i2, i3]; + for i in indexes { + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], false); + if !clocks_triggered[i] { + expected[i] = expected[i].wrapping_add(1); + } + clocks_triggered[i] = true; + if clocks_triggered == [true; N] { + clocks_triggered = [false; N]; + } + let output = sim.read(sim.io().outputs); + assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); + } + } + } + } + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_fork_join.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_fork_join.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt index 12e86f30..2f25f359 100644 --- a/crates/fayalite/tests/sim/expected/array_rw.txt +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -828,7 +828,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "array_rw", children: [ @@ -1699,7 +1698,12 @@ Simulation { }, ), ], - instant: 34 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 34 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt index 58b2d20f..47997e5b 100644 --- a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -124,7 +124,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "conditional_assignment_last", children: [ @@ -177,7 +176,12 @@ Simulation { }, ), ], - instant: 2 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 2 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/connect_const.txt b/crates/fayalite/tests/sim/expected/connect_const.txt index 182ed84f..ac6c052b 100644 --- a/crates/fayalite/tests/sim/expected/connect_const.txt +++ b/crates/fayalite/tests/sim/expected/connect_const.txt @@ -100,7 +100,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const", children: [ @@ -130,7 +129,12 @@ Simulation { ], trace_memories: {}, trace_writers: [], - instant: 0 s, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 0 s, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/connect_const_reset.txt b/crates/fayalite/tests/sim/expected/connect_const_reset.txt index f56a6b4e..999f414c 100644 --- a/crates/fayalite/tests/sim/expected/connect_const_reset.txt +++ b/crates/fayalite/tests/sim/expected/connect_const_reset.txt @@ -143,7 +143,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const_reset", children: [ @@ -197,7 +196,12 @@ Simulation { }, ), ], - instant: 1 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_async.txt b/crates/fayalite/tests/sim/expected/counter_async.txt index 8c8809ab..7a43720e 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.txt +++ b/crates/fayalite/tests/sim/expected/counter_async.txt @@ -263,7 +263,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ @@ -329,7 +328,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -355,7 +354,7 @@ Simulation { ty: UInt<4>, }, state: 0x3, - last_state: 0x3, + last_state: 0x2, }, ], trace_memories: {}, @@ -368,9 +367,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_async.vcd b/crates/fayalite/tests/sim/expected/counter_async.vcd index a4b2ee91..dab690f5 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.vcd +++ b/crates/fayalite/tests/sim/expected/counter_async.vcd @@ -26,192 +26,192 @@ b11 $ 0! #3000000 1! -b100 $ b100 # +b100 $ #4000000 0! #5000000 1! -b101 $ b101 # +b101 $ #6000000 0! #7000000 1! -b110 $ b110 # +b110 $ #8000000 0! #9000000 1! -b111 $ b111 # +b111 $ #10000000 0! #11000000 1! -b1000 $ b1000 # +b1000 $ #12000000 0! #13000000 1! -b1001 $ b1001 # +b1001 $ #14000000 0! #15000000 1! -b1010 $ b1010 # +b1010 $ #16000000 0! #17000000 1! -b1011 $ b1011 # +b1011 $ #18000000 0! #19000000 1! -b1100 $ b1100 # +b1100 $ #20000000 0! #21000000 1! -b1101 $ b1101 # +b1101 $ #22000000 0! #23000000 1! -b1110 $ b1110 # +b1110 $ #24000000 0! #25000000 1! -b1111 $ b1111 # +b1111 $ #26000000 0! #27000000 1! -b0 $ b0 # +b0 $ #28000000 0! #29000000 1! -b1 $ b1 # +b1 $ #30000000 0! #31000000 1! -b10 $ b10 # +b10 $ #32000000 0! #33000000 1! -b11 $ b11 # +b11 $ #34000000 0! #35000000 1! -b100 $ b100 # +b100 $ #36000000 0! #37000000 1! -b101 $ b101 # +b101 $ #38000000 0! #39000000 1! -b110 $ b110 # +b110 $ #40000000 0! #41000000 1! -b111 $ b111 # +b111 $ #42000000 0! #43000000 1! -b1000 $ b1000 # +b1000 $ #44000000 0! #45000000 1! -b1001 $ b1001 # +b1001 $ #46000000 0! #47000000 1! -b1010 $ b1010 # +b1010 $ #48000000 0! #49000000 1! -b1011 $ b1011 # +b1011 $ #50000000 0! #51000000 1! -b1100 $ b1100 # +b1100 $ #52000000 0! #53000000 1! -b1101 $ b1101 # +b1101 $ #54000000 0! #55000000 1! -b1110 $ b1110 # +b1110 $ #56000000 0! #57000000 1! -b1111 $ b1111 # +b1111 $ #58000000 0! #59000000 1! -b0 $ b0 # +b0 $ #60000000 0! #61000000 1! -b1 $ b1 # +b1 $ #62000000 0! #63000000 1! -b10 $ b10 # +b10 $ #64000000 0! #65000000 1! -b11 $ b11 # +b11 $ #66000000 diff --git a/crates/fayalite/tests/sim/expected/counter_sync.txt b/crates/fayalite/tests/sim/expected/counter_sync.txt index 1d975b3d..b96ce5fd 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.txt +++ b/crates/fayalite/tests/sim/expected/counter_sync.txt @@ -244,7 +244,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ @@ -310,7 +309,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -336,7 +335,7 @@ Simulation { ty: UInt<4>, }, state: 0x3, - last_state: 0x3, + last_state: 0x2, }, ], trace_memories: {}, @@ -349,9 +348,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/counter_sync.vcd b/crates/fayalite/tests/sim/expected/counter_sync.vcd index bf6249e4..9504a30a 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.vcd +++ b/crates/fayalite/tests/sim/expected/counter_sync.vcd @@ -16,199 +16,199 @@ b0 $ $end #1000000 1! -b11 $ b11 # +b11 $ 0" #2000000 0! #3000000 1! -b100 $ b100 # +b100 $ #4000000 0! #5000000 1! -b101 $ b101 # +b101 $ #6000000 0! #7000000 1! -b110 $ b110 # +b110 $ #8000000 0! #9000000 1! -b111 $ b111 # +b111 $ #10000000 0! #11000000 1! -b1000 $ b1000 # +b1000 $ #12000000 0! #13000000 1! -b1001 $ b1001 # +b1001 $ #14000000 0! #15000000 1! -b1010 $ b1010 # +b1010 $ #16000000 0! #17000000 1! -b1011 $ b1011 # +b1011 $ #18000000 0! #19000000 1! -b1100 $ b1100 # +b1100 $ #20000000 0! #21000000 1! -b1101 $ b1101 # +b1101 $ #22000000 0! #23000000 1! -b1110 $ b1110 # +b1110 $ #24000000 0! #25000000 1! -b1111 $ b1111 # +b1111 $ #26000000 0! #27000000 1! -b0 $ b0 # +b0 $ #28000000 0! #29000000 1! -b1 $ b1 # +b1 $ #30000000 0! #31000000 1! -b10 $ b10 # +b10 $ #32000000 0! #33000000 1! -b11 $ b11 # +b11 $ #34000000 0! #35000000 1! -b100 $ b100 # +b100 $ #36000000 0! #37000000 1! -b101 $ b101 # +b101 $ #38000000 0! #39000000 1! -b110 $ b110 # +b110 $ #40000000 0! #41000000 1! -b111 $ b111 # +b111 $ #42000000 0! #43000000 1! -b1000 $ b1000 # +b1000 $ #44000000 0! #45000000 1! -b1001 $ b1001 # +b1001 $ #46000000 0! #47000000 1! -b1010 $ b1010 # +b1010 $ #48000000 0! #49000000 1! -b1011 $ b1011 # +b1011 $ #50000000 0! #51000000 1! -b1100 $ b1100 # +b1100 $ #52000000 0! #53000000 1! -b1101 $ b1101 # +b1101 $ #54000000 0! #55000000 1! -b1110 $ b1110 # +b1110 $ #56000000 0! #57000000 1! -b1111 $ b1111 # +b1111 $ #58000000 0! #59000000 1! -b0 $ b0 # +b0 $ #60000000 0! #61000000 1! -b1 $ b1 # +b1 $ #62000000 0! #63000000 1! -b10 $ b10 # +b10 $ #64000000 0! #65000000 1! -b11 $ b11 # +b11 $ #66000000 diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt index 4c54aa85..e127210b 100644 --- a/crates/fayalite/tests/sim/expected/duplicate_names.txt +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -104,7 +104,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "duplicate_names", children: [ @@ -160,7 +159,12 @@ Simulation { }, ), ], - instant: 1 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index 4850a215..eeef867d 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -1456,7 +1456,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "enums", children: [ @@ -1744,7 +1743,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -1924,9 +1923,14 @@ Simulation { }, ), ], - instant: 16 μs, clocks_triggered: [ StatePartIndex(3), ], + event_queue: EventQueue(EventQueueData { + instant: 16 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt index e09a7671..03349409 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.txt +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -186,14 +186,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Instant( - 20.500000000000 μs, - ), - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "extern_module", children: [ @@ -247,7 +241,14 @@ Simulation { }, ), ], - instant: 20 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: { + Event { instant: 20.500000000000 μs, kind: ExternModule(0) }: 1, + }, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.vcd b/crates/fayalite/tests/sim/expected/extern_module.vcd index e026a50c..5d6a0bc9 100644 --- a/crates/fayalite/tests/sim/expected/extern_module.vcd +++ b/crates/fayalite/tests/sim/expected/extern_module.vcd @@ -6,8 +6,9 @@ $upscope $end $enddefinitions $end $dumpvars 0! -1" +0" $end +1" #500000 #1500000 0" diff --git a/crates/fayalite/tests/sim/expected/extern_module2.txt b/crates/fayalite/tests/sim/expected/extern_module2.txt index 1023b2ba..3d7cfaef 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.txt +++ b/crates/fayalite/tests/sim/expected/extern_module2.txt @@ -234,55 +234,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 1, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "extern_module2", children: [ @@ -356,7 +309,113 @@ Simulation { }, ), ], - instant: 60 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 60 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 59, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 59, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module2.vcd b/crates/fayalite/tests/sim/expected/extern_module2.vcd index 464f4bd4..42045679 100644 --- a/crates/fayalite/tests/sim/expected/extern_module2.vcd +++ b/crates/fayalite/tests/sim/expected/extern_module2.vcd @@ -8,8 +8,9 @@ $enddefinitions $end $dumpvars 1! 0" -b1001000 # +b0 # $end +b1001000 # #1000000 1" b1100101 # diff --git a/crates/fayalite/tests/sim/expected/many_memories.txt b/crates/fayalite/tests/sim/expected/many_memories.txt index fbbc5819..f311cf7e 100644 --- a/crates/fayalite/tests/sim/expected/many_memories.txt +++ b/crates/fayalite/tests/sim/expected/many_memories.txt @@ -3836,7 +3836,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "many_memories", children: [ @@ -7759,7 +7758,6 @@ Simulation { }, ), ], - instant: 38 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), @@ -7778,5 +7776,11 @@ Simulation { StatePartIndex(85), StatePartIndex(90), ], + event_queue: EventQueue(EventQueueData { + instant: 38 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/many_memories.vcd b/crates/fayalite/tests/sim/expected/many_memories.vcd index cbaeb7b1..77d1447a 100644 --- a/crates/fayalite/tests/sim/expected/many_memories.vcd +++ b/crates/fayalite/tests/sim/expected/many_memories.vcd @@ -1052,12 +1052,16 @@ $end 1U# 1e# 1# +1$ 1' 1+ +1, 1/ 13 +14 17 1; +1< 1? 1C 1H @@ -1068,29 +1072,25 @@ $end 1a 1f 1k +1l 1o 1t 1x 1} +1~ 1#" 1(" 1," 11" +12" 15" 1:" 1>" 1C" +1D" 1G" 1L" 1P" -1$ -1, -14 -1< -1l -1~ -12" -1D" #4000000 0# 0' @@ -1150,13 +1150,21 @@ $end 0U# 0e# 1# +0$ 1' +0( 1+ +0, 1/ +00 13 +04 17 +08 1; +0< 1? +0@ 1C 1H 1M @@ -1166,37 +1174,29 @@ $end 1a 1f 1k +0l 1o 1t +0u 1x 1} +0~ 1#" 1(" +0)" 1," 11" +02" 15" 1:" +0;" 1>" 1C" +0D" 1G" 1L" -1P" -0$ -0( -0, -00 -04 -08 -0< -0@ -0l -0u -0~ -0)" -02" -0;" -0D" 0M" +1P" #6000000 0# 0' diff --git a/crates/fayalite/tests/sim/expected/memories.txt b/crates/fayalite/tests/sim/expected/memories.txt index f7f88e3b..03c5ee37 100644 --- a/crates/fayalite/tests/sim/expected/memories.txt +++ b/crates/fayalite/tests/sim/expected/memories.txt @@ -721,7 +721,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories", children: [ @@ -1616,10 +1615,15 @@ Simulation { }, ), ], - instant: 22 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), ], + event_queue: EventQueue(EventQueueData { + instant: 22 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories.vcd b/crates/fayalite/tests/sim/expected/memories.vcd index bedc354b..d8f58172 100644 --- a/crates/fayalite/tests/sim/expected/memories.vcd +++ b/crates/fayalite/tests/sim/expected/memories.vcd @@ -234,13 +234,13 @@ b100000 6 b10000 9 b100000 I 1# -1( -1/ -14 b10000 $ b100000 % +1( +1/ b10000 0 b100000 1 +14 #4000000 0# 0( @@ -256,11 +256,11 @@ b1000000 6 b10000 9 b1000000 I 1# +b1000000 % 1( 1/ -14 -b1000000 % b1000000 1 +14 #6000000 0# 0( @@ -278,11 +278,11 @@ b1100000 6 b1010000 9 b1000000 I 1# +b1010000 $ 1( 1/ -14 -b1010000 $ b1010000 0 +14 #8000000 0# 0( diff --git a/crates/fayalite/tests/sim/expected/memories2.txt b/crates/fayalite/tests/sim/expected/memories2.txt index c216104c..f1cb72b6 100644 --- a/crates/fayalite/tests/sim/expected/memories2.txt +++ b/crates/fayalite/tests/sim/expected/memories2.txt @@ -679,7 +679,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories2", children: [ @@ -1260,9 +1259,14 @@ Simulation { }, ), ], - instant: 22 μs, clocks_triggered: [ StatePartIndex(3), ], + event_queue: EventQueue(EventQueueData { + instant: 22 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories2.vcd b/crates/fayalite/tests/sim/expected/memories2.vcd index 4039754f..0ac20f19 100644 --- a/crates/fayalite/tests/sim/expected/memories2.vcd +++ b/crates/fayalite/tests/sim/expected/memories2.vcd @@ -100,8 +100,8 @@ $end 1) #1250000 1# -1* b11 $ +1* sHdlSome\x20(1) + 1, #1500000 @@ -113,8 +113,8 @@ sHdlSome\x20(1) + 0) #2250000 1# -1* b0 $ +1* sHdlNone\x20(0) + 0, #2500000 @@ -303,8 +303,8 @@ b11 ! b11 ( #17250000 1# -1* b11 $ +1* sHdlSome\x20(1) + 1, #17500000 @@ -316,8 +316,8 @@ b10 ! b10 ( #18250000 1# -1* b0 $ +1* sHdlNone\x20(0) + 0, #18500000 @@ -339,8 +339,8 @@ b1 ! b1 ( #20250000 1# -1* b1 $ +1* sHdlSome\x20(1) + #20500000 #20750000 @@ -353,8 +353,8 @@ b0 ( 0) #21250000 1# -1* b0 $ +1* sHdlNone\x20(0) + #21500000 #21750000 diff --git a/crates/fayalite/tests/sim/expected/memories3.txt b/crates/fayalite/tests/sim/expected/memories3.txt index 8114c7e4..3166e17b 100644 --- a/crates/fayalite/tests/sim/expected/memories3.txt +++ b/crates/fayalite/tests/sim/expected/memories3.txt @@ -1763,7 +1763,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "memories3", children: [ @@ -3275,10 +3274,15 @@ Simulation { }, ), ], - instant: 15 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(6), ], + event_queue: EventQueue(EventQueueData { + instant: 15 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/memories3.vcd b/crates/fayalite/tests/sim/expected/memories3.vcd index 5768560e..32ee75e6 100644 --- a/crates/fayalite/tests/sim/expected/memories3.vcd +++ b/crates/fayalite/tests/sim/expected/memories3.vcd @@ -420,6 +420,10 @@ b10000 T 1\ #3250000 1# +b110100 % +b1111000 ' +b10011010 ( +b11110000 + 1. 1A b110100 C @@ -427,10 +431,6 @@ b1111000 E b10011010 F b11110000 I 1L -b110100 % -b1111000 ' -b10011010 ( -b11110000 + #3500000 #3750000 0# @@ -508,6 +508,14 @@ b1010100 '" b110010 /" b10000 7" 1# +b11111110 $ +b11011100 % +b10111010 & +b10011000 ' +b1110110 ( +b1010100 ) +b110010 * +b10000 + 1. 1A b11111110 B @@ -519,14 +527,6 @@ b1010100 G b110010 H b10000 I 1L -b11111110 $ -b11011100 % -b10111010 & -b10011000 ' -b1110110 ( -b1010100 ) -b110010 * -b10000 + #6500000 #6750000 0# @@ -562,6 +562,14 @@ b1000110 (" b10001010 0" b11001110 8" 1# +b0 $ +b0 % +b0 & +b0 ' +b0 ( +b0 ) +b0 * +b0 + 1. 1A b0 B @@ -573,14 +581,6 @@ b0 G b0 H b0 I 1L -b0 $ -b0 % -b0 & -b0 ' -b0 ( -b0 ) -b0 * -b0 + #7500000 #7750000 0# @@ -688,6 +688,14 @@ b1 ! b1 ? #10250000 1# +b11111110 $ +b11011100 % +b10111010 & +b10011000 ' +b1110110 ( +b1010100 ) +b110010 * +b10000 + 1. 1A b11111110 B @@ -699,14 +707,6 @@ b1010100 G b110010 H b10000 I 1L -b11111110 $ -b11011100 % -b10111010 & -b10011000 ' -b1110110 ( -b1010100 ) -b110010 * -b10000 + #10500000 #10750000 0# @@ -718,6 +718,14 @@ b10 ! b10 ? #11250000 1# +b10011 $ +b1010111 % +b10011011 & +b11011111 ' +b10 ( +b1000110 ) +b10001010 * +b11001110 + 1. 1A b10011 B @@ -729,14 +737,6 @@ b1000110 G b10001010 H b11001110 I 1L -b10011 $ -b1010111 % -b10011011 & -b11011111 ' -b10 ( -b1000110 ) -b10001010 * -b11001110 + #11500000 #11750000 0# @@ -748,6 +748,14 @@ b11 ! b11 ? #12250000 1# +b1110100 $ +b1100101 % +b1110011 & +b1110100 ' +b1101001 ( +b1101110 ) +b1100111 * +b100001 + 1. 1A b1110100 B @@ -759,14 +767,6 @@ b1101110 G b1100111 H b100001 I 1L -b1110100 $ -b1100101 % -b1110011 & -b1110100 ' -b1101001 ( -b1101110 ) -b1100111 * -b100001 + #12500000 #12750000 0# @@ -780,6 +780,14 @@ b0 ? 0@ #13250000 1# +b1101101 $ +b1101111 % +b1110010 & +b1100101 ' +b100000 ( +b1110100 ) +b1110011 * +b1110100 + 1. 1A b1101101 B @@ -791,14 +799,6 @@ b1110100 G b1110011 H b1110100 I 1L -b1101101 $ -b1101111 % -b1110010 & -b1100101 ' -b100000 ( -b1110100 ) -b1110011 * -b1110100 + #13500000 #13750000 0# @@ -808,6 +808,14 @@ b1110100 + #14000000 #14250000 1# +b0 $ +b0 % +b0 & +b0 ' +b0 ( +b0 ) +b0 * +b0 + 1. 1A b0 B @@ -819,14 +827,6 @@ b0 G b0 H b0 I 1L -b0 $ -b0 % -b0 & -b0 ' -b0 ( -b0 ) -b0 * -b0 + #14500000 #14750000 0# diff --git a/crates/fayalite/tests/sim/expected/mod1.txt b/crates/fayalite/tests/sim/expected/mod1.txt index 4ef02b2e..355b6ee0 100644 --- a/crates/fayalite/tests/sim/expected/mod1.txt +++ b/crates/fayalite/tests/sim/expected/mod1.txt @@ -276,7 +276,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "mod1", children: [ @@ -558,7 +557,12 @@ Simulation { }, ), ], - instant: 2 μs, clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 2 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.txt b/crates/fayalite/tests/sim/expected/ripple_counter.txt index 9e4e0d1b..58189754 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.txt +++ b/crates/fayalite/tests/sim/expected/ripple_counter.txt @@ -827,52 +827,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 3, len: 0 }, - big_slots: StatePartIndexRange { start: 33, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -956,52 +910,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 6, len: 0 }, - big_slots: StatePartIndexRange { start: 44, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -1085,55 +993,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 9, len: 0 }, - big_slots: StatePartIndexRange { start: 55, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x0_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "ripple_counter", children: [ @@ -1593,11 +1454,315 @@ Simulation { }, ), ], - instant: 256 μs, clocks_triggered: [ StatePartIndex(1), StatePartIndex(4), StatePartIndex(7), ], + event_queue: EventQueue(EventQueueData { + instant: 256 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 152, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 9, len: 0 }, + big_slots: StatePartIndexRange { start: 55, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 167, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 6, len: 0 }, + big_slots: StatePartIndexRange { start: 44, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 170, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 3, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_1: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 3, len: 0 }, + big_slots: StatePartIndexRange { start: 33, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 170, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_3: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 6, len: 0 }, + big_slots: StatePartIndexRange { start: 44, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 167, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(ripple_counter.bit_reg_5: sw_reg).sw_reg::clk", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 9, len: 0 }, + big_slots: StatePartIndexRange { start: 55, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 152, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/ripple_counter.vcd b/crates/fayalite/tests/sim/expected/ripple_counter.vcd index 6f14a8e8..550205f8 100644 --- a/crates/fayalite/tests/sim/expected/ripple_counter.vcd +++ b/crates/fayalite/tests/sim/expected/ripple_counter.vcd @@ -66,1688 +66,1648 @@ b0 " $end #1000000 1! -1) b1 " 1# +1) 1* 1, -1+ -b11 " +b111 " 1$ +1% +1+ 1- 1. -b111 " -1% 1/ 11 -10 -b1111 " +b11111 " 1& +1' +10 12 13 -b11111 " -1' 14 16 -15 b111111 " 1( +15 17 #2000000 0! #3000000 1! -0) b111110 " 0# +0) 0* 0, #4000000 0! #5000000 1! -1) b111111 " 1# +1) 1* 1, -0+ b111101 " 0$ +0+ 0- #6000000 0! #7000000 1! -0) b111100 " 0# +0) 0* 0, #8000000 0! #9000000 1! -1) b111101 " 1# +1) 1* 1, -1+ -b111111 " +b111011 " 1$ +0% +1+ 1- 0. -b111011 " -0% 0/ 01 #10000000 0! #11000000 1! -0) b111010 " 0# +0) 0* 0, #12000000 0! #13000000 1! -1) b111011 " 1# +1) 1* 1, -0+ b111001 " 0$ +0+ 0- #14000000 0! #15000000 1! -0) b111000 " 0# +0) 0* 0, #16000000 0! #17000000 1! -1) b111001 " 1# +1) 1* 1, -1+ -b111011 " +b111111 " 1$ +1% +1+ 1- 1. -b111111 " -1% 1/ 11 -00 b110111 " 0& +00 02 #18000000 0! #19000000 1! -0) b110110 " 0# +0) 0* 0, #20000000 0! #21000000 1! -1) b110111 " 1# +1) 1* 1, -0+ b110101 " 0$ +0+ 0- #22000000 0! #23000000 1! -0) b110100 " 0# +0) 0* 0, #24000000 0! #25000000 1! -1) b110101 " 1# +1) 1* 1, -1+ -b110111 " +b110011 " 1$ +0% +1+ 1- 0. -b110011 " -0% 0/ 01 #26000000 0! #27000000 1! -0) b110010 " 0# +0) 0* 0, #28000000 0! #29000000 1! -1) b110011 " 1# +1) 1* 1, -0+ b110001 " 0$ +0+ 0- #30000000 0! #31000000 1! -0) b110000 " 0# +0) 0* 0, #32000000 0! #33000000 1! -1) b110001 " 1# +1) 1* 1, -1+ -b110011 " +b110111 " 1$ +1% +1+ 1- 1. -b110111 " -1% 1/ 11 -10 -b111111 " +b101111 " 1& +0' +10 12 03 -b101111 " -0' 04 06 #34000000 0! #35000000 1! -0) b101110 " 0# +0) 0* 0, #36000000 0! #37000000 1! -1) b101111 " 1# +1) 1* 1, -0+ b101101 " 0$ +0+ 0- #38000000 0! #39000000 1! -0) b101100 " 0# +0) 0* 0, #40000000 0! #41000000 1! -1) b101101 " 1# +1) 1* 1, -1+ -b101111 " +b101011 " 1$ +0% +1+ 1- 0. -b101011 " -0% 0/ 01 #42000000 0! #43000000 1! -0) b101010 " 0# +0) 0* 0, #44000000 0! #45000000 1! -1) b101011 " 1# +1) 1* 1, -0+ b101001 " 0$ +0+ 0- #46000000 0! #47000000 1! -0) b101000 " 0# +0) 0* 0, #48000000 0! #49000000 1! -1) b101001 " 1# +1) 1* 1, -1+ -b101011 " +b101111 " 1$ +1% +1+ 1- 1. -b101111 " -1% 1/ 11 -00 b100111 " 0& +00 02 #50000000 0! #51000000 1! -0) b100110 " 0# +0) 0* 0, #52000000 0! #53000000 1! -1) b100111 " 1# +1) 1* 1, -0+ b100101 " 0$ +0+ 0- #54000000 0! #55000000 1! -0) b100100 " 0# +0) 0* 0, #56000000 0! #57000000 1! -1) b100101 " 1# +1) 1* 1, -1+ -b100111 " +b100011 " 1$ +0% +1+ 1- 0. -b100011 " -0% 0/ 01 #58000000 0! #59000000 1! -0) b100010 " 0# +0) 0* 0, #60000000 0! #61000000 1! -1) b100011 " 1# +1) 1* 1, -0+ b100001 " 0$ +0+ 0- #62000000 0! #63000000 1! -0) b100000 " 0# +0) 0* 0, #64000000 0! #65000000 1! -1) b100001 " 1# +1) 1* 1, -1+ -b100011 " +b100111 " 1$ +1% +1+ 1- 1. -b100111 " -1% 1/ 11 -10 -b101111 " +b111111 " 1& +1' +10 12 13 -b111111 " -1' 14 16 -05 b11111 " 0( +05 07 #66000000 0! #67000000 1! -0) b11110 " 0# +0) 0* 0, #68000000 0! #69000000 1! -1) b11111 " 1# +1) 1* 1, -0+ b11101 " 0$ +0+ 0- #70000000 0! #71000000 1! -0) b11100 " 0# +0) 0* 0, #72000000 0! #73000000 1! -1) b11101 " 1# +1) 1* 1, -1+ -b11111 " +b11011 " 1$ +0% +1+ 1- 0. -b11011 " -0% 0/ 01 #74000000 0! #75000000 1! -0) b11010 " 0# +0) 0* 0, #76000000 0! #77000000 1! -1) b11011 " 1# +1) 1* 1, -0+ b11001 " 0$ +0+ 0- #78000000 0! #79000000 1! -0) b11000 " 0# +0) 0* 0, #80000000 0! #81000000 1! -1) b11001 " 1# +1) 1* 1, -1+ -b11011 " +b11111 " 1$ +1% +1+ 1- 1. -b11111 " -1% 1/ 11 -00 b10111 " 0& +00 02 #82000000 0! #83000000 1! -0) b10110 " 0# +0) 0* 0, #84000000 0! #85000000 1! -1) b10111 " 1# +1) 1* 1, -0+ b10101 " 0$ +0+ 0- #86000000 0! #87000000 1! -0) b10100 " 0# +0) 0* 0, #88000000 0! #89000000 1! -1) b10101 " 1# +1) 1* 1, -1+ -b10111 " +b10011 " 1$ +0% +1+ 1- 0. -b10011 " -0% 0/ 01 #90000000 0! #91000000 1! -0) b10010 " 0# +0) 0* 0, #92000000 0! #93000000 1! -1) b10011 " 1# +1) 1* 1, -0+ b10001 " 0$ +0+ 0- #94000000 0! #95000000 1! -0) b10000 " 0# +0) 0* 0, #96000000 0! #97000000 1! -1) b10001 " 1# +1) 1* 1, -1+ -b10011 " +b10111 " 1$ +1% +1+ 1- 1. -b10111 " -1% 1/ 11 -10 -b11111 " +b1111 " 1& +0' +10 12 03 -b1111 " -0' 04 06 #98000000 0! #99000000 1! -0) b1110 " 0# +0) 0* 0, #100000000 0! #101000000 1! -1) b1111 " 1# +1) 1* 1, -0+ b1101 " 0$ +0+ 0- #102000000 0! #103000000 1! -0) b1100 " 0# +0) 0* 0, #104000000 0! #105000000 1! -1) b1101 " 1# +1) 1* 1, -1+ -b1111 " +b1011 " 1$ +0% +1+ 1- 0. -b1011 " -0% 0/ 01 #106000000 0! #107000000 1! -0) b1010 " 0# +0) 0* 0, #108000000 0! #109000000 1! -1) b1011 " 1# +1) 1* 1, -0+ b1001 " 0$ +0+ 0- #110000000 0! #111000000 1! -0) b1000 " 0# +0) 0* 0, #112000000 0! #113000000 1! -1) b1001 " 1# +1) 1* 1, -1+ -b1011 " +b1111 " 1$ +1% +1+ 1- 1. -b1111 " -1% 1/ 11 -00 b111 " 0& +00 02 #114000000 0! #115000000 1! -0) b110 " 0# +0) 0* 0, #116000000 0! #117000000 1! -1) b111 " 1# +1) 1* 1, -0+ b101 " 0$ +0+ 0- #118000000 0! #119000000 1! -0) b100 " 0# +0) 0* 0, #120000000 0! #121000000 1! -1) b101 " 1# +1) 1* 1, -1+ -b111 " +b11 " 1$ +0% +1+ 1- 0. -b11 " -0% 0/ 01 #122000000 0! #123000000 1! -0) b10 " 0# +0) 0* 0, #124000000 0! #125000000 1! -1) b11 " 1# +1) 1* 1, -0+ b1 " 0$ +0+ 0- #126000000 0! #127000000 1! -0) b0 " 0# +0) 0* 0, #128000000 0! #129000000 1! -1) b1 " 1# +1) 1* 1, -1+ -b11 " +b111 " 1$ +1% +1+ 1- 1. -b111 " -1% 1/ 11 -10 -b1111 " +b11111 " 1& +1' +10 12 13 -b11111 " -1' 14 16 -15 b111111 " 1( +15 17 #130000000 0! #131000000 1! -0) b111110 " 0# +0) 0* 0, #132000000 0! #133000000 1! -1) b111111 " 1# +1) 1* 1, -0+ b111101 " 0$ +0+ 0- #134000000 0! #135000000 1! -0) b111100 " 0# +0) 0* 0, #136000000 0! #137000000 1! -1) b111101 " 1# +1) 1* 1, -1+ -b111111 " +b111011 " 1$ +0% +1+ 1- 0. -b111011 " -0% 0/ 01 #138000000 0! #139000000 1! -0) b111010 " 0# +0) 0* 0, #140000000 0! #141000000 1! -1) b111011 " 1# +1) 1* 1, -0+ b111001 " 0$ +0+ 0- #142000000 0! #143000000 1! -0) b111000 " 0# +0) 0* 0, #144000000 0! #145000000 1! -1) b111001 " 1# +1) 1* 1, -1+ -b111011 " +b111111 " 1$ +1% +1+ 1- 1. -b111111 " -1% 1/ 11 -00 b110111 " 0& +00 02 #146000000 0! #147000000 1! -0) b110110 " 0# +0) 0* 0, #148000000 0! #149000000 1! -1) b110111 " 1# +1) 1* 1, -0+ b110101 " 0$ +0+ 0- #150000000 0! #151000000 1! -0) b110100 " 0# +0) 0* 0, #152000000 0! #153000000 1! -1) b110101 " 1# +1) 1* 1, -1+ -b110111 " +b110011 " 1$ +0% +1+ 1- 0. -b110011 " -0% 0/ 01 #154000000 0! #155000000 1! -0) b110010 " 0# +0) 0* 0, #156000000 0! #157000000 1! -1) b110011 " 1# +1) 1* 1, -0+ b110001 " 0$ +0+ 0- #158000000 0! #159000000 1! -0) b110000 " 0# +0) 0* 0, #160000000 0! #161000000 1! -1) b110001 " 1# +1) 1* 1, -1+ -b110011 " +b110111 " 1$ +1% +1+ 1- 1. -b110111 " -1% 1/ 11 -10 -b111111 " +b101111 " 1& +0' +10 12 03 -b101111 " -0' 04 06 #162000000 0! #163000000 1! -0) b101110 " 0# +0) 0* 0, #164000000 0! #165000000 1! -1) b101111 " 1# +1) 1* 1, -0+ b101101 " 0$ +0+ 0- #166000000 0! #167000000 1! -0) b101100 " 0# +0) 0* 0, #168000000 0! #169000000 1! -1) b101101 " 1# +1) 1* 1, -1+ -b101111 " +b101011 " 1$ +0% +1+ 1- 0. -b101011 " -0% 0/ 01 #170000000 0! #171000000 1! -0) b101010 " 0# +0) 0* 0, #172000000 0! #173000000 1! -1) b101011 " 1# +1) 1* 1, -0+ b101001 " 0$ +0+ 0- #174000000 0! #175000000 1! -0) b101000 " 0# +0) 0* 0, #176000000 0! #177000000 1! -1) b101001 " 1# +1) 1* 1, -1+ -b101011 " +b101111 " 1$ +1% +1+ 1- 1. -b101111 " -1% 1/ 11 -00 b100111 " 0& +00 02 #178000000 0! #179000000 1! -0) b100110 " 0# +0) 0* 0, #180000000 0! #181000000 1! -1) b100111 " 1# +1) 1* 1, -0+ b100101 " 0$ +0+ 0- #182000000 0! #183000000 1! -0) b100100 " 0# +0) 0* 0, #184000000 0! #185000000 1! -1) b100101 " 1# +1) 1* 1, -1+ -b100111 " +b100011 " 1$ +0% +1+ 1- 0. -b100011 " -0% 0/ 01 #186000000 0! #187000000 1! -0) b100010 " 0# +0) 0* 0, #188000000 0! #189000000 1! -1) b100011 " 1# +1) 1* 1, -0+ b100001 " 0$ +0+ 0- #190000000 0! #191000000 1! -0) b100000 " 0# +0) 0* 0, #192000000 0! #193000000 1! -1) b100001 " 1# +1) 1* 1, -1+ -b100011 " +b100111 " 1$ +1% +1+ 1- 1. -b100111 " -1% 1/ 11 -10 -b101111 " +b111111 " 1& +1' +10 12 13 -b111111 " -1' 14 16 -05 b11111 " 0( +05 07 #194000000 0! #195000000 1! -0) b11110 " 0# +0) 0* 0, #196000000 0! #197000000 1! -1) b11111 " 1# +1) 1* 1, -0+ b11101 " 0$ +0+ 0- #198000000 0! #199000000 1! -0) b11100 " 0# +0) 0* 0, #200000000 0! #201000000 1! -1) b11101 " 1# +1) 1* 1, -1+ -b11111 " +b11011 " 1$ +0% +1+ 1- 0. -b11011 " -0% 0/ 01 #202000000 0! #203000000 1! -0) b11010 " 0# +0) 0* 0, #204000000 0! #205000000 1! -1) b11011 " 1# +1) 1* 1, -0+ b11001 " 0$ +0+ 0- #206000000 0! #207000000 1! -0) b11000 " 0# +0) 0* 0, #208000000 0! #209000000 1! -1) b11001 " 1# +1) 1* 1, -1+ -b11011 " +b11111 " 1$ +1% +1+ 1- 1. -b11111 " -1% 1/ 11 -00 b10111 " 0& +00 02 #210000000 0! #211000000 1! -0) b10110 " 0# +0) 0* 0, #212000000 0! #213000000 1! -1) b10111 " 1# +1) 1* 1, -0+ b10101 " 0$ +0+ 0- #214000000 0! #215000000 1! -0) b10100 " 0# +0) 0* 0, #216000000 0! #217000000 1! -1) b10101 " 1# +1) 1* 1, -1+ -b10111 " +b10011 " 1$ +0% +1+ 1- 0. -b10011 " -0% 0/ 01 #218000000 0! #219000000 1! -0) b10010 " 0# +0) 0* 0, #220000000 0! #221000000 1! -1) b10011 " 1# +1) 1* 1, -0+ b10001 " 0$ +0+ 0- #222000000 0! #223000000 1! -0) b10000 " 0# +0) 0* 0, #224000000 0! #225000000 1! -1) b10001 " 1# +1) 1* 1, -1+ -b10011 " +b10111 " 1$ +1% +1+ 1- 1. -b10111 " -1% 1/ 11 -10 -b11111 " +b1111 " 1& +0' +10 12 03 -b1111 " -0' 04 06 #226000000 0! #227000000 1! -0) b1110 " 0# +0) 0* 0, #228000000 0! #229000000 1! -1) b1111 " 1# +1) 1* 1, -0+ b1101 " 0$ +0+ 0- #230000000 0! #231000000 1! -0) b1100 " 0# +0) 0* 0, #232000000 0! #233000000 1! -1) b1101 " 1# +1) 1* 1, -1+ -b1111 " +b1011 " 1$ +0% +1+ 1- 0. -b1011 " -0% 0/ 01 #234000000 0! #235000000 1! -0) b1010 " 0# +0) 0* 0, #236000000 0! #237000000 1! -1) b1011 " 1# +1) 1* 1, -0+ b1001 " 0$ +0+ 0- #238000000 0! #239000000 1! -0) b1000 " 0# +0) 0* 0, #240000000 0! #241000000 1! -1) b1001 " 1# +1) 1* 1, -1+ -b1011 " +b1111 " 1$ +1% +1+ 1- 1. -b1111 " -1% 1/ 11 -00 b111 " 0& +00 02 #242000000 0! #243000000 1! -0) b110 " 0# +0) 0* 0, #244000000 0! #245000000 1! -1) b111 " 1# +1) 1* 1, -0+ b101 " 0$ +0+ 0- #246000000 0! #247000000 1! -0) b100 " 0# +0) 0* 0, #248000000 0! #249000000 1! -1) b101 " 1# +1) 1* 1, -1+ -b111 " +b11 " 1$ +0% +1+ 1- 0. -b11 " -0% 0/ 01 #250000000 0! #251000000 1! -0) b10 " 0# +0) 0* 0, #252000000 0! #253000000 1! -1) b11 " 1# +1) 1* 1, -0+ b1 " 0$ +0+ 0- #254000000 0! #255000000 1! -0) b0 " 0# +0) 0* 0, #256000000 diff --git a/crates/fayalite/tests/sim/expected/shift_register.txt b/crates/fayalite/tests/sim/expected/shift_register.txt index 9bab4248..2ca06d2d 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.txt +++ b/crates/fayalite/tests/sim/expected/shift_register.txt @@ -339,7 +339,6 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - state_ready_to_run: false, trace_decls: TraceModule { name: "shift_register", children: [ @@ -440,7 +439,7 @@ Simulation { index: StatePartIndex(0), }, state: 0x1, - last_state: 0x1, + last_state: 0x0, }, SimTrace { id: TraceScalarId(1), @@ -509,9 +508,14 @@ Simulation { }, ), ], - instant: 66 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 66 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/shift_register.vcd b/crates/fayalite/tests/sim/expected/shift_register.vcd index 0b5f4290..26726eb0 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.vcd +++ b/crates/fayalite/tests/sim/expected/shift_register.vcd @@ -52,9 +52,9 @@ $end 0! #11000000 1! +1$ 0& 1( -1$ #12000000 0! 1# @@ -67,10 +67,10 @@ $end 0# #15000000 1! +0$ 0% 1& 0( -0$ #16000000 0! 1# @@ -83,23 +83,23 @@ $end 0! #19000000 1! +1$ 1& 0' 1( -1$ #20000000 0! #21000000 1! +0$ 1' 0( -0$ #22000000 0! #23000000 1! -1( 1$ +1( #24000000 0! 0# @@ -120,8 +120,8 @@ $end 0! #31000000 1! -0( 0$ +0( #32000000 0! #33000000 diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join.txt b/crates/fayalite/tests/sim/expected/sim_fork_join.txt new file mode 100644 index 00000000..4ad3e620 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join.txt @@ -0,0 +1,523 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 6, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[0]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[1]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::clocks[2]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join: sim_fork_join).sim_fork_join::outputs[2]", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 49, + 50, + 50, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[0], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[1], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.clocks[2], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs, + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[0], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[1], + Instance { + name: ::sim_fork_join, + instantiated: Module { + name: sim_fork_join, + .. + }, + }.outputs[2], + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[0], + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[1], + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }[2], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[0], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[1], + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }[2], + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }: ModuleIO { + name: sim_fork_join::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }: ModuleIO { + name: sim_fork_join::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_fork_join", + children: [ + TraceModuleIO { + name: "clocks", + child: TraceArray { + name: "clocks", + elements: [ + TraceClock { + location: TraceScalarId(0), + name: "[0]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(1), + name: "[1]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(2), + name: "[2]", + flow: Source, + }, + ], + ty: Array, + flow: Source, + }, + ty: Array, + flow: Source, + }, + TraceModuleIO { + name: "outputs", + child: TraceArray { + name: "outputs", + elements: [ + TraceUInt { + location: TraceScalarId(3), + name: "[0]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(4), + name: "[1]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(5), + name: "[2]", + ty: UInt<8>, + flow: Sink, + }, + ], + ty: Array, 3>, + flow: Sink, + }, + ty: Array, 3>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigClock { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigUInt { + index: StatePartIndex(3), + ty: UInt<8>, + }, + state: 0x31, + last_state: 0x31, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigUInt { + index: StatePartIndex(5), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 648 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 198, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 198, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join.vcd b/crates/fayalite/tests/sim/expected/sim_fork_join.vcd new file mode 100644 index 00000000..a420c2b7 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join.vcd @@ -0,0 +1,1467 @@ +$timescale 1 ps $end +$scope module sim_fork_join $end +$scope struct clocks $end +$var wire 1 ! \[0] $end +$var wire 1 " \[1] $end +$var wire 1 # \[2] $end +$upscope $end +$scope struct outputs $end +$var wire 8 $ \[0] $end +$var wire 8 % \[1] $end +$var wire 8 & \[2] $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +b0 $ +b0 % +b0 & +$end +#1000000 +1! +b1 $ +#2000000 +0! +#3000000 +1! +#4000000 +0! +#5000000 +1! +#6000000 +0! +#7000000 +1! +#8000000 +0! +#9000000 +1! +#10000000 +0! +#11000000 +1! +#12000000 +0! +#13000000 +1! +#14000000 +0! +#15000000 +1" +b1 % +#16000000 +0" +#17000000 +1! +#18000000 +0! +#19000000 +1! +#20000000 +0! +#21000000 +1! +#22000000 +0! +#23000000 +1# +b1 & +#24000000 +0# +#25000000 +1! +b10 $ +#26000000 +0! +#27000000 +1! +#28000000 +0! +#29000000 +1" +b10 % +#30000000 +0" +#31000000 +1! +#32000000 +0! +#33000000 +1! +#34000000 +0! +#35000000 +1! +#36000000 +0! +#37000000 +1" +#38000000 +0" +#39000000 +1" +#40000000 +0" +#41000000 +1! +#42000000 +0! +#43000000 +1! +#44000000 +0! +#45000000 +1" +#46000000 +0" +#47000000 +1# +b10 & +#48000000 +0# +#49000000 +1! +b11 $ +#50000000 +0! +#51000000 +1! +#52000000 +0! +#53000000 +1# +b11 & +#54000000 +0# +#55000000 +1! +#56000000 +0! +#57000000 +1! +#58000000 +0! +#59000000 +1! +#60000000 +0! +#61000000 +1# +#62000000 +0# +#63000000 +1" +b11 % +#64000000 +0" +#65000000 +1! +b100 $ +#66000000 +0! +#67000000 +1! +#68000000 +0! +#69000000 +1# +b100 & +#70000000 +0# +#71000000 +1# +#72000000 +0# +#73000000 +1! +#74000000 +0! +#75000000 +1" +b100 % +#76000000 +0" +#77000000 +1! +b101 $ +#78000000 +0! +#79000000 +1! +#80000000 +0! +#81000000 +1! +#82000000 +0! +#83000000 +1" +b101 % +#84000000 +0" +#85000000 +1! +#86000000 +0! +#87000000 +1" +#88000000 +0" +#89000000 +1! +#90000000 +0! +#91000000 +1" +#92000000 +0" +#93000000 +1! +#94000000 +0! +#95000000 +1# +b101 & +#96000000 +0# +#97000000 +1! +b110 $ +#98000000 +0! +#99000000 +1" +b110 % +#100000000 +0" +#101000000 +1" +#102000000 +0" +#103000000 +1! +#104000000 +0! +#105000000 +1! +#106000000 +0! +#107000000 +1" +#108000000 +0" +#109000000 +1" +#110000000 +0" +#111000000 +1" +#112000000 +0" +#113000000 +1! +#114000000 +0! +#115000000 +1" +#116000000 +0" +#117000000 +1" +#118000000 +0" +#119000000 +1# +b110 & +#120000000 +0# +#121000000 +1! +b111 $ +#122000000 +0! +#123000000 +1" +b111 % +#124000000 +0" +#125000000 +1# +b111 & +#126000000 +0# +#127000000 +1! +b1000 $ +#128000000 +0! +#129000000 +1! +#130000000 +0! +#131000000 +1" +b1000 % +#132000000 +0" +#133000000 +1# +b1000 & +#134000000 +0# +#135000000 +1" +b1001 % +#136000000 +0" +#137000000 +1! +b1001 $ +#138000000 +0! +#139000000 +1" +#140000000 +0" +#141000000 +1# +b1001 & +#142000000 +0# +#143000000 +1# +b1010 & +#144000000 +0# +#145000000 +1! +b1010 $ +#146000000 +0! +#147000000 +1# +#148000000 +0# +#149000000 +1! +#150000000 +0! +#151000000 +1! +#152000000 +0! +#153000000 +1! +#154000000 +0! +#155000000 +1# +#156000000 +0# +#157000000 +1! +#158000000 +0! +#159000000 +1" +b1010 % +#160000000 +0" +#161000000 +1! +b1011 $ +#162000000 +0! +#163000000 +1# +b1011 & +#164000000 +0# +#165000000 +1! +#166000000 +0! +#167000000 +1# +#168000000 +0# +#169000000 +1! +#170000000 +0! +#171000000 +1# +#172000000 +0# +#173000000 +1" +b1011 % +#174000000 +0" +#175000000 +1! +b1100 $ +#176000000 +0! +#177000000 +1! +#178000000 +0! +#179000000 +1# +b1100 & +#180000000 +0# +#181000000 +1" +b1100 % +#182000000 +0" +#183000000 +1" +b1101 % +#184000000 +0" +#185000000 +1! +b1101 $ +#186000000 +0! +#187000000 +1# +b1101 & +#188000000 +0# +#189000000 +1" +b1110 % +#190000000 +0" +#191000000 +1# +b1110 & +#192000000 +0# +#193000000 +1! +b1110 $ +#194000000 +0! +#195000000 +1# +b1111 & +#196000000 +0# +#197000000 +1# +#198000000 +0# +#199000000 +1! +b1111 $ +#200000000 +0! +#201000000 +1! +#202000000 +0! +#203000000 +1# +#204000000 +0# +#205000000 +1# +#206000000 +0# +#207000000 +1" +b1111 % +#208000000 +0" +#209000000 +1! +b10000 $ +#210000000 +0! +#211000000 +1# +b10000 & +#212000000 +0# +#213000000 +1# +#214000000 +0# +#215000000 +1# +#216000000 +0# +#217000000 +1" +b10000 % +#218000000 +0" +#219000000 +1! +b10001 $ +#220000000 +0! +#221000000 +1! +#222000000 +0! +#223000000 +1! +#224000000 +0! +#225000000 +1" +b10001 % +#226000000 +0" +#227000000 +1! +#228000000 +0! +#229000000 +1! +#230000000 +0! +#231000000 +1" +#232000000 +0" +#233000000 +1" +#234000000 +0" +#235000000 +1! +#236000000 +0! +#237000000 +1! +#238000000 +0! +#239000000 +1# +b10001 & +#240000000 +0# +#241000000 +1" +b10010 % +#242000000 +0" +#243000000 +1! +b10010 $ +#244000000 +0! +#245000000 +1" +#246000000 +0" +#247000000 +1! +#248000000 +0! +#249000000 +1" +#250000000 +0" +#251000000 +1! +#252000000 +0! +#253000000 +1" +#254000000 +0" +#255000000 +1" +#256000000 +0" +#257000000 +1" +#258000000 +0" +#259000000 +1! +#260000000 +0! +#261000000 +1" +#262000000 +0" +#263000000 +1# +b10010 & +#264000000 +0# +#265000000 +1" +b10011 % +#266000000 +0" +#267000000 +1! +b10011 $ +#268000000 +0! +#269000000 +1# +b10011 & +#270000000 +0# +#271000000 +1! +b10100 $ +#272000000 +0! +#273000000 +1" +b10100 % +#274000000 +0" +#275000000 +1! +#276000000 +0! +#277000000 +1# +b10100 & +#278000000 +0# +#279000000 +1" +b10101 % +#280000000 +0" +#281000000 +1" +#282000000 +0" +#283000000 +1! +b10101 $ +#284000000 +0! +#285000000 +1# +b10101 & +#286000000 +0# +#287000000 +1# +b10110 & +#288000000 +0# +#289000000 +1" +b10110 % +#290000000 +0" +#291000000 +1" +#292000000 +0" +#293000000 +1! +b10110 $ +#294000000 +0! +#295000000 +1! +b10111 $ +#296000000 +0! +#297000000 +1" +b10111 % +#298000000 +0" +#299000000 +1" +#300000000 +0" +#301000000 +1! +#302000000 +0! +#303000000 +1" +#304000000 +0" +#305000000 +1" +#306000000 +0" +#307000000 +1" +#308000000 +0" +#309000000 +1! +#310000000 +0! +#311000000 +1# +b10111 & +#312000000 +0# +#313000000 +1" +b11000 % +#314000000 +0" +#315000000 +1" +#316000000 +0" +#317000000 +1" +#318000000 +0" +#319000000 +1! +b11000 $ +#320000000 +0! +#321000000 +1" +#322000000 +0" +#323000000 +1" +#324000000 +0" +#325000000 +1" +#326000000 +0" +#327000000 +1" +#328000000 +0" +#329000000 +1" +#330000000 +0" +#331000000 +1" +#332000000 +0" +#333000000 +1" +#334000000 +0" +#335000000 +1# +b11000 & +#336000000 +0# +#337000000 +1" +b11001 % +#338000000 +0" +#339000000 +1" +#340000000 +0" +#341000000 +1# +b11001 & +#342000000 +0# +#343000000 +1! +b11001 $ +#344000000 +0! +#345000000 +1" +b11010 % +#346000000 +0" +#347000000 +1" +#348000000 +0" +#349000000 +1# +b11010 & +#350000000 +0# +#351000000 +1" +#352000000 +0" +#353000000 +1" +#354000000 +0" +#355000000 +1" +#356000000 +0" +#357000000 +1# +#358000000 +0# +#359000000 +1# +#360000000 +0# +#361000000 +1" +#362000000 +0" +#363000000 +1# +#364000000 +0# +#365000000 +1! +b11010 $ +#366000000 +0! +#367000000 +1! +b11011 $ +#368000000 +0! +#369000000 +1" +b11011 % +#370000000 +0" +#371000000 +1# +b11011 & +#372000000 +0# +#373000000 +1! +b11100 $ +#374000000 +0! +#375000000 +1" +b11100 % +#376000000 +0" +#377000000 +1" +#378000000 +0" +#379000000 +1# +b11100 & +#380000000 +0# +#381000000 +1! +b11101 $ +#382000000 +0! +#383000000 +1# +b11101 & +#384000000 +0# +#385000000 +1" +b11101 % +#386000000 +0" +#387000000 +1# +b11110 & +#388000000 +0# +#389000000 +1" +b11110 % +#390000000 +0" +#391000000 +1! +b11110 $ +#392000000 +0! +#393000000 +1" +b11111 % +#394000000 +0" +#395000000 +1# +b11111 & +#396000000 +0# +#397000000 +1" +#398000000 +0" +#399000000 +1" +#400000000 +0" +#401000000 +1" +#402000000 +0" +#403000000 +1# +#404000000 +0# +#405000000 +1" +#406000000 +0" +#407000000 +1# +#408000000 +0# +#409000000 +1" +#410000000 +0" +#411000000 +1# +#412000000 +0# +#413000000 +1# +#414000000 +0# +#415000000 +1! +b11111 $ +#416000000 +0! +#417000000 +1" +b100000 % +#418000000 +0" +#419000000 +1# +b100000 & +#420000000 +0# +#421000000 +1# +#422000000 +0# +#423000000 +1" +#424000000 +0" +#425000000 +1" +#426000000 +0" +#427000000 +1# +#428000000 +0# +#429000000 +1# +#430000000 +0# +#431000000 +1# +#432000000 +0# +#433000000 +1# +#434000000 +0# +#435000000 +1! +b100000 $ +#436000000 +0! +#437000000 +1! +b100001 $ +#438000000 +0! +#439000000 +1! +#440000000 +0! +#441000000 +1# +b100001 & +#442000000 +0# +#443000000 +1! +#444000000 +0! +#445000000 +1! +#446000000 +0! +#447000000 +1" +b100001 % +#448000000 +0" +#449000000 +1# +b100010 & +#450000000 +0# +#451000000 +1! +b100010 $ +#452000000 +0! +#453000000 +1! +#454000000 +0! +#455000000 +1# +#456000000 +0# +#457000000 +1# +#458000000 +0# +#459000000 +1! +#460000000 +0! +#461000000 +1" +b100010 % +#462000000 +0" +#463000000 +1! +b100011 $ +#464000000 +0! +#465000000 +1# +b100011 & +#466000000 +0# +#467000000 +1! +#468000000 +0! +#469000000 +1" +b100011 % +#470000000 +0" +#471000000 +1" +b100100 % +#472000000 +0" +#473000000 +1# +b100100 & +#474000000 +0# +#475000000 +1! +b100100 $ +#476000000 +0! +#477000000 +1" +b100101 % +#478000000 +0" +#479000000 +1# +b100101 & +#480000000 +0# +#481000000 +1# +#482000000 +0# +#483000000 +1! +b100101 $ +#484000000 +0! +#485000000 +1# +b100110 & +#486000000 +0# +#487000000 +1! +b100110 $ +#488000000 +0! +#489000000 +1# +#490000000 +0# +#491000000 +1! +#492000000 +0! +#493000000 +1# +#494000000 +0# +#495000000 +1" +b100110 % +#496000000 +0" +#497000000 +1# +b100111 & +#498000000 +0# +#499000000 +1! +b100111 $ +#500000000 +0! +#501000000 +1# +#502000000 +0# +#503000000 +1# +#504000000 +0# +#505000000 +1# +#506000000 +0# +#507000000 +1" +b100111 % +#508000000 +0" +#509000000 +1! +b101000 $ +#510000000 +0! +#511000000 +1! +#512000000 +0! +#513000000 +1# +b101000 & +#514000000 +0# +#515000000 +1" +b101000 % +#516000000 +0" +#517000000 +1! +b101001 $ +#518000000 +0! +#519000000 +1" +b101001 % +#520000000 +0" +#521000000 +1# +b101001 & +#522000000 +0# +#523000000 +1" +b101010 % +#524000000 +0" +#525000000 +1! +b101010 $ +#526000000 +0! +#527000000 +1# +b101010 & +#528000000 +0# +#529000000 +1# +b101011 & +#530000000 +0# +#531000000 +1" +b101011 % +#532000000 +0" +#533000000 +1" +#534000000 +0" +#535000000 +1! +b101011 $ +#536000000 +0! +#537000000 +1# +b101100 & +#538000000 +0# +#539000000 +1" +b101100 % +#540000000 +0" +#541000000 +1" +#542000000 +0" +#543000000 +1" +#544000000 +0" +#545000000 +1# +#546000000 +0# +#547000000 +1" +#548000000 +0" +#549000000 +1" +#550000000 +0" +#551000000 +1# +#552000000 +0# +#553000000 +1# +#554000000 +0# +#555000000 +1" +#556000000 +0" +#557000000 +1# +#558000000 +0# +#559000000 +1! +b101100 $ +#560000000 +0! +#561000000 +1# +b101101 & +#562000000 +0# +#563000000 +1" +b101101 % +#564000000 +0" +#565000000 +1# +#566000000 +0# +#567000000 +1" +#568000000 +0" +#569000000 +1# +#570000000 +0# +#571000000 +1" +#572000000 +0" +#573000000 +1# +#574000000 +0# +#575000000 +1# +#576000000 +0# +#577000000 +1# +#578000000 +0# +#579000000 +1# +#580000000 +0# +#581000000 +1! +b101101 $ +#582000000 +0! +#583000000 +1! +b101110 $ +#584000000 +0! +#585000000 +1# +b101110 & +#586000000 +0# +#587000000 +1# +#588000000 +0# +#589000000 +1! +#590000000 +0! +#591000000 +1" +b101110 % +#592000000 +0" +#593000000 +1# +b101111 & +#594000000 +0# +#595000000 +1# +#596000000 +0# +#597000000 +1! +b101111 $ +#598000000 +0! +#599000000 +1# +#600000000 +0# +#601000000 +1# +#602000000 +0# +#603000000 +1# +#604000000 +0# +#605000000 +1" +b101111 % +#606000000 +0" +#607000000 +1! +b110000 $ +#608000000 +0! +#609000000 +1# +b110000 & +#610000000 +0# +#611000000 +1# +#612000000 +0# +#613000000 +1" +b110000 % +#614000000 +0" +#615000000 +1" +b110001 % +#616000000 +0" +#617000000 +1# +b110001 & +#618000000 +0# +#619000000 +1# +#620000000 +0# +#621000000 +1" +#622000000 +0" +#623000000 +1# +#624000000 +0# +#625000000 +1# +#626000000 +0# +#627000000 +1# +#628000000 +0# +#629000000 +1# +#630000000 +0# +#631000000 +1! +b110001 $ +#632000000 +0! +#633000000 +1# +b110010 & +#634000000 +0# +#635000000 +1# +#636000000 +0# +#637000000 +1# +#638000000 +0# +#639000000 +1" +b110010 % +#640000000 +0" +#641000000 +1# +#642000000 +0# +#643000000 +1# +#644000000 +0# +#645000000 +1# +#646000000 +0# +#647000000 +1# +#648000000 +0# diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.txt b/crates/fayalite/tests/sim/expected/sim_only_connects.txt index 114b3138..3c1605d1 100644 --- a/crates/fayalite/tests/sim/expected/sim_only_connects.txt +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.txt @@ -717,52 +717,6 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 0, len: 0 }, - big_slots: StatePartIndexRange { start: 4, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, SimulationExternModuleState { module_state: SimulationModuleState { @@ -922,55 +876,8 @@ Simulation { running_generator: Some( ..., ), - wait_targets: { - Change { - key: CompiledValue { - layout: CompiledTypeLayout { - ty: Clock, - layout: TypeLayout { - small_slots: StatePartLayout { - len: 0, - debug_data: [], - .. - }, - big_slots: StatePartLayout { - len: 1, - debug_data: [ - SlotDebugData { - name: "", - ty: Clock, - }, - ], - .. - }, - sim_only_slots: StatePartLayout { - len: 0, - debug_data: [], - layout_data: [], - .. - }, - }, - body: Scalar, - }, - range: TypeIndexRange { - small_slots: StatePartIndexRange { start: 4, len: 0 }, - big_slots: StatePartIndexRange { start: 12, len: 1 }, - sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, - }, - write: None, - }, - value: SimValue { - ty: Clock, - value: OpaqueSimValue { - bits: 0x1_u1, - sim_only_values: [], - }, - }, - }, - }, }, ], - state_ready_to_run: false, trace_decls: TraceModule { name: "sim_only_connects", children: [ @@ -1628,9 +1535,214 @@ Simulation { }, ), ], - instant: 16 μs, clocks_triggered: [ StatePartIndex(1), ], + event_queue: EventQueue(EventQueueData { + instant: 16 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 30, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 31, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 4, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 4, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 6, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 30, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 4, len: 0 }, + big_slots: StatePartIndexRange { start: 12, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 13, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x1_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 31, + .. + }, + }, + ), + }, .. } \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_only_connects.vcd b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd index 2f464c0e..1e4c2497 100644 --- a/crates/fayalite/tests/sim/expected/sim_only_connects.vcd +++ b/crates/fayalite/tests/sim/expected/sim_only_connects.vcd @@ -72,22 +72,22 @@ s{} 8 $end #1000000 1! +s{\"extra\":\x20\"value\"} $ 1' +s{\"extra\":\x20\"value\"} ) 1+ +s{\"extra\":\x20\"value\"} - 10 11 15 -s{\"extra\":\x20\"value\"} $ -s{\"extra\":\x20\"value\"} ) -s{\"extra\":\x20\"value\"} - -s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} * -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 4 s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} % -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} * s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} . s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 3 s{\"bar\":\x20\"\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 7 -s{\"bar\":\x20\"\",\x20\"foo\":\x20\"baz\"} 8 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} & +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 4 +s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8 #2000000 0! 0" @@ -107,9 +107,6 @@ s{\"extra\":\x20\"value\"} / 00 11 15 -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 4 -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} & -s{\"bar\":\x20\"baz\",\x20\"extra\":\x20\"value\",\x20\"foo\":\x20\"baz\"} 8 #4000000 0! 0' From 840c5e1895b7cdad3eaa2c009558de9196fe477b Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 3 Nov 2025 23:59:36 -0800 Subject: [PATCH 06/10] add ExternModuleSimulationState::resettable helper for procedural simulations that have a reset input. --- crates/fayalite/src/sim.rs | 152 ++++- crates/fayalite/tests/sim.rs | 136 +++++ .../expected/sim_resettable_counter_async.txt | 550 ++++++++++++++++++ .../expected/sim_resettable_counter_async.vcd | 68 +++ ...settable_counter_async_immediate_reset.txt | 550 ++++++++++++++++++ ...settable_counter_async_immediate_reset.vcd | 65 +++ .../expected/sim_resettable_counter_sync.txt | 505 ++++++++++++++++ .../expected/sim_resettable_counter_sync.vcd | 70 +++ ...esettable_counter_sync_immediate_reset.txt | 505 ++++++++++++++++ ...esettable_counter_sync_immediate_reset.vcd | 70 +++ 10 files changed, 2669 insertions(+), 2 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 35da3367..5887dee3 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -54,7 +54,7 @@ use std::{ future::{Future, IntoFuture}, hash::Hash, mem, - pin::Pin, + pin::{Pin, pin}, ptr, rc::Rc, sync::{Arc, Mutex}, @@ -1187,6 +1187,33 @@ impl SimulationModuleState { } } #[track_caller] + fn is_reset_async(&self, io: Expr, which_module: WhichModule) -> bool { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't read from an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't read from an expression that's not based on one of this module's inputs/outputs" + ), + } + }; + match self.get_io(*target, which_module).layout.ty { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) + | CanonicalType::DynSimOnly(_) => unreachable!(), + CanonicalType::AsyncReset(_) => true, + CanonicalType::SyncReset(_) => false, + } + } + #[track_caller] fn read_helper( &self, io: Expr, @@ -2085,6 +2112,23 @@ impl SimulationImpl { } } let sensitivity_set = Rc::new(sensitivity_set); + struct CancelOnDrop<'a> { + this: &'a RefCell, + sensitivity_set: &'a Rc, + } + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + let Self { + this, + sensitivity_set, + } = self; + this.borrow_mut().cancel_wake_after_change(&sensitivity_set); + } + } + let _cancel_on_drop = CancelOnDrop { + this, + sensitivity_set: &sensitivity_set, + }; let mut timeout_instant = None; std::future::poll_fn(|cx| { if sensitivity_set.changed.get() { @@ -2125,7 +2169,6 @@ impl SimulationImpl { } }) .await; - this.borrow_mut().cancel_wake_after_change(&sensitivity_set); } #[must_use] #[track_caller] @@ -2628,6 +2671,11 @@ impl SimulationImpl { any_change.get() } #[track_caller] + fn is_reset_async(&self, io: Expr, which_module: WhichModule) -> bool { + self.get_module(which_module) + .is_reset_async(io, which_module) + } + #[track_caller] fn read( &mut self, io: Expr, @@ -2969,6 +3017,13 @@ macro_rules! impl_simulation_methods { .read_bit(Expr::canonical(io), $which_module); $self.settle_if_needed(retval)$(.$await)? } + #[track_caller] + pub fn is_reset_async(&$self, io: Expr) -> bool { + $self + .sim_impl + .borrow_mut() + .is_reset_async(Expr::canonical(io), $which_module) + } $(#[$track_caller])? pub $($async)? fn read(&mut $self, io: Expr) -> SimValue { let retval = $self @@ -3143,6 +3198,99 @@ impl ExternModuleSimulationState { pub async fn fork_join(&mut self, futures: F) -> F::Output { F::fork_join(futures, self).await } + async fn resettable_helper( + &mut self, + cancellable: &Cell, + wait_for_reset: impl AsyncFn(Self), + main: impl AsyncFn(Self), + ) -> ! { + let mut wait_for_reset_invocation = pin!(Some(wait_for_reset(self.forked_state()))); + let mut main_invocation = pin!(Some(main(self.forked_state()))); + std::future::poll_fn(|cx: &mut std::task::Context<'_>| { + loop { + if cancellable.get() && wait_for_reset_invocation.is_none() { + cancellable.set(false); + main_invocation.set(Some(main(self.forked_state()))); + wait_for_reset_invocation.set(Some(wait_for_reset(self.forked_state()))) + } + match main_invocation.as_mut().as_pin_mut().map(|f| f.poll(cx)) { + None | Some(Poll::Pending) => {} + Some(Poll::Ready(())) => { + main_invocation.set(None); + continue; + } + } + match wait_for_reset_invocation + .as_mut() + .as_pin_mut() + .map(|f| f.poll(cx)) + { + None | Some(Poll::Pending) => {} + Some(Poll::Ready(())) => { + wait_for_reset_invocation.set(None); + continue; + } + } + return Poll::Pending; + } + }) + .await + } + /// When `cd.rst` is deduced to be an [`AsyncReset`]: + /// * when `cd.rst` is asserted or when first called, `reset` is invoked and any running `run` invocation is cancelled. + /// * when `cd.rst` is de-asserted and when `reset` finishes, `run` is invoked. + /// + /// When `cd.rst` is deduced to be a [`SyncReset`]: + /// * when there's a positive-going clock edge on `cd.clk` and `cd.rst` is asserted or when first called, `reset` is invoked and any running `run` invocation is cancelled. + /// * when `reset` finishes, `run` is invoked. + pub async fn resettable( + &mut self, + cd: impl ToExpr>, + reset: impl AsyncFn(Self) -> T, + run: impl AsyncFn(Self, T), + ) -> ! { + let cd = cd.to_expr(); + let rst = cd.rst; + if self.is_reset_async(rst) { + let cancellable = Cell::new(false); + let wait_for_reset = |mut this: Self| async move { + while this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + while !this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + }; + let main = |mut this: Self| async { + let run_arg = reset(this.forked_state()).await; + cancellable.set(true); + while this.read_reset(rst).await { + this.wait_for_changes([rst], None).await; + } + run(this, run_arg).await + }; + self.resettable_helper(&cancellable, wait_for_reset, main) + .await + } else { + let clk = cd.clk; + let wait_for_reset = |mut this: Self| async move { + loop { + this.wait_for_clock_edge(clk).await; + if this.read_reset(rst).await { + return; + } + } + }; + let cancellable = Cell::new(false); + let main = |this: Self| async { + let run_arg = reset(this.forked_state()).await; + cancellable.set(true); + run(this, run_arg).await + }; + self.resettable_helper(&cancellable, wait_for_reset, main) + .await + } + } fn forked_state(&self) -> Self { let Self { ref sim_impl, diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index b055ffa0..1f452c92 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2108,3 +2108,139 @@ fn test_sim_fork_join() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn sim_resettable_counter() { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let out: UInt<8> = m.output(); + m.extern_module_simulation_fn((cd, out), |(cd, out), mut sim| async move { + sim.resettable( + cd, + |mut sim: ExternModuleSimulationState| async move { + sim.write(out, 0u8).await; + }, + |mut sim: ExternModuleSimulationState, ()| async move { + loop { + sim.wait_for_clock_edge(cd.clk).await; + let v: u8 = sim + .read(out) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(out, v.wrapping_add(1)).await; + } + }, + ) + .await + }); +} + +fn test_sim_resettable_counter_helper( + sim: &mut Simulation>, + immediate_reset: bool, +) { + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, immediate_reset); + for _ in 0..2 { + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + sim.write_reset(sim.io().cd.rst, false); + for expected in 0..3u8 { + assert_eq!(sim.read(sim.io().out), expected.to_sim_value()); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_clock(sim.io().cd.clk, false); + } + } +} + +#[test] +fn test_sim_resettable_counter_sync() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, false); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_sync.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_sync_immediate_reset() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, true); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_async() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, false); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_async.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_async.txt") { + panic!(); + } +} + +#[test] +fn test_sim_resettable_counter_async_immediate_reset() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(sim_resettable_counter::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + test_sim_resettable_counter_helper(&mut sim, true); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt new file mode 100644 index 00000000..351f944c --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.txt @@ -0,0 +1,550 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: AsyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceAsyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigAsyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 16, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 23, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 16, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 23, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd new file mode 100644 index 00000000..f05658f4 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async.vcd @@ -0,0 +1,68 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +b0 # +$end +#1000000 +1! +b1 # +#2000000 +0! +1" +b0 # +#3000000 +1! +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +b0 # +#13000000 +1! +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt new file mode 100644 index 00000000..abd7cf65 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.txt @@ -0,0 +1,550 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: AsyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceAsyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: AsyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigAsyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 13, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 20, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: AsyncReset, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: AsyncReset, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 1, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: AsyncReset, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 13, + .. + }, + }, + ), + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 20, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd new file mode 100644 index 00000000..99f7b867 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_async_immediate_reset.vcd @@ -0,0 +1,65 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +b0 # +$end +#1000000 +1! +#2000000 +0! +#3000000 +1! +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +b0 # +#13000000 +1! +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt new file mode 100644 index 00000000..8ff0c2fd --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.txt @@ -0,0 +1,505 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigSyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 42, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 43, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 42, + .. + }, + SensitivitySet { + id: 43, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd new file mode 100644 index 00000000..39c2641d --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync.vcd @@ -0,0 +1,70 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +b0 # +$end +#1000000 +1! +b1 # +#2000000 +0! +1" +#3000000 +1! +b10 # +b0 # +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +#13000000 +1! +b101 # +b0 # +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt new file mode 100644 index 00000000..e6819475 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.txt @@ -0,0 +1,505 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 3, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.clk", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::cd.rst", + ty: SyncReset, + }, + SlotDebugData { + name: "InstantiatedModule(sim_resettable_counter: sim_resettable_counter).sim_resettable_counter::out", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 3, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.clk, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.cd.rst, + Instance { + name: ::sim_resettable_counter, + instantiated: Module { + name: sim_resettable_counter, + .. + }, + }.out, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.clk, + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }.rst, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }: ModuleIO { + name: sim_resettable_counter::cd, + is_input: true, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + .. + }, + ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }: ModuleIO { + name: sim_resettable_counter::out, + is_input: false, + ty: UInt<8>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_resettable_counter", + children: [ + TraceModuleIO { + name: "cd", + child: TraceBundle { + name: "cd", + fields: [ + TraceClock { + location: TraceScalarId(0), + name: "clk", + flow: Source, + }, + TraceSyncReset { + location: TraceScalarId(1), + name: "rst", + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + ty: Bundle { + /* offset = 0 */ + clk: Clock, + /* offset = 1 */ + rst: SyncReset, + }, + flow: Source, + }, + TraceModuleIO { + name: "out", + child: TraceUInt { + location: TraceScalarId(2), + name: "out", + ty: UInt<8>, + flow: Sink, + }, + ty: UInt<8>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigSyncReset { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x03, + last_state: 0x03, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 20 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 43, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + SensitivitySet { + id: 44, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 43, + .. + }, + SensitivitySet { + id: 44, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd new file mode 100644 index 00000000..3cb97e24 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_resettable_counter_sync_immediate_reset.vcd @@ -0,0 +1,70 @@ +$timescale 1 ps $end +$scope module sim_resettable_counter $end +$scope struct cd $end +$var wire 1 ! clk $end +$var wire 1 " rst $end +$upscope $end +$var wire 8 # out $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +b0 # +$end +#1000000 +1! +b1 # +b0 # +#2000000 +0! +#3000000 +1! +b1 # +b0 # +#4000000 +0! +0" +#5000000 +1! +b1 # +#6000000 +0! +#7000000 +1! +b10 # +#8000000 +0! +#9000000 +1! +b11 # +#10000000 +0! +#11000000 +1! +b100 # +#12000000 +0! +1" +#13000000 +1! +b101 # +b0 # +#14000000 +0! +0" +#15000000 +1! +b1 # +#16000000 +0! +#17000000 +1! +b10 # +#18000000 +0! +#19000000 +1! +b11 # +#20000000 +0! From 0b77d1bea0af932a40fd221daf65d5a9b7d62bc5 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 5 Nov 2025 22:44:43 -0800 Subject: [PATCH 07/10] fix Simulator panicking when you use PhantomConst --- crates/fayalite/src/int/uint_in_range.rs | 4 +- crates/fayalite/src/lib.rs | 8 +- crates/fayalite/src/phantom_const.rs | 27 +- crates/fayalite/src/platform/peripherals.rs | 11 +- crates/fayalite/src/sim.rs | 46 ++ crates/fayalite/src/sim/compiler.rs | 86 +-- crates/fayalite/src/sim/vcd.rs | 44 +- crates/fayalite/src/ty/serde_impls.rs | 2 +- crates/fayalite/tests/sim.rs | 38 ++ .../tests/sim/expected/phantom_const.txt | 514 ++++++++++++++++++ .../tests/sim/expected/phantom_const.vcd | 31 ++ 11 files changed, 756 insertions(+), 55 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/phantom_const.txt create mode 100644 crates/fayalite/tests/sim/expected/phantom_const.vcd diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 970a439c..65d30927 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -300,9 +300,7 @@ macro_rules! define_uint_in_range_type { } } pub fn new(start: Start::SizeType, end: End::SizeType) -> Self { - Self::from_phantom_const_range(PhantomConst::new( - $SerdeRange { start, end }.intern_sized(), - )) + Self::from_phantom_const_range(PhantomConst::new_sized($SerdeRange { start, end })) } pub fn bit_width(self) -> usize { self.value.width() diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 96ee1f71..156aeed2 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -152,7 +152,7 @@ pub use fayalite_proc_macros::hdl_module; /// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types. /// /// ``` -/// # use fayalite::{intern::Intern, prelude::*}; +/// # use fayalite::prelude::*; /// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] /// pub struct Config { /// pub foo: usize, @@ -172,7 +172,7 @@ pub use fayalite_proc_macros::hdl_module; /// /// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: /// let bar = Bundle::new(Default::default()); -/// let config = PhantomConst::new(Config { foo: 12, bar }.intern_sized()); +/// let config = PhantomConst::new_sized(Config { foo: 12, bar }); /// let ty = WrapMyArray[config]; /// assert_eq!(ty.my_array, Array[bar][12]); /// ``` @@ -182,7 +182,7 @@ pub use fayalite_proc_macros::hdl_module; /// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types. /// /// ``` -/// # use fayalite::{intern::Intern, prelude::*}; +/// # use fayalite::prelude::*; /// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)] /// # pub struct ConfigItem {} /// # impl ConfigItem { @@ -207,7 +207,7 @@ pub use fayalite_proc_macros::hdl_module; /// } /// /// // you can then use Fayalite's standard syntax for creating dynamic types at runtime: -/// let config = PhantomConst::new(Config { items: vec![ConfigItem::new(); 5] }.intern_sized()); +/// let config = PhantomConst::new_sized(Config { items: vec![ConfigItem::new(); 5] }); /// let ty = FlagPerItem[config]; /// assert_eq!(ty.flags, Array[Bool][5]); /// ``` diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 9f251665..eb6a7584 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -131,7 +131,7 @@ impl Index for PhantomConstWithoutGenerics { type Output = PhantomConst; fn index(&self, value: T) -> &Self::Output { - Interned::into_inner(PhantomConst::new(value.intern()).intern_sized()) + Interned::into_inner(PhantomConst::new(&value).intern_sized()) } } @@ -222,11 +222,26 @@ impl Memoize for PhantomConstCanonicalMemoize PhantomConst { - pub fn new(value: Interned) -> Self { + pub fn new_interned(value: Interned) -> Self { Self { value: LazyInterned::Interned(value), } } + pub fn new_sized(value: T) -> Self + where + T: Clone, + { + Self::new_interned(value.intern_sized()) + } + pub fn new(value: &T) -> Self { + Self::new_interned(value.intern()) + } + pub fn new_deref>(value: U) -> Self + where + T: ToOwned, + { + Self::new_interned(value.intern_deref()) + } pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { Self { value: LazyInterned::new_lazy(v), @@ -245,7 +260,7 @@ impl PhantomConst { if let Some(&retval) = ::downcast_ref::(&self) { return retval; } - ::new( + ::new_interned( PhantomConstCanonicalMemoize::(PhantomData).get_owned(self.get()), ) } @@ -253,7 +268,7 @@ impl PhantomConst { if let Some(&retval) = ::downcast_ref::(&canonical_type) { return retval; } - Self::new( + Self::new_interned( PhantomConstCanonicalMemoize::(PhantomData).get_owned(canonical_type.get()), ) } @@ -346,7 +361,9 @@ impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst { D: Deserializer<'de>, { match SerdeType::::deserialize(deserializer)? { - SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => Ok(Self::new(value)), + SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => { + Ok(Self::new_interned(value)) + } ty => Err(Error::invalid_value( serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), &"a PhantomConst", diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs index 90c66402..387142de 100644 --- a/crates/fayalite/src/platform/peripherals.rs +++ b/crates/fayalite/src/platform/peripherals.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{intern::Intern, prelude::*}; +use crate::prelude::*; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; @@ -26,12 +26,9 @@ impl ClockInput { ); Self { clk: Clock, - properties: PhantomConst::new( - ClockInputProperties { - frequency: NotNan::new(frequency).expect("just checked"), - } - .intern_sized(), - ), + properties: PhantomConst::new_sized(ClockInputProperties { + frequency: NotNan::new(frequency).expect("just checked"), + }), } } pub fn frequency(self) -> f64 { diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 5887dee3..b19eeb04 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -416,6 +416,15 @@ impl_trace_decl! { name: Interned, flow: Flow, }), + PhantomConst(TracePhantomConst { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: PhantomConst, + flow: Flow, + }), SimOnly(TraceSimOnly { fn location(self) -> _ { self.location @@ -526,6 +535,11 @@ pub trait TraceWriter: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> Result<(), Self::Error>; + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error>; fn set_signal_sim_only_value( &mut self, id: TraceScalarId, @@ -585,6 +599,11 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { variant_index: usize, ty: Enum, ) -> std::io::Result<()>; + fn set_signal_phantom_const_dyn( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> std::io::Result<()>; fn set_signal_sim_only_value_dyn( &mut self, id: TraceScalarId, @@ -649,6 +668,13 @@ impl TraceWriterDynTrait for T { .map_err(err_into_io)?, ) } + fn set_signal_phantom_const_dyn( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_phantom_const(self, id, ty).map_err(err_into_io)?) + } fn set_signal_sim_only_value_dyn( &mut self, id: TraceScalarId, @@ -720,6 +746,13 @@ impl TraceWriter for DynTraceWriter { self.0 .set_signal_enum_discriminant_dyn(id, variant_index, ty) } + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error> { + self.0.set_signal_phantom_const_dyn(id, ty) + } fn set_signal_sim_only_value( &mut self, id: TraceScalarId, @@ -895,12 +928,16 @@ pub(crate) enum SimTraceKind { index: StatePartIndex, ty: DynSimOnly, }, + PhantomConst { + ty: PhantomConst, + }, } #[derive(PartialEq, Eq)] pub(crate) enum SimTraceState { Bits(BitVec), SimOnly(DynSimOnlyValue), + PhantomConst, } impl Clone for SimTraceState { @@ -908,6 +945,7 @@ impl Clone for SimTraceState { match self { Self::Bits(v) => Self::Bits(v.clone()), Self::SimOnly(v) => Self::SimOnly(v.clone()), + Self::PhantomConst => Self::PhantomConst, } } fn clone_from(&mut self, source: &Self) { @@ -956,6 +994,7 @@ impl fmt::Debug for SimTraceState { match self { SimTraceState::Bits(v) => BitSliceWriteWithBase(v).fmt(f), SimTraceState::SimOnly(v) => v.fmt(f), + SimTraceState::PhantomConst => f.debug_tuple("PhantomConst").finish(), } } } @@ -982,6 +1021,7 @@ impl SimTraceKind { SimTraceKind::EnumDiscriminant { index: _, ty } => { SimTraceState::Bits(BitVec::repeat(false, ty.discriminant_bit_width())) } + SimTraceKind::PhantomConst { .. } => SimTraceState::PhantomConst, SimTraceKind::SimOnly { index: _, ty } => SimTraceState::SimOnly(ty.default_value()), } } @@ -1097,6 +1137,7 @@ impl SimulationModuleState { true } } + CompiledTypeLayoutBody::PhantomConst => false, CompiledTypeLayoutBody::Bundle { .. } => { let value = value.map_ty(Bundle::from_canonical); let mut sub_targets = Vec::new(); @@ -1910,6 +1951,9 @@ impl SimulationImpl { ty, )?; } + SimTraceKind::PhantomConst { ty } => { + trace_writer.set_signal_phantom_const(id, ty)? + } SimTraceKind::SimOnly { .. } => { trace_writer.set_signal_sim_only_value(id, state.unwrap_sim_only_ref())? } @@ -1980,6 +2024,7 @@ impl SimulationImpl { .unwrap_bits_mut() .set(0, self.state.small_slots[index] != 0); } + SimTraceKind::PhantomConst { .. } => {} SimTraceKind::SimOnly { index, ty: _ } => { state .unwrap_sim_only_mut() @@ -2545,6 +2590,7 @@ impl SimulationImpl { ); } } + CompiledTypeLayoutBody::PhantomConst => {} CompiledTypeLayoutBody::Bundle { fields } => { let ty = Bundle::from_canonical(compiled_value.layout.ty); for ( diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index fbede7b4..98e5abfb 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -28,8 +28,8 @@ use crate::{ ExternModuleSimulation, SimTrace, SimTraceKind, SimTraces, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, - TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalarId, - TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, + TraceMemoryLocation, TraceModule, TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, + TraceScalarId, TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, interpreter::{ Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, SmallUInt, StatePartArrayIndex, @@ -85,6 +85,7 @@ pub(crate) struct CompiledBundleField { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub(crate) enum CompiledTypeLayoutBody { Scalar, + PhantomConst, Array { /// debug names are ignored, use parent's layout instead element: Interned>, @@ -165,14 +166,11 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Array { element }, } } - CanonicalType::PhantomConst(_) => { - let unit_layout = CompiledTypeLayout::get(()); - CompiledTypeLayout { - ty: *input, - layout: unit_layout.layout, - body: unit_layout.body, - } - } + CanonicalType::PhantomConst(_) => CompiledTypeLayout { + ty: *input, + layout: TypeLayout::empty(), + body: CompiledTypeLayoutBody::PhantomConst, + }, CanonicalType::Bundle(bundle) => { let mut layout = TypeLayout::empty(); let fields = bundle @@ -1681,18 +1679,23 @@ macro_rules! impl_compiler { instantiated_module: InstantiatedModule, target: MakeTraceDeclTarget, source_location: SourceLocation, + empty_kind: impl FnOnce() -> SimTraceKind, $($type_singular_field: impl FnOnce(StatePartIndex<$type_kind>) -> SimTraceKind,)* ) -> TraceLocation { match target { MakeTraceDeclTarget::Expr(target) => { let compiled_value = self.compile_expr(instantiated_module, target); let compiled_value = self.compiled_expr_to_value(compiled_value, source_location); - TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() { - $(Some(TypeLenSingle::$type_singular_variant) => { - $type_singular_field(compiled_value.range.$type_plural_field.start) - })* - None => unreachable!(), - })) + if compiled_value.range.is_empty() { + TraceLocation::Scalar(self.new_sim_trace(empty_kind())) + } else { + TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() { + $(Some(TypeLenSingle::$type_singular_variant) => { + $type_singular_field(compiled_value.range.$type_plural_field.start) + })* + None => unreachable!(), + })) + } } MakeTraceDeclTarget::Memory { id, @@ -1723,9 +1726,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallUInt { index, ty }, |index| SimTraceKind::BigUInt { index, ty }, - |_| unreachable!(""), + |_| unreachable!(), ), name, ty, @@ -1737,9 +1741,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallSInt { index, ty }, |index| SimTraceKind::BigSInt { index, ty }, - |_| unreachable!(""), + |_| unreachable!(), ), name, ty, @@ -1751,9 +1756,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallBool { index }, |index| SimTraceKind::BigBool { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1798,15 +1804,16 @@ macro_rules! impl_compiler { } .into() } - CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), CanonicalType::AsyncReset(_) => TraceAsyncReset { location: self.make_trace_scalar_helper( instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallAsyncReset { index }, |index| SimTraceKind::BigAsyncReset { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1817,9 +1824,10 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallSyncReset { index }, |index| SimTraceKind::BigSyncReset { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, @@ -1831,21 +1839,38 @@ macro_rules! impl_compiler { instantiated_module, target, source_location, + || unreachable!(), |index| SimTraceKind::SmallClock { index }, |index| SimTraceKind::BigClock { index }, - |_| unreachable!(""), + |_| unreachable!(), ), name, flow, } .into(), + CanonicalType::PhantomConst(ty) => TracePhantomConst { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + || SimTraceKind::PhantomConst { ty }, + |_| unreachable!(), + |_| unreachable!(), + |_| unreachable!(), + ), + name, + ty, + flow, + } + .into(), CanonicalType::DynSimOnly(ty) => TraceSimOnly { location: self.make_trace_scalar_helper( instantiated_module, target, source_location, - |_| unreachable!(""), - |_| unreachable!(""), + || unreachable!(), + |_| unreachable!(), + |_| unreachable!(), |index| SimTraceKind::SimOnly { index, ty }, ), name, @@ -2295,16 +2320,10 @@ impl Compiler { | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) - | CanonicalType::DynSimOnly(_) => { + | CanonicalType::DynSimOnly(_) + | CanonicalType::PhantomConst(_) => { self.make_trace_scalar(instantiated_module, target, name, source_location) } - CanonicalType::PhantomConst(_) => TraceBundle { - name, - fields: Interned::default(), - ty: Bundle::new(Interned::default()), - flow: target.flow(), - } - .into(), } } fn make_trace_decl( @@ -4293,6 +4312,7 @@ impl Compiler { start += element_bit_width; } } + CompiledTypeLayoutBody::PhantomConst => {} CompiledTypeLayoutBody::Bundle { fields } => { let CompiledTypeLayoutBody::Bundle { fields: mask_fields, diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index e66c3ee2..6ba37b31 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -6,12 +6,14 @@ use crate::{ expr::Flow, int::UInt, intern::{Intern, Interned}, + prelude::PhantomConst, sim::{ TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, - TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSimOnly, - TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, + TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, TraceScalar, TraceScalarId, + TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, + TraceWriterDecls, time::{SimDuration, SimInstant}, value::DynSimOnlyValue, }, @@ -283,6 +285,7 @@ impl WriteTrace for TraceScalar { Self::Clock(v) => v.write_trace(writer, arg), Self::SyncReset(v) => v.write_trace(writer, arg), Self::AsyncReset(v) => v.write_trace(writer, arg), + Self::PhantomConst(v) => v.write_trace(writer, arg), Self::SimOnly(v) => v.write_trace(writer, arg), } } @@ -549,6 +552,33 @@ impl WriteTrace for TraceAsyncReset { } } +impl WriteTrace for TracePhantomConst { + fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { + let ArgInType { + source_var_type: _, + sink_var_type: _, + duplex_var_type: _, + properties, + scope, + } = arg.in_type(); + let Self { + location, + name, + ty: _, + flow: _, + } = self; + write_vcd_var( + properties, + MemoryElementPartBody::Scalar, + writer, + "string", + 1, + location, + scope.new_identifier(name), + ) + } +} + impl WriteTrace for TraceSimOnly { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { let ArgInType { @@ -1091,6 +1121,16 @@ impl TraceWriter for VcdWriter { write_enum_discriminant_value_change(&mut self.writer, variant_index, ty, id.as_usize()) } + fn set_signal_phantom_const( + &mut self, + id: TraceScalarId, + ty: PhantomConst, + ) -> Result<(), Self::Error> { + // avoid multi-line strings because GTKWave can't display them properly: + // https://github.com/gtkwave/gtkwave/issues/460 + write_string_value_change(&mut self.writer, format_args!("{ty:?}"), id.as_usize()) + } + fn set_signal_sim_only_value( &mut self, id: TraceScalarId, diff --git a/crates/fayalite/src/ty/serde_impls.rs b/crates/fayalite/src/ty/serde_impls.rs index 1ca916b7..af324f9f 100644 --- a/crates/fayalite/src/ty/serde_impls.rs +++ b/crates/fayalite/src/ty/serde_impls.rs @@ -127,7 +127,7 @@ impl From for CanonicalType { SerdeCanonicalType::Reset => Self::Reset(Reset), SerdeCanonicalType::Clock => Self::Clock(Clock), SerdeCanonicalType::PhantomConst(value) => { - Self::PhantomConst(PhantomConst::new(value.0)) + Self::PhantomConst(PhantomConst::new_interned(value.0)) } SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value), } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 1f452c92..75f7cef4 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2244,3 +2244,41 @@ fn test_sim_resettable_counter_async_immediate_reset() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn phantom_const() { + #[hdl] + let out: Array>, 2> = + m.output(Array::new_static(PhantomConst::new_sized(vec![ + "a".into(), + "b".into(), + ]))); + let _ = out; + #[hdl] + let mut mem = memory(PhantomConst::new("mem_element")); + mem.depth(1); + let port = mem.new_read_port(); + connect_any(port.addr, 0u8); + connect(port.clk, false.to_clock()); + connect(port.en, false); +} + +#[test] +fn test_phantom_const() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(phantom_const()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.advance_time(SimDuration::from_micros(1)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/phantom_const.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/phantom_const.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/phantom_const.txt b/crates/fayalite/tests/sim/expected/phantom_const.txt new file mode 100644 index 00000000..dbc8f129 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/phantom_const.txt @@ -0,0 +1,514 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 5, + debug_data: [ + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: UInt<0>, + }, + ], + .. + }, + big_slots: StatePartLayout { + len: 7, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", + ty: UInt<0>, + }, + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", + ty: Clock, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<0>, + }, + SlotDebugData { + name: "", + ty: Bool, + }, + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 1, + debug_data: [ + (), + ], + layout_data: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x1 + [0x0]: 0x0, + ], + }, + ], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Const { + dest: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + value: 0x0, + }, + 1: Copy { + dest: StatePartIndex(6), // (0x0) SlotDebugData { name: "", ty: Clock }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:6:1 + 2: Copy { + dest: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", ty: Clock }, + src: StatePartIndex(6), // (0x0) SlotDebugData { name: "", ty: Clock }, + }, + // at: module-XXXXXXXXXX.rs:7:1 + 3: Copy { + dest: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", ty: Bool }, + src: StatePartIndex(5), // (0x0) SlotDebugData { name: "", ty: Bool }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 4: Const { + dest: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x0, + }, + 5: CastToUInt { + dest: StatePartIndex(4), // (0x0) SlotDebugData { name: "", ty: UInt<0> }, + src: StatePartIndex(3), // (0x0) SlotDebugData { name: "", ty: UInt<8> }, + dest_width: 0, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 6: Copy { + dest: StatePartIndex(0), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", ty: UInt<0> }, + src: StatePartIndex(4), // (0x0) SlotDebugData { name: "", ty: UInt<0> }, + }, + // at: module-XXXXXXXXXX.rs:3:1 + 7: CastBigToArrayIndex { + dest: StatePartIndex(4), // (0x0 0) SlotDebugData { name: "", ty: UInt<0> }, + src: StatePartIndex(0), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.addr", ty: UInt<0> }, + }, + 8: IsNonZeroDestIsSmall { + dest: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(1), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.en", ty: Bool }, + }, + 9: BranchIfSmallZero { + target: 11, + value: StatePartIndex(3), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 10: Branch { + target: 11, + }, + 11: IsNonZeroDestIsSmall { + dest: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + src: StatePartIndex(2), // (0x0) SlotDebugData { name: "InstantiatedModule(phantom_const: phantom_const).phantom_const::mem::r0.clk", ty: Clock }, + }, + 12: AndSmall { + dest: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + }, + 13: BranchIfSmallZero { + target: 14, + value: StatePartIndex(1), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + }, + 14: XorSmallImmediate { + dest: StatePartIndex(0), // (0x1 1) SlotDebugData { name: "", ty: Bool }, + lhs: StatePartIndex(2), // (0x0 0) SlotDebugData { name: "", ty: Bool }, + rhs: 0x1, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 15: Return, + ], + .. + }, + pc: 15, + memory_write_log: [], + memories: StatePart { + value: [ + MemoryData { + array_type: Array, + data: [ + // len = 0x1 + [0x0]: 0x0, + ], + }, + ], + }, + small_slots: StatePart { + value: [ + 1, + 0, + 0, + 0, + 0, + ], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out, + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out[0], + Instance { + name: ::phantom_const, + instantiated: Module { + name: phantom_const, + .. + }, + }.out[1], + }, + did_initial_settle: true, + }, + extern_modules: [], + trace_decls: TraceModule { + name: "phantom_const", + children: [ + TraceModuleIO { + name: "out", + child: TraceArray { + name: "out", + elements: [ + TracePhantomConst { + location: TraceScalarId(0), + name: "[0]", + ty: PhantomConst( + ["a","b"], + ), + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(1), + name: "[1]", + ty: PhantomConst( + ["a","b"], + ), + flow: Sink, + }, + ], + ty: Array, + flow: Sink, + }, + ty: Array, + flow: Sink, + }, + TraceMem { + id: TraceMemoryId(0), + name: "mem", + stride: 0, + element_type: TracePhantomConst { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 1, + stride: 0, + start: 0, + len: 0, + }, + name: "mem", + ty: PhantomConst( + "mem_element", + ), + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(2), + name: "addr", + ty: UInt<0>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(3), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(4), + name: "clk", + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(5), + name: "data", + ty: PhantomConst( + "mem_element", + ), + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + }, + ], + array_type: Array, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: PhantomConst { + ty: PhantomConst( + ["a","b"], + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + SimTrace { + id: TraceScalarId(1), + kind: PhantomConst { + ty: PhantomConst( + ["a","b"], + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<0>, + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(5), + kind: PhantomConst { + ty: PhantomConst( + "mem_element", + ), + }, + state: PhantomConst, + last_state: PhantomConst, + }, + ], + trace_memories: { + StatePartIndex(0): TraceMem { + id: TraceMemoryId(0), + name: "mem", + stride: 0, + element_type: TracePhantomConst { + location: TraceMemoryLocation { + id: TraceMemoryId(0), + depth: 1, + stride: 0, + start: 0, + len: 0, + }, + name: "mem", + ty: PhantomConst( + "mem_element", + ), + flow: Duplex, + }, + ports: [ + TraceMemPort { + name: "r0", + bundle: TraceBundle { + name: "r0", + fields: [ + TraceUInt { + location: TraceScalarId(2), + name: "addr", + ty: UInt<0>, + flow: Sink, + }, + TraceBool { + location: TraceScalarId(3), + name: "en", + flow: Sink, + }, + TraceClock { + location: TraceScalarId(4), + name: "clk", + flow: Sink, + }, + TracePhantomConst { + location: TraceScalarId(5), + name: "data", + ty: PhantomConst( + "mem_element", + ), + flow: Source, + }, + ], + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + flow: Sink, + }, + ty: Bundle { + /* offset = 0 */ + addr: UInt<0>, + /* offset = 0 */ + en: Bool, + /* offset = 1 */ + clk: Clock, + #[hdl(flip)] /* offset = 2 */ + data: PhantomConst( + "mem_element", + ), + }, + }, + ], + array_type: Array, + }, + }, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [ + StatePartIndex(1), + ], + event_queue: EventQueue(EventQueueData { + instant: 1 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: {}, + waiting_sensitivity_sets_by_compiled_value: {}, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/phantom_const.vcd b/crates/fayalite/tests/sim/expected/phantom_const.vcd new file mode 100644 index 00000000..ba3869b8 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/phantom_const.vcd @@ -0,0 +1,31 @@ +$timescale 1 ps $end +$scope module phantom_const $end +$scope struct out $end +$var string 1 ! \[0] $end +$var string 1 " \[1] $end +$upscope $end +$scope struct mem $end +$scope struct contents $end +$scope struct \[0] $end +$var string 1 ' mem $end +$upscope $end +$upscope $end +$scope struct r0 $end +$var string 0 # addr $end +$var wire 1 $ en $end +$var wire 1 % clk $end +$var string 1 & data $end +$upscope $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +s0 ' +sPhantomConst([\"a\",\"b\"]) ! +sPhantomConst([\"a\",\"b\"]) " +s0 # +0$ +0% +sPhantomConst(\"mem_element\") & +$end +#1000000 From 26a709017873c47d2d7d81efb8d2159fd04fd928 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 6 Nov 2025 20:22:53 -0800 Subject: [PATCH 08/10] add ParsedVisibility --- .../src/hdl_type_common.rs | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index f5b353e0..3a0e5e9e 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -4612,3 +4612,124 @@ impl MakeHdlTypeExpr for ParsedTypeTuple { }) } } + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum ParsedSimpleVisibility { + Public(Token![pub]), + PubCrate { + pub_token: Token![pub], + paren_token: Paren, + crate_token: Token![crate], + }, + Inherited, +} + +impl From for Visibility { + fn from(value: ParsedSimpleVisibility) -> Self { + match value { + ParsedSimpleVisibility::Public(v) => Visibility::Public(v), + ParsedSimpleVisibility::PubCrate { + pub_token, + paren_token, + crate_token, + } => Visibility::Restricted(syn::VisRestricted { + pub_token, + paren_token, + in_token: None, + path: Box::new(Ident::new("crate", crate_token.span).into()), + }), + ParsedSimpleVisibility::Inherited => Visibility::Inherited, + } + } +} + +impl PartialOrd for ParsedSimpleVisibility { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ParsedSimpleVisibility { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.visibility_level().cmp(&other.visibility_level()) + } +} + +impl ParsedSimpleVisibility { + const VISIBILITY_LEVEL_INHERITED: u8 = 0; + const VISIBILITY_LEVEL_RESTRICTED: u8 = 1 + Self::VISIBILITY_LEVEL_INHERITED; + const VISIBILITY_LEVEL_PUB_CRATE: u8 = 1 + Self::VISIBILITY_LEVEL_RESTRICTED; + const VISIBILITY_LEVEL_PUB: u8 = 1 + Self::VISIBILITY_LEVEL_PUB_CRATE; + fn visibility_level(self) -> u8 { + match self { + Self::Public(_) => Self::VISIBILITY_LEVEL_PUB, + Self::PubCrate { .. } => Self::VISIBILITY_LEVEL_PUB_CRATE, + Self::Inherited => Self::VISIBILITY_LEVEL_INHERITED, + } + } + pub(crate) fn parse(vis: Visibility) -> Result { + match vis { + Visibility::Public(v) => Ok(Self::Public(v)), + Visibility::Restricted(syn::VisRestricted { + pub_token, + paren_token, + in_token: None, + path, + }) if path.is_ident("crate") => Ok(Self::PubCrate { + pub_token, + paren_token, + crate_token: Token![crate](path.get_ident().expect("just checked").span()), + }), + Visibility::Restricted(v) => Err(v), + Visibility::Inherited => Ok(Self::Inherited), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum ParsedVisibility { + Simple(ParsedSimpleVisibility), + Restricted(syn::VisRestricted), +} + +impl From for Visibility { + fn from(value: ParsedVisibility) -> Self { + match value { + ParsedVisibility::Simple(v) => v.into(), + ParsedVisibility::Restricted(v) => Visibility::Restricted(v), + } + } +} + +impl PartialOrd for ParsedVisibility { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (ParsedVisibility::Simple(l), ParsedVisibility::Simple(r)) => Some(l.cmp(r)), + (ParsedVisibility::Simple(l), ParsedVisibility::Restricted(_)) => Some( + l.visibility_level() + .cmp(&ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED), + ), + (ParsedVisibility::Restricted(_), ParsedVisibility::Simple(r)) => { + Some(ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED.cmp(&r.visibility_level())) + } + (ParsedVisibility::Restricted(l), ParsedVisibility::Restricted(r)) => { + (l == r).then_some(std::cmp::Ordering::Equal) + } + } + } +} + +impl ParsedVisibility { + #[allow(dead_code)] + pub(crate) fn parse(vis: Visibility) -> Self { + match ParsedSimpleVisibility::parse(vis) { + Ok(simple) => Self::Simple(simple), + Err(restricted) => Self::Restricted(restricted), + } + } + #[allow(dead_code)] + pub(crate) fn min<'a>(&'a self, other: &'a Self) -> Option<&'a Self> { + self.partial_cmp(other) + .map(|ord| if ord.is_lt() { self } else { other }) + } +} From fbc8ffa5aea6cc76d643880cd21a34993fb1ec4f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 6 Nov 2025 20:23:16 -0800 Subject: [PATCH 09/10] fix private fields in #[hdl] pub struct --- .../src/hdl_bundle.rs | 11 ++----- crates/fayalite/src/bundle.rs | 25 ++++++---------- crates/fayalite/src/int/uint_in_range.rs | 2 -- crates/fayalite/tests/hdl_types.rs | 30 +++++++++++++++++++ 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index e8dc51bf..f952f426 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -224,7 +224,7 @@ impl Builder { .args .push_value(match get_field_state(field_index) { BuilderFieldState::Unfilled => parse_quote_spanned! {self.ident.span()=> - ::fayalite::bundle::Unfilled<#ty> + () }, BuilderFieldState::Generic => { let type_var = type_var_for_field_name(ident); @@ -383,7 +383,7 @@ impl ToTokens for Builder { fn default() -> Self { #ident { #phantom_field_name: ::fayalite::__std::marker::PhantomData, - #(#field_idents: ::fayalite::__std::default::Default::default(),)* + #(#field_idents: (),)* } } } @@ -395,7 +395,7 @@ impl ToTokens for Builder { let type_generics = self.generics.split_for_impl().1; quote_spanned! {self.ident.span()=> #[automatically_derived] - #[allow(non_camel_case_types, dead_code)] + #[allow(non_camel_case_types, dead_code, private_interfaces)] impl #filled_impl_generics ::fayalite::expr::ToExpr for #filled_ty #filled_where_clause { @@ -499,7 +499,6 @@ impl ToTokens for ParsedBundle { }; builder.to_tokens(tokens); let unfilled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Unfilled); - let filled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Filled); let mut mask_type_fields = FieldsNamed::from(fields.clone()); for Field { ty, .. } in &mut mask_type_fields.named { *ty = parse_quote_spanned! {span=> @@ -517,8 +516,6 @@ impl ToTokens for ParsedBundle { mask_type_builder.to_tokens(tokens); let unfilled_mask_type_builder_ty = mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Unfilled); - let filled_mask_type_builder_ty = - mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Filled); ItemStruct { attrs: vec![ common_derives(span), @@ -785,7 +782,6 @@ impl ToTokens for ParsedBundle { #where_clause { type Builder = #unfilled_mask_type_builder_ty; - type FilledBuilder = #filled_mask_type_builder_ty; fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> { ::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..]) } @@ -935,7 +931,6 @@ impl ToTokens for ParsedBundle { #where_clause { type Builder = #unfilled_builder_ty; - type FilledBuilder = #filled_builder_ty; fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> { ::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..]) } diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index a0de189f..0edf1928 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -271,7 +271,6 @@ impl Type for Bundle { pub trait BundleType: Type { type Builder: Default; - type FilledBuilder: ToExpr; fn fields(&self) -> Interned<[BundleField]>; } @@ -374,17 +373,8 @@ impl<'a> BundleSimValueToOpaque<'a> { #[derive(Default)] pub struct NoBuilder; -pub struct Unfilled(PhantomData); - -impl Default for Unfilled { - fn default() -> Self { - Self(PhantomData) - } -} - impl BundleType for Bundle { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { self.0.fields } @@ -420,15 +410,14 @@ macro_rules! impl_tuple_builder_fields { ) => { impl< $($head_type_var,)* - $cur_type_var: Type, $($tail_type_var,)* > TupleBuilder<( $($head_type_var,)* - Unfilled<$cur_type_var>, + (), $($tail_type_var,)* )> { - pub fn $cur_field(self, $cur_var: impl ToExpr) -> TupleBuilder<( + pub fn $cur_field<$cur_type_var: Type>(self, $cur_var: impl ToExpr) -> TupleBuilder<( $($head_type_var,)* Expr<$cur_type_var>, $($tail_type_var,)* @@ -452,6 +441,12 @@ macro_rules! impl_tuple_builder_fields { ($global:tt []) => {}; } +macro_rules! get_unit_ty { + ($($tt:tt)*) => { + () + }; +} + macro_rules! impl_tuples { ( [$({ @@ -545,8 +540,7 @@ macro_rules! impl_tuples { } } impl<$($T: Type,)*> BundleType for ($($T,)*) { - type Builder = TupleBuilder<($(Unfilled<$T>,)*)>; - type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>; + type Builder = TupleBuilder<($(get_unit_ty!($T),)*)>; fn fields(&self) -> Interned<[BundleField]> { let ($($var,)*) = self; [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice() @@ -791,7 +785,6 @@ impl ToExpr for PhantomDataBuilder { impl BundleType for PhantomData { type Builder = PhantomDataBuilder; - type FilledBuilder = PhantomDataBuilder; fn fields(&self) -> Interned<[BundleField]> { Interned::default() } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 65d30927..4ed101ec 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -96,7 +96,6 @@ impl Type for UIntInRangeMaskType { impl BundleType for UIntInRangeMaskType { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; @@ -400,7 +399,6 @@ macro_rules! define_uint_in_range_type { impl BundleType for $UIntInRangeType { type Builder = NoBuilder; - type FilledBuilder = Expr; fn fields(&self) -> Interned<[BundleField]> { let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; diff --git a/crates/fayalite/tests/hdl_types.rs b/crates/fayalite/tests/hdl_types.rs index 148cb642..50302827 100644 --- a/crates/fayalite/tests/hdl_types.rs +++ b/crates/fayalite/tests/hdl_types.rs @@ -214,3 +214,33 @@ pub struct MyTypeWithPhantomConstParameter>, pub b: HdlOption>, } + +#[hdl(outline_generated)] +struct MyPrivateType {} + +#[hdl(outline_generated)] +pub(crate) struct MyPubCrateType {} + +#[hdl(outline_generated)] +pub struct MyTypeWithPrivateMembers { + a: MyPrivateType, + pub(crate) b: MyPubCrateType, + pub c: Bool, +} + +#[hdl(outline_generated)] +struct MyPrivateTypeWithArg { + v: T, +} + +#[hdl(outline_generated)] +pub(crate) struct MyPubCrateTypeWithArg { + v: T, +} + +#[hdl(outline_generated)] +pub struct MyTypeWithPrivateMembersWithArg { + a: MyPrivateTypeWithArg, + pub(crate) b: MyPubCrateTypeWithArg, + pub c: T, +} From 45fea70c1841aedbb32377d88c0280c7a83e6208 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 7 Nov 2025 02:18:43 -0800 Subject: [PATCH 10/10] add ExternModuleSimulationState::fork_join_scope --- crates/fayalite/src/sim.rs | 243 ++- crates/fayalite/tests/sim.rs | 93 ++ .../sim/expected/sim_fork_join_scope.txt | 523 ++++++ .../sim/expected/sim_fork_join_scope.vcd | 1467 +++++++++++++++++ 4 files changed, 2325 insertions(+), 1 deletion(-) create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt create mode 100644 crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index b19eeb04..01233cb2 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -49,7 +49,7 @@ use std::{ any::Any, borrow::Cow, cell::{Cell, RefCell}, - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fmt, future::{Future, IntoFuture}, hash::Hash, @@ -3347,6 +3347,128 @@ impl ExternModuleSimulationState { module_index, } } + pub async fn fork_join_scope<'env, F, Fut>( + &mut self, + in_scope: F, + ) -> ::Output + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + let scope = ForkJoinScope { + new_tasks: Rc::new(RefCell::new(vec![])), + sim: self.forked_state(), + }; + let join_handle = scope.spawn(in_scope); + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + struct TaskId(u64); + struct TasksStateInner { + next_task_id: u64, + ready_tasks: BTreeSet, + not_ready_tasks: BTreeSet, + base_waker: std::task::Waker, + } + impl Default for TasksStateInner { + fn default() -> Self { + Self { + next_task_id: Default::default(), + ready_tasks: Default::default(), + not_ready_tasks: Default::default(), + base_waker: std::task::Waker::noop().clone(), + } + } + } + #[derive(Default)] + struct TasksState { + inner: Mutex, + } + #[derive(Clone)] + struct TaskWaker { + state: std::sync::Weak, + task: TaskId, + } + impl std::task::Wake for TaskWaker { + fn wake(self: Arc) { + self.wake_by_ref(); + } + fn wake_by_ref(self: &Arc) { + let Some(state) = self.state.upgrade() else { + return; + }; + let mut inner = state.inner.lock().expect("not poisoned"); + if inner.not_ready_tasks.remove(&self.task) { + inner.ready_tasks.insert(self.task); + inner.base_waker.wake_by_ref(); + } + } + } + struct Task<'env> { + task: Pin + 'env>>, + waker: std::task::Waker, + } + let mut tasks: BTreeMap = BTreeMap::new(); + let tasks_state = Arc::new(TasksState::default()); + std::future::poll_fn(move |cx: &mut std::task::Context<'_>| { + let mut state_inner = tasks_state.inner.lock().expect("not poisoned"); + state_inner.base_waker.clone_from(cx.waker()); + loop { + for new_task in scope.new_tasks.borrow_mut().drain(..) { + let task_id = TaskId(state_inner.next_task_id); + let Some(next_task_id) = state_inner.next_task_id.checked_add(1) else { + drop(state_inner); + panic!("spawned too many tasks"); + }; + state_inner.next_task_id = next_task_id; + state_inner.ready_tasks.insert(task_id); + tasks.insert( + task_id, + Task { + task: new_task, + waker: Arc::new(TaskWaker { + state: Arc::downgrade(&tasks_state), + task: task_id, + }) + .into(), + }, + ); + } + let Some(task_id) = state_inner.ready_tasks.pop_first() else { + if state_inner.not_ready_tasks.is_empty() { + return Poll::Ready(()); + } else { + return Poll::Pending; + }; + }; + state_inner.not_ready_tasks.insert(task_id); // task can be woken while we're running poll + drop(state_inner); + let std::collections::btree_map::Entry::Occupied(mut entry) = tasks.entry(task_id) + else { + unreachable!(); + }; + let Task { task, waker } = entry.get_mut(); + match task.as_mut().poll(&mut std::task::Context::from_waker( + &std::task::Waker::from(waker.clone()), + )) { + Poll::Pending => { + state_inner = tasks_state.inner.lock().expect("not poisoned"); + continue; + } + Poll::Ready(()) => {} + } + drop(entry.remove()); // drop outside lock + state_inner = tasks_state.inner.lock().expect("not poisoned"); + state_inner.not_ready_tasks.remove(&task_id); + state_inner.ready_tasks.remove(&task_id); + } + }) + .await; + match &mut *join_handle.state.borrow_mut() { + JoinHandleState::Running(_) => unreachable!(), + JoinHandleState::Finished(state) => state + .take() + .expect("filled by running all futures to completion"), + } + } impl_simulation_methods!( async_await = (async, await), track_caller = (), @@ -3354,6 +3476,125 @@ impl ExternModuleSimulationState { ); } +pub struct ForkJoinScope<'env> { + new_tasks: Rc + 'env>>>>>, + sim: ExternModuleSimulationState, +} + +impl<'env> Clone for ForkJoinScope<'env> { + fn clone(&self) -> Self { + Self { + new_tasks: self.new_tasks.clone(), + sim: self.sim.forked_state(), + } + } +} + +impl<'env> ForkJoinScope<'env> { + fn spawn_inner(&self, fut: Pin + 'env>>) { + self.new_tasks.borrow_mut().push(fut); + } + pub fn spawn_detached_future( + &self, + fut: impl IntoFuture>, + ) { + self.spawn_inner(Box::pin(fut.into_future())); + } + pub fn spawn_detached(&self, f: F) + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + self.spawn_detached_future(f(self.clone(), self.sim.forked_state())); + } + pub fn spawn(&self, f: F) -> JoinHandle + where + F: FnOnce(ForkJoinScope<'env>, ExternModuleSimulationState) -> Fut, + Fut: IntoFuture>, + { + let join_handle = JoinHandle { + state: Default::default(), + }; + let state = Rc::downgrade(&join_handle.state); + let fut = f(self.clone(), self.sim.forked_state()).into_future(); + self.spawn_detached_future(async move { + let result = fut.await; + let Some(state) = state.upgrade() else { return }; + let mut state = state.borrow_mut(); + let waker = match &mut *state { + JoinHandleState::Running(waker) => waker.take(), + JoinHandleState::Finished(_) => unreachable!(), + }; + *state = JoinHandleState::Finished(Some(result)); + drop(state); + let Some(waker) = waker else { return }; + waker.wake(); + }); + join_handle + } +} + +enum JoinHandleState { + Running(Option), + Finished(Option), +} + +impl Default for JoinHandleState { + fn default() -> Self { + Self::Running(None) + } +} + +pub struct JoinHandle { + state: Rc>>, +} + +impl JoinHandle { + pub fn is_finished(&self) -> bool { + matches!(*self.state.borrow(), JoinHandleState::Finished(_)) + } + pub fn try_join(self) -> Result { + let mut state = self.state.borrow_mut(); + match &mut *state { + JoinHandleState::Running(_) => { + drop(state); + Err(self) + } + JoinHandleState::Finished(retval) => { + let Some(retval) = retval.take() else { + panic!("already returned the value in poll"); + }; + Ok(retval) + } + } + } + pub async fn join(self) -> T { + self.await + } +} + +impl Future for JoinHandle { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + match &mut *self.state.borrow_mut() { + JoinHandleState::Running(waker) => { + match waker { + None => *waker = Some(cx.waker().clone()), + Some(waker) => waker.clone_from(cx.waker()), + } + Poll::Pending + } + JoinHandleState::Finished(retval) => { + let Some(retval) = retval.take() else { + panic!("already returned Poll::Ready"); + }; + Poll::Ready(retval) + } + } + } +} + struct ForkJoinImpl<'a> { futures: Vec + 'a>>>, } diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 75f7cef4..6d0380ba 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -2109,6 +2109,99 @@ fn test_sim_fork_join() { } } +#[hdl_module(outline_generated, extern)] +pub fn sim_fork_join_scope() +where + ConstUsize: KnownSize, +{ + #[hdl] + let clocks: Array = m.input(); + #[hdl] + let outputs: Array, N> = m.output(); + m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { + sim.write(outputs, [0u8; N]).await; + loop { + let written = vec![std::cell::Cell::new(false); N]; // test shared scope + let written = &written; // work around move in async move + sim.fork_join_scope(|scope, _| async move { + let mut spawned = vec![]; + for i in 0..N { + let join_handle = + scope.spawn(move |_, mut sim: ExternModuleSimulationState| async move { + sim.wait_for_clock_edge(clocks[i]).await; + let v = sim + .read_bool_or_int(outputs[i]) + .await + .to_bigint() + .try_into() + .expect("known to be in range"); + sim.write(outputs[i], 1u8.wrapping_add(v)).await; + written[i].set(true); + i + }); + if i % 2 == 0 && i < N - 1 { + spawned.push((i, join_handle)); + } + } + for (i, join_handle) in spawned { + assert_eq!(i, join_handle.join().await); + } + }) + .await; + for written in written { + assert!(written.get()); + } + } + }); +} + +#[test] +fn test_sim_fork_join_scope() { + let _n = SourceLocation::normalize_files_for_tests(); + const N: usize = 3; + let mut sim = Simulation::new(sim_fork_join_scope::()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().clocks, [false; N]); + let mut clocks_triggered = [false; N]; + let mut expected = [0u8; N]; + for i0 in 0..N { + for i1 in 0..N { + for i2 in 0..N { + for i3 in 0..N { + let indexes = [i0, i1, i2, i3]; + for i in indexes { + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], true); + sim.advance_time(SimDuration::from_micros(1)); + sim.write(sim.io().clocks[i], false); + if !clocks_triggered[i] { + expected[i] = expected[i].wrapping_add(1); + } + clocks_triggered[i] = true; + if clocks_triggered == [true; N] { + clocks_triggered = [false; N]; + } + let output = sim.read(sim.io().outputs); + assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); + } + } + } + } + } + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/sim_fork_join_scope.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/sim_fork_join_scope.txt") { + panic!(); + } +} + #[hdl_module(outline_generated, extern)] pub fn sim_resettable_counter() { #[hdl] diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt new file mode 100644 index 00000000..ba5577d4 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.txt @@ -0,0 +1,523 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 6, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[0]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[1]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::clocks[2]", + ty: Clock, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[0]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[1]", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(sim_fork_join_scope: sim_fork_join_scope).sim_fork_join_scope::outputs[2]", + ty: UInt<8>, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 0, + 0, + 0, + 49, + 50, + 50, + ], + }, + sim_only_slots: StatePart { + value: [], + }, + }, + io: Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[0], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[1], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.clocks[2], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs, + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[0], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[1], + Instance { + name: ::sim_fork_join_scope, + instantiated: Module { + name: sim_fork_join_scope, + .. + }, + }.outputs[2], + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[0], + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[1], + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }[2], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[0], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[1], + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }[2], + }, + did_initial_settle: true, + }, + sim: ExternModuleSimulation { + generator: SimGeneratorFn { + args: ( + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + ), + f: ..., + }, + sim_io_to_generator_map: { + ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }: ModuleIO { + name: sim_fork_join_scope::clocks, + is_input: true, + ty: Array, + .. + }, + ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }: ModuleIO { + name: sim_fork_join_scope::outputs, + is_input: false, + ty: Array, 3>, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + }, + running_generator: Some( + ..., + ), + }, + ], + trace_decls: TraceModule { + name: "sim_fork_join_scope", + children: [ + TraceModuleIO { + name: "clocks", + child: TraceArray { + name: "clocks", + elements: [ + TraceClock { + location: TraceScalarId(0), + name: "[0]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(1), + name: "[1]", + flow: Source, + }, + TraceClock { + location: TraceScalarId(2), + name: "[2]", + flow: Source, + }, + ], + ty: Array, + flow: Source, + }, + ty: Array, + flow: Source, + }, + TraceModuleIO { + name: "outputs", + child: TraceArray { + name: "outputs", + elements: [ + TraceUInt { + location: TraceScalarId(3), + name: "[0]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(4), + name: "[1]", + ty: UInt<8>, + flow: Sink, + }, + TraceUInt { + location: TraceScalarId(5), + name: "[2]", + ty: UInt<8>, + flow: Sink, + }, + ], + ty: Array, 3>, + flow: Sink, + }, + ty: Array, 3>, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigClock { + index: StatePartIndex(0), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigClock { + index: StatePartIndex(1), + }, + state: 0x0, + last_state: 0x0, + }, + SimTrace { + id: TraceScalarId(2), + kind: BigClock { + index: StatePartIndex(2), + }, + state: 0x0, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(3), + kind: BigUInt { + index: StatePartIndex(3), + ty: UInt<8>, + }, + state: 0x31, + last_state: 0x31, + }, + SimTrace { + id: TraceScalarId(4), + kind: BigUInt { + index: StatePartIndex(4), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + SimTrace { + id: TraceScalarId(5), + kind: BigUInt { + index: StatePartIndex(5), + ty: UInt<8>, + }, + state: 0x32, + last_state: 0x32, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + clocks_triggered: [], + event_queue: EventQueue(EventQueueData { + instant: 648 μs, + events: {}, + }), + waiting_sensitivity_sets_by_address: { + SensitivitySet { + id: 198, + values: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + }, + changed: Cell { + value: false, + }, + .. + }, + }, + waiting_sensitivity_sets_by_compiled_value: { + CompiledValue { + layout: CompiledTypeLayout { + ty: Clock, + layout: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 1, + debug_data: [ + SlotDebugData { + name: "", + ty: Clock, + }, + ], + .. + }, + sim_only_slots: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + body: Scalar, + }, + range: TypeIndexRange { + small_slots: StatePartIndexRange { start: 0, len: 0 }, + big_slots: StatePartIndexRange { start: 0, len: 1 }, + sim_only_slots: StatePartIndexRange { start: 0, len: 0 }, + }, + write: None, + }: ( + SimValue { + ty: Clock, + value: OpaqueSimValue { + bits: 0x0_u1, + sim_only_values: [], + }, + }, + { + SensitivitySet { + id: 198, + .. + }, + }, + ), + }, + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd new file mode 100644 index 00000000..555e83ed --- /dev/null +++ b/crates/fayalite/tests/sim/expected/sim_fork_join_scope.vcd @@ -0,0 +1,1467 @@ +$timescale 1 ps $end +$scope module sim_fork_join_scope $end +$scope struct clocks $end +$var wire 1 ! \[0] $end +$var wire 1 " \[1] $end +$var wire 1 # \[2] $end +$upscope $end +$scope struct outputs $end +$var wire 8 $ \[0] $end +$var wire 8 % \[1] $end +$var wire 8 & \[2] $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +0" +0# +b0 $ +b0 % +b0 & +$end +#1000000 +1! +b1 $ +#2000000 +0! +#3000000 +1! +#4000000 +0! +#5000000 +1! +#6000000 +0! +#7000000 +1! +#8000000 +0! +#9000000 +1! +#10000000 +0! +#11000000 +1! +#12000000 +0! +#13000000 +1! +#14000000 +0! +#15000000 +1" +b1 % +#16000000 +0" +#17000000 +1! +#18000000 +0! +#19000000 +1! +#20000000 +0! +#21000000 +1! +#22000000 +0! +#23000000 +1# +b1 & +#24000000 +0# +#25000000 +1! +b10 $ +#26000000 +0! +#27000000 +1! +#28000000 +0! +#29000000 +1" +b10 % +#30000000 +0" +#31000000 +1! +#32000000 +0! +#33000000 +1! +#34000000 +0! +#35000000 +1! +#36000000 +0! +#37000000 +1" +#38000000 +0" +#39000000 +1" +#40000000 +0" +#41000000 +1! +#42000000 +0! +#43000000 +1! +#44000000 +0! +#45000000 +1" +#46000000 +0" +#47000000 +1# +b10 & +#48000000 +0# +#49000000 +1! +b11 $ +#50000000 +0! +#51000000 +1! +#52000000 +0! +#53000000 +1# +b11 & +#54000000 +0# +#55000000 +1! +#56000000 +0! +#57000000 +1! +#58000000 +0! +#59000000 +1! +#60000000 +0! +#61000000 +1# +#62000000 +0# +#63000000 +1" +b11 % +#64000000 +0" +#65000000 +1! +b100 $ +#66000000 +0! +#67000000 +1! +#68000000 +0! +#69000000 +1# +b100 & +#70000000 +0# +#71000000 +1# +#72000000 +0# +#73000000 +1! +#74000000 +0! +#75000000 +1" +b100 % +#76000000 +0" +#77000000 +1! +b101 $ +#78000000 +0! +#79000000 +1! +#80000000 +0! +#81000000 +1! +#82000000 +0! +#83000000 +1" +b101 % +#84000000 +0" +#85000000 +1! +#86000000 +0! +#87000000 +1" +#88000000 +0" +#89000000 +1! +#90000000 +0! +#91000000 +1" +#92000000 +0" +#93000000 +1! +#94000000 +0! +#95000000 +1# +b101 & +#96000000 +0# +#97000000 +1! +b110 $ +#98000000 +0! +#99000000 +1" +b110 % +#100000000 +0" +#101000000 +1" +#102000000 +0" +#103000000 +1! +#104000000 +0! +#105000000 +1! +#106000000 +0! +#107000000 +1" +#108000000 +0" +#109000000 +1" +#110000000 +0" +#111000000 +1" +#112000000 +0" +#113000000 +1! +#114000000 +0! +#115000000 +1" +#116000000 +0" +#117000000 +1" +#118000000 +0" +#119000000 +1# +b110 & +#120000000 +0# +#121000000 +1! +b111 $ +#122000000 +0! +#123000000 +1" +b111 % +#124000000 +0" +#125000000 +1# +b111 & +#126000000 +0# +#127000000 +1! +b1000 $ +#128000000 +0! +#129000000 +1! +#130000000 +0! +#131000000 +1" +b1000 % +#132000000 +0" +#133000000 +1# +b1000 & +#134000000 +0# +#135000000 +1" +b1001 % +#136000000 +0" +#137000000 +1! +b1001 $ +#138000000 +0! +#139000000 +1" +#140000000 +0" +#141000000 +1# +b1001 & +#142000000 +0# +#143000000 +1# +b1010 & +#144000000 +0# +#145000000 +1! +b1010 $ +#146000000 +0! +#147000000 +1# +#148000000 +0# +#149000000 +1! +#150000000 +0! +#151000000 +1! +#152000000 +0! +#153000000 +1! +#154000000 +0! +#155000000 +1# +#156000000 +0# +#157000000 +1! +#158000000 +0! +#159000000 +1" +b1010 % +#160000000 +0" +#161000000 +1! +b1011 $ +#162000000 +0! +#163000000 +1# +b1011 & +#164000000 +0# +#165000000 +1! +#166000000 +0! +#167000000 +1# +#168000000 +0# +#169000000 +1! +#170000000 +0! +#171000000 +1# +#172000000 +0# +#173000000 +1" +b1011 % +#174000000 +0" +#175000000 +1! +b1100 $ +#176000000 +0! +#177000000 +1! +#178000000 +0! +#179000000 +1# +b1100 & +#180000000 +0# +#181000000 +1" +b1100 % +#182000000 +0" +#183000000 +1" +b1101 % +#184000000 +0" +#185000000 +1! +b1101 $ +#186000000 +0! +#187000000 +1# +b1101 & +#188000000 +0# +#189000000 +1" +b1110 % +#190000000 +0" +#191000000 +1# +b1110 & +#192000000 +0# +#193000000 +1! +b1110 $ +#194000000 +0! +#195000000 +1# +b1111 & +#196000000 +0# +#197000000 +1# +#198000000 +0# +#199000000 +1! +b1111 $ +#200000000 +0! +#201000000 +1! +#202000000 +0! +#203000000 +1# +#204000000 +0# +#205000000 +1# +#206000000 +0# +#207000000 +1" +b1111 % +#208000000 +0" +#209000000 +1! +b10000 $ +#210000000 +0! +#211000000 +1# +b10000 & +#212000000 +0# +#213000000 +1# +#214000000 +0# +#215000000 +1# +#216000000 +0# +#217000000 +1" +b10000 % +#218000000 +0" +#219000000 +1! +b10001 $ +#220000000 +0! +#221000000 +1! +#222000000 +0! +#223000000 +1! +#224000000 +0! +#225000000 +1" +b10001 % +#226000000 +0" +#227000000 +1! +#228000000 +0! +#229000000 +1! +#230000000 +0! +#231000000 +1" +#232000000 +0" +#233000000 +1" +#234000000 +0" +#235000000 +1! +#236000000 +0! +#237000000 +1! +#238000000 +0! +#239000000 +1# +b10001 & +#240000000 +0# +#241000000 +1" +b10010 % +#242000000 +0" +#243000000 +1! +b10010 $ +#244000000 +0! +#245000000 +1" +#246000000 +0" +#247000000 +1! +#248000000 +0! +#249000000 +1" +#250000000 +0" +#251000000 +1! +#252000000 +0! +#253000000 +1" +#254000000 +0" +#255000000 +1" +#256000000 +0" +#257000000 +1" +#258000000 +0" +#259000000 +1! +#260000000 +0! +#261000000 +1" +#262000000 +0" +#263000000 +1# +b10010 & +#264000000 +0# +#265000000 +1" +b10011 % +#266000000 +0" +#267000000 +1! +b10011 $ +#268000000 +0! +#269000000 +1# +b10011 & +#270000000 +0# +#271000000 +1! +b10100 $ +#272000000 +0! +#273000000 +1" +b10100 % +#274000000 +0" +#275000000 +1! +#276000000 +0! +#277000000 +1# +b10100 & +#278000000 +0# +#279000000 +1" +b10101 % +#280000000 +0" +#281000000 +1" +#282000000 +0" +#283000000 +1! +b10101 $ +#284000000 +0! +#285000000 +1# +b10101 & +#286000000 +0# +#287000000 +1# +b10110 & +#288000000 +0# +#289000000 +1" +b10110 % +#290000000 +0" +#291000000 +1" +#292000000 +0" +#293000000 +1! +b10110 $ +#294000000 +0! +#295000000 +1! +b10111 $ +#296000000 +0! +#297000000 +1" +b10111 % +#298000000 +0" +#299000000 +1" +#300000000 +0" +#301000000 +1! +#302000000 +0! +#303000000 +1" +#304000000 +0" +#305000000 +1" +#306000000 +0" +#307000000 +1" +#308000000 +0" +#309000000 +1! +#310000000 +0! +#311000000 +1# +b10111 & +#312000000 +0# +#313000000 +1" +b11000 % +#314000000 +0" +#315000000 +1" +#316000000 +0" +#317000000 +1" +#318000000 +0" +#319000000 +1! +b11000 $ +#320000000 +0! +#321000000 +1" +#322000000 +0" +#323000000 +1" +#324000000 +0" +#325000000 +1" +#326000000 +0" +#327000000 +1" +#328000000 +0" +#329000000 +1" +#330000000 +0" +#331000000 +1" +#332000000 +0" +#333000000 +1" +#334000000 +0" +#335000000 +1# +b11000 & +#336000000 +0# +#337000000 +1" +b11001 % +#338000000 +0" +#339000000 +1" +#340000000 +0" +#341000000 +1# +b11001 & +#342000000 +0# +#343000000 +1! +b11001 $ +#344000000 +0! +#345000000 +1" +b11010 % +#346000000 +0" +#347000000 +1" +#348000000 +0" +#349000000 +1# +b11010 & +#350000000 +0# +#351000000 +1" +#352000000 +0" +#353000000 +1" +#354000000 +0" +#355000000 +1" +#356000000 +0" +#357000000 +1# +#358000000 +0# +#359000000 +1# +#360000000 +0# +#361000000 +1" +#362000000 +0" +#363000000 +1# +#364000000 +0# +#365000000 +1! +b11010 $ +#366000000 +0! +#367000000 +1! +b11011 $ +#368000000 +0! +#369000000 +1" +b11011 % +#370000000 +0" +#371000000 +1# +b11011 & +#372000000 +0# +#373000000 +1! +b11100 $ +#374000000 +0! +#375000000 +1" +b11100 % +#376000000 +0" +#377000000 +1" +#378000000 +0" +#379000000 +1# +b11100 & +#380000000 +0# +#381000000 +1! +b11101 $ +#382000000 +0! +#383000000 +1# +b11101 & +#384000000 +0# +#385000000 +1" +b11101 % +#386000000 +0" +#387000000 +1# +b11110 & +#388000000 +0# +#389000000 +1" +b11110 % +#390000000 +0" +#391000000 +1! +b11110 $ +#392000000 +0! +#393000000 +1" +b11111 % +#394000000 +0" +#395000000 +1# +b11111 & +#396000000 +0# +#397000000 +1" +#398000000 +0" +#399000000 +1" +#400000000 +0" +#401000000 +1" +#402000000 +0" +#403000000 +1# +#404000000 +0# +#405000000 +1" +#406000000 +0" +#407000000 +1# +#408000000 +0# +#409000000 +1" +#410000000 +0" +#411000000 +1# +#412000000 +0# +#413000000 +1# +#414000000 +0# +#415000000 +1! +b11111 $ +#416000000 +0! +#417000000 +1" +b100000 % +#418000000 +0" +#419000000 +1# +b100000 & +#420000000 +0# +#421000000 +1# +#422000000 +0# +#423000000 +1" +#424000000 +0" +#425000000 +1" +#426000000 +0" +#427000000 +1# +#428000000 +0# +#429000000 +1# +#430000000 +0# +#431000000 +1# +#432000000 +0# +#433000000 +1# +#434000000 +0# +#435000000 +1! +b100000 $ +#436000000 +0! +#437000000 +1! +b100001 $ +#438000000 +0! +#439000000 +1! +#440000000 +0! +#441000000 +1# +b100001 & +#442000000 +0# +#443000000 +1! +#444000000 +0! +#445000000 +1! +#446000000 +0! +#447000000 +1" +b100001 % +#448000000 +0" +#449000000 +1# +b100010 & +#450000000 +0# +#451000000 +1! +b100010 $ +#452000000 +0! +#453000000 +1! +#454000000 +0! +#455000000 +1# +#456000000 +0# +#457000000 +1# +#458000000 +0# +#459000000 +1! +#460000000 +0! +#461000000 +1" +b100010 % +#462000000 +0" +#463000000 +1! +b100011 $ +#464000000 +0! +#465000000 +1# +b100011 & +#466000000 +0# +#467000000 +1! +#468000000 +0! +#469000000 +1" +b100011 % +#470000000 +0" +#471000000 +1" +b100100 % +#472000000 +0" +#473000000 +1# +b100100 & +#474000000 +0# +#475000000 +1! +b100100 $ +#476000000 +0! +#477000000 +1" +b100101 % +#478000000 +0" +#479000000 +1# +b100101 & +#480000000 +0# +#481000000 +1# +#482000000 +0# +#483000000 +1! +b100101 $ +#484000000 +0! +#485000000 +1# +b100110 & +#486000000 +0# +#487000000 +1! +b100110 $ +#488000000 +0! +#489000000 +1# +#490000000 +0# +#491000000 +1! +#492000000 +0! +#493000000 +1# +#494000000 +0# +#495000000 +1" +b100110 % +#496000000 +0" +#497000000 +1# +b100111 & +#498000000 +0# +#499000000 +1! +b100111 $ +#500000000 +0! +#501000000 +1# +#502000000 +0# +#503000000 +1# +#504000000 +0# +#505000000 +1# +#506000000 +0# +#507000000 +1" +b100111 % +#508000000 +0" +#509000000 +1! +b101000 $ +#510000000 +0! +#511000000 +1! +#512000000 +0! +#513000000 +1# +b101000 & +#514000000 +0# +#515000000 +1" +b101000 % +#516000000 +0" +#517000000 +1! +b101001 $ +#518000000 +0! +#519000000 +1" +b101001 % +#520000000 +0" +#521000000 +1# +b101001 & +#522000000 +0# +#523000000 +1" +b101010 % +#524000000 +0" +#525000000 +1! +b101010 $ +#526000000 +0! +#527000000 +1# +b101010 & +#528000000 +0# +#529000000 +1# +b101011 & +#530000000 +0# +#531000000 +1" +b101011 % +#532000000 +0" +#533000000 +1" +#534000000 +0" +#535000000 +1! +b101011 $ +#536000000 +0! +#537000000 +1# +b101100 & +#538000000 +0# +#539000000 +1" +b101100 % +#540000000 +0" +#541000000 +1" +#542000000 +0" +#543000000 +1" +#544000000 +0" +#545000000 +1# +#546000000 +0# +#547000000 +1" +#548000000 +0" +#549000000 +1" +#550000000 +0" +#551000000 +1# +#552000000 +0# +#553000000 +1# +#554000000 +0# +#555000000 +1" +#556000000 +0" +#557000000 +1# +#558000000 +0# +#559000000 +1! +b101100 $ +#560000000 +0! +#561000000 +1# +b101101 & +#562000000 +0# +#563000000 +1" +b101101 % +#564000000 +0" +#565000000 +1# +#566000000 +0# +#567000000 +1" +#568000000 +0" +#569000000 +1# +#570000000 +0# +#571000000 +1" +#572000000 +0" +#573000000 +1# +#574000000 +0# +#575000000 +1# +#576000000 +0# +#577000000 +1# +#578000000 +0# +#579000000 +1# +#580000000 +0# +#581000000 +1! +b101101 $ +#582000000 +0! +#583000000 +1! +b101110 $ +#584000000 +0! +#585000000 +1# +b101110 & +#586000000 +0# +#587000000 +1# +#588000000 +0# +#589000000 +1! +#590000000 +0! +#591000000 +1" +b101110 % +#592000000 +0" +#593000000 +1# +b101111 & +#594000000 +0# +#595000000 +1# +#596000000 +0# +#597000000 +1! +b101111 $ +#598000000 +0! +#599000000 +1# +#600000000 +0# +#601000000 +1# +#602000000 +0# +#603000000 +1# +#604000000 +0# +#605000000 +1" +b101111 % +#606000000 +0" +#607000000 +1! +b110000 $ +#608000000 +0! +#609000000 +1# +b110000 & +#610000000 +0# +#611000000 +1# +#612000000 +0# +#613000000 +1" +b110000 % +#614000000 +0" +#615000000 +1" +b110001 % +#616000000 +0" +#617000000 +1# +b110001 & +#618000000 +0# +#619000000 +1# +#620000000 +0# +#621000000 +1" +#622000000 +0" +#623000000 +1# +#624000000 +0# +#625000000 +1# +#626000000 +0# +#627000000 +1# +#628000000 +0# +#629000000 +1# +#630000000 +0# +#631000000 +1! +b110001 $ +#632000000 +0! +#633000000 +1# +b110010 & +#634000000 +0# +#635000000 +1# +#636000000 +0# +#637000000 +1# +#638000000 +0# +#639000000 +1" +b110010 % +#640000000 +0" +#641000000 +1# +#642000000 +0# +#643000000 +1# +#644000000 +0# +#645000000 +1# +#646000000 +0# +#647000000 +1# +#648000000 +0#