1
0
Fork 0

WIP adding yosys-nextpnr-xray xilinx fpga toolchain -- blinky works on arty a7 100t (except for inverted reset)

This commit is contained in:
Jacob Lifshay 2025-10-09 02:22:11 -07:00
parent 338ce7ed32
commit 74ad0e104e
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
11 changed files with 1250 additions and 164 deletions

View file

@ -31,17 +31,20 @@ pub mod firrtl;
pub mod formal; pub mod formal;
pub mod graph; pub mod graph;
pub mod registry; pub mod registry;
pub mod vendor;
pub mod verilog; pub mod verilog;
pub(crate) const BUILT_IN_JOB_KINDS: &'static [fn() -> DynJobKind] = &[ pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|| DynJobKind::new(BaseJobKind), [
|| DynJobKind::new(CreateOutputDirJobKind), DynJobKind::new(BaseJobKind),
|| DynJobKind::new(firrtl::FirrtlJobKind), DynJobKind::new(CreateOutputDirJobKind),
|| DynJobKind::new(external::ExternalCommandJobKind::<verilog::UnadjustedVerilog>::new()), ]
|| DynJobKind::new(verilog::VerilogJobKind), .into_iter()
|| DynJobKind::new(formal::WriteSbyFileJobKind), .chain(firrtl::built_in_job_kinds())
|| DynJobKind::new(external::ExternalCommandJobKind::<formal::Formal>::new()), .chain(formal::built_in_job_kinds())
]; .chain(vendor::built_in_job_kinds())
.chain(verilog::built_in_job_kinds())
}
#[track_caller] #[track_caller]
fn intern_known_utf8_path_buf(v: PathBuf) -> Interned<str> { fn intern_known_utf8_path_buf(v: PathBuf) -> Interned<str> {
@ -99,7 +102,7 @@ impl JobItem {
} }
} }
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[non_exhaustive] #[non_exhaustive]
pub enum JobItemName { pub enum JobItemName {
Path { path: Interned<str> }, Path { path: Interned<str> },
@ -171,12 +174,30 @@ pub trait WriteArgs:
fn write_interned_arg(&mut self, arg: Interned<str>) { fn write_interned_arg(&mut self, arg: Interned<str>) {
self.extend([arg]); 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<str>) -> Option<&str>;
} }
#[derive(Default)] pub struct ArgsWriter<T>(pub Vec<T>);
struct WriteArgsWrapper<W>(W);
impl<'a, W> Extend<fmt::Arguments<'a>> for WriteArgsWrapper<W> impl<T> Default for ArgsWriter<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T: AsRef<str>> ArgsWriter<T> {
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<fmt::Arguments<'a>> for ArgsWriter<W>
where where
Self: Extend<String>, Self: Extend<String>,
{ {
@ -185,39 +206,47 @@ where
} }
} }
impl<'a> Extend<&'a str> for WriteArgsWrapper<Vec<Interned<str>>> { impl<'a> Extend<&'a str> for ArgsWriter<Interned<str>> {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(str::intern)) self.extend(iter.into_iter().map(str::intern))
} }
} }
impl Extend<String> for WriteArgsWrapper<Vec<Interned<str>>> { impl Extend<String> for ArgsWriter<Interned<str>> {
fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(str::intern_owned)) self.extend(iter.into_iter().map(str::intern_owned))
} }
} }
impl Extend<Interned<str>> for WriteArgsWrapper<Vec<String>> { impl Extend<Interned<str>> for ArgsWriter<String> {
fn extend<T: IntoIterator<Item = Interned<str>>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = Interned<str>>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(String::from)) self.extend(iter.into_iter().map(String::from))
} }
} }
impl<'a> Extend<&'a str> for WriteArgsWrapper<Vec<String>> { impl<'a> Extend<&'a str> for ArgsWriter<String> {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
self.extend(iter.into_iter().map(String::from)) self.extend(iter.into_iter().map(String::from))
} }
} }
impl<I> Extend<I> for WriteArgsWrapper<Vec<I>> { impl<I> Extend<I> for ArgsWriter<I> {
fn extend<T: IntoIterator<Item = I>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = I>>(&mut self, iter: T) {
self.0.extend(iter); self.0.extend(iter);
} }
} }
impl WriteArgs for WriteArgsWrapper<Vec<String>> {} impl WriteArgs for ArgsWriter<String> {
fn get_long_option_eq(&self, option_name: impl AsRef<str>) -> Option<&str> {
self.get_long_option_eq_helper(option_name.as_ref())
}
}
impl WriteArgs for WriteArgsWrapper<Vec<Interned<str>>> {} impl WriteArgs for ArgsWriter<Interned<str>> {
fn get_long_option_eq(&self, option_name: impl AsRef<str>) -> 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 { pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); 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()) Intern::intern_owned(self.to_interned_args_vec())
} }
fn to_interned_args_vec(&self) -> Vec<Interned<str>> { fn to_interned_args_vec(&self) -> Vec<Interned<str>> {
let mut retval = WriteArgsWrapper::default(); let mut retval = ArgsWriter::default();
self.to_args(&mut retval); self.to_args(&mut retval);
retval.0 retval.0
} }
fn to_string_args(&self) -> Vec<String> { fn to_string_args(&self) -> Vec<String> {
let mut retval = WriteArgsWrapper::default(); let mut retval = ArgsWriter::default();
self.to_args(&mut retval); self.to_args(&mut retval);
retval.0 retval.0
} }
@ -642,6 +671,9 @@ pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy
fn subcommand_hidden(self) -> bool { fn subcommand_hidden(self) -> bool {
false false
} }
fn external_program(self) -> Option<Interned<external::ExternalProgram>> {
None
}
} }
trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug {
@ -1010,7 +1042,7 @@ impl<K: JobKind> DynJobArgsTrait for DynJobArgsInner<K> {
} }
fn to_args_extend_vec(&self, args: Vec<Interned<str>>) -> Vec<Interned<str>> { fn to_args_extend_vec(&self, args: Vec<Interned<str>>) -> Vec<Interned<str>> {
let mut writer = WriteArgsWrapper(args); let mut writer = ArgsWriter(args);
self.0.args.to_args(&mut writer); self.0.args.to_args(&mut writer);
writer.0 writer.0
} }

