fayalite/crates/fayalite/src/firrtl.rs

2829 lines
100 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,
},
array::Array,
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},
expr::{
ops::{self, VariantAccess},
target::{
Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement,
},
Expr, ExprEnum,
},
formal::FormalKind,
int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue},
intern::{Intern, Interned},
memory::{Mem, PortKind, PortName, ReadUnderWrite},
module::{
transform::{
simplify_enums::{simplify_enums, SimplifyEnumsError, SimplifyEnumsKind},
simplify_memories::simplify_memories,
},
AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Module, ModuleBody, NameId, NormalModuleBody, Stmt,
StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg,
StmtWire,
},
reset::{AsyncReset, Reset, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::{
const_str_array_is_strictly_ascending, BitSliceWriteWithBase, DebugAsRawString,
GenericConstBool,
},
};
use bitvec::slice::BitSlice;
use clap::value_parser;
use hashbrown::{HashMap, HashSet};
use num_traits::Signed;
use serde::Serialize;
use std::{
cell::{Cell, RefCell},
cmp::Ordering,
collections::{BTreeMap, VecDeque},
error::Error,
fmt::{self, Write},
fs,
hash::Hash,
io,
ops::Range,
path::{Path, PathBuf},
rc::Rc,
};
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: NameId) -> Ident {
let mut num: usize = name.1;
let name = String::from(&*name.0);
// 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<NameId, 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: NameId) -> Ident {
#[cold]
fn make(name_maker: &mut NameMaker, name: NameId) -> Ident {
name_maker.make(name)
}
*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(NameId(prefix.intern(), 0))
}
}
#[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>(
&'a self,
key: K,
make: impl FnOnce(&K, &'a RcDefinitions) -> (Ident, V),
) -> (Ident, V)
where
K: Hash + Eq,
V: Clone,
{
if let Some(retval) = self.map.borrow().get(&key) {
return retval.clone();
}
let value = make(&key, &self.definitions);
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>) -> Ident {
self.bundle_ns(ty).borrow_mut().get(NameId(name, 0))
}
fn bundle_def(&self, ty: Bundle) -> (Ident, Rc<RefCell<Namespace>>) {
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(NameId(name, 0))).unwrap();
body.push_str(&self.ty(ty));
}
body.push('}');
let name = self.make_type_name();
definitions.add_definition_line(format_args!("type {name} = {body}"));
(name, Rc::new(RefCell::new(ns)))
})
}
fn bundle_ty(&self, ty: Bundle) -> Ident {
self.bundle_def(ty).0
}
fn bundle_ns(&self, ty: Bundle) -> Rc<RefCell<Namespace>> {
self.bundle_def(ty).1
}
fn enum_def(&self, ty: Enum) -> (Ident, Rc<EnumDef>) {
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(NameId(name, 0))).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}"));
(
name,
Rc::new(EnumDef {
variants: RefCell::new(variants),
body,
}),
)
})
}
fn enum_ty(&self, ty: Enum) -> Ident {
self.enum_def(ty).0
}
fn get_enum_variant(&mut self, ty: Enum, name: Interned<str>) -> Ident {
self.enum_def(ty)
.1
.variants
.borrow_mut()
.get(NameId(name, 0))
}
fn ty<T: Type>(&self, ty: T) -> String {
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(),
}
}
}
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;
}
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
}
}
#[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)?;
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, WrappedError> {
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>,
) -> 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(')');
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,
) -> 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
{
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 {
value
} else if ToTy::Signed::VALUE {
format!("asSInt({value})")
} else {
format!("asUInt({value})")
}
} else {
value = format!("tail({value}, {})", from_ty.width() - to_ty.width());
if ToTy::Signed::VALUE {
format!("asSInt({value})")
} else {
value
}
}
}
fn typed_bit_cast<FromTy: Type>(
&mut self,
firrtl_cast_fn: Option<&str>,
value: Expr<FromTy>,
definitions: &RcDefinitions,
const_ty: bool,
) -> String {
let value = self.expr(Expr::canonical(value), definitions, const_ty);
if let Some(firrtl_cast_fn) = firrtl_cast_fn {
format!("{firrtl_cast_fn}({value})")
} else {
value
}
}
fn slice<T: IntType>(
&mut self,
base: Expr<T>,
range: Range<usize>,
definitions: &RcDefinitions,
const_ty: bool,
) -> String {
let base_width = Expr::ty(base).width();
let base = self.expr(Expr::canonical(base), definitions, const_ty);
if range.is_empty() {
format!("tail({base}, {base_width})")
} else {
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,
) -> 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}"));
}
ident.to_string()
}
fn bundle_literal_expr(
&mut self,
expr: ops::BundleLiteral<Bundle>,
definitions: &RcDefinitions,
const_ty: bool,
) -> 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(NameId(name, 0));
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}"));
}
ident.to_string()
}
fn uninit_expr(
&mut self,
expr: ops::Uninit,
definitions: &RcDefinitions,
const_ty: bool,
) -> 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}"));
ident.to_string()
}
fn enum_literal_expr(
&mut self,
expr: ops::EnumLiteral<Enum>,
definitions: &RcDefinitions,
const_ty: bool,
) -> String {
let variant_expr = expr
.variant_value()
.map(|variant_value| self.expr(variant_value, definitions, const_ty));
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<'_>,
) -> String {
if ty.fields().is_empty() {
return "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}"));
retval.to_string()
}
fn expr_cast_enum_to_bits(
&mut self,
value_str: String,
ty: Enum,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> String {
if ty.variants().is_empty() {
return "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,
));
}
}
retval.to_string()
}
fn expr_cast_array_to_bits(
&mut self,
value_str: String,
ty: Array,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> String {
if ty.is_empty() {
return "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}"));
retval.to_string()
}
fn expr_cast_to_bits(
&mut self,
value_str: String,
ty: CanonicalType,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> 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(_) => {
value_str
}
CanonicalType::SInt(_)
| CanonicalType::Clock(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::Reset(_) => format!("asUInt({value_str})"),
}
}
fn expr_cast_bits_to_bundle(
&mut self,
value_str: String,
ty: Bundle,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> 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 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, field_offset) 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}"
));
}
retval.to_string()
}
fn expr_cast_bits_to_enum(
&mut self,
value_str: String,
ty: Enum,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> 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 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 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);
}
retval.to_string()
}
fn expr_cast_bits_to_array(
&mut self,
value_str: String,
ty: Array,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> 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 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}"
));
}
retval.to_string()
}
fn expr_cast_bits_to(
&mut self,
value_str: String,
ty: CanonicalType,
definitions: &RcDefinitions,
extra_indent: Indent<'_>,
) -> 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(_) => value_str,
CanonicalType::SInt(_) => format!("asSInt({value_str})"),
CanonicalType::Bool(_) => value_str,
CanonicalType::Clock(_) => format!("asClock({value_str})"),
CanonicalType::AsyncReset(_) => format!("asAsyncReset({value_str})"),
CanonicalType::SyncReset(_) => value_str,
CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"),
}
}
fn expr_unary<T: Type>(
&mut self,
func: &str,
arg: Expr<T>,
definitions: &RcDefinitions,
const_ty: bool,
) -> String {
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,
) -> String {
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,
) -> String {
match *Expr::expr_enum(expr) {
ExprEnum::UIntLiteral(literal) => self.uint_literal(&literal),
ExprEnum::SIntLiteral(literal) => self.sint_literal(&literal),
ExprEnum::BoolLiteral(literal) => self.bool_literal(literal),
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) => {
format!(
"shl({lhs}, {rhs})",
lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty),
rhs = expr.rhs(),
)
}
ExprEnum::FixedShlS(expr) => {
format!(
"shl({lhs}, {rhs})",
lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty),
rhs = expr.rhs(),
)
}
ExprEnum::FixedShrU(expr) => {
format!(
"shr({lhs}, {rhs})",
lhs = self.expr(Expr::canonical(expr.lhs()), definitions, const_ty),
rhs = expr.rhs(),
)
}
ExprEnum::FixedShrS(expr) => {
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();
out
}
ExprEnum::VariantAccess(variant_access) => 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();
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();
out
}
ExprEnum::ModuleIO(expr) => self.module.ns.get(expr.name_id()).to_string(),
ExprEnum::Instance(expr) => {
assert!(!const_ty, "not a constant");
self.module.ns.get(expr.scoped_name().1).to_string()
}
ExprEnum::Wire(expr) => {
assert!(!const_ty, "not a constant");
self.module.ns.get(expr.scoped_name().1).to_string()
}
ExprEnum::Reg(expr) => {
assert!(!const_ty, "not a constant");
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());
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<(), WrappedError> {
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(),
},
};
self.annotations.push(FirrtlAnnotation {
data,
target: AnnotationTarget {
circuit: self.circuit_name,
path: Some(path),
},
})
}
fn annotation_target_ref(&mut self, target: Interned<Target>) -> 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::Wire(v) => self.module.ns.get(v.name_id()),
TargetBase::Instance(v) => self.module.ns.get(v.name_id()),
};
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!(),
}
retval
}
}
}
fn targeted_annotations(
&mut self,
base_module: Ident,
submodules: Vec<AnnotationTargetSubmodule>,
annotations: &[crate::annotations::TargetedAnnotation],
) {
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(),
);
}
}
fn write_mem(&mut self, module_name: Ident, memory: Mem) -> Result<String, WrappedError> {
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 block(
&mut self,
module: Interned<Module<Bundle>>,
block: Block,
_block_indent: &PushIndent<'_>,
definitions: Option<RcDefinitions>,
) -> Result<String, WrappedError> {
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(StmtReg { annotations, reg })) => {
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();
}
}
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, WrappedError> {
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(NameId(module_io.name(), 0));
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,
}) => {
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 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 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 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 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 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,
mut top_module: Interned<Module<Bundle>>,
options: ExportOptions,
) -> Result<(), WrappedError> {
let ExportOptions {
simplify_memories: do_simplify_memories,
simplify_enums: do_simplify_enums,
__private: _,
} = options;
if let Some(kind) = do_simplify_enums {
top_module =
simplify_enums(top_module, kind).map_err(|e| file_backend.simplify_enums_error(e))?;
}
if do_simplify_memories {
top_module = simplify_memories(top_module);
}
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::new(),
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(());
#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExportOptions {
#[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)]
pub simplify_memories: bool,
#[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")]
pub simplify_enums: std::option::Option<SimplifyEnumsKind>,
#[doc(hidden)]
#[clap(skip = ExportOptionsPrivate(()))]
/// `#[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 ExportOptions {
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 { " }" }
)
}
}
impl Default for ExportOptions {
fn default() -> Self {
Self {
simplify_memories: true,
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
__private: ExportOptionsPrivate(()),
}
}
}
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)
})
}
#[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)*
);
};
}