Compare commits

...

10 commits

Author SHA1 Message Date
cdd84953d0
support unknown trait bounds in type parameters 2025-02-13 18:35:30 -08:00
86a1bb46be
add #[hdl] let destructuring and, while at it, tuple patterns 2025-02-10 22:49:41 -08:00
209d5b5fe1
fix broken doc links 2025-02-10 22:49:16 -08:00
d4ea826051
sim: fix "label address not set" bug when the last Assignment is conditional 2025-01-15 19:04:40 -08:00
404a2ee043
tests/sim: add test_array_rw 2025-01-12 21:38:59 -08:00
e3a2ccd41c
properly handle duplicate names in vcd 2025-01-09 22:52:22 -08:00
3771cea78e
Gather the FIFO debug ports in a bundle 2024-12-29 13:17:24 -03:00
dcf865caec
Add assertions and debug ports in order for the FIFO to pass induction
As some proofs involving memories, it is necessary to add more ports to
the queue interface, to sync state. These changes are predicated on the
test environment, so normal use is not affected.

Since some speedup is achieved, use the saved time to test with a deeper
FIFO.
2024-12-29 13:12:58 -03:00
31d01046a8
Initial queue formal proof based on one-entry FIFO equivalence
For now, only check that the basic properties work in bounded model check
mode, leave the induction proof for later.

Partially replace the previously existing proof.

Remove earlier assumptions and bounds that don't apply for this proof.

Use parameterized types instead of hard-coded types.
2024-12-29 13:04:01 -03:00
c16726cee6
fix #[hdl]/#[hdl_module] attributes getting the wrong hygiene when processing #[cfg]s 2024-12-29 00:48:15 -08:00
23 changed files with 4834 additions and 258 deletions

View file