View file

@ -3,9 +3,9 @@
use crate::{ use crate::{
build::{ build::{
CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, JobArgsAndDependencies, ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind,
JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, JobParams, ToArgs, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs,
WriteArgs, intern_known_utf8_path_buf, JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf,
}, },
intern::{Intern, Interned}, intern::{Intern, Interned},
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
@ -431,7 +431,7 @@ impl<T: ExternalCommand> ExternalCommandJobKind<T> {
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct ExternalCommandProgramPathValueParser<T: ExternalCommand>(PhantomData<T>); struct ExternalProgramPathValueParser(ExternalProgram);
fn parse_which_result( fn parse_which_result(
which_result: which::Result<PathBuf>, which_result: which::Result<PathBuf>,
@ -460,9 +460,7 @@ fn parse_which_result(
)) ))
} }
impl<T: ExternalCommand> clap::builder::TypedValueParser impl clap::builder::TypedValueParser for ExternalProgramPathValueParser {
for ExternalCommandProgramPathValueParser<T>
{
type Value = Interned<str>; type Value = Interned<str>;
fn parse_ref( fn parse_ref(
@ -471,10 +469,11 @@ impl<T: ExternalCommand> clap::builder::TypedValueParser
arg: Option<&clap::Arg>, arg: Option<&clap::Arg>,
value: &OsStr, value: &OsStr,
) -> clap::error::Result<Self::Value> { ) -> clap::error::Result<Self::Value> {
let program_path_arg_name = self.0.program_path_arg_name;
OsStringValueParser::new() OsStringValueParser::new()
.try_map(|program_name| { .try_map(move |program_name| {
parse_which_result(which::which(&program_name), 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) .parse_ref(cmd, arg, value)
@ -485,16 +484,8 @@ impl<T: ExternalCommand> clap::builder::TypedValueParser
#[group(id = T::args_group_id())] #[group(id = T::args_group_id())]
#[non_exhaustive] #[non_exhaustive]
pub struct ExternalCommandArgs<T: ExternalCommand> { pub struct ExternalCommandArgs<T: ExternalCommand> {
#[arg( #[command(flatten)]
name = Interned::into_inner(T::program_path_arg_name()), pub program_path: ExternalProgramPath<T::ExternalProgram>,
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::<T>(PhantomData),
default_value = T::default_program_name(),
value_hint = clap::ValueHint::CommandName,
)]
pub program_path: Interned<str>,
#[arg( #[arg(
name = Interned::into_inner(T::run_even_if_cached_arg_name()), name = Interned::into_inner(T::run_even_if_cached_arg_name()),
long = T::run_even_if_cached_arg_name(), long = T::run_even_if_cached_arg_name(),
@ -575,6 +566,15 @@ impl<T: ExternalCommand> ExternalCommandArgs<T> {
pub fn with_resolved_program_path( pub fn with_resolved_program_path(
program_path: Interned<str>, program_path: Interned<str>,
additional_args: T::AdditionalArgs, additional_args: T::AdditionalArgs,
) -> Self {
Self::new(
ExternalProgramPath::with_resolved_program_path(program_path),
additional_args,
)
}
pub fn new(
program_path: ExternalProgramPath<T::ExternalProgram>,
additional_args: T::AdditionalArgs,
) -> Self { ) -> Self {
Self { Self {
program_path, program_path,
@ -582,16 +582,12 @@ impl<T: ExternalCommand> ExternalCommandArgs<T> {
additional_args, additional_args,
} }
} }
pub fn new( pub fn resolve_program_path(
program_name: Option<&OsStr>, program_name: Option<&OsStr>,
additional_args: T::AdditionalArgs, additional_args: T::AdditionalArgs,
) -> Result<Self, ResolveProgramPathError> { ) -> Result<Self, ResolveProgramPathError> {
Ok(Self::with_resolved_program_path( Ok(Self::new(
resolve_program_path( ExternalProgramPath::resolve_program_path(program_name)?,
program_name,
T::default_program_name(),
T::program_path_env_var_name().as_ref().map(AsRef::as_ref),
)?,
additional_args, additional_args,
)) ))
} }
@ -604,10 +600,7 @@ impl<T: ExternalCommand> ToArgs for ExternalCommandArgs<T> {
run_even_if_cached, run_even_if_cached,
ref additional_args, ref additional_args,
} = *self; } = *self;
args.write_arg(format_args!( program_path.to_args(args);
"--{}={program_path}",
T::program_path_arg_name()
));
if run_even_if_cached { if run_even_if_cached {
args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name()));
} }
@ -626,13 +619,11 @@ struct ExternalCommandJobParams {
impl ExternalCommandJobParams { impl ExternalCommandJobParams {
fn new<T: ExternalCommand>(job: &ExternalCommandJob<T>) -> Self { fn new<T: ExternalCommand>(job: &ExternalCommandJob<T>) -> Self {
let output_paths = T::output_paths(job); 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 { Self {
command_params: CommandParams { command_params: CommandParams {
command_line: Interned::from_iter( command_line: Intern::intern_owned(command_line.0),
[job.program_path]
.into_iter()
.chain(T::command_line_args(job).iter().copied()),
),
current_dir: T::current_dir(job), current_dir: T::current_dir(job),
}, },
inputs: T::inputs(job), inputs: T::inputs(job),
@ -758,33 +749,184 @@ impl<T: ExternalCommand> ExternalCommandJob<T> {
} }
} }
pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Clone { #[derive(Copy, Clone, PartialEq, Eq, Hash)]
type AdditionalArgs: ToArgs; pub struct ExternalProgramPath<T: ExternalProgramTrait> {
type AdditionalJobData: 'static program_path: Interned<str>,
+ Send _phantom: PhantomData<T>,
+ Sync }
+ Hash
+ Eq impl<T: ExternalProgramTrait> ExternalProgramPath<T> {
+ fmt::Debug pub fn with_resolved_program_path(program_path: Interned<str>) -> Self {
+ Serialize Self {
+ DeserializeOwned; program_path,
type Dependencies: JobDependencies<JobsAndKinds: GetBaseJob>; _phantom: PhantomData,
fn dependencies() -> Self::Dependencies; }
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)>;
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]>;
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]>;
fn command_line_args(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]>;
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>>;
fn job_kind_name() -> Interned<str>;
fn args_group_id() -> clap::Id {
Interned::into_inner(Self::job_kind_name()).into()
} }
pub fn resolve_program_path(
program_name: Option<&OsStr>,
) -> Result<Self, ResolveProgramPathError> {
let ExternalProgram {
default_program_name,
program_path_arg_name: _,
program_path_arg_value_name: _,
program_path_env_var_name,
} = ExternalProgram::new::<T>();
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<str> {
self.program_path
}
}
impl<T: ExternalProgramTrait> fmt::Debug for ExternalProgramPath<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
program_path,
_phantom: _,
} = self;
write!(f, "ExternalProgramPath<{}>", std::any::type_name::<T>())?;
f.debug_tuple("").field(program_path).finish()
}
}
impl<T: ExternalProgramTrait> clap::FromArgMatches for ExternalProgramPath<T> {
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
let id = Interned::into_inner(ExternalProgram::new::<T>().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<T: ExternalProgramTrait> clap::Args for ExternalProgramPath<T> {
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::<T>();
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<T: ExternalProgramTrait> ToArgs for ExternalProgramPath<T> {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let ExternalProgram {
program_path_arg_name,
..
} = ExternalProgram::new::<T>();
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<str>,
program_path_arg_name: Interned<str>,
program_path_arg_value_name: Interned<str>,
program_path_env_var_name: Option<Interned<str>>,
}
impl ExternalProgram {
pub fn new<T: ExternalProgramTrait>() -> 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<str> {
self.default_program_name
}
pub fn program_path_arg_name(&self) -> Interned<str> {
self.program_path_arg_name
}
pub fn program_path_arg_value_name(&self) -> Interned<str> {
self.program_path_arg_value_name
}
pub fn program_path_env_var_name(&self) -> Option<Interned<str>> {
self.program_path_env_var_name
}
}
impl<T: ExternalProgramTrait> From<T> for ExternalProgram {
fn from(_value: T) -> Self {
Self::new::<T>()
}
}
impl<T: ExternalProgramTrait> From<T> for Interned<ExternalProgram> {
fn from(_value: T) -> Self {
ExternalProgram::new::<T>().intern_sized()
}
}
pub trait ExternalProgramTrait:
'static + Send + Sync + Hash + Ord + fmt::Debug + Default + Copy
{
fn program_path_arg_name() -> Interned<str> { fn program_path_arg_name() -> Interned<str> {
Self::default_program_name() Self::default_program_name()
} }
@ -799,6 +941,36 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size
.replace('-', "_"), .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<JobsAndKinds: GetBaseJob>;
type ExternalProgram: ExternalProgramTrait;
fn dependencies() -> Self::Dependencies;
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)>;
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]>;
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]>;
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W);
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>>;
fn job_kind_name() -> Interned<str>;
fn args_group_id() -> clap::Id {
Interned::into_inner(Self::job_kind_name()).into()
}
fn run_even_if_cached_arg_name() -> Interned<str> { fn run_even_if_cached_arg_name() -> Interned<str> {
Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name())) Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name()))
} }
@ -824,7 +996,11 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
kind, kind,
args: args:
ExternalCommandArgs { ExternalCommandArgs {
program_path, program_path:
ExternalProgramPath {
program_path,
_phantom: _,
},
run_even_if_cached, run_even_if_cached,
additional_args: _, additional_args: _,
}, },
@ -868,7 +1044,12 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
params: &JobParams, params: &JobParams,
acquired_job: &mut AcquiredJob, acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> { ) -> eyre::Result<Vec<JobItem>> {
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::<Self>(),
job.inputs(),
);
let CommandParams { let CommandParams {
command_line, command_line,
current_dir, current_dir,
@ -913,4 +1094,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
fn subcommand_hidden(self) -> bool { fn subcommand_hidden(self) -> bool {
T::subcommand_hidden() T::subcommand_hidden()
} }
fn external_program(self) -> Option<Interned<ExternalProgram>> {
Some(ExternalProgram::new::<T::ExternalProgram>().intern_sized())
}
} }

View file

@ -3,8 +3,9 @@
use crate::{ use crate::{
build::{ build::{
BaseJob, BaseJobKind, CommandParams, JobAndDependencies, JobArgsAndDependencies, JobItem, BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies,
JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams,
ToArgs, WriteArgs,
}, },
firrtl::{ExportOptions, FileBackend}, firrtl::{ExportOptions, FileBackend},
intern::{Intern, Interned}, intern::{Intern, Interned},
@ -118,3 +119,7 @@ impl JobKind for FirrtlJobKind {
}]) }])
} }
} }
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[DynJobKind::new(FirrtlJobKind)]
}

