From 3afab89c2a9d98e494ead2ea76c15b2abe977f0d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 4 Apr 2025 19:05:04 -0700 Subject: [PATCH] add UIntInRange[Inclusive][Type] --- crates/fayalite/src/int.rs | 67 +++- crates/fayalite/src/int/uint_in_range.rs | 477 +++++++++++++++++++++++ crates/fayalite/src/phantom_const.rs | 7 +- 3 files changed, 536 insertions(+), 15 deletions(-) create mode 100644 crates/fayalite/src/int/uint_in_range.rs diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 13b8cf1..c60d4d2 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -7,6 +7,7 @@ use crate::{ target::{GetTarget, Target}, Expr, NotALiteralExpr, ToExpr, ToLiteralBits, }, + hdl, intern::{Intern, Interned, Memoize}, sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, @@ -30,6 +31,23 @@ use std::{ sync::Arc, }; +mod uint_in_range; + +#[hdl] +pub type UIntInRangeType = uint_in_range::UIntInRangeType; + +#[hdl] +pub type UIntInRange = + UIntInRangeType, ConstUsize>; + +#[hdl] +pub type UIntInRangeInclusiveType = + uint_in_range::UIntInRangeInclusiveType; + +#[hdl] +pub type UIntInRangeInclusive = + UIntInRangeInclusiveType, ConstUsize>; + mod sealed { pub trait BoolOrIntTypeSealed {} pub trait SizeSealed {} @@ -536,19 +554,14 @@ macro_rules! impl_int { pub const $name: $generic_name = $generic_name; impl $name { - pub fn new(width: Width::SizeType) -> Self { + pub const fn new(width: Width::SizeType) -> Self { Self { width } } pub fn width(self) -> usize { Width::as_usize(self.width) } pub fn type_properties(self) -> TypeProperties { - TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: self.width(), - } + self.as_dyn_int().type_properties_dyn() } pub fn bits_from_bigint_wrapping(self, v: &BigInt) -> BitVec { BoolOrIntType::bits_from_bigint_wrapping(self, v) @@ -624,12 +637,20 @@ macro_rules! impl_int { } impl $name { - pub fn new_dyn(width: usize) -> Self { + pub const fn new_dyn(width: usize) -> Self { Self { width } } pub fn bits_to_bigint(bits: &BitSlice) -> BigInt { ::bits_to_bigint(bits) } + pub const fn type_properties_dyn(self) -> TypeProperties { + TypeProperties { + is_passive: true, + is_storable: true, + is_castable_from_bits: true, + bit_width: self.width, + } + } } impl $name { @@ -686,12 +707,10 @@ macro_rules! impl_int { impl StaticType for $name { const TYPE: Self = Self { width: Width::SIZE }; const MASK_TYPE: Self::MaskType = Bool; - const TYPE_PROPERTIES: TypeProperties = TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: Width::VALUE, - }; + const TYPE_PROPERTIES: TypeProperties = $name { + width: Width::VALUE, + } + .type_properties_dyn(); const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } @@ -905,6 +924,10 @@ impl UInt { let v: BigUint = v.into(); Self::new(v.bits().try_into().expect("too big")) } + /// gets the smallest `UInt` that fits `v` losslessly + pub const fn for_value_usize(v: usize) -> Self { + Self::new(v.next_power_of_two().ilog2() as usize) + } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] pub fn range(r: Range>) -> Self { @@ -915,6 +938,12 @@ impl UInt { } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] + pub const fn range_usize(r: Range) -> Self { + assert!(r.end != 0, "empty range"); + Self::range_inclusive_usize(r.start..=(r.end - 1)) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] pub fn range_inclusive(r: RangeInclusive>) -> Self { let (start, end) = r.into_inner(); let start: BigUint = start.into(); @@ -924,6 +953,16 @@ impl UInt { // so must not take more bits than `end` Self::for_value(end) } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub const fn range_inclusive_usize(r: RangeInclusive) -> Self { + let start = *r.start(); + let end = *r.end(); + assert!(start <= end, "empty range"); + // no need to check `start`` since it's no larger than `end` + // so must not take more bits than `end` + Self::for_value_usize(end) + } } impl SInt { diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs new file mode 100644 index 0000000..5c9254c --- /dev/null +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder}, + expr::{ + ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, + CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, + intern::{Intern, Interned}, + phantom_const::PhantomConst, + sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, + source_location::SourceLocation, + ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, +}; +use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; +use std::{fmt, ops::Index}; + +const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"]; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct UIntInRangeMaskType { + value: Bool, + range: PhantomConstRangeMaskType, +} + +impl Type for UIntInRangeMaskType { + type BaseType = Bundle; + type MaskType = Self; + type SimValue = bool; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + *self + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected UIntInRangeMaskType"); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = Bool::from_canonical(value); + let range = PhantomConstRangeMaskType::from_canonical(range); + Self { value, range } + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + Bool.sim_value_from_bits(bits) + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + Bool.sim_value_clone_from_bits(value, bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + Bool.sim_value_to_bits(value, bits); + } +} + +impl BundleType for UIntInRangeMaskType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } +} + +impl StaticType for UIntInRangeMaskType { + const TYPE: Self = Self { + value: Bool, + range: PhantomConstRangeMaskType::TYPE, + }; + const MASK_TYPE: Self::MaskType = Self::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Bool::TYPE_PROPERTIES) + .field(false, PhantomConstRangeMaskType::TYPE_PROPERTIES) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = Self::TYPE_PROPERTIES; +} + +impl ToSimValueWithType for bool { + fn to_sim_value_with_type(&self, ty: UIntInRangeMaskType) -> SimValue { + SimValue::from_value(ty, *self) + } +} + +impl ExprCastTo for UIntInRangeMaskType { + fn cast_to(src: Expr, to_type: Bool) -> Expr { + src.cast_to_bits().cast_to(to_type) + } +} + +impl ExprCastTo for Bool { + fn cast_to(src: Expr, to_type: UIntInRangeMaskType) -> Expr { + src.cast_to_static::>().cast_bits_to(to_type) + } +} + +impl ExprPartialEq for UIntInRangeMaskType { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } +} + +impl SimValuePartialEq for UIntInRangeMaskType { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + **this == **other + } +} + +type PhantomConstRange = + PhantomConst<(::SizeType, ::SizeType)>; +type PhantomConstRangeMaskType = ::MaskType; + +macro_rules! define_uint_in_range_type { + ( + $UIntInRangeType:ident, + $UIntInRangeTypeWithoutGenerics:ident, + $UIntInRangeTypeWithStart:ident, + |$uint_range_usize_start:ident, $uint_range_usize_end:ident| $uint_range_usize:expr, + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + pub struct $UIntInRangeType { + value: UInt, + range: PhantomConstRange, + } + + impl $UIntInRangeType { + fn from_phantom_const_range(range: PhantomConstRange) -> Self { + let (start, end) = *range.get(); + let $uint_range_usize_start = Start::as_usize(start); + let $uint_range_usize_end = End::as_usize(end); + Self { + value: $uint_range_usize, + range, + } + } + pub fn new(start: Start::SizeType, end: End::SizeType) -> Self { + Self::from_phantom_const_range(PhantomConst::new((start, end).intern_sized())) + } + } + + impl fmt::Debug for $UIntInRangeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { value, range } = self; + let (start, end) = *range.get(); + f.debug_struct(&format!( + "UIntInRange<{}, {}>", + Start::as_usize(start), + End::as_usize(end), + )) + .field("value", value) + .finish_non_exhaustive() + } + } + + impl Type for $UIntInRangeType { + type BaseType = Bundle; + type MaskType = UIntInRangeMaskType; + type SimValue = usize; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + UIntInRangeMaskType::TYPE + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected UIntInRange"); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = UInt::from_canonical(value); + let range = PhantomConstRange::::from_canonical(range); + let retval = Self::from_phantom_const_range(range); + assert_eq!(retval, Self { value, range }); + retval + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + let mut retval = 0usize; + retval.view_bits_mut::()[..bits.len()].clone_from_bitslice(bits); + retval + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + *value = self.sim_value_from_bits(bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + bits.clone_from_bitslice(&value.view_bits::()[..bits.len()]); + } + } + + impl BundleType for $UIntInRangeType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } + } + + impl Default for $UIntInRangeType { + fn default() -> Self { + Self::TYPE + } + } + + impl StaticType for $UIntInRangeType { + const TYPE: Self = { + let $uint_range_usize_start = Start::VALUE; + let $uint_range_usize_end = End::VALUE; + Self { + value: $uint_range_usize, + range: PhantomConstRange::::TYPE, + } + }; + const MASK_TYPE: Self::MaskType = UIntInRangeMaskType::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Self::TYPE.value.type_properties_dyn()) + .field(false, PhantomConstRange::::TYPE_PROPERTIES) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = UIntInRangeMaskType::TYPE_PROPERTIES; + } + + impl ToSimValueWithType<$UIntInRangeType> for usize { + fn to_sim_value_with_type( + &self, + ty: $UIntInRangeType, + ) -> SimValue<$UIntInRangeType> { + SimValue::from_value(ty, *self) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] + pub struct $UIntInRangeTypeWithoutGenerics; + + #[allow(non_upper_case_globals)] + pub const $UIntInRangeType: $UIntInRangeTypeWithoutGenerics = + $UIntInRangeTypeWithoutGenerics; + + impl Index for $UIntInRangeTypeWithoutGenerics { + type Output = $UIntInRangeTypeWithStart; + + fn index(&self, start: StartSize) -> &Self::Output { + Interned::into_inner($UIntInRangeTypeWithStart(start).intern_sized()) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct $UIntInRangeTypeWithStart(Start::SizeType); + + impl, End: Size> + Index for $UIntInRangeTypeWithStart + { + type Output = $UIntInRangeType; + + fn index(&self, end: EndSize) -> &Self::Output { + Interned::into_inner($UIntInRangeType::new(self.0, end).intern_sized()) + } + } + + impl ExprCastTo for $UIntInRangeType { + fn cast_to(src: Expr, to_type: UInt) -> Expr { + src.cast_to_bits().cast_to(to_type) + } + } + + impl ExprCastTo<$UIntInRangeType> for UInt { + fn cast_to( + src: Expr, + to_type: $UIntInRangeType, + ) -> Expr<$UIntInRangeType> { + src.cast_bits_to(to_type) + } + } + + impl + ExprPartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_eq( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } + } + + impl + ExprPartialOrd<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_lt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs.cast_to_bits()) + } + } + + impl + SimValuePartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn sim_value_eq( + this: &SimValue, + other: &SimValue<$UIntInRangeType>, + ) -> bool { + **this == **other + } + } + + impl ExprPartialEq> + for $UIntInRangeType + { + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs) + } + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs) + } + } + + impl ExprPartialEq<$UIntInRangeType> + for UIntType + { + fn cmp_eq(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ne(rhs.cast_to_bits()) + } + } + + impl ExprPartialOrd> + for $UIntInRangeType + { + fn cmp_lt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs) + } + fn cmp_le(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_le(rhs) + } + fn cmp_gt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs) + } + fn cmp_ge(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs) + } + } + + impl ExprPartialOrd<$UIntInRangeType> + for UIntType + { + fn cmp_lt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ge(rhs.cast_to_bits()) + } + } + }; +} + +define_uint_in_range_type! { + UIntInRangeType, + UIntInRangeTypeWithoutGenerics, + UIntInRangeTypeWithStart, + |start, end| UInt::range_usize(start..end), +} + +define_uint_in_range_type! { + UIntInRangeInclusiveType, + UIntInRangeInclusiveTypeWithoutGenerics, + UIntInRangeInclusiveTypeWithStart, + |start, end| UInt::range_inclusive_usize(start..=end), +} diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index d2e94d9..c481692 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -7,7 +7,7 @@ use crate::{ Expr, ToExpr, }, int::Bool, - intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, + intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -228,6 +228,11 @@ impl PhantomConst { value: LazyInterned::Interned(value), } } + pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { + Self { + value: LazyInterned::new_lazy(v), + } + } pub fn get(self) -> Interned { self.value.interned() }