3489 lines
122 KiB
Rust
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 ¶meters {
|
|
let escaped_string;
|
|
let value: &dyn fmt::Display = match value {
|
|
ExternModuleParameterValue::Integer(value) => value,
|
|
ExternModuleParameterValue::String(value) => {
|
|
escaped_string = EscapedString { value, raw: false };
|
|
&escaped_string
|
|
}
|
|
ExternModuleParameterValue::RawVerilog(value) => {
|
|
escaped_string = EscapedString { value, raw: true };
|
|
&escaped_string
|
|
}
|
|
};
|
|
let name = Ident(*name);
|
|
writeln!(body, "{indent}parameter {name} = {value}").unwrap();
|
|
}
|
|
"extmodule"
|
|
}
|
|
ModuleBody::Normal(NormalModuleBody { body: top_block }) => {
|
|
body.push_str(&self.block(
|
|
module,
|
|
top_block,
|
|
&module_indent,
|
|
Some(self.module.definitions.clone()),
|
|
)?);
|
|
"module"
|
|
}
|
|
};
|
|
if body.is_empty() {
|
|
writeln!(body, "{indent}skip").unwrap();
|
|
}
|
|
drop(module_indent);
|
|
let mut out = String::new();
|
|
writeln!(
|
|
out,
|
|
"{indent}{module_kw} {module_name}:{}",
|
|
FileInfo::new(module.source_location()),
|
|
)
|
|
.unwrap();
|
|
out.push_str(&body);
|
|
Ok(out)
|
|
}
|
|
}
|
|
|
|
pub trait FileBackendTrait {
|
|
type Error: From<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)*
|
|
);
|
|
};
|
|
}
|