View file

@ -3,18 +3,21 @@
use crate::{ use crate::{
build::{ build::{
CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies,
JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs,
external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
interned_known_utf8_method, interned_known_utf8_method,
verilog::{VerilogDialect, VerilogJobKind}, verilog::{VerilogDialect, VerilogJob, VerilogJobKind},
}, },
intern::{Intern, Interned}, intern::{Intern, Interned},
module::NameId, module::NameId,
util::job_server::AcquiredJob, util::job_server::AcquiredJob,
}; };
use clap::{Args, ValueEnum}; use clap::{Args, ValueEnum};
use eyre::{Context, eyre}; use eyre::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
@ -167,28 +170,12 @@ impl WriteSbyFileJob {
\n\ \n\
[script]\n", [script]\n",
)?; )?;
for verilog_file in [main_verilog_file].into_iter().chain(additional_files) { let all_verilog_files =
if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) {
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"))
{
Ok(v) => v, Ok(v) => v,
Err(e) => return Ok(Err(e)), Err(e) => return Ok(Err(e)),
}; };
if verilog_file.contains(|ch: char| { for verilog_file in all_verilog_files {
(ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"'
}) {
return Ok(Err(eyre!(
"verilog file path contains characters that aren't permitted"
)));
}
writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?;
} }
let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id);
@ -275,15 +262,10 @@ impl JobKind for WriteSbyFileJobKind {
_acquired_job: &mut AcquiredJob, _acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> { ) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
let [ let [additional_files] = inputs else {
JobItem::DynamicPaths {
paths: additional_files,
..
},
] = inputs
else {
unreachable!(); unreachable!();
}; };
let additional_files = VerilogJob::unwrap_additional_files(additional_files);
let mut contents = String::new(); let mut contents = String::new();
match job.write_sby( match job.write_sby(
&mut contents, &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<str> {
"sby".intern()
}
}
#[derive(Clone, Hash, PartialEq, Eq, Debug, Args)] #[derive(Clone, Hash, PartialEq, Eq, Debug, Args)]
pub struct FormalAdditionalArgs {} pub struct FormalAdditionalArgs {}
@ -352,6 +343,7 @@ impl ExternalCommand for Formal {
type AdditionalArgs = FormalAdditionalArgs; type AdditionalArgs = FormalAdditionalArgs;
type AdditionalJobData = Formal; type AdditionalJobData = Formal;
type Dependencies = JobKindAndDependencies<WriteSbyFileJobKind>; type Dependencies = JobKindAndDependencies<WriteSbyFileJobKind>;
type ExternalProgram = Symbiyosys;
fn dependencies() -> Self::Dependencies { fn dependencies() -> Self::Dependencies {
Default::default() Default::default()
@ -394,15 +386,11 @@ impl ExternalCommand for Formal {
Interned::default() Interned::default()
} }
fn command_line_args(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]> { fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
[ // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode
// "-j1".intern(), // sby seems not to respect job count in parallel mode args.write_str_arg("-f");
"-f".intern(), args.write_interned_arg(job.additional_job_data().sby_file_name);
job.additional_job_data().sby_file_name, args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args());
]
.into_iter()
.chain(job.additional_job_data().write_sby_file.sby_extra_args())
.collect()
} }
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> { fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> {
@ -412,8 +400,11 @@ impl ExternalCommand for Formal {
fn job_kind_name() -> Interned<str> { fn job_kind_name() -> Interned<str> {
"formal".intern() "formal".intern()
} }
}
fn default_program_name() -> Interned<str> {
"sby".intern() pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
} [
DynJobKind::new(WriteSbyFileJobKind),
DynJobKind::new(ExternalCommandJobKind::<Formal>::new()),
]
} }

View file

@ -726,8 +726,8 @@ impl JobGraph {
let running_job_in_thread = RunningJobInThread { let running_job_in_thread = RunningJobInThread {
job_node_id, job_node_id,
job: job.clone(), job: job.clone(),
inputs: Result::from_iter(inputs.into_iter().map(|(input_name, input)| { inputs: Result::from_iter(job.inputs().iter().map(|input_name| {
input.into_inner().wrap_err_with(|| { 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:?}") eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}")
}) })
}))?, }))?,

View file

@ -2,7 +2,7 @@
// See Notices.txt for copyright information // See Notices.txt for copyright information
use crate::{ use crate::{
build::{BUILT_IN_JOB_KINDS, DynJobKind, JobKind}, build::{DynJobKind, JobKind, built_in_job_kinds},
intern::Interned, intern::Interned,
}; };
use std::{ use std::{
@ -164,8 +164,8 @@ impl Default for JobKindRegistry {
let mut retval = Self { let mut retval = Self {
job_kinds: BTreeMap::new(), job_kinds: BTreeMap::new(),
}; };
for job_kind in BUILT_IN_JOB_KINDS { for job_kind in built_in_job_kinds() {
Self::register(&mut retval, job_kind()); Self::register(&mut retval, job_kind);
} }
retval retval
} }

View file

@ -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<Item = crate::build::DynJobKind> {
xilinx::built_in_job_kinds()
}

View file

@ -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<Item = crate::build::DynJobKind> {
yosys_nextpnr_prjxray::built_in_job_kinds()
}

View file

@ -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<str>,
ys_file: Interned<str>,
json_file: Interned<str>,
json_file_name: Interned<str>,
}
impl YosysNextpnrXrayWriteYsFile {
pub fn main_verilog_file(&self) -> Interned<str> {
self.main_verilog_file
}
pub fn ys_file(&self) -> Interned<str> {
self.ys_file
}
pub fn json_file(&self) -> Interned<str> {
self.json_file
}
pub fn json_file_name(&self) -> Interned<str> {
self.json_file_name
}
fn write_ys<W: ?Sized + fmt::Write>(
&self,
output: &mut W,
additional_files: &[Interned<str>],
main_module_name_id: NameId,
) -> Result<eyre::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<VerilogJobKind>;
fn dependencies(self) -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
) -> eyre::Result<JobAndDependencies<Self>> {
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<str> {
"yosys-nextpnr-xray-write-ys-file".intern()
}
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
None
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
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<str>,
}
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<str> {
self.write_ys_file.main_verilog_file()
}
pub fn ys_file(&self) -> Interned<str> {
self.write_ys_file.ys_file()
}
pub fn ys_file_name(&self) -> Interned<str> {
self.ys_file_name
}
pub fn json_file(&self) -> Interned<str> {
self.write_ys_file.json_file()
}
pub fn json_file_name(&self) -> Interned<str> {
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<str> {
"yosys".intern()
}
}
impl ExternalCommand for YosysNextpnrXraySynth {
type AdditionalArgs = YosysNextpnrXraySynthArgs;
type AdditionalJobData = Self;
type Dependencies = JobKindAndDependencies<YosysNextpnrXrayWriteYsFileJobKind>;
type ExternalProgram = Yosys;
fn dependencies() -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::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<Self>) -> 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<Self>) -> Interned<[Interned<str>]> {
[job.additional_job_data().json_file()][..].intern()
}
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
args.write_str_arg("-s");
args.write_interned_arg(job.additional_job_data().ys_file_name());
}
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> {
Some(job.output_dir())
}
fn job_kind_name() -> Interned<str> {
"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<str>,
xdc_file: Interned<str>,
}
impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
type Args = YosysNextpnrXrayWriteXdcFileArgs;
type Job = YosysNextpnrXrayWriteXdcFile;
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<YosysNextpnrXraySynth>>;
fn dependencies(self) -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
) -> eyre::Result<JobAndDependencies<Self>> {
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<str> {
"yosys-nextpnr-xray-write-xdc-file".intern()
}
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
None
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
_params: &JobParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
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<str> {
"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<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match $Device::from_str(v, false) {
Ok(v) => Ok(v),
Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)),
}
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) {
Some(v) => Ok(v),
None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)),
}
}
}
impl<'de> Deserialize<'de> for $Device {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(DeviceVisitor)
}
}
impl Serialize for $Device {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct YosysNextpnrXrayRunNextpnrArgs {
#[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<str>,
device: Device,
nextpnr_xilinx_seed: i32,
xdc_file: Interned<str>,
xdc_file_name: Interned<str>,
json_file: Interned<str>,
json_file_name: Interned<str>,
routed_json_file: Interned<str>,
routed_json_file_name: Interned<str>,
fasm_file: Interned<str>,
fasm_file_name: Interned<str>,
}
impl YosysNextpnrXrayRunNextpnr {
fn chipdb_file(&self) -> Interned<str> {
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<YosysNextpnrXrayWriteXdcFileJobKind>;
type ExternalProgram = NextpnrXilinx;
fn dependencies() -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::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<Self>) -> 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<Self>) -> Interned<[Interned<str>]> {
[
job.additional_job_data().routed_json_file,
job.additional_job_data().fasm_file,
][..]
.intern()
}
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, 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<Self>) -> Option<Interned<str>> {
Some(job.output_dir())
}
fn job_kind_name() -> Interned<str> {
"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<str> {
"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<str>,
device: Device,
fasm_file: Interned<str>,
fasm_file_name: Interned<str>,
frames_file: Interned<str>,
frames_file_name: Interned<str>,
bit_file: Interned<str>,
bit_file_name: Interned<str>,
}
impl YosysNextpnrXray {
fn db_root(&self) -> Interned<str> {
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<str> {
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<ExternalCommandJobKind<YosysNextpnrXrayRunNextpnr>>;
type ExternalProgram = Xcfasm;
fn dependencies() -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::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<Self>) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.additional_job_data().fasm_file,
}][..]
.intern()
}
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]> {
[
job.additional_job_data().frames_file,
job.additional_job_data().bit_file,
][..]
.intern()
}
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, 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<Self>) -> Option<Interned<str>> {
Some(job.output_dir())
}
fn job_kind_name() -> Interned<str> {
"yosys-nextpnr-xray".intern()
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[
DynJobKind::new(YosysNextpnrXrayWriteYsFileJobKind),
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrXraySynth>::new()),
DynJobKind::new(YosysNextpnrXrayWriteXdcFileJobKind),
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrXrayRunNextpnr>::new()),
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrXray>::new()),
]
}