@ -2069,11 +2069,16 @@ macro_rules! impl_bounds {
$( $(
$Variant:ident, $Variant:ident,
)* )*
$(
#[unknown]
$Unknown:ident,
)?
} }
) => { ) => {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
$vis enum $enum_type { $vis enum $enum_type {
$($Variant(known_items::$Variant),)* $($Variant(known_items::$Variant),)*
$($Unknown(syn::TypeParamBound),)?
} }
$(impl From<known_items::$Variant> for $enum_type { $(impl From<known_items::$Variant> for $enum_type {
@ -2086,28 +2091,54 @@ macro_rules! impl_bounds {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
match self { match self {
$(Self::$Variant(v) => v.to_tokens(tokens),)* $(Self::$Variant(v) => v.to_tokens(tokens),)*
$(Self::$Unknown(v) => v.to_tokens(tokens),)?
} }
} }
} }
impl $enum_type { impl $enum_type {
$vis fn parse_path(path: Path) -> Result<Self, Path> { $vis fn parse_path(path: Path) -> Result<Self, Path> {
#![allow(unreachable_code)]
$(let path = match known_items::$Variant::parse_path(path) { $(let path = match known_items::$Variant::parse_path(path) {
Ok(v) => return Ok(Self::$Variant(v)), Ok(v) => return Ok(Self::$Variant(v)),
Err(path) => path, Err(path) => path,
};)* };)*
$(return Ok(Self::$Unknown(syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path,
}.into()));)?
Err(path) 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 { impl Parse for $enum_type {
fn parse(input: ParseStream) -> syn::Result<Self> { fn parse(input: ParseStream) -> syn::Result<Self> {
Self::parse_path(Path::parse_mod_style(input)?).map_err(|path| { Self::parse_type_param_bound(input.parse()?)
syn::Error::new_spanned( .map_err(|type_param_bound| syn::Error::new_spanned(
path, type_param_bound,
format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")),
) ))
})
} }
} }
@ -2115,6 +2146,7 @@ macro_rules! impl_bounds {
#[allow(non_snake_case)] #[allow(non_snake_case)]
$vis struct $struct_type { $vis struct $struct_type {
$($vis $Variant: Option<known_items::$Variant>,)* $($vis $Variant: Option<known_items::$Variant>,)*
$($vis $Unknown: Vec<syn::TypeParamBound>,)?
} }
impl ToTokens for $struct_type { impl ToTokens for $struct_type {
@ -2126,42 +2158,63 @@ macro_rules! impl_bounds {
separator = Some(<Token![+]>::default()); separator = Some(<Token![+]>::default());
v.to_tokens(tokens); v.to_tokens(tokens);
})* })*
$(for v in &self.$Unknown {
separator.to_tokens(tokens);
separator = Some(<Token![+]>::default());
v.to_tokens(tokens);
})*
} }
} }
const _: () = { const _: () = {
#[derive(Clone, Debug)] #[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 { impl IntoIterator for $struct_type {
type Item = $enum_type; type Item = $enum_type;
type IntoIter = Iter; type IntoIter = Iter;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
Iter(self) Iter {
$($Variant: self.$Variant,)*
$($Unknown: self.$Unknown.into_iter(),)?
}
} }
} }
impl Iterator for Iter { impl Iterator for Iter {
type Item = $enum_type; type Item = $enum_type;
fn next(&mut self) -> Option<Self::Item> { 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)); return Some($enum_type::$Variant(value));
} }
)* )*
$(
if let Some(value) = self.$Unknown.next() {
return Some($enum_type::$Unknown(value));
}
)?
None None
} }
#[allow(unused_mut, unused_variables)] #[allow(unused_mut, unused_variables)]
fn fold<B, F: FnMut(B, Self::Item) -> B>(mut self, mut init: B, mut f: F) -> B { 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)); init = f(init, $enum_type::$Variant(value));
} }
)* )*
$(
if let Some(value) = self.$Unknown.next() {
init = f(init, $enum_type::$Unknown(value));
}
)?
init init
} }
} }
@ -2173,6 +2226,9 @@ macro_rules! impl_bounds {
$($enum_type::$Variant(v) => { $($enum_type::$Variant(v) => {
self.$Variant = Some(v); self.$Variant = Some(v);
})* })*
$($enum_type::$Unknown(v) => {
self.$Unknown.push(v);
})?
}); });
} }
} }
@ -2191,6 +2247,7 @@ macro_rules! impl_bounds {
$(if let Some(v) = v.$Variant { $(if let Some(v) = v.$Variant {
self.$Variant = Some(v); self.$Variant = Some(v);
})* })*
$(self.$Unknown.extend(v.$Unknown);)*
}); });
} }
} }
@ -2244,6 +2301,8 @@ impl_bounds! {
Size, Size,
StaticType, StaticType,
Type, Type,
#[unknown]
Unknown,
} }
} }
@ -2257,6 +2316,8 @@ impl_bounds! {
ResetType, ResetType,
StaticType, StaticType,
Type, Type,
#[unknown]
Unknown,
} }
} }
@ -2270,6 +2331,7 @@ impl From<ParsedTypeBound> for ParsedBound {
ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v),
ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v),
ParsedTypeBound::Type(v) => ParsedBound::Type(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v),
ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v),
} }
} }
} }
@ -2284,6 +2346,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
ResetType, ResetType,
StaticType, StaticType,
Type, Type,
Unknown,
} = value; } = value;
Self { Self {
BoolOrIntType, BoolOrIntType,
@ -2295,6 +2358,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
Size: None, Size: None,
StaticType, StaticType,
Type, Type,
Unknown,
} }
} }
} }
@ -2330,6 +2394,7 @@ impl ParsedTypeBound {
ParsedTypeBound::Type(known_items::Type(span)), ParsedTypeBound::Type(known_items::Type(span)),
]), ]),
Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]),
Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]),
} }
} }
} }
@ -2364,6 +2429,7 @@ impl From<ParsedSizeTypeBounds> for ParsedBounds {
Size, Size,
StaticType: None, StaticType: None,
Type: None, Type: None,
Unknown: vec![],
} }
} }
} }
@ -2391,6 +2457,7 @@ impl ParsedBounds {
fn categorize(self, errors: &mut Errors, span: Span) -> ParsedBoundsCategory { fn categorize(self, errors: &mut Errors, span: Span) -> ParsedBoundsCategory {
let mut type_bounds = None; let mut type_bounds = None;
let mut size_type_bounds = None; let mut size_type_bounds = None;
let mut unknown_bounds = vec![];
self.into_iter().for_each(|bound| match bound.categorize() { self.into_iter().for_each(|bound| match bound.categorize() {
ParsedBoundCategory::Type(bound) => { ParsedBoundCategory::Type(bound) => {
type_bounds type_bounds
@ -2402,15 +2469,37 @@ impl ParsedBounds {
.get_or_insert_with(ParsedSizeTypeBounds::default) .get_or_insert_with(ParsedSizeTypeBounds::default)
.extend([bound]); .extend([bound]);
} }
ParsedBoundCategory::Unknown(bound) => unknown_bounds.push(bound),
}); });
match (type_bounds, size_type_bounds) { match (type_bounds, size_type_bounds, unknown_bounds.is_empty()) {
(None, None) => ParsedBoundsCategory::Type(ParsedTypeBounds { (None, None, true) => ParsedBoundsCategory::Type(ParsedTypeBounds {
Type: Some(known_items::Type(span)), Type: Some(known_items::Type(span)),
..Default::default() ..Default::default()
}), }),
(None, Some(bounds)) => ParsedBoundsCategory::SizeType(bounds), (None, None, false) => {
(Some(bounds), None) => ParsedBoundsCategory::Type(bounds), errors.error(
(Some(type_bounds), Some(size_type_bounds)) => { 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( errors.error(
size_type_bounds size_type_bounds
.Size .Size
@ -2427,6 +2516,7 @@ impl ParsedBounds {
pub(crate) enum ParsedBoundCategory { pub(crate) enum ParsedBoundCategory {
Type(ParsedTypeBound), Type(ParsedTypeBound),
SizeType(ParsedSizeTypeBound), SizeType(ParsedSizeTypeBound),
Unknown(syn::TypeParamBound),
} }
impl ParsedBound { impl ParsedBound {
@ -2441,12 +2531,14 @@ impl ParsedBound {
Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)),
Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)),
Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)),
Self::Unknown(v) => ParsedBoundCategory::Unknown(v),
} }
} }
fn implied_bounds(self) -> ParsedBounds { fn implied_bounds(self) -> ParsedBounds {
match self.categorize() { match self.categorize() {
ParsedBoundCategory::Type(v) => v.implied_bounds().into(), ParsedBoundCategory::Type(v) => v.implied_bounds().into(),
ParsedBoundCategory::SizeType(v) => v.implied_bounds().into(), ParsedBoundCategory::SizeType(v) => v.implied_bounds().into(),
ParsedBoundCategory::Unknown(v) => ParsedBounds::from_iter([ParsedBound::Unknown(v)]),
} }
} }
} }
@ -3325,7 +3417,7 @@ impl ParsedGenerics {
| ParsedTypeBound::EnumType(_) | ParsedTypeBound::EnumType(_)
| ParsedTypeBound::IntType(_) | ParsedTypeBound::IntType(_)
| ParsedTypeBound::ResetType(_) => { | ParsedTypeBound::ResetType(_) => {
errors.error(bound, "bound on mask type not implemented"); errors.error(bound, "bounds on mask types are not implemented");
} }
ParsedTypeBound::StaticType(bound) => { ParsedTypeBound::StaticType(bound) => {
if bounds.StaticType.is_none() { if bounds.StaticType.is_none() {
@ -3337,6 +3429,12 @@ impl ParsedGenerics {
} }
} }
ParsedTypeBound::Type(_) => {} ParsedTypeBound::Type(_) => {}
ParsedTypeBound::Unknown(_) => {
errors.error(
bound,
"unknown bounds on mask types are not implemented",
);
}
} }
} }
bounds.add_implied_bounds(); bounds.add_implied_bounds();

View file

@ -1210,17 +1210,29 @@ fn hdl_main(
attr: TokenStream, attr: TokenStream,
item: TokenStream, item: TokenStream,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let (evaluated_cfgs, attr): (_, TokenStream) = Parser::parse2( fn parse_evaluated_cfgs_attr<R>(
input: ParseStream,
parse_inner: impl FnOnce(ParseStream) -> syn::Result<R>,
) -> syn::Result<R> {
let _: Token![#] = input.parse()?;
let bracket_content;
bracketed!(bracket_content in input);
let _: kw::__evaluated_cfgs = bracket_content.parse()?;
let paren_content;
parenthesized!(paren_content in bracket_content);
parse_inner(&paren_content)
}
let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2(
|input: ParseStream| { |input: ParseStream| {
if input.peek(Bracket) && input.peek2(kw::__evaluated_cfgs) { let peek = input.fork();
let cfgs = input.parse()?; if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() {
let _: kw::__evaluated_cfgs = input.parse()?; let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::<bool>::parse)?;
Ok((Some(cfgs), input.parse()?)) Ok((Some(evaluated_cfgs), input.parse()?))
} else { } else {
Ok((None, input.parse()?)) Ok((None, input.parse()?))
} }
}, },
attr, item,
)?; )?;
let cfgs = if let Some(cfgs) = evaluated_cfgs { let cfgs = if let Some(cfgs) = evaluated_cfgs {
cfgs cfgs
@ -1229,12 +1241,11 @@ fn hdl_main(
if cfgs.cfgs_list.is_empty() { if cfgs.cfgs_list.is_empty() {
Cfgs::default() Cfgs::default()
} else { } else {
let key = kw::__evaluated_cfgs::default();
return Ok(quote! { return Ok(quote! {
::fayalite::__cfg_expansion_helper! { ::fayalite::__cfg_expansion_helper! {
[] []
#cfgs #cfgs
#[::fayalite::#kw:(#key #attr)] { #item } {#[::fayalite::#kw(#attr)]} { #item }
} }
}); });
} }

View file

@ -1109,7 +1109,7 @@ fn parse_quote_let_pat<T, R: ToTokens, C: Borrow<Token![:]>>(
} }
} }
fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
parse_quote_spanned! {ty.span()=> parse_quote_spanned! {ty.span()=>
::fayalite::expr::Expr<#ty> ::fayalite::expr::Expr<#ty>
} }
@ -1586,7 +1586,7 @@ impl Visitor<'_> {
} }
} }
fn empty_let() -> Local { pub(crate) fn empty_let() -> Local {
Local { Local {
attrs: vec![], attrs: vec![],
let_token: Default::default(), let_token: Default::default(),
@ -1672,7 +1672,7 @@ impl Fold for Visitor<'_> {
} }
} }
fn fold_local(&mut self, let_stmt: Local) -> Local { fn fold_local(&mut self, mut let_stmt: Local) -> Local {
match self match self
.errors .errors
.ok(HdlAttr::<Nothing, kw::hdl>::parse_and_leave_attr( .ok(HdlAttr::<Nothing, kw::hdl>::parse_and_leave_attr(
@ -1682,6 +1682,25 @@ impl Fold for Visitor<'_> {
Some(None) => return fold_local(self, let_stmt), Some(None) => return fold_local(self, let_stmt),
Some(Some(HdlAttr { .. })) => {} Some(Some(HdlAttr { .. })) => {}
}; };
let mut pat = &let_stmt.pat;
if let Pat::Type(pat_type) = pat {
pat = &pat_type.pat;
}
let Pat::Ident(syn::PatIdent {
attrs: _,
by_ref: None,
mutability: _,
ident: _,
subpat: None,
}) = pat
else {
let hdl_attr = HdlAttr::<Nothing, kw::hdl>::parse_and_take_attr(&mut let_stmt.attrs)
.ok()
.flatten()
.expect("already checked above");
let let_stmt = fold_local(self, let_stmt);
return self.process_hdl_let_pat(hdl_attr, let_stmt);
};
let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream()); let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream());
let Some(hdl_let) = self.errors.ok(hdl_let) else { let Some(hdl_let) = self.errors.ok(hdl_let) else {
return empty_let(); return empty_let();

View file

@ -3,22 +3,111 @@
use crate::{ use crate::{
fold::{impl_fold, DoFold}, fold::{impl_fold, DoFold},
kw, kw,
module::transform_body::{with_debug_clone_and_fold, Visitor}, module::transform_body::{empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, Visitor},
Errors, HdlAttr, PairsIterExt, Errors, HdlAttr, PairsIterExt,
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
use std::collections::BTreeSet;
use syn::{ use syn::{
fold::{fold_arm, fold_expr_match, fold_pat, Fold}, fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold},
parse::Nothing, parse::Nothing,
parse_quote_spanned, parse_quote_spanned,
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned, spanned::Spanned,
token::{Brace, Paren}, token::{Brace, Paren},
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen, Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr,
PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath, PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment,
Token, TypePath,
}; };
macro_rules! visit_trait {
(
$($vis:vis fn $fn:ident($state:ident: _, $value:ident: &$Value:ty) $block:block)*
) => {
trait VisitMatchPat<'a> {
$(fn $fn(&mut self, $value: &'a $Value) {
$fn(self, $value);
})*
}
$($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a $Value) $block)*
};
}
visit_trait! {
fn visit_match_pat_binding(_state: _, v: &MatchPatBinding) {
let MatchPatBinding { ident: _ } = v;
}
fn visit_match_pat_wild(_state: _, v: &MatchPatWild) {
let MatchPatWild { underscore_token: _ } = v;
}
fn visit_match_pat_rest(_state: _, v: &MatchPatRest) {
let MatchPatRest { dot2_token: _ } = v;
}
fn visit_match_pat_paren(state: _, v: &MatchPatParen<MatchPat>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat(pat);
}
fn visit_match_pat_paren_simple(state: _, v: &MatchPatParen<MatchPatSimple>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_or(state: _, v: &MatchPatOr<MatchPat>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat(v);
}
}
fn visit_match_pat_or_simple(state: _, v: &MatchPatOr<MatchPatSimple>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_struct_field(state: _, v: &MatchPatStructField) {
let MatchPatStructField { field_name: _, colon_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_struct(state: _, v: &MatchPatStruct) {
let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v;
for v in fields {
state.visit_match_pat_struct_field(v);
}
}
fn visit_match_pat_tuple(state: _, v: &MatchPatTuple) {
let MatchPatTuple { paren_token: _, fields } = v;
for v in fields {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) {
let MatchPatEnumVariant {match_span:_, variant_path: _, enum_path: _, variant_name: _, field } = v;
if let Some((_, v)) = field {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_simple(state: _, v: &MatchPatSimple) {
match v {
MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v),
MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v),
MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v),
MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v),
MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v),
}
}
fn visit_match_pat(state: _, v: &MatchPat) {
match v {
MatchPat::Simple(v) => state.visit_match_pat_simple(v),
MatchPat::Or(v) => state.visit_match_pat_or(v),
MatchPat::Paren(v) => state.visit_match_pat_paren(v),
MatchPat::Struct(v) => state.visit_match_pat_struct(v),
MatchPat::Tuple(v) => state.visit_match_pat_tuple(v),
MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v),
}
}
}
with_debug_clone_and_fold! { with_debug_clone_and_fold! {
struct MatchPatBinding<> { struct MatchPatBinding<> {
ident: Ident, ident: Ident,
@ -53,6 +142,15 @@ with_debug_clone_and_fold! {
} }
} }
impl<P> MatchPatOr<P> {
/// returns the first `|` between two patterns
fn first_inner_vert(&self) -> Option<Token![|]> {
let mut pairs = self.cases.pairs();
pairs.next_back();
pairs.next().and_then(|v| v.into_tuple().1.copied())
}
}
impl<P: ToTokens> ToTokens for MatchPatOr<P> { impl<P: ToTokens> ToTokens for MatchPatOr<P> {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { let Self {
@ -77,6 +175,19 @@ impl ToTokens for MatchPatWild {
} }
} }
with_debug_clone_and_fold! {
struct MatchPatRest<> {
dot2_token: Token![..],
}
}
impl ToTokens for MatchPatRest {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { dot2_token } = self;
dot2_token.to_tokens(tokens);
}
}
with_debug_clone_and_fold! { with_debug_clone_and_fold! {
struct MatchPatStructField<> { struct MatchPatStructField<> {
field_name: Ident, field_name: Ident,
@ -159,6 +270,25 @@ impl ToTokens for MatchPatStruct {
} }
} }
with_debug_clone_and_fold! {
struct MatchPatTuple<> {
paren_token: Paren,
fields: Punctuated<MatchPatSimple, Token![,]>,
}
}
impl ToTokens for MatchPatTuple {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
paren_token,
fields,
} = self;
paren_token.surround(tokens, |tokens| {
fields.to_tokens(tokens);
})
}
}
with_debug_clone_and_fold! { with_debug_clone_and_fold! {
struct MatchPatEnumVariant<> { struct MatchPatEnumVariant<> {
match_span: Span, match_span: Span,
@ -194,6 +324,7 @@ enum MatchPatSimple {
Or(MatchPatOr<MatchPatSimple>), Or(MatchPatOr<MatchPatSimple>),
Binding(MatchPatBinding), Binding(MatchPatBinding),
Wild(MatchPatWild), Wild(MatchPatWild),
Rest(MatchPatRest),
} }
impl_fold! { impl_fold! {
@ -202,6 +333,7 @@ impl_fold! {
Or(MatchPatOr<MatchPatSimple>), Or(MatchPatOr<MatchPatSimple>),
Binding(MatchPatBinding), Binding(MatchPatBinding),
Wild(MatchPatWild), Wild(MatchPatWild),
Rest(MatchPatRest),
} }
} }
@ -212,6 +344,7 @@ impl ToTokens for MatchPatSimple {
Self::Paren(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens),
Self::Binding(v) => v.to_tokens(tokens), Self::Binding(v) => v.to_tokens(tokens),
Self::Wild(v) => v.to_tokens(tokens), Self::Wild(v) => v.to_tokens(tokens),
Self::Rest(v) => v.to_tokens(tokens),
} }
} }
} }
@ -278,6 +411,7 @@ trait ParseMatchPat: Sized {
fn or(v: MatchPatOr<Self>) -> Self; fn or(v: MatchPatOr<Self>) -> Self;
fn paren(v: MatchPatParen<Self>) -> Self; fn paren(v: MatchPatParen<Self>) -> Self;
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>; fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()>;
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant) fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
-> Result<Self, ()>; -> Result<Self, ()>;
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> { fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
@ -462,7 +596,34 @@ trait ParseMatchPat: Sized {
}) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild {
underscore_token, underscore_token,
}))), }))),
Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { Pat::Tuple(PatTuple {
attrs: _,
paren_token,
elems,
}) => {
let fields = elems
.into_pairs()
.filter_map_pair_value(|field_pat| {
if let Pat::Rest(PatRest {
attrs: _,
dot2_token,
}) = field_pat
{
Some(MatchPatSimple::Rest(MatchPatRest { dot2_token }))
} else {
MatchPatSimple::parse(state, field_pat).ok()
}
})
.collect();
Self::tuple(
state,
MatchPatTuple {
paren_token,
fields,
},
)
}
Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => {
state state
.errors .errors
.error(pat, "not yet implemented in #[hdl] patterns"); .error(pat, "not yet implemented in #[hdl] patterns");
@ -497,6 +658,14 @@ impl ParseMatchPat for MatchPatSimple {
Err(()) Err(())
} }
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
state.errors.push(syn::Error::new(
v.paren_token.span.open(),
"matching tuples is not yet implemented inside structs/enums in #[hdl] patterns",
));
Err(())
}
fn enum_variant( fn enum_variant(
state: &mut HdlMatchParseState<'_>, state: &mut HdlMatchParseState<'_>,
v: MatchPatEnumVariant, v: MatchPatEnumVariant,
@ -515,6 +684,7 @@ enum MatchPat {
Or(MatchPatOr<MatchPat>), Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>), Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct), Struct(MatchPatStruct),
Tuple(MatchPatTuple),
EnumVariant(MatchPatEnumVariant), EnumVariant(MatchPatEnumVariant),
} }
@ -524,6 +694,7 @@ impl_fold! {
Or(MatchPatOr<MatchPat>), Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>), Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct), Struct(MatchPatStruct),
Tuple(MatchPatTuple),
EnumVariant(MatchPatEnumVariant), EnumVariant(MatchPatEnumVariant),
} }
} }
@ -545,6 +716,10 @@ impl ParseMatchPat for MatchPat {
Ok(Self::Struct(v)) Ok(Self::Struct(v))
} }
fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
Ok(Self::Tuple(v))
}
fn enum_variant( fn enum_variant(
_state: &mut HdlMatchParseState<'_>, _state: &mut HdlMatchParseState<'_>,
v: MatchPatEnumVariant, v: MatchPatEnumVariant,
@ -560,6 +735,7 @@ impl ToTokens for MatchPat {
Self::Or(v) => v.to_tokens(tokens), Self::Or(v) => v.to_tokens(tokens),
Self::Paren(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens),
Self::Struct(v) => v.to_tokens(tokens), Self::Struct(v) => v.to_tokens(tokens),
Self::Tuple(v) => v.to_tokens(tokens),
Self::EnumVariant(v) => v.to_tokens(tokens), Self::EnumVariant(v) => v.to_tokens(tokens),
} }
} }
@ -622,10 +798,6 @@ struct RewriteAsCheckMatch {
} }
impl Fold for RewriteAsCheckMatch { impl Fold for RewriteAsCheckMatch {
fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat {
i.colon_token = Some(Token![:](i.member.span()));
i
}
fn fold_pat(&mut self, pat: Pat) -> Pat { fn fold_pat(&mut self, pat: Pat) -> Pat {
match pat { match pat {
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) { Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
@ -740,6 +912,30 @@ impl Fold for RewriteAsCheckMatch {
// don't recurse into expressions // don't recurse into expressions
i i
} }
fn fold_local(&mut self, mut let_stmt: Local) -> Local {
if let Some(syn::LocalInit {
eq_token,
expr: _,
diverge,
}) = let_stmt.init.take()
{
let_stmt.init = Some(syn::LocalInit {
eq_token,
expr: parse_quote_spanned! {self.span=>
__match_value
},
diverge: diverge.map(|(else_, _expr)| {
(
else_,
parse_quote_spanned! {self.span=>
match __infallible {}
},
)
}),
});
}
fold_local(self, let_stmt)
}
} }
struct HdlMatchParseState<'a> { struct HdlMatchParseState<'a> {
@ -747,7 +943,123 @@ struct HdlMatchParseState<'a> {
errors: &'a mut Errors, errors: &'a mut Errors,
} }
struct HdlLetPatVisitState<'a> {
errors: &'a mut Errors,
bindings: BTreeSet<&'a Ident>,
}
impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
fn visit_match_pat_binding(&mut self, v: &'a MatchPatBinding) {
self.bindings.insert(&v.ident);
}
fn visit_match_pat_or(&mut self, v: &'a MatchPatOr<MatchPat>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
"or-patterns are not supported in let statements",
);
}
visit_match_pat_or(self, v);
}
fn visit_match_pat_or_simple(&mut self, v: &'a MatchPatOr<MatchPatSimple>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
"or-patterns are not supported in let statements",
);
}
visit_match_pat_or_simple(self, v);
}
fn visit_match_pat_enum_variant(&mut self, v: &'a MatchPatEnumVariant) {
self.errors.error(v, "refutable pattern in let statement");
}
}
impl Visitor<'_> { impl Visitor<'_> {
pub(crate) fn process_hdl_let_pat(
&mut self,
_hdl_attr: HdlAttr<Nothing, kw::hdl>,
mut let_stmt: Local,
) -> Local {
let span = let_stmt.let_token.span();
if let Pat::Type(pat) = &mut let_stmt.pat {
*pat.ty = wrap_ty_with_expr((*pat.ty).clone());
}
let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone());
let Local {
attrs: _,
let_token,
pat,
init,
semi_token,
} = let_stmt;
self.require_normal_module_or_fn(let_token);
let Some(syn::LocalInit {
eq_token,
expr,
diverge,
}) = init
else {
self.errors
.error(let_token, "#[hdl] let must be assigned a value");
return empty_let();
};
if let Some((else_, _)) = diverge {
// TODO: implement let-else
self.errors
.error(else_, "#[hdl] let ... else { ... } is not implemented");
return empty_let();
}
let Ok(pat) = MatchPat::parse(
&mut HdlMatchParseState {
match_span: span,
errors: &mut self.errors,
},
pat,
) else {
return empty_let();
};
let mut state = HdlLetPatVisitState {
errors: &mut self.errors,
bindings: BTreeSet::new(),
};
state.visit_match_pat(&pat);
let HdlLetPatVisitState {
errors: _,
bindings,
} = state;
let retval = parse_quote_spanned! {span=>
let (#(#bindings,)* __scope,) = {
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| {
#[allow(unused_variables)]
#check_let_stmt
match __infallible {}
});
let mut __match_iter = ::fayalite::module::match_(__match_expr);
let ::fayalite::__std::option::Option::Some(__match_variant) = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else {
::fayalite::__std::unreachable!("#[hdl] let with uninhabited type");
};
let ::fayalite::__std::option::Option::None = ::fayalite::__std::iter::Iterator::next(&mut __match_iter) else {
::fayalite::__std::unreachable!("#[hdl] let with refutable pattern");
};
let (__match_variant, __scope) =
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
__match_variant,
);
#let_token #pat #eq_token __match_variant #semi_token
(#(#bindings,)* __scope,)
};
};
match retval {
syn::Stmt::Local(retval) => retval,
_ => unreachable!(),
}
}
pub(crate) fn process_hdl_match( pub(crate) fn process_hdl_match(
&mut self, &mut self,
_hdl_attr: HdlAttr<Nothing, kw::hdl>, _hdl_attr: HdlAttr<Nothing, kw::hdl>,

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
//! ## `#[hdl] let` statements //! ## `#[hdl] let` statements
pub mod destructuring;
pub mod inputs_outputs; pub mod inputs_outputs;
pub mod instances; pub mod instances;
pub mod memories; pub mod memories;

View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Destructuring Let
//!
//! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns.
//!
//! `#[hdl] let` statements can only match one level of struct/tuple pattern for now,
//! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`.
//!
//! ```
//! # use fayalite::prelude::*;
//! #[hdl]
//! struct MyStruct {
//! a: UInt<8>,
//! b: Bool,
//! }
//!
//! #[hdl_module]
//! fn my_module() {
//! #[hdl]
//! let my_input: MyStruct = m.input();
//! #[hdl]
//! let my_output: UInt<8> = m.input();
//! #[hdl]
//! let MyStruct { a, b } = my_input;
//! #[hdl]
//! if b {
//! connect(my_output, a);
//! } else {
//! connect(my_output, 0_hdl_u8);
//! }
//! }
//! ```

View file

@ -7,5 +7,5 @@
//! //!
//! `#[hdl] match` statements' bodies must evaluate to type `()` for now. //! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
//! //!
//! `#[hdl] match` statements can only match one level of struct/enum pattern for now, //! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now,
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`. //! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.

View file

@ -567,12 +567,12 @@ impl_prim_int!(i64, SInt<64>);
impl_prim_int!(i128, SInt<128>); impl_prim_int!(i128, SInt<128>);
impl_prim_int!( impl_prim_int!(
/// for portability reasons, [`usize`] always translates to [`UInt<64>`] /// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt]
usize, UInt<64> usize, UInt<64>
); );
impl_prim_int!( impl_prim_int!(
/// for portability reasons, [`isize`] always translates to [`SInt<64>`] /// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt]
isize, SInt<64> isize, SInt<64>
); );

View file

@ -22,7 +22,8 @@ macro_rules! __cfg_expansion_helper {
$cfg:ident($($expr:tt)*), $cfg:ident($($expr:tt)*),
$($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)* $($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)*
] ]
#[$after_evaluation:path:($($after_evaluation_attr_args:tt)*)] {$($after_evaluation_args:tt)*} // pass as tt so we get right span for attribute
$after_evaluation_attr:tt $after_evaluation_body:tt
) => { ) => {
#[$cfg($($expr)*)] #[$cfg($($expr)*)]
$crate::__cfg_expansion_helper! { $crate::__cfg_expansion_helper! {
@ -33,7 +34,7 @@ macro_rules! __cfg_expansion_helper {
[ [
$($unevaluated_cfgs($($unevaluated_exprs)*),)* $($unevaluated_cfgs($($unevaluated_exprs)*),)*
] ]
#[$after_evaluation:($($after_evaluation_attr_args)*)] {$($after_evaluation_args)*} $after_evaluation_attr $after_evaluation_body
} }
#[$cfg(not($($expr)*))] #[$cfg(not($($expr)*))]
$crate::__cfg_expansion_helper! { $crate::__cfg_expansion_helper! {
@ -44,7 +45,7 @@ macro_rules! __cfg_expansion_helper {
[ [
$($unevaluated_cfgs($($unevaluated_exprs)*),)* $($unevaluated_cfgs($($unevaluated_exprs)*),)*
] ]
#[$after_evaluation:($($after_evaluation_attr_args)*)] {$($after_evaluation_args)*} $after_evaluation_attr $after_evaluation_body
} }
}; };
( (
@ -52,12 +53,14 @@ macro_rules! __cfg_expansion_helper {
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
] ]
[] []
#[$after_evaluation:path:($($after_evaluation_attr_args:tt)*)] {$($after_evaluation_args:tt)*} // don't use #[...] so we get right span for `#` and `[]` of attribute
{$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*}
) => { ) => {
#[$after_evaluation([ $($after_evaluation_attr)*
#[__evaluated_cfgs([
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
] $($after_evaluation_attr_args)*)] ])]
$($after_evaluation_args)* $($after_evaluation_body)*
}; };
} }

View file

@ -4773,6 +4773,9 @@ impl Compiler {
} }
self.insns.extend(insns.iter().copied(), *source_location); self.insns.extend(insns.iter().copied(), *source_location);
} }
for CondStackEntry { cond: _, end_label } in cond_stack {
self.insns.define_label_at_next_insn(end_label);
}
} }
fn process_clocks(&mut self) -> Interned<[StatePartIndex<StatePartKindSmallSlots>]> { fn process_clocks(&mut self) -> Interned<[StatePartIndex<StatePartKindSmallSlots>]> {
mem::take(&mut self.clock_triggers) mem::take(&mut self.clock_triggers)

View file

@ -5,6 +5,7 @@ use crate::{
enum_::{Enum, EnumType}, enum_::{Enum, EnumType},
expr::Flow, expr::Flow,
int::UInt, int::UInt,
intern::{Intern, Interned},
sim::{ sim::{
time::{SimDuration, SimInstant}, time::{SimDuration, SimInstant},
TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl,
@ -15,12 +16,73 @@ use crate::{
}, },
}; };
use bitvec::{order::Lsb0, slice::BitSlice}; use bitvec::{order::Lsb0, slice::BitSlice};
use hashbrown::{hash_map::Entry, HashMap};
use std::{ use std::{
fmt, fmt::{self, Write as _},
io::{self, Write}, io, mem,
mem,
}; };
#[derive(Default)]
struct Scope {
last_inserted: HashMap<Interned<str>, usize>,
}
#[derive(Copy, Clone)]
struct VerilogIdentifier {
unescaped_name: Interned<str>,
}
impl VerilogIdentifier {
fn needs_escape(self) -> bool {
// we only allow ascii, so we can just check bytes
let Some((&first, rest)) = self.unescaped_name.as_bytes().split_first() else {
unreachable!("Scope::new_identifier guarantees a non-empty name");
};
if !first.is_ascii_alphabetic() && first != b'_' {
true
} else {
rest.iter()
.any(|&ch| !ch.is_ascii_alphanumeric() && ch != b'_' && ch != b'$')
}
}
}
impl fmt::Display for VerilogIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.needs_escape() {
f.write_str("\\")?;
}
write!(f, "{}", Escaped(self.unescaped_name))
}
}
impl Scope {
fn new_identifier(&mut self, unescaped_name: Interned<str>) -> VerilogIdentifier {
let next_disambiguator = match self.last_inserted.entry(unescaped_name) {
Entry::Vacant(entry) => {
entry.insert(1);
return VerilogIdentifier { unescaped_name };
}
Entry::Occupied(entry) => entry.get() + 1,
};
let mut disambiguated_name = String::from(&*unescaped_name);
for disambiguator in next_disambiguator.. {
disambiguated_name.truncate(unescaped_name.len());
write!(disambiguated_name, "_{disambiguator}").expect("can't fail");
if let Entry::Vacant(entry) = self.last_inserted.entry((*disambiguated_name).intern()) {
let retval = VerilogIdentifier {
unescaped_name: *entry.key(),
};
entry.insert(1);
// speed up future searches
self.last_inserted.insert(unescaped_name, disambiguator);
return retval;
}
}
panic!("too many names");
}
}
pub struct VcdWriterDecls<W: io::Write + 'static> { pub struct VcdWriterDecls<W: io::Write + 'static> {
writer: W, writer: W,
timescale: SimDuration, timescale: SimDuration,
@ -97,14 +159,20 @@ impl<W: io::Write> fmt::Debug for VcdWriterDecls<W> {
} }
} }
/// pass in scope to ensure it's not available in child scope
fn write_vcd_scope<W: io::Write, R>( fn write_vcd_scope<W: io::Write, R>(
writer: &mut W, writer: &mut W,
scope_type: &str, scope_type: &str,
scope_name: &str, scope_name: Interned<str>,
f: impl FnOnce(&mut W) -> io::Result<R>, scope: &mut Scope,
f: impl FnOnce(&mut W, &mut Scope) -> io::Result<R>,
) -> io::Result<R> { ) -> io::Result<R> {
writeln!(writer, "$scope {scope_type} {scope_name} $end")?; writeln!(
let retval = f(writer)?; writer,
"$scope {scope_type} {} $end",
scope.new_identifier(scope_name),
)?;
let retval = f(writer, &mut Scope::default())?;
writeln!(writer, "$upscope $end")?; writeln!(writer, "$upscope $end")?;
Ok(retval) Ok(retval)
} }
@ -143,24 +211,28 @@ trait_arg! {
struct ArgModule<'a> { struct ArgModule<'a> {
properties: &'a mut VcdWriterProperties, properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
} }
impl<'a> ArgModule<'a> { impl<'a> ArgModule<'a> {
fn reborrow(&mut self) -> ArgModule<'_> { fn reborrow(&mut self) -> ArgModule<'_> {
ArgModule { ArgModule {
properties: self.properties, properties: self.properties,
scope: self.scope,
} }
} }
} }
struct ArgModuleBody<'a> { struct ArgModuleBody<'a> {
properties: &'a mut VcdWriterProperties, properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
} }
impl<'a> ArgModuleBody<'a> { impl<'a> ArgModuleBody<'a> {
fn reborrow(&mut self) -> ArgModuleBody<'_> { fn reborrow(&mut self) -> ArgModuleBody<'_> {
ArgModuleBody { ArgModuleBody {
properties: self.properties, properties: self.properties,
scope: self.scope,
} }
} }
} }
@ -170,6 +242,7 @@ struct ArgInType<'a> {
sink_var_type: &'static str, sink_var_type: &'static str,
duplex_var_type: &'static str, duplex_var_type: &'static str,
properties: &'a mut VcdWriterProperties, properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
} }
impl<'a> ArgInType<'a> { impl<'a> ArgInType<'a> {
@ -179,6 +252,7 @@ impl<'a> ArgInType<'a> {
sink_var_type: self.sink_var_type, sink_var_type: self.sink_var_type,
duplex_var_type: self.duplex_var_type, duplex_var_type: self.duplex_var_type,
properties: self.properties, properties: self.properties,
scope: self.scope,
} }
} }
} }
@ -226,55 +300,42 @@ fn write_vcd_id<W: io::Write>(writer: &mut W, mut id: usize) -> io::Result<()> {
Ok(()) Ok(())
} }
fn write_escaped<W: io::Write>(writer: &mut W, value: impl fmt::Display) -> io::Result<()> { struct Escaped<T: fmt::Display>(T);
impl<T: fmt::Display> fmt::Display for Escaped<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// escaping rules from function GTKWave uses to decode VCD strings: // escaping rules from function GTKWave uses to decode VCD strings:
// https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090 // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
struct Wrapper<W>(W); struct Wrapper<W>(W);
impl<W: io::Write> io::Write for Wrapper<W> { impl<W: fmt::Write> fmt::Write for Wrapper<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write_str(&mut self, s: &str) -> fmt::Result {
if buf.is_empty() { for byte in s.bytes() {
return self.0.write(buf);
}
let mut retval = 0;
for &byte in buf {
match byte { match byte {
b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?, b'\\' | b'\'' | b'"' | b'?' => {
b'\n' => self.0.write_all(br"\n")?, self.0.write_str("\\")?;
b'\r' => self.0.write_all(br"\r")?, self.0.write_char(byte as char)?;
b'\t' => self.0.write_all(br"\t")?, }
0x7 => self.0.write_all(br"\a")?, b'\n' => self.0.write_str(r"\n")?,
0x8 => self.0.write_all(br"\b")?, b'\r' => self.0.write_str(r"\r")?,
0xC => self.0.write_all(br"\f")?, b'\t' => self.0.write_str(r"\t")?,
0xB => self.0.write_all(br"\v")?, 0x7 => self.0.write_str(r"\a")?,
0x8 => self.0.write_str(r"\b")?,
0xC => self.0.write_str(r"\f")?,
0xB => self.0.write_str(r"\v")?,
_ => { _ => {
if byte.is_ascii_graphic() { if byte.is_ascii_graphic() {
self.0.write_all(&[byte])?; self.0.write_char(byte as char)?;
} else { } else {
write!(self.0, r"\x{byte:02x}")?; write!(self.0, r"\x{byte:02x}")?;
} }
} }
} }
retval += 1;
} }
Ok(retval) Ok(())
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
} }
} }
write!(Wrapper(writer), "{value}") write!(Wrapper(f), "{}", self.0)
} }
fn is_unescaped_verilog_identifier(ident: &str) -> bool {
// we only allow ascii, so we can just check bytes
let Some((&first, rest)) = ident.as_bytes().split_first() else {
return false; // empty string is not an identifier
};
(first.is_ascii_alphabetic() || first == b'_')
&& rest
.iter()
.all(|&ch| ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'$')
} }
fn write_vcd_var<W: io::Write>( fn write_vcd_var<W: io::Write>(
@ -284,7 +345,7 @@ fn write_vcd_var<W: io::Write>(
var_type: &str, var_type: &str,
size: usize, size: usize,
location: TraceLocation, location: TraceLocation,
name: &str, name: VerilogIdentifier,
) -> io::Result<()> { ) -> io::Result<()> {
let id = match location { let id = match location {
TraceLocation::Scalar(id) => id.as_usize(), TraceLocation::Scalar(id) => id.as_usize(),
@ -319,12 +380,7 @@ fn write_vcd_var<W: io::Write>(
}; };
write!(writer, "$var {var_type} {size} ")?; write!(writer, "$var {var_type} {size} ")?;
write_vcd_id(writer, id)?; write_vcd_id(writer, id)?;
writer.write_all(b" ")?; writeln!(writer, " {name} $end")
if !is_unescaped_verilog_identifier(name) {
writer.write_all(b"\\")?;
}
write_escaped(writer, name)?;
writer.write_all(b" $end\n")
} }
impl WriteTrace for TraceUInt { impl WriteTrace for TraceUInt {
@ -334,6 +390,7 @@ impl WriteTrace for TraceUInt {
sink_var_type, sink_var_type,
duplex_var_type, duplex_var_type,
properties, properties,
scope,
} = arg.in_type(); } = arg.in_type();
let Self { let Self {
location, location,
@ -356,7 +413,7 @@ impl WriteTrace for TraceUInt {
var_type, var_type,
ty.width(), ty.width(),
location, location,
&name, scope.new_identifier(name),
) )
} }
} }
@ -421,6 +478,7 @@ impl WriteTrace for TraceEnumDiscriminant {
sink_var_type: _, sink_var_type: _,
duplex_var_type: _, duplex_var_type: _,
properties, properties,
scope,
} = arg.in_type(); } = arg.in_type();
let Self { let Self {
location, location,
@ -435,7 +493,7 @@ impl WriteTrace for TraceEnumDiscriminant {
"string", "string",
1, 1,
location, location,
&name, scope.new_identifier(name),
) )
} }
} }
@ -507,11 +565,11 @@ impl WriteTrace for TraceScope {
impl WriteTrace for TraceModule { impl WriteTrace for TraceModule {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModule { properties } = arg.module(); let ArgModule { properties, scope } = arg.module();
let Self { name, children } = self; let Self { name, children } = self;
write_vcd_scope(writer, "module", &name, |writer| { write_vcd_scope(writer, "module", name, scope, |writer, scope| {
for child in children { for child in children {
child.write_trace(writer, ArgModuleBody { properties })?; child.write_trace(writer, ArgModuleBody { properties, scope })?;
} }
Ok(()) Ok(())
}) })
@ -520,7 +578,7 @@ impl WriteTrace for TraceModule {
impl WriteTrace for TraceInstance { impl WriteTrace for TraceInstance {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
name: _, name: _,
instance_io, instance_io,
@ -534,15 +592,16 @@ impl WriteTrace for TraceInstance {
sink_var_type: "wire", sink_var_type: "wire",
duplex_var_type: "wire", duplex_var_type: "wire",
properties, properties,
scope,
}, },
)?; )?;
module.write_trace(writer, ArgModule { properties }) module.write_trace(writer, ArgModule { properties, scope })
} }
} }
impl WriteTrace for TraceMem { impl WriteTrace for TraceMem {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
id, id,
name, name,
@ -551,11 +610,22 @@ impl WriteTrace for TraceMem {
ports, ports,
array_type, array_type,
} = self; } = self;
write_vcd_scope(writer, "struct", &*name, |writer| { write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
write_vcd_scope(writer, "struct", "contents", |writer| { write_vcd_scope(
writer,
"struct",
"contents".intern(),
scope,
|writer, scope| {
for element_index in 0..array_type.len() { for element_index in 0..array_type.len() {
write_vcd_scope(writer, "struct", &format!("[{element_index}]"), |writer| { write_vcd_scope(
properties.memory_properties[id.as_usize()].element_index = element_index; writer,
"struct",
Intern::intern_owned(format!("[{element_index}]")),
scope,
|writer, scope| {
properties.memory_properties[id.as_usize()].element_index =
element_index;
properties.memory_properties[id.as_usize()].element_part_index = 0; properties.memory_properties[id.as_usize()].element_part_index = 0;
element_type.write_trace( element_type.write_trace(
writer, writer,
@ -564,14 +634,17 @@ impl WriteTrace for TraceMem {
sink_var_type: "reg", sink_var_type: "reg",
duplex_var_type: "reg", duplex_var_type: "reg",
properties, properties,
scope,
}, },
) )
})?; },
)?;
} }
Ok(()) Ok(())
})?; },
)?;
for port in ports { for port in ports {
port.write_trace(writer, ArgModuleBody { properties })?; port.write_trace(writer, ArgModuleBody { properties, scope })?;
} }
Ok(()) Ok(())
}) })
@ -580,7 +653,7 @@ impl WriteTrace for TraceMem {
impl WriteTrace for TraceMemPort { impl WriteTrace for TraceMemPort {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
name: _, name: _,
bundle, bundle,
@ -593,6 +666,7 @@ impl WriteTrace for TraceMemPort {
sink_var_type: "wire", sink_var_type: "wire",
duplex_var_type: "wire", duplex_var_type: "wire",
properties, properties,
scope,
}, },
) )
} }
@ -600,7 +674,7 @@ impl WriteTrace for TraceMemPort {
impl WriteTrace for TraceWire { impl WriteTrace for TraceWire {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
name: _, name: _,
child, child,
@ -613,6 +687,7 @@ impl WriteTrace for TraceWire {
sink_var_type: "wire", sink_var_type: "wire",
duplex_var_type: "wire", duplex_var_type: "wire",
properties, properties,
scope,
}, },
) )
} }
@ -620,7 +695,7 @@ impl WriteTrace for TraceWire {
impl WriteTrace for TraceReg { impl WriteTrace for TraceReg {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
name: _, name: _,
child, child,
@ -633,6 +708,7 @@ impl WriteTrace for TraceReg {
sink_var_type: "reg", sink_var_type: "reg",
duplex_var_type: "reg", duplex_var_type: "reg",
properties, properties,
scope,
}, },
) )
} }
@ -640,7 +716,7 @@ impl WriteTrace for TraceReg {
impl WriteTrace for TraceModuleIO { impl WriteTrace for TraceModuleIO {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties } = arg.module_body(); let ArgModuleBody { properties, scope } = arg.module_body();
let Self { let Self {
name: _, name: _,
child, child,
@ -654,6 +730,7 @@ impl WriteTrace for TraceModuleIO {
sink_var_type: "wire", sink_var_type: "wire",
duplex_var_type: "wire", duplex_var_type: "wire",
properties, properties,
scope,
}, },
) )
} }
@ -661,16 +738,31 @@ impl WriteTrace for TraceModuleIO {
impl WriteTrace for TraceBundle { impl WriteTrace for TraceBundle {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let mut arg = arg.in_type(); let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self { let Self {
name, name,
fields, fields,
ty: _, ty: _,
flow: _, flow: _,
} = self; } = self;
write_vcd_scope(writer, "struct", &name, |writer| { write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
for field in fields { for field in fields {
field.write_trace(writer, arg.reborrow())?; field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
} }
Ok(()) Ok(())
}) })
@ -679,16 +771,31 @@ impl WriteTrace for TraceBundle {
impl WriteTrace for TraceArray { impl WriteTrace for TraceArray {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let mut arg = arg.in_type(); let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self { let Self {
name, name,
elements, elements,
ty: _, ty: _,
flow: _, flow: _,
} = self; } = self;
write_vcd_scope(writer, "struct", &name, |writer| { write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
for element in elements { for element in elements {
element.write_trace(writer, arg.reborrow())?; element.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
} }
Ok(()) Ok(())
}) })
@ -697,7 +804,13 @@ impl WriteTrace for TraceArray {
impl WriteTrace for TraceEnumWithFields { impl WriteTrace for TraceEnumWithFields {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> { fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let mut arg = arg.in_type(); let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self { let Self {
name, name,
discriminant, discriminant,
@ -705,10 +818,28 @@ impl WriteTrace for TraceEnumWithFields {
ty: _, ty: _,
flow: _, flow: _,
} = self; } = self;
write_vcd_scope(writer, "struct", &name, |writer| { write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
discriminant.write_trace(writer, arg.reborrow())?; discriminant.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
for field in non_empty_fields { for field in non_empty_fields {
field.write_trace(writer, arg.reborrow())?; field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
} }
Ok(()) Ok(())
}) })
@ -744,6 +875,7 @@ impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
&mut writer, &mut writer,
ArgModule { ArgModule {
properties: &mut properties, properties: &mut properties,
scope: &mut Scope::default(),
}, },
)?; )?;
writeln!(writer, "$enddefinitions $end")?; writeln!(writer, "$enddefinitions $end")?;
@ -798,9 +930,7 @@ fn write_string_value_change(
value: impl fmt::Display, value: impl fmt::Display,
id: usize, id: usize,
) -> io::Result<()> { ) -> io::Result<()> {
writer.write_all(b"s")?; write!(writer, "s{} ", Escaped(value))?;
write_escaped(writer, value)?;
writer.write_all(b" ")?;
write_vcd_id(writer, id)?; write_vcd_id(writer, id)?;
writer.write_all(b"\n") writer.write_all(b"\n")
} }
@ -946,3 +1076,49 @@ impl<W: io::Write> fmt::Debug for VcdWriter<W> {
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope() {
let mut scope = Scope::default();
assert_eq!(&*scope.new_identifier("foo".intern()).unescaped_name, "foo");
assert_eq!(
&*scope.new_identifier("foo_0".intern()).unescaped_name,
"foo_0"
);
assert_eq!(
&*scope.new_identifier("foo_1".intern()).unescaped_name,
"foo_1"
);
assert_eq!(
&*scope.new_identifier("foo_3".intern()).unescaped_name,
"foo_3"
);
assert_eq!(
&*scope.new_identifier("foo".intern()).unescaped_name,
"foo_2"
);
assert_eq!(
&*scope.new_identifier("foo".intern()).unescaped_name,
"foo_4"
);
assert_eq!(
&*scope.new_identifier("foo_0".intern()).unescaped_name,
"foo_0_2"
);
assert_eq!(
&*scope.new_identifier("foo_1".intern()).unescaped_name,
"foo_1_2"
);
for i in 5..1000u64 {
// verify it actually picks the next available identifier with no skips or duplicates
assert_eq!(
*scope.new_identifier("foo".intern()).unescaped_name,
format!("foo_{i}"),
);
}
}
}

View file

@ -49,6 +49,18 @@ impl<T: Type> ReadyValid<T> {
} }
} }
/// This debug port is only meant to assist the formal proof of the queue.
#[cfg(test)]
#[doc(hidden)]
#[hdl]
pub struct QueueDebugPort<Element, Index> {
#[hdl(flip)]
index_to_check: Index,
stored: Element,
inp_index: Index,
out_index: Index,
}
#[hdl_module] #[hdl_module]
pub fn queue<T: Type>( pub fn queue<T: Type>(
ty: T, ty: T,
@ -178,6 +190,22 @@ pub fn queue<T: Type>(
} }
} }
} }
// These debug ports expose some internal state during the Induction phase
// of Formal Verification. They are not present in normal use.
#[cfg(test)]
{
#[hdl]
let dbg: QueueDebugPort<T, UInt> = m.output(QueueDebugPort[ty][index_ty]);
// read the memory word currently stored at some fixed index
let debug_port = mem.new_read_port();
connect(debug_port.addr, dbg.index_to_check);
connect(debug_port.en, true);
connect(debug_port.clk, cd.clk);
connect(dbg.stored, debug_port.data);
// also expose the current read and write indices
connect(dbg.inp_index, inp_index_reg);
connect(dbg.out_index, out_index_reg);
}
} }
#[cfg(test)] #[cfg(test)]
@ -196,13 +224,23 @@ mod tests {
format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"), format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"),
queue_test(capacity, inp_ready_is_comb, out_valid_is_comb), queue_test(capacity, inp_ready_is_comb, out_valid_is_comb),
FormalMode::Prove, FormalMode::Prove,
14, 2,
None, None,
ExportOptions { ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
..ExportOptions::default() ..ExportOptions::default()
}, },
); );
/// Formal verification of the FIFO queue
///
/// The strategy derives from the observation that, if we filter its
/// input and output streams to consider just one in every N reads and
/// writes (where N is the FIFO capacity), then the FIFO effectively
/// behaves as a one-entry FIFO.
///
/// In particular, any counterexample of the full FIFO behaving badly
/// will also be caught by one of the filtered versions (one which
/// happens to be in phase with the offending input or output).
#[hdl_module] #[hdl_module]
fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) { fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
#[hdl] #[hdl]
@ -217,6 +255,8 @@ mod tests {
rst: formal_reset().to_reset(), rst: formal_reset().to_reset(),
}, },
); );
// random input data
#[hdl] #[hdl]
let inp_data: HdlOption<UInt<8>> = wire(); let inp_data: HdlOption<UInt<8>> = wire();
#[hdl] #[hdl]
@ -225,16 +265,26 @@ mod tests {
} else { } else {
connect(inp_data, HdlNone()); connect(inp_data, HdlNone());
} }
// assert output ready at random
#[hdl] #[hdl]
let out_ready: Bool = wire(); let out_ready: Bool = wire();
connect(out_ready, any_seq(Bool)); connect(out_ready, any_seq(Bool));
let index_ty: UInt<32> = UInt::TYPE;
// The current number of elements in the FIFO ranges from zero to
// maximum capacity, inclusive.
let count_ty = UInt::range_inclusive(0..=capacity.get());
// type for counters that wrap around at the FIFO capacity
let index_ty = UInt::range(0..capacity.get());
// among all entries of the FIFO internal circular memory, choose
// one at random to check
#[hdl] #[hdl]
let index_to_check = wire(); let index_to_check = wire(index_ty);
connect(index_to_check, any_const(index_ty)); connect(index_to_check, any_const(index_ty));
let index_max = !index_ty.zero(); hdl_assume(clk, index_to_check.cmp_lt(capacity.get()), "");
// we saturate at index_max, so only check indexes where we properly maintain position
hdl_assume(clk, index_to_check.cmp_ne(index_max), ""); // instantiate and connect the queue
#[hdl] #[hdl]
let dut = instance(queue( let dut = instance(queue(
UInt[ConstUsize::<8>], UInt[ConstUsize::<8>],
@ -245,109 +295,172 @@ mod tests {
connect(dut.cd, cd); connect(dut.cd, cd);
connect(dut.inp.data, inp_data); connect(dut.inp.data, inp_data);
connect(dut.out.ready, out_ready); connect(dut.out.ready, out_ready);
hdl_assume(
clk,
index_to_check.cmp_ne(!Expr::ty(index_to_check).zero()),
"",
);
// Keep an independent count of words in the FIFO. Ensure that
// it's always correct, and never overflows.
#[hdl] #[hdl]
let expected_count_reg = reg_builder().clock_domain(cd).reset(0u32); let expected_count_reg = reg_builder().clock_domain(cd).reset(count_ty.zero());
#[hdl]
let next_expected_count = wire();
connect(next_expected_count, expected_count_reg);
connect(expected_count_reg, next_expected_count);
#[hdl] #[hdl]
if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) { if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg + 1u8); hdl_assert(clk, expected_count_reg.cmp_ne(capacity.get()), "");
connect_any(expected_count_reg, expected_count_reg + 1u8);
} else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) { } else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg - 1u8); hdl_assert(clk, expected_count_reg.cmp_ne(count_ty.zero()), "");
connect_any(expected_count_reg, expected_count_reg - 1u8);
} }
hdl_assert(cd.clk, expected_count_reg.cmp_eq(dut.count), ""); hdl_assert(clk, expected_count_reg.cmp_eq(dut.count), "");
#[hdl]
let prev_out_ready_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_out_ready_reg,
(prev_out_ready_reg << 1) | out_ready.cast_to(UInt[1]),
);
#[hdl]
let prev_inp_valid_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_inp_valid_reg,
(prev_inp_valid_reg << 1) | HdlOption::is_some(inp_data).cast_to(UInt[1]),
);
hdl_assume(
clk,
(prev_out_ready_reg & prev_inp_valid_reg).cmp_ne(0u8),
"",
);
// keep an independent write index into the FIFO's circular buffer
#[hdl] #[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero()); let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl] #[hdl]
let stored_inp_data_reg = reg_builder().clock_domain(cd).reset(0u8); if ReadyValid::firing(dut.inp) {
#[hdl] #[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.inp) { if inp_index_reg.cmp_ne(capacity.get() - 1) {
#[hdl]
if inp_index_reg.cmp_lt(index_max) {
connect_any(inp_index_reg, inp_index_reg + 1u8); connect_any(inp_index_reg, inp_index_reg + 1u8);
#[hdl] } else {
if inp_index_reg.cmp_eq(index_to_check) { connect_any(inp_index_reg, 0_hdl_u0);
connect(stored_inp_data_reg, data);
}
} }
} }
#[hdl] // keep an independent read index into the FIFO's circular buffer
if inp_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), "");
}
#[hdl] #[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero()); let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl] #[hdl]
let stored_out_data_reg = reg_builder().clock_domain(cd).reset(0u8); if ReadyValid::firing(dut.out) {
#[hdl] #[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.out) { if out_index_reg.cmp_ne(capacity.get() - 1) {
#[hdl]
if out_index_reg.cmp_lt(index_max) {
connect_any(out_index_reg, out_index_reg + 1u8); connect_any(out_index_reg, out_index_reg + 1u8);
#[hdl]
if out_index_reg.cmp_eq(index_to_check) {
connect(stored_out_data_reg, data);
}
}
}
#[hdl]
if out_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), "");
}
hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), "");
#[hdl]
if inp_index_reg.cmp_lt(index_max) & out_index_reg.cmp_lt(index_max) {
hdl_assert(
clk,
expected_count_reg.cmp_eq(inp_index_reg - out_index_reg),
"",
);
} else { } else {
hdl_assert( connect_any(out_index_reg, 0_hdl_u0);
clk, }
expected_count_reg.cmp_ge(inp_index_reg - out_index_reg),
"",
);
} }
// filter the input data stream, predicated by the read index
// matching the chosen position in the FIFO's circular buffer
#[hdl] #[hdl]
if inp_index_reg.cmp_gt(index_to_check) & out_index_reg.cmp_gt(index_to_check) { let inp_index_matches = wire();
hdl_assert(clk, stored_inp_data_reg.cmp_eq(stored_out_data_reg), ""); connect(inp_index_matches, inp_index_reg.cmp_eq(index_to_check));
#[hdl]
let inp_firing_data = wire();
connect(inp_firing_data, HdlNone());
#[hdl]
if inp_index_matches {
connect(inp_firing_data, ReadyValid::firing_data(dut.inp));
} }
// filter the output data stream, predicated by the write index
// matching the chosen position in the FIFO's circular buffer
#[hdl]
let out_index_matches = wire();
connect(out_index_matches, out_index_reg.cmp_eq(index_to_check));
#[hdl]
let out_firing_data = wire();
connect(out_firing_data, HdlNone());
#[hdl]
if out_index_matches {
connect(out_firing_data, ReadyValid::firing_data(dut.out));
}
// Implement a one-entry FIFO and ensure its equivalence to the
// filtered FIFO.
//
// the holding register for our one-entry FIFO
#[hdl]
let stored_reg = reg_builder().clock_domain(cd).reset(HdlNone());
#[hdl]
match stored_reg {
// If the holding register is empty...
HdlNone => {
#[hdl]
match inp_firing_data {
// ... and we are not receiving data, then we must not
// transmit any data.
HdlNone => hdl_assert(clk, HdlOption::is_none(out_firing_data), ""),
// If we are indeed receiving some data...
HdlSome(data_in) => {
#[hdl]
match out_firing_data {
// ... and transmitting at the same time, we
// must be transmitting the input data itself,
// since the holding register is empty.
HdlSome(data_out) => hdl_assert(clk, data_out.cmp_eq(data_in), ""),
// If we are receiving, but not transmitting,
// store the received data in the holding
// register.
HdlNone => connect(stored_reg, HdlSome(data_in)),
}
}
}
}
// If there is some value stored in the holding register...
HdlSome(stored) => {
#[hdl]
match out_firing_data {
// ... and we are not transmitting it, we cannot
// receive any more data.
HdlNone => hdl_assert(clk, HdlOption::is_none(inp_firing_data), ""),
// If we are transmitting a previously stored value...
HdlSome(data_out) => {
// ... it must be the same data we stored earlier.
hdl_assert(clk, data_out.cmp_eq(stored), "");
// Also, accept new data, if any. Otherwise,
// let the holding register become empty.
connect(stored_reg, inp_firing_data);
}
}
}
}
// from now on, some extra assertions in order to pass induction
// sync the holding register, when it's occupied, to the
// corresponding entry in the FIFO's circular buffer
connect(dut.dbg.index_to_check, index_to_check);
#[hdl]
if let HdlSome(stored) = stored_reg {
hdl_assert(clk, stored.cmp_eq(dut.dbg.stored), "");
}
// sync the read and write indices
hdl_assert(clk, inp_index_reg.cmp_eq(dut.dbg.inp_index), "");
hdl_assert(clk, out_index_reg.cmp_eq(dut.dbg.out_index), "");
// the indices should never go past the capacity, but induction
// doesn't know that...
hdl_assert(clk, inp_index_reg.cmp_lt(capacity.get()), "");
hdl_assert(clk, out_index_reg.cmp_lt(capacity.get()), "");
// strongly constrain the state of the holding register
//
// The holding register is full if and only if the corresponding
// FIFO entry was written to and not yet read. In other words, if
// the number of pending reads until the chosen entry is read out
// is greater than the current FIFO count, then the entry couldn't
// be in the FIFO in the first place.
#[hdl]
let pending_reads: UInt = wire(index_ty);
// take care of wrap-around when subtracting indices, add the
// capacity amount to keep the result positive if necessary
#[hdl]
if index_to_check.cmp_ge(out_index_reg) {
connect(pending_reads, index_to_check - out_index_reg);
} else {
connect(
pending_reads,
index_to_check + capacity.get() - out_index_reg,
);
}
// check whether the chosen entry is in the FIFO
#[hdl]
let expected_stored: Bool = wire();
connect(expected_stored, pending_reads.cmp_lt(dut.count));
// sync with the state of the holding register
hdl_assert(
clk,
expected_stored.cmp_eq(HdlOption::is_some(stored_reg)),
"",
);
} }
} }
@ -430,4 +543,24 @@ mod tests {
fn test_4_true_true() { fn test_4_true_true() {
test_queue(NonZero::new(4).unwrap(), true, true); test_queue(NonZero::new(4).unwrap(), true, true);
} }
#[test]
fn test_many_false_false() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, false);
}
#[test]
fn test_many_false_true() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, true);
}
#[test]
fn test_many_true_false() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, false);
}
#[test]
fn test_many_true_true() {
test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, true);
}
} }

View file

@ -191,10 +191,14 @@ circuit check_array_repeat:
}; };
} }
pub trait UnknownTrait {}
impl<T: ?Sized> UnknownTrait for T {}
#[hdl_module(outline_generated)] #[hdl_module(outline_generated)]
pub fn check_skipped_generics<T, #[hdl(skip)] U, const N: usize, #[hdl(skip)] const M: usize>(v: U) pub fn check_skipped_generics<T, #[hdl(skip)] U, const N: usize, #[hdl(skip)] const M: usize>(v: U)
where where
T: StaticType, T: StaticType + UnknownTrait,
ConstUsize<N>: KnownSize, ConstUsize<N>: KnownSize,
U: std::fmt::Display, U: std::fmt::Display,
{ {
@ -4288,7 +4292,8 @@ circuit check_deduce_resets:
}; };
} }
#[hdl_module(outline_generated)] // intentionally not outline_generated to ensure we get correct macro hygiene
#[hdl_module]
pub fn check_cfgs<#[cfg(cfg_false_for_tests)] A: Type, #[cfg(cfg_true_for_tests)] B: Type>( pub fn check_cfgs<#[cfg(cfg_false_for_tests)] A: Type, #[cfg(cfg_true_for_tests)] B: Type>(
#[cfg(cfg_false_for_tests)] a: A, #[cfg(cfg_false_for_tests)] a: A,
#[cfg(cfg_true_for_tests)] b: B, #[cfg(cfg_true_for_tests)] b: B,
@ -4335,12 +4340,88 @@ fn test_cfgs() {
"/test/check_cfgs.fir": r"FIRRTL version 3.2.0 "/test/check_cfgs.fir": r"FIRRTL version 3.2.0
circuit check_cfgs: circuit check_cfgs:
type Ty0 = {b: UInt<8>} type Ty0 = {b: UInt<8>}
module check_cfgs: @[module-XXXXXXXXXX.rs 1:1] module check_cfgs: @[the_test_file.rs 9962:1]
input i_b: UInt<8> @[module-XXXXXXXXXX.rs 2:1] input i_b: UInt<8> @[the_test_file.rs 9979:20]
output o_b: UInt<8> @[module-XXXXXXXXXX.rs 4:1] output o_b: UInt<8> @[the_test_file.rs 9992:24]
wire w: Ty0 @[module-XXXXXXXXXX.rs 3:1] wire w: Ty0 @[the_test_file.rs 9981:25]
connect o_b, w.b @[module-XXXXXXXXXX.rs 5:1] connect o_b, w.b @[the_test_file.rs 9993:9]
connect w.b, i_b @[module-XXXXXXXXXX.rs 6:1] connect w.b, i_b @[the_test_file.rs 9994:9]
",
};
}
#[hdl_module(outline_generated)]
pub fn check_let_patterns() {
#[hdl]
let tuple_in: (UInt<1>, SInt<1>, Bool) = m.input();
#[hdl]
let (tuple_0, tuple_1, tuple_2) = tuple_in;
#[hdl]
let tuple_0_out: UInt<1> = m.output();
connect(tuple_0_out, tuple_0);
#[hdl]
let tuple_1_out: SInt<1> = m.output();
connect(tuple_1_out, tuple_1);
#[hdl]
let tuple_2_out: Bool = m.output();
connect(tuple_2_out, tuple_2);
#[hdl]
let test_struct_in: TestStruct<SInt<8>> = m.input();
#[hdl]
let TestStruct::<_> { a, b } = test_struct_in;
#[hdl]
let test_struct_a_out: SInt<8> = m.output();
connect(test_struct_a_out, a);
#[hdl]
let test_struct_b_out: UInt<8> = m.output();
connect(test_struct_b_out, b);
#[hdl]
let test_struct_2_in: TestStruct2 = m.input();
#[hdl]
let TestStruct2 { v } = test_struct_2_in;
#[hdl]
let test_struct_2_v_out: UInt<8> = m.output();
connect(test_struct_2_v_out, v);
#[hdl]
let test_struct_3_in: TestStruct3 = m.input();
#[hdl]
let TestStruct3 {} = test_struct_3_in;
}
#[test]
fn test_let_patterns() {
let _n = SourceLocation::normalize_files_for_tests();
let m = check_let_patterns();
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
"/test/check_let_patterns.fir": r"FIRRTL version 3.2.0
circuit check_let_patterns:
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_let_patterns: @[module-XXXXXXXXXX.rs 1:1]
input tuple_in: Ty0 @[module-XXXXXXXXXX.rs 2:1]
output tuple_0_out: UInt<1> @[module-XXXXXXXXXX.rs 4:1]
output tuple_1_out: SInt<1> @[module-XXXXXXXXXX.rs 6:1]
output tuple_2_out: UInt<1> @[module-XXXXXXXXXX.rs 8:1]
input test_struct_in: Ty1 @[module-XXXXXXXXXX.rs 10:1]
output test_struct_a_out: SInt<8> @[module-XXXXXXXXXX.rs 12:1]
output test_struct_b_out: UInt<8> @[module-XXXXXXXXXX.rs 14:1]
input test_struct_2_in: Ty2 @[module-XXXXXXXXXX.rs 16:1]
output test_struct_2_v_out: UInt<8> @[module-XXXXXXXXXX.rs 18:1]
input test_struct_3_in: Ty3 @[module-XXXXXXXXXX.rs 20:1]
connect tuple_0_out, tuple_in.`0` @[module-XXXXXXXXXX.rs 5:1]
connect tuple_1_out, tuple_in.`1` @[module-XXXXXXXXXX.rs 7:1]
connect tuple_2_out, tuple_in.`2` @[module-XXXXXXXXXX.rs 9:1]
connect test_struct_a_out, test_struct_in.a @[module-XXXXXXXXXX.rs 13:1]
connect test_struct_b_out, test_struct_in.b @[module-XXXXXXXXXX.rs 15:1]
connect test_struct_2_v_out, test_struct_2_in.v @[module-XXXXXXXXXX.rs 19:1]
", ",
}; };
} }

View file

@ -1246,3 +1246,200 @@ fn test_memories3() {
panic!(); panic!();
} }
} }
#[hdl_module(outline_generated)]
pub fn duplicate_names() {
#[hdl]
let w = wire();
connect(w, 5u8);
#[hdl]
let w = wire();
connect(w, 6u8);
}
#[test]
fn test_duplicate_names() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(duplicate_names());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/duplicate_names.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/duplicate_names.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn array_rw() {
#[hdl]
let array_in: Array<UInt<8>, 16> = m.input();
#[hdl]
let array_out: Array<UInt<8>, 16> = m.output();
#[hdl]
let read_index: UInt<8> = m.input();
#[hdl]
let read_data: UInt<8> = m.output();
#[hdl]
let write_index: UInt<8> = m.input();
#[hdl]
let write_data: UInt<8> = m.input();
#[hdl]
let write_en: Bool = m.input();
#[hdl]
let array_wire = wire();
connect(array_wire, array_in);
connect(array_out, array_wire);
#[hdl]
if write_en {
connect(array_wire[write_index], write_data);
}
connect(read_data, array_wire[read_index]);
}
#[test]
fn test_array_rw() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(array_rw());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
#[derive(Debug, PartialEq)]
struct State {
array_in: [u8; 16],
array_out: [u8; 16],
read_index: u8,
read_data: u8,
write_index: u8,
write_data: u8,
write_en: bool,
}
let mut states = Vec::new();
let array_in = [
0xFFu8, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, //
0x00u8, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE,
];
for i in 0..=16 {
states.push(State {
array_in,
array_out: array_in,
read_index: i,
read_data: array_in.get(i as usize).copied().unwrap_or(0),
write_index: 0,
write_data: 0,
write_en: false,
});
}
for i in 0..=16u8 {
let mut array_out = array_in;
let write_data = i.wrapping_mul(i);
if let Some(v) = array_out.get_mut(i as usize) {
*v = write_data;
}
states.push(State {
array_in,
array_out,
read_index: 0,
read_data: array_out[0],
write_index: i,
write_data,
write_en: true,
});
}
for (cycle, expected) in states.into_iter().enumerate() {
let State {
array_in,
array_out: _,
read_index,
read_data: _,
write_index,
write_data,
write_en,
} = expected;
sim.write(sim.io().array_in, array_in);
sim.write(sim.io().read_index, read_index);
sim.write(sim.io().write_index, write_index);
sim.write(sim.io().write_data, write_data);
sim.write(sim.io().write_en, write_en);
sim.advance_time(SimDuration::from_micros(1));
let array_out = std::array::from_fn(|index| {
sim.read_bool_or_int(sim.io().array_out[index])
.to_bigint()
.try_into()
.expect("known to be in range")
});
let read_data = sim
.read_bool_or_int(sim.io().read_data)
.to_bigint()
.try_into()
.expect("known to be in range");
let state = State {
array_in,
array_out,
read_index,
read_data,
write_index,
write_data,
write_en,
};
assert_eq!(
state,
expected,
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/array_rw.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/array_rw.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn conditional_assignment_last() {
#[hdl]
let i: Bool = m.input();
#[hdl]
let w = wire();
connect(w, true);
#[hdl]
if i {
connect(w, false);
}
}
#[test]
fn test_conditional_assignment_last() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(conditional_assignment_last());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().i, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().i, true);
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/conditional_assignment_last.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/conditional_assignment_last.txt") {
panic!();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,283 @@
$timescale 1 ps $end
$scope module array_rw $end
$scope struct array_in $end
$var wire 8 ! \[0] $end
$var wire 8 " \[1] $end
$var wire 8 # \[2] $end
$var wire 8 $ \[3] $end
$var wire 8 % \[4] $end
$var wire 8 & \[5] $end
$var wire 8 ' \[6] $end
$var wire 8 ( \[7] $end
$var wire 8 ) \[8] $end
$var wire 8 * \[9] $end
$var wire 8 + \[10] $end
$var wire 8 , \[11] $end
$var wire 8 - \[12] $end
$var wire 8 . \[13] $end
$var wire 8 / \[14] $end
$var wire 8 0 \[15] $end
$upscope $end
$scope struct array_out $end
$var wire 8 1 \[0] $end
$var wire 8 2 \[1] $end
$var wire 8 3 \[2] $end
$var wire 8 4 \[3] $end
$var wire 8 5 \[4] $end
$var wire 8 6 \[5] $end
$var wire 8 7 \[6] $end
$var wire 8 8 \[7] $end
$var wire 8 9 \[8] $end
$var wire 8 : \[9] $end
$var wire 8 ; \[10] $end
$var wire 8 < \[11] $end
$var wire 8 = \[12] $end
$var wire 8 > \[13] $end
$var wire 8 ? \[14] $end
$var wire 8 @ \[15] $end
$upscope $end
$var wire 8 A read_index $end
$var wire 8 B read_data $end
$var wire 8 C write_index $end
$var wire 8 D write_data $end
$var wire 1 E write_en $end
$scope struct array_wire $end
$var wire 8 F \[0] $end
$var wire 8 G \[1] $end
$var wire 8 H \[2] $end
$var wire 8 I \[3] $end
$var wire 8 J \[4] $end
$var wire 8 K \[5] $end
$var wire 8 L \[6] $end
$var wire 8 M \[7] $end
$var wire 8 N \[8] $end
$var wire 8 O \[9] $end
$var wire 8 P \[10] $end
$var wire 8 Q \[11] $end
$var wire 8 R \[12] $end
$var wire 8 S \[13] $end
$var wire 8 T \[14] $end
$var wire 8 U \[15] $end
$upscope $end
$upscope $end
$enddefinitions $end
$dumpvars
b11111111 !
b1111111 "
b111111 #
b11111 $
b1111 %
b111 &
b11 '
b1 (
b0 )
b10000000 *
b11000000 +
b11100000 ,
b11110000 -
b11111000 .
b11111100 /
b11111110 0
b11111111 1
b1111111 2
b111111 3
b11111 4
b1111 5
b111 6
b11 7
b1 8
b0 9
b10000000 :
b11000000 ;
b11100000 <
b11110000 =
b11111000 >
b11111100 ?
b11111110 @
b0 A
b11111111 B
b0 C
b0 D
0E
b11111111 F
b1111111 G
b111111 H
b11111 I
b1111 J
b111 K
b11 L
b1 M
b0 N
b10000000 O
b11000000 P
b11100000 Q
b11110000 R
b11111000 S
b11111100 T
b11111110 U
$end
#1000000
b1 A
b1111111 B
#2000000
b10 A
b111111 B
#3000000
b11 A
b11111 B
#4000000
b100 A
b1111 B
#5000000
b101 A
b111 B
#6000000
b110 A
b11 B
#7000000
b111 A
b1 B
#8000000
b1000 A
b0 B
#9000000
b1001 A
b10000000 B
#10000000
b1010 A
b11000000 B
#11000000
b1011 A
b11100000 B
#12000000
b1100 A
b11110000 B
#13000000
b1101 A
b11111000 B
#14000000
b1110 A
b11111100 B
#15000000
b1111 A
b11111110 B
#16000000
b10000 A
b0 B
#17000000
b0 1
b0 A
1E
b0 F
#18000000
b11111111 1
b1 2
b11111111 B
b1 C
b1 D
b11111111 F
b1 G
#19000000
b1111111 2
b100 3
b10 C
b100 D
b1111111 G
b100 H
#20000000
b111111 3
b1001 4
b11 C
b1001 D
b111111 H
b1001 I
#21000000
b11111 4
b10000 5
b100 C
b10000 D
b11111 I
b10000 J
#22000000
b1111 5
b11001 6
b101 C
b11001 D
b1111 J
b11001 K
#23000000
b111 6
b100100 7
b110 C
b100100 D
b111 K
b100100 L
#24000000
b11 7
b110001 8
b111 C
b110001 D
b11 L
b110001 M
#25000000
b1 8
b1000000 9
b1000 C
b1000000 D
b1 M
b1000000 N
#26000000
b0 9
b1010001 :
b1001 C
b1010001 D
b0 N
b1010001 O
#27000000
b10000000 :
b1100100 ;
b1010 C
b1100100 D
b10000000 O
b1100100 P
#28000000
b11000000 ;
b1111001 <
b1011 C
b1111001 D
b11000000 P
b1111001 Q
#29000000
b11100000 <
b10010000 =
b1100 C
b10010000 D
b11100000 Q
b10010000 R
#30000000
b11110000 =
b10101001 >
b1101 C
b10101001 D
b11110000 R
b10101001 S
#31000000
b11111000 >
b11000100 ?
b1110 C
b11000100 D
b11111000 S
b11000100 T
#32000000
b11111100 ?
b11100001 @
b1111 C
b11100001 D
b11111100 T
b11100001 U
#33000000
b11111110 @
b10000 C
b0 D
b11111110 U
#34000000

View file

@ -0,0 +1,189 @@
Simulation {
state: State {
insns: Insns {
state_layout: StateLayout {
ty: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 4,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i",
ty: Bool,
},
SlotDebugData {
name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w",
ty: Bool,
},
SlotDebugData {
name: "",
ty: Bool,
},
SlotDebugData {
name: "",
ty: Bool,
},
],
..
},
},
memories: StatePartLayout<Memories> {
len: 0,
debug_data: [],
layout_data: [],
..
},
},
insns: [
// at: module-XXXXXXXXXX.rs:1:1
0: Const {
dest: StatePartIndex<BigSlots>(3), // (0x0) SlotDebugData { name: "", ty: Bool },
value: 0x0,
},
1: Const {
dest: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool },
value: 0x1,
},
// at: module-XXXXXXXXXX.rs:4:1
2: Copy {
dest: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool },
src: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:5:1
3: BranchIfZero {
target: 5,
value: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:6:1
4: Copy {
dest: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::w", ty: Bool },
src: StatePartIndex<BigSlots>(3), // (0x0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:1:1
5: Return,
],
..
},
pc: 5,
memory_write_log: [],
memories: StatePart {
value: [],
},
small_slots: StatePart {
value: [],
},
big_slots: StatePart {
value: [
1,
0,
1,
0,
],
},
},
io: Instance {
name: <simulator>::conditional_assignment_last,
instantiated: Module {
name: conditional_assignment_last,
..
},
},
uninitialized_inputs: {},
io_targets: {
Instance {
name: <simulator>::conditional_assignment_last,
instantiated: Module {
name: conditional_assignment_last,
..
},
}.i: CompiledValue {
layout: CompiledTypeLayout {
ty: Bool,
layout: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 1,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(conditional_assignment_last: conditional_assignment_last).conditional_assignment_last::i",
ty: Bool,
},
],
..
},
},
body: Scalar,
},
range: TypeIndexRange {
small_slots: StatePartIndexRange<SmallSlots> { start: 0, len: 0 },
big_slots: StatePartIndexRange<BigSlots> { start: 0, len: 1 },
},
write: None,
},
},
made_initial_step: true,
needs_settle: false,
trace_decls: TraceModule {
name: "conditional_assignment_last",
children: [
TraceModuleIO {
name: "i",
child: TraceBool {
location: TraceScalarId(0),
name: "i",
flow: Source,
},
ty: Bool,
flow: Source,
},
TraceWire {
name: "w",
child: TraceBool {
location: TraceScalarId(1),
name: "w",
flow: Duplex,
},
ty: Bool,
},
],
},
traces: [
SimTrace {
id: TraceScalarId(0),
kind: BigBool {
index: StatePartIndex<BigSlots>(0),
},
state: 0x1,
last_state: 0x0,
},
SimTrace {
id: TraceScalarId(1),
kind: BigBool {
index: StatePartIndex<BigSlots>(1),
},
state: 0x0,
last_state: 0x1,
},
],
trace_memories: {},
trace_writers: [
Running(
VcdWriter {
finished_init: true,
timescale: 1 ps,
..
},
),
],
instant: 2 μs,
clocks_triggered: [],
..
}

View file

@ -0,0 +1,14 @@
$timescale 1 ps $end
$scope module conditional_assignment_last $end
$var wire 1 ! i $end
$var wire 1 " w $end
$upscope $end
$enddefinitions $end
$dumpvars
0!
1"
$end
#1000000
1!
0"
#2000000

View file

@ -0,0 +1,153 @@
Simulation {
state: State {
insns: Insns {
state_layout: StateLayout {
ty: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 4,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w",
ty: UInt<8>,
},
SlotDebugData {
name: "",
ty: UInt<8>,
},
SlotDebugData {
name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w",
ty: UInt<8>,
},
SlotDebugData {
name: "",
ty: UInt<8>,
},
],
..
},
},
memories: StatePartLayout<Memories> {
len: 0,
debug_data: [],
layout_data: [],
..
},
},
insns: [
// at: module-XXXXXXXXXX.rs:1:1
0: Const {
dest: StatePartIndex<BigSlots>(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> },
value: 0x6,
},
// at: module-XXXXXXXXXX.rs:5:1
1: Copy {
dest: StatePartIndex<BigSlots>(2), // (0x6) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> },
src: StatePartIndex<BigSlots>(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> },
},
// at: module-XXXXXXXXXX.rs:1:1
2: Const {
dest: StatePartIndex<BigSlots>(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> },
value: 0x5,
},
// at: module-XXXXXXXXXX.rs:3:1
3: Copy {
dest: StatePartIndex<BigSlots>(0), // (0x5) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> },
src: StatePartIndex<BigSlots>(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> },
},
// at: module-XXXXXXXXXX.rs:1:1
4: Return,
],
..
},
pc: 4,
memory_write_log: [],
memories: StatePart {
value: [],
},
small_slots: StatePart {
value: [],
},
big_slots: StatePart {
value: [
5,
5,
6,
6,
],
},
},
io: Instance {
name: <simulator>::duplicate_names,
instantiated: Module {
name: duplicate_names,
..
},
},
uninitialized_inputs: {},
io_targets: {},
made_initial_step: true,
needs_settle: false,
trace_decls: TraceModule {
name: "duplicate_names",
children: [
TraceWire {
name: "w",
child: TraceUInt {
location: TraceScalarId(0),
name: "w",
ty: UInt<8>,
flow: Duplex,
},
ty: UInt<8>,
},
TraceWire {
name: "w",
child: TraceUInt {
location: TraceScalarId(1),
name: "w",
ty: UInt<8>,
flow: Duplex,
},
ty: UInt<8>,
},
],
},
traces: [
SimTrace {
id: TraceScalarId(0),
kind: BigUInt {
index: StatePartIndex<BigSlots>(0),
ty: UInt<8>,
},
state: 0x05,
last_state: 0x05,
},
SimTrace {
id: TraceScalarId(1),
kind: BigUInt {
index: StatePartIndex<BigSlots>(2),
ty: UInt<8>,
},
state: 0x06,
last_state: 0x06,
},
],
trace_memories: {},
trace_writers: [
Running(
VcdWriter {
finished_init: true,
timescale: 1 ps,
..
},
),
],
instant: 1 μs,
clocks_triggered: [],
..
}

View file

@ -0,0 +1,11 @@
$timescale 1 ps $end
$scope module duplicate_names $end
$var wire 8 ! w $end
$var wire 8 " w_2 $end
$upscope $end
$enddefinitions $end
$dumpvars
b101 !
b110 "
$end
#1000000

View file

@ -24,97 +24,97 @@ $upscope $end
$upscope $end $upscope $end
$scope struct mem $end $scope struct mem $end
$scope struct contents $end $scope struct contents $end
$scope struct [0] $end $scope struct \[0] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 9 \0 $end $var reg 8 9 \0 $end
$var reg 8 I \1 $end $var reg 8 I \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [1] $end $scope struct \[1] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 : \0 $end $var reg 8 : \0 $end
$var reg 8 J \1 $end $var reg 8 J \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [2] $end $scope struct \[2] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 ; \0 $end $var reg 8 ; \0 $end
$var reg 8 K \1 $end $var reg 8 K \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [3] $end $scope struct \[3] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 < \0 $end $var reg 8 < \0 $end
$var reg 8 L \1 $end $var reg 8 L \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [4] $end $scope struct \[4] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 = \0 $end $var reg 8 = \0 $end
$var reg 8 M \1 $end $var reg 8 M \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [5] $end $scope struct \[5] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 > \0 $end $var reg 8 > \0 $end
$var reg 8 N \1 $end $var reg 8 N \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [6] $end $scope struct \[6] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 ? \0 $end $var reg 8 ? \0 $end
$var reg 8 O \1 $end $var reg 8 O \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [7] $end $scope struct \[7] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 @ \0 $end $var reg 8 @ \0 $end
$var reg 8 P \1 $end $var reg 8 P \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [8] $end $scope struct \[8] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 A \0 $end $var reg 8 A \0 $end
$var reg 8 Q \1 $end $var reg 8 Q \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [9] $end $scope struct \[9] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 B \0 $end $var reg 8 B \0 $end
$var reg 8 R \1 $end $var reg 8 R \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [10] $end $scope struct \[10] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 C \0 $end $var reg 8 C \0 $end
$var reg 8 S \1 $end $var reg 8 S \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [11] $end $scope struct \[11] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 D \0 $end $var reg 8 D \0 $end
$var reg 8 T \1 $end $var reg 8 T \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [12] $end $scope struct \[12] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 E \0 $end $var reg 8 E \0 $end
$var reg 8 U \1 $end $var reg 8 U \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [13] $end $scope struct \[13] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 F \0 $end $var reg 8 F \0 $end
$var reg 8 V \1 $end $var reg 8 V \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [14] $end $scope struct \[14] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 G \0 $end $var reg 8 G \0 $end
$var reg 8 W \1 $end $var reg 8 W \1 $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [15] $end $scope struct \[15] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 H \0 $end $var reg 8 H \0 $end
$var reg 8 X \1 $end $var reg 8 X \1 $end

