forked from libre-chip/fayalite
		
	Compare commits
	
		
			No commits in common. "86a1bb46be4690e854470cf4a1a6e90c92268391" and "7005fa3330d6715008a3e3cd0b57c71108b465d2" have entirely different histories.
		
	
	
		
			86a1bb46be
			...
			7005fa3330
		
	
		
					 23 changed files with 234 additions and 4768 deletions
				
			
		|  | @ -1210,29 +1210,17 @@ fn hdl_main( | |||
|     attr: TokenStream, | ||||
|     item: TokenStream, | ||||
| ) -> syn::Result<TokenStream> { | ||||
|     fn parse_evaluated_cfgs_attr<R>( | ||||
|         input: ParseStream, | ||||
|         parse_inner: impl FnOnce(ParseStream) -> syn::Result<R>, | ||||
|     ) -> syn::Result<R> { | ||||
|         let _: Token![#] = input.parse()?; | ||||
|         let bracket_content; | ||||
|         bracketed!(bracket_content in input); | ||||
|         let _: kw::__evaluated_cfgs = bracket_content.parse()?; | ||||
|         let paren_content; | ||||
|         parenthesized!(paren_content in bracket_content); | ||||
|         parse_inner(&paren_content) | ||||
|     } | ||||
|     let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2( | ||||
|     let (evaluated_cfgs, attr): (_, TokenStream) = Parser::parse2( | ||||
|         |input: ParseStream| { | ||||
|             let peek = input.fork(); | ||||
|             if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() { | ||||
|                 let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::<bool>::parse)?; | ||||
|                 Ok((Some(evaluated_cfgs), input.parse()?)) | ||||
|             if input.peek(Bracket) && input.peek2(kw::__evaluated_cfgs) { | ||||
|                 let cfgs = input.parse()?; | ||||
|                 let _: kw::__evaluated_cfgs = input.parse()?; | ||||
|                 Ok((Some(cfgs), input.parse()?)) | ||||
|             } else { | ||||
|                 Ok((None, input.parse()?)) | ||||
|             } | ||||
|         }, | ||||
|         item, | ||||
|         attr, | ||||
|     )?; | ||||
|     let cfgs = if let Some(cfgs) = evaluated_cfgs { | ||||
|         cfgs | ||||
|  | @ -1241,11 +1229,12 @@ fn hdl_main( | |||
|         if cfgs.cfgs_list.is_empty() { | ||||
|             Cfgs::default() | ||||
|         } else { | ||||
|             let key = kw::__evaluated_cfgs::default(); | ||||
|             return Ok(quote! { | ||||
|                 ::fayalite::__cfg_expansion_helper! { | ||||
|                     [] | ||||
|                     #cfgs | ||||
|                     {#[::fayalite::#kw(#attr)]} { #item } | ||||
|                     #[::fayalite::#kw:(#key #attr)] { #item } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  |  | |||
|  | @ -1109,7 +1109,7 @@ fn parse_quote_let_pat<T, R: ToTokens, C: Borrow<Token![:]>>( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { | ||||
| fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { | ||||
|     parse_quote_spanned! {ty.span()=> | ||||
|         ::fayalite::expr::Expr<#ty> | ||||
|     } | ||||
|  | @ -1586,7 +1586,7 @@ impl Visitor<'_> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn empty_let() -> Local { | ||||
| fn empty_let() -> Local { | ||||
|     Local { | ||||
|         attrs: vec![], | ||||
|         let_token: Default::default(), | ||||
|  | @ -1672,7 +1672,7 @@ impl Fold for Visitor<'_> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn fold_local(&mut self, mut let_stmt: Local) -> Local { | ||||
|     fn fold_local(&mut self, let_stmt: Local) -> Local { | ||||
|         match self | ||||
|             .errors | ||||
|             .ok(HdlAttr::<Nothing, kw::hdl>::parse_and_leave_attr( | ||||
|  | @ -1682,25 +1682,6 @@ impl Fold for Visitor<'_> { | |||
|             Some(None) => return fold_local(self, let_stmt), | ||||
|             Some(Some(HdlAttr { .. })) => {} | ||||
|         }; | ||||
|         let mut pat = &let_stmt.pat; | ||||
|         if let Pat::Type(pat_type) = pat { | ||||
|             pat = &pat_type.pat; | ||||
|         } | ||||
|         let Pat::Ident(syn::PatIdent { | ||||
|             attrs: _, | ||||
|             by_ref: None, | ||||
|             mutability: _, | ||||
|             ident: _, | ||||
|             subpat: None, | ||||
|         }) = pat | ||||
|         else { | ||||
|             let hdl_attr = HdlAttr::<Nothing, kw::hdl>::parse_and_take_attr(&mut let_stmt.attrs) | ||||
|                 .ok() | ||||
|                 .flatten() | ||||
|                 .expect("already checked above"); | ||||
|             let let_stmt = fold_local(self, let_stmt); | ||||
|             return self.process_hdl_let_pat(hdl_attr, let_stmt); | ||||
|         }; | ||||
|         let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream()); | ||||
|         let Some(hdl_let) = self.errors.ok(hdl_let) else { | ||||
|             return empty_let(); | ||||
|  |  | |||
|  | @ -3,111 +3,22 @@ | |||
| use crate::{ | ||||
|     fold::{impl_fold, DoFold}, | ||||
|     kw, | ||||
|     module::transform_body::{empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, Visitor}, | ||||
|     module::transform_body::{with_debug_clone_and_fold, Visitor}, | ||||
|     Errors, HdlAttr, PairsIterExt, | ||||
| }; | ||||
| use proc_macro2::{Span, TokenStream}; | ||||
| use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; | ||||
| use std::collections::BTreeSet; | ||||
| use syn::{ | ||||
|     fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold}, | ||||
|     fold::{fold_arm, fold_expr_match, fold_pat, Fold}, | ||||
|     parse::Nothing, | ||||
|     parse_quote_spanned, | ||||
|     punctuated::Punctuated, | ||||
|     spanned::Spanned, | ||||
|     token::{Brace, Paren}, | ||||
|     Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr, | ||||
|     PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment, | ||||
|     Token, TypePath, | ||||
|     Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen, | ||||
|     PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath, | ||||
| }; | ||||
| 
 | ||||
| macro_rules! visit_trait { | ||||
|     ( | ||||
|         $($vis:vis fn $fn:ident($state:ident: _, $value:ident: &$Value:ty) $block:block)* | ||||
|     ) => { | ||||
|         trait VisitMatchPat<'a> { | ||||
|             $(fn $fn(&mut self, $value: &'a $Value) { | ||||
|                 $fn(self, $value); | ||||
|             })* | ||||
|         } | ||||
| 
 | ||||
|         $($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a $Value) $block)* | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| visit_trait! { | ||||
|     fn visit_match_pat_binding(_state: _, v: &MatchPatBinding) { | ||||
|         let MatchPatBinding { ident: _ } = v; | ||||
|     } | ||||
|     fn visit_match_pat_wild(_state: _, v: &MatchPatWild) { | ||||
|         let MatchPatWild { underscore_token: _ } = v; | ||||
|     } | ||||
|     fn visit_match_pat_rest(_state: _, v: &MatchPatRest) { | ||||
|         let MatchPatRest { dot2_token: _ } = v; | ||||
|     } | ||||
|     fn visit_match_pat_paren(state: _, v: &MatchPatParen<MatchPat>) { | ||||
|         let MatchPatParen { paren_token: _, pat } = v; | ||||
|         state.visit_match_pat(pat); | ||||
|     } | ||||
|     fn visit_match_pat_paren_simple(state: _, v: &MatchPatParen<MatchPatSimple>) { | ||||
|         let MatchPatParen { paren_token: _, pat } = v; | ||||
|         state.visit_match_pat_simple(pat); | ||||
|     } | ||||
|     fn visit_match_pat_or(state: _, v: &MatchPatOr<MatchPat>) { | ||||
|         let MatchPatOr { leading_vert: _, cases } = v; | ||||
|         for v in cases { | ||||
|             state.visit_match_pat(v); | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat_or_simple(state: _, v: &MatchPatOr<MatchPatSimple>) { | ||||
|         let MatchPatOr { leading_vert: _, cases } = v; | ||||
|         for v in cases { | ||||
|             state.visit_match_pat_simple(v); | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat_struct_field(state: _, v: &MatchPatStructField) { | ||||
|         let MatchPatStructField { field_name: _, colon_token: _, pat } = v; | ||||
|         state.visit_match_pat_simple(pat); | ||||
|     } | ||||
|     fn visit_match_pat_struct(state: _, v: &MatchPatStruct) { | ||||
|         let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v; | ||||
|         for v in fields { | ||||
|             state.visit_match_pat_struct_field(v); | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat_tuple(state: _, v: &MatchPatTuple) { | ||||
|         let MatchPatTuple { paren_token: _, fields } = v; | ||||
|         for v in fields { | ||||
|             state.visit_match_pat_simple(v); | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) { | ||||
|         let MatchPatEnumVariant {match_span:_, variant_path: _, enum_path: _, variant_name: _, field } = v; | ||||
|         if let Some((_, v)) = field { | ||||
|             state.visit_match_pat_simple(v); | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat_simple(state: _, v: &MatchPatSimple) { | ||||
|         match v { | ||||
|             MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v), | ||||
|             MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v), | ||||
|             MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v), | ||||
|             MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v), | ||||
|             MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v), | ||||
|         } | ||||
|     } | ||||
|     fn visit_match_pat(state: _, v: &MatchPat) { | ||||
|         match v { | ||||
|             MatchPat::Simple(v) => state.visit_match_pat_simple(v), | ||||
|             MatchPat::Or(v) => state.visit_match_pat_or(v), | ||||
|             MatchPat::Paren(v) => state.visit_match_pat_paren(v), | ||||
|             MatchPat::Struct(v) => state.visit_match_pat_struct(v), | ||||
|             MatchPat::Tuple(v) => state.visit_match_pat_tuple(v), | ||||
|             MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| with_debug_clone_and_fold! { | ||||
|     struct MatchPatBinding<> { | ||||
|         ident: Ident, | ||||
|  | @ -142,15 +53,6 @@ with_debug_clone_and_fold! { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<P> MatchPatOr<P> { | ||||
|     /// returns the first `|` between two patterns
 | ||||
|     fn first_inner_vert(&self) -> Option<Token![|]> { | ||||
|         let mut pairs = self.cases.pairs(); | ||||
|         pairs.next_back(); | ||||
|         pairs.next().and_then(|v| v.into_tuple().1.copied()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<P: ToTokens> ToTokens for MatchPatOr<P> { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         let Self { | ||||
|  | @ -175,19 +77,6 @@ impl ToTokens for MatchPatWild { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| with_debug_clone_and_fold! { | ||||
|     struct MatchPatRest<> { | ||||
|         dot2_token: Token![..], | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for MatchPatRest { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         let Self { dot2_token } = self; | ||||
|         dot2_token.to_tokens(tokens); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| with_debug_clone_and_fold! { | ||||
|     struct MatchPatStructField<> { | ||||
|         field_name: Ident, | ||||
|  | @ -270,25 +159,6 @@ impl ToTokens for MatchPatStruct { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| with_debug_clone_and_fold! { | ||||
|     struct MatchPatTuple<> { | ||||
|         paren_token: Paren, | ||||
|         fields: Punctuated<MatchPatSimple, Token![,]>, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ToTokens for MatchPatTuple { | ||||
|     fn to_tokens(&self, tokens: &mut TokenStream) { | ||||
|         let Self { | ||||
|             paren_token, | ||||
|             fields, | ||||
|         } = self; | ||||
|         paren_token.surround(tokens, |tokens| { | ||||
|             fields.to_tokens(tokens); | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| with_debug_clone_and_fold! { | ||||
|     struct MatchPatEnumVariant<> { | ||||
|         match_span: Span, | ||||
|  | @ -324,7 +194,6 @@ enum MatchPatSimple { | |||
|     Or(MatchPatOr<MatchPatSimple>), | ||||
|     Binding(MatchPatBinding), | ||||
|     Wild(MatchPatWild), | ||||
|     Rest(MatchPatRest), | ||||
| } | ||||
| 
 | ||||
| impl_fold! { | ||||
|  | @ -333,7 +202,6 @@ impl_fold! { | |||
|         Or(MatchPatOr<MatchPatSimple>), | ||||
|         Binding(MatchPatBinding), | ||||
|         Wild(MatchPatWild), | ||||
|         Rest(MatchPatRest), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -344,7 +212,6 @@ impl ToTokens for MatchPatSimple { | |||
|             Self::Paren(v) => v.to_tokens(tokens), | ||||
|             Self::Binding(v) => v.to_tokens(tokens), | ||||
|             Self::Wild(v) => v.to_tokens(tokens), | ||||
|             Self::Rest(v) => v.to_tokens(tokens), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -411,7 +278,6 @@ trait ParseMatchPat: Sized { | |||
|     fn or(v: MatchPatOr<Self>) -> Self; | ||||
|     fn paren(v: MatchPatParen<Self>) -> Self; | ||||
|     fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>; | ||||
|     fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()>; | ||||
|     fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant) | ||||
|         -> Result<Self, ()>; | ||||
|     fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> { | ||||
|  | @ -596,34 +462,7 @@ trait ParseMatchPat: Sized { | |||
|             }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { | ||||
|                 underscore_token, | ||||
|             }))), | ||||
|             Pat::Tuple(PatTuple { | ||||
|                 attrs: _, | ||||
|                 paren_token, | ||||
|                 elems, | ||||
|             }) => { | ||||
|                 let fields = elems | ||||
|                     .into_pairs() | ||||
|                     .filter_map_pair_value(|field_pat| { | ||||
|                         if let Pat::Rest(PatRest { | ||||
|                             attrs: _, | ||||
|                             dot2_token, | ||||
|                         }) = field_pat | ||||
|                         { | ||||
|                             Some(MatchPatSimple::Rest(MatchPatRest { dot2_token })) | ||||
|                         } else { | ||||
|                             MatchPatSimple::parse(state, field_pat).ok() | ||||
|                         } | ||||
|                     }) | ||||
|                     .collect(); | ||||
|                 Self::tuple( | ||||
|                     state, | ||||
|                     MatchPatTuple { | ||||
|                         paren_token, | ||||
|                         fields, | ||||
|                     }, | ||||
|                 ) | ||||
|             } | ||||
|             Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { | ||||
|             Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { | ||||
|                 state | ||||
|                     .errors | ||||
|                     .error(pat, "not yet implemented in #[hdl] patterns"); | ||||
|  | @ -658,14 +497,6 @@ impl ParseMatchPat for MatchPatSimple { | |||
|         Err(()) | ||||
|     } | ||||
| 
 | ||||
|     fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> { | ||||
|         state.errors.push(syn::Error::new( | ||||
|             v.paren_token.span.open(), | ||||
|             "matching tuples is not yet implemented inside structs/enums in #[hdl] patterns", | ||||
|         )); | ||||
|         Err(()) | ||||
|     } | ||||
| 
 | ||||
|     fn enum_variant( | ||||
|         state: &mut HdlMatchParseState<'_>, | ||||
|         v: MatchPatEnumVariant, | ||||
|  | @ -684,7 +515,6 @@ enum MatchPat { | |||
|     Or(MatchPatOr<MatchPat>), | ||||
|     Paren(MatchPatParen<MatchPat>), | ||||
|     Struct(MatchPatStruct), | ||||
|     Tuple(MatchPatTuple), | ||||
|     EnumVariant(MatchPatEnumVariant), | ||||
| } | ||||
| 
 | ||||
|  | @ -694,7 +524,6 @@ impl_fold! { | |||
|         Or(MatchPatOr<MatchPat>), | ||||
|         Paren(MatchPatParen<MatchPat>), | ||||
|         Struct(MatchPatStruct), | ||||
|         Tuple(MatchPatTuple), | ||||
|         EnumVariant(MatchPatEnumVariant), | ||||
|     } | ||||
| } | ||||
|  | @ -716,10 +545,6 @@ impl ParseMatchPat for MatchPat { | |||
|         Ok(Self::Struct(v)) | ||||
|     } | ||||
| 
 | ||||
|     fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> { | ||||
|         Ok(Self::Tuple(v)) | ||||
|     } | ||||
| 
 | ||||
|     fn enum_variant( | ||||
|         _state: &mut HdlMatchParseState<'_>, | ||||
|         v: MatchPatEnumVariant, | ||||
|  | @ -735,7 +560,6 @@ impl ToTokens for MatchPat { | |||
|             Self::Or(v) => v.to_tokens(tokens), | ||||
|             Self::Paren(v) => v.to_tokens(tokens), | ||||
|             Self::Struct(v) => v.to_tokens(tokens), | ||||
|             Self::Tuple(v) => v.to_tokens(tokens), | ||||
|             Self::EnumVariant(v) => v.to_tokens(tokens), | ||||
|         } | ||||
|     } | ||||
|  | @ -798,6 +622,10 @@ struct RewriteAsCheckMatch { | |||
| } | ||||
| 
 | ||||
| impl Fold for RewriteAsCheckMatch { | ||||
|     fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat { | ||||
|         i.colon_token = Some(Token)); | ||||
|         i | ||||
|     } | ||||
|     fn fold_pat(&mut self, pat: Pat) -> Pat { | ||||
|         match pat { | ||||
|             Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) { | ||||
|  | @ -912,30 +740,6 @@ impl Fold for RewriteAsCheckMatch { | |||
|         // don't recurse into expressions
 | ||||
|         i | ||||
|     } | ||||
|     fn fold_local(&mut self, mut let_stmt: Local) -> Local { | ||||
|         if let Some(syn::LocalInit { | ||||
|             eq_token, | ||||
|             expr: _, | ||||
|             diverge, | ||||
|         }) = let_stmt.init.take() | ||||
|         { | ||||
|             let_stmt.init = Some(syn::LocalInit { | ||||
|                 eq_token, | ||||
|                 expr: parse_quote_spanned! {self.span=> | ||||
|                     __match_value | ||||
|                 }, | ||||
|                 diverge: diverge.map(|(else_, _expr)| { | ||||
|                     ( | ||||
|                         else_, | ||||
|                         parse_quote_spanned! {self.span=> | ||||
|                             match __infallible {} | ||||
|                         }, | ||||
|                     ) | ||||
|                 }), | ||||
|             }); | ||||
|         } | ||||
|         fold_local(self, let_stmt) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct HdlMatchParseState<'a> { | ||||
|  | @ -943,123 +747,7 @@ struct HdlMatchParseState<'a> { | |||
|     errors: &'a mut Errors, | ||||
| } | ||||
| 
 | ||||
| struct HdlLetPatVisitState<'a> { | ||||
|     errors: &'a mut Errors, | ||||
|     bindings: BTreeSet<&'a Ident>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> { | ||||
|     fn visit_match_pat_binding(&mut self, v: &'a MatchPatBinding) { | ||||
|         self.bindings.insert(&v.ident); | ||||
|     } | ||||
| 
 | ||||
|     fn visit_match_pat_or(&mut self, v: &'a MatchPatOr<MatchPat>) { | ||||
|         if let Some(first_inner_vert) = v.first_inner_vert() { | ||||
|             self.errors.error( | ||||
|                 first_inner_vert, | ||||
|                 "or-patterns are not supported in let statements", | ||||
|             ); | ||||
|         } | ||||
|         visit_match_pat_or(self, v); | ||||
|     } | ||||
| 
 | ||||
|     fn visit_match_pat_or_simple(&mut self, v: &'a MatchPatOr<MatchPatSimple>) { | ||||
|         if let Some(first_inner_vert) = v.first_inner_vert() { | ||||
|             self.errors.error( | ||||
|                 first_inner_vert, | ||||
|                 "or-patterns are not supported in let statements", | ||||
|             ); | ||||
|         } | ||||
|         visit_match_pat_or_simple(self, v); | ||||
|     } | ||||
| 
 | ||||
|     fn visit_match_pat_enum_variant(&mut self, v: &'a MatchPatEnumVariant) { | ||||
|         self.errors.error(v, "refutable pattern in let statement"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Visitor<'_> { | ||||
|     pub(crate) fn process_hdl_let_pat( | ||||
|         &mut self, | ||||
|         _hdl_attr: HdlAttr<Nothing, kw::hdl>, | ||||
|         mut let_stmt: Local, | ||||
|     ) -> Local { | ||||
|         let span = let_stmt.let_token.span(); | ||||
|         if let Pat::Type(pat) = &mut let_stmt.pat { | ||||
|             *pat.ty = wrap_ty_with_expr((*pat.ty).clone()); | ||||
|         } | ||||
|         let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone()); | ||||
|         let Local { | ||||
|             attrs: _, | ||||
|             let_token, | ||||
|             pat, | ||||
|             init, | ||||
|             semi_token, | ||||
|         } = let_stmt; | ||||
|         self.require_normal_module_or_fn(let_token); | ||||
|         let Some(syn::LocalInit { | ||||
|             eq_token, | ||||
|             expr, | ||||
|             diverge, | ||||
|         }) = init | ||||
|         else { | ||||
|             self.errors | ||||
|                 .error(let_token, "#[hdl] let must be assigned a value"); | ||||
|             return empty_let(); | ||||
|         }; | ||||
|         if let Some((else_, _)) = diverge { | ||||
|             // TODO: implement let-else
 | ||||
|             self.errors | ||||
|                 .error(else_, "#[hdl] let ... else { ... } is not implemented"); | ||||
|             return empty_let(); | ||||
|         } | ||||
|         let Ok(pat) = MatchPat::parse( | ||||
|             &mut HdlMatchParseState { | ||||
|                 match_span: span, | ||||
|                 errors: &mut self.errors, | ||||
|             }, | ||||
|             pat, | ||||
|         ) else { | ||||
|             return empty_let(); | ||||
|         }; | ||||
|         let mut state = HdlLetPatVisitState { | ||||
|             errors: &mut self.errors, | ||||
|             bindings: BTreeSet::new(), | ||||
|         }; | ||||
|         state.visit_match_pat(&pat); | ||||
|         let HdlLetPatVisitState { | ||||
|             errors: _, | ||||
|             bindings, | ||||
|         } = state; | ||||
|         let retval = parse_quote_spanned! {span=> | ||||
|             let (#(#bindings,)* __scope,) = { | ||||
|                 type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant; | ||||
|                 let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); | ||||
|                 ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { | ||||
|                     #[allow(unused_variables)] | ||||
|                     #check_let_stmt | ||||
|                     match __infallible {} | ||||
|                 }); | ||||
|                 let mut __match_iter = ::fayalite::module::match_(__match_expr); | ||||
|                 let ::fayalite::__std::option::Option::Some(__match_variant) = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { | ||||
|                     ::fayalite::__std::unreachable!("#[hdl] let with uninhabited type"); | ||||
|                 }; | ||||
|                 let ::fayalite::__std::option::Option::None = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else { | ||||
|                     ::fayalite::__std::unreachable!("#[hdl] let with refutable pattern"); | ||||
|                 }; | ||||
|                 let (__match_variant, __scope) = | ||||
|                     ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( | ||||
|                         __match_variant, | ||||
|                     ); | ||||
|                 #let_token #pat #eq_token __match_variant #semi_token | ||||
|                 (#(#bindings,)* __scope,) | ||||
|             }; | ||||
|         }; | ||||
|         match retval { | ||||
|             syn::Stmt::Local(retval) => retval, | ||||
|             _ => unreachable!(), | ||||
|         } | ||||
|     } | ||||
|     pub(crate) fn process_hdl_match( | ||||
|         &mut self, | ||||
|         _hdl_attr: HdlAttr<Nothing, kw::hdl>, | ||||
|  |  | |||
|  | @ -5,9 +5,6 @@ use std::{env, fs, path::Path}; | |||
| 
 | ||||
| fn main() { | ||||
|     println!("cargo::rustc-check-cfg=cfg(todo)"); | ||||
|     println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)"); | ||||
|     println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)"); | ||||
|     println!("cargo::rustc-cfg=cfg_true_for_tests"); | ||||
|     let path = "visit_types.json"; | ||||
|     println!("cargo::rerun-if-changed={path}"); | ||||
|     println!("cargo::rerun-if-changed=build.rs"); | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| // See Notices.txt for copyright information
 | ||||
| //! ## `#[hdl] let` statements
 | ||||
| 
 | ||||
| pub mod destructuring; | ||||
| pub mod inputs_outputs; | ||||
| pub mod instances; | ||||
| pub mod memories; | ||||
|  |  | |||
|  | @ -1,33 +0,0 @@ | |||
| // SPDX-License-Identifier: LGPL-3.0-or-later
 | ||||
| // See Notices.txt for copyright information
 | ||||
| //! ### Destructuring Let
 | ||||
| //!
 | ||||
| //! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns.
 | ||||
| //!
 | ||||
| //! `#[hdl] let` statements can only match one level of struct/tuple pattern for now,
 | ||||
| //! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`.
 | ||||
| //!
 | ||||
| //! ```
 | ||||
| //! # use fayalite::prelude::*;
 | ||||
| //! #[hdl]
 | ||||
| //! struct MyStruct {
 | ||||
| //!     a: UInt<8>,
 | ||||
| //!     b: Bool,
 | ||||
| //! }
 | ||||
| //!
 | ||||
| //! #[hdl_module]
 | ||||
| //! fn my_module() {
 | ||||
| //!     #[hdl]
 | ||||
| //!     let my_input: MyStruct = m.input();
 | ||||
| //!     #[hdl]
 | ||||
| //!     let my_output: UInt<8> = m.input();
 | ||||
| //!     #[hdl]
 | ||||
| //!     let MyStruct { a, b } = my_input;
 | ||||
| //!     #[hdl]
 | ||||
| //!     if b {
 | ||||
| //!         connect(my_output, a);
 | ||||
| //!     } else {
 | ||||
| //!         connect(my_output, 0_hdl_u8);
 | ||||
| //!     }
 | ||||
| //! }
 | ||||
| //! ```
 | ||||
|  | @ -7,5 +7,5 @@ | |||
| //!
 | ||||
| //! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
 | ||||
| //!
 | ||||
| //! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now,
 | ||||
| //! `#[hdl] match` statements can only match one level of struct/enum pattern for now,
 | ||||
| //! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.
 | ||||
|  |  | |||
|  | @ -567,12 +567,12 @@ impl_prim_int!(i64, SInt<64>); | |||
| impl_prim_int!(i128, SInt<128>); | ||||
| 
 | ||||
| impl_prim_int!( | ||||
|     /// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt]
 | ||||
|     /// for portability reasons, [`usize`] always translates to [`UInt<64>`]
 | ||||
|     usize, UInt<64> | ||||
| ); | ||||
| 
 | ||||
| impl_prim_int!( | ||||
|     /// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt]
 | ||||
|     /// for portability reasons, [`isize`] always translates to [`SInt<64>`]
 | ||||
|     isize, SInt<64> | ||||
| ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,8 +22,7 @@ macro_rules! __cfg_expansion_helper { | |||
|             $cfg:ident($($expr:tt)*), | ||||
|             $($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)* | ||||
|         ] | ||||
|         // pass as tt so we get right span for attribute
 | ||||
|         $after_evaluation_attr:tt $after_evaluation_body:tt | ||||
|         #[$after_evaluation:path:($($after_evaluation_attr_args:tt)*)] {$($after_evaluation_args:tt)*} | ||||
|     ) => { | ||||
|         #[$cfg($($expr)*)] | ||||
|         $crate::__cfg_expansion_helper! { | ||||
|  | @ -34,7 +33,7 @@ macro_rules! __cfg_expansion_helper { | |||
|             [ | ||||
|                 $($unevaluated_cfgs($($unevaluated_exprs)*),)* | ||||
|             ] | ||||
|             $after_evaluation_attr $after_evaluation_body | ||||
|             #[$after_evaluation:($($after_evaluation_attr_args)*)] {$($after_evaluation_args)*} | ||||
|         } | ||||
|         #[$cfg(not($($expr)*))] | ||||
|         $crate::__cfg_expansion_helper! { | ||||
|  | @ -45,7 +44,7 @@ macro_rules! __cfg_expansion_helper { | |||
|             [ | ||||
|                 $($unevaluated_cfgs($($unevaluated_exprs)*),)* | ||||
|             ] | ||||
|             $after_evaluation_attr $after_evaluation_body | ||||
|             #[$after_evaluation:($($after_evaluation_attr_args)*)] {$($after_evaluation_args)*} | ||||
|         } | ||||
|     }; | ||||
|     ( | ||||
|  | @ -53,14 +52,12 @@ macro_rules! __cfg_expansion_helper { | |||
|             $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* | ||||
|         ] | ||||
|         [] | ||||
|         // don't use #[...] so we get right span for `#` and `[]` of attribute
 | ||||
|         {$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*} | ||||
|         #[$after_evaluation:path:($($after_evaluation_attr_args:tt)*)] {$($after_evaluation_args:tt)*} | ||||
|     ) => { | ||||
|         $($after_evaluation_attr)* | ||||
|         #[__evaluated_cfgs([ | ||||
|         #[$after_evaluation([ | ||||
|             $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* | ||||
|         ])] | ||||
|         $($after_evaluation_body)* | ||||
|         ] $($after_evaluation_attr_args)*)] | ||||
|         $($after_evaluation_args)* | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4773,9 +4773,6 @@ impl Compiler { | |||
|             } | ||||
|             self.insns.extend(insns.iter().copied(), *source_location); | ||||
|         } | ||||
|         for CondStackEntry { cond: _, end_label } in cond_stack { | ||||
|             self.insns.define_label_at_next_insn(end_label); | ||||
|         } | ||||
|     } | ||||
|     fn process_clocks(&mut self) -> Interned<[StatePartIndex<StatePartKindSmallSlots>]> { | ||||
|         mem::take(&mut self.clock_triggers) | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ use crate::{ | |||
|     enum_::{Enum, EnumType}, | ||||
|     expr::Flow, | ||||
|     int::UInt, | ||||
|     intern::{Intern, Interned}, | ||||
|     sim::{ | ||||
|         time::{SimDuration, SimInstant}, | ||||
|         TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, | ||||
|  | @ -16,73 +15,12 @@ use crate::{ | |||
|     }, | ||||
| }; | ||||
| use bitvec::{order::Lsb0, slice::BitSlice}; | ||||
| use hashbrown::{hash_map::Entry, HashMap}; | ||||
| use std::{ | ||||
|     fmt::{self, Write as _}, | ||||
|     io, mem, | ||||
|     fmt, | ||||
|     io::{self, Write}, | ||||
|     mem, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Default)] | ||||
| struct Scope { | ||||
|     last_inserted: HashMap<Interned<str>, usize>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone)] | ||||
| struct VerilogIdentifier { | ||||
|     unescaped_name: Interned<str>, | ||||
| } | ||||
| 
 | ||||
| impl VerilogIdentifier { | ||||
|     fn needs_escape(self) -> bool { | ||||
|         // we only allow ascii, so we can just check bytes
 | ||||
|         let Some((&first, rest)) = self.unescaped_name.as_bytes().split_first() else { | ||||
|             unreachable!("Scope::new_identifier guarantees a non-empty name"); | ||||
|         }; | ||||
|         if !first.is_ascii_alphabetic() && first != b'_' { | ||||
|             true | ||||
|         } else { | ||||
|             rest.iter() | ||||
|                 .any(|&ch| !ch.is_ascii_alphanumeric() && ch != b'_' && ch != b'$') | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for VerilogIdentifier { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         if self.needs_escape() { | ||||
|             f.write_str("\\")?; | ||||
|         } | ||||
|         write!(f, "{}", Escaped(self.unescaped_name)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Scope { | ||||
|     fn new_identifier(&mut self, unescaped_name: Interned<str>) -> VerilogIdentifier { | ||||
|         let next_disambiguator = match self.last_inserted.entry(unescaped_name) { | ||||
|             Entry::Vacant(entry) => { | ||||
|                 entry.insert(1); | ||||
|                 return VerilogIdentifier { unescaped_name }; | ||||
|             } | ||||
|             Entry::Occupied(entry) => entry.get() + 1, | ||||
|         }; | ||||
|         let mut disambiguated_name = String::from(&*unescaped_name); | ||||
|         for disambiguator in next_disambiguator.. { | ||||
|             disambiguated_name.truncate(unescaped_name.len()); | ||||
|             write!(disambiguated_name, "_{disambiguator}").expect("can't fail"); | ||||
|             if let Entry::Vacant(entry) = self.last_inserted.entry((*disambiguated_name).intern()) { | ||||
|                 let retval = VerilogIdentifier { | ||||
|                     unescaped_name: *entry.key(), | ||||
|                 }; | ||||
|                 entry.insert(1); | ||||
|                 // speed up future searches
 | ||||
|                 self.last_inserted.insert(unescaped_name, disambiguator); | ||||
|                 return retval; | ||||
|             } | ||||
|         } | ||||
|         panic!("too many names"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct VcdWriterDecls<W: io::Write + 'static> { | ||||
|     writer: W, | ||||
|     timescale: SimDuration, | ||||
|  | @ -159,20 +97,14 @@ impl<W: io::Write> fmt::Debug for VcdWriterDecls<W> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// pass in scope to ensure it's not available in child scope
 | ||||
| fn write_vcd_scope<W: io::Write, R>( | ||||
|     writer: &mut W, | ||||
|     scope_type: &str, | ||||
|     scope_name: Interned<str>, | ||||
|     scope: &mut Scope, | ||||
|     f: impl FnOnce(&mut W, &mut Scope) -> io::Result<R>, | ||||
|     scope_name: &str, | ||||
|     f: impl FnOnce(&mut W) -> io::Result<R>, | ||||
| ) -> io::Result<R> { | ||||
|     writeln!( | ||||
|         writer, | ||||
|         "$scope {scope_type} {} $end", | ||||
|         scope.new_identifier(scope_name), | ||||
|     )?; | ||||
|     let retval = f(writer, &mut Scope::default())?; | ||||
|     writeln!(writer, "$scope {scope_type} {scope_name} $end")?; | ||||
|     let retval = f(writer)?; | ||||
|     writeln!(writer, "$upscope $end")?; | ||||
|     Ok(retval) | ||||
| } | ||||
|  | @ -211,28 +143,24 @@ trait_arg! { | |||
| 
 | ||||
| struct ArgModule<'a> { | ||||
|     properties: &'a mut VcdWriterProperties, | ||||
|     scope: &'a mut Scope, | ||||
| } | ||||
| 
 | ||||
| impl<'a> ArgModule<'a> { | ||||
|     fn reborrow(&mut self) -> ArgModule<'_> { | ||||
|         ArgModule { | ||||
|             properties: self.properties, | ||||
|             scope: self.scope, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct ArgModuleBody<'a> { | ||||
|     properties: &'a mut VcdWriterProperties, | ||||
|     scope: &'a mut Scope, | ||||
| } | ||||
| 
 | ||||
| impl<'a> ArgModuleBody<'a> { | ||||
|     fn reborrow(&mut self) -> ArgModuleBody<'_> { | ||||
|         ArgModuleBody { | ||||
|             properties: self.properties, | ||||
|             scope: self.scope, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -242,7 +170,6 @@ struct ArgInType<'a> { | |||
|     sink_var_type: &'static str, | ||||
|     duplex_var_type: &'static str, | ||||
|     properties: &'a mut VcdWriterProperties, | ||||
|     scope: &'a mut Scope, | ||||
| } | ||||
| 
 | ||||
| impl<'a> ArgInType<'a> { | ||||
|  | @ -252,7 +179,6 @@ impl<'a> ArgInType<'a> { | |||
|             sink_var_type: self.sink_var_type, | ||||
|             duplex_var_type: self.duplex_var_type, | ||||
|             properties: self.properties, | ||||
|             scope: self.scope, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -300,42 +226,55 @@ fn write_vcd_id<W: io::Write>(writer: &mut W, mut id: usize) -> io::Result<()> { | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| struct Escaped<T: fmt::Display>(T); | ||||
| 
 | ||||
| impl<T: fmt::Display> fmt::Display for Escaped<T> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         // escaping rules from function GTKWave uses to decode VCD strings:
 | ||||
|         // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
 | ||||
|         struct Wrapper<W>(W); | ||||
|         impl<W: fmt::Write> fmt::Write for Wrapper<W> { | ||||
|             fn write_str(&mut self, s: &str) -> fmt::Result { | ||||
|                 for byte in s.bytes() { | ||||
|                     match byte { | ||||
|                         b'\\' | b'\'' | b'"' | b'?' => { | ||||
|                             self.0.write_str("\\")?; | ||||
|                             self.0.write_char(byte as char)?; | ||||
|                         } | ||||
|                         b'\n' => self.0.write_str(r"\n")?, | ||||
|                         b'\r' => self.0.write_str(r"\r")?, | ||||
|                         b'\t' => self.0.write_str(r"\t")?, | ||||
|                         0x7 => self.0.write_str(r"\a")?, | ||||
|                         0x8 => self.0.write_str(r"\b")?, | ||||
|                         0xC => self.0.write_str(r"\f")?, | ||||
|                         0xB => self.0.write_str(r"\v")?, | ||||
|                         _ => { | ||||
|                             if byte.is_ascii_graphic() { | ||||
|                                 self.0.write_char(byte as char)?; | ||||
|                             } else { | ||||
|                                 write!(self.0, r"\x{byte:02x}")?; | ||||
|                             } | ||||
| fn write_escaped<W: io::Write>(writer: &mut W, value: impl fmt::Display) -> io::Result<()> { | ||||
|     // escaping rules from function GTKWave uses to decode VCD strings:
 | ||||
|     // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
 | ||||
|     struct Wrapper<W>(W); | ||||
|     impl<W: io::Write> io::Write for Wrapper<W> { | ||||
|         fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|             if buf.is_empty() { | ||||
|                 return self.0.write(buf); | ||||
|             } | ||||
|             let mut retval = 0; | ||||
|             for &byte in buf { | ||||
|                 match byte { | ||||
|                     b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?, | ||||
|                     b'\n' => self.0.write_all(br"\n")?, | ||||
|                     b'\r' => self.0.write_all(br"\r")?, | ||||
|                     b'\t' => self.0.write_all(br"\t")?, | ||||
|                     0x7 => self.0.write_all(br"\a")?, | ||||
|                     0x8 => self.0.write_all(br"\b")?, | ||||
|                     0xC => self.0.write_all(br"\f")?, | ||||
|                     0xB => self.0.write_all(br"\v")?, | ||||
|                     _ => { | ||||
|                         if byte.is_ascii_graphic() { | ||||
|                             self.0.write_all(&[byte])?; | ||||
|                         } else { | ||||
|                             write!(self.0, r"\x{byte:02x}")?; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(()) | ||||
|                 retval += 1; | ||||
|             } | ||||
|             Ok(retval) | ||||
|         } | ||||
| 
 | ||||
|         fn flush(&mut self) -> io::Result<()> { | ||||
|             self.0.flush() | ||||
|         } | ||||
|         write!(Wrapper(f), "{}", self.0) | ||||
|     } | ||||
|     write!(Wrapper(writer), "{value}") | ||||
| } | ||||
| 
 | ||||
| fn is_unescaped_verilog_identifier(ident: &str) -> bool { | ||||
|     // we only allow ascii, so we can just check bytes
 | ||||
|     let Some((&first, rest)) = ident.as_bytes().split_first() else { | ||||
|         return false; // empty string is not an identifier
 | ||||
|     }; | ||||
|     (first.is_ascii_alphabetic() || first == b'_') | ||||
|         && rest | ||||
|             .iter() | ||||
|             .all(|&ch| ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'$') | ||||
| } | ||||
| 
 | ||||
| fn write_vcd_var<W: io::Write>( | ||||
|  | @ -345,7 +284,7 @@ fn write_vcd_var<W: io::Write>( | |||
|     var_type: &str, | ||||
|     size: usize, | ||||
|     location: TraceLocation, | ||||
|     name: VerilogIdentifier, | ||||
|     name: &str, | ||||
| ) -> io::Result<()> { | ||||
|     let id = match location { | ||||
|         TraceLocation::Scalar(id) => id.as_usize(), | ||||
|  | @ -380,7 +319,12 @@ fn write_vcd_var<W: io::Write>( | |||
|     }; | ||||
|     write!(writer, "$var {var_type} {size} ")?; | ||||
|     write_vcd_id(writer, id)?; | ||||
|     writeln!(writer, " {name} $end") | ||||
|     writer.write_all(b" ")?; | ||||
|     if !is_unescaped_verilog_identifier(name) { | ||||
|         writer.write_all(b"\\")?; | ||||
|     } | ||||
|     write_escaped(writer, name)?; | ||||
|     writer.write_all(b" $end\n") | ||||
| } | ||||
| 
 | ||||
| impl WriteTrace for TraceUInt { | ||||
|  | @ -390,7 +334,6 @@ impl WriteTrace for TraceUInt { | |||
|             sink_var_type, | ||||
|             duplex_var_type, | ||||
|             properties, | ||||
|             scope, | ||||
|         } = arg.in_type(); | ||||
|         let Self { | ||||
|             location, | ||||
|  | @ -413,7 +356,7 @@ impl WriteTrace for TraceUInt { | |||
|             var_type, | ||||
|             ty.width(), | ||||
|             location, | ||||
|             scope.new_identifier(name), | ||||
|             &name, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -478,7 +421,6 @@ impl WriteTrace for TraceEnumDiscriminant { | |||
|             sink_var_type: _, | ||||
|             duplex_var_type: _, | ||||
|             properties, | ||||
|             scope, | ||||
|         } = arg.in_type(); | ||||
|         let Self { | ||||
|             location, | ||||
|  | @ -493,7 +435,7 @@ impl WriteTrace for TraceEnumDiscriminant { | |||
|             "string", | ||||
|             1, | ||||
|             location, | ||||
|             scope.new_identifier(name), | ||||
|             &name, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -565,11 +507,11 @@ impl WriteTrace for TraceScope { | |||
| 
 | ||||
| impl WriteTrace for TraceModule { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModule { properties, scope } = arg.module(); | ||||
|         let ArgModule { properties } = arg.module(); | ||||
|         let Self { name, children } = self; | ||||
|         write_vcd_scope(writer, "module", name, scope, |writer, scope| { | ||||
|         write_vcd_scope(writer, "module", &name, |writer| { | ||||
|             for child in children { | ||||
|                 child.write_trace(writer, ArgModuleBody { properties, scope })?; | ||||
|                 child.write_trace(writer, ArgModuleBody { properties })?; | ||||
|             } | ||||
|             Ok(()) | ||||
|         }) | ||||
|  | @ -578,7 +520,7 @@ impl WriteTrace for TraceModule { | |||
| 
 | ||||
| impl WriteTrace for TraceInstance { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             name: _, | ||||
|             instance_io, | ||||
|  | @ -592,16 +534,15 @@ impl WriteTrace for TraceInstance { | |||
|                 sink_var_type: "wire", | ||||
|                 duplex_var_type: "wire", | ||||
|                 properties, | ||||
|                 scope, | ||||
|             }, | ||||
|         )?; | ||||
|         module.write_trace(writer, ArgModule { properties, scope }) | ||||
|         module.write_trace(writer, ArgModule { properties }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl WriteTrace for TraceMem { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             id, | ||||
|             name, | ||||
|  | @ -610,41 +551,27 @@ impl WriteTrace for TraceMem { | |||
|             ports, | ||||
|             array_type, | ||||
|         } = self; | ||||
|         write_vcd_scope(writer, "struct", name, scope, |writer, scope| { | ||||
|             write_vcd_scope( | ||||
|                 writer, | ||||
|                 "struct", | ||||
|                 "contents".intern(), | ||||
|                 scope, | ||||
|                 |writer, scope| { | ||||
|                     for element_index in 0..array_type.len() { | ||||
|                         write_vcd_scope( | ||||
|         write_vcd_scope(writer, "struct", &*name, |writer| { | ||||
|             write_vcd_scope(writer, "struct", "contents", |writer| { | ||||
|                 for element_index in 0..array_type.len() { | ||||
|                     write_vcd_scope(writer, "struct", &format!("[{element_index}]"), |writer| { | ||||
|                         properties.memory_properties[id.as_usize()].element_index = element_index; | ||||
|                         properties.memory_properties[id.as_usize()].element_part_index = 0; | ||||
|                         element_type.write_trace( | ||||
|                             writer, | ||||
|                             "struct", | ||||
|                             Intern::intern_owned(format!("[{element_index}]")), | ||||
|                             scope, | ||||
|                             |writer, scope| { | ||||
|                                 properties.memory_properties[id.as_usize()].element_index = | ||||
|                                     element_index; | ||||
|                                 properties.memory_properties[id.as_usize()].element_part_index = 0; | ||||
|                                 element_type.write_trace( | ||||
|                                     writer, | ||||
|                                     ArgInType { | ||||
|                                         source_var_type: "reg", | ||||
|                                         sink_var_type: "reg", | ||||
|                                         duplex_var_type: "reg", | ||||
|                                         properties, | ||||
|                                         scope, | ||||
|                                     }, | ||||
|                                 ) | ||||
|                             ArgInType { | ||||
|                                 source_var_type: "reg", | ||||
|                                 sink_var_type: "reg", | ||||
|                                 duplex_var_type: "reg", | ||||
|                                 properties, | ||||
|                             }, | ||||
|                         )?; | ||||
|                     } | ||||
|                     Ok(()) | ||||
|                 }, | ||||
|             )?; | ||||
|                         ) | ||||
|                     })?; | ||||
|                 } | ||||
|                 Ok(()) | ||||
|             })?; | ||||
|             for port in ports { | ||||
|                 port.write_trace(writer, ArgModuleBody { properties, scope })?; | ||||
|                 port.write_trace(writer, ArgModuleBody { properties })?; | ||||
|             } | ||||
|             Ok(()) | ||||
|         }) | ||||
|  | @ -653,7 +580,7 @@ impl WriteTrace for TraceMem { | |||
| 
 | ||||
| impl WriteTrace for TraceMemPort { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             name: _, | ||||
|             bundle, | ||||
|  | @ -666,7 +593,6 @@ impl WriteTrace for TraceMemPort { | |||
|                 sink_var_type: "wire", | ||||
|                 duplex_var_type: "wire", | ||||
|                 properties, | ||||
|                 scope, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | @ -674,7 +600,7 @@ impl WriteTrace for TraceMemPort { | |||
| 
 | ||||
| impl WriteTrace for TraceWire { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             name: _, | ||||
|             child, | ||||
|  | @ -687,7 +613,6 @@ impl WriteTrace for TraceWire { | |||
|                 sink_var_type: "wire", | ||||
|                 duplex_var_type: "wire", | ||||
|                 properties, | ||||
|                 scope, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | @ -695,7 +620,7 @@ impl WriteTrace for TraceWire { | |||
| 
 | ||||
| impl WriteTrace for TraceReg { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             name: _, | ||||
|             child, | ||||
|  | @ -708,7 +633,6 @@ impl WriteTrace for TraceReg { | |||
|                 sink_var_type: "reg", | ||||
|                 duplex_var_type: "reg", | ||||
|                 properties, | ||||
|                 scope, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | @ -716,7 +640,7 @@ impl WriteTrace for TraceReg { | |||
| 
 | ||||
| impl WriteTrace for TraceModuleIO { | ||||
|     fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { | ||||
|         let ArgModuleBody { properties, scope } = arg.module_body(); | ||||
|         let ArgModuleBody { properties } = arg.module_body(); | ||||
|         let Self { | ||||
|             name: _, | ||||
|             child, | ||||
|  | @ -730,7 +654,6 @@ impl WriteTrace for TraceModuleIO { | |||
|                 sink_var_type: "wire", | ||||
|                 duplex_var_type: "wire", | ||||
|                 properties, | ||||
|                 scope, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | @ -738,31 +661,16 @@ impl WriteTrace for TraceModuleIO { | |||
| 
 | ||||
| impl WriteTrace for TraceBundle { | ||||
|     fn write_trace<W: io::Write, A: Arg>(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 mut arg = arg.in_type(); | ||||
|         let Self { | ||||
|             name, | ||||
|             fields, | ||||
|             ty: _, | ||||
|             flow: _, | ||||
|         } = self; | ||||
|         write_vcd_scope(writer, "struct", name, scope, |writer, scope| { | ||||
|         write_vcd_scope(writer, "struct", &name, |writer| { | ||||
|             for field in fields { | ||||
|                 field.write_trace( | ||||
|                     writer, | ||||
|                     ArgInType { | ||||
|                         source_var_type, | ||||
|                         sink_var_type, | ||||
|                         duplex_var_type, | ||||
|                         properties, | ||||
|                         scope, | ||||
|                     }, | ||||
|                 )?; | ||||
|                 field.write_trace(writer, arg.reborrow())?; | ||||
|             } | ||||
|             Ok(()) | ||||
|         }) | ||||
|  | @ -771,31 +679,16 @@ impl WriteTrace for TraceBundle { | |||
| 
 | ||||
| impl WriteTrace for TraceArray { | ||||
|     fn write_trace<W: io::Write, A: Arg>(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 mut arg = arg.in_type(); | ||||
|         let Self { | ||||
|             name, | ||||
|             elements, | ||||
|             ty: _, | ||||
|             flow: _, | ||||
|         } = self; | ||||
|         write_vcd_scope(writer, "struct", name, scope, |writer, scope| { | ||||
|         write_vcd_scope(writer, "struct", &name, |writer| { | ||||
|             for element in elements { | ||||
|                 element.write_trace( | ||||
|                     writer, | ||||
|                     ArgInType { | ||||
|                         source_var_type, | ||||
|                         sink_var_type, | ||||
|                         duplex_var_type, | ||||
|                         properties, | ||||
|                         scope, | ||||
|                     }, | ||||
|                 )?; | ||||
|                 element.write_trace(writer, arg.reborrow())?; | ||||
|             } | ||||
|             Ok(()) | ||||
|         }) | ||||
|  | @ -804,13 +697,7 @@ impl WriteTrace for TraceArray { | |||
| 
 | ||||
| impl WriteTrace for TraceEnumWithFields { | ||||
|     fn write_trace<W: io::Write, A: Arg>(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 mut arg = arg.in_type(); | ||||
|         let Self { | ||||
|             name, | ||||
|             discriminant, | ||||
|  | @ -818,28 +705,10 @@ impl WriteTrace for TraceEnumWithFields { | |||
|             ty: _, | ||||
|             flow: _, | ||||
|         } = self; | ||||
|         write_vcd_scope(writer, "struct", name, scope, |writer, scope| { | ||||
|             discriminant.write_trace( | ||||
|                 writer, | ||||
|                 ArgInType { | ||||
|                     source_var_type, | ||||
|                     sink_var_type, | ||||
|                     duplex_var_type, | ||||
|                     properties, | ||||
|                     scope, | ||||
|                 }, | ||||
|             )?; | ||||
|         write_vcd_scope(writer, "struct", &name, |writer| { | ||||
|             discriminant.write_trace(writer, arg.reborrow())?; | ||||
|             for field in non_empty_fields { | ||||
|                 field.write_trace( | ||||
|                     writer, | ||||
|                     ArgInType { | ||||
|                         source_var_type, | ||||
|                         sink_var_type, | ||||
|                         duplex_var_type, | ||||
|                         properties, | ||||
|                         scope, | ||||
|                     }, | ||||
|                 )?; | ||||
|                 field.write_trace(writer, arg.reborrow())?; | ||||
|             } | ||||
|             Ok(()) | ||||
|         }) | ||||
|  | @ -875,7 +744,6 @@ impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> { | |||
|             &mut writer, | ||||
|             ArgModule { | ||||
|                 properties: &mut properties, | ||||
|                 scope: &mut Scope::default(), | ||||
|             }, | ||||
|         )?; | ||||
|         writeln!(writer, "$enddefinitions $end")?; | ||||
|  | @ -930,7 +798,9 @@ fn write_string_value_change( | |||
|     value: impl fmt::Display, | ||||
|     id: usize, | ||||
| ) -> io::Result<()> { | ||||
|     write!(writer, "s{} ", Escaped(value))?; | ||||
|     writer.write_all(b"s")?; | ||||
|     write_escaped(writer, value)?; | ||||
|     writer.write_all(b" ")?; | ||||
|     write_vcd_id(writer, id)?; | ||||
|     writer.write_all(b"\n") | ||||
| } | ||||
|  | @ -1076,49 +946,3 @@ impl<W: io::Write> fmt::Debug for VcdWriter<W> { | |||
|             .finish_non_exhaustive() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_scope() { | ||||
|         let mut scope = Scope::default(); | ||||
|         assert_eq!(&*scope.new_identifier("foo".intern()).unescaped_name, "foo"); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo_0".intern()).unescaped_name, | ||||
|             "foo_0" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo_1".intern()).unescaped_name, | ||||
|             "foo_1" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo_3".intern()).unescaped_name, | ||||
|             "foo_3" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo".intern()).unescaped_name, | ||||
|             "foo_2" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo".intern()).unescaped_name, | ||||
|             "foo_4" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo_0".intern()).unescaped_name, | ||||
|             "foo_0_2" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             &*scope.new_identifier("foo_1".intern()).unescaped_name, | ||||
|             "foo_1_2" | ||||
|         ); | ||||
|         for i in 5..1000u64 { | ||||
|             // verify it actually picks the next available identifier with no skips or duplicates
 | ||||
|             assert_eq!( | ||||
|                 *scope.new_identifier("foo".intern()).unescaped_name, | ||||
|                 format!("foo_{i}"), | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -49,18 +49,6 @@ impl<T: Type> ReadyValid<T> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// This debug port is only meant to assist the formal proof of the queue.
 | ||||
| #[cfg(test)] | ||||
| #[doc(hidden)] | ||||
| #[hdl] | ||||
| pub struct QueueDebugPort<Element, Index> { | ||||
|     #[hdl(flip)] | ||||
|     index_to_check: Index, | ||||
|     stored: Element, | ||||
|     inp_index: Index, | ||||
|     out_index: Index, | ||||
| } | ||||
| 
 | ||||
| #[hdl_module] | ||||
| pub fn queue<T: Type>( | ||||
|     ty: T, | ||||
|  | @ -190,22 +178,6 @@ pub fn queue<T: Type>( | |||
|             } | ||||
|         } | ||||
|     } | ||||
|     // These debug ports expose some internal state during the Induction phase
 | ||||
|     // of Formal Verification. They are not present in normal use.
 | ||||
|     #[cfg(test)] | ||||
|     { | ||||
|         #[hdl] | ||||
|         let dbg: QueueDebugPort<T, UInt> = m.output(QueueDebugPort[ty][index_ty]); | ||||
|         // read the memory word currently stored at some fixed index
 | ||||
|         let debug_port = mem.new_read_port(); | ||||
|         connect(debug_port.addr, dbg.index_to_check); | ||||
|         connect(debug_port.en, true); | ||||
|         connect(debug_port.clk, cd.clk); | ||||
|         connect(dbg.stored, debug_port.data); | ||||
|         // also expose the current read and write indices
 | ||||
|         connect(dbg.inp_index, inp_index_reg); | ||||
|         connect(dbg.out_index, out_index_reg); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | @ -224,23 +196,13 @@ mod tests { | |||
|             format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"), | ||||
|             queue_test(capacity, inp_ready_is_comb, out_valid_is_comb), | ||||
|             FormalMode::Prove, | ||||
|             2, | ||||
|             14, | ||||
|             None, | ||||
|             ExportOptions { | ||||
|                 simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), | ||||
|                 ..ExportOptions::default() | ||||
|             }, | ||||
|         ); | ||||
|         /// Formal verification of the FIFO queue
 | ||||
|         ///
 | ||||
|         /// The strategy derives from the observation that, if we filter its
 | ||||
|         /// input and output streams to consider just one in every N reads and
 | ||||
|         /// writes (where N is the FIFO capacity), then the FIFO effectively
 | ||||
|         /// behaves as a one-entry FIFO.
 | ||||
|         ///
 | ||||
|         /// In particular, any counterexample of the full FIFO behaving badly
 | ||||
|         /// will also be caught by one of the filtered versions (one which
 | ||||
|         /// happens to be in phase with the offending input or output).
 | ||||
|         #[hdl_module] | ||||
|         fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) { | ||||
|             #[hdl] | ||||
|  | @ -255,8 +217,6 @@ mod tests { | |||
|                     rst: formal_reset().to_reset(), | ||||
|                 }, | ||||
|             ); | ||||
| 
 | ||||
|             // random input data
 | ||||
|             #[hdl] | ||||
|             let inp_data: HdlOption<UInt<8>> = wire(); | ||||
|             #[hdl] | ||||
|  | @ -265,26 +225,16 @@ mod tests { | |||
|             } else { | ||||
|                 connect(inp_data, HdlNone()); | ||||
|             } | ||||
| 
 | ||||
|             // assert output ready at random
 | ||||
|             #[hdl] | ||||
|             let out_ready: Bool = wire(); | ||||
|             connect(out_ready, any_seq(Bool)); | ||||
| 
 | ||||
|             // The current number of elements in the FIFO ranges from zero to
 | ||||
|             // maximum capacity, inclusive.
 | ||||
|             let count_ty = UInt::range_inclusive(0..=capacity.get()); | ||||
|             // type for counters that wrap around at the FIFO capacity
 | ||||
|             let index_ty = UInt::range(0..capacity.get()); | ||||
| 
 | ||||
|             // among all entries of the FIFO internal circular memory, choose
 | ||||
|             // one at random to check
 | ||||
|             let index_ty: UInt<32> = UInt::TYPE; | ||||
|             #[hdl] | ||||
|             let index_to_check = wire(index_ty); | ||||
|             let index_to_check = wire(); | ||||
|             connect(index_to_check, any_const(index_ty)); | ||||
|             hdl_assume(clk, index_to_check.cmp_lt(capacity.get()), ""); | ||||
| 
 | ||||
|             // instantiate and connect the queue
 | ||||
|             let index_max = !index_ty.zero(); | ||||
|             // we saturate at index_max, so only check indexes where we properly maintain position
 | ||||
|             hdl_assume(clk, index_to_check.cmp_ne(index_max), ""); | ||||
|             #[hdl] | ||||
|             let dut = instance(queue( | ||||
|                 UInt[ConstUsize::<8>], | ||||
|  | @ -295,172 +245,109 @@ mod tests { | |||
|             connect(dut.cd, cd); | ||||
|             connect(dut.inp.data, inp_data); | ||||
|             connect(dut.out.ready, out_ready); | ||||
|             hdl_assume( | ||||
|                 clk, | ||||
|                 index_to_check.cmp_ne(!Expr::ty(index_to_check).zero()), | ||||
|                 "", | ||||
|             ); | ||||
| 
 | ||||
|             // Keep an independent count of words in the FIFO. Ensure that
 | ||||
|             // it's always correct, and never overflows.
 | ||||
|             #[hdl] | ||||
|             let expected_count_reg = reg_builder().clock_domain(cd).reset(count_ty.zero()); | ||||
|             let expected_count_reg = reg_builder().clock_domain(cd).reset(0u32); | ||||
|             #[hdl] | ||||
|             let next_expected_count = wire(); | ||||
|             connect(next_expected_count, expected_count_reg); | ||||
|             connect(expected_count_reg, next_expected_count); | ||||
|             #[hdl] | ||||
|             if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) { | ||||
|                 hdl_assert(clk, expected_count_reg.cmp_ne(capacity.get()), ""); | ||||
|                 connect_any(expected_count_reg, expected_count_reg + 1u8); | ||||
|                 connect_any(next_expected_count, expected_count_reg + 1u8); | ||||
|             } else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) { | ||||
|                 hdl_assert(clk, expected_count_reg.cmp_ne(count_ty.zero()), ""); | ||||
|                 connect_any(expected_count_reg, expected_count_reg - 1u8); | ||||
|                 connect_any(next_expected_count, expected_count_reg - 1u8); | ||||
|             } | ||||
|             hdl_assert(clk, expected_count_reg.cmp_eq(dut.count), ""); | ||||
|             hdl_assert(cd.clk, expected_count_reg.cmp_eq(dut.count), ""); | ||||
| 
 | ||||
|             #[hdl] | ||||
|             let prev_out_ready_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3); | ||||
|             connect_any( | ||||
|                 prev_out_ready_reg, | ||||
|                 (prev_out_ready_reg << 1) | out_ready.cast_to(UInt[1]), | ||||
|             ); | ||||
|             #[hdl] | ||||
|             let prev_inp_valid_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3); | ||||
|             connect_any( | ||||
|                 prev_inp_valid_reg, | ||||
|                 (prev_inp_valid_reg << 1) | HdlOption::is_some(inp_data).cast_to(UInt[1]), | ||||
|             ); | ||||
|             hdl_assume( | ||||
|                 clk, | ||||
|                 (prev_out_ready_reg & prev_inp_valid_reg).cmp_ne(0u8), | ||||
|                 "", | ||||
|             ); | ||||
| 
 | ||||
|             // keep an independent write index into the FIFO's circular buffer
 | ||||
|             #[hdl] | ||||
|             let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero()); | ||||
|             #[hdl] | ||||
|             if ReadyValid::firing(dut.inp) { | ||||
|             let stored_inp_data_reg = reg_builder().clock_domain(cd).reset(0u8); | ||||
| 
 | ||||
|             #[hdl] | ||||
|             if let HdlSome(data) = ReadyValid::firing_data(dut.inp) { | ||||
|                 #[hdl] | ||||
|                 if inp_index_reg.cmp_ne(capacity.get() - 1) { | ||||
|                 if inp_index_reg.cmp_lt(index_max) { | ||||
|                     connect_any(inp_index_reg, inp_index_reg + 1u8); | ||||
|                 } else { | ||||
|                     connect_any(inp_index_reg, 0_hdl_u0); | ||||
|                     #[hdl] | ||||
|                     if inp_index_reg.cmp_eq(index_to_check) { | ||||
|                         connect(stored_inp_data_reg, data); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // keep an independent read index into the FIFO's circular buffer
 | ||||
|             #[hdl] | ||||
|             if inp_index_reg.cmp_lt(index_to_check) { | ||||
|                 hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), ""); | ||||
|             } | ||||
| 
 | ||||
|             #[hdl] | ||||
|             let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero()); | ||||
|             #[hdl] | ||||
|             if ReadyValid::firing(dut.out) { | ||||
|             let stored_out_data_reg = reg_builder().clock_domain(cd).reset(0u8); | ||||
| 
 | ||||
|             #[hdl] | ||||
|             if let HdlSome(data) = ReadyValid::firing_data(dut.out) { | ||||
|                 #[hdl] | ||||
|                 if out_index_reg.cmp_ne(capacity.get() - 1) { | ||||
|                 if out_index_reg.cmp_lt(index_max) { | ||||
|                     connect_any(out_index_reg, out_index_reg + 1u8); | ||||
|                 } else { | ||||
|                     connect_any(out_index_reg, 0_hdl_u0); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // filter the input data stream, predicated by the read index
 | ||||
|             // matching the chosen position in the FIFO's circular buffer
 | ||||
|             #[hdl] | ||||
|             let inp_index_matches = wire(); | ||||
|             connect(inp_index_matches, inp_index_reg.cmp_eq(index_to_check)); | ||||
|             #[hdl] | ||||
|             let inp_firing_data = wire(); | ||||
|             connect(inp_firing_data, HdlNone()); | ||||
|             #[hdl] | ||||
|             if inp_index_matches { | ||||
|                 connect(inp_firing_data, ReadyValid::firing_data(dut.inp)); | ||||
|             } | ||||
| 
 | ||||
|             // filter the output data stream, predicated by the write index
 | ||||
|             // matching the chosen position in the FIFO's circular buffer
 | ||||
|             #[hdl] | ||||
|             let out_index_matches = wire(); | ||||
|             connect(out_index_matches, out_index_reg.cmp_eq(index_to_check)); | ||||
|             #[hdl] | ||||
|             let out_firing_data = wire(); | ||||
|             connect(out_firing_data, HdlNone()); | ||||
|             #[hdl] | ||||
|             if out_index_matches { | ||||
|                 connect(out_firing_data, ReadyValid::firing_data(dut.out)); | ||||
|             } | ||||
| 
 | ||||
|             // Implement a one-entry FIFO and ensure its equivalence to the
 | ||||
|             // filtered FIFO.
 | ||||
|             //
 | ||||
|             // the holding register for our one-entry FIFO
 | ||||
|             #[hdl] | ||||
|             let stored_reg = reg_builder().clock_domain(cd).reset(HdlNone()); | ||||
|             #[hdl] | ||||
|             match stored_reg { | ||||
|                 // If the holding register is empty...
 | ||||
|                 HdlNone => { | ||||
|                     #[hdl] | ||||
|                     match inp_firing_data { | ||||
|                         // ... and we are not receiving data, then we must not
 | ||||
|                         // transmit any data.
 | ||||
|                         HdlNone => hdl_assert(clk, HdlOption::is_none(out_firing_data), ""), | ||||
|                         // If we are indeed receiving some data...
 | ||||
|                         HdlSome(data_in) => { | ||||
|                             #[hdl] | ||||
|                             match out_firing_data { | ||||
|                                 // ... and transmitting at the same time, we
 | ||||
|                                 // must be transmitting the input data itself,
 | ||||
|                                 // since the holding register is empty.
 | ||||
|                                 HdlSome(data_out) => hdl_assert(clk, data_out.cmp_eq(data_in), ""), | ||||
|                                 // If we are receiving, but not transmitting,
 | ||||
|                                 // store the received data in the holding
 | ||||
|                                 // register.
 | ||||
|                                 HdlNone => connect(stored_reg, HdlSome(data_in)), | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 // If there is some value stored in the holding register...
 | ||||
|                 HdlSome(stored) => { | ||||
|                     #[hdl] | ||||
|                     match out_firing_data { | ||||
|                         // ... and we are not transmitting it, we cannot
 | ||||
|                         // receive any more data.
 | ||||
|                         HdlNone => hdl_assert(clk, HdlOption::is_none(inp_firing_data), ""), | ||||
|                         // If we are transmitting a previously stored value...
 | ||||
|                         HdlSome(data_out) => { | ||||
|                             // ... it must be the same data we stored earlier.
 | ||||
|                             hdl_assert(clk, data_out.cmp_eq(stored), ""); | ||||
|                             // Also, accept new data, if any. Otherwise,
 | ||||
|                             // let the holding register become empty.
 | ||||
|                             connect(stored_reg, inp_firing_data); | ||||
|                         } | ||||
|                     if out_index_reg.cmp_eq(index_to_check) { | ||||
|                         connect(stored_out_data_reg, data); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // from now on, some extra assertions in order to pass induction
 | ||||
| 
 | ||||
|             // sync the holding register, when it's occupied, to the
 | ||||
|             // corresponding entry in the FIFO's circular buffer
 | ||||
|             connect(dut.dbg.index_to_check, index_to_check); | ||||
|             #[hdl] | ||||
|             if let HdlSome(stored) = stored_reg { | ||||
|                 hdl_assert(clk, stored.cmp_eq(dut.dbg.stored), ""); | ||||
|             if out_index_reg.cmp_lt(index_to_check) { | ||||
|                 hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), ""); | ||||
|             } | ||||
| 
 | ||||
|             // sync the read and write indices
 | ||||
|             hdl_assert(clk, inp_index_reg.cmp_eq(dut.dbg.inp_index), ""); | ||||
|             hdl_assert(clk, out_index_reg.cmp_eq(dut.dbg.out_index), ""); | ||||
|             hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), ""); | ||||
| 
 | ||||
|             // the indices should never go past the capacity, but induction
 | ||||
|             // doesn't know that...
 | ||||
|             hdl_assert(clk, inp_index_reg.cmp_lt(capacity.get()), ""); | ||||
|             hdl_assert(clk, out_index_reg.cmp_lt(capacity.get()), ""); | ||||
| 
 | ||||
|             // strongly constrain the state of the holding register
 | ||||
|             //
 | ||||
|             // The holding register is full if and only if the corresponding
 | ||||
|             // FIFO entry was written to and not yet read. In other words, if
 | ||||
|             // the number of pending reads until the chosen entry is read out
 | ||||
|             // is greater than the current FIFO count, then the entry couldn't
 | ||||
|             // be in the FIFO in the first place.
 | ||||
|             #[hdl] | ||||
|             let pending_reads: UInt = wire(index_ty); | ||||
|             // take care of wrap-around when subtracting indices, add the
 | ||||
|             // capacity amount to keep the result positive if necessary
 | ||||
|             #[hdl] | ||||
|             if index_to_check.cmp_ge(out_index_reg) { | ||||
|                 connect(pending_reads, index_to_check - out_index_reg); | ||||
|             if inp_index_reg.cmp_lt(index_max) & out_index_reg.cmp_lt(index_max) { | ||||
|                 hdl_assert( | ||||
|                     clk, | ||||
|                     expected_count_reg.cmp_eq(inp_index_reg - out_index_reg), | ||||
|                     "", | ||||
|                 ); | ||||
|             } else { | ||||
|                 connect( | ||||
|                     pending_reads, | ||||
|                     index_to_check + capacity.get() - out_index_reg, | ||||
|                 hdl_assert( | ||||
|                     clk, | ||||
|                     expected_count_reg.cmp_ge(inp_index_reg - out_index_reg), | ||||
|                     "", | ||||
|                 ); | ||||
|             } | ||||
|             // check whether the chosen entry is in the FIFO
 | ||||
| 
 | ||||
|             #[hdl] | ||||
|             let expected_stored: Bool = wire(); | ||||
|             connect(expected_stored, pending_reads.cmp_lt(dut.count)); | ||||
|             // sync with the state of the holding register
 | ||||
|             hdl_assert( | ||||
|                 clk, | ||||
|                 expected_stored.cmp_eq(HdlOption::is_some(stored_reg)), | ||||
|                 "", | ||||
|             ); | ||||
|             if inp_index_reg.cmp_gt(index_to_check) & out_index_reg.cmp_gt(index_to_check) { | ||||
|                 hdl_assert(clk, stored_inp_data_reg.cmp_eq(stored_out_data_reg), ""); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -543,24 +430,4 @@ mod tests { | |||
|     fn test_4_true_true() { | ||||
|         test_queue(NonZero::new(4).unwrap(), true, true); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_many_false_false() { | ||||
|         test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, false); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_many_false_true() { | ||||
|         test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, true); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_many_true_false() { | ||||
|         test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, false); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_many_true_true() { | ||||
|         test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, true); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4287,137 +4287,3 @@ circuit check_deduce_resets: | |||
| ",
 | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| // intentionally not outline_generated to ensure we get correct macro hygiene
 | ||||
| #[hdl_module] | ||||
| pub fn check_cfgs<#[cfg(cfg_false_for_tests)] A: Type, #[cfg(cfg_true_for_tests)] B: Type>( | ||||
|     #[cfg(cfg_false_for_tests)] a: A, | ||||
|     #[cfg(cfg_true_for_tests)] b: B, | ||||
| ) { | ||||
|     #[hdl] | ||||
|     struct S<#[cfg(cfg_false_for_tests)] A, #[cfg(cfg_true_for_tests)] B> { | ||||
|         #[cfg(cfg_false_for_tests)] | ||||
|         a: A, | ||||
|         #[cfg(cfg_true_for_tests)] | ||||
|         b: B, | ||||
|     } | ||||
|     #[hdl] | ||||
|     #[cfg(cfg_false_for_tests)] | ||||
|     let i_a: A = m.input(a); | ||||
|     #[hdl] | ||||
|     #[cfg(cfg_true_for_tests)] | ||||
|     let i_b: B = m.input(b); | ||||
|     #[hdl] | ||||
|     let w: S<UInt<8>> = wire(); | ||||
|     #[cfg(cfg_false_for_tests)] | ||||
|     { | ||||
|         #[hdl] | ||||
|         let o_a: A = m.output(a); | ||||
|         connect(o_a, w.a.cast_bits_to(a)); | ||||
|         connect_any(w.a, i_a.cast_to_bits()); | ||||
|     } | ||||
|     #[cfg(cfg_true_for_tests)] | ||||
|     { | ||||
|         #[hdl] | ||||
|         let o_b: B = m.output(b); | ||||
|         connect(o_b, w.b.cast_bits_to(b)); | ||||
|         connect_any(w.b, i_b.cast_to_bits()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_cfgs() { | ||||
|     let _n = SourceLocation::normalize_files_for_tests(); | ||||
|     let m = check_cfgs(UInt[8]); | ||||
|     dbg!(m); | ||||
|     #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
 | ||||
|     assert_export_firrtl! { | ||||
|         m => | ||||
|         "/test/check_cfgs.fir": r"FIRRTL version 3.2.0
 | ||||
| circuit check_cfgs: | ||||
|     type Ty0 = {b: UInt<8>} | ||||
|     module check_cfgs: @[the_test_file.rs 9962:1] | ||||
|         input i_b: UInt<8> @[the_test_file.rs 9979:20] | ||||
|         output o_b: UInt<8> @[the_test_file.rs 9992:24] | ||||
|         wire w: Ty0 @[the_test_file.rs 9981:25] | ||||
|         connect o_b, w.b @[the_test_file.rs 9993:9] | ||||
|         connect w.b, i_b @[the_test_file.rs 9994:9] | ||||
| ",
 | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #[hdl_module(outline_generated)] | ||||
| pub fn check_let_patterns() { | ||||
|     #[hdl] | ||||
|     let tuple_in: (UInt<1>, SInt<1>, Bool) = m.input(); | ||||
|     #[hdl] | ||||
|     let (tuple_0, tuple_1, tuple_2) = tuple_in; | ||||
|     #[hdl] | ||||
|     let tuple_0_out: UInt<1> = m.output(); | ||||
|     connect(tuple_0_out, tuple_0); | ||||
|     #[hdl] | ||||
|     let tuple_1_out: SInt<1> = m.output(); | ||||
|     connect(tuple_1_out, tuple_1); | ||||
|     #[hdl] | ||||
|     let tuple_2_out: Bool = m.output(); | ||||
|     connect(tuple_2_out, tuple_2); | ||||
| 
 | ||||
|     #[hdl] | ||||
|     let test_struct_in: TestStruct<SInt<8>> = m.input(); | ||||
|     #[hdl] | ||||
|     let TestStruct::<_> { a, b } = test_struct_in; | ||||
|     #[hdl] | ||||
|     let test_struct_a_out: SInt<8> = m.output(); | ||||
|     connect(test_struct_a_out, a); | ||||
|     #[hdl] | ||||
|     let test_struct_b_out: UInt<8> = m.output(); | ||||
|     connect(test_struct_b_out, b); | ||||
| 
 | ||||
|     #[hdl] | ||||
|     let test_struct_2_in: TestStruct2 = m.input(); | ||||
|     #[hdl] | ||||
|     let TestStruct2 { v } = test_struct_2_in; | ||||
|     #[hdl] | ||||
|     let test_struct_2_v_out: UInt<8> = m.output(); | ||||
|     connect(test_struct_2_v_out, v); | ||||
| 
 | ||||
|     #[hdl] | ||||
|     let test_struct_3_in: TestStruct3 = m.input(); | ||||
|     #[hdl] | ||||
|     let TestStruct3 {} = test_struct_3_in; | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_let_patterns() { | ||||
|     let _n = SourceLocation::normalize_files_for_tests(); | ||||
|     let m = check_let_patterns(); | ||||
|     dbg!(m); | ||||
|     #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
 | ||||
|     assert_export_firrtl! { | ||||
|         m => | ||||
|         "/test/check_let_patterns.fir": r"FIRRTL version 3.2.0
 | ||||
| circuit check_let_patterns: | ||||
|     type Ty0 = {`0`: UInt<1>, `1`: SInt<1>, `2`: UInt<1>} | ||||
|     type Ty1 = {a: SInt<8>, b: UInt<8>} | ||||
|     type Ty2 = {v: UInt<8>} | ||||
|     type Ty3 = {} | ||||
|     module check_let_patterns: @[module-XXXXXXXXXX.rs 1:1] | ||||
|         input tuple_in: Ty0 @[module-XXXXXXXXXX.rs 2:1] | ||||
|         output tuple_0_out: UInt<1> @[module-XXXXXXXXXX.rs 4:1] | ||||
|         output tuple_1_out: SInt<1> @[module-XXXXXXXXXX.rs 6:1] | ||||
|         output tuple_2_out: UInt<1> @[module-XXXXXXXXXX.rs 8:1] | ||||
|         input test_struct_in: Ty1 @[module-XXXXXXXXXX.rs 10:1] | ||||
|         output test_struct_a_out: SInt<8> @[module-XXXXXXXXXX.rs 12:1] | ||||
|         output test_struct_b_out: UInt<8> @[module-XXXXXXXXXX.rs 14:1] | ||||
|         input test_struct_2_in: Ty2 @[module-XXXXXXXXXX.rs 16:1] | ||||
|         output test_struct_2_v_out: UInt<8> @[module-XXXXXXXXXX.rs 18:1] | ||||
|         input test_struct_3_in: Ty3 @[module-XXXXXXXXXX.rs 20:1] | ||||
|         connect tuple_0_out, tuple_in.`0` @[module-XXXXXXXXXX.rs 5:1] | ||||
|         connect tuple_1_out, tuple_in.`1` @[module-XXXXXXXXXX.rs 7:1] | ||||
|         connect tuple_2_out, tuple_in.`2` @[module-XXXXXXXXXX.rs 9:1] | ||||
|         connect test_struct_a_out, test_struct_in.a @[module-XXXXXXXXXX.rs 13:1] | ||||
|         connect test_struct_b_out, test_struct_in.b @[module-XXXXXXXXXX.rs 15:1] | ||||
|         connect test_struct_2_v_out, test_struct_2_in.v @[module-XXXXXXXXXX.rs 19:1] | ||||
| ",
 | ||||
|     }; | ||||
| } | ||||
|  |  | |||
|  | @ -1246,200 +1246,3 @@ fn test_memories3() { | |||
|         panic!(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[hdl_module(outline_generated)] | ||||
| pub fn duplicate_names() { | ||||
|     #[hdl] | ||||
|     let w = wire(); | ||||
|     connect(w, 5u8); | ||||
|     #[hdl] | ||||
|     let w = wire(); | ||||
|     connect(w, 6u8); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_duplicate_names() { | ||||
|     let _n = SourceLocation::normalize_files_for_tests(); | ||||
|     let mut sim = Simulation::new(duplicate_names()); | ||||
|     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/duplicate_names.vcd") { | ||||
|         panic!(); | ||||
|     } | ||||
|     let sim_debug = format!("{sim:#?}"); | ||||
|     println!("#######\n{sim_debug}\n#######"); | ||||
|     if sim_debug != include_str!("sim/expected/duplicate_names.txt") { | ||||
|         panic!(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[hdl_module(outline_generated)] | ||||
| pub fn array_rw() { | ||||
|     #[hdl] | ||||
|     let array_in: Array<UInt<8>, 16> = m.input(); | ||||
|     #[hdl] | ||||
|     let array_out: Array<UInt<8>, 16> = m.output(); | ||||
|     #[hdl] | ||||
|     let read_index: UInt<8> = m.input(); | ||||
|     #[hdl] | ||||
|     let read_data: UInt<8> = m.output(); | ||||
|     #[hdl] | ||||
|     let write_index: UInt<8> = m.input(); | ||||
|     #[hdl] | ||||
|     let write_data: UInt<8> = m.input(); | ||||
|     #[hdl] | ||||
|     let write_en: Bool = m.input(); | ||||
|     #[hdl] | ||||
|     let array_wire = wire(); | ||||
|     connect(array_wire, array_in); | ||||
|     connect(array_out, array_wire); | ||||
|     #[hdl] | ||||
|     if write_en { | ||||
|         connect(array_wire[write_index], write_data); | ||||
|     } | ||||
|     connect(read_data, array_wire[read_index]); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_array_rw() { | ||||
|     let _n = SourceLocation::normalize_files_for_tests(); | ||||
|     let mut sim = Simulation::new(array_rw()); | ||||
|     let mut writer = RcWriter::default(); | ||||
|     sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); | ||||
|     #[derive(Debug, PartialEq)] | ||||
|     struct State { | ||||
|         array_in: [u8; 16], | ||||
|         array_out: [u8; 16], | ||||
|         read_index: u8, | ||||
|         read_data: u8, | ||||
|         write_index: u8, | ||||
|         write_data: u8, | ||||
|         write_en: bool, | ||||
|     } | ||||
|     let mut states = Vec::new(); | ||||
|     let array_in = [ | ||||
|         0xFFu8, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, //
 | ||||
|         0x00u8, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, | ||||
|     ]; | ||||
|     for i in 0..=16 { | ||||
|         states.push(State { | ||||
|             array_in, | ||||
|             array_out: array_in, | ||||
|             read_index: i, | ||||
|             read_data: array_in.get(i as usize).copied().unwrap_or(0), | ||||
|             write_index: 0, | ||||
|             write_data: 0, | ||||
|             write_en: false, | ||||
|         }); | ||||
|     } | ||||
|     for i in 0..=16u8 { | ||||
|         let mut array_out = array_in; | ||||
|         let write_data = i.wrapping_mul(i); | ||||
|         if let Some(v) = array_out.get_mut(i as usize) { | ||||
|             *v = write_data; | ||||
|         } | ||||
|         states.push(State { | ||||
|             array_in, | ||||
|             array_out, | ||||
|             read_index: 0, | ||||
|             read_data: array_out[0], | ||||
|             write_index: i, | ||||
|             write_data, | ||||
|             write_en: true, | ||||
|         }); | ||||
|     } | ||||
|     for (cycle, expected) in states.into_iter().enumerate() { | ||||
|         let State { | ||||
|             array_in, | ||||
|             array_out: _, | ||||
|             read_index, | ||||
|             read_data: _, | ||||
|             write_index, | ||||
|             write_data, | ||||
|             write_en, | ||||
|         } = expected; | ||||
|         sim.write(sim.io().array_in, array_in); | ||||
|         sim.write(sim.io().read_index, read_index); | ||||
|         sim.write(sim.io().write_index, write_index); | ||||
|         sim.write(sim.io().write_data, write_data); | ||||
|         sim.write(sim.io().write_en, write_en); | ||||
|         sim.advance_time(SimDuration::from_micros(1)); | ||||
|         let array_out = std::array::from_fn(|index| { | ||||
|             sim.read_bool_or_int(sim.io().array_out[index]) | ||||
|                 .to_bigint() | ||||
|                 .try_into() | ||||
|                 .expect("known to be in range") | ||||
|         }); | ||||
|         let read_data = sim | ||||
|             .read_bool_or_int(sim.io().read_data) | ||||
|             .to_bigint() | ||||
|             .try_into() | ||||
|             .expect("known to be in range"); | ||||
|         let state = State { | ||||
|             array_in, | ||||
|             array_out, | ||||
|             read_index, | ||||
|             read_data, | ||||
|             write_index, | ||||
|             write_data, | ||||
|             write_en, | ||||
|         }; | ||||
|         assert_eq!( | ||||
|             state, | ||||
|             expected, | ||||
|             "vcd:\n{}\ncycle: {cycle}", | ||||
|             String::from_utf8(writer.take()).unwrap(), | ||||
|         ); | ||||
|     } | ||||
|     sim.flush_traces().unwrap(); | ||||
|     let vcd = String::from_utf8(writer.take()).unwrap(); | ||||
|     println!("####### VCD:\n{vcd}\n#######"); | ||||
|     if vcd != include_str!("sim/expected/array_rw.vcd") { | ||||
|         panic!(); | ||||
|     } | ||||
|     let sim_debug = format!("{sim:#?}"); | ||||
|     println!("#######\n{sim_debug}\n#######"); | ||||
|     if sim_debug != include_str!("sim/expected/array_rw.txt") { | ||||
|         panic!(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[hdl_module(outline_generated)] | ||||
| pub fn conditional_assignment_last() { | ||||
|     #[hdl] | ||||
|     let i: Bool = m.input(); | ||||
|     #[hdl] | ||||
|     let w = wire(); | ||||
|     connect(w, true); | ||||
|     #[hdl] | ||||
|     if i { | ||||
|         connect(w, false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_conditional_assignment_last() { | ||||
|     let _n = SourceLocation::normalize_files_for_tests(); | ||||
|     let mut sim = Simulation::new(conditional_assignment_last()); | ||||
|     let mut writer = RcWriter::default(); | ||||
|     sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); | ||||
|     sim.write(sim.io().i, false); | ||||
|     sim.advance_time(SimDuration::from_micros(1)); | ||||
|     sim.write(sim.io().i, true); | ||||
|     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/conditional_assignment_last.vcd") { | ||||
|         panic!(); | ||||
|     } | ||||
|     let sim_debug = format!("{sim:#?}"); | ||||
|     println!("#######\n{sim_debug}\n#######"); | ||||
|     if sim_debug != include_str!("sim/expected/conditional_assignment_last.txt") { | ||||
|         panic!(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,283 +0,0 @@ | |||
| $timescale 1 ps $end | ||||
| $scope module array_rw $end | ||||
| $scope struct array_in $end | ||||
| $var wire 8 ! \[0] $end | ||||
| $var wire 8 " \[1] $end | ||||
| $var wire 8 # \[2] $end | ||||
| $var wire 8 $ \[3] $end | ||||
| $var wire 8 % \[4] $end | ||||
| $var wire 8 & \[5] $end | ||||
| $var wire 8 ' \[6] $end | ||||
| $var wire 8 ( \[7] $end | ||||
| $var wire 8 ) \[8] $end | ||||
| $var wire 8 * \[9] $end | ||||
| $var wire 8 + \[10] $end | ||||
| $var wire 8 , \[11] $end | ||||
| $var wire 8 - \[12] $end | ||||
| $var wire 8 . \[13] $end | ||||
| $var wire 8 / \[14] $end | ||||
| $var wire 8 0 \[15] $end | ||||
| $upscope $end | ||||
| $scope struct array_out $end | ||||
| $var wire 8 1 \[0] $end | ||||
| $var wire 8 2 \[1] $end | ||||
| $var wire 8 3 \[2] $end | ||||
| $var wire 8 4 \[3] $end | ||||
| $var wire 8 5 \[4] $end | ||||
| $var wire 8 6 \[5] $end | ||||
| $var wire 8 7 \[6] $end | ||||
| $var wire 8 8 \[7] $end | ||||
| $var wire 8 9 \[8] $end | ||||
| $var wire 8 : \[9] $end | ||||
| $var wire 8 ; \[10] $end | ||||
| $var wire 8 < \[11] $end | ||||
| $var wire 8 = \[12] $end | ||||
| $var wire 8 > \[13] $end | ||||
| $var wire 8 ? \[14] $end | ||||
| $var wire 8 @ \[15] $end | ||||
| $upscope $end | ||||
| $var wire 8 A read_index $end | ||||
| $var wire 8 B read_data $end | ||||
| $var wire 8 C write_index $end | ||||
| $var wire 8 D write_data $end | ||||
| $var wire 1 E write_en $end | ||||
| $scope struct array_wire $end | ||||
| $var wire 8 F \[0] $end | ||||
| $var wire 8 G \[1] $end | ||||
| $var wire 8 H \[2] $end | ||||
| $var wire 8 I \[3] $end | ||||
| $var wire 8 J \[4] $end | ||||
| $var wire 8 K \[5] $end | ||||
| $var wire 8 L \[6] $end | ||||
| $var wire 8 M \[7] $end | ||||
| $var wire 8 N \[8] $end | ||||
| $var wire 8 O \[9] $end | ||||
| $var wire 8 P \[10] $end | ||||
| $var wire 8 Q \[11] $end | ||||
| $var wire 8 R \[12] $end | ||||
| $var wire 8 S \[13] $end | ||||
| $var wire 8 T \[14] $end | ||||
| $var wire 8 U \[15] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $enddefinitions $end | ||||
| $dumpvars | ||||
| b11111111 ! | ||||
| b1111111 " | ||||
| b111111 # | ||||
| b11111 $ | ||||
| b1111 % | ||||
| b111 & | ||||
| b11 ' | ||||
| b1 ( | ||||
| b0 ) | ||||
| b10000000 * | ||||
| b11000000 + | ||||
| b11100000 , | ||||
| b11110000 - | ||||
| b11111000 . | ||||
| b11111100 / | ||||
| b11111110 0 | ||||
| b11111111 1 | ||||
| b1111111 2 | ||||
| b111111 3 | ||||
| b11111 4 | ||||
| b1111 5 | ||||
| b111 6 | ||||
| b11 7 | ||||
| b1 8 | ||||
| b0 9 | ||||
| b10000000 : | ||||
| b11000000 ; | ||||
| b11100000 < | ||||
| b11110000 = | ||||
| b11111000 > | ||||
| b11111100 ? | ||||
| b11111110 @ | ||||
| b0 A | ||||
| b11111111 B | ||||
| b0 C | ||||
| b0 D | ||||
| 0E | ||||
| b11111111 F | ||||
| b1111111 G | ||||
| b111111 H | ||||
| b11111 I | ||||
| b1111 J | ||||
| b111 K | ||||
| b11 L | ||||
| b1 M | ||||
| b0 N | ||||
| b10000000 O | ||||
| b11000000 P | ||||
| b11100000 Q | ||||
| b11110000 R | ||||
| b11111000 S | ||||
| b11111100 T | ||||
| b11111110 U | ||||
| $end | ||||
| #1000000 | ||||
| b1 A | ||||
| b1111111 B | ||||
| #2000000 | ||||
| b10 A | ||||
| b111111 B | ||||
| #3000000 | ||||
| b11 A | ||||
| b11111 B | ||||
| #4000000 | ||||
| b100 A | ||||
| b1111 B | ||||
| #5000000 | ||||
| b101 A | ||||
| b111 B | ||||
| #6000000 | ||||
| b110 A | ||||
| b11 B | ||||
| #7000000 | ||||
| b111 A | ||||
| b1 B | ||||
| #8000000 | ||||
| b1000 A | ||||
| b0 B | ||||
| #9000000 | ||||
| b1001 A | ||||
| b10000000 B | ||||
| #10000000 | ||||
| b1010 A | ||||
| b11000000 B | ||||
| #11000000 | ||||
| b1011 A | ||||
| b11100000 B | ||||
| #12000000 | ||||
| b1100 A | ||||
| b11110000 B | ||||
| #13000000 | ||||
| b1101 A | ||||
| b11111000 B | ||||
| #14000000 | ||||
| b1110 A | ||||
| b11111100 B | ||||
| #15000000 | ||||
| b1111 A | ||||
| b11111110 B | ||||
| #16000000 | ||||
| b10000 A | ||||
| b0 B | ||||
| #17000000 | ||||
| b0 1 | ||||
| b0 A | ||||
| 1E | ||||
| b0 F | ||||
| #18000000 | ||||
| b11111111 1 | ||||
| b1 2 | ||||
| b11111111 B | ||||
| b1 C | ||||
| b1 D | ||||
| b11111111 F | ||||
| b1 G | ||||
| #19000000 | ||||
| b1111111 2 | ||||
| b100 3 | ||||
| b10 C | ||||
| b100 D | ||||
| b1111111 G | ||||
| b100 H | ||||
| #20000000 | ||||
| b111111 3 | ||||
| b1001 4 | ||||
| b11 C | ||||
| b1001 D | ||||
| b111111 H | ||||
| b1001 I | ||||
| #21000000 | ||||
| b11111 4 | ||||
| b10000 5 | ||||
| b100 C | ||||
| b10000 D | ||||
| b11111 I | ||||
| b10000 J | ||||
| #22000000 | ||||
| b1111 5 | ||||
| b11001 6 | ||||
| b101 C | ||||
| b11001 D | ||||
| b1111 J | ||||
| b11001 K | ||||
| #23000000 | ||||
| b111 6 | ||||
| b100100 7 | ||||
| b110 C | ||||
| b100100 D | ||||
| b111 K | ||||
| b100100 L | ||||
| #24000000 | ||||
| b11 7 | ||||
| b110001 8 | ||||
| b111 C | ||||
| b110001 D | ||||
| b11 L | ||||
| b110001 M | ||||
| #25000000 | ||||
| b1 8 | ||||
| b1000000 9 | ||||
| b1000 C | ||||
| b1000000 D | ||||
| b1 M | ||||
| b1000000 N | ||||
| #26000000 | ||||
| b0 9 | ||||
| b1010001 : | ||||
| b1001 C | ||||
| b1010001 D | ||||
| b0 N | ||||
| b1010001 O | ||||
| #27000000 | ||||
| b10000000 : | ||||
| b1100100 ; | ||||
| b1010 C | ||||
| b1100100 D | ||||
| b10000000 O | ||||
| b1100100 P | ||||
| #28000000 | ||||
| b11000000 ; | ||||
| b1111001 < | ||||
| b1011 C | ||||
| b1111001 D | ||||
| b11000000 P | ||||
| b1111001 Q | ||||
| #29000000 | ||||
| b11100000 < | ||||
| b10010000 = | ||||
| b1100 C | ||||
| b10010000 D | ||||
| b11100000 Q | ||||
| b10010000 R | ||||
| #30000000 | ||||
| b11110000 = | ||||
| b10101001 > | ||||
| b1101 C | ||||
| b10101001 D | ||||
| b11110000 R | ||||
| b10101001 S | ||||
| #31000000 | ||||
| b11111000 > | ||||
| b11000100 ? | ||||
| b1110 C | ||||
| b11000100 D | ||||
| b11111000 S | ||||
| b11000100 T | ||||
| #32000000 | ||||
| b11111100 ? | ||||
| b11100001 @ | ||||
| b1111 C | ||||
| b11100001 D | ||||
| b11111100 T | ||||
| b11100001 U | ||||
| #33000000 | ||||
| b11111110 @ | ||||
| b10000 C | ||||
| b0 D | ||||
| b11111110 U | ||||
| #34000000 | ||||
|  | @ -1,189 +0,0 @@ | |||
| Simulation { | ||||
|     state: State { | ||||
|         insns: Insns { | ||||
|             state_layout: StateLayout { | ||||
|                 ty: TypeLayout { | ||||
|                     small_slots: StatePartLayout<SmallSlots> { | ||||
|                         len: 0, | ||||
|                         debug_data: [], | ||||
|                         .. | ||||
|                     }, | ||||
|                     big_slots: StatePartLayout<BigSlots> { | ||||
|                         len: 4, | ||||
|                         debug_data: [ | ||||
|                             SlotDebugData { | ||||
|                                 name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", | ||||
|                                 ty: Bool, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", | ||||
|                                 ty: Bool, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "", | ||||
|                                 ty: Bool, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "", | ||||
|                                 ty: Bool, | ||||
|                             }, | ||||
|                         ], | ||||
|                         .. | ||||
|                     }, | ||||
|                 }, | ||||
|                 memories: StatePartLayout<Memories> { | ||||
|                     len: 0, | ||||
|                     debug_data: [], | ||||
|                     layout_data: [], | ||||
|                     .. | ||||
|                 }, | ||||
|             }, | ||||
|             insns: [ | ||||
|                 // at: module-XXXXXXXXXX.rs:1:1 | ||||
|                 0: Const { | ||||
|                     dest: StatePartIndex<BigSlots>(3), // (0x0) SlotDebugData { name: "", ty: Bool }, | ||||
|                     value: 0x0, | ||||
|                 }, | ||||
|                 1: Const { | ||||
|                     dest: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool }, | ||||
|                     value: 0x1, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:4:1 | ||||
|                 2: Copy { | ||||
|                     dest: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool }, | ||||
|                     src: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool }, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:5:1 | ||||
|                 3: BranchIfZero { | ||||
|                     target: 5, | ||||
|                     value: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", ty: Bool }, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:6:1 | ||||
|                 4: Copy { | ||||
|                     dest: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool }, | ||||
|                     src: StatePartIndex<BigSlots>(3), // (0x0) SlotDebugData { name: "", ty: Bool }, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:1:1 | ||||
|                 5: Return, | ||||
|             ], | ||||
|             .. | ||||
|         }, | ||||
|         pc: 5, | ||||
|         memory_write_log: [], | ||||
|         memories: StatePart { | ||||
|             value: [], | ||||
|         }, | ||||
|         small_slots: StatePart { | ||||
|             value: [], | ||||
|         }, | ||||
|         big_slots: StatePart { | ||||
|             value: [ | ||||
|                 1, | ||||
|                 0, | ||||
|                 1, | ||||
|                 0, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
|     io: Instance { | ||||
|         name: <simulator>::conditional_assignment_last, | ||||
|         instantiated: Module { | ||||
|             name: conditional_assignment_last, | ||||
|             .. | ||||
|         }, | ||||
|     }, | ||||
|     uninitialized_inputs: {}, | ||||
|     io_targets: { | ||||
|         Instance { | ||||
|             name: <simulator>::conditional_assignment_last, | ||||
|             instantiated: Module { | ||||
|                 name: conditional_assignment_last, | ||||
|                 .. | ||||
|             }, | ||||
|         }.i: CompiledValue { | ||||
|             layout: CompiledTypeLayout { | ||||
|                 ty: Bool, | ||||
|                 layout: TypeLayout { | ||||
|                     small_slots: StatePartLayout<SmallSlots> { | ||||
|                         len: 0, | ||||
|                         debug_data: [], | ||||
|                         .. | ||||
|                     }, | ||||
|                     big_slots: StatePartLayout<BigSlots> { | ||||
|                         len: 1, | ||||
|                         debug_data: [ | ||||
|                             SlotDebugData { | ||||
|                                 name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", | ||||
|                                 ty: Bool, | ||||
|                             }, | ||||
|                         ], | ||||
|                         .. | ||||
|                     }, | ||||
|                 }, | ||||
|                 body: Scalar, | ||||
|             }, | ||||
|             range: TypeIndexRange { | ||||
|                 small_slots: StatePartIndexRange<SmallSlots> { start: 0, len: 0 }, | ||||
|                 big_slots: StatePartIndexRange<BigSlots> { start: 0, len: 1 }, | ||||
|             }, | ||||
|             write: None, | ||||
|         }, | ||||
|     }, | ||||
|     made_initial_step: true, | ||||
|     needs_settle: false, | ||||
|     trace_decls: TraceModule { | ||||
|         name: "conditional_assignment_last", | ||||
|         children: [ | ||||
|             TraceModuleIO { | ||||
|                 name: "i", | ||||
|                 child: TraceBool { | ||||
|                     location: TraceScalarId(0), | ||||
|                     name: "i", | ||||
|                     flow: Source, | ||||
|                 }, | ||||
|                 ty: Bool, | ||||
|                 flow: Source, | ||||
|             }, | ||||
|             TraceWire { | ||||
|                 name: "w", | ||||
|                 child: TraceBool { | ||||
|                     location: TraceScalarId(1), | ||||
|                     name: "w", | ||||
|                     flow: Duplex, | ||||
|                 }, | ||||
|                 ty: Bool, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     traces: [ | ||||
|         SimTrace { | ||||
|             id: TraceScalarId(0), | ||||
|             kind: BigBool { | ||||
|                 index: StatePartIndex<BigSlots>(0), | ||||
|             }, | ||||
|             state: 0x1, | ||||
|             last_state: 0x0, | ||||
|         }, | ||||
|         SimTrace { | ||||
|             id: TraceScalarId(1), | ||||
|             kind: BigBool { | ||||
|                 index: StatePartIndex<BigSlots>(1), | ||||
|             }, | ||||
|             state: 0x0, | ||||
|             last_state: 0x1, | ||||
|         }, | ||||
|     ], | ||||
|     trace_memories: {}, | ||||
|     trace_writers: [ | ||||
|         Running( | ||||
|             VcdWriter { | ||||
|                 finished_init: true, | ||||
|                 timescale: 1 ps, | ||||
|                 .. | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
|     instant: 2 μs, | ||||
|     clocks_triggered: [], | ||||
|     .. | ||||
| } | ||||
|  | @ -1,14 +0,0 @@ | |||
| $timescale 1 ps $end | ||||
| $scope module conditional_assignment_last $end | ||||
| $var wire 1 ! i $end | ||||
| $var wire 1 " w $end | ||||
| $upscope $end | ||||
| $enddefinitions $end | ||||
| $dumpvars | ||||
| 0! | ||||
| 1" | ||||
| $end | ||||
| #1000000 | ||||
| 1! | ||||
| 0" | ||||
| #2000000 | ||||
|  | @ -1,153 +0,0 @@ | |||
| Simulation { | ||||
|     state: State { | ||||
|         insns: Insns { | ||||
|             state_layout: StateLayout { | ||||
|                 ty: TypeLayout { | ||||
|                     small_slots: StatePartLayout<SmallSlots> { | ||||
|                         len: 0, | ||||
|                         debug_data: [], | ||||
|                         .. | ||||
|                     }, | ||||
|                     big_slots: StatePartLayout<BigSlots> { | ||||
|                         len: 4, | ||||
|                         debug_data: [ | ||||
|                             SlotDebugData { | ||||
|                                 name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", | ||||
|                                 ty: UInt<8>, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "", | ||||
|                                 ty: UInt<8>, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", | ||||
|                                 ty: UInt<8>, | ||||
|                             }, | ||||
|                             SlotDebugData { | ||||
|                                 name: "", | ||||
|                                 ty: UInt<8>, | ||||
|                             }, | ||||
|                         ], | ||||
|                         .. | ||||
|                     }, | ||||
|                 }, | ||||
|                 memories: StatePartLayout<Memories> { | ||||
|                     len: 0, | ||||
|                     debug_data: [], | ||||
|                     layout_data: [], | ||||
|                     .. | ||||
|                 }, | ||||
|             }, | ||||
|             insns: [ | ||||
|                 // at: module-XXXXXXXXXX.rs:1:1 | ||||
|                 0: Const { | ||||
|                     dest: StatePartIndex<BigSlots>(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, | ||||
|                     value: 0x6, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:5:1 | ||||
|                 1: Copy { | ||||
|                     dest: StatePartIndex<BigSlots>(2), // (0x6) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, | ||||
|                     src: StatePartIndex<BigSlots>(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:1:1 | ||||
|                 2: Const { | ||||
|                     dest: StatePartIndex<BigSlots>(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, | ||||
|                     value: 0x5, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:3:1 | ||||
|                 3: Copy { | ||||
|                     dest: StatePartIndex<BigSlots>(0), // (0x5) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, | ||||
|                     src: StatePartIndex<BigSlots>(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, | ||||
|                 }, | ||||
|                 // at: module-XXXXXXXXXX.rs:1:1 | ||||
|                 4: Return, | ||||
|             ], | ||||
|             .. | ||||
|         }, | ||||
|         pc: 4, | ||||
|         memory_write_log: [], | ||||
|         memories: StatePart { | ||||
|             value: [], | ||||
|         }, | ||||
|         small_slots: StatePart { | ||||
|             value: [], | ||||
|         }, | ||||
|         big_slots: StatePart { | ||||
|             value: [ | ||||
|                 5, | ||||
|                 5, | ||||
|                 6, | ||||
|                 6, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
|     io: Instance { | ||||
|         name: <simulator>::duplicate_names, | ||||
|         instantiated: Module { | ||||
|             name: duplicate_names, | ||||
|             .. | ||||
|         }, | ||||
|     }, | ||||
|     uninitialized_inputs: {}, | ||||
|     io_targets: {}, | ||||
|     made_initial_step: true, | ||||
|     needs_settle: false, | ||||
|     trace_decls: TraceModule { | ||||
|         name: "duplicate_names", | ||||
|         children: [ | ||||
|             TraceWire { | ||||
|                 name: "w", | ||||
|                 child: TraceUInt { | ||||
|                     location: TraceScalarId(0), | ||||
|                     name: "w", | ||||
|                     ty: UInt<8>, | ||||
|                     flow: Duplex, | ||||
|                 }, | ||||
|                 ty: UInt<8>, | ||||
|             }, | ||||
|             TraceWire { | ||||
|                 name: "w", | ||||
|                 child: TraceUInt { | ||||
|                     location: TraceScalarId(1), | ||||
|                     name: "w", | ||||
|                     ty: UInt<8>, | ||||
|                     flow: Duplex, | ||||
|                 }, | ||||
|                 ty: UInt<8>, | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
|     traces: [ | ||||
|         SimTrace { | ||||
|             id: TraceScalarId(0), | ||||
|             kind: BigUInt { | ||||
|                 index: StatePartIndex<BigSlots>(0), | ||||
|                 ty: UInt<8>, | ||||
|             }, | ||||
|             state: 0x05, | ||||
|             last_state: 0x05, | ||||
|         }, | ||||
|         SimTrace { | ||||
|             id: TraceScalarId(1), | ||||
|             kind: BigUInt { | ||||
|                 index: StatePartIndex<BigSlots>(2), | ||||
|                 ty: UInt<8>, | ||||
|             }, | ||||
|             state: 0x06, | ||||
|             last_state: 0x06, | ||||
|         }, | ||||
|     ], | ||||
|     trace_memories: {}, | ||||
|     trace_writers: [ | ||||
|         Running( | ||||
|             VcdWriter { | ||||
|                 finished_init: true, | ||||
|                 timescale: 1 ps, | ||||
|                 .. | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
|     instant: 1 μs, | ||||
|     clocks_triggered: [], | ||||
|     .. | ||||
| } | ||||
|  | @ -1,11 +0,0 @@ | |||
| $timescale 1 ps $end | ||||
| $scope module duplicate_names $end | ||||
| $var wire 8 ! w $end | ||||
| $var wire 8 " w_2 $end | ||||
| $upscope $end | ||||
| $enddefinitions $end | ||||
| $dumpvars | ||||
| b101 ! | ||||
| b110 " | ||||
| $end | ||||
| #1000000 | ||||
|  | @ -24,97 +24,97 @@ $upscope $end | |||
| $upscope $end | ||||
| $scope struct mem $end | ||||
| $scope struct contents $end | ||||
| $scope struct \[0] $end | ||||
| $scope struct [0] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 9 \0 $end | ||||
| $var reg 8 I \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[1] $end | ||||
| $scope struct [1] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 : \0 $end | ||||
| $var reg 8 J \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[2] $end | ||||
| $scope struct [2] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 ; \0 $end | ||||
| $var reg 8 K \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[3] $end | ||||
| $scope struct [3] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 < \0 $end | ||||
| $var reg 8 L \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[4] $end | ||||
| $scope struct [4] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 = \0 $end | ||||
| $var reg 8 M \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[5] $end | ||||
| $scope struct [5] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 > \0 $end | ||||
| $var reg 8 N \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[6] $end | ||||
| $scope struct [6] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 ? \0 $end | ||||
| $var reg 8 O \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[7] $end | ||||
| $scope struct [7] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 @ \0 $end | ||||
| $var reg 8 P \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[8] $end | ||||
| $scope struct [8] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 A \0 $end | ||||
| $var reg 8 Q \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[9] $end | ||||
| $scope struct [9] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 B \0 $end | ||||
| $var reg 8 R \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[10] $end | ||||
| $scope struct [10] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 C \0 $end | ||||
| $var reg 8 S \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[11] $end | ||||
| $scope struct [11] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 D \0 $end | ||||
| $var reg 8 T \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[12] $end | ||||
| $scope struct [12] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 E \0 $end | ||||
| $var reg 8 U \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[13] $end | ||||
| $scope struct [13] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 F \0 $end | ||||
| $var reg 8 V \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[14] $end | ||||
| $scope struct [14] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 G \0 $end | ||||
| $var reg 8 W \1 $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[15] $end | ||||
| $scope struct [15] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 H \0 $end | ||||
| $var reg 8 X \1 $end | ||||
|  |  | |||
|  | @ -11,31 +11,31 @@ $var wire 1 ' wmask $end | |||
| $upscope $end | ||||
| $scope struct mem $end | ||||
| $scope struct contents $end | ||||
| $scope struct \[0] $end | ||||
| $scope struct [0] $end | ||||
| $scope struct mem $end | ||||
| $var string 1 1 \$tag $end | ||||
| $var reg 1 6 HdlSome $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[1] $end | ||||
| $scope struct [1] $end | ||||
| $scope struct mem $end | ||||
| $var string 1 2 \$tag $end | ||||
| $var reg 1 7 HdlSome $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[2] $end | ||||
| $scope struct [2] $end | ||||
| $scope struct mem $end | ||||
| $var string 1 3 \$tag $end | ||||
| $var reg 1 8 HdlSome $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[3] $end | ||||
| $scope struct [3] $end | ||||
| $scope struct mem $end | ||||
| $var string 1 4 \$tag $end | ||||
| $var reg 1 9 HdlSome $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[4] $end | ||||
| $scope struct [4] $end | ||||
| $scope struct mem $end | ||||
| $var string 1 5 \$tag $end | ||||
| $var reg 1 : HdlSome $end | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ $upscope $end | |||
| $upscope $end | ||||
| $scope struct mem $end | ||||
| $scope struct contents $end | ||||
| $scope struct \[0] $end | ||||
| $scope struct [0] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 ] \[0] $end | ||||
| $var reg 8 e \[1] $end | ||||
|  | @ -54,7 +54,7 @@ $var reg 8 /" \[6] $end | |||
| $var reg 8 7" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[1] $end | ||||
| $scope struct [1] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 ^ \[0] $end | ||||
| $var reg 8 f \[1] $end | ||||
|  | @ -66,7 +66,7 @@ $var reg 8 0" \[6] $end | |||
| $var reg 8 8" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[2] $end | ||||
| $scope struct [2] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 _ \[0] $end | ||||
| $var reg 8 g \[1] $end | ||||
|  | @ -78,7 +78,7 @@ $var reg 8 1" \[6] $end | |||
| $var reg 8 9" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[3] $end | ||||
| $scope struct [3] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 ` \[0] $end | ||||
| $var reg 8 h \[1] $end | ||||
|  | @ -90,7 +90,7 @@ $var reg 8 2" \[6] $end | |||
| $var reg 8 :" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[4] $end | ||||
| $scope struct [4] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 a \[0] $end | ||||
| $var reg 8 i \[1] $end | ||||
|  | @ -102,7 +102,7 @@ $var reg 8 3" \[6] $end | |||
| $var reg 8 ;" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[5] $end | ||||
| $scope struct [5] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 b \[0] $end | ||||
| $var reg 8 j \[1] $end | ||||
|  | @ -114,7 +114,7 @@ $var reg 8 4" \[6] $end | |||
| $var reg 8 <" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[6] $end | ||||
| $scope struct [6] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 c \[0] $end | ||||
| $var reg 8 k \[1] $end | ||||
|  | @ -126,7 +126,7 @@ $var reg 8 5" \[6] $end | |||
| $var reg 8 =" \[7] $end | ||||
| $upscope $end | ||||
| $upscope $end | ||||
| $scope struct \[7] $end | ||||
| $scope struct [7] $end | ||||
| $scope struct mem $end | ||||
| $var reg 8 d \[0] $end | ||||
| $var reg 8 l \[1] $end | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue