WIP adding yosys-nextpnr-xray xilinx fpga toolchain -- blinky works on arty a7 100t (except for inverted reset)
Some checks failed
/ deps (pull_request) Failing after 1m0s
/ test (pull_request) Has been skipped

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 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::<verilog::UnadjustedVerilog>::new()),
|| DynJobKind::new(verilog::VerilogJobKind),
|| DynJobKind::new(formal::WriteSbyFileJobKind),
|| DynJobKind::new(external::ExternalCommandJobKind::<formal::Formal>::new()),
];
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[
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<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]
pub enum JobItemName {
Path { path: Interned<str> },
@ -171,12 +174,30 @@ pub trait WriteArgs:
fn write_interned_arg(&mut self, arg: Interned<str>) {
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)]
struct WriteArgsWrapper<W>(W);
pub struct ArgsWriter<T>(pub Vec<T>);
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
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) {
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) {
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) {
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) {
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) {
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 {
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<Interned<str>> {
let mut retval = WriteArgsWrapper::default();
let mut retval = ArgsWriter::default();
self.to_args(&mut retval);
retval.0
}
fn to_string_args(&self) -> Vec<String> {
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<Interned<external::ExternalProgram>> {
None
}
}
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>> {
let mut writer = WriteArgsWrapper(args);
let mut writer = ArgsWriter(args);
self.0.args.to_args(&mut writer);
writer.0
}

View file

@ -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<T: ExternalCommand> ExternalCommandJobKind<T> {
}
#[derive(Copy, Clone)]
struct ExternalCommandProgramPathValueParser<T: ExternalCommand>(PhantomData<T>);
struct ExternalProgramPathValueParser(ExternalProgram);
fn parse_which_result(
which_result: which::Result<PathBuf>,
@ -460,9 +460,7 @@ fn parse_which_result(
))
}
impl<T: ExternalCommand> clap::builder::TypedValueParser
for ExternalCommandProgramPathValueParser<T>
{
impl clap::builder::TypedValueParser for ExternalProgramPathValueParser {
type Value = Interned<str>;
fn parse_ref(
@ -471,10 +469,11 @@ impl<T: ExternalCommand> clap::builder::TypedValueParser
arg: Option<&clap::Arg>,
value: &OsStr,
) -> clap::error::Result<Self::Value> {
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<T: ExternalCommand> clap::builder::TypedValueParser
#[group(id = T::args_group_id())]
#[non_exhaustive]
pub struct ExternalCommandArgs<T: ExternalCommand> {
#[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::<T>(PhantomData),
default_value = T::default_program_name(),
value_hint = clap::ValueHint::CommandName,
)]
pub program_path: Interned<str>,
#[command(flatten)]
pub program_path: ExternalProgramPath<T::ExternalProgram>,
#[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<T: ExternalCommand> ExternalCommandArgs<T> {
pub fn with_resolved_program_path(
program_path: Interned<str>,
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 {
program_path,
@ -582,16 +582,12 @@ impl<T: ExternalCommand> ExternalCommandArgs<T> {
additional_args,
}
}
pub fn new(
pub fn resolve_program_path(
program_name: Option<&OsStr>,
additional_args: T::AdditionalArgs,
) -> Result<Self, ResolveProgramPathError> {
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<T: ExternalCommand> ToArgs for ExternalCommandArgs<T> {
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<T: ExternalCommand>(job: &ExternalCommandJob<T>) -> 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<T: ExternalCommand> ExternalCommandJob<T> {
}
}
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>;
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()
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExternalProgramPath<T: ExternalProgramTrait> {
program_path: Interned<str>,
_phantom: PhantomData<T>,
}
impl<T: ExternalProgramTrait> ExternalProgramPath<T> {
pub fn with_resolved_program_path(program_path: Interned<str>) -> Self {
Self {
program_path,
_phantom: PhantomData,
}
}
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> {
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<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> {
Intern::intern_owned(format!("{}-run-even-if-cached", Self::job_kind_name()))
}
@ -824,7 +996,11 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
kind,
args:
ExternalCommandArgs {
program_path,
program_path:
ExternalProgramPath {
program_path,
_phantom: _,
},
run_even_if_cached,
additional_args: _,
},
@ -868,7 +1044,12 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
params: &JobParams,
acquired_job: &mut AcquiredJob,
) -> 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 {
command_line,
current_dir,
@ -913,4 +1094,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
fn subcommand_hidden(self) -> bool {
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::{
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<Item = DynJobKind> {
[DynJobKind::new(FirrtlJobKind)]
}

View file

@ -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<Vec<JobItem>> {
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<str> {
"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<WriteSbyFileJobKind>;
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<Self>) -> Interned<[Interned<str>]> {
[
// "-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<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, 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<Self>) -> Option<Interned<str>> {
@ -412,8 +400,11 @@ impl ExternalCommand for Formal {
fn job_kind_name() -> Interned<str> {
"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 {
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:?}")
})
}))?,

View file

@ -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
}

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::{
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<str> {
"firtool".intern()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)]
pub struct UnadjustedVerilog {
firrtl_file: Interned<str>,
@ -129,6 +141,7 @@ impl ExternalCommand for UnadjustedVerilog {
type AdditionalArgs = UnadjustedVerilogArgs;
type AdditionalJobData = UnadjustedVerilog;
type Dependencies = JobKindAndDependencies<FirrtlJobKind>;
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<Self>) -> Interned<[Interned<str>]> {
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, 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<Self>) -> Option<Interned<str>> {
@ -224,10 +227,6 @@ impl ExternalCommand for UnadjustedVerilog {
"unadjusted-verilog".intern()
}
fn default_program_name() -> Interned<str> {
"firtool".intern()
}
fn subcommand_hidden() -> bool {
true
}
@ -267,6 +266,43 @@ impl VerilogJob {
pub fn main_verilog_file(&self) -> Interned<str> {
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 {
@ -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 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 })
}