Compare commits

...

9 commits

28 changed files with 1909 additions and 70 deletions

View file

@ -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

View file

@ -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

View file

@ -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<Self> for #target #type_generics
#where_clause
{
fn cmp_eq(
__lhs: ::fayalite::expr::Expr<Self>,
__rhs: ::fayalite::expr::Expr<Self>,
) -> ::fayalite::expr::Expr<::fayalite::int::Bool> {
#cmp_eq_body
}
fn cmp_ne(
__lhs: ::fayalite::expr::Expr<Self>,
__rhs: ::fayalite::expr::Expr<Self>,
) -> ::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) =

View file

@ -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();

View file

@ -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();

View file

@ -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<known_items::$Variant> 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<Self, Path> {
#![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<Self, syn::TypeParamBound> {
#![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> {
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<known_items::$Variant>,)*
$($vis $Unknown: Vec<syn::TypeParamBound>,)?
}
impl ToTokens for $struct_type {
@ -2126,42 +2159,63 @@ macro_rules! impl_bounds {
separator = Some(<Token![+]>::default());
v.to_tokens(tokens);
})*
$(for v in &self.$Unknown {
separator.to_tokens(tokens);
separator = Some(<Token![+]>::default());
v.to_tokens(tokens);
})*
}
}
const _: () = {
#[derive(Clone, Debug)]
$vis struct Iter($vis $struct_type);
#[allow(non_snake_case)]
$vis struct Iter {
$($Variant: Option<known_items::$Variant>,)*
$($Unknown: std::vec::IntoIter<syn::TypeParamBound>,)?
}
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<Self::Item> {
$(
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, F: FnMut(B, Self::Item) -> 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<ParsedTypeBound> 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<ParsedTypeBounds> for ParsedBounds {
ResetType,
StaticType,
Type,
Unknown,
} = value;
Self {
BoolOrIntType,
@ -2295,6 +2359,7 @@ impl From<ParsedTypeBounds> 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<ParsedSizeTypeBounds> 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();

View file

@ -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);

View file

@ -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<T: Type = CanonicalType, Len: Size = DynSize> {
@ -148,10 +151,8 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
this: Expr<Self>,
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::<T>::try_from(retval)
.ok()
@ -184,9 +185,7 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
fn expr_deref(this: &Expr<Self>) -> &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::<T>::try_from(retval)
.ok()
@ -218,3 +217,131 @@ impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
}
}
impl<Lhs: Type, Rhs: Type, Len: Size> ExprPartialEq<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
where
Lhs: ExprPartialEq<Rhs>,
{
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
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::<Expr<Array<Bool>>>()
.cast_to_bits()
.all_one_bits()
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
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::<Expr<Array<Bool>>>()
.cast_to_bits()
.any_one_bits()
}
}
impl<T: Type, Len: Size> ExprIntoIterator for ArrayType<T, Len> {
type Item = T;
type ExprIntoIter = ExprArrayIter<T, Len>;
fn expr_into_iter(e: Expr<Self>) -> Self::ExprIntoIter {
ExprArrayIter {
base: e,
indexes: 0..Expr::ty(e).len(),
}
}
}
#[derive(Clone, Debug)]
pub struct ExprArrayIter<T: Type, Len: Size> {
base: Expr<ArrayType<T, Len>>,
indexes: std::ops::Range<usize>,
}
impl<T: Type, Len: Size> ExprArrayIter<T, Len> {
pub fn base(&self) -> Expr<ArrayType<T, Len>> {
self.base
}
pub fn indexes(&self) -> std::ops::Range<usize> {
self.indexes.clone()
}
}
impl<T: Type, Len: Size> Iterator for ExprArrayIter<T, Len> {
type Item = Expr<T>;
fn next(&mut self) -> Option<Self::Item> {
self.indexes.next().map(|i| self.base[i])
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
fn count(self) -> usize {
self.indexes.count()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth(n).map(|i| self.base[i])
}
fn fold<B, F>(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<T: Type, Len: Size> DoubleEndedIterator for ExprArrayIter<T, Len> {
fn next_back(&mut self) -> Option<Self::Item> {
self.indexes.next_back().map(|i| self.base[i])
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth_back(n).map(|i| self.base[i])
}
fn rfold<B, F>(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<T: Type, Len: Size> ExactSizeIterator for ExprArrayIter<T, Len> {
fn len(&self) -> usize {
self.indexes.len()
}
}
impl<T: Type, Len: Size> FusedIterator for ExprArrayIter<T, Len> {}
impl<A: StaticType> ExprFromIterator<Expr<A>> for Array<A> {
fn expr_from_iter<T: IntoIterator<Item = Expr<A>>>(iter: T) -> Expr<Self> {
ArrayLiteral::new(
A::TYPE,
iter.into_iter().map(|v| Expr::canonical(v)).collect(),
)
.to_expr()
}
}
impl<'a, A: StaticType> ExprFromIterator<&'a Expr<A>> for Array<A> {
fn expr_from_iter<T: IntoIterator<Item = &'a Expr<A>>>(iter: T) -> Expr<Self> {
iter.into_iter().copied().collect()
}
}

View file

@ -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<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
let ($($lhs_var,)*) = *lhs;
let ($($rhs_var,)*) = *rhs;
ArrayLiteral::<Bool, DynSize>::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<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
let ($($lhs_var,)*) = *lhs;
let ($($rhs_var,)*) = *rhs;
ArrayLiteral::<Bool, DynSize>::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}
]
}

View file

@ -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<T: Type> {
HdlSome(T),
}
impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> for HdlOption<Lhs> {
#[hdl]
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[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<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[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<T: StaticType>() -> Expr<HdlOption<T>> {
HdlOption[T::TYPE].HdlNone()

View file

@ -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<UIntValue>),
SIntLiteral(Interned<SIntValue>),
BoolLiteral(bool),
PhantomConst(PhantomConst),
BundleLiteral(ops::BundleLiteral),
ArrayLiteral(ops::ArrayLiteral<CanonicalType, DynSize>),
EnumLiteral(ops::EnumLiteral),
@ -755,3 +757,27 @@ pub fn repeat<T: Type, L: SizeType>(
)
.to_expr()
}
impl<T: ?Sized + crate::phantom_const::PhantomConstValue> ToExpr for PhantomConst<T> {
type Type = Self;
fn to_expr(&self) -> Expr<Self::Type> {
Expr {
__enum: ExprEnum::PhantomConst(self.canonical_phantom_const()).intern_sized(),
__ty: *self,
__flow: Flow::Source,
}
}
}
impl<T: ?Sized + crate::phantom_const::PhantomConstValue> GetTarget for PhantomConst<T> {
fn target(&self) -> Option<Interned<Target>> {
None
}
}
impl<T: ?Sized + crate::phantom_const::PhantomConstValue> ToLiteralBits for PhantomConst<T> {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Ok(Interned::default())
}
}

View file

@ -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<Clock> for Clock {
}
}
impl<T: ?Sized + PhantomConstValue> ExprCastTo<()> for PhantomConst<T> {
fn cast_to(src: Expr<Self>, to_type: ()) -> Expr<()> {
src.cast_to_bits().cast_bits_to(to_type)
}
}
impl<T: ?Sized + PhantomConstValue> ExprCastTo<PhantomConst<T>> for () {
fn cast_to(src: Expr<Self>, to_type: PhantomConst<T>) -> Expr<PhantomConst<T>> {
src.cast_to_bits().cast_bits_to(to_type)
}
}
impl<T: ?Sized + PhantomConstValue, U: ?Sized + PhantomConstValue> ExprCastTo<PhantomConst<T>>
for PhantomConst<U>
{
fn cast_to(src: Expr<Self>, to_type: PhantomConst<T>) -> Expr<PhantomConst<T>> {
src.cast_to_bits().cast_bits_to(to_type)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct FieldAccess<FieldType: Type = CanonicalType> {
base: Expr<Bundle>,
@ -2708,3 +2729,47 @@ impl<T: Type> ToExpr for Uninit<T> {
}
}
}
pub trait ExprIntoIterator: Type {
type Item: Type;
type ExprIntoIter: Iterator<Item = Expr<Self::Item>>;
fn expr_into_iter(e: Expr<Self>) -> Self::ExprIntoIter;
}
impl<T: ExprIntoIterator> IntoIterator for Expr<T> {
type Item = Expr<T::Item>;
type IntoIter = T::ExprIntoIter;
fn into_iter(self) -> Self::IntoIter {
T::expr_into_iter(self)
}
}
impl<T: ExprIntoIterator> IntoIterator for &'_ Expr<T> {
type Item = Expr<T::Item>;
type IntoIter = T::ExprIntoIter;
fn into_iter(self) -> Self::IntoIter {
T::expr_into_iter(*self)
}
}
impl<T: ExprIntoIterator> IntoIterator for &'_ mut Expr<T> {
type Item = Expr<T::Item>;
type IntoIter = T::ExprIntoIter;
fn into_iter(self) -> Self::IntoIter {
T::expr_into_iter(*self)
}
}
pub trait ExprFromIterator<A>: Type {
fn expr_from_iter<T: IntoIterator<Item = A>>(iter: T) -> Expr<Self>;
}
impl<This: ExprFromIterator<A>, A> FromIterator<A> for Expr<This> {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
This::expr_from_iter(iter)
}
}

