From 3458c21f442652713f2f531f02d747d43550562c Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 16 Feb 2025 20:48:16 -0800 Subject: [PATCH 01/10] add #[hdl(cmp_eq)] to implement HdlPartialEq automatically --- .../src/hdl_bundle.rs | 65 +++++++++ .../fayalite-proc-macros-impl/src/hdl_enum.rs | 5 + .../src/hdl_type_alias.rs | 5 + .../src/hdl_type_common.rs | 1 + crates/fayalite-proc-macros-impl/src/lib.rs | 1 + crates/fayalite/src/array.rs | 40 +++++- crates/fayalite/src/bundle.rs | 67 +++++++-- crates/fayalite/src/enum_.rs | 59 +++++++- crates/fayalite/tests/module.rs | 128 +++++++++++++++++- 9 files changed, 351 insertions(+), 20 deletions(-) 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 3b2e1ec..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), } } diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index cbd5f4a..5fe3ae8 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -72,6 +72,7 @@ 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); diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index f617f91..647b2e2 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::{ArrayIndex, ArrayLiteral, 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, @@ -218,3 +221,36 @@ 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()); + ArrayLiteral::::new( + Bool, + (0..lhs_ty.len()) + .map(|i| Expr::canonical(lhs[i].cmp_eq(rhs[i]))) + .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()); + ArrayLiteral::::new( + Bool, + (0..lhs_ty.len()) + .map(|i| Expr::canonical(lhs[i].cmp_ne(rhs[i]))) + .collect(), + ) + .cast_to_bits() + .any_one_bits() + } +} 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/tests/module.rs b/crates/fayalite/tests/module.rs index 2f93fa5..49b226a 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -380,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)] @@ -4425,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] +", + }; +} From 60734cc9d170a87496c69a532a029a9ea23e48b8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 17:43:29 -0800 Subject: [PATCH 02/10] switch CI to use mirrors --- .forgejo/workflows/deps.yml | 12 ++++++------ .forgejo/workflows/test.yml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) 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 From 50c86e18dc2fda0622b471ddf55a4d28f1471eeb Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 16:11:05 -0800 Subject: [PATCH 03/10] add Expr>: IntoIterator and Expr>: FromIterator --- crates/fayalite/src/array.rs | 139 ++++++++++++++++++++++++++------ crates/fayalite/src/expr/ops.rs | 44 ++++++++++ 2 files changed, 159 insertions(+), 24 deletions(-) diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index 647b2e2..0d9b63f 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -3,7 +3,7 @@ use crate::{ expr::{ - ops::{ArrayIndex, ArrayLiteral, ExprPartialEq}, + ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, }, int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, @@ -15,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 { @@ -151,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() @@ -187,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() @@ -230,27 +226,122 @@ where let lhs_ty = Expr::ty(lhs); let rhs_ty = Expr::ty(rhs); assert_eq!(lhs_ty.len(), rhs_ty.len()); - ArrayLiteral::::new( - Bool, - (0..lhs_ty.len()) - .map(|i| Expr::canonical(lhs[i].cmp_eq(rhs[i]))) - .collect(), - ) - .cast_to_bits() - .all_one_bits() + 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()); - ArrayLiteral::::new( - Bool, - (0..lhs_ty.len()) - .map(|i| Expr::canonical(lhs[i].cmp_ne(rhs[i]))) - .collect(), - ) - .cast_to_bits() - .any_one_bits() + 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/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) + } +} From bd75fdfefd642f6dd2210cfb003fa63f9dce114e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 2 Mar 2025 23:04:17 -0800 Subject: [PATCH 04/10] add efficient prefix-sums and reductions --- crates/fayalite/src/util.rs | 1 + crates/fayalite/src/util/prefix_sum.rs | 839 +++++++++++++++++++++++++ 2 files changed, 840 insertions(+) create mode 100644 crates/fayalite/src/util/prefix_sum.rs 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 + ); + } +} From 2fa0ea61920590dcc331d9740a25720e025b9d66 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 20:59:21 -0700 Subject: [PATCH 05/10] make FillInDefaultedGenerics work with `Size`s and not just `Type`s --- crates/fayalite/src/ty.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 69080c9..55c7e8f 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -11,6 +11,7 @@ use crate::{ intern::{Intern, Interned}, reset::{AsyncReset, Reset, SyncReset}, source_location::SourceLocation, + util::ConstUsize, }; use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index}; @@ -166,7 +167,7 @@ impl MatchVariantAndInactiveScope for MatchVariantWith } pub trait FillInDefaultedGenerics { - type Type: Type; + type Type; fn fill_in_defaulted_generics(self) -> Self::Type; } @@ -178,6 +179,22 @@ impl FillInDefaultedGenerics for T { } } +impl FillInDefaultedGenerics for usize { + type Type = usize; + + fn fill_in_defaulted_generics(self) -> Self::Type { + self + } +} + +impl FillInDefaultedGenerics for ConstUsize { + type Type = ConstUsize; + + fn fill_in_defaulted_generics(self) -> Self::Type { + self + } +} + mod sealed { pub trait TypeOrDefaultSealed {} pub trait BaseTypeSealed {} From c0c5b550bc1ca2ddbb2917084211cf420091df75 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 21:03:47 -0700 Subject: [PATCH 06/10] add PhantomConst --- crates/fayalite/src/expr.rs | 26 ++ crates/fayalite/src/expr/ops.rs | 25 +- crates/fayalite/src/firrtl.rs | 15 +- crates/fayalite/src/lib.rs | 1 + crates/fayalite/src/memory.rs | 1 + crates/fayalite/src/module.rs | 3 + .../src/module/transform/deduce_resets.rs | 13 +- .../src/module/transform/simplify_enums.rs | 13 +- .../src/module/transform/simplify_memories.rs | 14 +- crates/fayalite/src/module/transform/visit.rs | 1 + crates/fayalite/src/phantom_const.rs | 273 ++++++++++++++++++ crates/fayalite/src/prelude.rs | 1 + crates/fayalite/src/sim.rs | 36 ++- crates/fayalite/src/ty.rs | 12 + crates/fayalite/visit_types.json | 9 +- 15 files changed, 428 insertions(+), 15 deletions(-) create mode 100644 crates/fayalite/src/phantom_const.rs diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index f0008f4..016ec8e 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -16,6 +16,7 @@ use crate::{ transform::visit::{Fold, Folder, Visit, Visitor}, Instance, ModuleIO, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, ty::{CanonicalType, StaticType, Type, TypeWithDeref}, @@ -109,6 +110,7 @@ expr_enum! { UIntLiteral(Interned), SIntLiteral(Interned), BoolLiteral(bool), + PhantomConst(PhantomConst), BundleLiteral(ops::BundleLiteral), ArrayLiteral(ops::ArrayLiteral), EnumLiteral(ops::EnumLiteral), @@ -755,3 +757,27 @@ pub fn repeat( ) .to_expr() } + +impl ToExpr for PhantomConst { + type Type = Self; + + fn to_expr(&self) -> Expr { + Expr { + __enum: ExprEnum::PhantomConst(self.canonical_phantom_const()).intern_sized(), + __ty: *self, + __flow: Flow::Source, + } + } +} + +impl GetTarget for PhantomConst { + fn target(&self) -> Option> { + None + } +} + +impl ToLiteralBits for PhantomConst { + fn to_literal_bits(&self) -> Result, NotALiteralExpr> { + Ok(Interned::default()) + } +} diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index c502fd5..e794a68 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -11,14 +11,15 @@ use crate::{ GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - CastTo, Expr, ExprEnum, Flow, HdlPartialEq, HdlPartialOrd, NotALiteralExpr, ReduceBits, - ToExpr, ToLiteralBits, + CastBitsTo as _, CastTo, CastToBits as _, Expr, ExprEnum, Flow, HdlPartialEq, + HdlPartialOrd, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits, }, int::{ Bool, BoolOrIntType, DynSize, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue, }, intern::{Intern, Interned}, + phantom_const::{PhantomConst, PhantomConstValue}, reset::{ AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset, ToAsyncReset, ToReset, ToSyncReset, @@ -1892,6 +1893,26 @@ impl ExprCastTo for Clock { } } +impl ExprCastTo<()> for PhantomConst { + fn cast_to(src: Expr, to_type: ()) -> Expr<()> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> for () { + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> + for PhantomConst +{ + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct FieldAccess { base: Expr, diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index ea76cf8..dd5fc2e 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -15,7 +15,7 @@ use crate::{ target::{ Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - Expr, ExprEnum, + CastBitsTo, Expr, ExprEnum, }, formal::FormalKind, int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue}, @@ -447,6 +447,7 @@ impl TypeState { CanonicalType::AsyncReset(AsyncReset {}) => "AsyncReset".into(), CanonicalType::SyncReset(SyncReset {}) => "UInt<1>".into(), CanonicalType::Reset(Reset {}) => "Reset".into(), + CanonicalType::PhantomConst(_) => "{}".into(), } } } @@ -1152,6 +1153,7 @@ impl<'a> Exporter<'a> { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::Reset(_) => format!("asUInt({value_str})"), + CanonicalType::PhantomConst(_) => "UInt<0>(0)".into(), } } fn expr_cast_bits_to_bundle( @@ -1357,6 +1359,12 @@ impl<'a> Exporter<'a> { CanonicalType::AsyncReset(_) => format!("asAsyncReset({value_str})"), CanonicalType::SyncReset(_) => value_str, CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"), + CanonicalType::PhantomConst(_) => { + let retval = self.module.ns.make_new("_cast_bits_to_phantom_const_expr"); + definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {{}}")); + definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); + return retval.to_string(); + } } } fn expr_unary( @@ -1395,6 +1403,11 @@ impl<'a> Exporter<'a> { ExprEnum::UIntLiteral(literal) => self.uint_literal(&literal), ExprEnum::SIntLiteral(literal) => self.sint_literal(&literal), ExprEnum::BoolLiteral(literal) => self.bool_literal(literal), + ExprEnum::PhantomConst(ty) => self.expr( + UInt[0].zero().cast_bits_to(ty.canonical()), + definitions, + const_ty, + ), ExprEnum::ArrayLiteral(array_literal) => { self.array_literal_expr(array_literal, definitions, const_ty) } diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 88fe169..512572d 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -96,6 +96,7 @@ pub mod int; pub mod intern; pub mod memory; pub mod module; +pub mod phantom_const; pub mod prelude; pub mod reg; pub mod reset; diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index 2f0ec47..1101157 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -1082,6 +1082,7 @@ pub fn splat_mask(ty: T, value: Expr) -> Expr> { ) .to_expr(), )), + CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())), } } diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 5a18ac9..446746a 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -1490,6 +1490,9 @@ impl TargetState { }) .collect(), }, + CanonicalType::PhantomConst(_) => TargetStateInner::Decomposed { + subtargets: HashMap::new(), + }, CanonicalType::Array(ty) => TargetStateInner::Decomposed { subtargets: (0..ty.len()) .map(|index| { diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index fe518a5..a70dc33 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -155,6 +155,7 @@ impl ResetsLayout { CanonicalType::SyncReset(_) => ResetsLayout::SyncReset, CanonicalType::Reset(_) => ResetsLayout::Reset, CanonicalType::Clock(_) => ResetsLayout::NoResets, + CanonicalType::PhantomConst(_) => ResetsLayout::NoResets, } } } @@ -407,7 +408,8 @@ impl Resets { | CanonicalType::Bool(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Clock(_) => Ok(self.ty), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => Ok(self.ty), CanonicalType::Array(ty) => Ok(CanonicalType::Array(Array::new_dyn( self.array_elements().substituted_type( reset_graph, @@ -998,7 +1000,8 @@ fn cast_bit_op( CanonicalType::Array(_) | CanonicalType::Enum(_) | CanonicalType::Bundle(_) - | CanonicalType::Reset(_) => unreachable!(), + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => unreachable!(), $(CanonicalType::$Variant(ty) => Expr::expr_enum($arg.cast_to(ty)),)* } }; @@ -1010,6 +1013,7 @@ fn cast_bit_op( | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) => unreachable!(), + CanonicalType::PhantomConst(_) => Expr::expr_enum(arg), $(CanonicalType::$Variant(_) => { let arg = Expr::<$Variant>::from_canonical(arg); match_expr_ty!(arg, UInt, SInt, Bool, AsyncReset, SyncReset, Clock) @@ -1040,6 +1044,7 @@ impl RunPass

