properly handle all XilinxAnnotations, this makes nextpnr-xilinx properly pick up the clock frequency
All checks were successful
/ test (pull_request) Successful in 4m34s
/ test (push) Successful in 5m9s

This commit is contained in:
Jacob Lifshay 2025-10-21 22:22:30 -07:00
parent 409992961c
commit c6feea6d51
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
3 changed files with 250 additions and 26 deletions

View file

@ -1883,7 +1883,11 @@ impl<'a> Exporter<'a> {
} }
fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) { fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) {
let data = match annotation { let data = match annotation {
Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch, Annotation::DontTouch(DontTouchAnnotation {}) => {
// TODO: error if the annotated thing was renamed because of a naming conflict,
// unless Target::base() is one of the ports of the top-level module since that's handled by ScalarizedModuleABI
AnnotationData::DontTouch
}
Annotation::SVAttribute(SVAttributeAnnotation { text }) => { Annotation::SVAttribute(SVAttributeAnnotation { text }) => {
AnnotationData::AttributeAnnotation { description: *text } AnnotationData::AttributeAnnotation { description: *text }
} }

View file

@ -2,9 +2,8 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::{ use crate::{
annotations::Annotation,
intern::{Intern, Interned}, intern::{Intern, Interned},
module::instance_with_loc, module::{instance_with_loc, wire_with_loc},
platform::{ platform::{
DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory,
PeripheralsBuilderFinished, Platform, PlatformAspectSet, PeripheralsBuilderFinished, Platform, PlatformAspectSet,
@ -205,11 +204,7 @@ impl Platform for ArtyA7Platform {
ld6, ld6,
ld7, ld7,
} = peripherals; } = peripherals;
let make_buffered_input = |name: &str, let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| {
location: &str,
io_standard: &str,
additional_annotations: &[Annotation],
invert: bool| {
let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool);
annotate( annotate(
pin, pin,
@ -223,7 +218,6 @@ impl Platform for ArtyA7Platform {
value: io_standard.intern(), value: io_standard.intern(),
}, },
); );
annotate(pin, additional_annotations);
let buf = instance_with_loc( let buf = instance_with_loc(
&format!("{name}_buf"), &format!("{name}_buf"),
primitives::IBUF(), primitives::IBUF(),
@ -258,13 +252,7 @@ impl Platform for ArtyA7Platform {
let clock_annotation = XdcCreateClockAnnotation { let clock_annotation = XdcCreateClockAnnotation {
period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"), period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"),
}; };
let clk100_buf = make_buffered_input( let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false);
"clk100",
"E3",
"LVCMOS33",
&[clock_annotation.into()],
false,
);
let startup = instance_with_loc( let startup = instance_with_loc(
"startup", "startup",
STARTUPE2_default_inputs(), STARTUPE2_default_inputs(),
@ -273,12 +261,15 @@ impl Platform for ArtyA7Platform {
let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin());
connect(clk100_sync.CE, startup.EOS); connect(clk100_sync.CE, startup.EOS);
connect(clk100_sync.I, clk100_buf); connect(clk100_sync.I, clk100_buf);
annotate(clk100_sync.O, clock_annotation); let clk100_out = wire_with_loc("clk100_out", SourceLocation::builtin(), Clock);
connect(clk100_out, clk100_sync.O);
annotate(clk100_out, clock_annotation);
annotate(clk100_out, DontTouchAnnotation);
if let Some(clk100) = clk100.into_used() { if let Some(clk100) = clk100.into_used() {
connect(clk100.instance_io_field().clk, clk100_sync.O); connect(clk100.instance_io_field().clk, clk100_out);
} }
let rst_value = { let rst_value = {
let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", &[], true); let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true);
let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin()); let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin());
connect(rst_sync.clk, clk100_sync.O); connect(rst_sync.clk, clk100_sync.O);
connect(rst_sync.inp, rst_buf); connect(rst_sync.inp, rst_buf);

View file

@ -2,7 +2,7 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::{ use crate::{
annotations::Annotation, annotations::{Annotation, TargetedAnnotation},
build::{ build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
@ -12,12 +12,17 @@ use crate::{
}, },
verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind},
}, },
bundle::Bundle, bundle::{Bundle, BundleType},
expr::target::{Target, TargetBase},
firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort},
intern::{Intern, InternSlice, Interned}, intern::{Intern, InternSlice, Interned},
module::{Module, NameId}, module::{
prelude::JobParams, NameId, ScopedNameId, TargetName,
util::job_server::AcquiredJob, transform::visit::{Visit, Visitor},
},
prelude::*,
source_location::SourceLocation,
util::{HashSet, job_server::AcquiredJob},
vendor::xilinx::{ vendor::xilinx::{
Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation,
XilinxAnnotation, XilinxArgs, XilinxAnnotation, XilinxArgs,
@ -26,6 +31,7 @@ use crate::{
use eyre::Context; use eyre::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
convert::Infallible,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fmt::{self, Write}, fmt::{self, Write},
ops::ControlFlow, ops::ControlFlow,
@ -370,6 +376,228 @@ fn tcl_escape(s: impl AsRef<str>) -> String {
retval retval
} }
#[derive(Copy, Clone, Debug)]
enum AnnotationTarget {
None,
Module(Module<Bundle>),
Mem(Mem),
Target(Interned<Target>),
}
impl AnnotationTarget {
fn source_location(self) -> SourceLocation {
match self {
AnnotationTarget::None => unreachable!(),
AnnotationTarget::Module(module) => module.source_location(),
AnnotationTarget::Mem(mem) => mem.source_location(),
AnnotationTarget::Target(target) => target.base().source_location(),
}
}
}
struct XdcFileWriter<W: fmt::Write> {
output: W,
module_depth: usize,
annotation_target: AnnotationTarget,
dont_touch_targets: HashSet<Interned<Target>>,
required_dont_touch_targets: HashSet<Interned<Target>>,
}
impl<W: fmt::Write> XdcFileWriter<W> {
fn run(output: W, top_module: Module<Bundle>) -> Result<(), WriteXdcContentsError> {
let mut this = Self {
output,
module_depth: 0,
annotation_target: AnnotationTarget::None,
dont_touch_targets: HashSet::default(),
required_dont_touch_targets: HashSet::default(),
};
top_module.visit(&mut this)?;
let Self {
output: _,
module_depth: _,
annotation_target: _,
dont_touch_targets,
required_dont_touch_targets,
} = this;
for &target in required_dont_touch_targets.difference(&dont_touch_targets) {
return Err(eyre::eyre!(
"a DontTouchAnnotation is required since the target is also annotated with a XilinxAnnotation:\ntarget: {target:?}\nat: {}",
target.base().source_location(),
).into());
}
Ok(())
}
fn default_visit_with<T: ?Sized + Visit<Self>>(
&mut self,
module_depth: usize,
annotation_target: AnnotationTarget,
v: &T,
) -> Result<(), WriteXdcContentsError> {
let Self {
output: _,
module_depth: old_module_depth,
annotation_target: old_annotation_target,
dont_touch_targets: _,
required_dont_touch_targets: _,
} = *self;
self.module_depth = module_depth;
self.annotation_target = annotation_target;
let retval = v.default_visit(self);
self.module_depth = old_module_depth;
self.annotation_target = old_annotation_target;
retval
}
}
impl<W: fmt::Write> Visitor for XdcFileWriter<W> {
type Error = WriteXdcContentsError;
fn visit_targeted_annotation(&mut self, v: &TargetedAnnotation) -> Result<(), Self::Error> {
self.default_visit_with(self.module_depth, AnnotationTarget::Target(v.target()), v)
}
fn visit_module<T: BundleType>(&mut self, v: &Module<T>) -> Result<(), Self::Error> {
self.default_visit_with(
self.module_depth + 1,
AnnotationTarget::Module(v.canonical()),
v,
)
}
fn visit_mem<Element: Type, Len: Size>(
&mut self,
v: &Mem<Element, Len>,
) -> Result<(), Self::Error>
where
Element: Visit<Self>,
{
self.default_visit_with(
self.module_depth + 1,
AnnotationTarget::Mem(v.canonical()),
v,
)
}
fn visit_dont_touch_annotation(&mut self, _v: &DontTouchAnnotation) -> Result<(), Self::Error> {
if let AnnotationTarget::Target(target) = self.annotation_target {
self.dont_touch_targets.insert(target);
}
Ok(())
}
fn visit_xilinx_annotation(&mut self, v: &XilinxAnnotation) -> Result<(), Self::Error> {
fn todo(
msg: &str,
annotation: &XilinxAnnotation,
source_location: SourceLocation,
) -> Result<Infallible, WriteXdcContentsError> {
Err(WriteXdcContentsError(eyre::eyre!(
"{msg}\nannotation: {annotation:?}\nat: {source_location}"
)))
}
if self.module_depth != 1 {
match todo(
"annotations are not yet supported outside of the top module since the logic to figure out the correct name isn't implemented",
v,
self.annotation_target.source_location(),
)? {}
}
match self.annotation_target {
AnnotationTarget::None => unreachable!(),
AnnotationTarget::Module(module) => match v {
XilinxAnnotation::XdcIOStandard(_)
| XilinxAnnotation::XdcLocation(_)
| XilinxAnnotation::XdcCreateClock(_) => {
return Err(WriteXdcContentsError(eyre::eyre!(
"annotation not allowed on a module: {v:?}\nat: {}",
module.source_location(),
)));
}
},
AnnotationTarget::Mem(mem) => match todo(
"xilinx annotations are not yet supported on memories since the logic to figure out the correct name isn't implemented",
v,
mem.source_location(),
)? {},
AnnotationTarget::Target(target) => {
let base = target.base();
match *base {
TargetBase::ModuleIO(_) => {
// already handled by write_xdc_contents handling the main module's ScalarizedModuleABI
Ok(())
}
TargetBase::MemPort(mem_port) => {
match todo(
"xilinx annotations are not yet supported on memory ports since the logic to figure out the correct name isn't implemented",
v,
mem_port.source_location(),
)? {}
}
TargetBase::Reg(_)
| TargetBase::RegSync(_)
| TargetBase::RegAsync(_)
| TargetBase::Wire(_) => {
match *target {
Target::Base(_) => {}
Target::Child(_) => match todo(
"xilinx annotations are not yet supported on parts of registers/wires since the logic to figure out the correct name isn't implemented",
v,
base.source_location(),
)? {},
}
match base.canonical_ty() {
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_) => {}
CanonicalType::Enum(_)
| CanonicalType::Array(_)
| CanonicalType::Bundle(_)
| CanonicalType::PhantomConst(_)
| CanonicalType::DynSimOnly(_) => match todo(
"xilinx annotations are not yet supported on types other than integers, Bool, resets, or Clock since the logic to figure out the correct name isn't implemented",
v,
base.source_location(),
)? {},
}
self.required_dont_touch_targets.insert(target);
match v {
XilinxAnnotation::XdcIOStandard(_)
| XilinxAnnotation::XdcLocation(_) => {
return Err(WriteXdcContentsError(eyre::eyre!(
"annotation must be on a ModuleIO: {v:?}\nat: {}",
base.source_location(),
)));
}
XilinxAnnotation::XdcCreateClock(XdcCreateClockAnnotation {
period,
}) => {
let TargetName(ScopedNameId(_, NameId(name, _)), _) =
base.target_name();
writeln!(
self.output,
"create_clock -period {period} [get_nets {}]",
tcl_escape(name),
)?;
Ok(())
}
}
}
TargetBase::Instance(instance) => match todo(
"xilinx annotations are not yet supported on instances' IO since the logic to figure out the correct name isn't implemented",
v,
instance.source_location(),
)? {},
}
}
}
}
}
impl YosysNextpnrXrayWriteXdcFile { impl YosysNextpnrXrayWriteXdcFile {
fn write_xdc_contents_for_port_and_annotations( fn write_xdc_contents_for_port_and_annotations(
&self, &self,
@ -426,9 +654,10 @@ impl YosysNextpnrXrayWriteXdcFile {
Err(e) => ControlFlow::Break(e), Err(e) => ControlFlow::Break(e),
} }
}) { }) {
ControlFlow::Continue(()) => Ok(()), ControlFlow::Continue(()) => {}
ControlFlow::Break(e) => Err(e.0), ControlFlow::Break(e) => return Err(e.0),
} }
XdcFileWriter::run(output, *top_module).map_err(|e| e.0)
} }
} }