View file

@ -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<T: Type>(
@ -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)
}

View file

@ -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;

View file

@ -1082,6 +1082,7 @@ pub fn splat_mask<T: Type>(ty: T, value: Expr<Bool>) -> Expr<AsMask<T>> {
)
.to_expr(),
)),
CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())),
}
}

View file

@ -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| {

View file

@ -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<P: Pass, T: Type, A: Type>(
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<P: Pass, T: Type, A: Type>(
| 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<P: Pass> RunPass<P> 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 {
(

View file

@ -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),
}
}

View file

@ -62,6 +62,7 @@ enum MemSplit {
Bundle {
fields: Rc<[MemSplit]>,
},
PhantomConst,
Single {
output_mem: Option<Mem>,
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());

View file

@ -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,

View file

@ -0,0 +1,274 @@
// 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<str>,
}
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<str> {
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<H: Hasher>(&self, state: &mut H) {
self.serialized.hash(state);
}
}
impl Serialize for PhantomConstCanonicalValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.parsed.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PhantomConstCanonicalValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
where
D: serde::Deserializer<'de>;
}
impl<T> PhantomConstValue for T
where
T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug,
Interned<T>: DeserializeOwned,
{
fn deserialize_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
where
D: serde::Deserializer<'de>,
{
<Interned<T> 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<T: ?Sized + PhantomConstValue = PhantomConstCanonicalValue> {
value: LazyInterned<T>,
}
impl<T: ?Sized + PhantomConstValue> fmt::Debug for PhantomConst<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PhantomConst").field(&self.get()).finish()
}
}
impl<T: ?Sized + PhantomConstValue> Clone for PhantomConst<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized + PhantomConstValue> Copy for PhantomConst<T> {}
impl<T: ?Sized + PhantomConstValue> PartialEq for PhantomConst<T> {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
impl<T: ?Sized + PhantomConstValue> Eq for PhantomConst<T> {}
impl<T: ?Sized + PhantomConstValue> Hash for PhantomConst<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}
struct PhantomConstCanonicalMemoize<T: ?Sized, const IS_FROM_CANONICAL: bool>(PhantomData<T>);
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Copy
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Clone
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Eq
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> PartialEq
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Hash
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, false> {
type Input = Interned<T>;
type InputOwned = Interned<T>;
type Output = Interned<PhantomConstCanonicalValue>;
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<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, true> {
type Input = Interned<PhantomConstCanonicalValue>;
type InputOwned = Interned<PhantomConstCanonicalValue>;
type Output = Interned<T>;
fn inner(self, input: &Self::Input) -> Self::Output {
PhantomConstValue::deserialize_value(input.as_json_value())
.expect("deserialization failed ")
}
}
impl<T: ?Sized + PhantomConstValue> PhantomConst<T>
where
Interned<T>: Default,
{
pub const fn default() -> Self {
PhantomConst {
value: LazyInterned::new_lazy(&Interned::<T>::default),
}
}
}
impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
pub fn new(value: Interned<T>) -> Self {
Self {
value: LazyInterned::Interned(value),
}
}
pub fn get(self) -> Interned<T> {
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) = <dyn Any>::downcast_ref::<PhantomConst>(&self) {
return retval;
}
<PhantomConst>::new(
PhantomConstCanonicalMemoize::<T, false>(PhantomData).get_owned(self.get()),
)
}
pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self {
if let Some(&retval) = <dyn Any>::downcast_ref::<Self>(&canonical_type) {
return retval;
}
Self::new(
PhantomConstCanonicalMemoize::<T, true>(PhantomData).get_owned(canonical_type.get()),
)
}
}
impl<T: ?Sized + PhantomConstValue> Type for PhantomConst<T> {
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<T: ?Sized + PhantomConstValue> StaticType for PhantomConst<T>
where
Interned<T>: Default,
{
const TYPE: Self = Self::default();
const MASK_TYPE: Self::MaskType = ();
const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
}

View file

@ -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,

View file

@ -167,6 +167,14 @@ impl<T: Type> CompiledTypeLayout<T> {
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::<Bundle>::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> {
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<CanonicalType> 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(

View file

@ -9,8 +9,10 @@ use crate::{
expr::Expr,
int::{Bool, SInt, UInt},
intern::{Intern, Interned},
phantom_const::PhantomConst,
reset::{AsyncReset, Reset, SyncReset},
source_location::SourceLocation,
util::ConstUsize,
};
use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index};
@ -35,6 +37,7 @@ pub enum CanonicalType {
SyncReset(SyncReset),
Reset(Reset),
Clock(Clock),
PhantomConst(PhantomConst),
}
impl fmt::Debug for CanonicalType {
@ -50,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),
}
}
}
@ -67,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 {
@ -143,6 +148,12 @@ impl CanonicalType {
};
lhs.can_connect(rhs)
}
CanonicalType::PhantomConst(lhs) => {
let CanonicalType::PhantomConst(rhs) = rhs else {
return false;
};
lhs.can_connect(rhs)
}
}
}
}
@ -166,7 +177,7 @@ impl<T: 'static + Send + Sync> MatchVariantAndInactiveScope for MatchVariantWith
}
pub trait FillInDefaultedGenerics {
type Type: Type;
type Type;
fn fill_in_defaulted_generics(self) -> Self::Type;
}
@ -178,6 +189,22 @@ impl<T: Type> FillInDefaultedGenerics for T {
}
}
impl FillInDefaultedGenerics for usize {
type Type = usize;
fn fill_in_defaulted_generics(self) -> Self::Type {
self
}
}
impl<const V: usize> FillInDefaultedGenerics for ConstUsize<V> {
type Type = ConstUsize<V>;
fn fill_in_defaulted_generics(self) -> Self::Type {
self
}
}
mod sealed {
pub trait TypeOrDefaultSealed {}
pub trait BaseTypeSealed {}
@ -205,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 {}
@ -299,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 {

View file

@ -29,4 +29,5 @@ pub use misc::{
};
pub mod job_server;
pub mod prefix_sum;
pub mod ready_valid;

View file

@ -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 = PrefixSumOp>, item_count: usize) -> String {
#[derive(Copy, Clone, Debug)]
struct DiagramCell {
slant: bool,
plus: bool,
tee: bool,
}
let mut ops_by_row: Vec<Vec<PrefixSumOp>> = 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 = Self>, item_count: usize) -> String {
Self::diagram_with_config(ops, item_count, DiagramConfig::new())
}
pub fn diagram_with_config(
ops: impl IntoIterator<Item = Self>,
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<PrefixSumOp> {
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<T>(self, items: impl IntoIterator<Item = T>, f: impl FnMut(&T, &T) -> T) -> Vec<T> {
let mut items = Vec::from_iter(items);
self.run_on_slice(&mut items, f);
items
}
pub fn run_on_slice<T>(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<Item = bool>,
) -> Vec<PrefixSumOp> {
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<T>(items: impl IntoIterator<Item = T>, mut f: impl FnMut(T, T) -> T) -> Option<T> {
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<String> = 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 = PrefixSumOp>, 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
);
}
}

View file

@ -191,10 +191,14 @@ circuit check_array_repeat:
};
}
pub trait UnknownTrait {}
impl<T: ?Sized> UnknownTrait for T {}
#[hdl_module(outline_generated)]
pub fn check_skipped_generics<T, #[hdl(skip)] U, const N: usize, #[hdl(skip)] const M: usize>(v: U)
where
T: StaticType,
T: StaticType + UnknownTrait,
ConstUsize<N>: 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<T> {
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<SInt<8>> = m.input();
#[hdl]
let test_struct_rhs: TestStruct<SInt<8>> = 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]
",
};
}

View file

@ -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": "<T: ?Sized + crate::phantom_const::PhantomConstValue>"
}
}
}