WIP adding annotations for generating the .xdc file for yosys-nextpnr-prjxray
All checks were successful
/ test (pull_request) Successful in 4m26s

This commit is contained in:
Jacob Lifshay 2025-10-15 04:11:23 -07:00
parent faf1e5113c
commit c2df30f844
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
8 changed files with 667 additions and 32 deletions

View file

@ -147,7 +147,7 @@ macro_rules! make_annotation_enum {
(
$(#[$meta:meta])*
$vis:vis enum $Annotation:ident {
$($Variant:ident($T:ident),)*
$($Variant:ident($T:ty),)*
}
) => {
$(#[$meta])*
@ -199,6 +199,8 @@ make_annotation_enum! {
BlackBoxPath(BlackBoxPathAnnotation),
DocString(DocStringAnnotation),
CustomFirrtl(CustomFirrtlAnnotation),
XdcLocation(crate::build::vendor::xilinx::XdcLocationAnnotation),
XdcIOStandard(crate::build::vendor::xilinx::XdcIOStandardAnnotation),
}
}

View file

@ -1,8 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::intern::Interned;
pub mod yosys_nextpnr_prjxray;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcIOStandardAnnotation {
pub value: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcLocationAnnotation {
pub location: Interned<str>,
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
yosys_nextpnr_prjxray::built_in_job_kinds()
}

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
use crate::{
annotations::Annotation,
build::{
CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies,
JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs,
@ -9,17 +10,20 @@ use crate::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
interned_known_utf8_method, interned_known_utf8_path_buf_method,
vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation},
verilog::{VerilogDialect, VerilogJob, VerilogJobKind},
},
bundle::Bundle,
firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort},
intern::{Intern, Interned},
module::NameId,
module::{Module, NameId},
prelude::JobParams,
util::job_server::AcquiredJob,
};
use clap::ValueEnum;
use eyre::Context;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{fmt, ops::ControlFlow};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
pub struct YosysNextpnrXrayWriteYsFileJobKind;
@ -312,10 +316,90 @@ impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs {
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct YosysNextpnrXrayWriteXdcFile {
firrtl_export_options: crate::firrtl::ExportOptions,
output_dir: Interned<str>,
xdc_file: Interned<str>,
}
struct WriteXdcContentsError(eyre::Report);
impl From<eyre::Report> for WriteXdcContentsError {
fn from(v: eyre::Report) -> Self {
Self(v)
}
}
impl From<fmt::Error> for WriteXdcContentsError {
fn from(_v: fmt::Error) -> Self {
unreachable!("String write can't fail")
}
}
fn tcl_escape(s: impl AsRef<str>) -> String {
let s = s.as_ref();
let mut retval = String::with_capacity(s.len().saturating_add(2));
retval.push('"');
for ch in s.chars() {
if let '$' | '\\' | '[' = ch {
retval.push('\\');
}
retval.push(ch);
}
retval.push('"');
retval
}
impl YosysNextpnrXrayWriteXdcFile {
fn write_xdc_contents_for_port_and_annotations(
&self,
output: &mut impl fmt::Write,
port: &ScalarizedModuleABIPort,
annotations: ScalarizedModuleABIAnnotations<'_>,
) -> Result<(), WriteXdcContentsError> {
for annotation in annotations {
match annotation.annotation() {
Annotation::DontTouch(_)
| Annotation::SVAttribute(_)
| Annotation::BlackBoxInline(_)
| Annotation::BlackBoxPath(_)
| Annotation::DocString(_)
| Annotation::CustomFirrtl(_) => {}
Annotation::XdcLocation(XdcLocationAnnotation { location }) => writeln!(
output,
"set_property LOC {} [get_ports {}]",
tcl_escape(location),
tcl_escape(port.scalarized_name())
)?,
Annotation::XdcIOStandard(XdcIOStandardAnnotation { value }) => writeln!(
output,
"set_property IOSTANDARD {} [get_ports {}]",
tcl_escape(value),
tcl_escape(port.scalarized_name())
)?,
}
}
Ok(())
}
fn write_xdc_contents(
&self,
output: &mut String,
top_module: &Module<Bundle>,
) -> eyre::Result<()> {
let scalarized_module_abi =
ScalarizedModuleABI::new(top_module, self.firrtl_export_options)
.map_err(eyre::Report::from)?;
match scalarized_module_abi.for_each_port_and_annotations(|port, annotations| {
match self.write_xdc_contents_for_port_and_annotations(output, port, annotations) {
Ok(()) => ControlFlow::Continue(()),
Err(e) => ControlFlow::Break(e),
}
}) {
ControlFlow::Continue(()) => Ok(()),
ControlFlow::Break(e) => Err(e.0),
}
}
}
impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
type Args = YosysNextpnrXrayWriteXdcFileArgs;
type Job = YosysNextpnrXrayWriteXdcFile;
@ -329,9 +413,19 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
args: JobArgsAndDependencies<Self>,
params: &JobParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let firrtl_export_options = args
.dependencies
.dependencies
.dependencies
.dependencies
.dependencies
.args
.args
.export_options;
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
let YosysNextpnrXrayWriteXdcFileArgs {} = args;
Ok(YosysNextpnrXrayWriteXdcFile {
firrtl_export_options,
output_dir: dependencies.base_job().output_dir(),
xdc_file: dependencies.base_job().file_with_ext("xdc"),
})
@ -361,10 +455,12 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
self,
job: &Self::Job,
inputs: &[JobItem],
_params: &JobParams,
params: &JobParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
let mut xdc = String::new();
job.write_xdc_contents(&mut xdc, params.main_module())?;
// TODO: create actual .xdc from input module
std::fs::write(
job.xdc_file,

View file

@ -4,7 +4,7 @@
use crate::{
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::Array,
build::{ToArgs, WriteArgs},
@ -24,9 +24,9 @@ use crate::{
memory::{Mem, PortKind, PortName, ReadUnderWrite},
module::{
AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Module, ModuleBody, NameId, NameOptId, NormalModuleBody, Stmt,
StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg,
StmtWire,
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,
@ -53,7 +53,7 @@ use std::{
fs,
hash::Hash,
io,
ops::Range,
ops::{ControlFlow, Range},
path::{Path, PathBuf},
rc::Rc,
};
@ -405,10 +405,10 @@ impl TypeState {
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> {
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>>)> {
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();
@ -429,13 +429,13 @@ impl TypeState {
Ok((name, Rc::new(RefCell::new(ns))))
})
}
fn bundle_ty(&self, ty: Bundle) -> Result<Ident> {
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>>> {
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>)> {
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();
@ -462,13 +462,13 @@ impl TypeState {
))
})
}
fn enum_ty(&self, ty: Enum) -> Result<Ident> {
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> {
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> {
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(),
@ -486,7 +486,7 @@ impl TypeState {
CanonicalType::Reset(Reset {}) => "Reset".into(),
CanonicalType::PhantomConst(_) => "{}".into(),
CanonicalType::DynSimOnly(_) => {
return Err(FirrtlError::SimOnlyValuesAreNotPermitted.into());
return Err(FirrtlError::SimOnlyValuesAreNotPermitted);
}
})
}
@ -1904,6 +1904,7 @@ impl<'a> Exporter<'a> {
class: str::to_string(class),
additional_fields: (*additional_fields).into(),
},
Annotation::XdcLocation(_) | Annotation::XdcIOStandard(_) => return,
};
self.annotations.push(FirrtlAnnotation {
data,
@ -2674,21 +2675,12 @@ impl FileBackendTrait for TestBackend {
fn export_impl(
file_backend: &mut dyn WrappedFileBackendTrait,
mut top_module: Interned<Module<Bundle>>,
top_module: Interned<Module<Bundle>>,
options: ExportOptions,
) -> Result<(), WrappedError> {
let ExportOptions {
simplify_memories: do_simplify_memories,
simplify_enums: do_simplify_enums,
__private: _,
} = options;
if let Some(kind) = do_simplify_enums {
top_module =
simplify_enums(top_module, kind).map_err(|e| file_backend.simplify_enums_error(e))?;
}
if do_simplify_memories {
top_module = simplify_memories(top_module);
}
let 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());
@ -2856,6 +2848,29 @@ impl ExportOptions {
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 {
@ -2885,6 +2900,497 @@ pub fn export<T: BundleType, B: FileBackendTrait>(
})
}
#[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) {

View file

@ -833,6 +833,8 @@ pub struct AnnotatedModuleIO<S: ModuleBuildingStatus = ModuleBuilt> {
pub module_io: ModuleIO<CanonicalType>,
}
impl Copy for AnnotatedModuleIO {}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum ModuleKind {
Extern,

View file

@ -1817,6 +1817,8 @@ impl_run_pass_copy!([] UInt);
impl_run_pass_copy!([] usize);
impl_run_pass_copy!([] FormalKind);
impl_run_pass_copy!([] PhantomConst);
impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcIOStandardAnnotation);
impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcLocationAnnotation);
macro_rules! impl_run_pass_for_struct {
(
@ -2217,6 +2219,8 @@ impl_run_pass_for_enum! {
BlackBoxPath(v),
DocString(v),
CustomFirrtl(v),
XdcLocation(v),
XdcIOStandard(v),
}
}

View file

@ -7,6 +7,7 @@ use crate::{
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::ArrayType,
build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation},
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},

View file

@ -1176,7 +1176,9 @@
"BlackBoxInline": "Visible",
"BlackBoxPath": "Visible",
"DocString": "Visible",
"CustomFirrtl": "Visible"
"CustomFirrtl": "Visible",
"XdcLocation": "Visible",
"XdcIOStandard": "Visible"
}
},
"DontTouchAnnotation": {
@ -1214,6 +1216,16 @@
"$kind": "Opaque"
}
},
"XdcLocationAnnotation": {
"data": {
"$kind": "Opaque"
}
},
"XdcIOStandardAnnotation": {
"data": {
"$kind": "Opaque"
}
},
"Target": {
"data": {
"$kind": "Enum",