for ExprEnum { ExprEnum::UIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::SIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::BoolLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::PhantomConst(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::BundleLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::ArrayLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), ExprEnum::EnumLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), @@ -1670,7 +1675,8 @@ impl RunPassDispatch for AnyReg { | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => unreachable!(), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), } }) } @@ -1769,6 +1775,7 @@ impl_run_pass_copy!([] SVAttributeAnnotation); impl_run_pass_copy!([] UInt); impl_run_pass_copy!([] usize); impl_run_pass_copy!([] FormalKind); +impl_run_pass_copy!([] PhantomConst); macro_rules! impl_run_pass_for_struct { ( diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index 4eb0d0c..e8b6168 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -69,7 +69,8 @@ fn contains_any_enum_types(ty: CanonicalType) -> bool { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => false, + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => false, } } } @@ -512,7 +513,8 @@ impl State { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => unreachable!(), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), } } } @@ -577,7 +579,8 @@ fn connect_port( | (CanonicalType::Clock(_), _) | (CanonicalType::AsyncReset(_), _) | (CanonicalType::SyncReset(_), _) - | (CanonicalType::Reset(_), _) => unreachable!( + | (CanonicalType::Reset(_), _) + | (CanonicalType::PhantomConst(_), _) => unreachable!( "trying to connect memory ports:\n{:?}\n{:?}", Expr::ty(lhs), Expr::ty(rhs), @@ -665,6 +668,7 @@ impl Folder for State { ExprEnum::UIntLiteral(_) | ExprEnum::SIntLiteral(_) | ExprEnum::BoolLiteral(_) + | ExprEnum::PhantomConst(_) | ExprEnum::BundleLiteral(_) | ExprEnum::ArrayLiteral(_) | ExprEnum::Uninit(_) @@ -923,7 +927,8 @@ impl Folder for State { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) => canonical_type.default_fold(self), + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => canonical_type.default_fold(self), } } diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index e8f9cbf..101385e 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -62,6 +62,7 @@ enum MemSplit { Bundle { fields: Rc<[MemSplit]>, }, + PhantomConst, Single { output_mem: Option, element_type: SingleType, @@ -76,6 +77,7 @@ impl MemSplit { fn mark_changed_element_type(self) -> Self { match self { MemSplit::Bundle { fields: _ } => self, + MemSplit::PhantomConst => self, MemSplit::Single { output_mem, element_type, @@ -97,6 +99,7 @@ impl MemSplit { .map(|field| Self::new(field.ty).mark_changed_element_type()) .collect(), }, + CanonicalType::PhantomConst(_) => MemSplit::PhantomConst, CanonicalType::Array(ty) => { let element = MemSplit::new(ty.element()); if let Self::Single { @@ -339,6 +342,7 @@ impl SplitMemState<'_, '_> { self.split_state_stack.pop(); } } + MemSplit::PhantomConst => {} MemSplit::Single { output_mem, element_type: single_type, @@ -538,7 +542,12 @@ impl ModuleState { }; loop { match input_element_type { - CanonicalType::Bundle(_) => unreachable!("bundle types are always split"), + CanonicalType::Bundle(_) => { + unreachable!("bundle types are always split") + } + CanonicalType::PhantomConst(_) => { + unreachable!("PhantomConst are always removed") + } CanonicalType::Enum(_) if input_array_types .first() @@ -743,7 +752,8 @@ impl ModuleState { .. } | MemSplit::Bundle { .. } - | MemSplit::Array { .. } => { + | MemSplit::Array { .. } + | MemSplit::PhantomConst => { let mut replacement_ports = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len()); diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 97de4fc..662a578 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -28,6 +28,7 @@ use crate::{ NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs new file mode 100644 index 0000000..81f5d6f --- /dev/null +++ b/crates/fayalite/src/phantom_const.rs @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::{ + intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, + source_location::SourceLocation, + ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, +}; +use std::{ + any::Any, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +#[derive(Clone)] +pub struct PhantomConstCanonicalValue { + parsed: serde_json::Value, + serialized: Interned, +} + +impl PhantomConstCanonicalValue { + pub fn from_json_value(parsed: serde_json::Value) -> Self { + let serialized = Intern::intern_owned( + serde_json::to_string(&parsed) + .expect("conversion from json value to text shouldn't fail"), + ); + Self { parsed, serialized } + } + pub fn as_json_value(&self) -> &serde_json::Value { + &self.parsed + } + pub fn as_str(&self) -> Interned { + self.serialized + } +} + +impl fmt::Debug for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl fmt::Display for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl PartialEq for PhantomConstCanonicalValue { + fn eq(&self, other: &Self) -> bool { + self.serialized == other.serialized + } +} + +impl Eq for PhantomConstCanonicalValue {} + +impl Hash for PhantomConstCanonicalValue { + fn hash(&self, state: &mut H) { + self.serialized.hash(state); + } +} + +impl Serialize for PhantomConstCanonicalValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.parsed.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PhantomConstCanonicalValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_json_value(serde_json::Value::deserialize( + deserializer, + )?)) + } +} + +pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug { + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>; +} + +impl PhantomConstValue for T +where + T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug, + Interned: DeserializeOwned, +{ + fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + as Deserialize<'de>>::deserialize(deserializer) + } +} + +/// Wrapper type that allows any Rust value to be smuggled as a HDL [`Type`]. +/// This only works for values that can be [serialized][Serialize] to and [deserialized][Deserialize] from [JSON][serde_json]. +pub struct PhantomConst { + value: LazyInterned, +} + +impl fmt::Debug for PhantomConst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PhantomConst").field(&self.get()).finish() + } +} + +impl Clone for PhantomConst { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for PhantomConst {} + +impl PartialEq for PhantomConst { + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +impl Eq for PhantomConst {} + +impl Hash for PhantomConst { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +struct PhantomConstCanonicalMemoize(PhantomData); + +impl Copy + for PhantomConstCanonicalMemoize +{ +} + +impl Clone + for PhantomConstCanonicalMemoize +{ + fn clone(&self) -> Self { + *self + } +} + +impl Eq + for PhantomConstCanonicalMemoize +{ +} + +impl PartialEq + for PhantomConstCanonicalMemoize +{ + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Hash + for PhantomConstCanonicalMemoize +{ + fn hash(&self, _state: &mut H) {} +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + Intern::intern_sized(PhantomConstCanonicalValue::from_json_value( + serde_json::to_value(input) + .expect("serialization failed when constructing a canonical PhantomConst"), + )) + } +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + PhantomConstValue::deserialize(input.as_json_value()).expect("deserialization failed ") + } +} + +impl PhantomConst +where + Interned: Default, +{ + pub const fn default() -> Self { + PhantomConst { + value: LazyInterned::new_lazy(&Interned::::default), + } + } +} + +impl PhantomConst { + pub fn new(value: Interned) -> Self { + Self { + value: LazyInterned::Interned(value), + } + } + pub fn get(self) -> Interned { + self.value.interned() + } + pub fn type_properties(self) -> TypeProperties { + <()>::TYPE_PROPERTIES + } + pub fn can_connect(self, other: Self) -> bool { + self == other + } + pub fn canonical_phantom_const(self) -> PhantomConst { + if let Some(&retval) = ::downcast_ref::(&self) { + return retval; + } + ::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(self.get()), + ) + } + pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self { + if let Some(&retval) = ::downcast_ref::(&canonical_type) { + return retval; + } + Self::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(canonical_type.get()), + ) + } +} + +impl Type for PhantomConst { + type BaseType = PhantomConst; + type MaskType = (); + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + () + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::PhantomConst(self.canonical_phantom_const()) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let CanonicalType::PhantomConst(phantom_const) = canonical_type else { + panic!("expected PhantomConst"); + }; + Self::from_canonical_phantom_const(phantom_const) + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } +} + +impl StaticType for PhantomConst +where + Interned: Default, +{ + const TYPE: Self = Self::default(); + const MASK_TYPE: Self::MaskType = (); + const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; + const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 9e7a85e..39fa143 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -26,6 +26,7 @@ pub use crate::{ annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, source_location::SourceLocation, diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index f630f5a..96f6dd9 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -167,6 +167,14 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Array { element }, } } + CanonicalType::PhantomConst(_) => { + let unit_layout = CompiledTypeLayout::get(()); + CompiledTypeLayout { + ty: *input, + layout: unit_layout.layout, + body: unit_layout.body, + } + } CanonicalType::Bundle(bundle) => { let mut layout = TypeLayout::empty(); let fields = bundle @@ -1792,7 +1800,7 @@ impl Compiler { } .into() } - CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), CanonicalType::AsyncReset(_) => TraceAsyncReset { location: self.make_trace_scalar_helper( instantiated_module, @@ -2009,6 +2017,13 @@ impl Compiler { | CanonicalType::Clock(_) => { self.make_trace_scalar(instantiated_module, target, name, source_location) } + CanonicalType::PhantomConst(_) => TraceBundle { + name, + fields: Interned::default(), + ty: Bundle::new(Interned::default()), + flow: target.flow(), + } + .into(), } } fn make_trace_decl( @@ -2469,6 +2484,9 @@ impl Compiler { Expr::field(Expr::::from_canonical(expr.arg()), &field.name) }), ), + CanonicalType::PhantomConst(_) => { + self.compile_cast_aggregate_to_bits(instantiated_module, []) + } } } fn compile_cast_bits_to( @@ -2518,6 +2536,10 @@ impl Compiler { CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), CanonicalType::Reset(_) => unreachable!(), CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::PhantomConst(ty) => { + let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); + Expr::canonical(ty.to_expr()) + } }; let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) @@ -2567,6 +2589,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; let dest_signed = match Expr::ty(expr) { CanonicalType::UInt(_) => false, @@ -2579,6 +2602,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { match (src_signed, dest_signed) { @@ -2634,6 +2658,9 @@ impl Compiler { }] }) .into(), + ExprEnum::PhantomConst(_) => self + .compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default()) + .into(), ExprEnum::BundleLiteral(literal) => self .compile_aggregate_literal( instantiated_module, @@ -3537,6 +3564,7 @@ impl Compiler { CanonicalType::SyncReset(_) => unreachable!(), CanonicalType::Reset(_) => unreachable!(), CanonicalType::Clock(_) => unreachable!(), + CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), } } let Some(target) = lhs.target() else { @@ -3901,6 +3929,7 @@ impl Compiler { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; let width = data_layout.ty.bit_width(); if let Some(MemoryPortReadInsns { @@ -5909,6 +5938,7 @@ impl SimValue { CanonicalType::Clock(ty) => { Expr::canonical(Bool::bits_to_expr(Cow::Borrowed(bits)).cast_to(ty)) } + CanonicalType::PhantomConst(ty) => Expr::canonical(ty.to_expr()), } } } @@ -6312,7 +6342,8 @@ impl ToSimValue for bool { | CanonicalType::SInt(_) | CanonicalType::Array(_) | CanonicalType::Enum(_) - | CanonicalType::Bundle(_) => { + | CanonicalType::Bundle(_) + | CanonicalType::PhantomConst(_) => { panic!("can't create SimValue from bool: expected value of type: {ty:?}"); } CanonicalType::Bool(_) @@ -6977,6 +7008,7 @@ impl SimulationImpl { CanonicalType::SyncReset(_) => false, CanonicalType::Reset(_) => false, CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), }; match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => read_write_small_scalar( diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index 55c7e8f..2786782 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -9,6 +9,7 @@ use crate::{ expr::Expr, int::{Bool, SInt, UInt}, intern::{Intern, Interned}, + phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, source_location::SourceLocation, util::ConstUsize, @@ -36,6 +37,7 @@ pub enum CanonicalType { SyncReset(SyncReset), Reset(Reset), Clock(Clock), + PhantomConst(PhantomConst), } impl fmt::Debug for CanonicalType { @@ -51,6 +53,7 @@ impl fmt::Debug for CanonicalType { Self::SyncReset(v) => v.fmt(f), Self::Reset(v) => v.fmt(f), Self::Clock(v) => v.fmt(f), + Self::PhantomConst(v) => v.fmt(f), } } } @@ -68,6 +71,7 @@ impl CanonicalType { CanonicalType::SyncReset(v) => v.type_properties(), CanonicalType::Reset(v) => v.type_properties(), CanonicalType::Clock(v) => v.type_properties(), + CanonicalType::PhantomConst(v) => v.type_properties(), } } pub fn is_passive(self) -> bool { @@ -144,6 +148,12 @@ impl CanonicalType { }; lhs.can_connect(rhs) } + CanonicalType::PhantomConst(lhs) => { + let CanonicalType::PhantomConst(rhs) = rhs else { + return false; + }; + lhs.can_connect(rhs) + } } } } @@ -222,6 +232,7 @@ impl_base_type!(AsyncReset); impl_base_type!(SyncReset); impl_base_type!(Reset); impl_base_type!(Clock); +impl_base_type!(PhantomConst); impl sealed::BaseTypeSealed for CanonicalType {} @@ -316,6 +327,7 @@ impl Type for CanonicalType { CanonicalType::SyncReset(v) => v.mask_type().canonical(), CanonicalType::Reset(v) => v.mask_type().canonical(), CanonicalType::Clock(v) => v.mask_type().canonical(), + CanonicalType::PhantomConst(v) => v.mask_type().canonical(), } } fn canonical(&self) -> CanonicalType { diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index 3eff1f5..b284372 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -49,7 +49,8 @@ "AsyncReset": "Visible", "SyncReset": "Visible", "Reset": "Visible", - "Clock": "Visible" + "Clock": "Visible", + "PhantomConst": "Visible" } }, "Bundle": { @@ -1262,6 +1263,12 @@ "ArrayElement": "Visible", "DynArrayElement": "Visible" } + }, + "PhantomConst": { + "data": { + "$kind": "Opaque" + }, + "generics": "" } } } \ No newline at end of file From 450e1004b6eef6fcdce74a94e3bded3e0268610d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 9 Mar 2025 23:14:14 -0700 Subject: [PATCH 07/10] fix using fayalite as a dependency --- crates/fayalite/src/phantom_const.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 81f5d6f..b8f3f09 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -84,7 +84,7 @@ impl<'de> Deserialize<'de> for PhantomConstCanonicalValue { } pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug { - fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>; } @@ -94,7 +94,7 @@ where T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug, Interned: DeserializeOwned, { - fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -189,7 +189,8 @@ impl Memoize for PhantomConstCanonicalMemoize; fn inner(self, input: &Self::Input) -> Self::Output { - PhantomConstValue::deserialize(input.as_json_value()).expect("deserialization failed ") + PhantomConstValue::deserialize_value(input.as_json_value()) + .expect("deserialization failed ") } } From d453755bb2cd0b6f2340f3e49058d29a2ee279e8 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 10 Mar 2025 19:40:03 -0700 Subject: [PATCH 08/10] add ExprPartialEq/ExprPartialOrd impls for PhantomConst --- crates/fayalite/src/phantom_const.rs | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index b8f3f09..dd6cff6 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -4,6 +4,11 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ + expr::{ + ops::{ExprPartialEq, ExprPartialOrd}, + Expr, ToExpr, + }, + int::Bool, intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, @@ -272,3 +277,37 @@ where const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; } + +impl ExprPartialEq for PhantomConst { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } +} + +impl ExprPartialOrd for PhantomConst { + fn cmp_lt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_le(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_gt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_ge(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } +} From 920d8d875f80b970a996b4d45f06b145a07fba84 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 19 Mar 2025 17:10:51 -0700 Subject: [PATCH 09/10] add some missing #[track_caller] --- crates/fayalite/src/module.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 446746a..d26dc7b 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -2174,6 +2174,7 @@ impl ModuleBuilder { .builder_extern_body() .verilog_name = name.intern(); } + #[track_caller] pub fn parameter(&self, name: impl AsRef, value: ExternModuleParameterValue) { let name = name.as_ref(); self.impl_ @@ -2186,6 +2187,7 @@ impl ModuleBuilder { value, }); } + #[track_caller] pub fn parameter_int(&self, name: impl AsRef, value: impl Into) { let name = name.as_ref(); let value = value.into(); @@ -2199,6 +2201,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::Integer(value), }); } + #[track_caller] pub fn parameter_str(&self, name: impl AsRef, value: impl AsRef) { let name = name.as_ref(); let value = value.as_ref(); @@ -2212,6 +2215,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::String(value.intern()), }); } + #[track_caller] pub fn parameter_raw_verilog(&self, name: impl AsRef, raw_verilog: impl AsRef) { let name = name.as_ref(); let raw_verilog = raw_verilog.as_ref(); From 6e0016b370e89738442876d0289798aa51e66c4e Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 19 Mar 2025 17:11:41 -0700 Subject: [PATCH 10/10] start adding ExternModuleSimulation --- crates/fayalite/src/firrtl.rs | 1 + crates/fayalite/src/module.rs | 25 +++- crates/fayalite/src/module/transform/visit.rs | 1 + crates/fayalite/src/sim.rs | 119 +++++++++++++++++- crates/fayalite/visit_types.json | 9 +- 5 files changed, 150 insertions(+), 5 deletions(-) diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index dd5fc2e..d082187 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -2258,6 +2258,7 @@ impl<'a> Exporter<'a> { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation: _, }) => { let verilog_name = Ident(verilog_name); writeln!(body, "{indent}defname = {verilog_name}").unwrap(); diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index d26dc7b..87f86cc 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -21,6 +21,7 @@ use crate::{ memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, + sim::{ExternModuleSimGenerator, ExternModuleSimulation}, source_location::SourceLocation, ty::{CanonicalType, Type}, util::ScopedRef, @@ -1081,6 +1082,7 @@ pub struct ExternModuleBody< > { pub verilog_name: Interned, pub parameters: P, + pub simulation: Option>, } impl From>> for ExternModuleBody { @@ -1088,11 +1090,13 @@ impl From>> for ExternModuleBody { let ExternModuleBody { verilog_name, parameters, + simulation, } = value; let parameters = Intern::intern_owned(parameters); Self { verilog_name, parameters, + simulation, } } } @@ -1283,10 +1287,12 @@ impl fmt::Debug for DebugModuleBody { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation, }) => { debug_struct .field("verilog_name", verilog_name) - .field("parameters", parameters); + .field("parameters", parameters) + .field("simulation", simulation); } } debug_struct.finish_non_exhaustive() @@ -1761,7 +1767,12 @@ impl AssertValidityState { ModuleBody::Extern(ExternModuleBody { verilog_name: _, parameters: _, - }) => {} + simulation, + }) => { + if let Some(simulation) = simulation { + simulation.check_io_ty(self.module.io_ty); + } + } ModuleBody::Normal(NormalModuleBody { body }) => { let body = self.make_block_index(body); assert_eq!(body, 0); @@ -2108,6 +2119,7 @@ impl ModuleBuilder { ModuleKind::Extern => ModuleBody::Extern(ExternModuleBody { verilog_name: name.0, parameters: vec![], + simulation: None, }), ModuleKind::Normal => ModuleBody::Normal(NormalModuleBody { body: BuilderModuleBody { @@ -2229,6 +2241,15 @@ impl ModuleBuilder { value: ExternModuleParameterValue::RawVerilog(raw_verilog.intern()), }); } + #[track_caller] + pub fn extern_module_simulation(&self, generator: G) { + let mut impl_ = self.impl_.borrow_mut(); + let simulation = &mut impl_.body.builder_extern_body().simulation; + if simulation.is_some() { + panic!("already added an extern module simulation"); + } + *simulation = Some(ExternModuleSimulation::new(generator)); + } } #[track_caller] diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 662a578..526a62c 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -31,6 +31,7 @@ use crate::{ phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, ResetType, SyncReset}, + sim::ExternModuleSimulation, source_location::SourceLocation, ty::{CanonicalType, Type}, wire::Wire, diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 96f6dd9..29a8b67 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -15,7 +15,9 @@ use crate::{ ExprEnum, Flow, ToLiteralBits, }, int::{BoolOrIntType, IntType, SIntValue, UIntValue}, - intern::{Intern, Interned, Memoize}, + intern::{ + Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, memory::PortKind, module::{ transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, Id, InstantiatedModule, @@ -51,7 +53,15 @@ use petgraph::{ }, }; use std::{ - borrow::Cow, collections::BTreeSet, fmt, marker::PhantomData, mem, ops::IndexMut, sync::Arc, + any::Any, + borrow::Cow, + collections::BTreeSet, + fmt, + future::{Future, IntoFuture}, + marker::PhantomData, + mem, + ops::IndexMut, + sync::Arc, }; mod interpreter; @@ -7386,3 +7396,108 @@ impl Simulation { }); } } + +#[derive(Debug)] +pub struct ExternModuleSimulationState { + io_ty: T, +} + +impl ExternModuleSimulationState { + pub fn canonical(self) -> ExternModuleSimulationState { + ExternModuleSimulationState { + io_ty: Bundle::from_canonical(self.io_ty.canonical()), + } + } + pub fn from_canonical(sim: ExternModuleSimulationState) -> Self { + Self { + io_ty: T::from_canonical(sim.io_ty.canonical()), + } + } +} + +pub trait ExternModuleSimGenerator: + Clone + Eq + std::hash::Hash + Any + Send + Sync + fmt::Debug +{ + type IOType: BundleType; + + fn run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> impl IntoFuture + 'a; +} + +trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { + fn dyn_run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a>; + #[track_caller] + fn check_io_ty(&self, io_ty: Bundle); +} + +impl DynExternModuleSimGenerator for T { + fn dyn_run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a> { + Box::new( + self.run(ExternModuleSimulationState::from_canonical(sim)) + .into_future(), + ) + } + #[track_caller] + fn check_io_ty(&self, io_ty: Bundle) { + T::IOType::from_canonical(io_ty.canonical()); + } +} + +impl InternedCompare for dyn DynExternModuleSimGenerator { + type InternedCompareKey = PtrEqWithTypeId; + + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + this.get_ptr_eq_with_type_id() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct ExternModuleSimulation { + generator: Interned, + _phantom: PhantomData, +} + +impl ExternModuleSimulation { + pub fn new(generator: G) -> Self { + Self { + generator: Interned::cast_unchecked( + generator.intern(), + |v| -> &dyn DynExternModuleSimGenerator { v }, + ), + _phantom: PhantomData, + } + } + pub fn canonical(self) -> ExternModuleSimulation { + ExternModuleSimulation { + generator: self.generator, + _phantom: PhantomData, + } + } + pub fn from_canonical(v: ExternModuleSimulation) -> Self { + Self { + generator: v.generator, + _phantom: PhantomData, + } + } +} + +impl ExternModuleSimulation { + fn run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a> { + self.generator.dyn_run(sim) + } + #[track_caller] + pub fn check_io_ty(self, io_ty: Bundle) { + self.generator.check_io_ty(io_ty); + } +} diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index b284372..451dc90 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -160,7 +160,8 @@ "data": { "$kind": "Struct", "verilog_name": "Visible", - "parameters": "Visible" + "parameters": "Visible", + "simulation": "Visible" } }, "ExternModuleParameter": { @@ -1269,6 +1270,12 @@ "$kind": "Opaque" }, "generics": "" + }, + "ExternModuleSimulation": { + "data": { + "$kind": "Opaque" + }, + "generics": "" } } } \ No newline at end of file