forked from libre-chip/fayalite
Compare commits
10 commits
3267cb38c4
...
4b24a88641
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b24a88641 | |||
| 094c77e26e | |||
| d2c8b023bf | |||
| c043ee54d0 | |||
| edcc5927a5 | |||
| 7dc4417874 | |||
| 838bd469ce | |||
| b6e4cd0614 | |||
| 3e5b2f126a | |||
| 040cefea21 |
20 changed files with 11561 additions and 128 deletions
32
README.md
32
README.md
|
|
@ -12,7 +12,7 @@ Fayalite is a library for designing digital hardware -- a hardware description l
|
||||||
|
|
||||||
[Blinky example]: crates/fayalite/examples/blinky.rs
|
[Blinky example]: crates/fayalite/examples/blinky.rs
|
||||||
|
|
||||||
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in https://git.libre-chip.org/libre-chip/fayalite-deps
|
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in <https://git.libre-chip.org/libre-chip/fayalite-deps>
|
||||||
|
|
||||||
Steps:
|
Steps:
|
||||||
|
|
||||||
|
|
@ -45,6 +45,36 @@ To program the Flash also, so it stays programmed when power-cycling the board:
|
||||||
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
|
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Building the [Transmit-only UART example] for the Arty A7 100T on Linux
|
||||||
|
|
||||||
|
[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs
|
||||||
|
|
||||||
|
Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`.
|
||||||
|
|
||||||
|
View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`.
|
||||||
|
|
||||||
|
Find the correct USB device:
|
||||||
|
```bash
|
||||||
|
sudo tio --list
|
||||||
|
```
|
||||||
|
|
||||||
|
You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port):
|
||||||
|
`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0`
|
||||||
|
|
||||||
|
Connect to the serial port:
|
||||||
|
```bash
|
||||||
|
sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see (repeating endlessly):
|
||||||
|
```text
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
Press Ctrl+T then `q` to exit tio.
|
||||||
|
|
||||||
# Funding
|
# Funding
|
||||||
|
|
||||||
## NLnet Grants
|
## NLnet Grants
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,11 @@ impl ParsedBundle {
|
||||||
no_static: _,
|
no_static: _,
|
||||||
no_runtime_generics: _,
|
no_runtime_generics: _,
|
||||||
cmp_eq: _,
|
cmp_eq: _,
|
||||||
|
ref get,
|
||||||
} = options.body;
|
} = options.body;
|
||||||
|
if let Some((get, ..)) = get {
|
||||||
|
errors.error(get, "#[hdl(get(...))] is not allowed on structs");
|
||||||
|
}
|
||||||
let mut fields = match fields {
|
let mut fields = match fields {
|
||||||
syn::Fields::Named(fields) => fields,
|
syn::Fields::Named(fields) => fields,
|
||||||
syn::Fields::Unnamed(fields) => {
|
syn::Fields::Unnamed(fields) => {
|
||||||
|
|
@ -445,6 +449,7 @@ impl ToTokens for ParsedBundle {
|
||||||
no_static,
|
no_static,
|
||||||
no_runtime_generics,
|
no_runtime_generics,
|
||||||
cmp_eq,
|
cmp_eq,
|
||||||
|
get: _,
|
||||||
} = &options.body;
|
} = &options.body;
|
||||||
let target = get_target(target, ident);
|
let target = get_target(target, ident);
|
||||||
let mut item_attrs = attrs.clone();
|
let mut item_attrs = attrs.clone();
|
||||||
|
|
|
||||||
|
|
@ -159,10 +159,14 @@ impl ParsedEnum {
|
||||||
no_static: _,
|
no_static: _,
|
||||||
no_runtime_generics: _,
|
no_runtime_generics: _,
|
||||||
cmp_eq,
|
cmp_eq,
|
||||||
|
ref get,
|
||||||
} = options.body;
|
} = options.body;
|
||||||
if let Some((cmp_eq,)) = cmp_eq {
|
if let Some((cmp_eq,)) = cmp_eq {
|
||||||
errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums");
|
errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums");
|
||||||
}
|
}
|
||||||
|
if let Some((get, ..)) = get {
|
||||||
|
errors.error(get, "#[hdl(get(...))] is not allowed on enums");
|
||||||
|
}
|
||||||
attrs.retain(|attr| {
|
attrs.retain(|attr| {
|
||||||
if attr.path().is_ident("repr") {
|
if attr.path().is_ident("repr") {
|
||||||
errors.error(attr, "#[repr] is not supported on #[hdl] enums");
|
errors.error(attr, "#[repr] is not supported on #[hdl] enums");
|
||||||
|
|
@ -225,6 +229,7 @@ impl ToTokens for ParsedEnum {
|
||||||
no_static,
|
no_static,
|
||||||
no_runtime_generics,
|
no_runtime_generics,
|
||||||
cmp_eq: _, // TODO: implement cmp_eq for enums
|
cmp_eq: _, // TODO: implement cmp_eq for enums
|
||||||
|
get: _,
|
||||||
} = &options.body;
|
} = &options.body;
|
||||||
let target = get_target(target, ident);
|
let target = get_target(target, ident);
|
||||||
let mut struct_attrs = attrs.clone();
|
let mut struct_attrs = attrs.clone();
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,353 @@ use crate::{
|
||||||
Errors, HdlAttr,
|
Errors, HdlAttr,
|
||||||
hdl_type_common::{
|
hdl_type_common::{
|
||||||
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser,
|
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser,
|
||||||
get_target,
|
WrappedInConst, common_derives, get_target, known_items,
|
||||||
},
|
},
|
||||||
kw,
|
kw,
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::{ToTokens, format_ident, quote_spanned};
|
||||||
use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned};
|
use syn::{
|
||||||
|
AngleBracketedGenericArguments, Attribute, Expr, Fields, GenericArgument, GenericParam,
|
||||||
|
Generics, Ident, ItemStruct, ItemType, Path, PathArguments, Token, TraitBound,
|
||||||
|
TraitBoundModifier, Type, TypeGroup, TypeParam, TypeParamBound, TypeParen, Visibility,
|
||||||
|
parse_quote_spanned, punctuated::Pair, token::Paren,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct ParsedTypeAlias {
|
pub(crate) struct PhantomConstGetBound {
|
||||||
pub(crate) attrs: Vec<Attribute>,
|
pub(crate) phantom_const_get: known_items::PhantomConstGet,
|
||||||
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
|
pub(crate) colon2_token: Option<Token![::]>,
|
||||||
pub(crate) vis: Visibility,
|
pub(crate) lt_token: Token![<],
|
||||||
pub(crate) type_token: Token![type],
|
pub(crate) ty: Type,
|
||||||
pub(crate) ident: Ident,
|
pub(crate) comma_token: Option<Token![,]>,
|
||||||
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
|
pub(crate) gt_token: Token![>],
|
||||||
pub(crate) eq_token: Token![=],
|
}
|
||||||
pub(crate) ty: MaybeParsed<ParsedType, Type>,
|
|
||||||
pub(crate) semi_token: Token![;],
|
impl From<PhantomConstGetBound> for Path {
|
||||||
|
fn from(value: PhantomConstGetBound) -> Self {
|
||||||
|
let PhantomConstGetBound {
|
||||||
|
phantom_const_get,
|
||||||
|
colon2_token,
|
||||||
|
lt_token,
|
||||||
|
ty,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
} = value;
|
||||||
|
let mut path = phantom_const_get.path;
|
||||||
|
path.segments.last_mut().expect("known to exist").arguments =
|
||||||
|
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||||
|
colon2_token,
|
||||||
|
lt_token,
|
||||||
|
args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]),
|
||||||
|
gt_token,
|
||||||
|
});
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstGetBound> for TraitBound {
|
||||||
|
fn from(value: PhantomConstGetBound) -> Self {
|
||||||
|
let path = Path::from(value);
|
||||||
|
TraitBound {
|
||||||
|
paren_token: None,
|
||||||
|
modifier: TraitBoundModifier::None,
|
||||||
|
lifetimes: None,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstGetBound> for TypeParamBound {
|
||||||
|
fn from(value: PhantomConstGetBound) -> Self {
|
||||||
|
TraitBound::from(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstGetBound {
|
||||||
|
fn parse_opt(bound: TypeParamBound) -> Option<Self> {
|
||||||
|
let TypeParamBound::Trait(TraitBound {
|
||||||
|
paren_token: None,
|
||||||
|
modifier: TraitBoundModifier::None,
|
||||||
|
lifetimes: None,
|
||||||
|
path,
|
||||||
|
}) = bound
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok((
|
||||||
|
phantom_const_get,
|
||||||
|
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
|
||||||
|
colon2_token,
|
||||||
|
lt_token,
|
||||||
|
args,
|
||||||
|
gt_token,
|
||||||
|
}),
|
||||||
|
)) = known_items::PhantomConstGet::parse_path_with_arguments(path)
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let mut args = args.into_pairs();
|
||||||
|
let (GenericArgument::Type(ty), comma_token) = args.next()?.into_tuple() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let None = args.next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
phantom_const_get,
|
||||||
|
colon2_token,
|
||||||
|
lt_token,
|
||||||
|
ty,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct PhantomConstAccessorTypeParam {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
ident: Ident,
|
||||||
|
colon_token: Token![:],
|
||||||
|
phantom_const_get_bound: PhantomConstGetBound,
|
||||||
|
plus_token: Option<Token![+]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorTypeParam> for TypeParam {
|
||||||
|
fn from(value: PhantomConstAccessorTypeParam) -> Self {
|
||||||
|
let PhantomConstAccessorTypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
phantom_const_get_bound,
|
||||||
|
plus_token,
|
||||||
|
} = value;
|
||||||
|
TypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token: Some(colon_token),
|
||||||
|
bounds: FromIterator::from_iter([Pair::new(
|
||||||
|
phantom_const_get_bound.into(),
|
||||||
|
plus_token,
|
||||||
|
)]),
|
||||||
|
eq_token: None,
|
||||||
|
default: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorTypeParam> for GenericParam {
|
||||||
|
fn from(value: PhantomConstAccessorTypeParam) -> Self {
|
||||||
|
TypeParam::from(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstAccessorTypeParam {
|
||||||
|
fn parse_opt(generic_param: GenericParam) -> Option<Self> {
|
||||||
|
let GenericParam::Type(TypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
bounds,
|
||||||
|
eq_token: None,
|
||||||
|
default: None,
|
||||||
|
}) = generic_param
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let colon_token = colon_token.unwrap_or(Token));
|
||||||
|
let mut bounds = bounds.into_pairs();
|
||||||
|
let (bound, plus_token) = bounds.next()?.into_tuple();
|
||||||
|
let phantom_const_get_bound = PhantomConstGetBound::parse_opt(bound)?;
|
||||||
|
let None = bounds.next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
phantom_const_get_bound,
|
||||||
|
plus_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct PhantomConstAccessorGenerics {
|
||||||
|
lt_token: Token![<],
|
||||||
|
type_param: PhantomConstAccessorTypeParam,
|
||||||
|
comma_token: Option<Token![,]>,
|
||||||
|
gt_token: Token![>],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorGenerics> for Generics {
|
||||||
|
fn from(value: PhantomConstAccessorGenerics) -> Self {
|
||||||
|
let PhantomConstAccessorGenerics {
|
||||||
|
lt_token,
|
||||||
|
type_param,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
} = value;
|
||||||
|
Generics {
|
||||||
|
lt_token: Some(lt_token),
|
||||||
|
params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]),
|
||||||
|
gt_token: Some(gt_token),
|
||||||
|
where_clause: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics {
|
||||||
|
fn from(value: &'a PhantomConstAccessorGenerics) -> Self {
|
||||||
|
value.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstAccessorGenerics {
|
||||||
|
fn parse_opt(generics: Generics) -> Option<Self> {
|
||||||
|
let Generics {
|
||||||
|
lt_token,
|
||||||
|
params,
|
||||||
|
gt_token,
|
||||||
|
where_clause: None,
|
||||||
|
} = generics
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let mut params = params.into_pairs();
|
||||||
|
let (generic_param, comma_token) = params.next()?.into_tuple();
|
||||||
|
let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?;
|
||||||
|
let span = type_param.ident.span();
|
||||||
|
let lt_token = lt_token.unwrap_or(Token);
|
||||||
|
let gt_token = gt_token.unwrap_or(Token);
|
||||||
|
let None = params.next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
lt_token,
|
||||||
|
type_param,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) enum ParsedTypeAlias {
|
||||||
|
TypeAlias {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
vis: Visibility,
|
||||||
|
type_token: Token![type],
|
||||||
|
ident: Ident,
|
||||||
|
generics: MaybeParsed<ParsedGenerics, Generics>,
|
||||||
|
eq_token: Token![=],
|
||||||
|
ty: MaybeParsed<ParsedType, Type>,
|
||||||
|
semi_token: Token![;],
|
||||||
|
},
|
||||||
|
PhantomConstAccessor {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
get: (kw::get, Paren, Expr),
|
||||||
|
vis: Visibility,
|
||||||
|
type_token: Token![type],
|
||||||
|
ident: Ident,
|
||||||
|
generics: PhantomConstAccessorGenerics,
|
||||||
|
eq_token: Token![=],
|
||||||
|
ty: Type,
|
||||||
|
ty_is_dyn_size: Option<known_items::DynSize>,
|
||||||
|
semi_token: Token![;],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedTypeAlias {
|
impl ParsedTypeAlias {
|
||||||
|
fn ty_is_dyn_size(ty: &Type) -> Option<known_items::DynSize> {
|
||||||
|
match ty {
|
||||||
|
Type::Group(TypeGroup {
|
||||||
|
group_token: _,
|
||||||
|
elem,
|
||||||
|
}) => Self::ty_is_dyn_size(elem),
|
||||||
|
Type::Paren(TypeParen {
|
||||||
|
paren_token: _,
|
||||||
|
elem,
|
||||||
|
}) => Self::ty_is_dyn_size(elem),
|
||||||
|
Type::Path(syn::TypePath { qself: None, path }) => {
|
||||||
|
known_items::DynSize::parse_path(path.clone()).ok()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn parse_phantom_const_accessor(
|
||||||
|
item: ItemType,
|
||||||
|
mut errors: Errors,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
get: (kw::get, Paren, Expr),
|
||||||
|
) -> syn::Result<Self> {
|
||||||
|
let ItemType {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
} = item;
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
ref target,
|
||||||
|
custom_bounds,
|
||||||
|
no_static,
|
||||||
|
no_runtime_generics,
|
||||||
|
cmp_eq,
|
||||||
|
get: _,
|
||||||
|
} = options.body;
|
||||||
|
if let Some((no_static,)) = no_static {
|
||||||
|
errors.error(no_static, "no_static is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((target, ..)) = target {
|
||||||
|
errors.error(
|
||||||
|
target,
|
||||||
|
"target is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((no_runtime_generics,)) = no_runtime_generics {
|
||||||
|
errors.error(
|
||||||
|
no_runtime_generics,
|
||||||
|
"no_runtime_generics is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((cmp_eq,)) = cmp_eq {
|
||||||
|
errors.error(cmp_eq, "cmp_eq is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((custom_bounds,)) = custom_bounds {
|
||||||
|
errors.error(
|
||||||
|
custom_bounds,
|
||||||
|
"custom_bounds is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else {
|
||||||
|
errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter<P: PhantomConstGet<MyType>> = RetType;");
|
||||||
|
errors.finish()?;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
errors.finish()?;
|
||||||
|
let ty_is_dyn_size = Self::ty_is_dyn_size(&ty);
|
||||||
|
Ok(Self::PhantomConstAccessor {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
get,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty: *ty,
|
||||||
|
ty_is_dyn_size,
|
||||||
|
semi_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
fn parse(item: ItemType) -> syn::Result<Self> {
|
fn parse(item: ItemType) -> syn::Result<Self> {
|
||||||
let ItemType {
|
let ItemType {
|
||||||
mut attrs,
|
mut attrs,
|
||||||
|
|
@ -51,7 +376,25 @@ impl ParsedTypeAlias {
|
||||||
no_static,
|
no_static,
|
||||||
no_runtime_generics: _,
|
no_runtime_generics: _,
|
||||||
cmp_eq,
|
cmp_eq,
|
||||||
|
ref mut get,
|
||||||
} = options.body;
|
} = options.body;
|
||||||
|
if let Some(get) = get.take() {
|
||||||
|
return Self::parse_phantom_const_accessor(
|
||||||
|
ItemType {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
},
|
||||||
|
errors,
|
||||||
|
options,
|
||||||
|
get,
|
||||||
|
);
|
||||||
|
}
|
||||||
if let Some((no_static,)) = no_static {
|
if let Some((no_static,)) = no_static {
|
||||||
errors.error(no_static, "no_static is not valid on type aliases");
|
errors.error(no_static, "no_static is not valid on type aliases");
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +410,7 @@ impl ParsedTypeAlias {
|
||||||
};
|
};
|
||||||
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
|
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
|
||||||
errors.finish()?;
|
errors.finish()?;
|
||||||
Ok(Self {
|
Ok(Self::TypeAlias {
|
||||||
attrs,
|
attrs,
|
||||||
options,
|
options,
|
||||||
vis,
|
vis,
|
||||||
|
|
@ -83,7 +426,8 @@ impl ParsedTypeAlias {
|
||||||
|
|
||||||
impl ToTokens for ParsedTypeAlias {
|
impl ToTokens for ParsedTypeAlias {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let Self {
|
match self {
|
||||||
|
Self::TypeAlias {
|
||||||
attrs,
|
attrs,
|
||||||
options,
|
options,
|
||||||
vis,
|
vis,
|
||||||
|
|
@ -93,7 +437,7 @@ impl ToTokens for ParsedTypeAlias {
|
||||||
eq_token,
|
eq_token,
|
||||||
ty,
|
ty,
|
||||||
semi_token,
|
semi_token,
|
||||||
} = self;
|
} => {
|
||||||
let ItemOptions {
|
let ItemOptions {
|
||||||
outline_generated: _,
|
outline_generated: _,
|
||||||
target,
|
target,
|
||||||
|
|
@ -101,6 +445,7 @@ impl ToTokens for ParsedTypeAlias {
|
||||||
no_static: _,
|
no_static: _,
|
||||||
no_runtime_generics,
|
no_runtime_generics,
|
||||||
cmp_eq: _,
|
cmp_eq: _,
|
||||||
|
get: _,
|
||||||
} = &options.body;
|
} = &options.body;
|
||||||
let target = get_target(target, ident);
|
let target = get_target(target, ident);
|
||||||
let mut type_attrs = attrs.clone();
|
let mut type_attrs = attrs.clone();
|
||||||
|
|
@ -126,11 +471,110 @@ impl ToTokens for ParsedTypeAlias {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::PhantomConstAccessor {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
get: (_get_kw, _get_paren, get_expr),
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
ty_is_dyn_size,
|
||||||
|
semi_token,
|
||||||
|
} => {
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
target: _,
|
||||||
|
custom_bounds: _,
|
||||||
|
no_static: _,
|
||||||
|
no_runtime_generics: _,
|
||||||
|
cmp_eq: _,
|
||||||
|
get: _,
|
||||||
|
} = &options.body;
|
||||||
|
let span = ident.span();
|
||||||
|
let mut type_attrs = attrs.clone();
|
||||||
|
type_attrs.push(parse_quote_spanned! {span=>
|
||||||
|
#[allow(type_alias_bounds)]
|
||||||
|
});
|
||||||
|
let type_param_ident = &generics.type_param.ident;
|
||||||
|
let syn_generics = Generics::from(generics);
|
||||||
|
ItemType {
|
||||||
|
attrs: type_attrs,
|
||||||
|
vis: vis.clone(),
|
||||||
|
type_token: *type_token,
|
||||||
|
ident: ident.clone(),
|
||||||
|
generics: syn_generics.clone(),
|
||||||
|
eq_token: *eq_token,
|
||||||
|
ty: parse_quote_spanned! {span=>
|
||||||
|
<#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type
|
||||||
|
},
|
||||||
|
semi_token: *semi_token,
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
let generics_accumulation_ident =
|
||||||
|
format_ident!("__{}__GenericsAccumulation", ident);
|
||||||
|
ItemStruct {
|
||||||
|
attrs: vec![
|
||||||
|
common_derives(span),
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vis: vis.clone(),
|
||||||
|
struct_token: Token,
|
||||||
|
ident: generics_accumulation_ident.clone(),
|
||||||
|
generics: Generics::default(),
|
||||||
|
fields: Fields::Unnamed(parse_quote_spanned! {span=>
|
||||||
|
(())
|
||||||
|
}),
|
||||||
|
semi_token: Some(Token),
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(non_upper_case_globals, dead_code)]
|
||||||
|
#vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(());
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
|
||||||
|
let tokens = wrapped_in_const.inner();
|
||||||
|
let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl();
|
||||||
|
let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty;
|
||||||
|
let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size {
|
||||||
|
known_items::usize(ty_is_dyn_size.span).to_token_stream()
|
||||||
|
} else {
|
||||||
|
ty.to_token_stream()
|
||||||
|
};
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident>
|
||||||
|
for #generics_accumulation_ident
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
type Output = #index_output;
|
||||||
|
|
||||||
|
fn index(&self, __param: #type_param_ident) -> &Self::Output {
|
||||||
|
::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>(
|
||||||
|
__param,
|
||||||
|
#get_expr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
|
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
|
||||||
let item = ParsedTypeAlias::parse(item)?;
|
let item = ParsedTypeAlias::parse(item)?;
|
||||||
let outline_generated = item.options.body.outline_generated;
|
let outline_generated = match &item {
|
||||||
|
ParsedTypeAlias::TypeAlias { options, .. }
|
||||||
|
| ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated,
|
||||||
|
};
|
||||||
let mut contents = item.to_token_stream();
|
let mut contents = item.to_token_stream();
|
||||||
if outline_generated.is_some() {
|
if outline_generated.is_some() {
|
||||||
contents = crate::outline_generated(contents, "hdl-type-alias-");
|
contents = crate::outline_generated(contents, "hdl-type-alias-");
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ crate::options! {
|
||||||
NoStatic(no_static),
|
NoStatic(no_static),
|
||||||
NoRuntimeGenerics(no_runtime_generics),
|
NoRuntimeGenerics(no_runtime_generics),
|
||||||
CmpEq(cmp_eq),
|
CmpEq(cmp_eq),
|
||||||
|
Get(get, Expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2045,6 +2046,7 @@ pub(crate) mod known_items {
|
||||||
impl_known_item!(::fayalite::int::Size);
|
impl_known_item!(::fayalite::int::Size);
|
||||||
impl_known_item!(::fayalite::int::UInt);
|
impl_known_item!(::fayalite::int::UInt);
|
||||||
impl_known_item!(::fayalite::int::UIntType);
|
impl_known_item!(::fayalite::int::UIntType);
|
||||||
|
impl_known_item!(::fayalite::phantom_const::PhantomConstGet);
|
||||||
impl_known_item!(::fayalite::reset::ResetType);
|
impl_known_item!(::fayalite::reset::ResetType);
|
||||||
impl_known_item!(::fayalite::ty::CanonicalType);
|
impl_known_item!(::fayalite::ty::CanonicalType);
|
||||||
impl_known_item!(::fayalite::ty::StaticType);
|
impl_known_item!(::fayalite::ty::StaticType);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ mod kw {
|
||||||
custom_keyword!(connect_inexact);
|
custom_keyword!(connect_inexact);
|
||||||
custom_keyword!(custom_bounds);
|
custom_keyword!(custom_bounds);
|
||||||
custom_keyword!(flip);
|
custom_keyword!(flip);
|
||||||
|
custom_keyword!(get);
|
||||||
custom_keyword!(hdl);
|
custom_keyword!(hdl);
|
||||||
custom_keyword!(hdl_module);
|
custom_keyword!(hdl_module);
|
||||||
custom_keyword!(incomplete_wire);
|
custom_keyword!(incomplete_wire);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
|
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
|
||||||
use clap::builder::OsStringValueParser;
|
use clap::builder::OsStringValueParser;
|
||||||
use eyre::{Context, bail, ensure, eyre};
|
use eyre::{Context, ensure, eyre};
|
||||||
use serde::{
|
use serde::{
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
de::{DeserializeOwned, Error},
|
de::{DeserializeOwned, Error},
|
||||||
|
|
@ -26,6 +26,7 @@ use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
process::ExitStatus,
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -365,13 +366,17 @@ impl ExternalJobCaching {
|
||||||
.stdin(std::process::Stdio::null());
|
.stdin(std::process::Stdio::null());
|
||||||
Ok(cmd)
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
pub fn run<F: FnOnce(std::process::Command) -> eyre::Result<()>>(
|
pub fn run<F>(
|
||||||
self,
|
self,
|
||||||
command_line: Interned<[Interned<OsStr>]>,
|
command_line: Interned<[Interned<OsStr>]>,
|
||||||
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
||||||
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
||||||
run_fn: F,
|
run_fn: F,
|
||||||
) -> eyre::Result<()> {
|
exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report,
|
||||||
|
) -> eyre::Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(std::process::Command) -> eyre::Result<Result<(), ExitStatus>>,
|
||||||
|
{
|
||||||
let mut hasher = JobCacheHasher::default();
|
let mut hasher = JobCacheHasher::default();
|
||||||
hasher.hash_iter(command_line.iter(), |hasher, arg| {
|
hasher.hash_iter(command_line.iter(), |hasher, arg| {
|
||||||
hasher.hash_sized_os_str(arg)
|
hasher.hash_sized_os_str(arg)
|
||||||
|
|
@ -419,7 +424,26 @@ impl ExternalJobCaching {
|
||||||
})
|
})
|
||||||
.expect("spawn shouldn't fail");
|
.expect("spawn shouldn't fail");
|
||||||
run_fn(cmd)
|
run_fn(cmd)
|
||||||
});
|
})?;
|
||||||
|
if let Err(exit_status) = result {
|
||||||
|
// check if the user may have terminated it or something, don't cache the failure
|
||||||
|
let user_maybe_terminated;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
user_maybe_terminated = std::os::unix::process::ExitStatusExt::signal(&exit_status)
|
||||||
|
.is_some()
|
||||||
|
|| exit_status.code().is_none_or(|code| code > 1);
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
user_maybe_terminated = !exit_status.success();
|
||||||
|
}
|
||||||
|
if user_maybe_terminated {
|
||||||
|
let _ = std::fs::remove_file(self.cache_json_path);
|
||||||
|
return Err(exit_status_to_error(exit_status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = result.map_err(exit_status_to_error);
|
||||||
ExternalJobCacheV2 {
|
ExternalJobCacheV2 {
|
||||||
version: ExternalJobCacheVersion::CURRENT,
|
version: ExternalJobCacheVersion::CURRENT,
|
||||||
inputs_hash,
|
inputs_hash,
|
||||||
|
|
@ -444,16 +468,26 @@ impl ExternalJobCaching {
|
||||||
.write_to_file(self.cache_json_path)?;
|
.write_to_file(self.cache_json_path)?;
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
pub fn run_maybe_cached<F: FnOnce(std::process::Command) -> eyre::Result<()>>(
|
pub fn run_maybe_cached<F>(
|
||||||
this: Option<Self>,
|
this: Option<Self>,
|
||||||
command_line: Interned<[Interned<OsStr>]>,
|
command_line: Interned<[Interned<OsStr>]>,
|
||||||
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
||||||
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
||||||
run_fn: F,
|
run_fn: F,
|
||||||
) -> eyre::Result<()> {
|
exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report,
|
||||||
|
) -> eyre::Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(std::process::Command) -> eyre::Result<Result<(), ExitStatus>>,
|
||||||
|
{
|
||||||
match this {
|
match this {
|
||||||
Some(this) => this.run(command_line, input_file_paths, output_file_paths, run_fn),
|
Some(this) => this.run(
|
||||||
None => run_fn(Self::make_command(command_line)?),
|
command_line,
|
||||||
|
input_file_paths,
|
||||||
|
output_file_paths,
|
||||||
|
run_fn,
|
||||||
|
exit_status_to_error,
|
||||||
|
),
|
||||||
|
None => run_fn(Self::make_command(command_line)?)?.map_err(exit_status_to_error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1119,10 +1153,12 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
|
||||||
}
|
}
|
||||||
let status = acquired_job.run_command(cmd, |cmd| cmd.status())?;
|
let status = acquired_job.run_command(cmd, |cmd| cmd.status())?;
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("running {command_line:?} failed: {status}")
|
Ok(Err(status))
|
||||||
|
} else {
|
||||||
|
Ok(Ok(()))
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
},
|
},
|
||||||
|
|status| eyre!("running {command_line:?} failed: {status}"),
|
||||||
)?;
|
)?;
|
||||||
Ok(job
|
Ok(job
|
||||||
.output_paths()
|
.output_paths()
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ use crate::{
|
||||||
},
|
},
|
||||||
intern::{Intern, InternSlice, Interned},
|
intern::{Intern, InternSlice, Interned},
|
||||||
module::NameId,
|
module::NameId,
|
||||||
|
testing::FormalMode,
|
||||||
util::job_server::AcquiredJob,
|
util::job_server::AcquiredJob,
|
||||||
};
|
};
|
||||||
use clap::{Args, ValueEnum};
|
use clap::Args;
|
||||||
use eyre::Context;
|
use eyre::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -24,33 +25,6 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FormalMode {
|
|
||||||
#[default]
|
|
||||||
BMC,
|
|
||||||
Prove,
|
|
||||||
Live,
|
|
||||||
Cover,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FormalMode {
|
|
||||||
pub fn as_str(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
FormalMode::BMC => "bmc",
|
|
||||||
FormalMode::Prove => "prove",
|
|
||||||
FormalMode::Live => "live",
|
|
||||||
FormalMode::Cover => "cover",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FormalMode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str(self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct FormalArgs {
|
pub struct FormalArgs {
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,15 @@ macro_rules! define_uint_in_range_type {
|
||||||
$SerdeRange { start, end }.intern_sized(),
|
$SerdeRange { start, end }.intern_sized(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
pub fn bit_width(self) -> usize {
|
||||||
|
self.value.width()
|
||||||
|
}
|
||||||
|
pub fn start(self) -> Start::SizeType {
|
||||||
|
self.range.get().start
|
||||||
|
}
|
||||||
|
pub fn end(self) -> End::SizeType {
|
||||||
|
self.range.get().end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Start: Size, End: Size> fmt::Debug for $UIntInRangeType<Start, End> {
|
impl<Start: Size, End: Size> fmt::Debug for $UIntInRangeType<Start, End> {
|
||||||
|
|
@ -477,18 +486,22 @@ macro_rules! define_uint_in_range_type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Start: Size, End: Size> ExprCastTo<UInt> for $UIntInRangeType<Start, End> {
|
impl<Start: Size, End: Size, Width: Size> ExprCastTo<UIntType<Width>>
|
||||||
fn cast_to(src: Expr<Self>, to_type: UInt) -> Expr<UInt> {
|
for $UIntInRangeType<Start, End>
|
||||||
|
{
|
||||||
|
fn cast_to(src: Expr<Self>, to_type: UIntType<Width>) -> Expr<UIntType<Width>> {
|
||||||
src.cast_to_bits().cast_to(to_type)
|
src.cast_to_bits().cast_to(to_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Start: Size, End: Size> ExprCastTo<$UIntInRangeType<Start, End>> for UInt {
|
impl<Start: Size, End: Size, Width: Size> ExprCastTo<$UIntInRangeType<Start, End>>
|
||||||
|
for UIntType<Width>
|
||||||
|
{
|
||||||
fn cast_to(
|
fn cast_to(
|
||||||
src: Expr<Self>,
|
src: Expr<Self>,
|
||||||
to_type: $UIntInRangeType<Start, End>,
|
to_type: $UIntInRangeType<Start, End>,
|
||||||
) -> Expr<$UIntInRangeType<Start, End>> {
|
) -> Expr<$UIntInRangeType<Start, End>> {
|
||||||
src.cast_bits_to(to_type)
|
src.cast_to(to_type.value).cast_bits_to(to_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
// TODO: enable:
|
// TODO: enable:
|
||||||
// #![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
|
#![deny(
|
||||||
|
rustdoc::bare_urls,
|
||||||
|
rustdoc::broken_intra_doc_links,
|
||||||
|
rustdoc::invalid_codeblock_attributes,
|
||||||
|
rustdoc::invalid_html_tags,
|
||||||
|
rustdoc::invalid_rust_codeblocks,
|
||||||
|
rustdoc::private_doc_tests,
|
||||||
|
rustdoc::private_intra_doc_links,
|
||||||
|
rustdoc::redundant_explicit_links,
|
||||||
|
rustdoc::unescaped_backticks
|
||||||
|
)]
|
||||||
|
|
||||||
//! [Main Documentation][_docs]
|
//! [Main Documentation][_docs]
|
||||||
|
|
||||||
extern crate self as fayalite;
|
extern crate self as fayalite;
|
||||||
|
|
@ -74,6 +86,135 @@ macro_rules! __cfg_expansion_helper {
|
||||||
pub use fayalite_proc_macros::hdl_module;
|
pub use fayalite_proc_macros::hdl_module;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
/// The `#[hdl]` attribute is supported on several different kinds of [Rust Items](https://doc.rust-lang.org/reference/items.html):
|
||||||
|
///
|
||||||
|
/// # Functions and Methods
|
||||||
|
/// Enable's the stuff that you can use inside a [module's body](crate::_docs::modules::module_bodies),
|
||||||
|
/// but without being a module or changing the function's signature.
|
||||||
|
/// The only exception is that you can't use stuff that requires the automatically-provided `m` variable.
|
||||||
|
///
|
||||||
|
/// # Structs
|
||||||
|
// TODO: expand on struct docs
|
||||||
|
/// e.g.:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct OtherStruct {}
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub struct MyStruct {
|
||||||
|
/// #[hdl(flip)]
|
||||||
|
/// pub a: UInt<5>,
|
||||||
|
/// pub b: Bool,
|
||||||
|
/// #[hdl(flip)]
|
||||||
|
/// pub c: OtherStruct,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Enums
|
||||||
|
// TODO: expand on enum docs
|
||||||
|
/// e.g.:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct MyStruct {}
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub enum MyEnum {
|
||||||
|
/// A(UInt<3>),
|
||||||
|
/// B,
|
||||||
|
/// C(MyStruct),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Aliases
|
||||||
|
///
|
||||||
|
/// There's three different ways you can create a type alias:
|
||||||
|
///
|
||||||
|
/// # Normal Type Alias
|
||||||
|
///
|
||||||
|
/// This works exactly how you'd expect:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct MyStruct<T: Type> {
|
||||||
|
/// # v: T,
|
||||||
|
/// # }
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub type MyType<T: Type> = MyStruct<T>;
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
///
|
||||||
|
/// let ty = MyType[UInt[3]];
|
||||||
|
/// assert_eq!(ty, MyStruct[UInt[3]]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Alias that gets a [`Type`] from a [`PhantomConst`]
|
||||||
|
///
|
||||||
|
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::{intern::Intern, prelude::*};
|
||||||
|
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// pub struct Config {
|
||||||
|
/// pub foo: usize,
|
||||||
|
/// pub bar: Bundle,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // the expression inside `get` is called with `Interned<Config>` and returns `Array<Bundle>`
|
||||||
|
/// #[hdl(get(|config| Array[config.bar][config.foo]))]
|
||||||
|
/// pub type GetMyArray<P: PhantomConstGet<Config>> = Array<Bundle>;
|
||||||
|
///
|
||||||
|
/// // you can then use it in other types:
|
||||||
|
///
|
||||||
|
/// #[hdl(no_static)]
|
||||||
|
/// pub struct WrapMyArray<P: Type + PhantomConstGet<Config>> {
|
||||||
|
/// pub my_array: GetMyArray<P>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
/// let bar = Bundle::new(Default::default());
|
||||||
|
/// let config = PhantomConst::new(Config { foo: 12, bar }.intern_sized());
|
||||||
|
/// let ty = WrapMyArray[config];
|
||||||
|
/// assert_eq!(ty.my_array, Array[bar][12]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Alias that gets a [`Size`] from a [`PhantomConst`]
|
||||||
|
///
|
||||||
|
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::{intern::Intern, prelude::*};
|
||||||
|
/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// # pub struct ConfigItem {}
|
||||||
|
/// # impl ConfigItem {
|
||||||
|
/// # pub fn new() -> Self {
|
||||||
|
/// # Self {}
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// pub struct Config {
|
||||||
|
/// pub items: Vec<ConfigItem>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // the expression inside `get` is called with `Interned<Config>` and returns `usize` (not DynSize)
|
||||||
|
/// #[hdl(get(|config| config.items.len()))]
|
||||||
|
/// pub type GetItemsLen<P: PhantomConstGet<Config>> = DynSize; // must be DynSize
|
||||||
|
///
|
||||||
|
/// // you can then use it in other types:
|
||||||
|
///
|
||||||
|
/// #[hdl(no_static)]
|
||||||
|
/// pub struct FlagPerItem<P: Type + PhantomConstGet<Config>> {
|
||||||
|
/// pub flags: ArrayType<Bool, GetItemsLen<P>>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
/// let config = PhantomConst::new(Config { items: vec![ConfigItem::new(); 5] }.intern_sized());
|
||||||
|
/// let ty = FlagPerItem[config];
|
||||||
|
/// assert_eq!(ty.flags, Array[Bool][5]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`PhantomConst`]: crate::phantom_const::PhantomConst
|
||||||
|
/// [`Size`]: crate::int::Size
|
||||||
|
/// [`Type`]: crate::ty::Type
|
||||||
pub use fayalite_proc_macros::hdl;
|
pub use fayalite_proc_macros::hdl;
|
||||||
|
|
||||||
pub use bitvec;
|
pub use bitvec;
|
||||||
|
|
|
||||||
|
|
@ -415,3 +415,71 @@ impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<CanonicalType> for Phanto
|
||||||
SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self))
|
SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<T: ?Sized> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhantomConstGet<T: ?Sized + PhantomConstValue>: sealed::Sealed<T> {
|
||||||
|
fn get(&self) -> Interned<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
|
||||||
|
sealed::Sealed<T> for This
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
|
||||||
|
PhantomConstGet<T> for This
|
||||||
|
{
|
||||||
|
fn get(&self) -> Interned<T> {
|
||||||
|
This::Target::get(&**self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_phantom_const_get {
|
||||||
|
(
|
||||||
|
impl PhantomConstGet<$T:ident> for $ty:ty {
|
||||||
|
fn $get:ident(&$get_self:ident) -> _ $get_body:block
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {}
|
||||||
|
|
||||||
|
impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty {
|
||||||
|
fn $get(&$get_self) -> Interned<$T> $get_body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_phantom_const_get! {
|
||||||
|
impl PhantomConstGet<T> for PhantomConst<T> {
|
||||||
|
fn get(&self) -> _ {
|
||||||
|
PhantomConst::get(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_phantom_const_get! {
|
||||||
|
impl PhantomConstGet<T> for Expr<PhantomConst<T>> {
|
||||||
|
fn get(&self) -> _ {
|
||||||
|
PhantomConst::get(Expr::ty(*self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait ReturnSelfUnchanged<T: ?Sized> {
|
||||||
|
type Type: ?Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<This: ?Sized, T: ?Sized> ReturnSelfUnchanged<T> for This {
|
||||||
|
type Type = This;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn type_alias_phantom_const_get_helper<T: ?Sized + PhantomConstValue, R: Intern + Clone>(
|
||||||
|
param: impl PhantomConstGet<T>,
|
||||||
|
get: impl FnOnce(Interned<T>) -> R,
|
||||||
|
) -> &'static R {
|
||||||
|
Interned::into_inner(get(param.get()).intern_sized())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub use crate::{
|
||||||
Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance,
|
Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance,
|
||||||
memory, memory_array, memory_with_init, reg_builder, wire,
|
memory, memory_array, memory_with_init, reg_builder, wire,
|
||||||
},
|
},
|
||||||
phantom_const::PhantomConst,
|
phantom_const::{PhantomConst, PhantomConstGet},
|
||||||
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
|
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
|
||||||
reg::Reg,
|
reg::Reg,
|
||||||
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
|
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
|
||||||
|
|
@ -37,7 +37,7 @@ pub use crate::{
|
||||||
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
|
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
|
||||||
},
|
},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
testing::assert_formal,
|
testing::{FormalMode, assert_formal},
|
||||||
ty::{AsMask, CanonicalType, Type},
|
ty::{AsMask, CanonicalType, Type},
|
||||||
util::{ConstUsize, GenericConstUsize},
|
util::{ConstUsize, GenericConstUsize},
|
||||||
wire::Wire,
|
wire::Wire,
|
||||||
|
|
|
||||||
|
|
@ -1522,7 +1522,7 @@ struct SimulationImpl {
|
||||||
state_ready_to_run: bool,
|
state_ready_to_run: bool,
|
||||||
trace_decls: TraceModule,
|
trace_decls: TraceModule,
|
||||||
traces: SimTraces<Box<[SimTrace<SimTraceKind, SimTraceState>]>>,
|
traces: SimTraces<Box<[SimTrace<SimTraceKind, SimTraceState>]>>,
|
||||||
trace_memories: HashMap<StatePartIndex<StatePartKindMemories>, TraceMem>,
|
trace_memories: BTreeMap<StatePartIndex<StatePartKindMemories>, TraceMem>,
|
||||||
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
|
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
|
||||||
instant: SimInstant,
|
instant: SimInstant,
|
||||||
clocks_triggered: Interned<[StatePartIndex<StatePartKindSmallSlots>]>,
|
clocks_triggered: Interned<[StatePartIndex<StatePartKindSmallSlots>]>,
|
||||||
|
|
@ -1622,7 +1622,7 @@ impl SimulationImpl {
|
||||||
last_state: kind.make_state(),
|
last_state: kind.make_state(),
|
||||||
},
|
},
|
||||||
))),
|
))),
|
||||||
trace_memories: HashMap::from_iter(compiled.trace_memories.iter().copied()),
|
trace_memories: BTreeMap::from_iter(compiled.trace_memories.iter().copied()),
|
||||||
trace_writers: vec![],
|
trace_writers: vec![],
|
||||||
instant: SimInstant::START,
|
instant: SimInstant::START,
|
||||||
clocks_triggered: compiled.clocks_triggered,
|
clocks_triggered: compiled.clocks_triggered,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
NoArgs, RunBuild,
|
NoArgs, RunBuild,
|
||||||
external::{ExternalCommandArgs, ExternalCommandJobKind},
|
external::{ExternalCommandArgs, ExternalCommandJobKind},
|
||||||
firrtl::{FirrtlArgs, FirrtlJobKind},
|
firrtl::{FirrtlArgs, FirrtlJobKind},
|
||||||
formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind},
|
formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind},
|
||||||
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
|
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
|
||||||
},
|
},
|
||||||
bundle::BundleType,
|
bundle::BundleType,
|
||||||
|
|
@ -14,14 +14,43 @@ use crate::{
|
||||||
module::Module,
|
module::Module,
|
||||||
util::HashMap,
|
util::HashMap,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Write,
|
fmt::{self, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
sync::{Mutex, OnceLock},
|
sync::{Mutex, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize,
|
||||||
|
)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum FormalMode {
|
||||||
|
#[default]
|
||||||
|
BMC,
|
||||||
|
Prove,
|
||||||
|
Live,
|
||||||
|
Cover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalMode {
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
FormalMode::BMC => "bmc",
|
||||||
|
FormalMode::Prove => "prove",
|
||||||
|
FormalMode::Live => "live",
|
||||||
|
FormalMode::Cover => "cover",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FormalMode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CargoMetadata {
|
struct CargoMetadata {
|
||||||
target_directory: String,
|
target_directory: String,
|
||||||
|
|
|
||||||
|
|
@ -212,9 +212,7 @@ pub fn queue<T: Type>(
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
build::formal::FormalMode, firrtl::ExportOptions,
|
firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind, ty::StaticType,
|
||||||
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
|
|
||||||
ty::StaticType,
|
|
||||||
};
|
};
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,7 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
//! Formal tests in Fayalite
|
//! Formal tests in Fayalite
|
||||||
|
|
||||||
use fayalite::{
|
use fayalite::prelude::*;
|
||||||
build::formal::FormalMode,
|
|
||||||
clock::{Clock, ClockDomain},
|
|
||||||
expr::{CastTo, HdlPartialEq},
|
|
||||||
firrtl::ExportOptions,
|
|
||||||
formal::{any_const, any_seq, formal_reset, hdl_assert, hdl_assume},
|
|
||||||
hdl, hdl_module,
|
|
||||||
int::{Bool, DynSize, Size, UInt, UIntType},
|
|
||||||
module::{connect, connect_any, instance, memory, reg_builder, wire},
|
|
||||||
reset::ToReset,
|
|
||||||
testing::assert_formal,
|
|
||||||
ty::StaticType,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Test hidden state
|
/// Test hidden state
|
||||||
///
|
///
|
||||||
|
|
@ -119,7 +107,7 @@ mod hidden_state {
|
||||||
FormalMode::Prove,
|
FormalMode::Prove,
|
||||||
16,
|
16,
|
||||||
None,
|
None,
|
||||||
ExportOptions::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
// here a couple of cycles is enough
|
// here a couple of cycles is enough
|
||||||
assert_formal(
|
assert_formal(
|
||||||
|
|
@ -128,7 +116,7 @@ mod hidden_state {
|
||||||
FormalMode::Prove,
|
FormalMode::Prove,
|
||||||
2,
|
2,
|
||||||
None,
|
None,
|
||||||
ExportOptions::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +230,7 @@ mod memory {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let wr: WritePort<DynSize> = wire(WritePort[n]);
|
let wr: WritePort<DynSize> = wire(WritePort[n]);
|
||||||
connect(wr.addr, any_seq(UInt[n]));
|
connect(wr.addr, any_seq(UInt[n]));
|
||||||
connect(wr.data, any_seq(UInt::<8>::TYPE));
|
connect(wr.data, any_seq(UInt::<8>::new_static()));
|
||||||
connect(wr.en, any_seq(Bool));
|
connect(wr.en, any_seq(Bool));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let dut = instance(example_sram(n));
|
let dut = instance(example_sram(n));
|
||||||
|
|
@ -289,7 +277,7 @@ mod memory {
|
||||||
FormalMode::Prove,
|
FormalMode::Prove,
|
||||||
2,
|
2,
|
||||||
None,
|
None,
|
||||||
ExportOptions::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use fayalite::{
|
||||||
bundle::BundleType,
|
bundle::BundleType,
|
||||||
enum_::EnumType,
|
enum_::EnumType,
|
||||||
int::{BoolOrIntType, IntType},
|
int::{BoolOrIntType, IntType},
|
||||||
phantom_const::PhantomConst,
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
ty::StaticType,
|
ty::StaticType,
|
||||||
};
|
};
|
||||||
|
|
@ -197,3 +196,21 @@ check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +,
|
||||||
check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
||||||
check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
||||||
check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MyPhantomConstInner {
|
||||||
|
pub a: usize,
|
||||||
|
pub b: UInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(outline_generated, get(|v| v.a))]
|
||||||
|
pub type GetA<P: PhantomConstGet<MyPhantomConstInner>> = DynSize;
|
||||||
|
|
||||||
|
#[hdl(outline_generated, get(|v| v.b))]
|
||||||
|
pub type GetB<P: PhantomConstGet<MyPhantomConstInner>> = UInt;
|
||||||
|
|
||||||
|
#[hdl(outline_generated, no_static)]
|
||||||
|
pub struct MyTypeWithPhantomConstParameter<P: Type + PhantomConstGet<MyPhantomConstInner>> {
|
||||||
|
pub a: ArrayType<Bool, GetA<P>>,
|
||||||
|
pub b: HdlOption<GetB<P>>,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
memory::{ReadStruct, ReadWriteStruct, WriteStruct},
|
memory::{ReadStruct, ReadWriteStruct, WriteStruct},
|
||||||
module::{instance_with_loc, reg_builder_with_loc},
|
module::{instance_with_loc, memory_with_init_and_loc, reg_builder_with_loc},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
reset::ResetType,
|
reset::ResetType,
|
||||||
sim::vcd::VcdWriterDecls,
|
sim::vcd::VcdWriterDecls,
|
||||||
|
|
@ -1261,6 +1261,310 @@ fn test_memories3() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[hdl_module(outline_generated)]
|
||||||
|
pub fn many_memories() {
|
||||||
|
#[hdl]
|
||||||
|
let r: Array<ReadStruct<Bool, ConstUsize<4>>, 8> = m.input();
|
||||||
|
#[hdl]
|
||||||
|
let w: Array<WriteStruct<Bool, ConstUsize<4>>, 8> = m.input();
|
||||||
|
for (mem_index, (r, w)) in r.into_iter().zip(w).enumerate() {
|
||||||
|
let mut mem = memory_with_init_and_loc(
|
||||||
|
&format!("mem_{mem_index}"),
|
||||||
|
(0..16)
|
||||||
|
.map(|bit_index| mem_index.pow(5).to_expr()[bit_index])
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
SourceLocation::caller(),
|
||||||
|
);
|
||||||
|
connect_any(mem.new_read_port(), r);
|
||||||
|
connect_any(mem.new_write_port(), w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
#[test]
|
||||||
|
fn test_many_memories() {
|
||||||
|
let _n = SourceLocation::normalize_files_for_tests();
|
||||||
|
let mut sim = Simulation::new(many_memories());
|
||||||
|
let mut writer = RcWriter::default();
|
||||||
|
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
||||||
|
for r in sim.io().r {
|
||||||
|
sim.write_clock(r.clk, false);
|
||||||
|
}
|
||||||
|
for w in sim.io().w {
|
||||||
|
sim.write_clock(w.clk, false);
|
||||||
|
}
|
||||||
|
#[hdl(cmp_eq)]
|
||||||
|
struct IO {
|
||||||
|
r_addr: UInt<4>,
|
||||||
|
r_en: Bool,
|
||||||
|
r_data: Array<Bool, 8>,
|
||||||
|
w_addr: UInt<4>,
|
||||||
|
w_en: Bool,
|
||||||
|
w_data: Array<Bool, 8>,
|
||||||
|
w_mask: Array<Bool, 8>,
|
||||||
|
}
|
||||||
|
let io_cycles = [
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0_hdl_u4,
|
||||||
|
r_en: false,
|
||||||
|
r_data: [false; 8],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [false; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, true, false, true, false, true, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: true,
|
||||||
|
w_data: [true; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [true; 8],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: true,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false; 8],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 1_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, true, false, false, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 2_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, true, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 3_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, false, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 4_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, true, false, true, false, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 5_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, true, true, false, true, true, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 6_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, true, false, false, true, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 7_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, true, false, false, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 8_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 9_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, true, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xA_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, true, true, true, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xB_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, true, true, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xC_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, true, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xD_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, false, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xE_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, false, true],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
#[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr: 0xF_hdl_u4,
|
||||||
|
r_en: true,
|
||||||
|
r_data: [false, false, false, false, false, false, false, false],
|
||||||
|
w_addr: 0_hdl_u4,
|
||||||
|
w_en: false,
|
||||||
|
w_data: [false; 8],
|
||||||
|
w_mask: [true; 8],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (cycle, expected) in io_cycles.into_iter().enumerate() {
|
||||||
|
#[hdl(sim)]
|
||||||
|
let IO {
|
||||||
|
r_addr,
|
||||||
|
r_en,
|
||||||
|
r_data: _,
|
||||||
|
w_addr,
|
||||||
|
w_en,
|
||||||
|
w_data,
|
||||||
|
w_mask,
|
||||||
|
} = expected;
|
||||||
|
for (((r, w), w_data), w_mask) in sim
|
||||||
|
.io()
|
||||||
|
.r
|
||||||
|
.into_iter()
|
||||||
|
.zip(sim.io().w)
|
||||||
|
.zip(w_data.iter())
|
||||||
|
.zip(w_mask.iter())
|
||||||
|
{
|
||||||
|
sim.write(r.addr, &r_addr);
|
||||||
|
sim.write(r.en, &r_en);
|
||||||
|
sim.write(w.addr, &w_addr);
|
||||||
|
sim.write(w.en, &w_en);
|
||||||
|
sim.write(w.data, w_data);
|
||||||
|
sim.write(w.mask, w_mask);
|
||||||
|
}
|
||||||
|
let io = #[hdl(sim)]
|
||||||
|
IO {
|
||||||
|
r_addr,
|
||||||
|
r_en,
|
||||||
|
r_data: std::array::from_fn(|i| sim.read(sim.io().r[i].data)),
|
||||||
|
w_addr,
|
||||||
|
w_en,
|
||||||
|
w_data,
|
||||||
|
w_mask,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
expected,
|
||||||
|
io,
|
||||||
|
"vcd:\n{}\ncycle: {cycle}",
|
||||||
|
String::from_utf8(writer.take()).unwrap(),
|
||||||
|
);
|
||||||
|
sim.advance_time(SimDuration::from_micros(1));
|
||||||
|
for r in sim.io().r {
|
||||||
|
sim.write_clock(r.clk, true);
|
||||||
|
}
|
||||||
|
for w in sim.io().w {
|
||||||
|
sim.write_clock(w.clk, true);
|
||||||
|
}
|
||||||
|
sim.advance_time(SimDuration::from_micros(1));
|
||||||
|
for r in sim.io().r {
|
||||||
|
sim.write_clock(r.clk, false);
|
||||||
|
}
|
||||||
|
for w in sim.io().w {
|
||||||
|
sim.write_clock(w.clk, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sim.flush_traces().unwrap();
|
||||||
|
let vcd = String::from_utf8(writer.take()).unwrap();
|
||||||
|
println!("####### VCD:\n{vcd}\n#######");
|
||||||
|
if vcd != include_str!("sim/expected/many_memories.vcd") {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
let sim_debug = format!("{sim:#?}");
|
||||||
|
println!("#######\n{sim_debug}\n#######");
|
||||||
|
if sim_debug != include_str!("sim/expected/many_memories.txt") {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[hdl_module(outline_generated)]
|
#[hdl_module(outline_generated)]
|
||||||
pub fn duplicate_names() {
|
pub fn duplicate_names() {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
|
||||||
7782
crates/fayalite/tests/sim/expected/many_memories.txt
Normal file
7782
crates/fayalite/tests/sim/expected/many_memories.txt
Normal file
File diff suppressed because it is too large
Load diff
2596
crates/fayalite/tests/sim/expected/many_memories.vcd
Normal file
2596
crates/fayalite/tests/sim/expected/many_memories.vcd
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue