Compare commits

..

No commits in common. "9e30a58982f68caf94259dcc0d6e2c78ebeccfcd" and "36bad529789f70e1732c6f43ef12a9bfd0e3afc7" have entirely different histories.

18 changed files with 226 additions and 7009 deletions

8
Cargo.lock generated
View file

@ -543,9 +543,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
dependencies = [
"unicode-ident",
]
@ -647,9 +647,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.93"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",

View file

@ -37,7 +37,7 @@ quote = "1.0.36"
serde = { version = "1.0.202", features = ["derive"] }
serde_json = { version = "1.0.117", features = ["preserve_order"] }
sha2 = "0.10.8"
syn = { version = "2.0.93", features = ["full", "fold", "visit", "extra-traits"] }
syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"] }
tempfile = "3.10.1"
thiserror = "1.0.61"
trybuild = "1.0"

View file

@ -3,20 +3,14 @@
#![cfg_attr(test, recursion_limit = "512")]
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::{
collections::{hash_map::Entry, HashMap},
io::{ErrorKind, Write},
};
use std::io::{ErrorKind, Write};
use syn::{
bracketed,
ext::IdentExt,
parenthesized,
bracketed, parenthesized,
parse::{Parse, ParseStream, Parser},
parse_quote,
punctuated::{Pair, Punctuated},
punctuated::Pair,
spanned::Spanned,
token::{Bracket, Paren},
AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token,
AttrStyle, Attribute, Error, Item, ItemFn, Token,
};
mod fold;
@ -25,7 +19,6 @@ mod hdl_enum;
mod hdl_type_alias;
mod hdl_type_common;
mod module;
mod process_cfg;
pub(crate) trait CustomToken:
Copy
@ -66,11 +59,6 @@ mod kw {
};
}
custom_keyword!(__evaluated_cfgs);
custom_keyword!(all);
custom_keyword!(any);
custom_keyword!(cfg);
custom_keyword!(cfg_attr);
custom_keyword!(clock_domain);
custom_keyword!(connect_inexact);
custom_keyword!(custom_bounds);
@ -87,7 +75,6 @@ mod kw {
custom_keyword!(no_reset);
custom_keyword!(no_runtime_generics);
custom_keyword!(no_static);
custom_keyword!(not);
custom_keyword!(outline_generated);
custom_keyword!(output);
custom_keyword!(reg_builder);
@ -914,346 +901,15 @@ fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
Ok(contents)
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) enum CfgExpr {
Option {
ident: Ident,
value: Option<(Token![=], LitStr)>,
},
All {
all: kw::all,
paren: Paren,
exprs: Punctuated<CfgExpr, Token![,]>,
},
Any {
any: kw::any,
paren: Paren,
exprs: Punctuated<CfgExpr, Token![,]>,
},
Not {
not: kw::not,
paren: Paren,
expr: Box<CfgExpr>,
trailing_comma: Option<Token![,]>,
},
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let kw = kw::hdl_module::default();
hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?)
}
impl Parse for CfgExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
match input.cursor().ident() {
Some((_, cursor)) if cursor.eof() => {
return Ok(CfgExpr::Option {
ident: input.call(Ident::parse_any)?,
value: None,
});
}
_ => {}
}
if input.peek(Ident::peek_any) && input.peek2(Token![=]) {
return Ok(CfgExpr::Option {
ident: input.call(Ident::parse_any)?,
value: Some((input.parse()?, input.parse()?)),
});
}
let contents;
if input.peek(kw::all) {
Ok(CfgExpr::All {
all: input.parse()?,
paren: parenthesized!(contents in input),
exprs: contents.call(Punctuated::parse_terminated)?,
})
} else if input.peek(kw::any) {
Ok(CfgExpr::Any {
any: input.parse()?,
paren: parenthesized!(contents in input),
exprs: contents.call(Punctuated::parse_terminated)?,
})
} else if input.peek(kw::not) {
Ok(CfgExpr::Not {
not: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
trailing_comma: contents.parse()?,
})
} else {
Err(input.error("expected cfg-pattern"))
}
}
}
impl ToTokens for CfgExpr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
CfgExpr::Option { ident, value } => {
ident.to_tokens(tokens);
if let Some((eq, value)) = value {
eq.to_tokens(tokens);
value.to_tokens(tokens);
}
}
CfgExpr::All { all, paren, exprs } => {
all.to_tokens(tokens);
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
}
CfgExpr::Any { any, paren, exprs } => {
any.to_tokens(tokens);
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
}
CfgExpr::Not {
not,
paren,
expr,
trailing_comma,
} => {
not.to_tokens(tokens);
paren.surround(tokens, |tokens| {
expr.to_tokens(tokens);
trailing_comma.to_tokens(tokens);
});
}
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct Cfg {
cfg: kw::cfg,
paren: Paren,
expr: CfgExpr,
trailing_comma: Option<Token![,]>,
}
impl Cfg {
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
syn::parse2(meta.to_token_stream())
}
}
impl ToTokens for Cfg {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
cfg,
paren,
expr,
trailing_comma,
} = self;
cfg.to_tokens(tokens);
paren.surround(tokens, |tokens| {
expr.to_tokens(tokens);
trailing_comma.to_tokens(tokens);
});
}
}
impl Parse for Cfg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
Ok(Self {
cfg: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
trailing_comma: contents.parse()?,
})
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct CfgAttr {
cfg_attr: kw::cfg_attr,
paren: Paren,
expr: CfgExpr,
comma: Token![,],
attrs: Punctuated<Meta, Token![,]>,
}
impl CfgAttr {
pub(crate) fn to_cfg(&self) -> Cfg {
Cfg {
cfg: kw::cfg(self.cfg_attr.span),
paren: self.paren,
expr: self.expr.clone(),
trailing_comma: None,
}
}
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
syn::parse2(meta.to_token_stream())
}
}
impl Parse for CfgAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
Ok(Self {
cfg_attr: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
comma: contents.parse()?,
attrs: contents.call(Punctuated::parse_terminated)?,
})
}
}
pub(crate) struct CfgAndValue {
cfg: Cfg,
eq_token: Token![=],
value: LitBool,
}
impl Parse for CfgAndValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
cfg: input.parse()?,
eq_token: input.parse()?,
value: input.parse()?,
})
}
}
pub(crate) struct Cfgs<T> {
pub(crate) bracket: Bracket,
pub(crate) cfgs_map: HashMap<Cfg, T>,
pub(crate) cfgs_list: Vec<Cfg>,
}
impl<T> Default for Cfgs<T> {
fn default() -> Self {
Self {
bracket: Default::default(),
cfgs_map: Default::default(),
cfgs_list: Default::default(),
}
}
}
impl<T> Cfgs<T> {
fn insert_cfg(&mut self, cfg: Cfg, value: T) {
match self.cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
self.cfgs_list.push(entry.key().clone());
entry.insert(value);
}
}
}
}
impl Parse for Cfgs<bool> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
let bracket = bracketed!(contents in input);
let mut cfgs_map = HashMap::new();
let mut cfgs_list = Vec::new();
for CfgAndValue {
cfg,
eq_token,
value,
} in contents.call(Punctuated::<CfgAndValue, Token![,]>::parse_terminated)?
{
let _ = eq_token;
match cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
cfgs_list.push(entry.key().clone());
entry.insert(value.value);
}
}
}
Ok(Self {
bracket,
cfgs_map,
cfgs_list,
})
}
}
impl Parse for Cfgs<()> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
let bracket = bracketed!(contents in input);
let mut cfgs_map = HashMap::new();
let mut cfgs_list = Vec::new();
for cfg in contents.call(Punctuated::<Cfg, Token![,]>::parse_terminated)? {
match cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
cfgs_list.push(entry.key().clone());
entry.insert(());
}
}
}
Ok(Self {
bracket,
cfgs_map,
cfgs_list,
})
}
}
impl ToTokens for Cfgs<()> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
bracket,
cfgs_map: _,
cfgs_list,
} = self;
bracket.surround(tokens, |tokens| {
for cfg in cfgs_list {
cfg.to_tokens(tokens);
<Token![,]>::default().to_tokens(tokens);
}
});
}
}
fn hdl_main(
kw: impl CustomToken,
attr: TokenStream,
item: TokenStream,
) -> syn::Result<TokenStream> {
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| {
let peek = input.fork();
if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() {
let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::<bool>::parse)?;
Ok((Some(evaluated_cfgs), input.parse()?))
} else {
Ok((None, input.parse()?))
}
},
item,
)?;
let cfgs = if let Some(cfgs) = evaluated_cfgs {
cfgs
} else {
let cfgs = process_cfg::collect_cfgs(syn::parse2(item.clone())?)?;
if cfgs.cfgs_list.is_empty() {
Cfgs::default()
} else {
return Ok(quote! {
::fayalite::__cfg_expansion_helper! {
[]
#cfgs
{#[::fayalite::#kw(#attr)]} { #item }
}
});
}
};
let item = syn::parse2(quote! { #[#kw(#attr)] #item })?;
let Some(item) = process_cfg::process_cfgs(item, cfgs)? else {
return Ok(TokenStream::new());
};
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let kw = kw::hdl::default();
let item = quote! { #[#kw(#attr)] #item };
let item = syn::parse2::<Item>(item)?;
match item {
Item::Enum(item) => hdl_enum::hdl_enum(item),
Item::Struct(item) => hdl_bundle::hdl_bundle(item),
@ -1265,11 +921,3 @@ fn hdl_main(
)),
}
}
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
hdl_main(kw::hdl_module::default(), attr, item)
}
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
hdl_main(kw::hdl::default(), attr, item)
}

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,6 @@ use std::{env, fs, path::Path};
fn main() {
println!("cargo::rustc-check-cfg=cfg(todo)");
println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)");
println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)");
println!("cargo::rustc-cfg=cfg_true_for_tests");
let path = "visit_types.json";
println!("cargo::rerun-if-changed={path}");
println!("cargo::rerun-if-changed=build.rs");

View file

@ -11,59 +11,6 @@ extern crate self as fayalite;
#[doc(hidden)]
pub use std as __std;
#[doc(hidden)]
#[macro_export]
macro_rules! __cfg_expansion_helper {
(
[
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
]
[
$cfg:ident($($expr:tt)*),
$($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)*
]
// pass as tt so we get right span for attribute
$after_evaluation_attr:tt $after_evaluation_body:tt
) => {
#[$cfg($($expr)*)]
$crate::__cfg_expansion_helper! {
[
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
$cfg($($expr)*) = true,
]
[
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
]
$after_evaluation_attr $after_evaluation_body
}
#[$cfg(not($($expr)*))]
$crate::__cfg_expansion_helper! {
[
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
$cfg($($expr)*) = false,
]
[
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
]
$after_evaluation_attr $after_evaluation_body
}
};
(
[
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
]
[]
// don't use #[...] so we get right span for `#` and `[]` of attribute
{$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*}
) => {
$($after_evaluation_attr)*
#[__evaluated_cfgs([
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
])]
$($after_evaluation_body)*
};
}
#[doc(inline)]
/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates
/// a [`Module`][`::fayalite::module::Module`] when called.

View file

@ -1601,14 +1601,6 @@ impl MakeTraceDeclTarget {
}
}
struct DebugOpaque<T>(T);
impl<T> fmt::Debug for DebugOpaque<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<...>")
}
}
#[derive(Debug)]
pub struct Compiler {
insns: Insns<InsnsBuilding>,
@ -1630,7 +1622,6 @@ pub struct Compiler {
registers: Vec<Register>,
traces: SimTraces<Vec<SimTrace<SimTraceKind, ()>>>,
memories: Vec<Memory>,
dump_assignments_dot: Option<DebugOpaque<Box<dyn Fn(&dyn fmt::Debug)>>>,
}
impl Compiler {
@ -1656,14 +1647,8 @@ impl Compiler {
registers: Vec::new(),
traces: SimTraces(Vec::new()),
memories: Vec::new(),
dump_assignments_dot: None,
}
}
#[doc(hidden)]
/// This is explicitly unstable and may be changed/removed at any time
pub fn dump_assignments_dot(&mut self, callback: Box<dyn Fn(&dyn fmt::Debug)>) {
self.dump_assignments_dot = Some(DebugOpaque(callback));
}
fn new_sim_trace(&mut self, kind: SimTraceKind) -> TraceScalarId {
let id = TraceScalarId(self.traces.0.len());
self.traces.0.push(SimTrace {
@ -4665,11 +4650,12 @@ impl Compiler {
fn process_assignments(&mut self) {
self.assignments
.finalize(self.insns.state_layout().ty.clone().into());
if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot {
let graph =
petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements());
dump_assignments_dot(&petgraph::dot::Dot::new(&graph));
}
println!(
"{:#?}",
petgraph::dot::Dot::new(&petgraph::graph::DiGraph::<_, _, usize>::from_elements(
self.assignments.elements()
))
);
let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) {
Ok(nodes) => nodes
.into_iter()

View file

@ -5,7 +5,6 @@ use crate::{
enum_::{Enum, EnumType},
expr::Flow,
int::UInt,
intern::{Intern, Interned},
sim::{
time::{SimDuration, SimInstant},
TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl,
@ -16,73 +15,12 @@ use crate::{
},
};
use bitvec::{order::Lsb0, slice::BitSlice};
use hashbrown::{hash_map::Entry, HashMap};
use std::{
fmt::{self, Write as _},
io, mem,
fmt,
io::{self, Write},
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> {
writer: W,
timescale: SimDuration,
@ -159,20 +97,14 @@ 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>(
writer: &mut W,
scope_type: &str,
scope_name: Interned<str>,
scope: &mut Scope,
f: impl FnOnce(&mut W, &mut Scope) -> io::Result<R>,
scope_name: &str,
f: impl FnOnce(&mut W) -> io::Result<R>,
) -> io::Result<R> {
writeln!(
writer,
"$scope {scope_type} {} $end",
scope.new_identifier(scope_name),
)?;
let retval = f(writer, &mut Scope::default())?;
writeln!(writer, "$scope {scope_type} {scope_name} $end")?;
let retval = f(writer)?;
writeln!(writer, "$upscope $end")?;
Ok(retval)
}
@ -211,28 +143,24 @@ trait_arg! {
struct ArgModule<'a> {
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgModule<'a> {
fn reborrow(&mut self) -> ArgModule<'_> {
ArgModule {
properties: self.properties,
scope: self.scope,
}
}
}
struct ArgModuleBody<'a> {
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgModuleBody<'a> {
fn reborrow(&mut self) -> ArgModuleBody<'_> {
ArgModuleBody {
properties: self.properties,
scope: self.scope,
}
}
}
@ -242,7 +170,6 @@ struct ArgInType<'a> {
sink_var_type: &'static str,
duplex_var_type: &'static str,
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgInType<'a> {
@ -252,7 +179,6 @@ impl<'a> ArgInType<'a> {
sink_var_type: self.sink_var_type,
duplex_var_type: self.duplex_var_type,
properties: self.properties,
scope: self.scope,
}
}
}
@ -300,42 +226,55 @@ fn write_vcd_id<W: io::Write>(writer: &mut W, mut id: usize) -> io::Result<()> {
Ok(())
}
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:
// https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
struct Wrapper<W>(W);
impl<W: fmt::Write> fmt::Write for Wrapper<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
match byte {
b'\\' | b'\'' | b'"' | b'?' => {
self.0.write_str("\\")?;
self.0.write_char(byte as char)?;
}
b'\n' => self.0.write_str(r"\n")?,
b'\r' => self.0.write_str(r"\r")?,
b'\t' => self.0.write_str(r"\t")?,
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() {
self.0.write_char(byte as char)?;
} else {
write!(self.0, r"\x{byte:02x}")?;
}
fn write_escaped<W: io::Write>(writer: &mut W, value: impl fmt::Display) -> io::Result<()> {
// escaping rules from function GTKWave uses to decode VCD strings:
// https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
struct Wrapper<W>(W);
impl<W: io::Write> io::Write for Wrapper<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return self.0.write(buf);
}
let mut retval = 0;
for &byte in buf {
match byte {
b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?,
b'\n' => self.0.write_all(br"\n")?,
b'\r' => self.0.write_all(br"\r")?,
b'\t' => self.0.write_all(br"\t")?,
0x7 => self.0.write_all(br"\a")?,
0x8 => self.0.write_all(br"\b")?,
0xC => self.0.write_all(br"\f")?,
0xB => self.0.write_all(br"\v")?,
_ => {
if byte.is_ascii_graphic() {
self.0.write_all(&[byte])?;
} else {
write!(self.0, r"\x{byte:02x}")?;
}
}
}
Ok(())
retval += 1;
}
Ok(retval)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
write!(Wrapper(f), "{}", self.0)
}
write!(Wrapper(writer), "{value}")
}
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>(
@ -345,7 +284,7 @@ fn write_vcd_var<W: io::Write>(
var_type: &str,
size: usize,
location: TraceLocation,
name: VerilogIdentifier,
name: &str,
) -> io::Result<()> {
let id = match location {
TraceLocation::Scalar(id) => id.as_usize(),
@ -380,7 +319,12 @@ fn write_vcd_var<W: io::Write>(
};
write!(writer, "$var {var_type} {size} ")?;
write_vcd_id(writer, id)?;
writeln!(writer, " {name} $end")
writer.write_all(b" ")?;
if !is_unescaped_verilog_identifier(name) {
writer.write_all(b"\\")?;
}
write_escaped(writer, name)?;
writer.write_all(b" $end\n")
}
impl WriteTrace for TraceUInt {
@ -390,7 +334,6 @@ impl WriteTrace for TraceUInt {
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self {
location,
@ -413,7 +356,7 @@ impl WriteTrace for TraceUInt {
var_type,
ty.width(),
location,
scope.new_identifier(name),
&name,
)
}
}
@ -478,7 +421,6 @@ impl WriteTrace for TraceEnumDiscriminant {
sink_var_type: _,
duplex_var_type: _,
properties,
scope,
} = arg.in_type();
let Self {
location,
@ -493,7 +435,7 @@ impl WriteTrace for TraceEnumDiscriminant {
"string",
1,
location,
scope.new_identifier(name),
&name,
)
}
}
@ -565,11 +507,11 @@ impl WriteTrace for TraceScope {
impl WriteTrace for TraceModule {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModule { properties, scope } = arg.module();
let ArgModule { properties } = arg.module();
let Self { name, children } = self;
write_vcd_scope(writer, "module", name, scope, |writer, scope| {
write_vcd_scope(writer, "module", &name, |writer| {
for child in children {
child.write_trace(writer, ArgModuleBody { properties, scope })?;
child.write_trace(writer, ArgModuleBody { properties })?;
}
Ok(())
})
@ -578,7 +520,7 @@ impl WriteTrace for TraceModule {
impl WriteTrace for TraceInstance {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
name: _,
instance_io,
@ -592,16 +534,15 @@ impl WriteTrace for TraceInstance {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)?;
module.write_trace(writer, ArgModule { properties, scope })
module.write_trace(writer, ArgModule { properties })
}
}
impl WriteTrace for TraceMem {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
id,
name,
@ -610,41 +551,27 @@ impl WriteTrace for TraceMem {
ports,
array_type,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
write_vcd_scope(
writer,
"struct",
"contents".intern(),
scope,
|writer, scope| {
for element_index in 0..array_type.len() {
write_vcd_scope(
write_vcd_scope(writer, "struct", &*name, |writer| {
write_vcd_scope(writer, "struct", "contents", |writer| {
for element_index in 0..array_type.len() {
write_vcd_scope(writer, "struct", &format!("[{element_index}]"), |writer| {
properties.memory_properties[id.as_usize()].element_index = element_index;
properties.memory_properties[id.as_usize()].element_part_index = 0;
element_type.write_trace(
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;
element_type.write_trace(
writer,
ArgInType {
source_var_type: "reg",
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
},
)
ArgInType {
source_var_type: "reg",
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
},
)?;
}
Ok(())
},
)?;
)
})?;
}
Ok(())
})?;
for port in ports {
port.write_trace(writer, ArgModuleBody { properties, scope })?;
port.write_trace(writer, ArgModuleBody { properties })?;
}
Ok(())
})
@ -653,7 +580,7 @@ impl WriteTrace for TraceMem {
impl WriteTrace for TraceMemPort {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
name: _,
bundle,
@ -666,7 +593,6 @@ impl WriteTrace for TraceMemPort {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
@ -674,7 +600,7 @@ impl WriteTrace for TraceMemPort {
impl WriteTrace for TraceWire {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
name: _,
child,
@ -687,7 +613,6 @@ impl WriteTrace for TraceWire {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
@ -695,7 +620,7 @@ impl WriteTrace for TraceWire {
impl WriteTrace for TraceReg {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
name: _,
child,
@ -708,7 +633,6 @@ impl WriteTrace for TraceReg {
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
},
)
}
@ -716,7 +640,7 @@ impl WriteTrace for TraceReg {
impl WriteTrace for TraceModuleIO {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let ArgModuleBody { properties } = arg.module_body();
let Self {
name: _,
child,
@ -730,7 +654,6 @@ impl WriteTrace for TraceModuleIO {
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
@ -738,31 +661,16 @@ impl WriteTrace for TraceModuleIO {
impl WriteTrace for TraceBundle {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let mut arg = arg.in_type();
let Self {
name,
fields,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
write_vcd_scope(writer, "struct", &name, |writer| {
for field in fields {
field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
field.write_trace(writer, arg.reborrow())?;
}
Ok(())
})
@ -771,31 +679,16 @@ impl WriteTrace for TraceBundle {
impl WriteTrace for TraceArray {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let mut arg = arg.in_type();
let Self {
name,
elements,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
write_vcd_scope(writer, "struct", &name, |writer| {
for element in elements {
element.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
element.write_trace(writer, arg.reborrow())?;
}
Ok(())
})
@ -804,13 +697,7 @@ impl WriteTrace for TraceArray {
impl WriteTrace for TraceEnumWithFields {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let mut arg = arg.in_type();
let Self {
name,
discriminant,
@ -818,28 +705,10 @@ impl WriteTrace for TraceEnumWithFields {
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
discriminant.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
write_vcd_scope(writer, "struct", &name, |writer| {
discriminant.write_trace(writer, arg.reborrow())?;
for field in non_empty_fields {
field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
field.write_trace(writer, arg.reborrow())?;
}
Ok(())
})
@ -875,7 +744,6 @@ impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
&mut writer,
ArgModule {
properties: &mut properties,
scope: &mut Scope::default(),
},
)?;
writeln!(writer, "$enddefinitions $end")?;
@ -930,7 +798,9 @@ fn write_string_value_change(
value: impl fmt::Display,
id: usize,
) -> io::Result<()> {
write!(writer, "s{} ", Escaped(value))?;
writer.write_all(b"s")?;
write_escaped(writer, value)?;
writer.write_all(b" ")?;
write_vcd_id(writer, id)?;
writer.write_all(b"\n")
}
@ -1076,49 +946,3 @@ impl<W: io::Write> fmt::Debug for VcdWriter<W> {
.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,18 +49,6 @@ 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]
pub fn queue<T: Type>(
ty: T,
@ -190,22 +178,6 @@ 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)]
@ -224,23 +196,13 @@ mod tests {
format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"),
queue_test(capacity, inp_ready_is_comb, out_valid_is_comb),
FormalMode::Prove,
2,
14,
None,
ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
..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]
fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
#[hdl]
@ -255,8 +217,6 @@ mod tests {
rst: formal_reset().to_reset(),
},
);
// random input data
#[hdl]
let inp_data: HdlOption<UInt<8>> = wire();
#[hdl]
@ -265,26 +225,16 @@ mod tests {
} else {
connect(inp_data, HdlNone());
}
// assert output ready at random
#[hdl]
let out_ready: Bool = wire();
connect(out_ready, any_seq(Bool));
// 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
let index_ty: UInt<32> = UInt::TYPE;
#[hdl]
let index_to_check = wire(index_ty);
let index_to_check = wire();
connect(index_to_check, any_const(index_ty));
hdl_assume(clk, index_to_check.cmp_lt(capacity.get()), "");
// instantiate and connect the queue
let index_max = !index_ty.zero();
// we saturate at index_max, so only check indexes where we properly maintain position
hdl_assume(clk, index_to_check.cmp_ne(index_max), "");
#[hdl]
let dut = instance(queue(
UInt[ConstUsize::<8>],
@ -295,172 +245,109 @@ mod tests {
connect(dut.cd, cd);
connect(dut.inp.data, inp_data);
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]
let expected_count_reg = reg_builder().clock_domain(cd).reset(count_ty.zero());
let expected_count_reg = reg_builder().clock_domain(cd).reset(0u32);
#[hdl]
let next_expected_count = wire();
connect(next_expected_count, expected_count_reg);
connect(expected_count_reg, next_expected_count);
#[hdl]
if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) {
hdl_assert(clk, expected_count_reg.cmp_ne(capacity.get()), "");
connect_any(expected_count_reg, expected_count_reg + 1u8);
connect_any(next_expected_count, expected_count_reg + 1u8);
} else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) {
hdl_assert(clk, expected_count_reg.cmp_ne(count_ty.zero()), "");
connect_any(expected_count_reg, expected_count_reg - 1u8);
connect_any(next_expected_count, expected_count_reg - 1u8);
}
hdl_assert(clk, expected_count_reg.cmp_eq(dut.count), "");
hdl_assert(cd.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]
let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
if ReadyValid::firing(dut.inp) {
let stored_inp_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.inp) {
#[hdl]
if inp_index_reg.cmp_ne(capacity.get() - 1) {
if inp_index_reg.cmp_lt(index_max) {
connect_any(inp_index_reg, inp_index_reg + 1u8);
} else {
connect_any(inp_index_reg, 0_hdl_u0);
#[hdl]
if inp_index_reg.cmp_eq(index_to_check) {
connect(stored_inp_data_reg, data);
}
}
}
// keep an independent read index into the FIFO's circular buffer
#[hdl]
if inp_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), "");
}
#[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
if ReadyValid::firing(dut.out) {
let stored_out_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.out) {
#[hdl]
if out_index_reg.cmp_ne(capacity.get() - 1) {
if out_index_reg.cmp_lt(index_max) {
connect_any(out_index_reg, out_index_reg + 1u8);
} else {
connect_any(out_index_reg, 0_hdl_u0);
}
}
// filter the input data stream, predicated by the read index
// matching the chosen position in the FIFO's circular buffer
#[hdl]
let inp_index_matches = wire();
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);
}
if out_index_reg.cmp_eq(index_to_check) {
connect(stored_out_data_reg, 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), "");
if out_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), "");
}
// 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), "");
hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), "");
// 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);
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 {
connect(
pending_reads,
index_to_check + capacity.get() - out_index_reg,
hdl_assert(
clk,
expected_count_reg.cmp_ge(inp_index_reg - 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)),
"",
);
if inp_index_reg.cmp_gt(index_to_check) & out_index_reg.cmp_gt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(stored_out_data_reg), "");
}
}
}
@ -543,24 +430,4 @@ mod tests {
fn test_4_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

@ -4287,61 +4287,3 @@ circuit check_deduce_resets:
",
};
}
// 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>(
#[cfg(cfg_false_for_tests)] a: A,
#[cfg(cfg_true_for_tests)] b: B,
) {
#[hdl]
struct S<#[cfg(cfg_false_for_tests)] A, #[cfg(cfg_true_for_tests)] B> {
#[cfg(cfg_false_for_tests)]
a: A,
#[cfg(cfg_true_for_tests)]
b: B,
}
#[hdl]
#[cfg(cfg_false_for_tests)]
let i_a: A = m.input(a);
#[hdl]
#[cfg(cfg_true_for_tests)]
let i_b: B = m.input(b);
#[hdl]
let w: S<UInt<8>> = wire();
#[cfg(cfg_false_for_tests)]
{
#[hdl]
let o_a: A = m.output(a);
connect(o_a, w.a.cast_bits_to(a));
connect_any(w.a, i_a.cast_to_bits());
}
#[cfg(cfg_true_for_tests)]
{
#[hdl]
let o_b: B = m.output(b);
connect(o_b, w.b.cast_bits_to(b));
connect_any(w.b, i_b.cast_to_bits());
}
}
#[test]
fn test_cfgs() {
let _n = SourceLocation::normalize_files_for_tests();
let m = check_cfgs(UInt[8]);
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
"/test/check_cfgs.fir": r"FIRRTL version 3.2.0
circuit check_cfgs:
type Ty0 = {b: UInt<8>}
module check_cfgs: @[the_test_file.rs 9962:1]
input i_b: UInt<8> @[the_test_file.rs 9979:20]
output o_b: UInt<8> @[the_test_file.rs 9992:24]
wire w: Ty0 @[the_test_file.rs 9981:25]
connect o_b, w.b @[the_test_file.rs 9993:9]
connect w.b, i_b @[the_test_file.rs 9994:9]
",
};
}

View file

@ -1246,164 +1246,3 @@ fn test_memories3() {
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!();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,283 +0,0 @@
$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

@ -1,153 +0,0 @@
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

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

View file

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

View file

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