Add peripherals and Arty A7 platforms -- blinky works correctly now on arty-a7-100t!

This commit is contained in:
Jacob Lifshay 2025-10-17 18:00:11 -07:00
parent 4d54f903be
commit 477a1f2d29
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
23 changed files with 2297 additions and 554 deletions

View file

@ -21,4 +21,4 @@ jobs:
- run: cargo test --doc --features=unstable-doc
- run: cargo doc --features=unstable-doc
- run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher
- run: cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100))
- run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out

31
Cargo.lock generated
View file

@ -319,6 +319,7 @@ dependencies = [
"jobslot",
"num-bigint",
"num-traits",
"ordered-float",
"petgraph",
"serde",
"serde_json",
@ -524,6 +525,17 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"num-traits",
"rand",
"serde",
]
[[package]]
name = "petgraph"
version = "0.8.1"
@ -576,6 +588,25 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
"serde",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"serde",
]
[[package]]
name = "rustix"
version = "0.38.31"

View file

@ -30,6 +30,7 @@ indexmap = { version = "2.5.0", features = ["serde"] }
jobslot = "0.2.23"
num-bigint = "0.4.6"
num-traits = "0.2.16"
ordered-float = { version = "5.1.0", features = ["serde"] }
petgraph = "0.8.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"

View file

@ -26,6 +26,7 @@ hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
ordered-float.workspace = true
petgraph.workspace = true
serde_json.workspace = true
serde.workspace = true

View file

