From d2efb9468698fa5e2c1998faa6714c1cfdd3092a Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 17 Oct 2025 18:00:11 -0700 Subject: [PATCH] WIP adding peripherals --- Cargo.lock | 31 ++ Cargo.toml | 1 + crates/fayalite/Cargo.toml | 1 + crates/fayalite/examples/blinky.rs | 69 +-- crates/fayalite/src/build.rs | 457 +++++++++++++++--- crates/fayalite/src/build/external.rs | 11 +- crates/fayalite/src/build/firrtl.rs | 5 +- crates/fayalite/src/build/formal.rs | 11 +- crates/fayalite/src/build/graph.rs | 16 +- crates/fayalite/src/build/registry.rs | 30 +- crates/fayalite/src/build/verilog.rs | 11 +- crates/fayalite/src/platform.rs | 447 ++++++++++++++++- crates/fayalite/src/platform/peripherals.rs | 52 ++ crates/fayalite/src/prelude.rs | 1 + crates/fayalite/src/testing.rs | 12 +- crates/fayalite/src/util.rs | 2 +- crates/fayalite/src/util/misc.rs | 27 ++ crates/fayalite/src/vendor.rs | 4 + crates/fayalite/src/vendor/xilinx.rs | 166 ++++++- crates/fayalite/src/vendor/xilinx/arty_a7.rs | 274 +++++++++++ .../fayalite/src/vendor/xilinx/primitives.rs | 66 +++ .../vendor/xilinx/yosys_nextpnr_prjxray.rs | 160 ++---- 22 files changed, 1578 insertions(+), 276 deletions(-) create mode 100644 crates/fayalite/src/platform/peripherals.rs create mode 100644 crates/fayalite/src/vendor/xilinx/arty_a7.rs create mode 100644 crates/fayalite/src/vendor/xilinx/primitives.rs 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..7384e0d 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")) + BuildCli::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..0242bea 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, }; @@ -318,6 +319,7 @@ impl JobKindAndArgs { self, dependencies: ::KindsAndArgs, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { K::args_to_jobs( JobArgsAndDependencies { @@ -325,6 +327,7 @@ impl JobKindAndArgs { dependencies, }, params, + global_params, ) } } @@ -446,8 +449,12 @@ 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) } } @@ -455,6 +462,7 @@ impl>, D: JobKind> JobArgsAn pub fn args_to_jobs_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result> where @@ -464,7 +472,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 +487,7 @@ impl>, D: pub fn args_to_jobs_external_simple( self, params: &JobParams, + global_params: &GlobalParams, f: F, ) -> eyre::Result<( C::AdditionalJobData, @@ -494,7 +503,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)) } @@ -624,7 +633,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 +642,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 } @@ -710,6 +759,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 +770,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 { @@ -1064,6 +1115,7 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { self: Arc, dependencies_args: Vec, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)>; } @@ -1117,12 +1169,13 @@ 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))) } } @@ -1179,8 +1232,9 @@ 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) } } @@ -1261,6 +1315,7 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { &self, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; } @@ -1317,9 +1372,11 @@ 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) } } @@ -1429,9 +1486,10 @@ 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) } } @@ -1481,56 +1539,159 @@ 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 main(application_name: impl AsRef, make_params: F) + where + Self: clap::Parser + Clone + GetPlatform, + 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 + GetPlatform, + 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() 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 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 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) } } @@ -1629,15 +1790,18 @@ 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) } } @@ -1674,11 +1838,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 +1857,15 @@ 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) + } } #[derive( @@ -1730,16 +1904,52 @@ 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 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,7 +1969,11 @@ 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, { @@ -1774,16 +1988,17 @@ 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())], &extra_args, ) .as_bytes(), @@ -1956,21 +2171,24 @@ 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) } } @@ -2045,6 +2263,7 @@ impl JobKind for CreateOutputDirJobKind { fn args_to_jobs( args: JobArgsAndDependencies, _params: &JobParams, + _global_params: &GlobalParams, ) -> eyre::Result> { let JobArgsAndDependencies { args: @@ -2120,6 +2339,7 @@ impl JobKind for CreateOutputDirJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, + _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [] = inputs else { @@ -2162,11 +2382,14 @@ pub struct BaseJobArgs { /// 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 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 { + pub fn from_output_dir_and_env(output: PathBuf, platform: Option) -> Self { Self { create_output_dir_args: CreateOutputDirArgs { output: Some(output), @@ -2174,6 +2397,7 @@ impl BaseJobArgs { }, file_stem: None, run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), + platform, } } } @@ -2184,6 +2408,7 @@ impl ToArgs for BaseJobArgs { create_output_dir_args, file_stem, run_even_if_cached, + platform, } = self; create_output_dir_args.to_args(args); if let Some(file_stem) = file_stem { @@ -2192,6 +2417,9 @@ impl ToArgs for BaseJobArgs { 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()); + } } } @@ -2202,6 +2430,7 @@ pub struct BaseJob { create_output_dir: CreateOutputDir, file_stem: Interned, run_even_if_cached: bool, + platform: Option, } impl BaseJob { @@ -2219,6 +2448,9 @@ impl BaseJob { 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)] @@ -2236,17 +2468,22 @@ impl JobKind for BaseJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, + global_params: &GlobalParams, ) -> eyre::Result> { let BaseJobArgs { create_output_dir_args, file_stem, run_even_if_cached, + platform, } = 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 create_output_dir = create_output_dir_args + .args_to_jobs((), params, global_params)? + .job + .job; let file_stem = file_stem .map(Intern::intern_deref) .unwrap_or(params.main_module().name().into()); @@ -2257,6 +2494,7 @@ impl JobKind for BaseJobKind { create_output_dir, file_stem, run_even_if_cached, + platform, }, }, dependencies: (), @@ -2284,9 +2522,16 @@ impl JobKind for BaseJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, + global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - CreateOutputDirJobKind.run(&job.create_output_dir, inputs, params, acquired_job) + CreateOutputDirJobKind.run( + &job.create_output_dir, + inputs, + params, + global_params, + acquired_job, + ) } fn subcommand_hidden(self) -> bool { @@ -2382,3 +2627,105 @@ 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 + } +} + +pub trait GetPlatform { + fn get_platform(&self) -> Option; +} + +impl GetPlatform for &'_ T { + fn get_platform(&self) -> Option { + T::get_platform(self) + } +} + +impl GetPlatform for &'_ mut T { + fn get_platform(&self) -> Option { + T::get_platform(self) + } +} + +impl GetPlatform for Box { + fn get_platform(&self) -> Option { + T::get_platform(self) + } +} + +impl GetPlatform for BaseJobArgs { + fn get_platform(&self) -> Option { + self.platform.clone() + } +} + +impl GetPlatform for BaseJob { + fn get_platform(&self) -> Option { + self.platform.clone() + } +} + +impl GetPlatform for JobAndDependencies { + fn get_platform(&self) -> Option { + self.job.get_platform() + } +} + +impl GetPlatform for JobArgsAndDependencies { + fn get_platform(&self) -> Option { + self.args.get_platform() + } +} + +impl> GetPlatform for JobAndKind { + fn get_platform(&self) -> Option { + self.job.get_platform() + } +} + +impl> GetPlatform for JobKindAndArgs { + fn get_platform(&self) -> Option { + self.args.get_platform() + } +} + +impl>> GetPlatform + for JobAndDependencies +{ + fn get_platform(&self) -> Option { + self.dependencies.get_platform() + } +} + +impl>> GetPlatform + for JobArgsAndDependencies +{ + fn get_platform(&self) -> Option { + self.dependencies.get_platform() + } +} diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index a6936e5..37b13b4 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,7 +3,7 @@ use crate::{ build::{ - ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind, + ArgsWriter, BaseJob, CommandParams, GetJob, GlobalParams, JobAndDependencies, JobAndKind, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, JobParams, ToArgs, WriteArgs, }, @@ -996,6 +996,7 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size 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,7 +1044,7 @@ impl JobKind for ExternalCommandJobKind { additional_args: _, }, } = args.args; - let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; + let (additional_job_data, dependencies) = T::args_to_jobs(args, params, global_params)?; let base_job = GetJob::::get_job(&dependencies); let job = ExternalCommandJob { additional_job_data, @@ -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..41b6d66 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -2,7 +2,9 @@ // 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, util::{HashMap, HashSet, job_server::AcquiredJob}, }; @@ -639,7 +641,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 +727,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 +756,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..1018055 100644 --- a/crates/fayalite/src/platform.rs +++ b/crates/fayalite/src/platform.rs @@ -8,10 +8,12 @@ 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, InternedStrCompareAsStr}, }; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use std::{ any::{Any, TypeId}, + borrow::Cow, cmp::Ordering, collections::{BTreeMap, BTreeSet}, convert::Infallible, @@ -19,13 +21,16 @@ use std::{ hash::{Hash, Hasher}, 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>, @@ -51,6 +56,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>, @@ -155,6 +164,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>, @@ -518,6 +530,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { } } +#[must_use] pub struct Peripheral { ty: T, common: PeripheralCommon, @@ -528,6 +541,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() } @@ -617,6 +651,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)] @@ -1033,6 +1085,7 @@ impl<'a> fmt::Debug for PlatformIOBuilder<'a> { 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>, @@ -1144,3 +1197,393 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { .unwrap_or_else(|e: Infallible| match e {}) } } + +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..2298541 100644 --- a/crates/fayalite/src/vendor/xilinx.rs +++ b/crates/fayalite/src/vendor/xilinx.rs @@ -1,8 +1,17 @@ // 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, +}; +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 +32,157 @@ 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, global_params: &GlobalParams) -> clap::error::Result { + self.device.ok_or_else(|| { + 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..5a1123b --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + intern::{Intern, Interned}, + module::instance_with_loc, + platform::{ + DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, + PeripheralsBuilderFinished, Platform, + peripherals::{ClockInput, Led, RgbLed}, + }, + prelude::*, + vendor::xilinx::{ + Device, XdcIOStandardAnnotation, XdcLocationAnnotation, + primitives::{self, BUFGCE, FDPE, 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,)* + } + } + } + }; +} + +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 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.input_peripheral("ld0", RgbLed), + ld1: builder.input_peripheral("ld1", RgbLed), + ld2: builder.input_peripheral("ld2", RgbLed), + ld3: builder.input_peripheral("ld3", RgbLed), + ld4: builder.input_peripheral("ld4", Led), + ld5: builder.input_peripheral("ld5", Led), + ld6: builder.input_peripheral("ld6", Led), + ld7: builder.input_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_0, rst_sync_1] = std::array::from_fn(|index| { + let rst_sync = instance_with_loc( + &format!("rst_sync_{index}"), + FDPE(true), + SourceLocation::builtin(), + ); + annotate( + rst_sync, + SVAttributeAnnotation { + text: "ASYNC_REG = \"TRUE\"".intern(), + }, + ); + connect(rst_sync.C, clk100_sync.O); + connect(rst_sync.CE, true); + connect(rst_sync.PRE, rst_buf.to_async_reset()); + rst_sync + }); + connect(rst_sync_0.D, false); + connect(rst_sync_1.D, rst_sync_0.Q); + let rst_value = rst_sync_1.Q.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); + } + } + } +} + +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..6e2bfb9 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/primitives.rs @@ -0,0 +1,66 @@ +// 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(); +} + +#[hdl_module(extern)] +pub fn FDPE(init: bool) { + m.verilog_name("FDPE"); + m.parameter_raw_verilog("INIT", if init { "1'b1" } else { "1'b0" }); + #[hdl] + let Q: Bool = m.output(); + #[hdl] + let C: Clock = m.input(); + #[hdl] + let CE: Bool = m.input(); + #[hdl] + let PRE: AsyncReset = m.input(); + #[hdl] + let D: Bool = m.input(); +} diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs index c3b1245..904ed73 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(), @@ -429,6 +433,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 +444,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,6 +479,7 @@ 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))); @@ -508,130 +514,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 +527,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 +578,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 +596,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(global_params)?, nextpnr_xilinx_seed, xdc_file: write_xdc_file.xdc_file, xdc_file_name: write_xdc_file @@ -842,11 +731,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 +808,7 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator { DynJobKind::new(ExternalCommandJobKind::::new()), ] } + +pub(crate) fn built_in_platforms() -> impl IntoIterator { + [] +}