View file

@ -3,9 +3,12 @@
use crate::{ use crate::{
build::{ build::{
CommandParams, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, JobDependencies, CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies,
JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs,
external::{ExternalCommand, ExternalCommandJob, ExternalCommandJobKind}, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
firrtl::FirrtlJobKind, firrtl::FirrtlJobKind,
interned_known_utf8_method, interned_known_utf8_path_buf_method, interned_known_utf8_method, interned_known_utf8_path_buf_method,
}, },
@ -13,7 +16,7 @@ use crate::{
util::job_server::AcquiredJob, util::job_server::AcquiredJob,
}; };
use clap::Args; use clap::Args;
use eyre::bail; use eyre::{Context, bail};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt, mem}; 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<str> {
"firtool".intern()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)]
pub struct UnadjustedVerilog { pub struct UnadjustedVerilog {
firrtl_file: Interned<str>, firrtl_file: Interned<str>,
@ -129,6 +141,7 @@ impl ExternalCommand for UnadjustedVerilog {
type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalArgs = UnadjustedVerilogArgs;
type AdditionalJobData = UnadjustedVerilog; type AdditionalJobData = UnadjustedVerilog;
type Dependencies = JobKindAndDependencies<FirrtlJobKind>; type Dependencies = JobKindAndDependencies<FirrtlJobKind>;
type ExternalProgram = Firtool;
fn dependencies() -> Self::Dependencies { fn dependencies() -> Self::Dependencies {
Default::default() Default::default()
@ -184,7 +197,7 @@ impl ExternalCommand for UnadjustedVerilog {
[job.additional_job_data().unadjusted_verilog_file][..].intern() [job.additional_job_data().unadjusted_verilog_file][..].intern()
} }
fn command_line_args(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]> { fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
let UnadjustedVerilog { let UnadjustedVerilog {
firrtl_file: _, firrtl_file: _,
firrtl_file_name, firrtl_file_name,
@ -194,26 +207,16 @@ impl ExternalCommand for UnadjustedVerilog {
verilog_dialect, verilog_dialect,
verilog_debug, verilog_debug,
} = *job.additional_job_data(); } = *job.additional_job_data();
let mut retval = vec![ args.write_interned_arg(firrtl_file_name);
firrtl_file_name, args.write_str_arg("-o");
"-o".intern(), args.write_interned_arg(unadjusted_verilog_file_name);
unadjusted_verilog_file_name,
];
if verilog_debug { if verilog_debug {
retval.push("-g".intern()); args.write_str_args(["-g", "--preserve-values=all"]);
retval.push("--preserve-values=all".intern());
} }
if let Some(dialect) = verilog_dialect { if let Some(dialect) = verilog_dialect {
retval.extend( args.write_str_args(dialect.firtool_extra_args().iter().copied());
dialect
.firtool_extra_args()
.iter()
.copied()
.map(str::intern),
);
} }
retval.extend_from_slice(&firtool_extra_args); args.write_interned_args(firtool_extra_args);
Intern::intern_owned(retval)
} }
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> { fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> {
@ -224,10 +227,6 @@ impl ExternalCommand for UnadjustedVerilog {
"unadjusted-verilog".intern() "unadjusted-verilog".intern()
} }
fn default_program_name() -> Interned<str> {
"firtool".intern()
}
fn subcommand_hidden() -> bool { fn subcommand_hidden() -> bool {
true true
} }
@ -267,6 +266,43 @@ impl VerilogJob {
pub fn main_verilog_file(&self) -> Interned<str> { pub fn main_verilog_file(&self) -> Interned<str> {
self.main_verilog_file self.main_verilog_file
} }
#[track_caller]
pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned<str>] {
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<str>,
additional_files: &[Interned<str>],
) -> eyre::Result<Interned<[Interned<str>]>> {
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 { impl JobKind for VerilogJobKind {
@ -371,3 +407,10 @@ impl JobKind for VerilogJobKind {
]) ])
} }
} }
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[
DynJobKind::new(ExternalCommandJobKind::<UnadjustedVerilog>::new()),
DynJobKind::new(VerilogJobKind),
]
}

View file

@ -125,7 +125,7 @@ fn make_assert_formal_args(
let dependencies = JobArgsAndDependencies { args, dependencies }; let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs { let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(), kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::new( args: ExternalCommandArgs::resolve_program_path(
None, None,
UnadjustedVerilogArgs { UnadjustedVerilogArgs {
firtool_extra_args: vec![], firtool_extra_args: vec![],
@ -153,7 +153,7 @@ fn make_assert_formal_args(
let dependencies = JobArgsAndDependencies { args, dependencies }; let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs { let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(), kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::new(None, FormalAdditionalArgs {})?, args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?,
}; };
Ok(JobArgsAndDependencies { args, dependencies }) Ok(JobArgsAndDependencies { args, dependencies })
} }