// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information #![allow(clippy::type_complexity)] use crate::{ annotations::{ Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation, DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::Array, build::{ToArgs, WriteArgs}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, expr::{ CastBitsTo, Expr, ExprEnum, ops::{self, VariantAccess}, target::{ Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, }, formal::FormalKind, int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue}, intern::{Intern, Interned}, memory::{Mem, PortKind, PortName, ReadUnderWrite}, module::{ AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter, ExternModuleParameterValue, Module, ModuleBody, ModuleIO, NameId, NameOptId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, transform::{ simplify_enums::{SimplifyEnumsError, SimplifyEnumsKind, simplify_enums}, simplify_memories::simplify_memories, }, }, reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, ty::{CanonicalType, OpaqueSimValueSize, Type}, util::{ BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet, const_str_array_is_strictly_ascending, }, }; use bitvec::slice::BitSlice; use clap::value_parser; use num_traits::Signed; use serde::{Deserialize, Serialize}; use std::{ cell::{Cell, RefCell}, cmp::Ordering, collections::{BTreeMap, VecDeque}, error::Error, fmt::{self, Write}, fs, hash::Hash, io, ops::{ControlFlow, Range}, path::{Path, PathBuf}, rc::Rc, }; #[derive(Clone, Debug)] #[non_exhaustive] enum FirrtlError { SimOnlyValuesAreNotPermitted, } impl fmt::Display for FirrtlError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FirrtlError::SimOnlyValuesAreNotPermitted => { f.write_str("`SimOnlyValue`s are not permitted") } } } } impl std::error::Error for FirrtlError {} enum FirrtlOrWrappedError { FirrtlError(FirrtlError), WrappedError(WrappedError), } impl From for FirrtlOrWrappedError { fn from(value: FirrtlError) -> Self { Self::FirrtlError(value) } } impl From for FirrtlOrWrappedError { fn from(value: WrappedError) -> Self { Self::WrappedError(value) } } type Result = std::result::Result; struct EscapedString<'a> { value: &'a str, raw: bool, } impl fmt::Display for EscapedString<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let quote = if self.raw { "'" } else { "\"" }; f.write_str(quote)?; for ch in self.value.chars() { match (ch, self.raw) { ('\\', false) => f.write_str(r"\\")?, ('\'', true) => f.write_str(r"\'")?, ('\"', false) => f.write_str(r#"\""#)?, ('\x08', false) => f.write_str(r"\b")?, ('\t', false) => f.write_str(r"\t")?, ('\x20'..='\x7E', _) => f.write_char(ch)?, // vertical whitespace is unrepresentable in raw strings, so escape either way ('\n', _) => f.write_str(r"\n")?, ('\x0C', _) => f.write_str(r"\f")?, ('\r', _) => f.write_str(r"\r")?, ('\x0B', _) => f.write_str(r"\v")?, (ch, false) => { for b in ch.encode_utf8(&mut [0; 4]).bytes() { write!(f, r"\{b:02X}")?; } } (ch, true) => f.write_char(ch)?, } } f.write_str(quote) } } impl fmt::Debug for EscapedString<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } /// from https://github.com/llvm/circt/blob/d5327de174c0fc96b838e505c4b1ee8eb689e59b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def#L86 const FIRRTL_KEYWORDS: &[&str] = &[ "Analog", "AnyRef", "AsyncReset", "Bool", "Clock", "Double", "FIRRTL", "Fixed", "Inst", "Integer", "List", "Path", "Probe", "RWProbe", "Reset", "SInt", "String", "UInt", "attach", "case", "circuit", "class", "cmem", "connect", "const", "declgroup", "define", "defname", "else", "enablelayer", "extclass", "extmodule", "false", "flip", "group", "infer", "input", "inst", "instchoice", "intmodule", "intrinsic", "invalid", "invalidate", "is", "layer", "layerblock", "match", "mem", "module", "mport", "new", "node", "object", "of", "old", "option", "output", "parameter", "propassign", "public", "rdwr", "read", "ref", "reg", "regreset", "reset", "skip", "smem", "true", "type", "undefined", "version", "when", "wire", "with", "write", ]; const _: () = assert!(const_str_array_is_strictly_ascending(FIRRTL_KEYWORDS)); #[derive(Default)] struct NameMaker { symbols: HashSet>, last_inserted: HashMap<(Interned, usize), usize>, } impl NameMaker { fn make(&mut self, name: impl Into) -> Ident { let mut num = 0usize; let name: String = name.into(); // remove all invalid characters -- all valid characters are ASCII, so we can just remove invalid bytes let mut name = String::from_iter( name.bytes() .filter(|&ch| ch.is_ascii_alphanumeric() || ch == b'_') .map(char::from), ); let name_len = name.len(); let mut original_name = None; loop { name.truncate(name_len); if name_len == 0 || num != 0 { write!(name, "_{num}").unwrap(); } let name = str::intern(&name); let original_name = *original_name.get_or_insert(name); if let Some(&last_num) = self.last_inserted.get(&(name, name_len)) { num = last_num.wrapping_add(1); continue; } if self.symbols.insert(name) { self.last_inserted.insert((original_name, name_len), num); break Ident(name); } num = num.wrapping_add(1); } } } #[derive(Default)] struct Namespace { name_maker: NameMaker, map: HashMap, } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] struct Ident(Interned); impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.0.as_bytes()[0].is_ascii_digit() || FIRRTL_KEYWORDS.binary_search(&&*self.0).is_ok() { f.write_str("`")?; f.write_str(&self.0)?; f.write_str("`") } else { f.write_str(&self.0) } } } impl From for Ident { fn from(value: PortName) -> Self { Self(Intern::intern_owned(value.to_string())) } } impl Namespace { fn get(&mut self, name: impl Into) -> Ident { let name: NameOptId = name.into(); #[cold] fn make(name_maker: &mut NameMaker, name: NameOptId) -> Ident { name_maker.make(name.0) } *self .map .entry(name) .or_insert_with(|| make(&mut self.name_maker, name)) } fn make_new(&mut self, prefix: &str) -> Ident { self.name_maker.make(prefix) } } #[derive(Default, Debug, Copy, Clone)] struct FileInfo(Option); impl FileInfo { const fn new(source_location: SourceLocation) -> Self { Self(Some(source_location)) } } impl fmt::Display for FileInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Some(loc) = self.0 else { return Ok(()); }; f.write_str(" @[")?; for ch in loc.file().chars() { match ch { '\n' => f.write_str("\\n")?, '\t' => f.write_str("\\t")?, '\\' => f.write_str("\\\\")?, ']' => f.write_str("\\]")?, _ => f.write_char(ch)?, } } write!(f, " {}:{}]", loc.line(), loc.column()) } } #[derive(Default, Clone)] struct RcDefinitions { unindented_lines: Rc>, } impl RcDefinitions { fn add_definition_line(&self, v: impl fmt::Display) { writeln!(self.unindented_lines.borrow_mut(), "{v}").unwrap(); } fn write_and_clear(&self, indent: Indent<'_>, out: &mut String) { let mut unindented_lines = self.unindented_lines.borrow_mut(); for line in unindented_lines.lines() { writeln!(out, "{indent}{line}").unwrap(); } unindented_lines.clear(); } } struct DefinitionsMap { definitions: RcDefinitions, map: RefCell>, } impl DefinitionsMap { fn new(definitions: RcDefinitions) -> Self { Self { definitions, map: Default::default(), } } fn get_or_make<'a, E>( &'a self, key: K, make: impl FnOnce(&K, &'a RcDefinitions) -> Result<(Ident, V), E>, ) -> Result<(Ident, V), E> where K: Hash + Eq, V: Clone, { if let Some(retval) = self.map.borrow().get(&key) { return Ok(retval.clone()); } let value = make(&key, &self.definitions)?; Ok(self.map.borrow_mut().entry(key).or_insert(value).clone()) } } struct EnumDef { variants: RefCell, body: String, } struct TypeState { definitions: RcDefinitions, bundle_defs: DefinitionsMap>>, enum_defs: DefinitionsMap>, next_type_name: Cell, } impl Default for TypeState { fn default() -> Self { let definitions = RcDefinitions::default(); Self { bundle_defs: DefinitionsMap::new(definitions.clone()), enum_defs: DefinitionsMap::new(definitions.clone()), definitions, next_type_name: Cell::new(0), } } } impl TypeState { fn make_type_name(&self) -> Ident { let id = self.next_type_name.get(); self.next_type_name.set(id + 1); Ident(Intern::intern_owned(format!("Ty{id}"))) } fn get_bundle_field(&mut self, ty: Bundle, name: Interned) -> Result { Ok(self.bundle_ns(ty)?.borrow_mut().get(name)) } fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc>), FirrtlError> { self.bundle_defs.get_or_make(ty, |&ty, definitions| { let mut ns = Namespace::default(); let mut body = String::new(); body.push('{'); let mut separator = ""; for BundleField { name, flipped, ty } in ty.fields() { body.push_str(separator); separator = ", "; if flipped { body.push_str("flip "); } write!(body, "{}: ", ns.get(name)).unwrap(); body.push_str(&self.ty(ty)?); } body.push('}'); let name = self.make_type_name(); definitions.add_definition_line(format_args!("type {name} = {body}")); Ok((name, Rc::new(RefCell::new(ns)))) }) } fn bundle_ty(&self, ty: Bundle) -> Result { Ok(self.bundle_def(ty)?.0) } fn bundle_ns(&self, ty: Bundle) -> Result>, FirrtlError> { Ok(self.bundle_def(ty)?.1) } fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc), FirrtlError> { self.enum_defs.get_or_make(ty, |&ty, definitions| { let mut variants = Namespace::default(); let mut body = String::new(); body.push_str("{|"); let mut separator = ""; for EnumVariant { name, ty } in ty.variants() { body.push_str(separator); separator = ", "; write!(body, "{}", variants.get(name)).unwrap(); if let Some(ty) = ty { body.push_str(": "); body.push_str(&self.ty(ty)?); } } body.push_str("|}"); let name = self.make_type_name(); definitions.add_definition_line(format_args!("type {name} = {body}")); Ok(( name, Rc::new(EnumDef { variants: RefCell::new(variants), body, }), )) }) } fn enum_ty(&self, ty: Enum) -> Result { Ok(self.enum_def(ty)?.0) } fn get_enum_variant(&mut self, ty: Enum, name: Interned) -> Result { Ok(self.enum_def(ty)?.1.variants.borrow_mut().get(name)) } fn ty(&self, ty: T) -> Result { Ok(match ty.canonical() { CanonicalType::Bundle(ty) => self.bundle_ty(ty)?.to_string(), CanonicalType::Enum(ty) => self.enum_ty(ty)?.to_string(), CanonicalType::Array(ty) => { let mut retval = self.ty(ty.element())?; write!(retval, "[{}]", ty.len()).unwrap(); retval } CanonicalType::UInt(ty) => format!("UInt<{}>", ty.width), CanonicalType::SInt(ty) => format!("SInt<{}>", ty.width), CanonicalType::Bool(Bool {}) => "UInt<1>".into(), CanonicalType::Clock(Clock {}) => "Clock".into(), CanonicalType::AsyncReset(AsyncReset {}) => "AsyncReset".into(), CanonicalType::SyncReset(SyncReset {}) => "UInt<1>".into(), CanonicalType::Reset(Reset {}) => "Reset".into(), CanonicalType::PhantomConst(_) => "{}".into(), CanonicalType::DynSimOnly(_) => { return Err(FirrtlError::SimOnlyValuesAreNotPermitted); } }) } } struct ModuleState { ns: Namespace, definitions: RcDefinitions, match_arm_values: HashMap, Ident>, } impl Default for ModuleState { fn default() -> Self { let definitions = RcDefinitions::default(); Self { ns: Default::default(), definitions, match_arm_values: Default::default(), } } } struct WrappedError; trait WrappedFileBackendTrait { fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result; fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), WrappedError>; fn simplify_enums_error(&mut self, error: SimplifyEnumsError) -> WrappedError; fn firrtl_error(&mut self, error: FirrtlError) -> WrappedError; } struct WrappedFileBackend { file_backend: B, error: Result<(), B::Error>, } impl WrappedFileBackend { fn with( file_backend: B, f: impl FnOnce(&mut dyn WrappedFileBackendTrait) -> Result<(), WrappedError>, ) -> Result { let mut this = Self { file_backend, error: Ok(()), }; match f(&mut this) { Ok(()) => Ok(this.file_backend), Err(WrappedError) => Err(this.error.expect_err("should have set self.error")), } } } impl WrappedFileBackendTrait for WrappedFileBackend { fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result { let path = self .file_backend .write_mem_init_file(module_name, memory_name, contents) .map_err(|e| { self.error = Err(e); WrappedError })?; self.file_backend .path_to_string(path.as_ref()) .map_err(|e| { self.error = Err(e); WrappedError }) } fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), WrappedError> { self.file_backend .write_top_fir_file(circuit_name, contents) .map_err(|e| { self.error = Err(e); WrappedError }) } fn simplify_enums_error(&mut self, error: SimplifyEnumsError) -> WrappedError { self.error = Err(error.into()); WrappedError } fn firrtl_error(&mut self, error: FirrtlError) -> WrappedError { self.error = Err(self.file_backend.custom_error(Box::new(error))); WrappedError } } #[derive(Clone)] struct AnnotationTargetSubmodule { instance: Ident, submodule: Ident, } impl fmt::Display for AnnotationTargetSubmodule { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { instance: Ident(instance), submodule: Ident(submodule), } = self; write!(f, "/{instance}:{submodule}") } } #[derive(Clone)] #[allow(dead_code)] enum AnnotationTargetRefSegment { Field { name: Ident }, Index { index: usize }, } impl fmt::Display for AnnotationTargetRefSegment { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Field { name: Ident(name) } => write!(f, ".{name}"), Self::Index { index } => write!(f, "[{index}]"), } } } #[derive(Clone)] struct AnnotationTargetRef { base: Ident, segments: Vec, } impl fmt::Display for AnnotationTargetRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { base: Ident(base), segments, } = self; write!(f, ">{base}")?; segments.iter().try_for_each(|v| v.fmt(f)) } } #[derive(Clone)] struct AnnotationTargetPath { base_module: Ident, submodules: Vec, target_ref: Option, } impl fmt::Display for AnnotationTargetPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { base_module: Ident(base_module), submodules, target_ref, } = self; write!(f, "|{base_module}")?; submodules.iter().try_for_each(|v| v.fmt(f))?; let Some(target_ref) = target_ref else { return Ok(()); }; target_ref.fmt(f) } } struct AnnotationTarget { circuit: Ident, path: Option, } impl fmt::Display for AnnotationTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { circuit: Ident(circuit), path, } = self; write!(f, "~{circuit}")?; let Some(path) = path else { return Ok(()); }; path.fmt(f) } } impl Serialize for AnnotationTarget { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.collect_str(self) } } #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize)] enum HexOrBinary { #[serde(rename = "h")] Hex, #[serde(rename = "b")] Binary, } #[derive(Serialize)] #[serde(tag = "class")] enum AnnotationData { #[serde(rename = "firrtl.annotations.MemoryFileInlineAnnotation")] MemoryFileInline { filename: String, #[serde(rename = "hexOrBinary")] hex_or_binary: HexOrBinary, }, #[serde(rename = "firrtl.transforms.DontTouchAnnotation")] DontTouch, #[serde(rename = "firrtl.AttributeAnnotation")] AttributeAnnotation { description: Interned }, #[serde(rename = "firrtl.transforms.BlackBoxInlineAnno")] BlackBoxInlineAnno { name: Interned, text: Interned, }, #[serde(rename = "firrtl.transforms.BlackBoxPathAnno")] BlackBoxPathAnno { path: Interned }, #[serde(rename = "firrtl.DocStringAnnotation")] DocStringAnnotation { description: Interned }, #[allow(dead_code)] #[serde(untagged)] Other { class: String, #[serde(flatten)] additional_fields: serde_json::Map, }, } #[derive(Serialize)] struct FirrtlAnnotation { #[serde(flatten)] data: AnnotationData, target: AnnotationTarget, } struct Exporter<'a> { file_backend: &'a mut dyn WrappedFileBackendTrait, indent: Indent<'a>, seen_modules: HashSet>>, unwritten_modules: VecDeque>>, global_ns: Namespace, module: ModuleState, type_state: TypeState, circuit_name: Ident, annotations: Vec, } struct PushIndent<'a> { indent_depth: &'a Cell, } impl Drop for PushIndent<'_> { fn drop(&mut self) { self.indent_depth.set(self.indent_depth.get() - 1); } } #[derive(Copy, Clone)] struct Indent<'a> { indent_depth: &'a Cell, indent: &'a str, } impl<'a> Indent<'a> { fn push(self) -> PushIndent<'a> { self.indent_depth.set(self.indent_depth.get() + 1); PushIndent { indent_depth: self.indent_depth, } } } impl fmt::Display for Indent<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for _ in 0..self.indent_depth.get() { f.write_str(self.indent)?; } Ok(()) } } impl<'a> Exporter<'a> { fn add_module(&mut self, module: Interned>) { if self.seen_modules.insert(module) { self.unwritten_modules.push_back(module); } } fn run(&mut self, top_module: Interned>) -> Result<(), WrappedError> { let mut contents = self.version(); let circuit = self.circuit(top_module).map_err(|e| match e { FirrtlOrWrappedError::FirrtlError(e) => self.file_backend.firrtl_error(e), FirrtlOrWrappedError::WrappedError(e) => e, })?; contents.push_str(&circuit); self.file_backend .write_top_fir_file(self.circuit_name.to_string(), contents) } fn version(&mut self) -> String { "FIRRTL version 3.2.0\n".to_string() } fn circuit(&mut self, top_module: Interned>) -> Result { let indent = self.indent; self.add_module(top_module); let circuit_indent = indent.push(); let mut modules = vec![]; while let Some(module) = self.unwritten_modules.pop_front() { modules.push(self.module(module)?); } drop(circuit_indent); let mut out = format!("{indent}circuit {}:", self.circuit_name); if !self.annotations.is_empty() { let annotations = serde_json::to_string_pretty(&self.annotations).unwrap(); write!(out, " %[{annotations}]").unwrap(); } writeln!(out).unwrap(); let circuit_indent = indent.push(); self.type_state .definitions .write_and_clear(indent, &mut out); for module in modules { out.push_str(&module); } drop(circuit_indent); Ok(out) } fn enum_expr_impl( &mut self, enum_ty: Enum, variant_name: Interned, variant_expr: Option, ) -> Result { let (_, enum_def) = self.type_state.enum_def(enum_ty)?; let variant_ident = self.type_state.get_enum_variant(enum_ty, variant_name)?; let mut retval = enum_def.body.clone(); write!(retval, "({variant_ident}").unwrap(); if let Some(variant_expr) = variant_expr { retval.push_str(", "); retval.push_str(&variant_expr); } retval.push(')'); Ok(retval) } fn uint_literal(&mut self, value: &UIntValue) -> String { format!( "UInt<{width}>(0h{value:X})", width = value.ty().width, value = value.to_bigint() ) } fn sint_literal(&mut self, value: &SIntValue) -> String { let width = value.ty().width; let mut value = value.to_bigint(); let neg = if value.is_negative() { value = -value; "-" } else { "" }; format!("SInt<{width}>({neg}0h{value:X})") } fn bool_literal(&mut self, value: bool) -> String { format!("UInt<1>(0h{value})", value = value as u32) } fn int_cast( &mut self, value: Expr, to_ty: ToTy, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let from_ty = Expr::ty(value); let mut value = self.expr(Expr::canonical(value), definitions, const_ty)?; if from_ty.width().checked_add(1) == Some(to_ty.width()) && !FromTy::Signed::VALUE && ToTy::Signed::VALUE { Ok(format!("cvt({value})")) } else if from_ty.width() <= to_ty.width() { // must pad before changing type to preserve value modulo 2^to_ty.width if from_ty.width() < to_ty.width() { value = format!("pad({value}, {})", to_ty.width()); } if FromTy::Signed::VALUE == ToTy::Signed::VALUE { Ok(value) } else if ToTy::Signed::VALUE { Ok(format!("asSInt({value})")) } else { Ok(format!("asUInt({value})")) } } else { value = format!("tail({value}, {})", from_ty.width() - to_ty.width()); if ToTy::Signed::VALUE { Ok(format!("asSInt({value})")) } else { Ok(value) } } } fn typed_bit_cast( &mut self, firrtl_cast_fn: Option<&str>, value: Expr, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let value = self.expr(Expr::canonical(value), definitions, const_ty)?; if let Some(firrtl_cast_fn) = firrtl_cast_fn { Ok(format!("{firrtl_cast_fn}({value})")) } else { Ok(value) } } fn slice( &mut self, base: Expr, range: Range, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let base_width = Expr::ty(base).width(); let base = self.expr(Expr::canonical(base), definitions, const_ty)?; if range.is_empty() { Ok(format!("tail({base}, {base_width})")) } else { Ok(format!( "bits({base}, {hi}, {lo})", hi = range.end - 1, lo = range.start, )) } } fn array_literal_expr( &mut self, expr: ops::ArrayLiteral, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let ident = self.module.ns.make_new("_array_literal_expr"); let ty_str = self.type_state.ty(expr.ty())?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_str}")); for (index, element) in expr.element_values().into_iter().enumerate() { let element = self.expr(Expr::canonical(element), definitions, const_ty)?; definitions.add_definition_line(format_args!("connect {ident}[{index}], {element}")); } if expr.element_values().is_empty() { definitions.add_definition_line(format_args!("invalidate {ident}")); } Ok(ident.to_string()) } fn bundle_literal_expr( &mut self, expr: ops::BundleLiteral, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let ident = self.module.ns.make_new("_bundle_literal_expr"); let ty = expr.ty(); let (ty_ident, bundle_ns) = self.type_state.bundle_def(ty)?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_ident}")); for ( field_value, BundleField { name, flipped, ty: _, }, ) in expr.field_values().into_iter().zip(ty.fields()) { debug_assert!( !flipped, "can't have bundle literal with flipped field -- this should have been caught in BundleLiteral::new_unchecked" ); let name = bundle_ns.borrow_mut().get(name); let field_value = self.expr(Expr::canonical(field_value), definitions, const_ty)?; definitions.add_definition_line(format_args!("connect {ident}.{name}, {field_value}")); } if ty.fields().is_empty() { definitions.add_definition_line(format_args!("invalidate {ident}")); } Ok(ident.to_string()) } fn uninit_expr( &mut self, expr: ops::Uninit, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let ident = self.module.ns.make_new("_uninit_expr"); let ty = expr.ty(); let ty_ident = self.type_state.ty(ty)?; let const_ = if const_ty { "const " } else { "" }; definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_ident}")); definitions.add_definition_line(format_args!("invalidate {ident}")); Ok(ident.to_string()) } fn enum_literal_expr( &mut self, expr: ops::EnumLiteral, definitions: &RcDefinitions, const_ty: bool, ) -> Result { let variant_expr = expr .variant_value() .map(|variant_value| self.expr(variant_value, definitions, const_ty)) .transpose()?; self.enum_expr_impl(expr.ty(), expr.variant_name(), variant_expr) } fn expr_cast_bundle_to_bits( &mut self, value_str: String, ty: Bundle, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { if ty.fields().is_empty() { return Ok("UInt<0>(0)".into()); } if let [field] = *ty.fields() { let field_ident = self.type_state.get_bundle_field(ty, field.name)?; return self.expr_cast_to_bits( format!("{value_str}.{field_ident}"), field.ty, definitions, extra_indent, ); } let flattened_bundle_ty = Bundle::new(Interned::from_iter(ty.fields().iter().map( |&BundleField { name, flipped: _, ty: field_ty, }| BundleField { name, flipped: false, ty: UInt[field_ty.bit_width()].canonical(), }, ))); let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty)?; let ident = self.module.ns.make_new("_cast_bundle_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {ident}: {flattened_ty_ident}" )); let mut cat_expr = None; for field in ty.fields() { let field_ident = self.type_state.get_bundle_field(ty, field.name)?; let flattened_field_ident = self .type_state .get_bundle_field(flattened_bundle_ty, field.name)?; let field_bits = self.expr_cast_to_bits( format!("{value_str}.{field_ident}"), field.ty, definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {ident}.{flattened_field_ident}, {field_bits}" )); cat_expr = Some(if let Some(cat_expr) = cat_expr { format!("cat({ident}.{flattened_field_ident}, {cat_expr})") } else { format!("{ident}.{flattened_field_ident}") }); } let retval = self.module.ns.make_new("_cast_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {retval}: UInt<{}>", ty.type_properties().bit_width )); let cat_expr = cat_expr.expect("bundle already checked to have fields"); definitions.add_definition_line(format_args!("{extra_indent}connect {retval}, {cat_expr}")); Ok(retval.to_string()) } fn expr_cast_enum_to_bits( &mut self, value_str: String, ty: Enum, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { if ty.variants().is_empty() { return Ok("UInt<0>(0)".into()); } let retval = self.module.ns.make_new("_cast_enum_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {retval}: UInt<{}>", ty.type_properties().bit_width )); definitions.add_definition_line(format_args!("{extra_indent}match {value_str}:")); let _match_arms_indent = extra_indent.push(); for (variant_index, variant) in ty.variants().into_iter().enumerate() { if let Some(variant_ty) = variant.ty { let variant_value = self .module .ns .make_new(&format!("_cast_enum_to_bits_expr_{}", variant.name)); definitions.add_definition_line(format_args!( "{extra_indent}{}({variant_value}):", self.type_state.get_enum_variant(ty, variant.name)?, )); let _match_arm_indent = extra_indent.push(); let variant_bits = self.expr_cast_to_bits( variant_value.to_string(), variant_ty, definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, pad(cat({variant_bits}, UInt<{}>({variant_index})), {})", ty.discriminant_bit_width(), ty.type_properties().bit_width, )); } else { definitions.add_definition_line(format_args!( "{extra_indent}{}:", self.type_state.get_enum_variant(ty, variant.name)?, )); let _match_arm_indent = extra_indent.push(); definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, UInt<{}>({variant_index})", ty.type_properties().bit_width, )); } } Ok(retval.to_string()) } fn expr_cast_array_to_bits( &mut self, value_str: String, ty: Array, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { if ty.is_empty() { return Ok("UInt<0>(0)".into()); } if ty.len() == 1 { return self.expr_cast_to_bits( value_str + "[0]", ty.element(), definitions, extra_indent, ); } let element_width = ty.element().bit_width(); let ident = self.module.ns.make_new("_cast_array_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {ident}: UInt<{element_width}>[{}]", ty.len(), )); let mut cat_expr = None; for index in 0..ty.len() { let element_bits = self.expr_cast_to_bits( format!("{value_str}[{index}]"), ty.element(), definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {ident}[{index}], {element_bits}" )); cat_expr = Some(if let Some(cat_expr) = cat_expr { format!("cat({ident}[{index}], {cat_expr})") } else { format!("{ident}[{index}]") }); } let retval = self.module.ns.make_new("_cast_to_bits_expr"); definitions.add_definition_line(format_args!( "{extra_indent}wire {retval}: UInt<{}>", ty.type_properties().bit_width )); let cat_expr = cat_expr.expect("array already checked to have elements"); definitions.add_definition_line(format_args!("{extra_indent}connect {retval}, {cat_expr}")); Ok(retval.to_string()) } fn expr_cast_to_bits( &mut self, value_str: String, ty: CanonicalType, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { match ty { CanonicalType::Bundle(ty) => { self.expr_cast_bundle_to_bits(value_str, ty, definitions, extra_indent) } CanonicalType::Enum(ty) => { self.expr_cast_enum_to_bits(value_str, ty, definitions, extra_indent) } CanonicalType::Array(ty) => { self.expr_cast_array_to_bits(value_str, ty, definitions, extra_indent) } CanonicalType::UInt(_) | CanonicalType::SyncReset(_) | CanonicalType::Bool(_) => { Ok(value_str) } CanonicalType::SInt(_) | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::Reset(_) => Ok(format!("asUInt({value_str})")), CanonicalType::PhantomConst(_) => Ok("UInt<0>(0)".into()), CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()), } } fn expr_cast_bits_to_bundle( &mut self, value_str: String, ty: Bundle, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { let (ty_ident, _) = self.type_state.bundle_def(ty)?; let retval = self.module.ns.make_new("_cast_bits_to_bundle_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {ty_ident}")); if ty.fields().is_empty() { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); return Ok(retval.to_string()); } let flattened_bundle_ty = Bundle::new(Interned::from_iter(ty.fields().iter().map( |&BundleField { name, flipped: _, ty: field_ty, }| BundleField { name, flipped: false, ty: UInt[field_ty.bit_width()].canonical(), }, ))); let (flattened_ty_ident, _) = self.type_state.bundle_def(flattened_bundle_ty)?; let flattened_ident = self .module .ns .make_new("_cast_bits_to_bundle_expr_flattened"); definitions.add_definition_line(format_args!( "{extra_indent}wire {flattened_ident}: {flattened_ty_ident}" )); for ( field, OpaqueSimValueSize { bit_width: field_offset, sim_only_values_len: _, }, ) in ty.fields().into_iter().zip(ty.field_offsets()) { let flattened_field_ident = self .type_state .get_bundle_field(flattened_bundle_ty, field.name)?; let field_ident = self.type_state.get_bundle_field(ty, field.name)?; if let Some(field_bit_width_minus_one) = field.ty.bit_width().checked_sub(1usize) { definitions.add_definition_line(format_args!( "{extra_indent}connect {flattened_ident}.{flattened_field_ident}, bits({value_str}, {}, {field_offset})", field_offset + field_bit_width_minus_one )); } else { definitions.add_definition_line(format_args!( "{extra_indent}connect {flattened_ident}.{flattened_field_ident}, UInt<0>(0)" )); } let field_value = self.expr_cast_bits_to( format!("{flattened_ident}.{flattened_field_ident}"), field.ty, definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}.{field_ident}, {field_value}" )); } Ok(retval.to_string()) } fn expr_cast_bits_to_enum( &mut self, value_str: String, ty: Enum, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { let (ty_ident, enum_def) = self.type_state.enum_def(ty)?; let retval = self.module.ns.make_new("_cast_bits_to_enum_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {ty_ident}")); if ty.variants().is_empty() { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); return Ok(retval.to_string()); } if let [variant] = *ty.variants() { let enum_variant = self.type_state.get_enum_variant(ty, variant.name)?; if let Some(variant_ty) = variant.ty { let variant_value = self.expr_cast_bits_to(value_str, variant_ty, definitions, extra_indent)?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant}, {variant_value})", enum_def.body )); } else { definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant})", enum_def.body )); } return Ok(retval.to_string()); } let discriminant_bit_width = ty.discriminant_bit_width(); let body_bit_width = ty.type_properties().bit_width - discriminant_bit_width; let body_ident = self.module.ns.make_new("_cast_bits_to_enum_expr_body"); let body_value = if body_bit_width != 0 { definitions.add_definition_line(format_args!( "{extra_indent}wire {body_ident}: UInt<{body_bit_width}>" )); definitions.add_definition_line(format_args!( "{extra_indent}connect {body_ident}, head({value_str}, {body_bit_width})" )); body_ident.to_string() } else { "UInt<0>(0)".into() }; for (variant_index, variant) in ty.variants().into_iter().enumerate() { let when_cond = format!( "eq(UInt<{discriminant_bit_width}>({variant_index}), tail({value_str}, {body_bit_width}))" ); if variant_index == ty.variants().len() - 1 { definitions.add_definition_line(format_args!("{extra_indent}else:")); } else if variant_index == 0 { definitions.add_definition_line(format_args!("{extra_indent}when {when_cond}:")); } else { definitions .add_definition_line(format_args!("{extra_indent}else when {when_cond}:")); } let when_pushed_indent = extra_indent.push(); let enum_variant = self.type_state.get_enum_variant(ty, variant.name)?; if let Some(variant_ty) = variant.ty { let variant_value = self.expr_cast_bits_to( body_value.clone(), variant_ty, definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant}, {variant_value})", enum_def.body )); } else { definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}, {}({enum_variant})", enum_def.body )); } drop(when_pushed_indent); } Ok(retval.to_string()) } fn expr_cast_bits_to_array( &mut self, value_str: String, ty: Array, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { let retval = self.module.ns.make_new("_cast_bits_to_array_expr"); let array_ty = self.type_state.ty(ty)?; definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {array_ty}")); let element_bit_width = ty.element().bit_width(); if ty.is_empty() || element_bit_width == 0 { definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); return Ok(retval.to_string()); } let flattened_ident = self .module .ns .make_new("_cast_bits_to_array_expr_flattened"); definitions.add_definition_line(format_args!( "{extra_indent}wire {flattened_ident}: UInt<{element_bit_width}>[{}]", ty.len(), )); for index in 0..ty.len() { definitions.add_definition_line(format_args!( "{extra_indent}connect {flattened_ident}[{index}], bits({value_str}, {}, {})", element_bit_width * index + element_bit_width - 1, element_bit_width * index, )); let element_value = self.expr_cast_bits_to( format!("{flattened_ident}[{index}]"), ty.element(), definitions, extra_indent, )?; definitions.add_definition_line(format_args!( "{extra_indent}connect {retval}[{index}], {element_value}" )); } Ok(retval.to_string()) } fn expr_cast_bits_to( &mut self, value_str: String, ty: CanonicalType, definitions: &RcDefinitions, extra_indent: Indent<'_>, ) -> Result { match ty { CanonicalType::Bundle(ty) => { self.expr_cast_bits_to_bundle(value_str, ty, definitions, extra_indent) } CanonicalType::Enum(ty) => { self.expr_cast_bits_to_enum(value_str, ty, definitions, extra_indent) } CanonicalType::Array(ty) => { self.expr_cast_bits_to_array(value_str, ty, definitions, extra_indent) } CanonicalType::UInt(_) => Ok(value_str), CanonicalType::SInt(_) => Ok(format!("asSInt({value_str})")), CanonicalType::Bool(_) => Ok(value_str), CanonicalType::Clock(_) => Ok(format!("asClock({value_str})")), CanonicalType::AsyncReset(_) => Ok(format!("asAsyncReset({value_str})")), CanonicalType::SyncReset(_) => Ok(value_str), CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"), CanonicalType::PhantomConst(_) => { let retval = self.module.ns.make_new("_cast_bits_to_phantom_const_expr"); definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {{}}")); definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); return Ok(retval.to_string()); } CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()), } } fn expr_unary( &mut self, func: &str, arg: Expr, definitions: &RcDefinitions, const_ty: bool, ) -> Result { Ok(format!( "{func}({arg})", arg = self.expr(Expr::canonical(arg), definitions, const_ty)?, )) } fn expr_binary( &mut self, func: &str, lhs: Expr, rhs: Expr, definitions: &RcDefinitions, const_ty: bool, ) -> Result { Ok(format!( "{func}({lhs}, {rhs})", lhs = self.expr(Expr::canonical(lhs), definitions, const_ty)?, rhs = self.expr(Expr::canonical(rhs), definitions, const_ty)?, )) } fn expr( &mut self, expr: Expr, definitions: &RcDefinitions, const_ty: bool, ) -> Result { match *Expr::expr_enum(expr) { ExprEnum::UIntLiteral(literal) => Ok(self.uint_literal(&literal)), ExprEnum::SIntLiteral(literal) => Ok(self.sint_literal(&literal)), ExprEnum::BoolLiteral(literal) => Ok(self.bool_literal(literal)), ExprEnum::PhantomConst(ty) => self.expr( UInt[0].zero().cast_bits_to(ty.canonical()), definitions, const_ty, ), ExprEnum::ArrayLiteral(array_literal) => { self.array_literal_expr(array_literal, definitions, const_ty) } ExprEnum::BundleLiteral(bundle_literal) => { self.bundle_literal_expr(bundle_literal, definitions, const_ty) } ExprEnum::EnumLiteral(enum_literal) => { self.enum_literal_expr(enum_literal, definitions, const_ty) } ExprEnum::Uninit(uninit) => self.uninit_expr(uninit, definitions, const_ty), ExprEnum::NotU(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty), ExprEnum::NotS(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty), ExprEnum::NotB(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty), ExprEnum::Neg(expr) => self.expr_unary("neg", expr.arg(), definitions, const_ty), ExprEnum::BitAndU(expr) => { self.expr_binary("and", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitAndS(expr) => { self.expr_binary("and", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitAndB(expr) => { self.expr_binary("and", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitOrU(expr) => { self.expr_binary("or", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitOrS(expr) => { self.expr_binary("or", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitOrB(expr) => { self.expr_binary("or", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitXorU(expr) => { self.expr_binary("xor", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitXorS(expr) => { self.expr_binary("xor", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::BitXorB(expr) => { self.expr_binary("xor", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::AddU(expr) => { self.expr_binary("add", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::AddS(expr) => { self.expr_binary("add", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::SubU(expr) => { self.expr_binary("sub", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::SubS(expr) => { self.expr_binary("sub", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::MulU(expr) => { self.expr_binary("mul", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::MulS(expr) => { self.expr_binary("mul", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DivU(expr) => { self.expr_binary("div", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DivS(expr) => { self.expr_binary("div", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::RemU(expr) => { self.expr_binary("rem", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::RemS(expr) => { self.expr_binary("rem", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DynShlU(expr) => { self.expr_binary("dshl", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DynShlS(expr) => { self.expr_binary("dshl", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DynShrU(expr) => { self.expr_binary("dshr", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::DynShrS(expr) => { self.expr_binary("dshr", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::FixedShlU(expr) => Ok(format!( "shl({lhs}, {rhs})", lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, rhs = expr.rhs(), )), ExprEnum::FixedShlS(expr) => Ok(format!( "shl({lhs}, {rhs})", lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, rhs = expr.rhs(), )), ExprEnum::FixedShrU(expr) => Ok(format!( "shr({lhs}, {rhs})", lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, rhs = expr.rhs(), )), ExprEnum::FixedShrS(expr) => Ok(format!( "shr({lhs}, {rhs})", lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty)?, rhs = expr.rhs(), )), ExprEnum::CmpLtU(expr) => { self.expr_binary("lt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpLtS(expr) => { self.expr_binary("lt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpLtB(expr) => { self.expr_binary("lt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpLeU(expr) => { self.expr_binary("leq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpLeS(expr) => { self.expr_binary("leq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpLeB(expr) => { self.expr_binary("leq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGtU(expr) => { self.expr_binary("gt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGtS(expr) => { self.expr_binary("gt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGtB(expr) => { self.expr_binary("gt", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGeU(expr) => { self.expr_binary("geq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGeS(expr) => { self.expr_binary("geq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpGeB(expr) => { self.expr_binary("geq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpEqU(expr) => { self.expr_binary("eq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpEqS(expr) => { self.expr_binary("eq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpEqB(expr) => { self.expr_binary("eq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpNeU(expr) => { self.expr_binary("neq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpNeS(expr) => { self.expr_binary("neq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CmpNeB(expr) => { self.expr_binary("neq", expr.lhs(), expr.rhs(), definitions, const_ty) } ExprEnum::CastUIntToUInt(expr) => { self.int_cast(expr.arg(), expr.ty(), definitions, const_ty) } ExprEnum::CastUIntToSInt(expr) => { self.int_cast(expr.arg(), expr.ty(), definitions, const_ty) } ExprEnum::CastSIntToUInt(expr) => { self.int_cast(expr.arg(), expr.ty(), definitions, const_ty) } ExprEnum::CastSIntToSInt(expr) => { self.int_cast(expr.arg(), expr.ty(), definitions, const_ty) } ExprEnum::SliceUInt(expr) => { self.slice(expr.base(), expr.range(), definitions, const_ty) } ExprEnum::SliceSInt(expr) => { self.slice(expr.base(), expr.range(), definitions, const_ty) } ExprEnum::CastToBits(expr) => { let value_str = self.expr(expr.arg(), definitions, const_ty)?; self.expr_cast_to_bits( value_str, Expr::ty(expr.arg()), definitions, Indent { indent_depth: &Cell::new(0), indent: self.indent.indent, }, ) } ExprEnum::CastBitsTo(expr) => { let value_str = self.expr(Expr::canonical(expr.arg()), definitions, const_ty)?; self.expr_cast_bits_to( value_str, expr.ty(), definitions, Indent { indent_depth: &Cell::new(0), indent: self.indent.indent, }, ) } ExprEnum::CastBoolToUInt(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastBoolToSInt(expr) => { self.typed_bit_cast(Some("asSInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastUIntToBool(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastSIntToBool(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastBoolToClock(expr) => { self.typed_bit_cast(Some("asClock"), expr.arg(), definitions, const_ty) } ExprEnum::CastUIntToClock(expr) => { self.typed_bit_cast(Some("asClock"), expr.arg(), definitions, const_ty) } ExprEnum::CastSIntToClock(expr) => { self.typed_bit_cast(Some("asClock"), expr.arg(), definitions, const_ty) } ExprEnum::CastBoolToSyncReset(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastUIntToSyncReset(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastSIntToSyncReset(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastBoolToAsyncReset(expr) => { self.typed_bit_cast(Some("asAsyncReset"), expr.arg(), definitions, const_ty) } ExprEnum::CastUIntToAsyncReset(expr) => { self.typed_bit_cast(Some("asAsyncReset"), expr.arg(), definitions, const_ty) } ExprEnum::CastSIntToAsyncReset(expr) => { self.typed_bit_cast(Some("asAsyncReset"), expr.arg(), definitions, const_ty) } ExprEnum::CastSyncResetToReset(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastAsyncResetToReset(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastClockToBool(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastClockToUInt(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastClockToSInt(expr) => { self.typed_bit_cast(Some("asSInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastSyncResetToBool(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastSyncResetToUInt(expr) => { self.typed_bit_cast(None, expr.arg(), definitions, const_ty) } ExprEnum::CastSyncResetToSInt(expr) => { self.typed_bit_cast(Some("asSInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastAsyncResetToBool(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastAsyncResetToUInt(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastAsyncResetToSInt(expr) => { self.typed_bit_cast(Some("asSInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastResetToBool(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastResetToUInt(expr) => { self.typed_bit_cast(Some("asUInt"), expr.arg(), definitions, const_ty) } ExprEnum::CastResetToSInt(expr) => { self.typed_bit_cast(Some("asSInt"), expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitAndU(expr) => { self.expr_unary("andr", expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitAndS(expr) => { self.expr_unary("andr", expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitOrU(expr) => { self.expr_unary("orr", expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitOrS(expr) => { self.expr_unary("orr", expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitXorU(expr) => { self.expr_unary("xorr", expr.arg(), definitions, const_ty) } ExprEnum::ReduceBitXorS(expr) => { self.expr_unary("xorr", expr.arg(), definitions, const_ty) } ExprEnum::FieldAccess(expr) => { let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; let name = self .type_state .get_bundle_field(Expr::ty(expr.base()), expr.field_name())?; write!(out, ".{name}").unwrap(); Ok(out) } ExprEnum::VariantAccess(variant_access) => Ok(self .module .match_arm_values .get(&variant_access) .expect("VariantAccess must be in its corresponding match arm") .to_string()), ExprEnum::ArrayIndex(expr) => { let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; write!(out, "[{}]", expr.element_index()).unwrap(); Ok(out) } ExprEnum::DynArrayIndex(expr) => { let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?; let index = self.expr(Expr::canonical(expr.element_index()), definitions, const_ty)?; write!(out, "[{index}]").unwrap(); Ok(out) } ExprEnum::ModuleIO(expr) => Ok(self.module.ns.get(expr.name_id()).to_string()), ExprEnum::Instance(expr) => { assert!(!const_ty, "not a constant"); Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::Wire(expr) => { assert!(!const_ty, "not a constant"); Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::Reg(expr) => { assert!(!const_ty, "not a constant"); Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::RegSync(expr) => { assert!(!const_ty, "not a constant"); Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::RegAsync(expr) => { assert!(!const_ty, "not a constant"); Ok(self.module.ns.get(expr.scoped_name().1).to_string()) } ExprEnum::MemPort(expr) => { assert!(!const_ty, "not a constant"); let mem_name = self.module.ns.get(expr.mem_name().1); let port_name = Ident::from(expr.port_name()); Ok(format!("{mem_name}.{port_name}")) } } } fn write_mem_init( &mut self, module_name: Ident, memory_name: Ident, array_type: Array, initial_value: Interned, ) -> Result<()> { assert_eq!( initial_value.len(), array_type.type_properties().bit_width, "literal bits don't match memory array type bit width" ); if initial_value.is_empty() { return Ok(()); } let element_bit_width = array_type.element().bit_width(); let mut contents = String::new(); let hex_or_binary = if element_bit_width % 4 == 0 { HexOrBinary::Hex } else { HexOrBinary::Binary }; for i in 0..array_type.len() { let element_bits = BitSliceWriteWithBase(&initial_value[i * element_bit_width..][..element_bit_width]); match hex_or_binary { HexOrBinary::Hex => writeln!(contents, "{element_bits:x}").unwrap(), HexOrBinary::Binary => writeln!(contents, "{element_bits:b}").unwrap(), } } let filename = self.file_backend.write_mem_init_file( module_name.0.to_string(), memory_name.0.to_string(), contents, )?; self.annotations.push(FirrtlAnnotation { data: AnnotationData::MemoryFileInline { filename, hex_or_binary, }, target: AnnotationTarget { circuit: self.circuit_name, path: Some(AnnotationTargetPath { base_module: module_name, submodules: vec![], target_ref: Some(AnnotationTargetRef { base: memory_name, segments: vec![], }), }), }, }); Ok(()) } fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) { let data = match annotation { Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch, Annotation::SVAttribute(SVAttributeAnnotation { text }) => { AnnotationData::AttributeAnnotation { description: *text } } Annotation::BlackBoxInline(BlackBoxInlineAnnotation { path, text }) => { AnnotationData::BlackBoxInlineAnno { name: *path, text: *text, } } Annotation::BlackBoxPath(BlackBoxPathAnnotation { path }) => { AnnotationData::BlackBoxPathAnno { path: *path } } Annotation::DocString(DocStringAnnotation { text }) => { AnnotationData::DocStringAnnotation { description: *text } } Annotation::CustomFirrtl(CustomFirrtlAnnotation { class, additional_fields, }) => AnnotationData::Other { class: str::to_string(class), additional_fields: (*additional_fields).into(), }, Annotation::XdcLocation(_) | Annotation::XdcIOStandard(_) => return, }; self.annotations.push(FirrtlAnnotation { data, target: AnnotationTarget { circuit: self.circuit_name, path: Some(path), }, }) } fn annotation_target_ref(&mut self, target: Interned) -> Result { match *target { Target::Base(base) => { let mut segments = vec![]; let base = match &*base { TargetBase::ModuleIO(v) => self.module.ns.get(v.name_id()), TargetBase::MemPort(v) => { segments.push(AnnotationTargetRefSegment::Field { name: Ident::from(v.port_name()), }); self.module.ns.get(v.mem_name().1) } TargetBase::Reg(v) => self.module.ns.get(v.name_id()), TargetBase::RegSync(v) => self.module.ns.get(v.name_id()), TargetBase::RegAsync(v) => self.module.ns.get(v.name_id()), TargetBase::Wire(v) => self.module.ns.get(v.name_id()), TargetBase::Instance(v) => self.module.ns.get(v.name_id()), }; Ok(AnnotationTargetRef { base, segments }) } Target::Child(child) => { let mut retval = self.annotation_target_ref(child.parent())?; match *child.path_element() { TargetPathElement::BundleField(TargetPathBundleField { name }) => { retval.segments.push(AnnotationTargetRefSegment::Field { name: self.type_state.get_bundle_field( Bundle::from_canonical(child.parent().canonical_ty()), name, )?, }) } TargetPathElement::ArrayElement(TargetPathArrayElement { index, .. }) => retval .segments .push(AnnotationTargetRefSegment::Index { index }), TargetPathElement::DynArrayElement(_) => unreachable!(), } Ok(retval) } } } fn targeted_annotations( &mut self, base_module: Ident, submodules: Vec, annotations: &[crate::annotations::TargetedAnnotation], ) -> Result<()> { for annotation in annotations { let target_ref = Some(self.annotation_target_ref(annotation.target())?); self.annotation( AnnotationTargetPath { base_module, submodules: submodules.clone(), target_ref, }, annotation.annotation(), ); } Ok(()) } fn write_mem(&mut self, module_name: Ident, memory: Mem) -> Result { let indent = self.indent; let name_id = memory.scoped_name().1; let source_location = memory.source_location(); let array_type = memory.array_type(); let initial_value = memory.initial_value(); let ports = memory.ports(); let read_latency = memory.read_latency(); let write_latency = memory.write_latency(); let read_under_write = memory.read_under_write(); let name = self.module.ns.get(name_id); for annotation in &memory.mem_annotations() { self.annotation( AnnotationTargetPath { base_module: module_name, submodules: vec![], target_ref: Some(AnnotationTargetRef { base: name, segments: vec![], }), }, annotation, ); } self.targeted_annotations(module_name, vec![], &memory.port_annotations())?; if let Some(initial_value) = initial_value { self.write_mem_init(module_name, name, array_type, initial_value)?; } let data_type = self.type_state.ty(array_type.element())?; let mut body = String::new(); writeln!( body, "{indent}mem {name}:{}", FileInfo::new(source_location), ) .unwrap(); let memory_indent = indent.push(); writeln!(body, "{indent}data-type => {data_type}").unwrap(); writeln!(body, "{indent}depth => {}", array_type.len()).unwrap(); writeln!(body, "{indent}read-latency => {read_latency}").unwrap(); writeln!(body, "{indent}write-latency => {write_latency}").unwrap(); let read_under_write = match read_under_write { ReadUnderWrite::Old => "old", ReadUnderWrite::New => "new", ReadUnderWrite::Undefined => "undefined", }; writeln!(body, "{indent}read-under-write => {read_under_write}").unwrap(); let mut ports = Vec::from_iter(ports); ports.sort_by_key(|p| match p.port_kind() { PortKind::ReadOnly => 0, PortKind::WriteOnly => 1, PortKind::ReadWrite => 2, }); for port in ports { let kind = match port.port_kind() { PortKind::ReadOnly => "reader", PortKind::WriteOnly => "writer", PortKind::ReadWrite => "readwriter", }; let name = Ident::from(port.port_name()); writeln!(body, "{indent}{kind} => {name}").unwrap(); } drop(memory_indent); Ok(body) } fn stmt_reg( &mut self, stmt_reg: StmtReg, module_name: Ident, definitions: &RcDefinitions, body: &mut String, ) -> Result<()> { let StmtReg { annotations, reg } = stmt_reg; let indent = self.indent; self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(reg.name_id()); let ty = self.type_state.ty(reg.ty())?; let clk = self.expr(Expr::canonical(reg.clock_domain().clk), definitions, false)?; if let Some(init) = reg.init() { let rst = self.expr(Expr::canonical(reg.clock_domain().rst), definitions, false)?; let init = self.expr(init, definitions, false)?; writeln!( body, "{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}", FileInfo::new(reg.source_location()), ) .unwrap(); } else { writeln!( body, "{indent}reg {name}: {ty}, {clk}{}", FileInfo::new(reg.source_location()), ) .unwrap(); } Ok(()) } fn block( &mut self, module: Interned>, block: Block, _block_indent: &PushIndent<'_>, definitions: Option, ) -> Result { let indent = self.indent; let definitions = definitions.unwrap_or_default(); let mut body = String::new(); let mut out = String::new(); let Block { memories, stmts } = block; let module_name = self.global_ns.get(module.name_id()); for memory in memories { body.push_str(&self.write_mem(module_name, memory)?); } for stmt in stmts { match stmt { Stmt::Connect(StmtConnect { lhs, rhs, source_location, }) => { if Expr::ty(lhs) != Expr::ty(rhs) { writeln!( body, "{indent}; connect different types:\n{indent}; lhs: {:?}\n{indent}; rhs: {:?}", Expr::ty(lhs), Expr::ty(rhs), ) .unwrap(); } let lhs = self.expr(lhs, &definitions, false)?; let rhs = self.expr(rhs, &definitions, false)?; writeln!( body, "{indent}connect {lhs}, {rhs}{}", FileInfo::new(source_location), ) .unwrap(); } Stmt::Formal(StmtFormal { kind, clk, pred, en, text, source_location, }) => { let clk = self.expr(Expr::canonical(clk), &definitions, false)?; let pred = self.expr(Expr::canonical(pred), &definitions, false)?; let en = self.expr(Expr::canonical(en), &definitions, false)?; let kind = match kind { FormalKind::Assert => "assert", FormalKind::Assume => "assume", FormalKind::Cover => "cover", }; let text = EscapedString { value: &text, raw: false, }; writeln!( body, "{indent}{kind}({clk}, {pred}, {en}, {text}){}", FileInfo::new(source_location), ) .unwrap(); } Stmt::If(StmtIf { mut cond, mut source_location, blocks: [mut then_block, mut else_block], }) => { let mut when = "when"; let mut pushed_indent; loop { let cond_str = self.expr(Expr::canonical(cond), &definitions, false)?; writeln!( body, "{indent}{when} {cond_str}:{}", FileInfo::new(source_location), ) .unwrap(); pushed_indent = indent.push(); let then_block_str = self.block(module, then_block, &pushed_indent, None)?; if !then_block_str.is_empty() { body.push_str(&then_block_str); } else { writeln!(body, "{indent}skip").unwrap(); } if let [Stmt::If(else_if)] = &*else_block.stmts { when = "else when"; drop(pushed_indent); StmtIf { cond, source_location, blocks: [then_block, else_block], } = *else_if; } else { break; } } let else_block = self.block(module, else_block, &pushed_indent, None)?; drop(pushed_indent); if !else_block.is_empty() { writeln!(body, "{indent}else:").unwrap(); body.push_str(&else_block); } } Stmt::Match(StmtMatch { expr, source_location, blocks, }) => { writeln!( body, "{indent}match {}:{}", self.expr(Expr::canonical(expr), &definitions, false)?, FileInfo::new(source_location), ) .unwrap(); let enum_ty = Expr::ty(expr); let match_arms_indent = indent.push(); for ((variant_index, variant), match_arm_block) in enum_ty.variants().iter().enumerate().zip(blocks) { write!( body, "{indent}{}", self.type_state.get_enum_variant(enum_ty, variant.name)?, ) .unwrap(); let variant_access = if variant.ty.is_some() { let variant_access = VariantAccess::new_by_index(expr, variant_index); let variant_value = self.module.ns.make_new("_match_arm_value"); write!(body, "({variant_value})").unwrap(); self.module .match_arm_values .insert(variant_access, variant_value); Some(variant_access) } else { None }; body.push_str(":\n"); let match_arm_indent = indent.push(); let block = self.block(module, match_arm_block, &match_arm_indent, None)?; if !block.is_empty() { body.push_str(&block); } else { writeln!(body, "{indent}skip").unwrap(); } drop(match_arm_indent); if let Some(variant_access) = variant_access { self.module.match_arm_values.remove(&variant_access); } } drop(match_arms_indent); } Stmt::Declaration(StmtDeclaration::Wire(StmtWire { annotations, wire })) => { self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(wire.name_id()); let ty = self.type_state.ty(wire.ty())?; writeln!( body, "{indent}wire {name}: {ty}{}", FileInfo::new(wire.source_location()), ) .unwrap(); } Stmt::Declaration(StmtDeclaration::Reg(stmt_reg)) => { self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::RegSync(stmt_reg)) => { self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::RegAsync(stmt_reg)) => { self.stmt_reg(stmt_reg, module_name, &definitions, &mut body)?; } Stmt::Declaration(StmtDeclaration::Instance(StmtInstance { annotations, instance, })) => { self.targeted_annotations(module_name, vec![], &annotations)?; let name = self.module.ns.get(instance.name_id()); let instantiated = instance.instantiated(); self.add_module(instantiated); let module_name = self.global_ns.get(instantiated.name_id()); writeln!( body, "{indent}inst {name} of {module_name}{}", FileInfo::new(instance.source_location()), ) .unwrap(); } } definitions.write_and_clear(indent, &mut out); out.push_str(&body); body.clear(); } Ok(out) } fn module(&mut self, module: Interned>) -> Result { self.module = ModuleState::default(); let indent = self.indent; let module_name = self.global_ns.get(module.name_id()); let mut body = String::new(); let module_indent = indent.push(); for annotation in &module.module_annotations() { self.annotation( AnnotationTargetPath { base_module: module_name, submodules: vec![], target_ref: None, }, annotation, ); } for AnnotatedModuleIO { annotations, module_io, } in module.module_io().iter() { self.targeted_annotations(module_name, vec![], annotations)?; let name = self.module.ns.get(module_io.name_id()); let ty = self.type_state.ty(module_io.ty())?; if module_io.is_input() { writeln!( body, "{indent}input {name}: {ty}{}", FileInfo::new(module_io.source_location()), ) .unwrap(); } else if module_io.is_output() { writeln!( body, "{indent}output {name}: {ty}{}", FileInfo::new(module_io.source_location()), ) .unwrap(); } else { unimplemented!("unimplemented module io kind"); } } let module_kw = match module.body() { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, simulation: _, }) => { let verilog_name = Ident(verilog_name); writeln!(body, "{indent}defname = {verilog_name}").unwrap(); for ExternModuleParameter { name, value } in ¶meters { let escaped_string; let value: &dyn fmt::Display = match value { ExternModuleParameterValue::Integer(value) => value, ExternModuleParameterValue::String(value) => { escaped_string = EscapedString { value, raw: false }; &escaped_string } ExternModuleParameterValue::RawVerilog(value) => { escaped_string = EscapedString { value, raw: true }; &escaped_string } }; let name = Ident(*name); writeln!(body, "{indent}parameter {name} = {value}").unwrap(); } "extmodule" } ModuleBody::Normal(NormalModuleBody { body: top_block }) => { body.push_str(&self.block( module, top_block, &module_indent, Some(self.module.definitions.clone()), )?); "module" } }; if body.is_empty() { writeln!(body, "{indent}skip").unwrap(); } drop(module_indent); let mut out = String::new(); writeln!( out, "{indent}{module_kw} {module_name}:{}", FileInfo::new(module.source_location()), ) .unwrap(); out.push_str(&body); Ok(out) } } pub trait FileBackendTrait { type Error: From; type Path: AsRef + fmt::Debug + ?Sized; type PathBuf: AsRef + fmt::Debug; fn custom_error(&self, error: Box) -> Self::Error; fn path_to_string(&mut self, path: &Self::Path) -> Result; fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result; fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), Self::Error>; } impl FileBackendTrait for Box { type Error = T::Error; type Path = T::Path; type PathBuf = T::PathBuf; fn custom_error(&self, error: Box) -> Self::Error { (**self).custom_error(error) } fn path_to_string(&mut self, path: &Self::Path) -> Result { (**self).path_to_string(path) } fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result { (**self).write_mem_init_file(module_name, memory_name, contents) } fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), Self::Error> { (**self).write_top_fir_file(circuit_name, contents) } } impl FileBackendTrait for &'_ mut T { type Error = T::Error; type Path = T::Path; type PathBuf = T::PathBuf; fn custom_error(&self, error: Box) -> Self::Error { (**self).custom_error(error) } fn path_to_string(&mut self, path: &Self::Path) -> Result { (**self).path_to_string(path) } fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result { (**self).write_mem_init_file(module_name, memory_name, contents) } fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), Self::Error> { (**self).write_top_fir_file(circuit_name, contents) } } #[derive(Debug)] #[non_exhaustive] pub struct FileBackend { pub dir_path: PathBuf, pub circuit_name: Option, pub top_fir_file_stem: Option, } impl FileBackend { pub fn new(dir_path: impl AsRef) -> Self { Self { dir_path: dir_path.as_ref().to_owned(), circuit_name: None, top_fir_file_stem: None, } } } impl FileBackendTrait for FileBackend { type Error = io::Error; type Path = Path; type PathBuf = PathBuf; fn custom_error(&self, error: Box) -> Self::Error { io::Error::new(io::ErrorKind::Other, error) } fn path_to_string(&mut self, path: &Self::Path) -> Result { path.to_str() .map(String::from) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "path is not UTF-8")) } fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result { let mut path = self.dir_path.join(module_name); fs::create_dir_all(&path)?; path.push(memory_name); path.set_extension("mem"); fs::write(&path, contents)?; Ok(path) } fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), Self::Error> { let top_fir_file_stem = self .top_fir_file_stem .get_or_insert_with(|| circuit_name.clone()); self.circuit_name = Some(circuit_name); let mut path = self.dir_path.join(top_fir_file_stem); if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) { fs::create_dir_all(parent)?; } path.set_extension("fir"); fs::write(path, contents) } } #[doc(hidden)] #[derive(PartialEq, Eq, Clone, Copy)] pub struct TestBackendPrivate { pub module_var_name: &'static str, pub included_fields: &'static [&'static str], } impl Default for TestBackendPrivate { fn default() -> Self { Self { module_var_name: "m", included_fields: &[], } } } #[derive(Default, PartialEq, Eq)] pub struct TestBackend { pub files: BTreeMap, pub error_after: Option, pub options: ExportOptions, #[doc(hidden)] /// `#[non_exhaustive]` except allowing struct update syntax pub __private: TestBackendPrivate, } impl fmt::Debug for TestBackend { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { files, error_after, options, __private: TestBackendPrivate { module_var_name, included_fields, }, } = self; writeln!( f, " #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161" )?; writeln!(f, " assert_export_firrtl! {{")?; writeln!(f, " {module_var_name} =>")?; if *error_after != Option::default() || included_fields.contains(&"error_after") { writeln!(f, " error_after: {error_after:?},")?; } if *options != ExportOptions::default() || included_fields.contains(&"options") { struct DebugWithForceIncludeFields<'a> { options: ExportOptions, included_fields: &'a [&'a str], } impl fmt::Debug for DebugWithForceIncludeFields<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.options.debug_fmt(f, |field| { self.included_fields.iter().any(|included_field| { if let Some(("options", suffix)) = included_field.split_once(".") { suffix == field } else { false } }) }) } } let options_str = format!( "{:#?}", DebugWithForceIncludeFields { options: *options, included_fields } ); let mut sep = " options: "; for line in options_str.lines() { write!(f, "{sep}{line}")?; sep = "\n "; } writeln!(f, ",")?; } for (file, content) in files { writeln!(f, " {file:?}: {:?},", DebugAsRawString(content))?; } write!(f, " }};") } } #[derive(Debug, Clone)] pub struct TestBackendError(String); impl fmt::Display for TestBackendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl Error for TestBackendError {} impl From for TestBackendError { fn from(value: SimplifyEnumsError) -> Self { TestBackendError(value.to_string()) } } impl TestBackend { #[track_caller] pub fn step_error_after(&mut self, args: &dyn fmt::Debug) -> Result<(), TestBackendError> { let Some(error_after) = &mut self.error_after else { return Ok(()); }; match (*error_after).cmp(&0) { Ordering::Less => panic!("failed to return error previously"), Ordering::Equal => Err(TestBackendError(format!( "error: {}: error_after == Some(0): args = {args:?}", std::panic::Location::caller() ))), Ordering::Greater => { *error_after -= 1; Ok(()) } } } } impl FileBackendTrait for TestBackend { type Error = TestBackendError; type Path = str; type PathBuf = String; fn custom_error(&self, error: Box) -> Self::Error { TestBackendError(error.to_string()) } fn path_to_string(&mut self, path: &Self::Path) -> Result { self.step_error_after(&path)?; Ok(path.to_owned()) } fn write_mem_init_file( &mut self, module_name: String, memory_name: String, contents: String, ) -> Result { self.step_error_after(&(&module_name, &memory_name))?; let path = format!("/test/{module_name}/{memory_name}.mem"); self.files.insert(path.clone(), contents); Ok(path) } fn write_top_fir_file( &mut self, circuit_name: String, contents: String, ) -> Result<(), Self::Error> { self.step_error_after(&circuit_name)?; let path = format!("/test/{circuit_name}.fir"); self.files.insert(path, contents); Ok(()) } } fn export_impl( file_backend: &mut dyn WrappedFileBackendTrait, top_module: Interned>, options: ExportOptions, ) -> Result<(), WrappedError> { let top_module = options .prepare_top_module(top_module) .map_err(|e| file_backend.simplify_enums_error(e))?; let indent_depth = Cell::new(0); let mut global_ns = Namespace::default(); let circuit_name = global_ns.get(top_module.name_id()); Exporter { file_backend, indent: Indent { indent_depth: &indent_depth, indent: " ", }, seen_modules: HashSet::default(), unwritten_modules: VecDeque::new(), global_ns, module: ModuleState::default(), type_state: TypeState::default(), circuit_name, annotations: vec![], } .run(top_module) } #[derive(Clone)] struct OptionSimplifyEnumsKindValueParser; impl OptionSimplifyEnumsKindValueParser { const NONE_NAME: &'static str = "off"; } impl clap::builder::TypedValueParser for OptionSimplifyEnumsKindValueParser { type Value = Option; fn parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result { if value == Self::NONE_NAME { Ok(None) } else { Ok(Some( value_parser!(SimplifyEnumsKind).parse_ref(cmd, arg, value)?, )) } } fn possible_values( &self, ) -> Option + '_>> { Some(Box::new( [Self::NONE_NAME.into()] .into_iter() .chain(value_parser!(SimplifyEnumsKind).possible_values()?) .collect::>() .into_iter(), )) } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ExportOptionsPrivate(()); impl ExportOptionsPrivate { fn private_new() -> Self { Self(()) } } #[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct ExportOptions { #[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)] #[serde(default = "ExportOptions::default_simplify_memories")] pub simplify_memories: bool, #[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")] #[serde(default = "ExportOptions::default_simplify_enums")] pub simplify_enums: std::option::Option, // use std::option::Option instead of Option to avoid clap mis-parsing #[doc(hidden)] #[clap(skip = ExportOptionsPrivate(()))] #[serde(skip, default = "ExportOptionsPrivate::private_new")] /// `#[non_exhaustive]` except allowing struct update syntax pub __private: ExportOptionsPrivate, } impl fmt::Debug for ExportOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.debug_fmt(f, |_| false) } } impl ToArgs for ExportOptions { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { simplify_memories, simplify_enums, __private: ExportOptionsPrivate(()), } = *self; if !simplify_memories { args.write_str_arg("--no-simplify-memories"); } let simplify_enums = simplify_enums.map(|v| { clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") }); let simplify_enums = match &simplify_enums { None => OptionSimplifyEnumsKindValueParser::NONE_NAME, Some(v) => v.get_name(), }; args.write_arg(format_args!("--simplify-enums={simplify_enums}")); } } impl ExportOptions { fn default_simplify_memories() -> bool { true } fn default_simplify_enums() -> Option { Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts) } fn debug_fmt( &self, f: &mut fmt::Formatter<'_>, force_include_field: impl Fn(&str) -> bool, ) -> fmt::Result { let Self { simplify_memories, simplify_enums, __private: _, } = *self; f.write_str("ExportOptions {")?; let mut sep = if f.alternate() { "\n " } else { " " }; let comma_sep = if f.alternate() { ",\n " } else { ", " }; let default = ExportOptions::default(); if simplify_memories != default.simplify_memories || force_include_field("simplify_memories") { write!(f, "{sep}simplify_memories: {:?}", simplify_memories)?; sep = comma_sep; } if simplify_enums != default.simplify_enums || force_include_field("simplify_enums") { write!(f, "{sep}simplify_enums: ")?; macro_rules! debug_cases { ($($ident:ident $(($($args:tt)*))?,)*) => { match simplify_enums { // use more complex stringify to avoid the compiler inserting spaces $($ident $(($($args)*))? => { f.write_str(concat!( stringify!($ident), $("(", $(stringify!($args),)* ")")? ))?; })* } }; } debug_cases! { Some(SimplifyEnumsKind::SimplifyToEnumsWithNoBody), Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), Some(SimplifyEnumsKind::ReplaceWithUInt), None, } sep = comma_sep; } write!( f, "{sep}..ExportOptions::default(){}", if f.alternate() { "\n}" } else { " }" } ) } fn prepare_top_module_helper( self, mut top_module: Interned>, ) -> Result>, SimplifyEnumsError> { let Self { simplify_memories: do_simplify_memories, simplify_enums: do_simplify_enums, __private: _, } = self; if let Some(kind) = do_simplify_enums { top_module = simplify_enums(top_module, kind)?; } if do_simplify_memories { top_module = simplify_memories(top_module); } Ok(top_module) } pub fn prepare_top_module( self, top_module: impl AsRef>, ) -> Result>, SimplifyEnumsError> { self.prepare_top_module_helper(top_module.as_ref().canonical().intern()) } } impl Default for ExportOptions { fn default() -> Self { Self { simplify_memories: Self::default_simplify_memories(), simplify_enums: Self::default_simplify_enums(), __private: ExportOptionsPrivate(()), } } } pub fn get_circuit_name(top_module_name_id: NameId) -> Interned { let mut global_ns = Namespace::default(); let circuit_name = global_ns.get(top_module_name_id); circuit_name.0 } pub fn export( file_backend: B, top_module: &Module, options: ExportOptions, ) -> Result { let top_module = Intern::intern_sized(top_module.canonical()); WrappedFileBackend::with(file_backend, |file_backend| { export_impl(file_backend, top_module, options) }) } #[derive(Debug)] #[non_exhaustive] pub enum ScalarizedModuleABIError { SimOnlyValuesAreNotPermitted, SimplifyEnumsError(SimplifyEnumsError), } impl fmt::Display for ScalarizedModuleABIError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted => { FirrtlError::SimOnlyValuesAreNotPermitted.fmt(f) } ScalarizedModuleABIError::SimplifyEnumsError(e) => e.fmt(f), } } } impl std::error::Error for ScalarizedModuleABIError {} impl From for ScalarizedModuleABIError { fn from(value: SimplifyEnumsError) -> Self { Self::SimplifyEnumsError(value) } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum ScalarizedModuleABIPortItem { Group(ScalarizedModuleABIPortGroup), Port(ScalarizedModuleABIPort), } impl ScalarizedModuleABIPortItem { pub fn module_io(self) -> ModuleIO { *self .target() .base() .module_io() .expect("known to be ModuleIO") } pub fn target(self) -> Interned { match self { Self::Group(v) => v.target(), Self::Port(v) => v.target(), } } fn for_each_port_and_annotations_helper< F: for<'a> FnMut( &'a ScalarizedModuleABIPort, ScalarizedModuleABIAnnotations<'a>, ) -> ControlFlow, B, >( &self, parent: Option<&ScalarizedModuleABIAnnotations<'_>>, f: &mut F, ) -> ControlFlow { match self { Self::Group(v) => v.for_each_port_and_annotations_helper(parent, f), Self::Port(port) => f( port, ScalarizedModuleABIAnnotations::new(parent, port.annotations.iter()), ), } } pub fn for_each_port_and_annotations< F: for<'a> FnMut( &'a ScalarizedModuleABIPort, ScalarizedModuleABIAnnotations<'a>, ) -> ControlFlow, B, >( self, mut f: F, ) -> ControlFlow { self.for_each_port_and_annotations_helper(None, &mut f) } } impl fmt::Debug for ScalarizedModuleABIPortItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Group(v) => v.fmt(f), Self::Port(v) => v.fmt(f), } } } #[derive(Debug, Clone)] pub struct ScalarizedModuleABIAnnotations<'a> { parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>, parent_len: usize, annotations: std::slice::Iter<'a, TargetedAnnotation>, } impl<'a> ScalarizedModuleABIAnnotations<'a> { fn new( parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>, annotations: std::slice::Iter<'a, TargetedAnnotation>, ) -> Self { Self { parent, parent_len: parent.map_or(0, |parent| parent.len()), annotations, } } } impl<'a> Default for ScalarizedModuleABIAnnotations<'a> { fn default() -> Self { Self { parent: None, parent_len: 0, annotations: Default::default(), } } } impl<'a> Iterator for ScalarizedModuleABIAnnotations<'a> { type Item = &'a TargetedAnnotation; fn next(&mut self) -> Option { loop { if let retval @ Some(_) = self.annotations.next() { break retval; } *self = self.parent?.clone(); } } fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } fn fold(mut self, mut init: B, mut f: F) -> B where F: FnMut(B, Self::Item) -> B, { loop { let Self { parent, parent_len: _, annotations, } = self; init = annotations.fold(init, &mut f); let Some(next) = parent else { break; }; self = next.clone(); } init } } impl std::iter::FusedIterator for ScalarizedModuleABIAnnotations<'_> {} impl ExactSizeIterator for ScalarizedModuleABIAnnotations<'_> { fn len(&self) -> usize { self.parent_len + self.annotations.len() } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScalarizedModuleABIPortGroup { target: Interned, common_annotations: Interned<[TargetedAnnotation]>, children: Interned<[ScalarizedModuleABIPortItem]>, } impl ScalarizedModuleABIPortGroup { pub fn module_io(self) -> ModuleIO { *self .target .base() .module_io() .expect("known to be ModuleIO") } pub fn target(self) -> Interned { self.target } pub fn common_annotations(self) -> Interned<[TargetedAnnotation]> { self.common_annotations } pub fn children(self) -> Interned<[ScalarizedModuleABIPortItem]> { self.children } fn for_each_port_and_annotations_helper< F: for<'a> FnMut( &'a ScalarizedModuleABIPort, ScalarizedModuleABIAnnotations<'a>, ) -> ControlFlow, B, >( &self, parent: Option<&ScalarizedModuleABIAnnotations<'_>>, f: &mut F, ) -> ControlFlow { let parent = ScalarizedModuleABIAnnotations::new(parent, self.common_annotations.iter()); for item in &self.children { item.for_each_port_and_annotations_helper(Some(&parent), f)?; } ControlFlow::Continue(()) } pub fn for_each_port_and_annotations< F: for<'a> FnMut( &'a ScalarizedModuleABIPort, ScalarizedModuleABIAnnotations<'a>, ) -> ControlFlow, B, >( self, mut f: F, ) -> ControlFlow { self.for_each_port_and_annotations_helper(None, &mut f) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScalarizedModuleABIPort { target: Interned, annotations: Interned<[TargetedAnnotation]>, scalarized_name: Interned, } impl ScalarizedModuleABIPort { pub fn module_io(self) -> ModuleIO { *self .target .base() .module_io() .expect("known to be ModuleIO") } pub fn target(self) -> Interned { self.target } pub fn annotations(self) -> Interned<[TargetedAnnotation]> { self.annotations } pub fn scalarized_name(self) -> Interned { self.scalarized_name } } enum ScalarizeTreeNodeBody { Leaf { scalarized_name: Interned, }, Bundle { ty: Bundle, fields: Vec, }, Array { elements: Vec, }, } struct ScalarizeTreeNode { target: Interned, annotations: Vec, body: ScalarizeTreeNodeBody, } impl ScalarizeTreeNode { #[track_caller] fn find_target(&mut self, annotation_target: Interned) -> &mut Self { match *annotation_target { Target::Base(_) => { assert_eq!( annotation_target, self.target, "annotation not on correct ModuleIO", ); self } Target::Child(target_child) => { let parent = self.find_target(target_child.parent()); match *target_child.path_element() { TargetPathElement::BundleField(TargetPathBundleField { name }) => { match parent.body { ScalarizeTreeNodeBody::Leaf { .. } => parent, ScalarizeTreeNodeBody::Bundle { ty, ref mut fields } => { &mut fields[ty.name_indexes()[&name]] } ScalarizeTreeNodeBody::Array { .. } => { unreachable!("types are known to match") } } } TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => { match parent.body { ScalarizeTreeNodeBody::Leaf { .. } => parent, ScalarizeTreeNodeBody::Bundle { .. } => { unreachable!("types are known to match") } ScalarizeTreeNodeBody::Array { ref mut elements } => { &mut elements[index] } } } TargetPathElement::DynArrayElement(_) => { unreachable!("annotations are only on static targets"); } } } } } fn into_scalarized_item(self) -> ScalarizedModuleABIPortItem { let Self { target, annotations, body, } = self; match body { ScalarizeTreeNodeBody::Leaf { scalarized_name } => { ScalarizedModuleABIPortItem::Port(ScalarizedModuleABIPort { target, annotations: Intern::intern_owned(annotations), scalarized_name, }) } ScalarizeTreeNodeBody::Bundle { fields: items, .. } | ScalarizeTreeNodeBody::Array { elements: items } => { ScalarizedModuleABIPortItem::Group(ScalarizedModuleABIPortGroup { target, common_annotations: Intern::intern_owned(annotations), children: Interned::from_iter( items.into_iter().map(Self::into_scalarized_item), ), }) } } } } #[derive(Default)] struct ScalarizeTreeBuilder { scalarized_ns: Namespace, type_state: TypeState, name: String, } impl ScalarizeTreeBuilder { #[track_caller] fn build_bundle( &mut self, target: Interned, ty: Bundle, ) -> Result { let mut fields = Vec::with_capacity(ty.fields().len()); let original_len = self.name.len(); for BundleField { name, .. } in ty.fields() { let firrtl_name = self .type_state .get_bundle_field(ty, name) .map_err(|e| match e { FirrtlError::SimOnlyValuesAreNotPermitted => { ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted } })? .0; write!(self.name, "_{firrtl_name}").expect("writing to String is infallible"); fields.push( self.build( target .join(TargetPathElement::intern_sized( TargetPathBundleField { name }.into(), )) .intern_sized(), )?, ); self.name.truncate(original_len); } Ok(ScalarizeTreeNode { target, annotations: Vec::new(), body: ScalarizeTreeNodeBody::Bundle { ty, fields }, }) } #[track_caller] fn build( &mut self, target: Interned, ) -> Result { Ok(match target.canonical_ty() { CanonicalType::UInt(_) | CanonicalType::SInt(_) | CanonicalType::Bool(_) | CanonicalType::Enum(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) | CanonicalType::Clock(_) => { let scalarized_name = self.scalarized_ns.get(str::intern(&self.name)).0; ScalarizeTreeNode { target, annotations: Vec::new(), body: ScalarizeTreeNodeBody::Leaf { scalarized_name }, } } CanonicalType::Array(ty) => { let mut elements = Vec::with_capacity(ty.len()); let original_len = self.name.len(); for index in 0..ty.len() { write!(self.name, "_{index}").expect("writing to String is infallible"); elements.push( self.build( target .join(TargetPathElement::intern_sized( TargetPathArrayElement { index }.into(), )) .intern_sized(), )?, ); self.name.truncate(original_len); } ScalarizeTreeNode { target, annotations: Vec::new(), body: ScalarizeTreeNodeBody::Array { elements }, } } CanonicalType::Bundle(ty) => self.build_bundle(target, ty)?, CanonicalType::PhantomConst(_) => { self.build_bundle(target, Bundle::new(Interned::default()))? } CanonicalType::DynSimOnly(_) => { return Err(ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted); } }) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScalarizedModuleABI { module_io: Interned<[AnnotatedModuleIO]>, items: Interned<[ScalarizedModuleABIPortItem]>, } impl ScalarizedModuleABI { #[track_caller] fn from_io(module_io: Interned<[AnnotatedModuleIO]>) -> Result { let mut firrtl_ns = Namespace::default(); let mut tree_builder = ScalarizeTreeBuilder::default(); let mut items = Vec::new(); for module_io in module_io { let firrtl_name = firrtl_ns.get(module_io.module_io.name_id()); tree_builder.name.clear(); tree_builder.name.push_str(&firrtl_name.0); let mut tree = tree_builder.build(Target::from(module_io.module_io).intern_sized())?; for annotation in module_io.annotations { tree.find_target(annotation.target()) .annotations .push(annotation); } items.push(tree.into_scalarized_item()); } Ok(Self { module_io, items: Intern::intern_owned(items), }) } #[track_caller] pub fn new( module: impl AsRef>, options: ExportOptions, ) -> Result { Self::from_io(options.prepare_top_module(module)?.module_io()) } pub fn module_io(&self) -> Interned<[AnnotatedModuleIO]> { self.module_io } pub fn items(&self) -> Interned<[ScalarizedModuleABIPortItem]> { self.items } pub fn for_each_port_and_annotations< F: for<'a> FnMut( &'a ScalarizedModuleABIPort, ScalarizedModuleABIAnnotations<'a>, ) -> ControlFlow, B, >( self, mut f: F, ) -> ControlFlow { for item in &self.items { item.for_each_port_and_annotations_helper(None, &mut f)?; } ControlFlow::Continue(()) } } #[doc(hidden)] #[track_caller] pub fn assert_export_firrtl_impl(top_module: &Module, expected: TestBackend) { let result = export( TestBackend { files: BTreeMap::default(), error_after: expected.error_after, options: expected.options, __private: expected.__private, }, top_module, expected.options, ) .unwrap(); if result != expected { panic!( "assert_export_firrtl failed:\nyou can update the expected output by using:\n-------START-------\n{result:?}\n-------END-------" ); } } #[doc(hidden)] pub fn make_test_expected_files(v: &[(&str, &str)]) -> BTreeMap { v.iter().map(|&(k, v)| (k.into(), v.into())).collect() } #[macro_export] macro_rules! assert_export_firrtl { { $m:ident => $($field:ident: $value:expr,)* @parsed_fields($($field_strings:expr,)*) $($file_name:literal: $file_contents:literal,)* } => { $crate::firrtl::assert_export_firrtl_impl( &$m, $crate::firrtl::TestBackend { $($field: $value,)* files: $crate::firrtl::make_test_expected_files(&[ $(($file_name, $file_contents),)* ]), __private: $crate::firrtl::TestBackendPrivate { module_var_name: stringify!($m), included_fields: &[$($field_strings,)*], }, ..<$crate::firrtl::TestBackend as $crate::__std::default::Default>::default() }, ); }; { $m:ident => $($parsed_fields:ident: $parsed_field_values:expr,)* @parsed_fields($($field_strings:expr,)*) options: ExportOptions { $($export_option_fields:ident: $parsed_export_option_field_values:expr,)* ..$export_option_default:expr }, $($rest:tt)* } => { $crate::assert_export_firrtl!( $m => $($parsed_fields: $parsed_field_values,)* options: ExportOptions { $($export_option_fields: $parsed_export_option_field_values,)* ..$export_option_default }, @parsed_fields($($field_strings,)* "options", $(concat!("options.", stringify!($export_option_fields)),)*) $($rest)* ); }; { $m:ident => $($parsed_fields:ident: $parsed_field_values:expr,)* @parsed_fields($($field_strings:expr,)*) $field:ident: $field_value:expr, $($rest:tt)* } => { $crate::assert_export_firrtl!( $m => $($parsed_fields: $parsed_field_values,)* $field: $field_value, @parsed_fields($($field_strings,)* stringify!($field),) $($rest)* ); }; { $m:ident => $($rest:tt)* } => { $crate::assert_export_firrtl!( $m => @parsed_fields() $($rest)* ); }; }