diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index ffaca53..b29723c 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -12,10 +12,10 @@ jobs: outputs: cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }} steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 id: restore-deps with: path: deps @@ -58,19 +58,19 @@ jobs: - name: Get SymbiYosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --branch=yosys-0.45 https://github.com/YosysHQ/sby.git deps/sby + git clone --depth=1 --branch=yosys-0.45 https://git.libre-chip.org/mirrors/sby deps/sby - name: Build Z3 if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=z3-4.13.3 https://github.com/Z3Prover/z3.git deps/z3 + git clone --depth=1 --recursive --branch=z3-4.13.3 https://git.libre-chip.org/mirrors/z3 deps/z3 (cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local) make -C deps/z3/build -j"$(nproc)" - name: Build Yosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=0.45 https://github.com/YosysHQ/yosys.git deps/yosys + git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys make -C deps/yosys -j"$(nproc)" - - uses: https://code.forgejo.org/actions/cache/save@v3 + - uses: https://git.libre-chip.org/mirrors/cache/save@v3 if: steps.restore-deps.outputs.cache-hit != 'true' with: path: deps diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index e83c668..49fb3e4 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: debian-12 needs: deps steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - run: | @@ -41,7 +41,7 @@ jobs: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0 source "$HOME/.cargo/env" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 with: path: deps key: ${{ needs.deps.outputs.cache-primary-key }} @@ -52,7 +52,7 @@ jobs: make -C deps/yosys install export PATH="$(realpath deps/firtool/bin):$PATH" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://github.com/Swatinem/rust-cache@v2 + - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} - run: cargo test diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 79326e2..b0fe498 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -83,6 +83,7 @@ impl ParsedBundle { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq: _, } = options.body; let mut fields = match fields { syn::Fields::Named(fields) => fields, @@ -437,6 +438,7 @@ impl ToTokens for ParsedBundle { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); @@ -765,6 +767,69 @@ impl ToTokens for ParsedBundle { } } .to_tokens(tokens); + if let Some((cmp_eq,)) = cmp_eq { + let mut where_clause = + Generics::from(generics) + .where_clause + .unwrap_or_else(|| syn::WhereClause { + where_token: Token![where](span), + predicates: Punctuated::new(), + }); + let mut fields_cmp_eq = vec![]; + let mut fields_cmp_ne = vec![]; + for field in fields.named() { + let field_ident = field.ident(); + let field_ty = field.ty(); + where_clause + .predicates + .push(parse_quote_spanned! {cmp_eq.span=> + #field_ty: ::fayalite::expr::ops::ExprPartialEq<#field_ty> + }); + fields_cmp_eq.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_eq(__lhs.#field_ident, __rhs.#field_ident) + }); + fields_cmp_ne.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_ne(__lhs.#field_ident, __rhs.#field_ident) + }); + } + let cmp_eq_body; + let cmp_ne_body; + if fields_len == 0 { + cmp_eq_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&true) + }; + cmp_ne_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&false) + }; + } else { + cmp_eq_body = quote_spanned! {span=> + #(#fields_cmp_eq)&* + }; + cmp_ne_body = quote_spanned! {span=> + #(#fields_cmp_ne)|* + }; + }; + quote_spanned! {span=> + #[automatically_derived] + impl #impl_generics ::fayalite::expr::ops::ExprPartialEq for #target #type_generics + #where_clause + { + fn cmp_eq( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_eq_body + } + fn cmp_ne( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_ne_body + } + } + } + .to_tokens(tokens); + } if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { let static_generics = generics.clone().for_static_type(); let (static_impl_generics, static_type_generics, static_where_clause) = diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 1d16177..9174566 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -155,7 +155,11 @@ impl ParsedEnum { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq, } = options.body; + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -211,6 +215,7 @@ impl ToTokens for ParsedEnum { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq: _, // TODO: implement cmp_eq for enums } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index e5d5f7b..97501e7 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -49,10 +49,14 @@ impl ParsedTypeAlias { custom_bounds, no_static, no_runtime_generics: _, + cmp_eq, } = options.body; if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } let generics = if custom_bounds.is_some() { MaybeParsed::Unrecognized(generics) } else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) { @@ -95,6 +99,7 @@ impl ToTokens for ParsedTypeAlias { custom_bounds: _, no_static: _, no_runtime_generics, + cmp_eq: _, } = &options.body; let target = get_target(target, ident); let mut type_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 6193dc3..2da0915 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -26,6 +26,7 @@ crate::options! { CustomBounds(custom_bounds), NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), + CmpEq(cmp_eq), } } @@ -2069,11 +2070,16 @@ macro_rules! impl_bounds { $( $Variant:ident, )* + $( + #[unknown] + $Unknown:ident, + )? } ) => { #[derive(Clone, Debug)] $vis enum $enum_type { $($Variant(known_items::$Variant),)* + $($Unknown(syn::TypeParamBound),)? } $(impl From for $enum_type { @@ -2086,28 +2092,54 @@ macro_rules! impl_bounds { fn to_tokens(&self, tokens: &mut TokenStream) { match self { $(Self::$Variant(v) => v.to_tokens(tokens),)* + $(Self::$Unknown(v) => v.to_tokens(tokens),)? } } } impl $enum_type { $vis fn parse_path(path: Path) -> Result { + #![allow(unreachable_code)] $(let path = match known_items::$Variant::parse_path(path) { Ok(v) => return Ok(Self::$Variant(v)), Err(path) => path, };)* + $(return Ok(Self::$Unknown(syn::TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + }.into()));)? Err(path) } + $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result { + #![allow(unreachable_code)] + if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound { + if let syn::TraitBound { + paren_token: _, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: _, + } = trait_bound { + match Self::parse_path(trait_bound.path) { + Ok(retval) => return Ok(retval), + Err(path) => trait_bound.path = path, + } + } + type_param_bound = trait_bound.into(); + } + $(return Ok(Self::$Unknown(type_param_bound));)? + Err(type_param_bound) + } } impl Parse for $enum_type { fn parse(input: ParseStream) -> syn::Result { - Self::parse_path(Path::parse_mod_style(input)?).map_err(|path| { - syn::Error::new_spanned( - path, + Self::parse_type_param_bound(input.parse()?) + .map_err(|type_param_bound| syn::Error::new_spanned( + type_param_bound, format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), - ) - }) + )) } } @@ -2115,6 +2147,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct $struct_type { $($vis $Variant: Option,)* + $($vis $Unknown: Vec,)? } impl ToTokens for $struct_type { @@ -2126,42 +2159,63 @@ macro_rules! impl_bounds { separator = Some(::default()); v.to_tokens(tokens); })* + $(for v in &self.$Unknown { + separator.to_tokens(tokens); + separator = Some(::default()); + v.to_tokens(tokens); + })* } } const _: () = { #[derive(Clone, Debug)] - $vis struct Iter($vis $struct_type); + #[allow(non_snake_case)] + $vis struct Iter { + $($Variant: Option,)* + $($Unknown: std::vec::IntoIter,)? + } impl IntoIterator for $struct_type { type Item = $enum_type; type IntoIter = Iter; fn into_iter(self) -> Self::IntoIter { - Iter(self) + Iter { + $($Variant: self.$Variant,)* + $($Unknown: self.$Unknown.into_iter(),)? + } } } impl Iterator for Iter { type Item = $enum_type; - fn next(&mut self) -> Option { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { return Some($enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + return Some($enum_type::$Unknown(value)); + } + )? None } #[allow(unused_mut, unused_variables)] fn fold B>(mut self, mut init: B, mut f: F) -> B { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { init = f(init, $enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + init = f(init, $enum_type::$Unknown(value)); + } + )? init } } @@ -2173,6 +2227,9 @@ macro_rules! impl_bounds { $($enum_type::$Variant(v) => { self.$Variant = Some(v); })* + $($enum_type::$Unknown(v) => { + self.$Unknown.push(v); + })? }); } } @@ -2191,6 +2248,7 @@ macro_rules! impl_bounds { $(if let Some(v) = v.$Variant { self.$Variant = Some(v); })* + $(self.$Unknown.extend(v.$Unknown);)* }); } } @@ -2244,6 +2302,8 @@ impl_bounds! { Size, StaticType, Type, + #[unknown] + Unknown, } } @@ -2257,6 +2317,8 @@ impl_bounds! { ResetType, StaticType, Type, + #[unknown] + Unknown, } } @@ -2270,6 +2332,7 @@ impl From for ParsedBound { ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v), + ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v), } } } @@ -2284,6 +2347,7 @@ impl From for ParsedBounds { ResetType, StaticType, Type, + Unknown, } = value; Self { BoolOrIntType, @@ -2295,6 +2359,7 @@ impl From for ParsedBounds { Size: None, StaticType, Type, + Unknown, } } } @@ -2330,6 +2395,7 @@ impl ParsedTypeBound { ParsedTypeBound::Type(known_items::Type(span)), ]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), + Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]), } } } @@ -2364,6 +2430,7 @@ impl From for ParsedBounds { Size, StaticType: None, Type: None, + Unknown: vec![], } } } @@ -2391,6 +2458,7 @@ impl ParsedBounds { fn categorize(self, errors: &mut Errors, span: Span) -> ParsedBoundsCategory { let mut type_bounds = None; let mut size_type_bounds = None; + let mut unknown_bounds = vec![]; self.into_iter().for_each(|bound| match bound.categorize() { ParsedBoundCategory::Type(bound) => { type_bounds @@ -2402,15 +2470,37 @@ impl ParsedBounds { .get_or_insert_with(ParsedSizeTypeBounds::default) .extend([bound]); } + ParsedBoundCategory::Unknown(bound) => unknown_bounds.push(bound), }); - match (type_bounds, size_type_bounds) { - (None, None) => ParsedBoundsCategory::Type(ParsedTypeBounds { + match (type_bounds, size_type_bounds, unknown_bounds.is_empty()) { + (None, None, true) => ParsedBoundsCategory::Type(ParsedTypeBounds { Type: Some(known_items::Type(span)), ..Default::default() }), - (None, Some(bounds)) => ParsedBoundsCategory::SizeType(bounds), - (Some(bounds), None) => ParsedBoundsCategory::Type(bounds), - (Some(type_bounds), Some(size_type_bounds)) => { + (None, None, false) => { + errors.error( + unknown_bounds.remove(0), + "unknown bounds: must use at least one known bound (such as `Type`) with any unknown bounds", + ); + ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..Default::default() + }) + } + (None, Some(bounds), true) => ParsedBoundsCategory::SizeType(bounds), + (None, Some(bounds), false) => { + // TODO: implement + errors.error( + unknown_bounds.remove(0), + "unknown bounds with `Size` bounds are not implemented", + ); + ParsedBoundsCategory::SizeType(bounds) + } + (Some(bounds), None, _) => ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..bounds + }), + (Some(type_bounds), Some(size_type_bounds), _) => { errors.error( size_type_bounds .Size @@ -2427,6 +2517,7 @@ impl ParsedBounds { pub(crate) enum ParsedBoundCategory { Type(ParsedTypeBound), SizeType(ParsedSizeTypeBound), + Unknown(syn::TypeParamBound), } impl ParsedBound { @@ -2441,12 +2532,14 @@ impl ParsedBound { Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), + Self::Unknown(v) => ParsedBoundCategory::Unknown(v), } } fn implied_bounds(self) -> ParsedBounds { match self.categorize() { ParsedBoundCategory::Type(v) => v.implied_bounds().into(), ParsedBoundCategory::SizeType(v) => v.implied_bounds().into(), + ParsedBoundCategory::Unknown(v) => ParsedBounds::from_iter([ParsedBound::Unknown(v)]), } } } @@ -3325,7 +3418,7 @@ impl ParsedGenerics { | ParsedTypeBound::EnumType(_) | ParsedTypeBound::IntType(_) | ParsedTypeBound::ResetType(_) => { - errors.error(bound, "bound on mask type not implemented"); + errors.error(bound, "bounds on mask types are not implemented"); } ParsedTypeBound::StaticType(bound) => { if bounds.StaticType.is_none() { @@ -3337,6 +3430,12 @@ impl ParsedGenerics { } } ParsedTypeBound::Type(_) => {} + ParsedTypeBound::Unknown(_) => { + errors.error( + bound, + "unknown bounds on mask types are not implemented", + ); + } } } bounds.add_implied_bounds(); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 6ba177b..5fe3ae8 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -72,13 +72,14 @@ mod kw { custom_keyword!(cfg); custom_keyword!(cfg_attr); custom_keyword!(clock_domain); + custom_keyword!(cmp_eq); custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); custom_keyword!(hdl); custom_keyword!(hdl_module); - custom_keyword!(input); custom_keyword!(incomplete_wire); + custom_keyword!(input); custom_keyword!(instance); custom_keyword!(m); custom_keyword!(memory); diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index f617f91..0d9b63f 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -2,8 +2,11 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::ArrayIndex, Expr, ToExpr}, - int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE}, + expr::{ + ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, + CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, intern::{Intern, Interned, LazyInterned}, module::transform::visit::{Fold, Folder, Visit, Visitor}, source_location::SourceLocation, @@ -12,7 +15,7 @@ use crate::{ }, util::ConstUsize, }; -use std::ops::Index; +use std::{iter::FusedIterator, ops::Index}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ArrayType { @@ -148,10 +151,8 @@ impl Type for ArrayType { this: Expr, source_location: SourceLocation, ) -> Self::MatchVariantsIter { - let base = Expr::as_dyn_array(this); - let base_ty = Expr::ty(base); let _ = source_location; - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(this); std::iter::once(MatchVariantWithoutScope( Len::ArrayMatch::::try_from(retval) .ok() @@ -184,9 +185,7 @@ impl Type for ArrayType { impl TypeWithDeref for ArrayType { fn expr_deref(this: &Expr) -> &Self::MatchVariant { - let base = Expr::as_dyn_array(*this); - let base_ty = Expr::ty(base); - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(*this); Interned::into_inner(Intern::intern_sized( Len::ArrayMatch::::try_from(retval) .ok() @@ -218,3 +217,131 @@ impl Index for ArrayWithoutLen { Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len))) } } + +impl ExprPartialEq> for ArrayType +where + Lhs: ExprPartialEq, +{ + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_eq(r)) + .collect::>>() + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_ne(r)) + .collect::>>() + .cast_to_bits() + .any_one_bits() + } +} + +impl ExprIntoIterator for ArrayType { + type Item = T; + type ExprIntoIter = ExprArrayIter; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter { + ExprArrayIter { + base: e, + indexes: 0..Expr::ty(e).len(), + } + } +} + +#[derive(Clone, Debug)] +pub struct ExprArrayIter { + base: Expr>, + indexes: std::ops::Range, +} + +impl ExprArrayIter { + pub fn base(&self) -> Expr> { + self.base + } + pub fn indexes(&self) -> std::ops::Range { + self.indexes.clone() + } +} + +impl Iterator for ExprArrayIter { + type Item = Expr; + + fn next(&mut self) -> Option { + self.indexes.next().map(|i| self.base[i]) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.count() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + self.indexes.nth(n).map(|i| self.base[i]) + } + + fn fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.fold(init, |b, i| f(b, self.base[i])) + } +} + +impl DoubleEndedIterator for ExprArrayIter { + fn next_back(&mut self) -> Option { + self.indexes.next_back().map(|i| self.base[i]) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.indexes.nth_back(n).map(|i| self.base[i]) + } + + fn rfold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.rfold(init, |b, i| f(b, self.base[i])) + } +} + +impl ExactSizeIterator for ExprArrayIter { + fn len(&self) -> usize { + self.indexes.len() + } +} + +impl FusedIterator for ExprArrayIter {} + +impl ExprFromIterator> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + ArrayLiteral::new( + A::TYPE, + iter.into_iter().map(|v| Expr::canonical(v)).collect(), + ) + .to_expr() + } +} + +impl<'a, A: StaticType> ExprFromIterator<&'a Expr> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + iter.into_iter().copied().collect() + } +} diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 995510e..9807b92 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -2,7 +2,11 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::BundleLiteral, Expr, ToExpr}, + expr::{ + ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, + CastToBits, Expr, ReduceBits, ToExpr, + }, + int::{Bool, DynSize}, intern::{Intern, Interned}, sim::{SimValue, ToSimValue}, source_location::SourceLocation, @@ -325,7 +329,19 @@ macro_rules! impl_tuple_builder_fields { } macro_rules! impl_tuples { - ([$({#[num = $num:literal, field = $field:ident, ty = $ty_var:ident: $Ty:ident] $var:ident: $T:ident})*] []) => { + ( + [$({ + #[ + num = $num:tt, + field = $field:ident, + ty = $ty_var:ident: $Ty:ident, + lhs = $lhs_var:ident: $Lhs:ident, + rhs = $rhs_var:ident: $Rhs:ident + ] + $var:ident: $T:ident + })*] + [] + ) => { impl_tuple_builder_fields! { {} [$({ @@ -498,6 +514,29 @@ macro_rules! impl_tuples { Self::into_sim_value(*self, ty) } } + impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) { + fn cmp_eq(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_eq($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_ne($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .any_one_bits() + } + } }; ([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => { impl_tuples!([$($lhs)*] []); @@ -507,18 +546,18 @@ macro_rules! impl_tuples { impl_tuples! { [] [ - {#[num = 0, field = field_0, ty = ty0: Ty0] v0: T0} - {#[num = 1, field = field_1, ty = ty1: Ty1] v1: T1} - {#[num = 2, field = field_2, ty = ty2: Ty2] v2: T2} - {#[num = 3, field = field_3, ty = ty3: Ty3] v3: T3} - {#[num = 4, field = field_4, ty = ty4: Ty4] v4: T4} - {#[num = 5, field = field_5, ty = ty5: Ty5] v5: T5} - {#[num = 6, field = field_6, ty = ty6: Ty6] v6: T6} - {#[num = 7, field = field_7, ty = ty7: Ty7] v7: T7} - {#[num = 8, field = field_8, ty = ty8: Ty8] v8: T8} - {#[num = 9, field = field_9, ty = ty9: Ty9] v9: T9} - {#[num = 10, field = field_10, ty = ty10: Ty10] v10: T10} - {#[num = 11, field = field_11, ty = ty11: Ty11] v11: T11} + {#[num = 0, field = field_0, ty = ty0: Ty0, lhs = lhs0: Lhs0, rhs = rhs0: Rhs0] v0: T0} + {#[num = 1, field = field_1, ty = ty1: Ty1, lhs = lhs1: Lhs1, rhs = rhs1: Rhs1] v1: T1} + {#[num = 2, field = field_2, ty = ty2: Ty2, lhs = lhs2: Lhs2, rhs = rhs2: Rhs2] v2: T2} + {#[num = 3, field = field_3, ty = ty3: Ty3, lhs = lhs3: Lhs3, rhs = rhs3: Rhs3] v3: T3} + {#[num = 4, field = field_4, ty = ty4: Ty4, lhs = lhs4: Lhs4, rhs = rhs4: Rhs4] v4: T4} + {#[num = 5, field = field_5, ty = ty5: Ty5, lhs = lhs5: Lhs5, rhs = rhs5: Rhs5] v5: T5} + {#[num = 6, field = field_6, ty = ty6: Ty6, lhs = lhs6: Lhs6, rhs = rhs6: Rhs6] v6: T6} + {#[num = 7, field = field_7, ty = ty7: Ty7, lhs = lhs7: Lhs7, rhs = rhs7: Rhs7] v7: T7} + {#[num = 8, field = field_8, ty = ty8: Ty8, lhs = lhs8: Lhs8, rhs = rhs8: Rhs8] v8: T8} + {#[num = 9, field = field_9, ty = ty9: Ty9, lhs = lhs9: Lhs9, rhs = rhs9: Rhs9] v9: T9} + {#[num = 10, field = field_10, ty = ty10: Ty10, lhs = lhs10: Lhs10, rhs = rhs10: Rhs10] v10: T10} + {#[num = 11, field = field_11, ty = ty11: Ty11, lhs = lhs11: Lhs11, rhs = rhs11: Rhs11] v11: T11} ] } diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 2ed0b8e..70c58c0 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -2,7 +2,10 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::VariantAccess, Expr, ToExpr}, + expr::{ + ops::{ExprPartialEq, VariantAccess}, + Expr, ToExpr, + }, hdl, int::Bool, intern::{Intern, Interned}, @@ -360,6 +363,60 @@ pub enum HdlOption { HdlSome(T), } +impl, Rhs: Type> ExprPartialEq> for HdlOption { + #[hdl] + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_eq = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_eq, ExprPartialEq::cmp_eq(lhs, rhs)), + HdlNone => connect(cmp_eq, false), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_eq, false), + HdlNone => connect(cmp_eq, true), + } + } + } + cmp_eq + } + + #[hdl] + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_ne = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_ne, ExprPartialEq::cmp_ne(lhs, rhs)), + HdlNone => connect(cmp_ne, true), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_ne, true), + HdlNone => connect(cmp_ne, false), + } + } + } + cmp_ne + } +} + #[allow(non_snake_case)] pub fn HdlNone() -> Expr> { HdlOption[T::TYPE].HdlNone() diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index 15c195e..c502fd5 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -2708,3 +2708,47 @@ impl ToExpr for Uninit { } } } + +pub trait ExprIntoIterator: Type { + type Item: Type; + type ExprIntoIter: Iterator>; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter; +} + +impl IntoIterator for Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(self) + } +} + +impl IntoIterator for &'_ Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +impl IntoIterator for &'_ mut Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +pub trait ExprFromIterator: Type { + fn expr_from_iter>(iter: T) -> Expr; +} + +impl, A> FromIterator for Expr { + fn from_iter>(iter: T) -> Self { + This::expr_from_iter(iter) + } +} diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index fadc7af..66fc921 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -29,4 +29,5 @@ pub use misc::{ }; pub mod job_server; +pub mod prefix_sum; pub mod ready_valid; diff --git a/crates/fayalite/src/util/prefix_sum.rs b/crates/fayalite/src/util/prefix_sum.rs new file mode 100644 index 0000000..758d89c --- /dev/null +++ b/crates/fayalite/src/util/prefix_sum.rs @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +// code derived from: +// https://web.archive.org/web/20250303054010/https://git.libre-soc.org/?p=nmutil.git;a=blob;f=src/nmutil/prefix_sum.py;hb=effeb28e5848392adddcdad1f6e7a098f2a44c9c + +use crate::intern::{Intern, Interned, Memoize}; +use std::{borrow::Cow, num::NonZeroUsize}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct PrefixSumOp { + pub lhs_index: usize, + pub rhs_and_dest_index: NonZeroUsize, + pub row: u32, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub struct DiagramConfig { + pub space: Cow<'static, str>, + pub vertical_bar: Cow<'static, str>, + pub plus: Cow<'static, str>, + pub slant: Cow<'static, str>, + pub connect: Cow<'static, str>, + pub no_connect: Cow<'static, str>, + pub padding: usize, +} + +impl DiagramConfig { + pub const fn new() -> Self { + Self { + space: Cow::Borrowed(" "), + vertical_bar: Cow::Borrowed("|"), + plus: Cow::Borrowed("\u{2295}"), // ⊕ + slant: Cow::Borrowed(r"\"), + connect: Cow::Borrowed("\u{25CF}"), // ● + no_connect: Cow::Borrowed("X"), + padding: 1, + } + } + pub fn draw(self, ops: impl IntoIterator, item_count: usize) -> String { + #[derive(Copy, Clone, Debug)] + struct DiagramCell { + slant: bool, + plus: bool, + tee: bool, + } + let mut ops_by_row: Vec> = Vec::new(); + let mut last_row = 0; + ops.into_iter().for_each(|op| { + assert!( + op.lhs_index < op.rhs_and_dest_index.get(), + "invalid PrefixSumOp! lhs_index must be less \ + than rhs_and_dest_index: {op:?}", + ); + assert!( + op.row >= last_row, + "invalid PrefixSumOp! row must \ + not decrease (row last was: {last_row}): {op:?}", + ); + let ops = if op.row > last_row || ops_by_row.is_empty() { + ops_by_row.push(vec![]); + ops_by_row.last_mut().expect("just pushed") + } else { + ops_by_row + .last_mut() + .expect("just checked if ops_by_row is empty") + }; + if let Some(last) = ops.last() { + assert!( + op.rhs_and_dest_index < last.rhs_and_dest_index, + "invalid PrefixSumOp! rhs_and_dest_index must strictly \ + decrease in a row:\nthis op: {op:?}\nlast op: {last:?}", + ); + } + ops.push(op); + last_row = op.row; + }); + let blank_row = || { + vec![ + DiagramCell { + slant: false, + plus: false, + tee: false + }; + item_count + ] + }; + let mut cells = vec![blank_row()]; + for ops in ops_by_row { + let max_distance = ops + .iter() + .map( + |&PrefixSumOp { + lhs_index, + rhs_and_dest_index, + .. + }| { rhs_and_dest_index.get() - lhs_index }, + ) + .max() + .expect("ops is known to be non-empty"); + cells.extend((0..max_distance).map(|_| blank_row())); + for op in ops { + let mut y = cells.len() - 1; + assert!( + op.rhs_and_dest_index.get() < item_count, + "invalid PrefixSumOp! rhs_and_dest_index must be \ + less than item_count ({item_count}): {op:?}", + ); + let mut x = op.rhs_and_dest_index.get(); + cells[y][x].plus = true; + x -= 1; + y -= 1; + while op.lhs_index < x { + cells[y][x].slant = true; + x -= 1; + y -= 1; + } + cells[y][x].tee = true; + } + } + let mut retval = String::new(); + let mut row_text = vec![String::new(); 2 * self.padding + 1]; + for cells_row in cells { + for cell in cells_row { + // top padding + for y in 0..self.padding { + // top left padding + for x in 0..self.padding { + row_text[y] += if x == y && (cell.plus || cell.slant) { + &self.slant + } else { + &self.space + }; + } + // top vertical bar + row_text[y] += &self.vertical_bar; + // top right padding + for _ in 0..self.padding { + row_text[y] += &self.space; + } + } + // center left padding + for _ in 0..self.padding { + row_text[self.padding] += &self.space; + } + // center + row_text[self.padding] += if cell.plus { + &self.plus + } else if cell.tee { + &self.connect + } else if cell.slant { + &self.no_connect + } else { + &self.vertical_bar + }; + // center right padding + for _ in 0..self.padding { + row_text[self.padding] += &self.space; + } + let bottom_padding_start = self.padding + 1; + let bottom_padding_last = self.padding * 2; + // bottom padding + for y in bottom_padding_start..=bottom_padding_last { + // bottom left padding + for _ in 0..self.padding { + row_text[y] += &self.space; + } + // bottom vertical bar + row_text[y] += &self.vertical_bar; + // bottom right padding + for x in bottom_padding_start..=bottom_padding_last { + row_text[y] += if x == y && (cell.tee || cell.slant) { + &self.slant + } else { + &self.space + }; + } + } + } + for line in &mut row_text { + retval += line.trim_end(); + retval += "\n"; + line.clear(); + } + } + retval + } +} + +impl Default for DiagramConfig { + fn default() -> Self { + Self::new() + } +} + +impl PrefixSumOp { + pub fn diagram(ops: impl IntoIterator, item_count: usize) -> String { + Self::diagram_with_config(ops, item_count, DiagramConfig::new()) + } + pub fn diagram_with_config( + ops: impl IntoIterator, + item_count: usize, + config: DiagramConfig, + ) -> String { + config.draw(ops, item_count) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum PrefixSumAlgorithm { + /// Uses the algorithm from: + /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_1:_Shorter_span,_more_parallel + LowLatency, + /// Uses the algorithm from: + /// https://en.wikipedia.org/wiki/Prefix_sum#Algorithm_2:_Work-efficient + WorkEfficient, +} + +impl PrefixSumAlgorithm { + fn ops_impl(self, item_count: usize) -> Vec { + let mut retval = Vec::new(); + let mut distance = 1; + let mut row = 0; + while distance < item_count { + let double_distance = distance + .checked_mul(2) + .expect("prefix-sum item_count is too big"); + let (start, step) = match self { + Self::LowLatency => (distance, 1), + Self::WorkEfficient => (double_distance - 1, double_distance), + }; + for rhs_and_dest_index in (start..item_count).step_by(step).rev() { + let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else { + unreachable!(); + }; + let lhs_index = rhs_and_dest_index.get() - distance; + retval.push(PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row, + }); + } + distance = double_distance; + row += 1; + } + match self { + Self::LowLatency => {} + Self::WorkEfficient => { + distance /= 2; + while distance >= 1 { + let start = distance + .checked_mul(3) + .expect("prefix-sum item_count is too big") + - 1; + for rhs_and_dest_index in (start..item_count).step_by(distance * 2).rev() { + let Some(rhs_and_dest_index) = NonZeroUsize::new(rhs_and_dest_index) else { + unreachable!(); + }; + let lhs_index = rhs_and_dest_index.get() - distance; + retval.push(PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row, + }); + } + row += 1; + distance /= 2; + } + } + } + retval + } + pub fn ops(self, item_count: usize) -> Interned<[PrefixSumOp]> { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct MyMemoize(PrefixSumAlgorithm); + impl Memoize for MyMemoize { + type Input = usize; + type InputOwned = usize; + type Output = Interned<[PrefixSumOp]>; + + fn inner(self, item_count: &Self::Input) -> Self::Output { + Intern::intern_owned(self.0.ops_impl(*item_count)) + } + } + MyMemoize(self).get_owned(item_count) + } + pub fn run(self, items: impl IntoIterator, f: impl FnMut(&T, &T) -> T) -> Vec { + let mut items = Vec::from_iter(items); + self.run_on_slice(&mut items, f); + items + } + pub fn run_on_slice(self, items: &mut [T], mut f: impl FnMut(&T, &T) -> T) -> &mut [T] { + self.ops(items.len()).into_iter().for_each( + |PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row: _, + }| { + items[rhs_and_dest_index.get()] = + f(&items[lhs_index], &items[rhs_and_dest_index.get()]); + }, + ); + items + } + pub fn filtered_ops( + self, + item_live_out_flags: impl IntoIterator, + ) -> Vec { + let mut item_live_out_flags = Vec::from_iter(item_live_out_flags); + let prefix_sum_ops = self.ops(item_live_out_flags.len()); + let mut ops_live_flags = vec![false; prefix_sum_ops.len()]; + for ( + op_index, + &PrefixSumOp { + lhs_index, + rhs_and_dest_index, + row: _, + }, + ) in prefix_sum_ops.iter().enumerate().rev() + { + let live = item_live_out_flags[rhs_and_dest_index.get()]; + item_live_out_flags[lhs_index] |= live; + ops_live_flags[op_index] = live; + } + prefix_sum_ops + .into_iter() + .zip(ops_live_flags) + .filter_map(|(op, live)| live.then_some(op)) + .collect() + } + pub fn reduce_ops(self, item_count: usize) -> Interned<[PrefixSumOp]> { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct MyMemoize(PrefixSumAlgorithm); + impl Memoize for MyMemoize { + type Input = usize; + type InputOwned = usize; + type Output = Interned<[PrefixSumOp]>; + + fn inner(self, item_count: &Self::Input) -> Self::Output { + let mut item_live_out_flags = vec![false; *item_count]; + let Some(last_item_live_out_flag) = item_live_out_flags.last_mut() else { + return Interned::default(); + }; + *last_item_live_out_flag = true; + Intern::intern_owned(self.0.filtered_ops(item_live_out_flags)) + } + } + MyMemoize(self).get_owned(item_count) + } +} + +pub fn reduce_ops(item_count: usize) -> Interned<[PrefixSumOp]> { + PrefixSumAlgorithm::LowLatency.reduce_ops(item_count) +} + +pub fn reduce(items: impl IntoIterator, mut f: impl FnMut(T, T) -> T) -> Option { + let mut items: Vec<_> = items.into_iter().map(Some).collect(); + for op in reduce_ops(items.len()) { + let (Some(lhs), Some(rhs)) = ( + items[op.lhs_index].take(), + items[op.rhs_and_dest_index.get()].take(), + ) else { + unreachable!(); + }; + items[op.rhs_and_dest_index.get()] = Some(f(lhs, rhs)); + } + items.last_mut().and_then(Option::take) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn input_strings() -> [String; 9] { + std::array::from_fn(|i| String::from_utf8(vec![b'a' + i as u8]).unwrap()) + } + + #[test] + fn test_prefix_sum_strings() { + let input = input_strings(); + let expected: Vec = input + .iter() + .scan(String::new(), |l, r| { + *l += r; + Some(l.clone()) + }) + .collect(); + println!("expected: {expected:?}"); + assert_eq!( + *PrefixSumAlgorithm::WorkEfficient + .run_on_slice(&mut input.clone(), |l, r| l.to_string() + r), + *expected + ); + assert_eq!( + *PrefixSumAlgorithm::LowLatency + .run_on_slice(&mut input.clone(), |l, r| l.to_string() + r), + *expected + ); + } + + #[test] + fn test_reduce_string() { + let input = input_strings(); + let expected = input.clone().into_iter().reduce(|l, r| l + &r); + assert_eq!(reduce(input, |l, r| l + &r), expected); + } + + fn op(lhs_index: usize, rhs_and_dest_index: usize, row: u32) -> PrefixSumOp { + PrefixSumOp { + lhs_index, + rhs_and_dest_index: NonZeroUsize::new(rhs_and_dest_index).expect("should be non-zero"), + row, + } + } + + #[test] + fn test_reduce_ops_9() { + let expected = vec![ + op(7, 8, 0), + op(5, 6, 0), + op(3, 4, 0), + op(1, 2, 0), + op(6, 8, 1), + op(2, 4, 1), + op(4, 8, 2), + op(0, 8, 3), + ]; + println!("expected: {expected:#?}"); + let ops = reduce_ops(9); + println!("ops: {ops:#?}"); + assert_eq!(*ops, *expected); + } + + #[test] + fn test_reduce_ops_8() { + let expected = vec![ + op(6, 7, 0), + op(4, 5, 0), + op(2, 3, 0), + op(0, 1, 0), + op(5, 7, 1), + op(1, 3, 1), + op(3, 7, 2), + ]; + println!("expected: {expected:#?}"); + let ops = reduce_ops(8); + println!("ops: {ops:#?}"); + assert_eq!(*ops, *expected); + } + + #[test] + fn test_count_ones() { + for width in 0..=10u32 { + for v in 0..1u32 << width { + let expected = v.count_ones(); + assert_eq!( + reduce((0..width).map(|i| (v >> i) & 1), |l, r| l + r).unwrap_or(0), + expected, + "v={v:#X}" + ); + } + } + } + + #[track_caller] + fn test_diagram(ops: impl IntoIterator, item_count: usize, expected: &str) { + let text = PrefixSumOp::diagram_with_config( + ops, + item_count, + DiagramConfig { + plus: Cow::Borrowed("@"), + ..Default::default() + }, + ); + println!("text:\n{text}\n"); + assert_eq!(text, expected); + } + + #[test] + fn test_work_efficient_diagram_16() { + let item_count = 16; + test_diagram( + PrefixSumAlgorithm::WorkEfficient.ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● | ● | ● | ● | ● | ● | ● | ● | + |\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ | + | \| | \| | \| | \| | \| | \| | \| | \| + | @ | @ | @ | @ | @ | @ | @ | @ + | |\ | | | |\ | | | |\ | | | |\ | | + | | \| | | | \| | | | \| | | | \| | + | | X | | | X | | | X | | | X | + | | |\ | | | |\ | | | |\ | | | |\ | + | | | \| | | | \| | | | \| | | | \| + | | | @ | | | @ | | | @ | | | @ + | | | |\ | | | | | | | |\ | | | | + | | | | \| | | | | | | | \| | | | + | | | | X | | | | | | | X | | | + | | | | |\ | | | | | | | |\ | | | + | | | | | \| | | | | | | | \| | | + | | | | | X | | | | | | | X | | + | | | | | |\ | | | | | | | |\ | | + | | | | | | \| | | | | | | | \| | + | | | | | | X | | | | | | | X | + | | | | | | |\ | | | | | | | |\ | + | | | | | | | \| | | | | | | | \| + | | | | | | | @ | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | | | | | | | | | X | | | | + | | | | | | | | | | | |\ | | | | + | | | | | | | | | | | | \| | | | + | | | | | | | | | | | | X | | | + | | | | | | | | | | | | |\ | | | + | | | | | | | | | | | | | \| | | + | | | | | | | | | | | | | X | | + | | | | | | | | | | | | | |\ | | + | | | | | | | | | | | | | | \| | + | | | | | | | | | | | | | | X | + | | | | | | | | | | | | | | |\ | + | | | | | | | | | | | | | | | \| + | | | | | | | ● | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | ● | | | ● | | | @ | | | | + | | | |\ | | | |\ | | | |\ | | | | + | | | | \| | | | \| | | | \| | | | + | | | | X | | | X | | | X | | | + | | | | |\ | | | |\ | | | |\ | | | + | | | | | \| | | | \| | | | \| | | + | ● | ● | @ | ● | @ | ● | @ | | + | |\ | |\ | |\ | |\ | |\ | |\ | |\ | | + | | \| | \| | \| | \| | \| | \| | \| | + | | @ | @ | @ | @ | @ | @ | @ | + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_low_latency_diagram_16() { + let item_count = 16; + test_diagram( + PrefixSumAlgorithm::LowLatency.ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● | + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | \| \| \| \| \| \| \| \| \| \| \| \| \| \| \| + ● @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | + | \| \| \| \| \| \| \| \| \| \| \| \| \| \| | + | X X X X X X X X X X X X X X | + | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | | \| \| \| \| \| \| \| \| \| \| \| \| \| \| + ● ● @ @ @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | | | + | \| \| \| \| \| \| \| \| \| \| \| \| | | | + | X X X X X X X X X X X X | | | + | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | | + | | \| \| \| \| \| \| \| \| \| \| \| \| | | + | | X X X X X X X X X X X X | | + | | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | | + | | | \| \| \| \| \| \| \| \| \| \| \| \| | + | | | X X X X X X X X X X X X | + | | | |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ |\ | + | | | | \| \| \| \| \| \| \| \| \| \| \| \| + ● ● ● ● @ @ @ @ @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | | | + | \| \| \| \| \| \| \| \| | | | | | | | + | X X X X X X X X | | | | | | | + | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | | + | | \| \| \| \| \| \| \| \| | | | | | | + | | X X X X X X X X | | | | | | + | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | | + | | | \| \| \| \| \| \| \| \| | | | | | + | | | X X X X X X X X | | | | | + | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | | + | | | | \| \| \| \| \| \| \| \| | | | | + | | | | X X X X X X X X | | | | + | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | | + | | | | | \| \| \| \| \| \| \| \| | | | + | | | | | X X X X X X X X | | | + | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | | + | | | | | | \| \| \| \| \| \| \| \| | | + | | | | | | X X X X X X X X | | + | | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | | + | | | | | | | \| \| \| \| \| \| \| \| | + | | | | | | | X X X X X X X X | + | | | | | | | |\ |\ |\ |\ |\ |\ |\ |\ | + | | | | | | | | \| \| \| \| \| \| \| \| + | | | | | | | | @ @ @ @ @ @ @ @ + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_work_efficient_diagram_9() { + let item_count = 9; + test_diagram( + PrefixSumAlgorithm::WorkEfficient.ops(item_count), + item_count, + &r" + | | | | | | | | | + ● | ● | ● | ● | | + |\ | |\ | |\ | |\ | | + | \| | \| | \| | \| | + | @ | @ | @ | @ | + | |\ | | | |\ | | | + | | \| | | | \| | | + | | X | | | X | | + | | |\ | | | |\ | | + | | | \| | | | \| | + | | | @ | | | @ | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | ● | | | @ | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | ● | ● | @ | ● | + | |\ | |\ | |\ | |\ | + | | \| | \| | \| | \| + | | @ | @ | @ | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_low_latency_diagram_9() { + let item_count = 9; + test_diagram( + PrefixSumAlgorithm::LowLatency.ops(item_count), + item_count, + &r" + | | | | | | | | | + ● ● ● ● ● ● ● ● | + |\ |\ |\ |\ |\ |\ |\ |\ | + | \| \| \| \| \| \| \| \| + ● @ @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ |\ |\ | | + | \| \| \| \| \| \| \| | + | X X X X X X X | + | |\ |\ |\ |\ |\ |\ |\ | + | | \| \| \| \| \| \| \| + ● ● @ @ @ @ @ @ @ + |\ |\ |\ |\ |\ | | | | + | \| \| \| \| \| | | | + | X X X X X | | | + | |\ |\ |\ |\ |\ | | | + | | \| \| \| \| \| | | + | | X X X X X | | + | | |\ |\ |\ |\ |\ | | + | | | \| \| \| \| \| | + | | | X X X X X | + | | | |\ |\ |\ |\ |\ | + | | | | \| \| \| \| \| + ● | | | @ @ @ @ @ + |\ | | | | | | | | + | \| | | | | | | | + | X | | | | | | | + | |\ | | | | | | | + | | \| | | | | | | + | | X | | | | | | + | | |\ | | | | | | + | | | \| | | | | | + | | | X | | | | | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + | | | | | | | | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_reduce_diagram_16() { + let item_count = 16; + test_diagram( + reduce_ops(item_count), + item_count, + &r" + | | | | | | | | | | | | | | | | + ● | ● | ● | ● | ● | ● | ● | ● | + |\ | |\ | |\ | |\ | |\ | |\ | |\ | |\ | + | \| | \| | \| | \| | \| | \| | \| | \| + | @ | @ | @ | @ | @ | @ | @ | @ + | |\ | | | |\ | | | |\ | | | |\ | | + | | \| | | | \| | | | \| | | | \| | + | | X | | | X | | | X | | | X | + | | |\ | | | |\ | | | |\ | | | |\ | + | | | \| | | | \| | | | \| | | | \| + | | | @ | | | @ | | | @ | | | @ + | | | |\ | | | | | | | |\ | | | | + | | | | \| | | | | | | | \| | | | + | | | | X | | | | | | | X | | | + | | | | |\ | | | | | | | |\ | | | + | | | | | \| | | | | | | | \| | | + | | | | | X | | | | | | | X | | + | | | | | |\ | | | | | | | |\ | | + | | | | | | \| | | | | | | | \| | + | | | | | | X | | | | | | | X | + | | | | | | |\ | | | | | | | |\ | + | | | | | | | \| | | | | | | | \| + | | | | | | | @ | | | | | | | @ + | | | | | | | |\ | | | | | | | | + | | | | | | | | \| | | | | | | | + | | | | | | | | X | | | | | | | + | | | | | | | | |\ | | | | | | | + | | | | | | | | | \| | | | | | | + | | | | | | | | | X | | | | | | + | | | | | | | | | |\ | | | | | | + | | | | | | | | | | \| | | | | | + | | | | | | | | | | X | | | | | + | | | | | | | | | | |\ | | | | | + | | | | | | | | | | | \| | | | | + | | | | | | | | | | | X | | | | + | | | | | | | | | | | |\ | | | | + | | | | | | | | | | | | \| | | | + | | | | | | | | | | | | X | | | + | | | | | | | | | | | | |\ | | | + | | | | | | | | | | | | | \| | | + | | | | | | | | | | | | | X | | + | | | | | | | | | | | | | |\ | | + | | | | | | | | | | | | | | \| | + | | | | | | | | | | | | | | X | + | | | | | | | | | | | | | | |\ | + | | | | | | | | | | | | | | | \| + | | | | | | | | | | | | | | | @ + | | | | | | | | | | | | | | | | +"[1..], // trim newline at start + ); + } + + #[test] + fn test_reduce_diagram_9() { + let item_count = 9; + test_diagram( + reduce_ops(item_count), + item_count, + &r" + | | | | | | | | | + | ● | ● | ● | ● | + | |\ | |\ | |\ | |\ | + | | \| | \| | \| | \| + | | @ | @ | @ | @ + | | |\ | | | |\ | | + | | | \| | | | \| | + | | | X | | | X | + | | | |\ | | | |\ | + | | | | \| | | | \| + | | | | @ | | | @ + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + ● | | | | | | | @ + |\ | | | | | | | | + | \| | | | | | | | + | X | | | | | | | + | |\ | | | | | | | + | | \| | | | | | | + | | X | | | | | | + | | |\ | | | | | | + | | | \| | | | | | + | | | X | | | | | + | | | |\ | | | | | + | | | | \| | | | | + | | | | X | | | | + | | | | |\ | | | | + | | | | | \| | | | + | | | | | X | | | + | | | | | |\ | | | + | | | | | | \| | | + | | | | | | X | | + | | | | | | |\ | | + | | | | | | | \| | + | | | | | | | X | + | | | | | | | |\ | + | | | | | | | | \| + | | | | | | | | @ + | | | | | | | | | +"[1..], // trim newline at start + ); + } +} diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 49f5689..49b226a 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -191,10 +191,14 @@ circuit check_array_repeat: }; } +pub trait UnknownTrait {} + +impl UnknownTrait for T {} + #[hdl_module(outline_generated)] pub fn check_skipped_generics(v: U) where - T: StaticType, + T: StaticType + UnknownTrait, ConstUsize: KnownSize, U: std::fmt::Display, { @@ -376,18 +380,18 @@ circuit check_written_inside_both_if_else: }; } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct { pub a: T, pub b: UInt<8>, } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct2 { pub v: UInt<8>, } -#[hdl(outline_generated)] +#[hdl(outline_generated, cmp_eq)] pub struct TestStruct3 {} #[hdl_module(outline_generated)] @@ -4421,3 +4425,125 @@ circuit check_let_patterns: ", }; } + +#[hdl_module(outline_generated)] +pub fn check_struct_cmp_eq() { + #[hdl] + let tuple_lhs: (UInt<1>, SInt<1>, Bool) = m.input(); + #[hdl] + let tuple_rhs: (UInt<1>, SInt<1>, Bool) = m.input(); + #[hdl] + let tuple_cmp_eq: Bool = m.output(); + connect(tuple_cmp_eq, tuple_lhs.cmp_eq(tuple_rhs)); + #[hdl] + let tuple_cmp_ne: Bool = m.output(); + connect(tuple_cmp_ne, tuple_lhs.cmp_ne(tuple_rhs)); + + #[hdl] + let test_struct_lhs: TestStruct> = m.input(); + #[hdl] + let test_struct_rhs: TestStruct> = m.input(); + #[hdl] + let test_struct_cmp_eq: Bool = m.output(); + connect(test_struct_cmp_eq, test_struct_lhs.cmp_eq(test_struct_rhs)); + #[hdl] + let test_struct_cmp_ne: Bool = m.output(); + connect(test_struct_cmp_ne, test_struct_lhs.cmp_ne(test_struct_rhs)); + + #[hdl] + let test_struct_2_lhs: TestStruct2 = m.input(); + #[hdl] + let test_struct_2_rhs: TestStruct2 = m.input(); + #[hdl] + let test_struct_2_cmp_eq: Bool = m.output(); + connect( + test_struct_2_cmp_eq, + test_struct_2_lhs.cmp_eq(test_struct_2_rhs), + ); + #[hdl] + let test_struct_2_cmp_ne: Bool = m.output(); + connect( + test_struct_2_cmp_ne, + test_struct_2_lhs.cmp_ne(test_struct_2_rhs), + ); + + #[hdl] + let test_struct_3_lhs: TestStruct3 = m.input(); + #[hdl] + let test_struct_3_rhs: TestStruct3 = m.input(); + #[hdl] + let test_struct_3_cmp_eq: Bool = m.output(); + connect( + test_struct_3_cmp_eq, + test_struct_3_lhs.cmp_eq(test_struct_3_rhs), + ); + #[hdl] + let test_struct_3_cmp_ne: Bool = m.output(); + connect( + test_struct_3_cmp_ne, + test_struct_3_lhs.cmp_ne(test_struct_3_rhs), + ); +} + +#[test] +fn test_struct_cmp_eq() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_struct_cmp_eq(); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_struct_cmp_eq.fir": r"FIRRTL version 3.2.0 +circuit check_struct_cmp_eq: + 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_struct_cmp_eq: @[module-XXXXXXXXXX.rs 1:1] + input tuple_lhs: Ty0 @[module-XXXXXXXXXX.rs 2:1] + input tuple_rhs: Ty0 @[module-XXXXXXXXXX.rs 3:1] + output tuple_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 4:1] + output tuple_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 6:1] + input test_struct_lhs: Ty1 @[module-XXXXXXXXXX.rs 8:1] + input test_struct_rhs: Ty1 @[module-XXXXXXXXXX.rs 9:1] + output test_struct_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 10:1] + output test_struct_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 12:1] + input test_struct_2_lhs: Ty2 @[module-XXXXXXXXXX.rs 14:1] + input test_struct_2_rhs: Ty2 @[module-XXXXXXXXXX.rs 15:1] + output test_struct_2_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 16:1] + output test_struct_2_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 18:1] + input test_struct_3_lhs: Ty3 @[module-XXXXXXXXXX.rs 20:1] + input test_struct_3_rhs: Ty3 @[module-XXXXXXXXXX.rs 21:1] + output test_struct_3_cmp_eq: UInt<1> @[module-XXXXXXXXXX.rs 22:1] + output test_struct_3_cmp_ne: UInt<1> @[module-XXXXXXXXXX.rs 24:1] + wire _array_literal_expr: UInt<1>[3] + connect _array_literal_expr[0], eq(tuple_lhs.`0`, tuple_rhs.`0`) + connect _array_literal_expr[1], eq(tuple_lhs.`1`, tuple_rhs.`1`) + connect _array_literal_expr[2], eq(tuple_lhs.`2`, tuple_rhs.`2`) + wire _cast_array_to_bits_expr: UInt<1>[3] + connect _cast_array_to_bits_expr[0], _array_literal_expr[0] + connect _cast_array_to_bits_expr[1], _array_literal_expr[1] + connect _cast_array_to_bits_expr[2], _array_literal_expr[2] + wire _cast_to_bits_expr: UInt<3> + connect _cast_to_bits_expr, cat(_cast_array_to_bits_expr[2], cat(_cast_array_to_bits_expr[1], _cast_array_to_bits_expr[0])) + connect tuple_cmp_eq, andr(_cast_to_bits_expr) @[module-XXXXXXXXXX.rs 5:1] + wire _array_literal_expr_1: UInt<1>[3] + connect _array_literal_expr_1[0], neq(tuple_lhs.`0`, tuple_rhs.`0`) + connect _array_literal_expr_1[1], neq(tuple_lhs.`1`, tuple_rhs.`1`) + connect _array_literal_expr_1[2], neq(tuple_lhs.`2`, tuple_rhs.`2`) + wire _cast_array_to_bits_expr_1: UInt<1>[3] + connect _cast_array_to_bits_expr_1[0], _array_literal_expr_1[0] + connect _cast_array_to_bits_expr_1[1], _array_literal_expr_1[1] + connect _cast_array_to_bits_expr_1[2], _array_literal_expr_1[2] + wire _cast_to_bits_expr_1: UInt<3> + connect _cast_to_bits_expr_1, cat(_cast_array_to_bits_expr_1[2], cat(_cast_array_to_bits_expr_1[1], _cast_array_to_bits_expr_1[0])) + connect tuple_cmp_ne, orr(_cast_to_bits_expr_1) @[module-XXXXXXXXXX.rs 7:1] + connect test_struct_cmp_eq, and(eq(test_struct_lhs.a, test_struct_rhs.a), eq(test_struct_lhs.b, test_struct_rhs.b)) @[module-XXXXXXXXXX.rs 11:1] + connect test_struct_cmp_ne, or(neq(test_struct_lhs.a, test_struct_rhs.a), neq(test_struct_lhs.b, test_struct_rhs.b)) @[module-XXXXXXXXXX.rs 13:1] + connect test_struct_2_cmp_eq, eq(test_struct_2_lhs.v, test_struct_2_rhs.v) @[module-XXXXXXXXXX.rs 17:1] + connect test_struct_2_cmp_ne, neq(test_struct_2_lhs.v, test_struct_2_rhs.v) @[module-XXXXXXXXXX.rs 19:1] + connect test_struct_3_cmp_eq, UInt<1>(0h1) @[module-XXXXXXXXXX.rs 23:1] + connect test_struct_3_cmp_ne, UInt<1>(0h0) @[module-XXXXXXXXXX.rs 25:1] +", + }; +}