@ -1,55 +1,64 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
build::{ToArgs, WriteArgs},
prelude::*,
};
use fayalite::prelude::*;
#[hdl_module]
fn blinky(clock_frequency: u64) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let rst: SyncReset = m.input();
fn blinky(platform_io_builder: PlatformIOBuilder<'_>) {
let clk_input =
platform_io_builder.peripherals_with_type::<peripherals::ClockInput>()[0].use_peripheral();
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
let cd = #[hdl]
ClockDomain {
clk,
rst: rst.to_reset(),
clk: clk_input.clk,
rst,
};
let max_value = clock_frequency / 2 - 1;
let max_value = (Expr::ty(clk_input).frequency() / 2.0).round_ties_even() as u64 - 1;
let int_ty = UInt::range_inclusive(0..=max_value);
#[hdl]
let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
#[hdl]
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
#[hdl]
let rgb_output_reg = reg_builder().clock_domain(cd).reset(
#[hdl]
peripherals::RgbLed {
r: false,
g: false,
b: false,
},
);
#[hdl]
if counter_reg.cmp_eq(max_value) {
connect_any(counter_reg, 0u8);
connect(output_reg, !output_reg);
connect(rgb_output_reg.r, !rgb_output_reg.r);
#[hdl]
if rgb_output_reg.r {
connect(rgb_output_reg.g, !rgb_output_reg.g);
#[hdl]
if rgb_output_reg.g {
connect(rgb_output_reg.b, !rgb_output_reg.b);
}
}
} else {
connect_any(counter_reg, counter_reg + 1_hdl_u1);
}
#[hdl]
let led: Bool = m.output();
connect(led, output_reg);
}
#[derive(clap::Args, Clone, PartialEq, Eq, Hash, Debug)]
struct ExtraArgs {
/// clock frequency in hertz
#[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))]
clock_frequency: u64,
}
impl ToArgs for ExtraArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self { clock_frequency } = self;
args.write_arg(format!("--clock-frequency={clock_frequency}"));
for led in platform_io_builder.peripherals_with_type::<peripherals::Led>() {
if let Ok(led) = led.try_use_peripheral() {
connect(led.on, output_reg);
}
}
for rgb_led in platform_io_builder.peripherals_with_type::<peripherals::RgbLed>() {
if let Ok(rgb_led) = rgb_led.try_use_peripheral() {
connect(rgb_led, rgb_output_reg);
}
}
#[hdl]
let io = m.add_platform_io(platform_io_builder);
}
fn main() {
BuildCli::main(|_cli, ExtraArgs { clock_frequency }| {
Ok(JobParams::new(blinky(clock_frequency), "blinky"))
<BuildCli>::main("blinky", |_, platform, _| {
Ok(JobParams::new(platform.wrap_main_module(blinky)))
});
}

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,9 @@
use crate::{
build::{
ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs,
JobParams, ToArgs, WriteArgs,
ArgsWriter, CommandParams, GlobalParams, JobAndDependencies, JobAndKind,
JobArgsAndDependencies, JobDependencies, JobDependenciesHasBase, JobItem, JobItemName,
JobKind, JobKindAndArgs, JobParams, ToArgs, WriteArgs,
},
intern::{Intern, Interned},
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
@ -990,12 +990,13 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size
+ Serialize
+ DeserializeOwned;
type BaseJobPosition;
type Dependencies: JobDependencies<JobsAndKinds: GetJob<BaseJob, Self::BaseJobPosition>>;
type Dependencies: JobDependenciesHasBase;
type ExternalProgram: ExternalProgramTrait;
fn dependencies() -> Self::Dependencies;
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
@ -1028,6 +1029,7 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let JobKindAndArgs {
kind,
@ -1042,8 +1044,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
additional_args: _,
},
} = args.args;
let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?;
let base_job = GetJob::<BaseJob, _>::get_job(&dependencies);
let (additional_job_data, dependencies) = T::args_to_jobs(args, params, global_params)?;
let base_job = T::Dependencies::base_job(&dependencies);
let job = ExternalCommandJob {
additional_job_data,
program_path,
@ -1078,7 +1080,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(
@ -1093,7 +1096,7 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
} = job.command_params();
ExternalJobCaching::new(
&job.output_dir,
&params.application_name(),
&global_params.application_name(),
&T::job_kind_name(),
job.run_even_if_cached,
)?

View file

@ -3,7 +3,7 @@
use crate::{
build::{
BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies,
BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies,
JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams,
ToArgs, WriteArgs,
},
@ -64,9 +64,11 @@ impl JobKind for FirrtlJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(
params,
global_params,
|_kind, FirrtlArgs { export_options }, dependencies| {
Ok(Firrtl {
base: dependencies.get_job::<BaseJob, _>().clone(),
@ -103,6 +105,7 @@ impl JobKind for FirrtlJobKind {
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
let [JobItem::Path { path: input_path }] = *inputs else {

View file

@ -3,8 +3,8 @@
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
@ -201,6 +201,7 @@ impl JobKind for WriteSbyFileJobKind {
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.dependencies
.dependencies
@ -209,7 +210,7 @@ impl JobKind for WriteSbyFileJobKind {
.additional_args
.verilog_dialect
.get_or_insert(VerilogDialect::Yosys);
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let FormalArgs {
sby_extra_args,
formal_mode,
@ -255,6 +256,7 @@ impl JobKind for WriteSbyFileJobKind {
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
@ -351,11 +353,12 @@ impl ExternalCommand for Formal {
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let FormalAdditionalArgs {} = args.additional_args;
let write_sby_file = dependencies.get_job::<WriteSbyFileJob, _>().clone();
Ok(Formal {

View file

@ -2,8 +2,11 @@
// See Notices.txt for copyright information
use crate::{
build::{DynJob, JobItem, JobItemName, JobParams, program_name_for_internal_jobs},
build::{
DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs,
},
intern::Interned,
platform::DynPlatform,
util::{HashMap, HashSet, job_server::AcquiredJob},
};
use eyre::{ContextCompat, eyre};
@ -489,15 +492,21 @@ impl JobGraph {
Err(e) => panic!("error: {e}"),
}
}
pub fn to_unix_makefile(&self, extra_args: &[Interned<OsStr>]) -> Result<String, Utf8Error> {
pub fn to_unix_makefile(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
self.to_unix_makefile_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_makefile_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
let mut retval = String::new();
@ -572,19 +581,23 @@ impl JobGraph {
}
}
retval.push_str("\n\t");
job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args)
.to_unix_shell_line(&mut retval, |arg, output| {
write_str!(
output,
"{}",
EscapeForUnixMakefile::new(
arg,
UnixMakefileEscapeKind::RecipeWithShellEscaping,
&mut needed_variables
)?
);
Ok(())
})?;
job.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| {
write_str!(
output,
"{}",
EscapeForUnixMakefile::new(
arg,
UnixMakefileEscapeKind::RecipeWithShellEscaping,
&mut needed_variables
)?
);
Ok(())
})?;
retval.push_str("\n\n");
}
if !phony_targets.is_empty() {
@ -610,15 +623,21 @@ impl JobGraph {
}
Ok(retval)
}
pub fn to_unix_shell_script(&self, extra_args: &[Interned<OsStr>]) -> String {
pub fn to_unix_shell_script(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
self.to_unix_shell_script_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_shell_script_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
let mut retval = String::from(
@ -630,7 +649,11 @@ impl JobGraph {
continue;
};
let Ok(()) = job
.command_params_with_internal_program_prefix(internal_program_prefix, extra_args)
.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> {
write_str!(output, "{}", EscapeForUnixShell::new(&arg));
Ok(())
@ -639,7 +662,7 @@ impl JobGraph {
}
retval
}
pub fn run(&self, params: &JobParams) -> eyre::Result<()> {
pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> {
// use scope to auto-join threads on errors
thread::scope(|scope| {
struct WaitingJobState {
@ -725,13 +748,18 @@ impl JobGraph {
job: DynJob,
inputs: Vec<JobItem>,
params: &'a JobParams,
global_params: &'a GlobalParams,
acquired_job: AcquiredJob,
finished_jobs_sender: mpsc::Sender<<JobGraphInner as GraphBase>::NodeId>,
}
impl RunningJobInThread<'_> {
fn run(mut self) -> eyre::Result<Vec<JobItem>> {
self.job
.run(&self.inputs, self.params, &mut self.acquired_job)
self.job.run(
&self.inputs,
self.params,
self.global_params,
&mut self.acquired_job,
)
}
}
impl Drop for RunningJobInThread<'_> {
@ -749,6 +777,7 @@ impl JobGraph {
})
}))?,
params,
global_params,
acquired_job: AcquiredJob::acquire()?,
finished_jobs_sender: finished_jobs_sender.clone(),
};

View file

@ -4,10 +4,9 @@
use crate::{
build::{DynJobKind, JobKind, built_in_job_kinds},
intern::Interned,
util::InternedStrCompareAsStr,
};
use std::{
borrow::Borrow,
cmp::Ordering,
collections::BTreeMap,
fmt,
sync::{Arc, OnceLock, RwLock, RwLockWriteGuard},
@ -23,33 +22,6 @@ impl DynJobKind {
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
struct InternedStrCompareAsStr(Interned<str>);
impl fmt::Debug for InternedStrCompareAsStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Ord for InternedStrCompareAsStr {
fn cmp(&self, other: &Self) -> Ordering {
str::cmp(&self.0, &other.0)
}
}
impl PartialOrd for InternedStrCompareAsStr {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Borrow<str> for InternedStrCompareAsStr {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug)]
struct JobKindRegistry {
job_kinds: BTreeMap<InternedStrCompareAsStr, DynJobKind>,

View file

@ -4,8 +4,8 @@
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem,
JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
@ -152,11 +152,12 @@ impl ExternalCommand for UnadjustedVerilog {
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let UnadjustedVerilogArgs {
firtool_extra_args,
verilog_dialect,
@ -316,8 +317,9 @@ impl JobKind for VerilogJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let VerilogJobArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(VerilogJob {
@ -364,6 +366,7 @@ impl JobKind for VerilogJobKind {
job: &Self::Job,
inputs: &[JobItem],
_params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));

View file

@ -8,24 +8,30 @@ use crate::{
module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::HashMap,
util::{HashMap, HashSet, InternedStrCompareAsStr},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::{
any::{Any, TypeId},
borrow::Cow,
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
convert::Infallible,
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
marker::PhantomData,
mem,
sync::{Arc, Mutex, MutexGuard, OnceLock},
sync::{Arc, Mutex, MutexGuard, OnceLock, RwLock, RwLockWriteGuard},
};
pub mod peripherals;
trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug {
fn as_any(&self) -> &dyn Any;
fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
fn name_dyn(&self) -> Interned<str>;
fn new_peripherals_dyn<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -33,6 +39,7 @@ trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug {
fn source_location_dyn(&self) -> SourceLocation;
#[track_caller]
fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals);
fn aspects_dyn(&self) -> PlatformAspectSet;
}
impl<T: Platform> DynPlatformTrait for T {
@ -51,6 +58,10 @@ impl<T: Platform> DynPlatformTrait for T {
self.hash(&mut state);
}
fn name_dyn(&self) -> Interned<str> {
self.name()
}
fn new_peripherals_dyn<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -80,6 +91,10 @@ impl<T: Platform> DynPlatformTrait for T {
};
self.add_peripherals_in_wrapper_module(m, *peripherals)
}
fn aspects_dyn(&self) -> PlatformAspectSet {
self.aspects()
}
}
#[derive(Clone)]
@ -155,6 +170,9 @@ impl Peripherals for DynPeripherals {
impl Platform for DynPlatform {
type Peripherals = DynPeripherals;
fn name(&self) -> Interned<str> {
DynPlatformTrait::name_dyn(&*self.0)
}
fn new_peripherals<'a>(
&self,
builder_factory: PeripheralsBuilderFactory<'a>,
@ -168,6 +186,9 @@ impl Platform for DynPlatform {
fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) {
DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals);
}
fn aspects(&self) -> PlatformAspectSet {
DynPlatformTrait::aspects_dyn(&*self.0)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -448,9 +469,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> {
id,
Box::new(move |state, peripheral_ref, wire| {
on_use(
state
.as_any()
.downcast_mut()
<dyn Any>::downcast_mut::<S>(PeripheralsOnUseSharedState::as_any(state))
.expect("known to be correct type"),
PeripheralRef::from_canonical(peripheral_ref),
Expr::from_canonical(wire),
@ -518,6 +537,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> {
}
}
#[must_use]
pub struct Peripheral<T: Type> {
ty: T,
common: PeripheralCommon,
@ -528,6 +548,27 @@ impl<T: Type> Peripheral<T> {
let Self { ty, ref common } = *self;
PeripheralRef { ty, common }
}
pub fn ty(&self) -> T {
self.as_ref().ty()
}
pub fn id(&self) -> PeripheralId {
self.as_ref().id()
}
pub fn name(&self) -> Interned<str> {
self.as_ref().name()
}
pub fn is_input(&self) -> bool {
self.as_ref().is_input()
}
pub fn is_output(&self) -> bool {
self.as_ref().is_output()
}
pub fn conflicts_with(&self) -> Interned<BTreeSet<PeripheralId>> {
self.as_ref().conflicts_with()
}
pub fn availability(&self) -> PeripheralAvailability {
self.as_ref().availability()
}
pub fn is_available(&self) -> bool {
self.as_ref().is_available()
}
@ -588,7 +629,7 @@ impl<T: Type> Peripheral<T> {
impl<T: Type> fmt::Debug for Peripheral<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().debug_fmt("Peripheral", f)
self.as_ref().debug_common_fields("Peripheral", f).finish()
}
}
@ -599,7 +640,10 @@ pub struct UsedPeripheral<T: Type> {
impl<T: Type> fmt::Debug for UsedPeripheral<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().debug_fmt("UsedPeripheral", f)
self.as_ref()
.debug_common_fields("UsedPeripheral", f)
.field("instance_io_field", &self.instance_io_field())
.finish()
}
}
@ -617,6 +661,24 @@ impl<T: Type> UsedPeripheral<T> {
pub fn instance_io_field(&self) -> Expr<T> {
self.instance_io_field
}
pub fn ty(&self) -> T {
self.as_ref().ty()
}
pub fn id(&self) -> PeripheralId {
self.as_ref().id()
}
pub fn name(&self) -> Interned<str> {
self.as_ref().name()
}
pub fn is_input(&self) -> bool {
self.as_ref().is_input()
}
pub fn is_output(&self) -> bool {
self.as_ref().is_output()
}
pub fn conflicts_with(&self) -> Interned<BTreeSet<PeripheralId>> {
self.as_ref().conflicts_with()
}
}
#[derive(Copy, Clone)]
@ -627,7 +689,7 @@ pub struct PeripheralRef<'a, T: Type> {
impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug_fmt("PeripheralRef", f)
self.debug_common_fields("PeripheralRef", f).finish()
}
}
@ -662,7 +724,11 @@ impl fmt::Display for PeripheralUnavailableError {
impl std::error::Error for PeripheralUnavailableError {}
impl<'a, T: Type> PeripheralRef<'a, T> {
fn debug_fmt(&self, struct_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn debug_common_fields<'f1, 'f2>(
&self,
struct_name: &str,
f: &'f1 mut fmt::Formatter<'f2>,
) -> fmt::DebugStruct<'f1, 'f2> {
let Self {
ty,
common:
@ -673,12 +739,13 @@ impl<'a, T: Type> PeripheralRef<'a, T> {
peripherals_state: _,
},
} = self;
f.debug_struct(struct_name)
let mut retval = f.debug_struct(struct_name);
retval
.field("ty", ty)
.field("id", id)
.field("is_input", is_input)
.field("availability", &self.availability())
.finish()
.field("availability", &self.availability());
retval
}
pub fn ty(&self) -> T {
self.ty
@ -850,7 +917,7 @@ impl<'a, T: Type> PeripheralRef<'a, T> {
flipped: self.is_input(),
ty: Expr::ty(canonical_wire),
});
on_use_function(shared_state, self.canonical(), canonical_wire);
on_use_function(&mut **shared_state, self.canonical(), canonical_wire);
drop(on_use_state);
Ok(wire)
}
@ -1031,8 +1098,327 @@ impl<'a> fmt::Debug for PlatformIOBuilder<'a> {
}
}
trait PlatformAspectTrait: 'static + Send + Sync + fmt::Debug {
fn any_ref(&self) -> &dyn Any;
fn any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
}
impl<T: 'static + Send + Sync + fmt::Debug + Eq + Hash> PlatformAspectTrait for T {
fn any_ref(&self) -> &dyn Any {
self
}
fn any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool {
other
.any_ref()
.downcast_ref::<T>()
.is_some_and(|other| self == other)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
}
#[derive(Clone)]
pub struct PlatformAspect {
type_id: TypeId,
type_name: &'static str,
value: Arc<dyn PlatformAspectTrait>,
}
impl Hash for PlatformAspect {
fn hash<H: Hasher>(&self, state: &mut H) {
PlatformAspectTrait::hash_dyn(&*self.value, state);
}
}
impl Eq for PlatformAspect {}
impl PartialEq for PlatformAspect {
fn eq(&self, other: &Self) -> bool {
self.type_id == other.type_id && PlatformAspectTrait::eq_dyn(&*self.value, &*other.value)
}
}
impl fmt::Debug for PlatformAspect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
type_id: _,
type_name,
value,
} = self;
write!(f, "PlatformAspect<{type_name}>")?;
f.debug_tuple("").field(value).finish()
}
}
impl PlatformAspect {
pub fn new_arc<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(value: Arc<T>) -> Self {
Self {
type_id: TypeId::of::<T>(),
type_name: std::any::type_name::<T>(),
value,
}
}
pub fn new<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(value: T) -> Self {
Self::new_arc(Arc::new(value))
}
pub fn type_id(&self) -> TypeId {
self.type_id
}
pub fn downcast_arc<T: 'static + Send + Sync + fmt::Debug>(self) -> Result<Arc<T>, Self> {
if self.type_id == TypeId::of::<T>() {
let Ok(retval) = self.value.any_arc().downcast() else {
unreachable!();
};
Ok(retval)
} else {
Err(self)
}
}
pub fn downcast_unwrap_or_clone<T: 'static + Send + Sync + fmt::Debug + Clone>(
self,
) -> Result<T, Self> {
Ok(Arc::unwrap_or_clone(self.downcast_arc()?))
}
pub fn downcast_ref<T: 'static + Send + Sync + fmt::Debug>(&self) -> Option<&T> {
PlatformAspectTrait::any_ref(&*self.value).downcast_ref()
}
}
#[derive(Clone, Default)]
pub struct PlatformAspectSet {
aspects_by_type_id: Arc<HashMap<TypeId, HashSet<PlatformAspect>>>,
aspects: Arc<Vec<PlatformAspect>>,
}
impl PlatformAspectSet {
pub fn new() -> Self {
Self::default()
}
pub fn insert_new<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&mut self,
value: T,
) -> bool {
self.insert(PlatformAspect::new(value))
}
pub fn insert_new_arc<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&mut self,
value: Arc<T>,
) -> bool {
self.insert(PlatformAspect::new_arc(value))
}
fn insert_inner(
aspects_by_type_id: &mut HashMap<TypeId, HashSet<PlatformAspect>>,
aspects: &mut Vec<PlatformAspect>,
value: PlatformAspect,
) -> bool {
if aspects_by_type_id
.entry(value.type_id)
.or_default()
.insert(value.clone())
{
aspects.push(value);
true
} else {
false
}
}
pub fn insert(&mut self, value: PlatformAspect) -> bool {
Self::insert_inner(
Arc::make_mut(&mut self.aspects_by_type_id),
Arc::make_mut(&mut self.aspects),
value,
)
}
pub fn contains(&self, value: &PlatformAspect) -> bool {
self.aspects_by_type_id
.get(&value.type_id)
.is_some_and(|aspects| aspects.contains(value))
}
pub fn get_aspects_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = &'a PlatformAspect> + FusedIterator + ExactSizeIterator + 'a
{
self.aspects_by_type_id
.get(&TypeId::of::<T>())
.map(|aspects| aspects.iter())
.unwrap_or_default()
}
pub fn get_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = &'a T> + FusedIterator + ExactSizeIterator + 'a {
self.get_aspects_by_type::<T>()
.map(|aspect| aspect.downcast_ref().expect("already checked type"))
}
pub fn get_single_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> Option<&'a T> {
let mut aspects = self.get_by_type::<T>();
if aspects.len() == 1 {
aspects.next()
} else {
None
}
}
pub fn get_arcs_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = Arc<T>> + FusedIterator + ExactSizeIterator + 'a {
self.get_aspects_by_type::<T>().map(|aspect| {
aspect
.clone()
.downcast_arc()
.ok()
.expect("already checked type")
})
}
}
impl<'a> Extend<&'a PlatformAspect> for PlatformAspectSet {
fn extend<T: IntoIterator<Item = &'a PlatformAspect>>(&mut self, iter: T) {
self.extend(iter.into_iter().cloned());
}
}
impl Extend<PlatformAspect> for PlatformAspectSet {
fn extend<T: IntoIterator<Item = PlatformAspect>>(&mut self, iter: T) {
let Self {
aspects_by_type_id,
aspects,
} = self;
let aspects_by_type_id = Arc::make_mut(aspects_by_type_id);
let aspects = Arc::make_mut(aspects);
iter.into_iter().for_each(|value| {
Self::insert_inner(aspects_by_type_id, aspects, value);
});
}
}
impl<'a> FromIterator<&'a PlatformAspect> for PlatformAspectSet {
fn from_iter<T: IntoIterator<Item = &'a PlatformAspect>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
impl FromIterator<PlatformAspect> for PlatformAspectSet {
fn from_iter<T: IntoIterator<Item = PlatformAspect>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
impl std::ops::Deref for PlatformAspectSet {
type Target = [PlatformAspect];
fn deref(&self) -> &Self::Target {
&self.aspects
}
}
impl fmt::Debug for PlatformAspectSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self).finish()
}
}
impl IntoIterator for PlatformAspectSet {
type Item = PlatformAspect;
type IntoIter = PlatformAspectsIntoIter;
fn into_iter(self) -> Self::IntoIter {
PlatformAspectsIntoIter {
indexes: 0..self.aspects.len(),
aspects: self.aspects,
}
}
}
impl<'a> IntoIterator for &'a PlatformAspectSet {
type Item = &'a PlatformAspect;
type IntoIter = std::slice::Iter<'a, PlatformAspect>;
fn into_iter(self) -> Self::IntoIter {
self.aspects.iter()
}
}
#[derive(Clone, Debug)]
pub struct PlatformAspectsIntoIter {
aspects: Arc<Vec<PlatformAspect>>,
indexes: std::ops::Range<usize>,
}
impl Iterator for PlatformAspectsIntoIter {
type Item = PlatformAspect;
fn next(&mut self) -> Option<Self::Item> {
self.indexes.next().map(|index| self.aspects[index].clone())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
fn count(self) -> usize {
self.indexes.len()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth(n).map(|index| self.aspects[index].clone())
}
fn fold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes
.fold(init, |v, index| f(v, self.aspects[index].clone()))
}
}
impl FusedIterator for PlatformAspectsIntoIter {}
impl ExactSizeIterator for PlatformAspectsIntoIter {}
impl DoubleEndedIterator for PlatformAspectsIntoIter {
fn next_back(&mut self) -> Option<Self::Item> {
self.indexes
.next_back()
.map(|index| self.aspects[index].clone())
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.indexes
.nth_back(n)
.map(|index| self.aspects[index].clone())
}
fn rfold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes
.rfold(init, |v, index| f(v, self.aspects[index].clone()))
}
}
pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
type Peripherals: Peripherals;
fn name(&self) -> Interned<str>;
fn new_peripherals<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -1113,7 +1499,7 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
crate::module::ModuleKind::Normal,
|m| {
let instance =
instance_with_loc("main", main_module.intern(), SourceLocation::caller());
instance_with_loc("main", main_module.intern(), self.source_location());
let output_expr = Expr::field(instance, &output_module_io.bundle_field().name);
let mut state = peripherals_state.state.lock().expect("shouldn't be poison");
let PeripheralsStateEnum::BuildingWrapperModule(
@ -1143,4 +1529,395 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
self.try_wrap_main_module(|p| Ok(make_main_module(p)))
.unwrap_or_else(|e: Infallible| match e {})
}
fn aspects(&self) -> PlatformAspectSet;
}
impl DynPlatform {
pub fn registry() -> PlatformRegistrySnapshot {
PlatformRegistrySnapshot(PlatformRegistry::get())
}
#[track_caller]
pub fn register(self) {
PlatformRegistry::register(PlatformRegistry::lock(), self);
}
}
#[derive(Clone, Debug)]
struct PlatformRegistry {
platforms: BTreeMap<InternedStrCompareAsStr, DynPlatform>,
}
enum PlatformRegisterError {
SameName {
name: InternedStrCompareAsStr,
old_platform: DynPlatform,
new_platform: DynPlatform,
},
}
impl fmt::Display for PlatformRegisterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SameName {
name,
old_platform,
new_platform,
} => write!(
f,
"two different `Platform` can't share the same name:\n\
{name:?}\n\
old platform:\n\
{old_platform:?}\n\
new platform:\n\
{new_platform:?}",
),
}
}
}
trait PlatformRegistryRegisterLock {
type Locked;
fn lock(self) -> Self::Locked;
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry;
}
impl PlatformRegistryRegisterLock for &'static RwLock<Arc<PlatformRegistry>> {
type Locked = RwLockWriteGuard<'static, Arc<PlatformRegistry>>;
fn lock(self) -> Self::Locked {
self.write().expect("shouldn't be poisoned")
}
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry {
Arc::make_mut(locked)
}
}
impl PlatformRegistryRegisterLock for &'_ mut PlatformRegistry {
type Locked = Self;
fn lock(self) -> Self::Locked {
self
}
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry {
locked
}
}
impl PlatformRegistry {
fn lock() -> &'static RwLock<Arc<Self>> {
static REGISTRY: OnceLock<RwLock<Arc<PlatformRegistry>>> = OnceLock::new();
REGISTRY.get_or_init(Default::default)
}
fn try_register<L: PlatformRegistryRegisterLock>(
lock: L,
platform: DynPlatform,
) -> Result<(), PlatformRegisterError> {
use std::collections::btree_map::Entry;
let name = InternedStrCompareAsStr(platform.name());
// run user code only outside of lock
let mut locked = lock.lock();
let this = L::make_mut(&mut locked);
let result = match this.platforms.entry(name) {
Entry::Occupied(entry) => Err(PlatformRegisterError::SameName {
name,
old_platform: entry.get().clone(),
new_platform: platform,
}),
Entry::Vacant(entry) => {
entry.insert(platform);
Ok(())
}
};
drop(locked);
// outside of lock now, so we can test if it's the same DynPlatform
match result {
Err(PlatformRegisterError::SameName {
name: _,
old_platform,
new_platform,
}) if old_platform == new_platform => Ok(()),
result => result,
}
}
#[track_caller]
fn register<L: PlatformRegistryRegisterLock>(lock: L, platform: DynPlatform) {
match Self::try_register(lock, platform) {
Err(e) => panic!("{e}"),
Ok(()) => {}
}
}
fn get() -> Arc<Self> {
Self::lock().read().expect("shouldn't be poisoned").clone()
}
}
impl Default for PlatformRegistry {
fn default() -> Self {
let mut retval = Self {
platforms: BTreeMap::new(),
};
for platform in built_in_platforms() {
Self::register(&mut retval, platform);
}
retval
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistrySnapshot(Arc<PlatformRegistry>);
impl PlatformRegistrySnapshot {
pub fn get() -> Self {
PlatformRegistrySnapshot(PlatformRegistry::get())
}
pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynPlatform> {
self.0.platforms.get(name)
}
pub fn iter_with_names(&self) -> PlatformRegistryIterWithNames<'_> {
PlatformRegistryIterWithNames(self.0.platforms.iter())
}
pub fn iter(&self) -> PlatformRegistryIter<'_> {
PlatformRegistryIter(self.0.platforms.values())
}
}
impl<'a> IntoIterator for &'a PlatformRegistrySnapshot {
type Item = &'a DynPlatform;
type IntoIter = PlatformRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut PlatformRegistrySnapshot {
type Item = &'a DynPlatform;
type IntoIter = PlatformRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistryIter<'a>(
std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynPlatform>,
);
impl<'a> Iterator for PlatformRegistryIter<'a> {
type Item = &'a DynPlatform;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n)
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for PlatformRegistryIter<'a> {}
impl<'a> ExactSizeIterator for PlatformRegistryIter<'a> {}
impl<'a> DoubleEndedIterator for PlatformRegistryIter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth_back(n)
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.rfold(init, f)
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistryIterWithNames<'a>(
std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynPlatform>,
);
impl<'a> Iterator for PlatformRegistryIterWithNames<'a> {
type Item = (Interned<str>, &'a DynPlatform);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(name, platform)| (name.0, platform))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last().map(|(name, platform)| (name.0, platform))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n).map(|(name, platform)| (name.0, platform))
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, platform)| (name.0, platform))
.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for PlatformRegistryIterWithNames<'a> {}
impl<'a> ExactSizeIterator for PlatformRegistryIterWithNames<'a> {}
impl<'a> DoubleEndedIterator for PlatformRegistryIterWithNames<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.next_back()
.map(|(name, platform)| (name.0, platform))
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0
.nth_back(n)
.map(|(name, platform)| (name.0, platform))
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, platform)| (name.0, platform))
.rfold(init, f)
}
}
#[track_caller]
pub fn register_platform<K: Platform>(kind: K) {
DynPlatform::new(kind).register();
}
impl Serialize for DynPlatform {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.name().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DynPlatform {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name = Cow::<str>::deserialize(deserializer)?;
match Self::registry().get_by_name(&name) {
Some(retval) => Ok(retval.clone()),
None => Err(D::Error::custom(format_args!(
"unknown platform: name not found in registry: {name:?}"
))),
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct DynPlatformValueParser;
#[derive(Clone, PartialEq, Eq, Hash)]
struct DynPlatformValueEnum {
name: Interned<str>,
platform: DynPlatform,
}
impl clap::ValueEnum for DynPlatformValueEnum {
fn value_variants<'a>() -> &'a [Self] {
Interned::into_inner(
PlatformRegistrySnapshot::get()
.iter_with_names()
.map(|(name, platform)| Self {
name,
platform: platform.clone(),
})
.collect(),
)
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(clap::builder::PossibleValue::new(Interned::into_inner(
self.name,
)))
}
}
impl clap::builder::TypedValueParser for DynPlatformValueParser {
type Value = DynPlatform;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> clap::error::Result<Self::Value> {
clap::builder::EnumValueParser::<DynPlatformValueEnum>::new()
.parse_ref(cmd, arg, value)
.map(|v| v.platform)
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
static ENUM_VALUE_PARSER: OnceLock<clap::builder::EnumValueParser<DynPlatformValueEnum>> =
OnceLock::new();
ENUM_VALUE_PARSER
.get_or_init(clap::builder::EnumValueParser::<DynPlatformValueEnum>::new)
.possible_values()
}
}
impl clap::builder::ValueParserFactory for DynPlatform {
type Parser = DynPlatformValueParser;
fn value_parser() -> Self::Parser {
DynPlatformValueParser::default()
}
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = DynPlatform> {
crate::vendor::built_in_platforms()
}

View file

@ -0,0 +1,52 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{intern::Intern, prelude::*};
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClockInputProperties {
pub frequency: NotNan<f64>,
}
#[hdl(no_runtime_generics, no_static)]
pub struct ClockInput {
pub clk: Clock,
pub properties: PhantomConst<ClockInputProperties>,
}
impl ClockInput {
#[track_caller]
pub fn new(frequency: f64) -> Self {
assert!(
frequency > 0.0 && frequency.is_finite(),
"invalid clock frequency: {frequency}"
);
Self {
clk: Clock,
properties: PhantomConst::new(
ClockInputProperties {
frequency: NotNan::new(frequency).expect("just checked"),
}
.intern_sized(),
),
}
}
pub fn frequency(self) -> f64 {
self.properties.get().frequency.into_inner()
}
}
#[hdl]
pub struct Led {
pub on: Bool,
}
#[hdl]
pub struct RgbLed {
pub r: Bool,
pub g: Bool,
pub b: Bool,
}

View file

@ -28,6 +28,7 @@ pub use crate::{
memory, memory_array, memory_with_init, reg_builder, wire,
},
phantom_const::PhantomConst,
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
sim::{

View file

@ -2,8 +2,8 @@
// See Notices.txt for copyright information
use crate::{
build::{
BaseJobArgs, BaseJobKind, JobArgsAndDependencies, JobKindAndArgs, JobParams, NoArgs,
RunBuild,
BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams,
NoArgs, RunBuild,
external::{ExternalCommandArgs, ExternalCommandJobKind},
firrtl::{FirrtlArgs, FirrtlJobKind},
formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind},
@ -106,7 +106,7 @@ fn make_assert_formal_args(
) -> eyre::Result<JobArgsAndDependencies<ExternalCommandJobKind<Formal>>> {
let args = JobKindAndArgs {
kind: BaseJobKind,
args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name)),
args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None),
};
let dependencies = JobArgsAndDependencies {
args,
@ -168,9 +168,9 @@ pub fn try_assert_formal<M: AsRef<Module<T>>, T: BundleType>(
solver,
export_options,
)?
.run(
|NoArgs {}| Ok(JobParams::new(module, APP_NAME)),
clap::Command::new(APP_NAME), // not actually used, so we can use an arbitrary value
.run_without_platform(
|NoArgs {}| Ok(JobParams::new(module)),
&GlobalParams::new(None, APP_NAME),
)
}

View file

@ -33,7 +33,6 @@ pub use const_cmp::{
#[doc(inline)]
pub use scoped_ref::ScopedRef;
pub(crate) use misc::chain;
#[doc(inline)]
pub use misc::{
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter,
@ -42,6 +41,7 @@ pub use misc::{
os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty,
serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range,
};
pub(crate) use misc::{InternedStrCompareAsStr, chain};
pub mod job_server;
pub mod prefix_sum;

View file

@ -585,3 +585,30 @@ pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef<str>) -> Op
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
})
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned<str>);
impl fmt::Debug for InternedStrCompareAsStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Ord for InternedStrCompareAsStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
str::cmp(&self.0, &other.0)
}
}
impl PartialOrd for InternedStrCompareAsStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::borrow::Borrow<str> for InternedStrCompareAsStr {
fn borrow(&self) -> &str {
&self.0
}
}

View file

@ -6,3 +6,7 @@ pub mod xilinx;
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
xilinx::built_in_job_kinds()
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
xilinx::built_in_platforms()
}

View file

@ -1,8 +1,18 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{annotations::make_annotation_enum, intern::Interned};
use crate::{
annotations::make_annotation_enum,
build::{GlobalParams, ToArgs, WriteArgs},
intern::Interned,
prelude::{DynPlatform, Platform},
};
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::fmt;
pub mod arty_a7;
pub mod primitives;
pub mod yosys_nextpnr_prjxray;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -23,6 +33,167 @@ make_annotation_enum! {
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
yosys_nextpnr_prjxray::built_in_job_kinds()
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct XilinxArgs {
#[arg(long)]
pub device: Option<Device>,
}
impl XilinxArgs {
pub fn require_device(
&self,
platform: Option<&DynPlatform>,
global_params: &GlobalParams,
) -> clap::error::Result<Device> {
if let Some(device) = self.device {
return Ok(device);
}
if let Some(device) =
platform.and_then(|platform| platform.aspects().get_single_by_type::<Device>().copied())
{
return Ok(device);
}
Err(global_params.clap_error(
clap::error::ErrorKind::MissingRequiredArgument,
"missing --device option",
))
}
}
impl ToArgs for XilinxArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
if let Some(device) = self.device {
args.write_long_option_eq("device", device.as_str());
}
}
}
macro_rules! make_device_enum {
($vis:vis enum $Device:ident {
$(
#[
name = $name:literal,
xray_part = $xray_part:literal,
xray_device = $xray_device:literal,
xray_family = $xray_family:literal,
]
$variant:ident,
)*
}) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
$vis enum $Device {
$(
#[value(name = $name, alias = $xray_part)]
$variant,
)*
}
impl $Device {
$vis fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $name,)*
}
}
$vis fn xray_part(self) -> &'static str {
match self {
$(Self::$variant => $xray_part,)*
}
}
$vis fn xray_device(self) -> &'static str {
match self {
$(Self::$variant => $xray_device,)*
}
}
$vis fn xray_family(self) -> &'static str {
match self {
$(Self::$variant => $xray_family,)*
}
}
}
struct DeviceVisitor;
impl<'de> serde::de::Visitor<'de> for DeviceVisitor {
type Value = $Device;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Xilinx device string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match $Device::from_str(v, false) {
Ok(v) => Ok(v),
Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)),
}
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) {
Some(v) => Ok(v),
None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)),
}
}
}
impl<'de> Deserialize<'de> for $Device {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(DeviceVisitor)
}
}
impl Serialize for $Device {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
arty_a7::built_in_job_kinds()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_job_kinds())
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
arty_a7::built_in_platforms()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_platforms())
}

