diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 1b9910e..13e9843 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index f4b564a..be5f3bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index b905f73..2380ea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 2403ff5..fdf1c87 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -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 diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 8682a33..75799fd 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -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::()[0].use_peripheral(); + let rst = platform_io_builder.peripherals_with_type::()[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::() { + if let Ok(led) = led.try_use_peripheral() { + connect(led.on, output_reg); + } } + for rgb_led in platform_io_builder.peripherals_with_type::() { + 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")) + ::main("blinky", |_, platform, _| { + Ok(JobParams::new(platform.wrap_main_module(blinky))) }); } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index ccf81d1..a9e9635 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -6,6 +6,7 @@ use crate::{ bundle::{Bundle, BundleType}, intern::{Intern, InternSlice, Interned}, module::Module, + platform::{DynPlatform, Platform}, util::{job_server::AcquiredJob, os_str_strip_prefix}, vendor, }; @@ -37,15 +38,12 @@ pub mod registry; pub mod verilog; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - [ - DynJobKind::new(BaseJobKind), - DynJobKind::new(CreateOutputDirJobKind), - ] - .into_iter() - .chain(firrtl::built_in_job_kinds()) - .chain(formal::built_in_job_kinds()) - .chain(vendor::built_in_job_kinds()) - .chain(verilog::built_in_job_kinds()) + [DynJobKind::new(BaseJobKind)] + .into_iter() + .chain(firrtl::built_in_job_kinds()) + .chain(formal::built_in_job_kinds()) + .chain(vendor::built_in_job_kinds()) + .chain(verilog::built_in_job_kinds()) } #[derive(Clone, Hash, PartialEq, Eq, Debug)] @@ -318,6 +316,7 @@ impl JobKindAndArgs { self, dependencies: ::KindsAndArgs, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { K::args_to_jobs( JobArgsAndDependencies { @@ -325,6 +324,7 @@ impl JobKindAndArgs { dependencies, }, params, + global_params, ) } } @@ -410,6 +410,9 @@ impl JobAndDependencies { { GetJob::get_job(self) } + pub fn base_job(&self) -> &BaseJob { + self.job.kind.base_job(&self.job.job, &self.dependencies) + } } impl Clone for JobAndDependencies @@ -446,8 +449,17 @@ where } impl JobArgsAndDependencies { - pub fn args_to_jobs(self, params: &JobParams) -> eyre::Result> { - K::args_to_jobs(self, params) + pub fn args_to_jobs( + self, + params: &JobParams, + global_params: &GlobalParams, + ) -> eyre::Result> { + K::args_to_jobs(self, params, global_params) + } + pub fn base_job_args(&self) -> &BaseJobArgs { + self.args + .kind + .base_job_args(&self.args.args, &self.dependencies) } } @@ -455,6 +467,7 @@ impl>, D: JobKind> JobArgsAn pub fn args_to_jobs_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result> where @@ -464,7 +477,7 @@ impl>, D: JobKind> JobArgsAn args: JobKindAndArgs { kind, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params)?; + let mut dependencies = dependencies.args_to_jobs(params, global_params)?; let job = f(kind, args, &mut dependencies)?; Ok(JobAndDependencies { job: JobAndKind { kind, job }, @@ -479,6 +492,7 @@ impl>, D: pub fn args_to_jobs_external_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result<( C::AdditionalJobData, @@ -494,7 +508,7 @@ impl>, D: args: JobKindAndArgs { kind: _, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params)?; + let mut dependencies = dependencies.args_to_jobs(params, global_params)?; let additional_job_data = f(args, &mut dependencies)?; Ok((additional_job_data, dependencies)) } @@ -530,6 +544,15 @@ pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy } } +pub trait JobDependenciesHasBase: JobDependencies { + fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs; + fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob; + #[track_caller] + fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs; + #[track_caller] + fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob; +} + impl JobDependencies for JobKindAndDependencies { type KindsAndArgs = JobArgsAndDependencies; type JobsAndKinds = JobAndDependencies; @@ -569,6 +592,44 @@ impl JobDependencies for JobKindAndDependencies { } } +impl JobDependenciesHasBase for JobKindAndDependencies { + fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs { + args.base_job_args() + } + + fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob { + jobs.base_job() + } + + #[track_caller] + fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs { + let [dependencies_args @ .., args] = dependencies_args else { + panic!("wrong number of dependencies"); + }; + let Some((kind, args)) = args.downcast_ref::() else { + panic!( + "wrong type of dependency, expected {} got:\n{args:?}", + std::any::type_name::() + ) + }; + kind.base_job_args_dyn(args, dependencies_args) + } + + #[track_caller] + fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob { + let [dependencies @ .., job] = dependencies else { + panic!("wrong number of dependencies"); + }; + let Some((kind, job)) = job.downcast_ref::() else { + panic!( + "wrong type of dependency, expected {} got:\n{job:?}", + std::any::type_name::() + ) + }; + kind.base_job_dyn(job, dependencies) + } +} + macro_rules! impl_job_dependencies { (@impl $(($v:ident: $T:ident),)*) => { impl<$($T: JobDependencies),*> JobDependencies for ($($T,)*) { @@ -624,7 +685,6 @@ impl_job_dependencies! { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct JobParams { main_module: Module, - application_name: Interned, } impl AsRef for JobParams { @@ -634,24 +694,65 @@ impl AsRef for JobParams { } impl JobParams { - pub fn new_canonical(main_module: Module, application_name: Interned) -> Self { - Self { - main_module, - application_name, - } + pub fn new_canonical(main_module: Module) -> Self { + Self { main_module } } - pub fn new( - main_module: impl AsRef>, - application_name: impl AsRef, - ) -> Self { - Self::new_canonical( - main_module.as_ref().canonical(), - application_name.as_ref().intern(), - ) + pub fn new(main_module: impl AsRef>) -> Self { + Self::new_canonical(main_module.as_ref().canonical()) } pub fn main_module(&self) -> &Module { &self.main_module } +} + +#[derive(Clone, Debug)] +pub struct GlobalParams { + top_level_cmd: Option, + application_name: Interned, +} + +impl AsRef for GlobalParams { + fn as_ref(&self) -> &Self { + self + } +} + +impl GlobalParams { + pub fn new(top_level_cmd: Option, application_name: impl AsRef) -> Self { + Self { + top_level_cmd, + application_name: application_name.as_ref().intern(), + } + } + pub fn top_level_cmd(&self) -> Option<&clap::Command> { + self.top_level_cmd.as_ref() + } + pub fn into_top_level_cmd(self) -> Option { + self.top_level_cmd + } + pub fn extract_clap_error(&self, e: eyre::Report) -> eyre::Result { + let e = e.downcast::()?; + Ok(match &self.top_level_cmd { + Some(cmd) => e.with_cmd(cmd), + None => e, + }) + } + pub fn exit_if_clap_error(&self, e: eyre::Report) -> eyre::Report { + match self.extract_clap_error(e) { + Ok(e) => e.exit(), + Err(e) => e, + } + } + pub fn clap_error( + &self, + kind: clap::error::ErrorKind, + message: impl fmt::Display, + ) -> clap::Error { + match self.top_level_cmd.clone() { + Some(top_level_cmd) => top_level_cmd.clone().error(kind, message), + None => clap::Error::raw(kind, message), + } + } pub fn application_name(&self) -> Interned { self.application_name } @@ -702,7 +803,73 @@ impl CommandParams { } } -pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy { +pub trait JobKindHelper: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy { + fn base_job_args<'a>( + self, + args: &'a ::Args, + dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs + where + Self: JobKind; + fn base_job<'a>( + self, + job: &'a ::Job, + dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob + where + Self: JobKind; + #[track_caller] + fn base_job_args_dyn<'a>( + self, + args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs + where + Self: JobKind; + #[track_caller] + fn base_job_dyn<'a>( + self, + job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob + where + Self: JobKind; +} + +impl> JobKindHelper for K { + fn base_job_args<'a>( + self, + _args: &'a ::Args, + dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs { + K::Dependencies::base_job_args(dependencies) + } + fn base_job<'a>( + self, + _job: &'a ::Job, + dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob { + K::Dependencies::base_job(dependencies) + } + #[track_caller] + fn base_job_args_dyn<'a>( + self, + _args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs { + K::Dependencies::base_job_args_dyn(dependencies_args) + } + #[track_caller] + fn base_job_dyn<'a>( + self, + _job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob { + K::Dependencies::base_job_dyn(dependencies) + } +} + +pub trait JobKind: JobKindHelper { type Args: ToArgs; type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug + Serialize + DeserializeOwned; type Dependencies: JobDependencies; @@ -710,6 +877,7 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result>; fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>; fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>; @@ -720,6 +888,7 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy job: &Self::Job, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; fn subcommand_hidden(self) -> bool { @@ -752,7 +921,7 @@ trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { ) -> serde_json::Result; } -impl DynJobKindTrait for T { +impl DynJobKindTrait for K { fn as_any(&self) -> &dyn Any { self } @@ -777,15 +946,15 @@ impl DynJobKindTrait for T { } fn args_group_id_dyn(&self) -> Option { - ::group_id() + ::group_id() } fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args(cmd) + ::augment_args(cmd) } fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args_for_update(cmd) + ::augment_args_for_update(cmd) } fn from_arg_matches_dyn( @@ -794,7 +963,7 @@ impl DynJobKindTrait for T { ) -> clap::error::Result { Ok(DynJobArgs::new( *self, - ::from_arg_matches_mut(matches)?, + ::from_arg_matches_mut(matches)?, )) } @@ -822,21 +991,21 @@ impl DynJobKindTrait for T { pub struct DynJobKind(Arc); impl DynJobKind { - pub fn from_arc(job_kind: Arc) -> Self { + pub fn from_arc(job_kind: Arc) -> Self { Self(job_kind) } - pub fn new(job_kind: T) -> Self { + pub fn new(job_kind: K) -> Self { Self(Arc::new(job_kind)) } pub fn type_id(&self) -> TypeId { DynJobKindTrait::as_any(&*self.0).type_id() } - pub fn downcast(&self) -> Option { + pub fn downcast(&self) -> Option { DynJobKindTrait::as_any(&*self.0).downcast_ref().copied() } - pub fn downcast_arc(self) -> Result, Self> { - if self.downcast::().is_some() { - Ok(Arc::downcast::(self.0.as_arc_any()) + pub fn downcast_arc(self) -> Result, Self> { + if self.downcast::().is_some() { + Ok(Arc::downcast::(self.0.as_arc_any()) .ok() .expect("already checked type")) } else { @@ -1064,7 +1233,10 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { self: Arc, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)>; + #[track_caller] + fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs; } impl DynJobArgsTrait for DynJobArgsInner { @@ -1117,14 +1289,22 @@ impl DynJobArgsTrait for DynJobArgsInner { self: Arc, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { let JobAndDependencies { job, dependencies } = JobArgsAndDependencies { args: Arc::unwrap_or_clone(self).0, dependencies: K::Dependencies::from_dyn_args(dependencies_args), } - .args_to_jobs(params)?; + .args_to_jobs(params, global_params)?; Ok((job.into(), K::Dependencies::into_dyn_jobs(dependencies))) } + + #[track_caller] + fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { + self.0 + .kind + .base_job_args_dyn(&self.0.args, dependencies_args) + } } #[derive(Clone)] @@ -1179,8 +1359,13 @@ impl DynJobArgs { self, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { - DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params) + DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params, global_params) + } + #[track_caller] + pub fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { + DynJobArgsTrait::base_job_args_dyn(&*self.0, dependencies_args) } } @@ -1206,15 +1391,15 @@ impl fmt::Debug for DynJobArgs { } #[derive(PartialEq, Eq, Hash)] -struct DynJobInner { - kind: Arc, - job: T::Job, +struct DynJobInner { + kind: Arc, + job: K::Job, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, external_command_params: Option, } -impl> Clone for DynJobInner { +impl> Clone for DynJobInner { fn clone(&self) -> Self { Self { kind: self.kind.clone(), @@ -1226,7 +1411,7 @@ impl> Clone for DynJobInner { } } -impl fmt::Debug for DynJobInner { +impl fmt::Debug for DynJobInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { kind, @@ -1261,11 +1446,14 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; + #[track_caller] + fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob; } -impl DynJobTrait for DynJobInner { +impl DynJobTrait for DynJobInner { fn as_any(&self) -> &dyn Any { self } @@ -1286,7 +1474,7 @@ impl DynJobTrait for DynJobInner { } fn kind_type_id(&self) -> TypeId { - TypeId::of::() + TypeId::of::() } fn kind(&self) -> DynJobKind { @@ -1317,9 +1505,16 @@ impl DynJobTrait for DynJobInner { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - self.kind.run(&self.job, inputs, params, acquired_job) + self.kind + .run(&self.job, inputs, params, global_params, acquired_job) + } + + #[track_caller] + fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { + self.kind.base_job_dyn(&self.job, dependencies) } } @@ -1327,7 +1522,7 @@ impl DynJobTrait for DynJobInner { pub struct DynJob(Arc); impl DynJob { - pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { + pub fn from_arc(job_kind: Arc, job: K::Job) -> Self { let inputs = job_kind.inputs(&job); let outputs = job_kind.outputs(&job); let external_command_params = job_kind.external_command_params(&job); @@ -1339,7 +1534,7 @@ impl DynJob { external_command_params, })) } - pub fn new(job_kind: T, job: T::Job) -> Self { + pub fn new(job_kind: K, job: K::Job) -> Self { Self::from_arc(Arc::new(job_kind), job) } pub fn kind_type_id(&self) -> TypeId { @@ -1384,10 +1579,12 @@ impl DynJob { pub fn internal_command_params_with_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> CommandParams { let mut command_line = internal_program_prefix.to_vec(); - let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { + let command_line = match RunSingleJob::try_add_subcommand(platform, self, &mut command_line) + { Ok(()) => { command_line.extend_from_slice(extra_args); Intern::intern_owned(command_line) @@ -1400,9 +1597,14 @@ impl DynJob { } } #[track_caller] - pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn internal_command_params( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> CommandParams { self.internal_command_params_with_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } @@ -1410,18 +1612,27 @@ impl DynJob { pub fn command_params_with_internal_program_prefix( &self, internal_program_prefix: &[Interned], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> CommandParams { match self.external_command_params() { Some(v) => v, - None => self - .internal_command_params_with_program_prefix(internal_program_prefix, extra_args), + None => self.internal_command_params_with_program_prefix( + internal_program_prefix, + platform, + extra_args, + ), } } #[track_caller] - pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { + pub fn command_params( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> CommandParams { self.command_params_with_internal_program_prefix( &[program_name_for_internal_jobs()], + platform, extra_args, ) } @@ -1429,9 +1640,14 @@ impl DynJob { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - DynJobTrait::run(&*self.0, inputs, params, acquired_job) + DynJobTrait::run(&*self.0, inputs, params, global_params, acquired_job) + } + #[track_caller] + pub fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { + DynJobTrait::base_job_dyn(&*self.0, dependencies) } } @@ -1481,61 +1697,172 @@ impl<'de> Deserialize<'de> for DynJob { } pub trait RunBuild: Sized { - fn main(make_params: F) + fn main_without_platform(application_name: impl AsRef, make_params: F) where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { - match Self::try_main(make_params) { + let application_name = application_name.as_ref(); + match Self::try_main_without_platform(application_name, make_params) { Ok(()) => {} Err(e) => { + let e = GlobalParams::new(Some(Self::command()), application_name) + .exit_if_clap_error(e); eprintln!("{e:#}"); std::process::exit(1); } } } - fn try_main(make_params: F) -> eyre::Result<()> + fn try_main_without_platform( + application_name: impl AsRef, + make_params: F, + ) -> eyre::Result<()> where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { let args = Self::parse(); + let global_params = GlobalParams::new(Some(Self::command()), application_name); args.clone() - .run(|extra| make_params(args, extra), Self::command()) + .run_without_platform(|extra| make_params(args, extra), &global_params) + .map_err(|e| global_params.exit_if_clap_error(e)) } - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result; + fn get_platform(&self) -> Option<&DynPlatform>; + fn main(application_name: impl AsRef, make_params: F) + where + Self: clap::Parser + Clone, + F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, + { + let application_name = application_name.as_ref(); + match Self::try_main(application_name, make_params) { + Ok(()) => {} + Err(e) => { + let e = GlobalParams::new(Some(Self::command()), application_name) + .exit_if_clap_error(e); + eprintln!("{e:#}"); + std::process::exit(1); + } + } + } + fn try_main(application_name: impl AsRef, make_params: F) -> eyre::Result<()> + where + Self: clap::Parser + Clone, + F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, + { + let args = Self::parse(); + let global_params = GlobalParams::new(Some(Self::command()), application_name); + let Some(platform) = args.get_platform().cloned() else { + return args.handle_missing_platform(&global_params); + }; + args.clone() + .run( + |platform, extra| make_params(args, platform, extra), + platform, + &global_params, + ) + .map_err(|e| global_params.exit_if_clap_error(e)) + } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + global_params + .clap_error( + clap::error::ErrorKind::MissingRequiredArgument, + "--platform is required", + ) + .exit(); + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, Extra) -> eyre::Result, + { + self.run_without_platform(|extra| make_params(platform, extra), global_params) + } } impl RunBuild for JobArgsAndDependencies { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let params = make_params(NoArgs)?; - self.args_to_jobs(¶ms)?.run(|_| Ok(params), cmd) + self.args_to_jobs(¶ms, global_params)? + .run_without_platform(|_| Ok(params), global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.base_job_args().platform.as_ref() + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, + { + let params = make_params(platform.clone(), NoArgs)?; + self.args_to_jobs(¶ms, global_params)? + .run(|_, _| Ok(params), platform, global_params) } } impl RunBuild for JobAndDependencies { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { - let _ = cmd; let params = make_params(NoArgs)?; let Self { job, dependencies } = self; let mut jobs = vec![DynJob::from(job)]; K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); let mut job_graph = JobGraph::new(); job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.base_job().platform() + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, + { + let params = make_params(platform, NoArgs)?; + let Self { job, dependencies } = self; + let mut jobs = vec![DynJob::from(job)]; + K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); + let mut job_graph = JobGraph::new(); + job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times + job_graph.run(¶ms, global_params) } } #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct RunSingleJob { + pub platform: Option, pub job: DynJob, pub extra: Extra, } @@ -1543,18 +1870,26 @@ pub struct RunSingleJob { impl RunSingleJob { pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; fn try_add_subcommand( + platform: Option<&DynPlatform>, job: &DynJob, subcommand_line: &mut Vec>, ) -> serde_json::Result<()> { let mut json = job.serialize_to_json_ascii()?; json.insert_str(0, "--json="); - subcommand_line.extend([ - Interned::::from(Self::SUBCOMMAND_NAME.intern()), + subcommand_line.push(Self::SUBCOMMAND_NAME.intern().into()); + if let Some(platform) = platform { + subcommand_line.push( + format!("--platform={}", platform.name()) + .intern_deref() + .into(), + ); + } + subcommand_line.push( format!("--name={}", job.kind().name()) .intern_deref() .into(), - json.intern_deref().into(), - ]); + ); + subcommand_line.push(json.intern_deref().into()); Ok(()) } } @@ -1564,6 +1899,7 @@ impl TryFrom> for RunSingleJob { fn try_from(value: RunSingleJobClap) -> Result { let RunSingleJobClap::RunSingleJob { + platform, name: job_kind, json, extra, @@ -1577,7 +1913,11 @@ impl TryFrom> for RunSingleJob { format_args!("failed to parse job {name} from JSON: {e}"), ) }) - .map(|job| Self { job, extra }) + .map(|job| Self { + platform, + job, + extra, + }) } } @@ -1585,6 +1925,8 @@ impl TryFrom> for RunSingleJob { enum RunSingleJobClap { #[command(name = RunSingleJob::SUBCOMMAND_NAME, hide = true)] RunSingleJob { + #[arg(long)] + platform: Option, #[arg(long)] name: DynJobKind, #[arg(long)] @@ -1629,15 +1971,21 @@ impl clap::FromArgMatches for RunSingleJob { } impl RunBuild for RunSingleJob { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { - let _ = cmd; let params = make_params(self.extra)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([self.job]); - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.platform.as_ref() } } @@ -1674,11 +2022,18 @@ impl Completions { } impl RunBuild for Completions { - fn run(self, _make_params: F, mut cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + _make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let Self::Completions { shell } = self; + let Some(cmd) = global_params.top_level_cmd() else { + eyre::bail!("completions command requires GlobalParams::top_level_cmd() to be Some"); + }; let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| { program_name_for_internal_jobs() .to_interned_str() @@ -1686,12 +2041,18 @@ impl RunBuild for Completions { }); clap_complete::aot::generate( shell, - &mut cmd, + &mut cmd.clone(), &*bin_name, &mut std::io::BufWriter::new(std::io::stdout().lock()), ); Ok(()) } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + self.run_without_platform(|_| unreachable!(), global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + None + } } #[derive( @@ -1730,16 +2091,61 @@ pub enum BuildCli { } impl RunBuild for BuildCli { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { match self { - BuildCli::Job(v) => v.run(make_params, cmd), - BuildCli::RunSingleJob(v) => v.run(make_params, cmd), - BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + BuildCli::Job(v) => v.run_without_platform(make_params, global_params), + BuildCli::RunSingleJob(v) => v.run_without_platform(make_params, global_params), + BuildCli::Completions(v) => { + v.run_without_platform(|NoArgs {}| unreachable!(), global_params) + } #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), + BuildCli::CreateUnixShellScript(v) => { + v.run_without_platform(make_params, global_params) + } + } + } + fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { + match self { + BuildCli::Job(v) => v.handle_missing_platform(global_params), + BuildCli::RunSingleJob(v) => v.handle_missing_platform(global_params), + BuildCli::Completions(v) => v.handle_missing_platform(global_params), + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.handle_missing_platform(global_params), + } + } + fn get_platform(&self) -> Option<&DynPlatform> { + match self { + BuildCli::Job(v) => v.get_platform(), + BuildCli::RunSingleJob(v) => v.get_platform(), + BuildCli::Completions(v) => v.get_platform(), + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.get_platform(), + } + } + fn run( + self, + make_params: F, + platform: DynPlatform, + global_params: &GlobalParams, + ) -> eyre::Result<()> + where + F: FnOnce(DynPlatform, Extra) -> eyre::Result, + { + match self { + BuildCli::Job(v) => v.run(make_params, platform, global_params), + BuildCli::RunSingleJob(v) => v.run(make_params, platform, global_params), + BuildCli::Completions(v) => { + v.run(|_, NoArgs {}| unreachable!(), platform, global_params) + } + #[cfg(unix)] + BuildCli::CreateUnixShellScript(v) => v.run(make_params, platform, global_params), } } } @@ -1759,10 +2165,15 @@ enum CreateUnixShellScriptInner { pub struct CreateUnixShellScript(CreateUnixShellScriptInner); impl RunBuild for CreateUnixShellScript { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { + let platform = self.get_platform().cloned(); let CreateUnixShellScriptInner::CreateUnixShellScript { _incomplete: (), inner: @@ -1774,22 +2185,28 @@ impl RunBuild for CreateUnixShellScript { } = self.0; let extra_args = extra.to_interned_args_vec(); let params = make_params(extra)?; - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let bin_name = global_params + .top_level_cmd() + .and_then(clap::Command::get_bin_name) + .map(|v| OsStr::new(v).intern()); + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); std::io::stdout().write_all( job_graph .to_unix_shell_script_with_internal_program_prefix( - &[cmd - .get_bin_name() - .map(|v| OsStr::new(v).intern()) - .unwrap_or_else(|| program_name_for_internal_jobs())], + &[bin_name.unwrap_or_else(|| program_name_for_internal_jobs())], + platform.as_ref(), &extra_args, ) .as_bytes(), )?; Ok(()) } + fn get_platform(&self) -> Option<&DynPlatform> { + let CreateUnixShellScriptInner::CreateUnixShellScript { inner, .. } = &self.0; + inner.get_platform() + } } impl clap::FromArgMatches for CreateUnixShellScript { @@ -1956,21 +2373,30 @@ impl clap::FromArgMatches for AnyJobSubcommand { } impl RunBuild for AnyJobSubcommand { - fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> + fn run_without_platform( + self, + make_params: F, + global_params: &GlobalParams, + ) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { - let _ = cmd; let Self { args, dependencies_args, extra, } = self; let params = make_params(extra)?; - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms) + job_graph.run(¶ms, global_params) + } + fn get_platform(&self) -> Option<&DynPlatform> { + self.args + .base_job_args_dyn(&self.dependencies_args) + .platform + .as_ref() } } @@ -1984,21 +2410,47 @@ pub fn program_name_for_internal_jobs() -> Interned { }) } -#[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] -#[group(id = "CreateOutputDir")] -pub struct CreateOutputDirArgs { +#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] +#[group(id = "BaseJob")] +#[non_exhaustive] +pub struct BaseJobArgs { /// the directory to put the generated main output file and associated files in #[arg(short, long, value_hint = clap::ValueHint::DirPath)] pub output: Option, #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] pub keep_temp_dir: bool, + /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo + #[arg(long)] + pub file_stem: Option, + /// run commands even if their results are already cached + #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] + pub run_even_if_cached: bool, + /// platform + #[arg(long)] + pub platform: Option, } -impl ToArgs for CreateOutputDirArgs { +impl BaseJobArgs { + pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; + pub fn from_output_dir_and_env(output: PathBuf, platform: Option) -> Self { + Self { + output: Some(output), + keep_temp_dir: false, + file_stem: None, + run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), + platform, + } + } +} + +impl ToArgs for BaseJobArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { output, keep_temp_dir, + file_stem, + run_even_if_cached, + platform, } = self; if let Some(output) = output { args.write_long_option_eq("output", output); @@ -2006,36 +2458,130 @@ impl ToArgs for CreateOutputDirArgs { if *keep_temp_dir { args.write_arg("--keep-temp-dir"); } + if let Some(file_stem) = file_stem { + args.write_long_option_eq("file-stem", file_stem); + } + if *run_even_if_cached { + args.write_arg("--run-even-if-cached"); + } + if let Some(platform) = platform { + args.write_long_option_eq("platform", platform.name()); + } } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CreateOutputDir { +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BaseJob { output_dir: Interned, #[serde(skip)] temp_dir: Option>, + file_stem: Interned, + run_even_if_cached: bool, + platform: Option, } -impl Eq for CreateOutputDir {} - -impl PartialEq for CreateOutputDir { - fn eq(&self, other: &Self) -> bool { - self.compare_key() == other.compare_key() - } -} - -impl Hash for CreateOutputDir { +impl Hash for BaseJob { fn hash(&self, state: &mut H) { - self.compare_key().hash(state); + let Self { + output_dir, + temp_dir: _, + file_stem, + run_even_if_cached, + platform, + } = self; + output_dir.hash(state); + file_stem.hash(state); + run_even_if_cached.hash(state); + platform.hash(state); } } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct CreateOutputDirJobKind; +impl Eq for BaseJob {} -impl JobKind for CreateOutputDirJobKind { - type Args = CreateOutputDirArgs; - type Job = CreateOutputDir; +impl PartialEq for BaseJob { + fn eq(&self, other: &Self) -> bool { + let Self { + output_dir, + temp_dir: _, + file_stem, + run_even_if_cached, + ref platform, + } = *self; + output_dir == other.output_dir + && file_stem == other.file_stem + && run_even_if_cached == other.run_even_if_cached + && *platform == other.platform + } +} + +impl BaseJob { + pub fn output_dir(&self) -> Interned { + self.output_dir + } + pub fn temp_dir(&self) -> Option<&Arc> { + self.temp_dir.as_ref() + } + pub fn file_stem(&self) -> Interned { + self.file_stem + } + pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { + let mut retval = self.output_dir().join(self.file_stem()); + retval.set_extension(ext); + retval.intern_deref() + } + pub fn run_even_if_cached(&self) -> bool { + self.run_even_if_cached + } + pub fn platform(&self) -> Option<&DynPlatform> { + self.platform.as_ref() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct BaseJobKind; + +impl JobKindHelper for BaseJobKind { + fn base_job<'a>( + self, + job: &'a ::Job, + _dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, + ) -> &'a BaseJob { + job + } + fn base_job_args<'a>( + self, + args: &'a ::Args, + _dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, + ) -> &'a BaseJobArgs { + args + } + #[track_caller] + fn base_job_args_dyn<'a>( + self, + args: &'a ::Args, + dependencies_args: &'a [DynJobArgs], + ) -> &'a BaseJobArgs { + let [] = dependencies_args else { + panic!("wrong number of dependencies"); + }; + args + } + #[track_caller] + fn base_job_dyn<'a>( + self, + job: &'a ::Job, + dependencies: &'a [DynJob], + ) -> &'a BaseJob { + let [] = dependencies else { + panic!("wrong number of dependencies"); + }; + job + } +} + +impl JobKind for BaseJobKind { + type Args = BaseJobArgs; + type Job = BaseJob; type Dependencies = (); fn dependencies(self) -> Self::Dependencies { @@ -2044,20 +2590,16 @@ impl JobKind for CreateOutputDirJobKind { fn args_to_jobs( args: JobArgsAndDependencies, - _params: &JobParams, + params: &JobParams, + _global_params: &GlobalParams, ) -> eyre::Result> { - let JobArgsAndDependencies { - args: - JobKindAndArgs { - kind, - args: - CreateOutputDirArgs { - output, - keep_temp_dir, - }, - }, - dependencies: (), - } = args; + let BaseJobArgs { + output, + keep_temp_dir, + file_stem, + run_even_if_cached, + platform, + } = args.args.args; let (output_dir, temp_dir) = if let Some(output) = output { (Intern::intern_owned(output), None) } else { @@ -2075,12 +2617,18 @@ impl JobKind for CreateOutputDirJobKind { }; (output_dir, temp_dir) }; + let file_stem = file_stem + .map(Intern::intern_deref) + .unwrap_or(params.main_module().name().into()); Ok(JobAndDependencies { job: JobAndKind { - kind, - job: CreateOutputDir { + kind: BaseJobKind, + job: BaseJob { output_dir, temp_dir, + file_stem, + run_even_if_cached, + platform, }, }, dependencies: (), @@ -2099,7 +2647,7 @@ impl JobKind for CreateOutputDirJobKind { } fn name(self) -> Interned { - "create-output-dir".intern() + "base-job".intern() } fn external_command_params(self, job: &Self::Job) -> Option { @@ -2120,10 +2668,11 @@ impl JobKind for CreateOutputDirJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [] = inputs else { - panic!("invalid inputs for CreateOutputDir"); + panic!("invalid inputs for BaseJob"); }; std::fs::create_dir_all(&*job.output_dir)?; Ok(vec![JobItem::Path { @@ -2136,164 +2685,6 @@ impl JobKind for CreateOutputDirJobKind { } } -impl CreateOutputDir { - pub fn output_dir(&self) -> Interned { - self.output_dir - } - fn compare_key(&self) -> (&Path, bool) { - let Self { - output_dir, - temp_dir, - } = self; - (output_dir, temp_dir.is_some()) - } -} - -#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] -#[group(id = "BaseJob")] -#[non_exhaustive] -pub struct BaseJobArgs { - /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency - #[command(flatten)] - pub create_output_dir_args: CreateOutputDirArgs, - /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo - #[arg(long)] - pub file_stem: Option, - /// run commands even if their results are already cached - #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] - pub run_even_if_cached: bool, -} - -impl BaseJobArgs { - pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; - pub fn from_output_dir_and_env(output: PathBuf) -> Self { - Self { - create_output_dir_args: CreateOutputDirArgs { - output: Some(output), - keep_temp_dir: false, - }, - file_stem: None, - run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), - } - } -} - -impl ToArgs for BaseJobArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - let Self { - create_output_dir_args, - file_stem, - run_even_if_cached, - } = self; - create_output_dir_args.to_args(args); - if let Some(file_stem) = file_stem { - args.write_long_option_eq("file-stem", file_stem); - } - if *run_even_if_cached { - args.write_arg("--run-even-if-cached"); - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BaseJob { - /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency - #[serde(flatten)] - create_output_dir: CreateOutputDir, - file_stem: Interned, - run_even_if_cached: bool, -} - -impl BaseJob { - pub fn output_dir(&self) -> Interned { - self.create_output_dir.output_dir() - } - pub fn file_stem(&self) -> Interned { - self.file_stem - } - pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { - let mut retval = self.output_dir().join(self.file_stem()); - retval.set_extension(ext); - retval.intern_deref() - } - pub fn run_even_if_cached(&self) -> bool { - self.run_even_if_cached - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub struct BaseJobKind; - -impl JobKind for BaseJobKind { - type Args = BaseJobArgs; - type Job = BaseJob; - type Dependencies = (); - - fn dependencies(self) -> Self::Dependencies { - () - } - - fn args_to_jobs( - args: JobArgsAndDependencies, - params: &JobParams, - ) -> eyre::Result> { - let BaseJobArgs { - create_output_dir_args, - file_stem, - run_even_if_cached, - } = args.args.args; - let create_output_dir_args = JobKindAndArgs { - kind: CreateOutputDirJobKind, - args: create_output_dir_args, - }; - let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; - let file_stem = file_stem - .map(Intern::intern_deref) - .unwrap_or(params.main_module().name().into()); - Ok(JobAndDependencies { - job: JobAndKind { - kind: BaseJobKind, - job: BaseJob { - create_output_dir, - file_stem, - run_even_if_cached, - }, - }, - dependencies: (), - }) - } - - fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - CreateOutputDirJobKind.inputs(&job.create_output_dir) - } - - fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - CreateOutputDirJobKind.outputs(&job.create_output_dir) - } - - fn name(self) -> Interned { - "base-job".intern() - } - - fn external_command_params(self, job: &Self::Job) -> Option { - CreateOutputDirJobKind.external_command_params(&job.create_output_dir) - } - - fn run( - self, - job: &Self::Job, - inputs: &[JobItem], - params: &JobParams, - acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - CreateOutputDirJobKind.run(&job.create_output_dir, inputs, params, acquired_job) - } - - fn subcommand_hidden(self) -> bool { - true - } -} - pub trait GetJob { fn get_job(this: &Self) -> &J; } @@ -2382,3 +2773,31 @@ impl GetJob for JobAndDependencies { &this.job.job } } + +impl>>> + GetJob> for JobArgsAndDependencies +{ + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) + } +} + +impl GetJob for JobArgsAndDependencies { + fn get_job(this: &Self) -> &K::Args { + &this.args.args + } +} + +impl>> + GetJob> for JobKindAndDependencies +{ + fn get_job(this: &Self) -> &J { + GetJob::get_job(&this.dependencies) + } +} + +impl GetJob for JobKindAndDependencies { + fn get_job(this: &Self) -> &K { + &this.kind + } +} diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index a6936e5..e4251a4 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -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>; + type Dependencies: JobDependenciesHasBase; type ExternalProgram: ExternalProgramTrait; fn dependencies() -> Self::Dependencies; fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, @@ -1028,6 +1029,7 @@ impl JobKind for ExternalCommandJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { let JobKindAndArgs { kind, @@ -1042,8 +1044,8 @@ impl JobKind for ExternalCommandJobKind { additional_args: _, }, } = args.args; - let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; - let base_job = GetJob::::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 JobKind for ExternalCommandJobKind { self, job: &Self::Job, inputs: &[JobItem], - params: &JobParams, + _params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!( @@ -1093,7 +1096,7 @@ impl JobKind for ExternalCommandJobKind { } = job.command_params(); ExternalJobCaching::new( &job.output_dir, - ¶ms.application_name(), + &global_params.application_name(), &T::job_kind_name(), job.run_even_if_cached, )? diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index a04739d..b5574a9 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -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, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { args.args_to_jobs_simple( params, + global_params, |_kind, FirrtlArgs { export_options }, dependencies| { Ok(Firrtl { base: dependencies.get_job::().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> { let [JobItem::Path { path: input_path }] = *inputs else { diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 02515f2..0708ff0 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -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, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { 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> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -351,11 +353,12 @@ impl ExternalCommand for Formal { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::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::().clone(); Ok(Formal { diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index b727715..d81b282 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -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]) -> Result { + pub fn to_unix_makefile( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> Result { 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], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> Result { 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]) -> String { + pub fn to_unix_shell_script( + &self, + platform: Option<&DynPlatform>, + extra_args: &[Interned], + ) -> 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], + platform: Option<&DynPlatform>, extra_args: &[Interned], ) -> 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, params: &'a JobParams, + global_params: &'a GlobalParams, acquired_job: AcquiredJob, finished_jobs_sender: mpsc::Sender<::NodeId>, } impl RunningJobInThread<'_> { fn run(mut self) -> eyre::Result> { - 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(), }; diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs index ccb401f..bbd9f2c 100644 --- a/crates/fayalite/src/build/registry.rs +++ b/crates/fayalite/src/build/registry.rs @@ -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); - -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 { - Some(self.cmp(other)) - } -} - -impl Borrow for InternedStrCompareAsStr { - fn borrow(&self) -> &str { - &self.0 - } -} - #[derive(Clone, Debug)] struct JobKindRegistry { job_kinds: BTreeMap, diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 6ecae2c..7ce77ec 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -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>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::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, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { - 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::(); 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> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs index 903f9cc..194aa6e 100644 --- a/crates/fayalite/src/platform.rs +++ b/crates/fayalite/src/platform.rs @@ -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; 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 DynPlatformTrait for T { @@ -51,6 +58,10 @@ impl DynPlatformTrait for T { self.hash(&mut state); } + fn name_dyn(&self) -> Interned { + self.name() + } + fn new_peripherals_dyn<'builder>( &self, builder_factory: PeripheralsBuilderFactory<'builder>, @@ -80,6 +91,10 @@ impl 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 { + 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() + ::downcast_mut::(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 { ty: T, common: PeripheralCommon, @@ -528,6 +548,27 @@ impl Peripheral { 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 { + 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> { + 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 Peripheral { impl fmt::Debug for Peripheral { 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 { impl fmt::Debug for UsedPeripheral { 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 UsedPeripheral { pub fn instance_io_field(&self) -> Expr { 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 { + 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> { + 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) -> Arc; + fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool; + fn hash_dyn(&self, state: &mut dyn Hasher); +} + +impl PlatformAspectTrait for T { + fn any_ref(&self) -> &dyn Any { + self + } + + fn any_arc(self: Arc) -> Arc { + self + } + + fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool { + other + .any_ref() + .downcast_ref::() + .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, +} + +impl Hash for PlatformAspect { + fn hash(&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(value: Arc) -> Self { + Self { + type_id: TypeId::of::(), + type_name: std::any::type_name::(), + value, + } + } + pub fn new(value: T) -> Self { + Self::new_arc(Arc::new(value)) + } + pub fn type_id(&self) -> TypeId { + self.type_id + } + pub fn downcast_arc(self) -> Result, Self> { + if self.type_id == TypeId::of::() { + let Ok(retval) = self.value.any_arc().downcast() else { + unreachable!(); + }; + Ok(retval) + } else { + Err(self) + } + } + pub fn downcast_unwrap_or_clone( + self, + ) -> Result { + Ok(Arc::unwrap_or_clone(self.downcast_arc()?)) + } + pub fn downcast_ref(&self) -> Option<&T> { + PlatformAspectTrait::any_ref(&*self.value).downcast_ref() + } +} + +#[derive(Clone, Default)] +pub struct PlatformAspectSet { + aspects_by_type_id: Arc>>, + aspects: Arc>, +} + +impl PlatformAspectSet { + pub fn new() -> Self { + Self::default() + } + pub fn insert_new( + &mut self, + value: T, + ) -> bool { + self.insert(PlatformAspect::new(value)) + } + pub fn insert_new_arc( + &mut self, + value: Arc, + ) -> bool { + self.insert(PlatformAspect::new_arc(value)) + } + fn insert_inner( + aspects_by_type_id: &mut HashMap>, + aspects: &mut Vec, + 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 + FusedIterator + ExactSizeIterator + 'a + { + self.aspects_by_type_id + .get(&TypeId::of::()) + .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 + FusedIterator + ExactSizeIterator + 'a { + self.get_aspects_by_type::() + .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::(); + 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> + FusedIterator + ExactSizeIterator + 'a { + self.get_aspects_by_type::().map(|aspect| { + aspect + .clone() + .downcast_arc() + .ok() + .expect("already checked type") + }) + } +} + +impl<'a> Extend<&'a PlatformAspect> for PlatformAspectSet { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().cloned()); + } +} + +impl Extend for PlatformAspectSet { + fn extend>(&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>(iter: T) -> Self { + let mut retval = Self::default(); + retval.extend(iter); + retval + } +} + +impl FromIterator for PlatformAspectSet { + fn from_iter>(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>, + indexes: std::ops::Range, +} + +impl Iterator for PlatformAspectsIntoIter { + type Item = PlatformAspect; + + fn next(&mut self) -> Option { + self.indexes.next().map(|index| self.aspects[index].clone()) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.len() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + self.indexes.nth(n).map(|index| self.aspects[index].clone()) + } + + fn fold(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.indexes + .next_back() + .map(|index| self.aspects[index].clone()) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.indexes + .nth_back(n) + .map(|index| self.aspects[index].clone()) + } + + fn rfold(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; 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, +} + +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> { + type Locked = RwLockWriteGuard<'static, Arc>; + 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> { + static REGISTRY: OnceLock>> = OnceLock::new(); + REGISTRY.get_or_init(Default::default) + } + fn try_register( + 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(lock: L, platform: DynPlatform) { + match Self::try_register(lock, platform) { + Err(e) => panic!("{e}"), + Ok(()) => {} + } + } + fn get() -> Arc { + 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); + +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.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last() + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n) + } + + fn fold(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.0.next_back() + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0.nth_back(n) + } + + fn rfold(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, &'a DynPlatform); + + fn next(&mut self) -> Option { + self.0.next().map(|(name, platform)| (name.0, platform)) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + + fn count(self) -> usize + where + Self: Sized, + { + self.0.count() + } + + fn last(self) -> Option { + self.0.last().map(|(name, platform)| (name.0, platform)) + } + + fn nth(&mut self, n: usize) -> Option { + self.0.nth(n).map(|(name, platform)| (name.0, platform)) + } + + fn fold(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.0 + .next_back() + .map(|(name, platform)| (name.0, platform)) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.0 + .nth_back(n) + .map(|(name, platform)| (name.0, platform)) + } + + fn rfold(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(kind: K) { + DynPlatform::new(kind).register(); +} + +impl Serialize for DynPlatform { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.name().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DynPlatform { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = Cow::::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, + 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 { + 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 { + clap::builder::EnumValueParser::::new() + .parse_ref(cmd, arg, value) + .map(|v| v.platform) + } + + fn possible_values( + &self, + ) -> Option + '_>> { + static ENUM_VALUE_PARSER: OnceLock> = + OnceLock::new(); + ENUM_VALUE_PARSER + .get_or_init(clap::builder::EnumValueParser::::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 { + crate::vendor::built_in_platforms() } diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs new file mode 100644 index 0000000..3ff4d6c --- /dev/null +++ b/crates/fayalite/src/platform/peripherals.rs @@ -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, +} + +#[hdl(no_runtime_generics, no_static)] +pub struct ClockInput { + pub clk: Clock, + pub properties: PhantomConst, +} + +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, +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5ff3a64..216f94e 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -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::{ diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index 30a8243..c7feb5e 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -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>> { 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>, 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), ) } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index cc8f8b0..9796488 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -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; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index bd5c53f..165ab3a 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -585,3 +585,30 @@ pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef) -> Op unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } }) } + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned); + +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 { + Some(self.cmp(other)) + } +} + +impl std::borrow::Borrow for InternedStrCompareAsStr { + fn borrow(&self) -> &str { + &self.0 + } +} diff --git a/crates/fayalite/src/vendor.rs b/crates/fayalite/src/vendor.rs index 56297cf..cdf302d 100644 --- a/crates/fayalite/src/vendor.rs +++ b/crates/fayalite/src/vendor.rs @@ -6,3 +6,7 @@ pub mod xilinx; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { xilinx::built_in_job_kinds() } + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + xilinx::built_in_platforms() +} diff --git a/crates/fayalite/src/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs index 05e45c7..b406610 100644 --- a/crates/fayalite/src/vendor/xilinx.rs +++ b/crates/fayalite/src/vendor/xilinx.rs @@ -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 { - yosys_nextpnr_prjxray::built_in_job_kinds() +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct XilinxArgs { + #[arg(long)] + pub device: Option, +} + +impl XilinxArgs { + pub fn require_device( + &self, + platform: Option<&DynPlatform>, + global_params: &GlobalParams, + ) -> clap::error::Result { + if let Some(device) = self.device { + return Ok(device); + } + if let Some(device) = + platform.and_then(|platform| platform.aspects().get_single_by_type::().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(self, v: &str) -> Result + 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(self, v: &[u8]) -> Result + 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(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(DeviceVisitor) + } + } + + impl Serialize for $Device { + fn serialize(&self, serializer: S) -> Result + 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 { + arty_a7::built_in_job_kinds() + .into_iter() + .chain(yosys_nextpnr_prjxray::built_in_job_kinds()) +} + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + arty_a7::built_in_platforms() + .into_iter() + .chain(yosys_nextpnr_prjxray::built_in_platforms()) } diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs new file mode 100644 index 0000000..cf8e805 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -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 = 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, + rst: Peripheral, + rst_sync: Peripheral, + ld0: Peripheral, + ld1: Peripheral, + ld2: Peripheral, + ld3: Peripheral, + ld4: Peripheral, + ld5: Peripheral, + ld6: Peripheral, + ld7: Peripheral, + // TODO: add rest of peripherals when we need them +} + +impl Peripherals for ArtyA7Peripherals { + fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { + 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 { + 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 { + [] +} + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + ArtyA7Platform::VARIANTS + .iter() + .map(|&v| DynPlatform::new(v)) +} diff --git a/crates/fayalite/src/vendor/xilinx/primitives.rs b/crates/fayalite/src/vendor/xilinx/primitives.rs new file mode 100644 index 0000000..5dc2567 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/primitives.rs @@ -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(); +} diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index c3b1245..70c74a9 100644 --- a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -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, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { 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::(); let verilog_job = dependencies.get_job::(); @@ -153,6 +155,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -260,11 +263,12 @@ impl ExternalCommand for YosysNextpnrXraySynth { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::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 for WriteXdcContentsError { fn tcl_escape(s: impl AsRef) -> 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, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { 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::(); 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> { 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(self, v: &str) -> Result - 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(self, v: &[u8]) -> Result - 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(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_string(DeviceVisitor) - } - } - - impl Serialize for $Device { - fn serialize(&self, serializer: S) -> Result - 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>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::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::(); @@ -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>, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::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::(); let frames_file = base_job.file_with_ext("frames"); @@ -918,3 +800,7 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator { DynJobKind::new(ExternalCommandJobKind::::new()), ] } + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + [] +}