View file

@ -11,31 +11,31 @@ $var wire 1 ' wmask $end
$upscope $end $upscope $end
$scope struct mem $end $scope struct mem $end
$scope struct contents $end $scope struct contents $end
$scope struct [0] $end $scope struct \[0] $end
$scope struct mem $end $scope struct mem $end
$var string 1 1 \$tag $end $var string 1 1 \$tag $end
$var reg 1 6 HdlSome $end $var reg 1 6 HdlSome $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [1] $end $scope struct \[1] $end
$scope struct mem $end $scope struct mem $end
$var string 1 2 \$tag $end $var string 1 2 \$tag $end
$var reg 1 7 HdlSome $end $var reg 1 7 HdlSome $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [2] $end $scope struct \[2] $end
$scope struct mem $end $scope struct mem $end
$var string 1 3 \$tag $end $var string 1 3 \$tag $end
$var reg 1 8 HdlSome $end $var reg 1 8 HdlSome $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [3] $end $scope struct \[3] $end
$scope struct mem $end $scope struct mem $end
$var string 1 4 \$tag $end $var string 1 4 \$tag $end
$var reg 1 9 HdlSome $end $var reg 1 9 HdlSome $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [4] $end $scope struct \[4] $end
$scope struct mem $end $scope struct mem $end
$var string 1 5 \$tag $end $var string 1 5 \$tag $end
$var reg 1 : HdlSome $end $var reg 1 : HdlSome $end

View file

@ -42,7 +42,7 @@ $upscope $end
$upscope $end $upscope $end
$scope struct mem $end $scope struct mem $end
$scope struct contents $end $scope struct contents $end
$scope struct [0] $end $scope struct \[0] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 ] \[0] $end $var reg 8 ] \[0] $end
$var reg 8 e \[1] $end $var reg 8 e \[1] $end
@ -54,7 +54,7 @@ $var reg 8 /" \[6] $end
$var reg 8 7" \[7] $end $var reg 8 7" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [1] $end $scope struct \[1] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 ^ \[0] $end $var reg 8 ^ \[0] $end
$var reg 8 f \[1] $end $var reg 8 f \[1] $end
@ -66,7 +66,7 @@ $var reg 8 0" \[6] $end
$var reg 8 8" \[7] $end $var reg 8 8" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [2] $end $scope struct \[2] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 _ \[0] $end $var reg 8 _ \[0] $end
$var reg 8 g \[1] $end $var reg 8 g \[1] $end
@ -78,7 +78,7 @@ $var reg 8 1" \[6] $end
$var reg 8 9" \[7] $end $var reg 8 9" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [3] $end $scope struct \[3] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 ` \[0] $end $var reg 8 ` \[0] $end
$var reg 8 h \[1] $end $var reg 8 h \[1] $end
@ -90,7 +90,7 @@ $var reg 8 2" \[6] $end
$var reg 8 :" \[7] $end $var reg 8 :" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [4] $end $scope struct \[4] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 a \[0] $end $var reg 8 a \[0] $end
$var reg 8 i \[1] $end $var reg 8 i \[1] $end
@ -102,7 +102,7 @@ $var reg 8 3" \[6] $end
$var reg 8 ;" \[7] $end $var reg 8 ;" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [5] $end $scope struct \[5] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 b \[0] $end $var reg 8 b \[0] $end
$var reg 8 j \[1] $end $var reg 8 j \[1] $end
@ -114,7 +114,7 @@ $var reg 8 4" \[6] $end
$var reg 8 <" \[7] $end $var reg 8 <" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [6] $end $scope struct \[6] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 c \[0] $end $var reg 8 c \[0] $end
$var reg 8 k \[1] $end $var reg 8 k \[1] $end
@ -126,7 +126,7 @@ $var reg 8 5" \[6] $end
$var reg 8 =" \[7] $end $var reg 8 =" \[7] $end
$upscope $end $upscope $end
$upscope $end $upscope $end
$scope struct [7] $end $scope struct \[7] $end
$scope struct mem $end $scope struct mem $end
$var reg 8 d \[0] $end $var reg 8 d \[0] $end
$var reg 8 l \[1] $end $var reg 8 l \[1] $end