View file

@ -0,0 +1,301 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::sync::OnceLock;
use crate::{
intern::{Intern, Interned},
module::{instance_with_loc, reg_builder_with_loc, wire_with_loc},
platform::{
DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory,
PeripheralsBuilderFinished, Platform, PlatformAspectSet,
peripherals::{ClockInput, Led, RgbLed},
},
prelude::*,
vendor::xilinx::{
Device, XdcIOStandardAnnotation, XdcLocationAnnotation,
primitives::{self, BUFGCE, STARTUPE2_default_inputs},
},
};
macro_rules! arty_a7_platform {
(
$vis:vis enum $ArtyA7Platform:ident {
$(#[name = $name:literal, device = $device:ident]
$Variant:ident,)*
}
) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
$vis enum $ArtyA7Platform {
$($Variant,)*
}
impl $ArtyA7Platform {
$vis const VARIANTS: &'static [Self] = &[$(Self::$Variant,)*];
$vis fn device(self) -> Device {
match self {
$(Self::$Variant => Device::$device,)*
}
}
$vis const fn as_str(self) -> &'static str {
match self {
$(Self::$Variant => $name,)*
}
}
fn get_aspects(self) -> &'static PlatformAspectSet {
match self {
$(Self::$Variant => {
static ASPECTS_SET: OnceLock<PlatformAspectSet> = OnceLock::new();
ASPECTS_SET.get_or_init(|| self.make_aspects())
})*
}
}
}
};
}
arty_a7_platform! {
pub enum ArtyA7Platform {
#[name = "arty-a7-35t", device = Xc7a35ticsg324_1l]
ArtyA7_35T,
#[name = "arty-a7-100t", device = Xc7a100ticsg324_1l]
ArtyA7_100T,
}
}
#[derive(Debug)]
pub struct ArtyA7Peripherals {
clk100: Peripheral<ClockInput>,
rst: Peripheral<Reset>,
rst_sync: Peripheral<SyncReset>,
ld0: Peripheral<RgbLed>,
ld1: Peripheral<RgbLed>,
ld2: Peripheral<RgbLed>,
ld3: Peripheral<RgbLed>,
ld4: Peripheral<Led>,
ld5: Peripheral<Led>,
ld6: Peripheral<Led>,
ld7: Peripheral<Led>,
// TODO: add rest of peripherals when we need them
}
impl Peripherals for ArtyA7Peripherals {
fn append_peripherals<'a>(&'a self, peripherals: &mut Vec<PeripheralRef<'a, CanonicalType>>) {
let Self {
clk100,
rst,
rst_sync,
ld0,
ld1,
ld2,
ld3,
ld4,
ld5,
ld6,
ld7,
} = self;
clk100.append_peripherals(peripherals);
rst.append_peripherals(peripherals);
rst_sync.append_peripherals(peripherals);
ld0.append_peripherals(peripherals);
ld1.append_peripherals(peripherals);
ld2.append_peripherals(peripherals);
ld3.append_peripherals(peripherals);
ld4.append_peripherals(peripherals);
ld5.append_peripherals(peripherals);
ld6.append_peripherals(peripherals);
ld7.append_peripherals(peripherals);
}
}
impl ArtyA7Platform {
fn make_aspects(self) -> PlatformAspectSet {
let mut retval = PlatformAspectSet::new();
retval.insert_new(self.device());
retval
}
}
impl Platform for ArtyA7Platform {
type Peripherals = ArtyA7Peripherals;
fn name(&self) -> Interned<str> {
self.as_str().intern()
}
fn new_peripherals<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) {
let mut builder = builder_factory.builder();
(
ArtyA7Peripherals {
clk100: builder.input_peripheral("clk100", ClockInput::new(100e6)),
rst: builder.input_peripheral("rst", Reset),
rst_sync: builder.input_peripheral("rst_sync", SyncReset),
ld0: builder.output_peripheral("ld0", RgbLed),
ld1: builder.output_peripheral("ld1", RgbLed),
ld2: builder.output_peripheral("ld2", RgbLed),
ld3: builder.output_peripheral("ld3", RgbLed),
ld4: builder.output_peripheral("ld4", Led),
ld5: builder.output_peripheral("ld5", Led),
ld6: builder.output_peripheral("ld6", Led),
ld7: builder.output_peripheral("ld7", Led),
},
builder.finish(),
)
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) {
let ArtyA7Peripherals {
clk100,
rst,
rst_sync,
ld0,
ld1,
ld2,
ld3,
ld4,
ld5,
ld6,
ld7,
} = peripherals;
let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| {
let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool);
annotate(
pin,
XdcLocationAnnotation {
location: location.intern(),
},
);
annotate(
pin,
XdcIOStandardAnnotation {
value: io_standard.intern(),
},
);
let buf = instance_with_loc(
&format!("{name}_buf"),
primitives::IBUF(),
SourceLocation::builtin(),
);
connect(buf.I, pin);
if invert { !buf.O } else { buf.O }
};
let make_buffered_output = |name: &str, location: &str, io_standard: &str| {
let pin = m.output_with_loc(name, SourceLocation::builtin(), Bool);
annotate(
pin,
XdcLocationAnnotation {
location: location.intern(),
},
);
annotate(
pin,
XdcIOStandardAnnotation {
value: io_standard.intern(),
},
);
let buf = instance_with_loc(
&format!("{name}_buf"),
primitives::OBUFT(),
SourceLocation::builtin(),
);
connect(pin, buf.O);
connect(buf.T, false);
buf.I
};
let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false);
let startup = instance_with_loc(
"startup",
STARTUPE2_default_inputs(),
SourceLocation::builtin(),
);
let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin());
connect(clk100_sync.CE, startup.EOS);
connect(clk100_sync.I, clk100_buf);
if let Some(clk100) = clk100.into_used() {
connect(clk100.instance_io_field().clk, clk100_sync.O);
}
let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true);
let rst_sync_cd = wire_with_loc(
"rst_sync_cd",
SourceLocation::builtin(),
ClockDomain[AsyncReset],
);
connect(rst_sync_cd.clk, clk100_sync.O);
connect(rst_sync_cd.rst, rst_buf.to_async_reset());
let [rst_sync_0, rst_sync_1] = std::array::from_fn(|index| {
let rst_sync =
reg_builder_with_loc(&format!("rst_sync_{index}"), SourceLocation::builtin())
.clock_domain(rst_sync_cd)
.reset(true)
.build();
annotate(
rst_sync,
SVAttributeAnnotation {
text: "ASYNC_REG = \"TRUE\"".intern(),
},
);
annotate(rst_sync, DontTouchAnnotation);
rst_sync
});
connect(rst_sync_0, false);
connect(rst_sync_1, rst_sync_0);
let rst_value = rst_sync_1.to_sync_reset();
if let Some(rst) = rst.into_used() {
connect(rst.instance_io_field(), rst_value.to_reset());
}
if let Some(rst_sync) = rst_sync.into_used() {
connect(rst_sync.instance_io_field(), rst_value);
}
let rgb_leds = [
(ld0, ("G6", "F6", "E1")),
(ld1, ("G3", "J4", "G4")),
(ld2, ("J3", "J2", "H4")),
(ld3, ("K1", "H6", "K2")),
];
for (rgb_led, (r_loc, g_loc, b_loc)) in rgb_leds {
let r = make_buffered_output(&format!("{}_r", rgb_led.name()), r_loc, "LVCMOS33");
let g = make_buffered_output(&format!("{}_g", rgb_led.name()), g_loc, "LVCMOS33");
let b = make_buffered_output(&format!("{}_b", rgb_led.name()), b_loc, "LVCMOS33");
if let Some(rgb_led) = rgb_led.into_used() {
connect(r, rgb_led.instance_io_field().r);
connect(g, rgb_led.instance_io_field().g);
connect(b, rgb_led.instance_io_field().b);
} else {
connect(r, false);
connect(g, false);
connect(b, false);
}
}
let leds = [(ld4, "H5"), (ld5, "J5"), (ld6, "T9"), (ld7, "T10")];
for (led, loc) in leds {
let o = make_buffered_output(&led.name(), loc, "LVCMOS33");
if let Some(led) = led.into_used() {
connect(o, led.instance_io_field().on);
} else {
connect(o, false);
}
}
}
fn aspects(&self) -> PlatformAspectSet {
self.get_aspects().clone()
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
[]
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = DynPlatform> {
ArtyA7Platform::VARIANTS
.iter()
.map(|&v| DynPlatform::new(v))
}

View file

@ -0,0 +1,50 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#![allow(non_snake_case)]
use crate::prelude::*;
#[hdl_module(extern)]
pub fn IBUF() {
m.verilog_name("IBUF");
#[hdl]
let O: Bool = m.output();
#[hdl]
let I: Bool = m.input();
}
#[hdl_module(extern)]
pub fn OBUFT() {
m.verilog_name("OBUFT");
#[hdl]
let O: Bool = m.output();
#[hdl]
let I: Bool = m.input();
#[hdl]
let T: Bool = m.input();
}
#[hdl_module(extern)]
pub fn BUFGCE() {
m.verilog_name("BUFGCE");
#[hdl]
let O: Clock = m.output();
#[hdl]
let CE: Bool = m.input();
#[hdl]
let I: Bool = m.input();
}
#[hdl_module(extern)]
pub fn STARTUPE2_default_inputs() {
m.verilog_name("STARTUPE2");
#[hdl]
let CFGCLK: Clock = m.output();
#[hdl]
let CFGMCLK: Clock = m.output();
#[hdl]
let EOS: Bool = m.output();
#[hdl]
let PREQ: Bool = m.output();
}

View file

@ -4,8 +4,8 @@
use crate::{
annotations::Annotation,
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
@ -18,9 +18,10 @@ use crate::{
module::{Module, NameId},
prelude::JobParams,
util::job_server::AcquiredJob,
vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation},
vendor::xilinx::{
Device, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, XilinxArgs,
},
};
use clap::ValueEnum;
use eyre::Context;
use serde::{Deserialize, Serialize};
use std::{
@ -105,6 +106,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.dependencies
.dependencies
@ -113,7 +115,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
.additional_args
.verilog_dialect
.get_or_insert(VerilogDialect::Yosys);
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let YosysNextpnrXrayWriteYsFileArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
let verilog_job = dependencies.get_job::<VerilogJob, _>();
@ -153,6 +155,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
@ -260,11 +263,12 @@ impl ExternalCommand for YosysNextpnrXraySynth {
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXraySynthArgs {} = args.additional_args;
Ok(Self {
write_ys_file: dependencies.job.job.clone(),
@ -350,6 +354,9 @@ impl From<fmt::Error> for WriteXdcContentsError {
fn tcl_escape(s: impl AsRef<str>) -> String {
let s = s.as_ref();
if !s.contains(|ch: char| !ch.is_alphanumeric() && ch != '_') {
return s.into();
}
let mut retval = String::with_capacity(s.len().saturating_add(2));
retval.push('"');
for ch in s.chars() {
@ -429,6 +436,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let firrtl_export_options = args
.dependencies
@ -439,7 +447,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
.args
.args
.export_options;
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let YosysNextpnrXrayWriteXdcFileArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(YosysNextpnrXrayWriteXdcFile {
@ -474,23 +482,13 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_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,
r"# autogenerated
set_property LOC G6 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports led]
set_property LOC E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property LOC C2 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports rst]
",
)?;
std::fs::write(job.xdc_file, xdc)?;
Ok(vec![JobItem::Path { path: job.xdc_file }])
}
@ -508,130 +506,12 @@ impl ExternalProgramTrait for NextpnrXilinx {
}
}
macro_rules! make_device_enum {
($vis:vis enum $Device:ident {
$(
#[
name = $name:literal,
xray_part = $xray_part:literal,
xray_device = $xray_device:literal,
xray_family = $xray_family:literal,
]
$variant:ident,
)*
}) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
$vis enum $Device {
$(
#[value(name = $name, alias = $xray_part)]
$variant,
)*
}
impl $Device {
$vis fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $name,)*
}
}
$vis fn xray_part(self) -> &'static str {
match self {
$(Self::$variant => $xray_part,)*
}
}
$vis fn xray_device(self) -> &'static str {
match self {
$(Self::$variant => $xray_device,)*
}
}
$vis fn xray_family(self) -> &'static str {
match self {
$(Self::$variant => $xray_family,)*
}
}
}
struct DeviceVisitor;
impl<'de> serde::de::Visitor<'de> for DeviceVisitor {
type Value = $Device;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Xilinx device string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match $Device::from_str(v, false) {
Ok(v) => Ok(v),
Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)),
}
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) {
Some(v) => Ok(v),
None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)),
}
}
}
impl<'de> Deserialize<'de> for $Device {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(DeviceVisitor)
}
}
impl Serialize for $Device {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct YosysNextpnrXrayRunNextpnrArgs {
#[command(flatten)]
pub common: XilinxArgs,
#[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)]
pub nextpnr_xilinx_chipdb_dir: PathBuf,
#[arg(long)]
pub device: Device,
#[arg(long, default_value_t = 0)]
pub nextpnr_xilinx_seed: i32,
}
@ -639,12 +519,12 @@ pub struct YosysNextpnrXrayRunNextpnrArgs {
impl ToArgs for YosysNextpnrXrayRunNextpnrArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
common,
nextpnr_xilinx_chipdb_dir,
device,
nextpnr_xilinx_seed,
} = self;
common.to_args(args);
args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir);
args.write_long_option_eq("device", device.as_str());
args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"));
}
}
@ -690,14 +570,15 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr {
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXrayRunNextpnrArgs {
common,
nextpnr_xilinx_chipdb_dir,
device,
nextpnr_xilinx_seed,
} = args.additional_args;
let base_job = dependencies.get_job::<BaseJob, _>();
@ -707,7 +588,7 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr {
let fasm_file = base_job.file_with_ext("fasm");
Ok(Self {
nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(),
device,
device: common.require_device(base_job.platform(), global_params)?,
nextpnr_xilinx_seed,
xdc_file: write_xdc_file.xdc_file,
xdc_file_name: write_xdc_file
@ -842,11 +723,12 @@ impl ExternalCommand for YosysNextpnrXray {
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args;
let base_job = dependencies.get_job::<BaseJob, _>();
let frames_file = base_job.file_with_ext("frames");
@ -918,3 +800,7 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrXray>::new()),
]
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
[]
}