fayalite/crates/fayalite/src/firrtl.rs
2025-10-15 04:29:00 -07:00

3489 lines
122 KiB
Rust

// 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<FirrtlError> for FirrtlOrWrappedError {
fn from(value: FirrtlError) -> Self {
Self::FirrtlError(value)
}
}
impl From<WrappedError> for FirrtlOrWrappedError {
fn from(value: WrappedError) -> Self {
Self::WrappedError(value)
}
}
type Result<T, E = FirrtlOrWrappedError> = std::result::Result<T, E>;
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<Interned<str>>,
last_inserted: HashMap<(Interned<str>, usize), usize>,
}
impl NameMaker {
fn make(&mut self, name: impl Into<String>) -> 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<NameOptId, Ident>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct Ident(Interned<str>);
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<PortName> for Ident {
fn from(value: PortName) -> Self {
Self(Intern::intern_owned(value.to_string()))
}
}
impl Namespace {
fn get(&mut self, name: impl Into<NameOptId>) -> 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<SourceLocation>);
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<RefCell<String>>,
}
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<K, V = ()> {
definitions: RcDefinitions,
map: RefCell<HashMap<K, (Ident, V)>>,
}
impl<K, V> DefinitionsMap<K, V> {
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<Namespace>,
body: String,
}
struct TypeState {
definitions: RcDefinitions,
bundle_defs: DefinitionsMap<Bundle, Rc<RefCell<Namespace>>>,
enum_defs: DefinitionsMap<Enum, Rc<EnumDef>>,
next_type_name: Cell<usize>,
}
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<str>) -> Result<Ident, FirrtlError> {
Ok(self.bundle_ns(ty)?.borrow_mut().get(name))
}
fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc<RefCell<Namespace>>), 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<Ident, FirrtlError> {
Ok(self.bundle_def(ty)?.0)
}
fn bundle_ns(&self, ty: Bundle) -> Result<Rc<RefCell<Namespace>>, FirrtlError> {
Ok(self.bundle_def(ty)?.1)
}
fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc<EnumDef>), 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<Ident, FirrtlError> {
Ok(self.enum_def(ty)?.0)
}
fn get_enum_variant(&mut self, ty: Enum, name: Interned<str>) -> Result<Ident, FirrtlError> {
Ok(self.enum_def(ty)?.1.variants.borrow_mut().get(name))
}
fn ty<T: Type>(&self, ty: T) -> Result<String, FirrtlError> {
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<VariantAccess<CanonicalType>, 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<String, WrappedError>;
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<B: FileBackendTrait> {
file_backend: B,
error: Result<(), B::Error>,
}
impl<B: FileBackendTrait> WrappedFileBackend<B> {
fn with(
file_backend: B,
f: impl FnOnce(&mut dyn WrappedFileBackendTrait) -> Result<(), WrappedError>,
) -> Result<B, B::Error> {
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<B: FileBackendTrait> WrappedFileBackendTrait for WrappedFileBackend<B> {
fn write_mem_init_file(
&mut self,
module_name: String,
memory_name: String,
contents: String,
) -> Result<String, WrappedError> {
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<AnnotationTargetRefSegment>,
}
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<AnnotationTargetSubmodule>,
target_ref: Option<AnnotationTargetRef>,
}
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<AnnotationTargetPath>,
}
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<str> },
#[serde(rename = "firrtl.transforms.BlackBoxInlineAnno")]
BlackBoxInlineAnno {
name: Interned<str>,
text: Interned<str>,
},
#[serde(rename = "firrtl.transforms.BlackBoxPathAnno")]
BlackBoxPathAnno { path: Interned<str> },
#[serde(rename = "firrtl.DocStringAnnotation")]
DocStringAnnotation { description: Interned<str> },
#[allow(dead_code)]
#[serde(untagged)]
Other {
class: String,
#[serde(flatten)]
additional_fields: serde_json::Map<String, serde_json::Value>,
},
}
#[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<Interned<Module<Bundle>>>,
unwritten_modules: VecDeque<Interned<Module<Bundle>>>,
global_ns: Namespace,
module: ModuleState,
type_state: TypeState,
circuit_name: Ident,
annotations: Vec<FirrtlAnnotation>,
}
struct PushIndent<'a> {
indent_depth: &'a Cell<usize>,
}
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<usize>,
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<Module<Bundle>>) {
if self.seen_modules.insert(module) {
self.unwritten_modules.push_back(module);
}
}
fn run(&mut self, top_module: Interned<Module<Bundle>>) -> 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<Module<Bundle>>) -> Result<String> {
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<str>,
variant_expr: Option<String>,
) -> Result<String> {
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<FromTy: IntType, ToTy: IntType>(
&mut self,
value: Expr<FromTy>,
to_ty: ToTy,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<FromTy: Type>(
&mut self,
firrtl_cast_fn: Option<&str>,
value: Expr<FromTy>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<T: IntType>(
&mut self,
base: Expr<T>,
range: Range<usize>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<CanonicalType, DynSize>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<Bundle>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<String> {
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<Enum>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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<T: Type>(
&mut self,
func: &str,
arg: Expr<T>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
Ok(format!(
"{func}({arg})",
arg = self.expr(Expr::canonical(arg), definitions, const_ty)?,
))
}
fn expr_binary<Lhs: Type, Rhs: Type>(
&mut self,
func: &str,
lhs: Expr<Lhs>,
rhs: Expr<Rhs>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<CanonicalType>,
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
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<BitSlice>,
) -> 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<Target>) -> Result<AnnotationTargetRef> {
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<AnnotationTargetSubmodule>,
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<String> {
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<R: ResetType>(
&mut self,
stmt_reg: StmtReg<R>,
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<Module<Bundle>>,
block: Block,
_block_indent: &PushIndent<'_>,
definitions: Option<RcDefinitions>,
) -> Result<String> {
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<Module<Bundle>>) -> Result<String> {
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 &parameters {
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<SimplifyEnumsError>;
type Path: AsRef<Self::Path> + fmt::Debug + ?Sized;
type PathBuf: AsRef<Self::Path> + fmt::Debug;
fn custom_error(&self, error: Box<dyn std::error::Error + Send + Sync>) -> Self::Error;
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error>;
fn write_mem_init_file(
&mut self,
module_name: String,
memory_name: String,
contents: String,
) -> Result<Self::PathBuf, Self::Error>;
fn write_top_fir_file(
&mut self,
circuit_name: String,
contents: String,
) -> Result<(), Self::Error>;
}
impl<T: ?Sized + FileBackendTrait> FileBackendTrait for Box<T> {
type Error = T::Error;
type Path = T::Path;
type PathBuf = T::PathBuf;
fn custom_error(&self, error: Box<dyn std::error::Error + Send + Sync>) -> Self::Error {
(**self).custom_error(error)
}
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error> {
(**self).path_to_string(path)
}
fn write_mem_init_file(
&mut self,
module_name: String,
memory_name: String,
contents: String,
) -> Result<Self::PathBuf, Self::Error> {
(**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<T: ?Sized + FileBackendTrait> FileBackendTrait for &'_ mut T {
type Error = T::Error;
type Path = T::Path;
type PathBuf = T::PathBuf;
fn custom_error(&self, error: Box<dyn std::error::Error + Send + Sync>) -> Self::Error {
(**self).custom_error(error)
}
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error> {
(**self).path_to_string(path)
}
fn write_mem_init_file(
&mut self,
module_name: String,
memory_name: String,
contents: String,
) -> Result<Self::PathBuf, Self::Error> {
(**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<String>,
pub top_fir_file_stem: Option<String>,
}
impl FileBackend {
pub fn new(dir_path: impl AsRef<Path>) -> 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<dyn std::error::Error + Send + Sync>) -> Self::Error {
io::Error::new(io::ErrorKind::Other, error)
}
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error> {
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<Self::PathBuf, Self::Error> {
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<String, String>,
pub error_after: Option<i64>,
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<SimplifyEnumsError> 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<dyn std::error::Error + Send + Sync>) -> Self::Error {
TestBackendError(error.to_string())
}
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error> {
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::PathBuf, Self::Error> {
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<Module<Bundle>>,
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<SimplifyEnumsKind>;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
if value == Self::NONE_NAME {
Ok(None)
} else {
Ok(Some(
value_parser!(SimplifyEnumsKind).parse_ref(cmd, arg, value)?,
))
}
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
Some(Box::new(
[Self::NONE_NAME.into()]
.into_iter()
.chain(value_parser!(SimplifyEnumsKind).possible_values()?)
.collect::<Vec<_>>()
.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<SimplifyEnumsKind>, // 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<SimplifyEnumsKind> {
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<Module<Bundle>>,
) -> Result<Interned<Module<Bundle>>, 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<T: BundleType>(
self,
top_module: impl AsRef<Module<T>>,
) -> Result<Interned<Module<Bundle>>, 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<str> {
let mut global_ns = Namespace::default();
let circuit_name = global_ns.get(top_module_name_id);
circuit_name.0
}
pub fn export<T: BundleType, B: FileBackendTrait>(
file_backend: B,
top_module: &Module<T>,
options: ExportOptions,
) -> Result<B, B::Error> {
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<SimplifyEnumsError> 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<CanonicalType> {
*self
.target()
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
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>,
B,
>(
&self,
parent: Option<&ScalarizedModuleABIAnnotations<'_>>,
f: &mut F,
) -> ControlFlow<B> {
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>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
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<Self::Item> {
loop {
if let retval @ Some(_) = self.annotations.next() {
break retval;
}
*self = self.parent?.clone();
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
fn fold<B, F>(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<Target>,
common_annotations: Interned<[TargetedAnnotation]>,
children: Interned<[ScalarizedModuleABIPortItem]>,
}
impl ScalarizedModuleABIPortGroup {
pub fn module_io(self) -> ModuleIO<CanonicalType> {
*self
.target
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
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>,
B,
>(
&self,
parent: Option<&ScalarizedModuleABIAnnotations<'_>>,
f: &mut F,
) -> ControlFlow<B> {
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>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
self.for_each_port_and_annotations_helper(None, &mut f)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScalarizedModuleABIPort {
target: Interned<Target>,
annotations: Interned<[TargetedAnnotation]>,
scalarized_name: Interned<str>,
}
impl ScalarizedModuleABIPort {
pub fn module_io(self) -> ModuleIO<CanonicalType> {
*self
.target
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
self.target
}
pub fn annotations(self) -> Interned<[TargetedAnnotation]> {
self.annotations
}
pub fn scalarized_name(self) -> Interned<str> {
self.scalarized_name
}
}
enum ScalarizeTreeNodeBody {
Leaf {
scalarized_name: Interned<str>,
},
Bundle {
ty: Bundle,
fields: Vec<ScalarizeTreeNode>,
},
Array {
elements: Vec<ScalarizeTreeNode>,
},
}
struct ScalarizeTreeNode {
target: Interned<Target>,
annotations: Vec<TargetedAnnotation>,
body: ScalarizeTreeNodeBody,
}
impl ScalarizeTreeNode {
#[track_caller]
fn find_target(&mut self, annotation_target: Interned<Target>) -> &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<Target>,
ty: Bundle,
) -> Result<ScalarizeTreeNode, ScalarizedModuleABIError> {
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<Target>,
) -> Result<ScalarizeTreeNode, ScalarizedModuleABIError> {
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<Self, ScalarizedModuleABIError> {
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<T: BundleType>(
module: impl AsRef<Module<T>>,
options: ExportOptions,
) -> Result<Self, ScalarizedModuleABIError> {
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>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
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<T: BundleType>(top_module: &Module<T>, 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<String, String> {
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)*
);
};
}