diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index 4659123..96492cf 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -31,17 +31,20 @@ pub mod firrtl; pub mod formal; pub mod graph; pub mod registry; +pub mod vendor; pub mod verilog; -pub(crate) const BUILT_IN_JOB_KINDS: &'static [fn() -> DynJobKind] = &[ - || DynJobKind::new(BaseJobKind), - || DynJobKind::new(CreateOutputDirJobKind), - || DynJobKind::new(firrtl::FirrtlJobKind), - || DynJobKind::new(external::ExternalCommandJobKind::::new()), - || DynJobKind::new(verilog::VerilogJobKind), - || DynJobKind::new(formal::WriteSbyFileJobKind), - || DynJobKind::new(external::ExternalCommandJobKind::::new()), -]; +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()) +} #[track_caller] fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { @@ -99,7 +102,7 @@ impl JobItem { } } -#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum JobItemName { Path { path: Interned }, @@ -171,12 +174,30 @@ pub trait WriteArgs: fn write_interned_arg(&mut self, arg: Interned) { self.extend([arg]); } + /// finds the first option that is `--{option_name}={value}` and returns `value` + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str>; } -#[derive(Default)] -struct WriteArgsWrapper(W); +pub struct ArgsWriter(pub Vec); -impl<'a, W> Extend> for WriteArgsWrapper +impl Default for ArgsWriter { + fn default() -> Self { + Self(Default::default()) + } +} + +impl> ArgsWriter { + fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&str> { + self.0.iter().find_map(|arg| { + arg.as_ref() + .strip_prefix("--") + .and_then(|arg| arg.strip_prefix(option_name)) + .and_then(|arg| arg.strip_prefix("=")) + }) + } +} + +impl<'a, W> Extend> for ArgsWriter where Self: Extend, { @@ -185,39 +206,47 @@ where } } -impl<'a> Extend<&'a str> for WriteArgsWrapper>> { +impl<'a> Extend<&'a str> for ArgsWriter> { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(str::intern)) } } -impl Extend for WriteArgsWrapper>> { +impl Extend for ArgsWriter> { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(str::intern_owned)) } } -impl Extend> for WriteArgsWrapper> { +impl Extend> for ArgsWriter { fn extend>>(&mut self, iter: T) { self.extend(iter.into_iter().map(String::from)) } } -impl<'a> Extend<&'a str> for WriteArgsWrapper> { +impl<'a> Extend<&'a str> for ArgsWriter { fn extend>(&mut self, iter: T) { self.extend(iter.into_iter().map(String::from)) } } -impl Extend for WriteArgsWrapper> { +impl Extend for ArgsWriter { fn extend>(&mut self, iter: T) { self.0.extend(iter); } } -impl WriteArgs for WriteArgsWrapper> {} +impl WriteArgs for ArgsWriter { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { + self.get_long_option_eq_helper(option_name.as_ref()) + } +} -impl WriteArgs for WriteArgsWrapper>> {} +impl WriteArgs for ArgsWriter> { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { + self.get_long_option_eq_helper(option_name.as_ref()) + } +} pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); @@ -225,12 +254,12 @@ pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Intern::intern_owned(self.to_interned_args_vec()) } fn to_interned_args_vec(&self) -> Vec> { - let mut retval = WriteArgsWrapper::default(); + let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } fn to_string_args(&self) -> Vec { - let mut retval = WriteArgsWrapper::default(); + let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } @@ -642,6 +671,9 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy fn subcommand_hidden(self) -> bool { false } + fn external_program(self) -> Option> { + None + } } trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { @@ -1010,7 +1042,7 @@ impl DynJobArgsTrait for DynJobArgsInner { } fn to_args_extend_vec(&self, args: Vec>) -> Vec> { - let mut writer = WriteArgsWrapper(args); + let mut writer = ArgsWriter(args); self.0.args.to_args(&mut writer); writer.0 } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index 8c6858a..021d63d 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, - JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, JobParams, ToArgs, - WriteArgs, intern_known_utf8_path_buf, + ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, + JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -431,7 +431,7 @@ impl ExternalCommandJobKind { } #[derive(Copy, Clone)] -struct ExternalCommandProgramPathValueParser(PhantomData); +struct ExternalProgramPathValueParser(ExternalProgram); fn parse_which_result( which_result: which::Result, @@ -460,9 +460,7 @@ fn parse_which_result( )) } -impl clap::builder::TypedValueParser - for ExternalCommandProgramPathValueParser -{ +impl clap::builder::TypedValueParser for ExternalProgramPathValueParser { type Value = Interned; fn parse_ref( @@ -471,10 +469,11 @@ impl clap::builder::TypedValueParser arg: Option<&clap::Arg>, value: &OsStr, ) -> clap::error::Result { + let program_path_arg_name = self.0.program_path_arg_name; OsStringValueParser::new() - .try_map(|program_name| { + .try_map(move |program_name| { parse_which_result(which::which(&program_name), program_name, || { - T::program_path_arg_name().into() + program_path_arg_name.into() }) }) .parse_ref(cmd, arg, value) @@ -485,16 +484,8 @@ impl clap::builder::TypedValueParser #[group(id = T::args_group_id())] #[non_exhaustive] pub struct ExternalCommandArgs { - #[arg( - name = Interned::into_inner(T::program_path_arg_name()), - long = T::program_path_arg_name(), - value_name = T::program_path_arg_value_name(), - env = T::program_path_env_var_name().map(Interned::into_inner), - value_parser = ExternalCommandProgramPathValueParser::(PhantomData), - default_value = T::default_program_name(), - value_hint = clap::ValueHint::CommandName, - )] - pub program_path: Interned, + #[command(flatten)] + pub program_path: ExternalProgramPath, #[arg( name = Interned::into_inner(T::run_even_if_cached_arg_name()), long = T::run_even_if_cached_arg_name(), @@ -575,6 +566,15 @@ impl ExternalCommandArgs { pub fn with_resolved_program_path( program_path: Interned, additional_args: T::AdditionalArgs, + ) -> Self { + Self::new( + ExternalProgramPath::with_resolved_program_path(program_path), + additional_args, + ) + } + pub fn new( + program_path: ExternalProgramPath, + additional_args: T::AdditionalArgs, ) -> Self { Self { program_path, @@ -582,16 +582,12 @@ impl ExternalCommandArgs { additional_args, } } - pub fn new( + pub fn resolve_program_path( program_name: Option<&OsStr>, additional_args: T::AdditionalArgs, ) -> Result { - Ok(Self::with_resolved_program_path( - resolve_program_path( - program_name, - T::default_program_name(), - T::program_path_env_var_name().as_ref().map(AsRef::as_ref), - )?, + Ok(Self::new( + ExternalProgramPath::resolve_program_path(program_name)?, additional_args, )) } @@ -604,10 +600,7 @@ impl ToArgs for ExternalCommandArgs { run_even_if_cached, ref additional_args, } = *self; - args.write_arg(format_args!( - "--{}={program_path}", - T::program_path_arg_name() - )); + program_path.to_args(args); if run_even_if_cached { args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); } @@ -626,13 +619,11 @@ struct ExternalCommandJobParams { impl ExternalCommandJobParams { fn new(job: &ExternalCommandJob) -> Self { let output_paths = T::output_paths(job); + let mut command_line = ArgsWriter(vec![job.program_path]); + T::command_line_args(job, &mut command_line); Self { command_params: CommandParams { - command_line: Interned::from_iter( - [job.program_path] - .into_iter() - .chain(T::command_line_args(job).iter().copied()), - ), + command_line: Intern::intern_owned(command_line.0), current_dir: T::current_dir(job), }, inputs: T::inputs(job), @@ -758,33 +749,184 @@ impl ExternalCommandJob { } } -pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { - type AdditionalArgs: ToArgs; - type AdditionalJobData: 'static - + Send - + Sync - + Hash - + Eq - + fmt::Debug - + Serialize - + DeserializeOwned; - type Dependencies: JobDependencies; - fn dependencies() -> Self::Dependencies; - fn args_to_jobs( - args: JobArgsAndDependencies>, - params: &JobParams, - ) -> eyre::Result<( - Self::AdditionalJobData, - ::JobsAndKinds, - )>; - fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]>; - fn current_dir(job: &ExternalCommandJob) -> Option>; - fn job_kind_name() -> Interned; - fn args_group_id() -> clap::Id { - Interned::into_inner(Self::job_kind_name()).into() +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ExternalProgramPath { + program_path: Interned, + _phantom: PhantomData, +} + +impl ExternalProgramPath { + pub fn with_resolved_program_path(program_path: Interned) -> Self { + Self { + program_path, + _phantom: PhantomData, + } } + pub fn resolve_program_path( + program_name: Option<&OsStr>, + ) -> Result { + let ExternalProgram { + default_program_name, + program_path_arg_name: _, + program_path_arg_value_name: _, + program_path_env_var_name, + } = ExternalProgram::new::(); + Ok(Self { + program_path: resolve_program_path( + program_name, + default_program_name, + program_path_env_var_name.as_ref().map(OsStr::new), + )?, + _phantom: PhantomData, + }) + } + pub fn program_path(&self) -> Interned { + self.program_path + } +} + +impl fmt::Debug for ExternalProgramPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + program_path, + _phantom: _, + } = self; + write!(f, "ExternalProgramPath<{}>", std::any::type_name::())?; + f.debug_tuple("").field(program_path).finish() + } +} + +impl clap::FromArgMatches for ExternalProgramPath { + fn from_arg_matches(matches: &clap::ArgMatches) -> Result { + let id = Interned::into_inner(ExternalProgram::new::().program_path_arg_name); + // don't remove argument so later instances of Self can use it too + let program_path = *matches.get_one(id).expect("arg should always be present"); + Ok(Self { + program_path, + _phantom: PhantomData, + }) + } + + fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> { + *self = Self::from_arg_matches(matches)?; + Ok(()) + } +} + +impl clap::Args for ExternalProgramPath { + fn augment_args(cmd: clap::Command) -> clap::Command { + let external_program @ ExternalProgram { + default_program_name, + program_path_arg_name, + program_path_arg_value_name, + program_path_env_var_name, + } = ExternalProgram::new::(); + let arg = cmd + .get_arguments() + .find(|arg| *arg.get_id().as_str() == *program_path_arg_name); + if let Some(arg) = arg { + // don't insert duplicate arguments. + // check that the previous argument actually matches this argument: + assert!(!arg.is_required_set()); + assert!(matches!(arg.get_action(), clap::ArgAction::Set)); + assert_eq!(arg.get_long(), Some(&*program_path_arg_name)); + assert_eq!( + arg.get_value_names(), + Some(&[clap::builder::Str::from(program_path_arg_value_name)][..]) + ); + assert_eq!( + arg.get_env(), + program_path_env_var_name.as_ref().map(OsStr::new) + ); + assert_eq!( + arg.get_default_values(), + &[OsStr::new(&default_program_name)] + ); + assert_eq!(arg.get_value_hint(), clap::ValueHint::CommandName); + cmd + } else { + cmd.arg( + clap::Arg::new(Interned::into_inner(program_path_arg_name)) + .required(false) + .value_parser(ExternalProgramPathValueParser(external_program)) + .action(clap::ArgAction::Set) + .long(program_path_arg_name) + .value_name(program_path_arg_value_name) + .env(program_path_env_var_name.map(Interned::into_inner)) + .default_value(default_program_name) + .value_hint(clap::ValueHint::CommandName), + ) + } + } + + fn augment_args_for_update(cmd: clap::Command) -> clap::Command { + Self::augment_args(cmd) + } +} + +impl ToArgs for ExternalProgramPath { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let ExternalProgram { + program_path_arg_name, + .. + } = ExternalProgram::new::(); + let Self { + program_path, + _phantom: _, + } = self; + if args.get_long_option_eq(program_path_arg_name) != Some(&**program_path) { + args.write_arg(format_args!("--{program_path_arg_name}={program_path}")); + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[non_exhaustive] +pub struct ExternalProgram { + default_program_name: Interned, + program_path_arg_name: Interned, + program_path_arg_value_name: Interned, + program_path_env_var_name: Option>, +} + +impl ExternalProgram { + pub fn new() -> Self { + Self { + default_program_name: T::default_program_name(), + program_path_arg_name: T::program_path_arg_name(), + program_path_arg_value_name: T::program_path_arg_value_name(), + program_path_env_var_name: T::program_path_env_var_name(), + } + } + pub fn default_program_name(&self) -> Interned { + self.default_program_name + } + pub fn program_path_arg_name(&self) -> Interned { + self.program_path_arg_name + } + pub fn program_path_arg_value_name(&self) -> Interned { + self.program_path_arg_value_name + } + pub fn program_path_env_var_name(&self) -> Option> { + self.program_path_env_var_name + } +} + +impl From for ExternalProgram { + fn from(_value: T) -> Self { + Self::new::() + } +} + +impl From for Interned { + fn from(_value: T) -> Self { + ExternalProgram::new::().intern_sized() + } +} + +pub trait ExternalProgramTrait: + 'static + Send + Sync + Hash + Ord + fmt::Debug + Default + Copy +{ fn program_path_arg_name() -> Interned { Self::default_program_name() } @@ -799,6 +941,36 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size .replace('-', "_"), )) } +} + +pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { + type AdditionalArgs: ToArgs; + type AdditionalJobData: 'static + + Send + + Sync + + Hash + + Eq + + fmt::Debug + + Serialize + + DeserializeOwned; + type Dependencies: JobDependencies; + type ExternalProgram: ExternalProgramTrait; + fn dependencies() -> Self::Dependencies; + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )>; + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn command_line_args(job: &ExternalCommandJob, args: &mut W); + fn current_dir(job: &ExternalCommandJob) -> Option>; + fn job_kind_name() -> Interned; + fn args_group_id() -> clap::Id { + Interned::into_inner(Self::job_kind_name()).into() + } fn run_even_if_cached_arg_name() -> Interned { Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name())) } @@ -824,7 +996,11 @@ impl JobKind for ExternalCommandJobKind { kind, args: ExternalCommandArgs { - program_path, + program_path: + ExternalProgramPath { + program_path, + _phantom: _, + }, run_even_if_cached, additional_args: _, }, @@ -868,7 +1044,12 @@ impl JobKind for ExternalCommandJobKind { params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - assert!(inputs.iter().map(JobItem::name).eq(job.inputs())); + assert!( + inputs.iter().map(JobItem::name).eq(job.inputs()), + "{}\ninputs:\n{inputs:?}\njob.inputs():\n{:?}", + std::any::type_name::(), + job.inputs(), + ); let CommandParams { command_line, current_dir, @@ -913,4 +1094,8 @@ impl JobKind for ExternalCommandJobKind { fn subcommand_hidden(self) -> bool { T::subcommand_hidden() } + + fn external_program(self) -> Option> { + Some(ExternalProgram::new::().intern_sized()) + } } diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index 5dc4106..62657f8 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -3,8 +3,9 @@ use crate::{ build::{ - BaseJob, BaseJobKind, CommandParams, JobAndDependencies, JobArgsAndDependencies, JobItem, - JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies, + JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, + ToArgs, WriteArgs, }, firrtl::{ExportOptions, FileBackend}, intern::{Intern, Interned}, @@ -118,3 +119,7 @@ impl JobKind for FirrtlJobKind { }]) } } + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [DynJobKind::new(FirrtlJobKind)] +} diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 366ce83..a289c81 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,18 +3,21 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, - JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, - external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, interned_known_utf8_method, - verilog::{VerilogDialect, VerilogJobKind}, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, }, intern::{Intern, Interned}, module::NameId, util::job_server::AcquiredJob, }; use clap::{Args, ValueEnum}; -use eyre::{Context, eyre}; +use eyre::Context; use serde::{Deserialize, Serialize}; use std::fmt; @@ -167,28 +170,12 @@ impl WriteSbyFileJob { \n\ [script]\n", )?; - for verilog_file in [main_verilog_file].into_iter().chain(additional_files) { - if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { - continue; - } - let verilog_file = match std::path::absolute(verilog_file) - .and_then(|v| { - v.into_os_string().into_string().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") - }) - }) - .wrap_err_with(|| format!("converting {verilog_file:?} to an absolute path failed")) - { + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { Ok(v) => v, Err(e) => return Ok(Err(e)), }; - if verilog_file.contains(|ch: char| { - (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' - }) { - return Ok(Err(eyre!( - "verilog file path contains characters that aren't permitted" - ))); - } + for verilog_file in all_verilog_files { writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); @@ -275,15 +262,10 @@ impl JobKind for WriteSbyFileJobKind { _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); - let [ - JobItem::DynamicPaths { - paths: additional_files, - .. - }, - ] = inputs - else { + let [additional_files] = inputs else { unreachable!(); }; + let additional_files = VerilogJob::unwrap_additional_files(additional_files); let mut contents = String::new(); match job.write_sby( &mut contents, @@ -339,6 +321,15 @@ impl fmt::Debug for Formal { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Symbiyosys; + +impl ExternalProgramTrait for Symbiyosys { + fn default_program_name() -> Interned { + "sby".intern() + } +} + #[derive(Clone, Hash, PartialEq, Eq, Debug, Args)] pub struct FormalAdditionalArgs {} @@ -352,6 +343,7 @@ impl ExternalCommand for Formal { type AdditionalArgs = FormalAdditionalArgs; type AdditionalJobData = Formal; type Dependencies = JobKindAndDependencies; + type ExternalProgram = Symbiyosys; fn dependencies() -> Self::Dependencies { Default::default() @@ -394,15 +386,11 @@ impl ExternalCommand for Formal { Interned::default() } - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { - [ - // "-j1".intern(), // sby seems not to respect job count in parallel mode - "-f".intern(), - job.additional_job_data().sby_file_name, - ] - .into_iter() - .chain(job.additional_job_data().write_sby_file.sby_extra_args()) - .collect() + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode + args.write_str_arg("-f"); + args.write_interned_arg(job.additional_job_data().sby_file_name); + args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args()); } fn current_dir(job: &ExternalCommandJob) -> Option> { @@ -412,8 +400,11 @@ impl ExternalCommand for Formal { fn job_kind_name() -> Interned { "formal".intern() } - - fn default_program_name() -> Interned { - "sby".intern() - } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(WriteSbyFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + ] } diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index a90d839..0cf54d5 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -726,8 +726,8 @@ impl JobGraph { let running_job_in_thread = RunningJobInThread { job_node_id, job: job.clone(), - inputs: Result::from_iter(inputs.into_iter().map(|(input_name, input)| { - input.into_inner().wrap_err_with(|| { + inputs: Result::from_iter(job.inputs().iter().map(|input_name| { + inputs.get(input_name).and_then(|v| v.get().cloned()).wrap_err_with(|| { eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}") }) }))?, diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs index 93d06f5..ccb401f 100644 --- a/crates/fayalite/src/build/registry.rs +++ b/crates/fayalite/src/build/registry.rs @@ -2,7 +2,7 @@ // See Notices.txt for copyright information use crate::{ - build::{BUILT_IN_JOB_KINDS, DynJobKind, JobKind}, + build::{DynJobKind, JobKind, built_in_job_kinds}, intern::Interned, }; use std::{ @@ -164,8 +164,8 @@ impl Default for JobKindRegistry { let mut retval = Self { job_kinds: BTreeMap::new(), }; - for job_kind in BUILT_IN_JOB_KINDS { - Self::register(&mut retval, job_kind()); + for job_kind in built_in_job_kinds() { + Self::register(&mut retval, job_kind); } retval } diff --git a/crates/fayalite/src/build/vendor.rs b/crates/fayalite/src/build/vendor.rs new file mode 100644 index 0000000..56297cf --- /dev/null +++ b/crates/fayalite/src/build/vendor.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +pub mod xilinx; + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + xilinx::built_in_job_kinds() +} diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/build/vendor/xilinx.rs new file mode 100644 index 0000000..049c49c --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +pub mod yosys_nextpnr_prjxray; + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + yosys_nextpnr_prjxray::built_in_job_kinds() +} diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs new file mode 100644 index 0000000..36f2da4 --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -0,0 +1,814 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + build::{ + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, + interned_known_utf8_method, interned_known_utf8_path_buf_method, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + }, + intern::{Intern, Interned}, + module::NameId, + prelude::JobParams, + util::job_server::AcquiredJob, +}; +use clap::ValueEnum; +use eyre::Context; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteYsFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteYsFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteYsFile { + main_verilog_file: Interned, + ys_file: Interned, + json_file: Interned, + json_file_name: Interned, +} + +impl YosysNextpnrXrayWriteYsFile { + pub fn main_verilog_file(&self) -> Interned { + self.main_verilog_file + } + pub fn ys_file(&self) -> Interned { + self.ys_file + } + pub fn json_file(&self) -> Interned { + self.json_file + } + pub fn json_file_name(&self) -> Interned { + self.json_file_name + } + fn write_ys( + &self, + output: &mut W, + additional_files: &[Interned], + main_module_name_id: NameId, + ) -> Result, fmt::Error> { + let Self { + main_verilog_file, + ys_file: _, + json_file: _, + json_file_name, + } = self; + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + for verilog_file in all_verilog_files { + writeln!(output, "read_verilog -sv \"{verilog_file}\"")?; + } + let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); + writeln!( + output, + "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" + )?; + writeln!(output, "write_json \"{json_file_name}\"")?; + Ok(Ok(())) + } +} + +impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { + type Args = YosysNextpnrXrayWriteYsFileArgs; + type Job = YosysNextpnrXrayWriteYsFile; + type Dependencies = JobKindAndDependencies; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + mut args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.dependencies + .dependencies + .args + .args + .additional_args + .verilog_dialect + .get_or_insert(VerilogDialect::Yosys); + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteYsFileArgs {} = args; + let json_file = dependencies.base_job().file_with_ext("json"); + Ok(YosysNextpnrXrayWriteYsFile { + main_verilog_file: dependencies.job.job.main_verilog_file(), + ys_file: dependencies.base_job().file_with_ext("ys"), + json_file, + json_file_name: interned_known_utf8_method(json_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.ys_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-ys-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let [additional_files] = inputs else { + unreachable!(); + }; + let additional_files = VerilogJob::unwrap_additional_files(additional_files); + let mut contents = String::new(); + match job.write_ys( + &mut contents, + additional_files, + params.main_module().name_id(), + ) { + Ok(result) => result?, + Err(fmt::Error) => unreachable!("writing to String can't fail"), + } + std::fs::write(job.ys_file, contents) + .wrap_err_with(|| format!("writing {} failed", job.ys_file))?; + Ok(vec![JobItem::Path { path: job.ys_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXraySynthArgs {} + +impl ToArgs for YosysNextpnrXraySynthArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct YosysNextpnrXraySynth { + #[serde(flatten)] + write_ys_file: YosysNextpnrXrayWriteYsFile, + ys_file_name: Interned, +} + +impl fmt::Debug for YosysNextpnrXraySynth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + write_ys_file: + YosysNextpnrXrayWriteYsFile { + main_verilog_file, + ys_file, + json_file, + json_file_name, + }, + ys_file_name, + } = self; + f.debug_struct("YosysNextpnrXraySynth") + .field("main_verilog_file", main_verilog_file) + .field("ys_file", ys_file) + .field("ys_file_name", ys_file_name) + .field("json_file", json_file) + .field("json_file_name", json_file_name) + .finish() + } +} + +impl YosysNextpnrXraySynth { + pub fn main_verilog_file(&self) -> Interned { + self.write_ys_file.main_verilog_file() + } + pub fn ys_file(&self) -> Interned { + self.write_ys_file.ys_file() + } + pub fn ys_file_name(&self) -> Interned { + self.ys_file_name + } + pub fn json_file(&self) -> Interned { + self.write_ys_file.json_file() + } + pub fn json_file_name(&self) -> Interned { + self.write_ys_file.json_file_name() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Yosys; + +impl ExternalProgramTrait for Yosys { + fn default_program_name() -> Interned { + "yosys".intern() + } +} + +impl ExternalCommand for YosysNextpnrXraySynth { + type AdditionalArgs = YosysNextpnrXraySynthArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = Yosys; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXraySynthArgs {} = args.additional_args; + Ok(Self { + write_ys_file: dependencies.job.job.clone(), + ys_file_name: interned_known_utf8_method(dependencies.job.job.ys_file(), |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().ys_file(), + }, + JobItemName::Path { + path: job.additional_job_data().main_verilog_file(), + }, + JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().json_file()][..].intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + args.write_str_arg("-s"); + args.write_interned_arg(job.additional_job_data().ys_file_name()); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-synth".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteXdcFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteXdcFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteXdcFile { + output_dir: Interned, + xdc_file: Interned, +} + +impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { + type Args = YosysNextpnrXrayWriteXdcFileArgs; + type Job = YosysNextpnrXrayWriteXdcFile; + type Dependencies = JobKindAndDependencies>; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteXdcFileArgs {} = args; + Ok(YosysNextpnrXrayWriteXdcFile { + output_dir: dependencies.base_job().output_dir(), + xdc_file: dependencies.base_job().file_with_ext("xdc"), + }) + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.output_dir, + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.xdc_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-xdc-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + _params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + // 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] +", + )?; + Ok(vec![JobItem::Path { path: job.xdc_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct NextpnrXilinx; + +impl ExternalProgramTrait for NextpnrXilinx { + fn default_program_name() -> Interned { + "nextpnr-xilinx".intern() + } +} + +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 { + #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] + pub nextpnr_xilinx_chipdb_dir: String, + #[arg(long)] + pub device: Device, + #[arg(long, default_value_t = 0)] + pub nextpnr_xilinx_seed: i32, +} + +impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = self; + args.write_args([ + format_args!("--nextpnr-xilinx-chipdb-dir={nextpnr_xilinx_chipdb_dir}"), + format_args!("--device={device}"), + format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"), + ]); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_chipdb_dir: Interned, + device: Device, + nextpnr_xilinx_seed: i32, + xdc_file: Interned, + xdc_file_name: Interned, + json_file: Interned, + json_file_name: Interned, + routed_json_file: Interned, + routed_json_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, +} + +impl YosysNextpnrXrayRunNextpnr { + fn chipdb_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.nextpnr_xilinx_chipdb_dir, |chipdb_dir| { + let mut retval = chipdb_dir.join(self.device.xray_device()); + retval.set_extension("bin"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXrayRunNextpnr { + type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = NextpnrXilinx; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayRunNextpnrArgs { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = args.additional_args; + let xdc_file = dependencies.job.job.xdc_file; + let routed_json_file = dependencies.base_job().file_with_ext("routed.json"); + let fasm_file = dependencies.base_job().file_with_ext("fasm"); + Ok(Self { + nextpnr_xilinx_chipdb_dir: str::intern_owned(nextpnr_xilinx_chipdb_dir), + device, + nextpnr_xilinx_seed, + xdc_file, + xdc_file_name: interned_known_utf8_method(xdc_file, |v| { + v.file_name().expect("known to have file name") + }), + json_file: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file(), + json_file_name: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file_name(), + routed_json_file, + routed_json_file_name: interned_known_utf8_method(routed_json_file, |v| { + v.file_name().expect("known to have file name") + }), + fasm_file, + fasm_file_name: interned_known_utf8_method(fasm_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().json_file, + }, + JobItemName::Path { + path: job.additional_job_data().xdc_file, + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().routed_json_file, + job.additional_job_data().fasm_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_seed, + xdc_file_name, + json_file_name, + routed_json_file_name, + fasm_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--chipdb={}", job_data.chipdb_file()), + format_args!("--xdc={xdc_file_name}"), + format_args!("--json={json_file_name}"), + format_args!("--write={routed_json_file_name}"), + format_args!("--fasm={fasm_file_name}"), + format_args!("--seed={nextpnr_xilinx_seed}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-run-nextpnr".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Xcfasm; + +impl ExternalProgramTrait for Xcfasm { + fn default_program_name() -> Interned { + "xcfasm".intern() + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayArgs { + #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] + pub prjxray_db_dir: String, +} + +impl ToArgs for YosysNextpnrXrayArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { prjxray_db_dir } = self; + args.write_arg(format_args!("--prjxray-db-dir={prjxray_db_dir}")); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXray { + prjxray_db_dir: Interned, + device: Device, + fasm_file: Interned, + fasm_file_name: Interned, + frames_file: Interned, + frames_file_name: Interned, + bit_file: Interned, + bit_file_name: Interned, +} + +impl YosysNextpnrXray { + fn db_root(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + prjxray_db_dir.join(self.device.xray_family()) + }) + } + fn part_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + let mut retval = prjxray_db_dir.join(self.device.xray_family()); + retval.push(self.device.xray_part()); + retval.push("part.yaml"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXray { + type AdditionalArgs = YosysNextpnrXrayArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies>; + type ExternalProgram = Xcfasm; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; + let frames_file = dependencies.base_job().file_with_ext("frames"); + let bit_file = dependencies.base_job().file_with_ext("bit"); + Ok(Self { + prjxray_db_dir: str::intern_owned(prjxray_db_dir), + device: dependencies.job.job.additional_job_data().device, + fasm_file: dependencies.job.job.additional_job_data().fasm_file, + fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, + frames_file, + frames_file_name: interned_known_utf8_method(frames_file, |v| { + v.file_name().expect("known to have file name") + }), + bit_file, + bit_file_name: interned_known_utf8_method(bit_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.additional_job_data().fasm_file, + }][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().frames_file, + job.additional_job_data().bit_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXray { + device, + fasm_file_name, + frames_file_name, + bit_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--sparse"), + format_args!("--db-root={}", job_data.db_root()), + format_args!("--part={}", device.xray_part()), + format_args!("--part_file={}", job_data.part_file()), + format_args!("--fn_in={fasm_file_name}"), + format_args!("--frm_out={frames_file_name}"), + format_args!("--bit_out={bit_file_name}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray".intern() + } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(YosysNextpnrXrayWriteYsFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(YosysNextpnrXrayWriteXdcFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(ExternalCommandJobKind::::new()), + ] +} diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 219dda8..39334f2 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -3,9 +3,12 @@ use crate::{ build::{ - CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, - JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, - external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, firrtl::FirrtlJobKind, interned_known_utf8_method, interned_known_utf8_path_buf_method, }, @@ -13,7 +16,7 @@ use crate::{ util::job_server::AcquiredJob, }; use clap::Args; -use eyre::bail; +use eyre::{Context, bail}; use serde::{Deserialize, Serialize}; use std::{fmt, mem}; @@ -96,6 +99,15 @@ impl ToArgs for UnadjustedVerilogArgs { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Firtool; + +impl ExternalProgramTrait for Firtool { + fn default_program_name() -> Interned { + "firtool".intern() + } +} + #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] pub struct UnadjustedVerilog { firrtl_file: Interned, @@ -129,6 +141,7 @@ impl ExternalCommand for UnadjustedVerilog { type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalJobData = UnadjustedVerilog; type Dependencies = JobKindAndDependencies; + type ExternalProgram = Firtool; fn dependencies() -> Self::Dependencies { Default::default() @@ -184,7 +197,7 @@ impl ExternalCommand for UnadjustedVerilog { [job.additional_job_data().unadjusted_verilog_file][..].intern() } - fn command_line_args(job: &ExternalCommandJob) -> Interned<[Interned]> { + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { let UnadjustedVerilog { firrtl_file: _, firrtl_file_name, @@ -194,26 +207,16 @@ impl ExternalCommand for UnadjustedVerilog { verilog_dialect, verilog_debug, } = *job.additional_job_data(); - let mut retval = vec![ - firrtl_file_name, - "-o".intern(), - unadjusted_verilog_file_name, - ]; + args.write_interned_arg(firrtl_file_name); + args.write_str_arg("-o"); + args.write_interned_arg(unadjusted_verilog_file_name); if verilog_debug { - retval.push("-g".intern()); - retval.push("--preserve-values=all".intern()); + args.write_str_args(["-g", "--preserve-values=all"]); } if let Some(dialect) = verilog_dialect { - retval.extend( - dialect - .firtool_extra_args() - .iter() - .copied() - .map(str::intern), - ); + args.write_str_args(dialect.firtool_extra_args().iter().copied()); } - retval.extend_from_slice(&firtool_extra_args); - Intern::intern_owned(retval) + args.write_interned_args(firtool_extra_args); } fn current_dir(job: &ExternalCommandJob) -> Option> { @@ -224,10 +227,6 @@ impl ExternalCommand for UnadjustedVerilog { "unadjusted-verilog".intern() } - fn default_program_name() -> Interned { - "firtool".intern() - } - fn subcommand_hidden() -> bool { true } @@ -267,6 +266,43 @@ impl VerilogJob { pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } + #[track_caller] + pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { + match additional_files { + JobItem::DynamicPaths { + paths, + source_job_name, + } if *source_job_name == VerilogJobKind.name() => paths, + v => panic!("expected VerilogJob's additional files JobItem: {v:?}"), + } + } + pub fn all_verilog_files( + main_verilog_file: Interned, + additional_files: &[Interned], + ) -> eyre::Result]>> { + let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1)); + for verilog_file in [main_verilog_file].iter().chain(additional_files) { + if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { + continue; + } + let verilog_file = std::path::absolute(verilog_file) + .and_then(|v| { + v.into_os_string().into_string().map_err(|_| { + std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") + }) + }) + .wrap_err_with(|| { + format!("converting {verilog_file:?} to an absolute path failed") + })?; + if verilog_file.contains(|ch: char| { + (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' + }) { + bail!("verilog file path contains characters that aren't permitted"); + } + retval.push(str::intern_owned(verilog_file)); + } + Ok(Intern::intern_owned(retval)) + } } impl JobKind for VerilogJobKind { @@ -371,3 +407,10 @@ impl JobKind for VerilogJobKind { ]) } } + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(VerilogJobKind), + ] +} diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index e7465bf..47c7cfa 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -125,7 +125,7 @@ fn make_assert_formal_args( let dependencies = JobArgsAndDependencies { args, dependencies }; let args = JobKindAndArgs { kind: ExternalCommandJobKind::new(), - args: ExternalCommandArgs::new( + args: ExternalCommandArgs::resolve_program_path( None, UnadjustedVerilogArgs { firtool_extra_args: vec![], @@ -153,7 +153,7 @@ fn make_assert_formal_args( let dependencies = JobArgsAndDependencies { args, dependencies }; let args = JobKindAndArgs { kind: ExternalCommandJobKind::new(), - args: ExternalCommandArgs::new(None, FormalAdditionalArgs {})?, + args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?, }; Ok(JobArgsAndDependencies { args, dependencies }) }