416 lines
14 KiB
Rust
416 lines
14 KiB
Rust
// 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, JobParams, ToArgs,
|
|
WriteArgs,
|
|
external::{
|
|
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
|
|
},
|
|
firrtl::FirrtlJobKind,
|
|
interned_known_utf8_method, interned_known_utf8_path_buf_method,
|
|
},
|
|
intern::{Intern, Interned},
|
|
util::job_server::AcquiredJob,
|
|
};
|
|
use clap::Args;
|
|
use eyre::{Context, bail};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{fmt, mem};
|
|
|
|
/// based on [LLVM Circt's recommended lowering options][lowering-options]
|
|
///
|
|
/// [lowering-options]: https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target
|
|
#[derive(clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
#[non_exhaustive]
|
|
pub enum VerilogDialect {
|
|
Questa,
|
|
Spyglass,
|
|
Verilator,
|
|
Vivado,
|
|
Yosys,
|
|
}
|
|
|
|
impl fmt::Display for VerilogDialect {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str(self.as_str())
|
|
}
|
|
}
|
|
|
|
impl VerilogDialect {
|
|
pub fn as_str(self) -> &'static str {
|
|
match self {
|
|
VerilogDialect::Questa => "questa",
|
|
VerilogDialect::Spyglass => "spyglass",
|
|
VerilogDialect::Verilator => "verilator",
|
|
VerilogDialect::Vivado => "vivado",
|
|
VerilogDialect::Yosys => "yosys",
|
|
}
|
|
}
|
|
pub fn firtool_extra_args(self) -> &'static [&'static str] {
|
|
match self {
|
|
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
|
|
VerilogDialect::Spyglass => {
|
|
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
|
|
}
|
|
VerilogDialect::Verilator => &[
|
|
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
|
|
],
|
|
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
|
|
VerilogDialect::Yosys => {
|
|
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Args, Debug, Clone, PartialEq, Eq, Hash)]
|
|
#[non_exhaustive]
|
|
pub struct UnadjustedVerilogArgs {
|
|
#[arg(long = "firtool-extra-arg", value_name = "ARG")]
|
|
pub firtool_extra_args: Vec<String>,
|
|
/// adapt the generated Verilog for a particular toolchain
|
|
#[arg(long)]
|
|
pub verilog_dialect: Option<VerilogDialect>,
|
|
#[arg(long)]
|
|
pub verilog_debug: bool,
|
|
}
|
|
|
|
impl ToArgs for UnadjustedVerilogArgs {
|
|
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
|
let Self {
|
|
ref firtool_extra_args,
|
|
verilog_dialect,
|
|
verilog_debug,
|
|
} = *self;
|
|
args.extend(
|
|
firtool_extra_args
|
|
.iter()
|
|
.map(|arg| format!("--firtool-extra-arg={arg}")),
|
|
);
|
|
if let Some(verilog_dialect) = verilog_dialect {
|
|
args.write_arg(format_args!("--verilog-dialect={verilog_dialect}"));
|
|
}
|
|
if verilog_debug {
|
|
args.write_str_arg("--verilog-debug");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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>,
|
|
firrtl_file_name: Interned<str>,
|
|
unadjusted_verilog_file: Interned<str>,
|
|
unadjusted_verilog_file_name: Interned<str>,
|
|
firtool_extra_args: Interned<[Interned<str>]>,
|
|
verilog_dialect: Option<VerilogDialect>,
|
|
verilog_debug: bool,
|
|
}
|
|
|
|
impl UnadjustedVerilog {
|
|
pub fn firrtl_file(&self) -> Interned<str> {
|
|
self.firrtl_file
|
|
}
|
|
pub fn unadjusted_verilog_file(&self) -> Interned<str> {
|
|
self.unadjusted_verilog_file
|
|
}
|
|
pub fn firtool_extra_args(&self) -> Interned<[Interned<str>]> {
|
|
self.firtool_extra_args
|
|
}
|
|
pub fn verilog_dialect(&self) -> Option<VerilogDialect> {
|
|
self.verilog_dialect
|
|
}
|
|
pub fn verilog_debug(&self) -> bool {
|
|
self.verilog_debug
|
|
}
|
|
}
|
|
|
|
impl ExternalCommand for UnadjustedVerilog {
|
|
type AdditionalArgs = UnadjustedVerilogArgs;
|
|
type AdditionalJobData = UnadjustedVerilog;
|
|
type Dependencies = JobKindAndDependencies<FirrtlJobKind>;
|
|
type ExternalProgram = Firtool;
|
|
|
|
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 UnadjustedVerilogArgs {
|
|
firtool_extra_args,
|
|
verilog_dialect,
|
|
verilog_debug,
|
|
} = args.additional_args;
|
|
let unadjusted_verilog_file = dependencies
|
|
.dependencies
|
|
.job
|
|
.job
|
|
.file_with_ext("unadjusted.v");
|
|
Ok(UnadjustedVerilog {
|
|
firrtl_file: dependencies.job.job.firrtl_file(),
|
|
firrtl_file_name: interned_known_utf8_method(
|
|
dependencies.job.job.firrtl_file(),
|
|
|v| v.file_name().expect("known to have file name"),
|
|
),
|
|
unadjusted_verilog_file,
|
|
unadjusted_verilog_file_name: interned_known_utf8_method(
|
|
unadjusted_verilog_file,
|
|
|v| v.file_name().expect("known to have file name"),
|
|
),
|
|
firtool_extra_args: firtool_extra_args
|
|
.into_iter()
|
|
.map(str::intern_owned)
|
|
.collect(),
|
|
verilog_dialect,
|
|
verilog_debug,
|
|
})
|
|
})
|
|
}
|
|
|
|
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
|
|
[JobItemName::Path {
|
|
path: job.additional_job_data().firrtl_file,
|
|
}][..]
|
|
.intern()
|
|
}
|
|
|
|
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<str>]> {
|
|
[job.additional_job_data().unadjusted_verilog_file][..].intern()
|
|
}
|
|
|
|
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
|
let UnadjustedVerilog {
|
|
firrtl_file: _,
|
|
firrtl_file_name,
|
|
unadjusted_verilog_file: _,
|
|
unadjusted_verilog_file_name,
|
|
firtool_extra_args,
|
|
verilog_dialect,
|
|
verilog_debug,
|
|
} = *job.additional_job_data();
|
|
args.write_interned_arg(firrtl_file_name);
|
|
args.write_str_arg("-o");
|
|
args.write_interned_arg(unadjusted_verilog_file_name);
|
|
if verilog_debug {
|
|
args.write_str_args(["-g", "--preserve-values=all"]);
|
|
}
|
|
if let Some(dialect) = verilog_dialect {
|
|
args.write_str_args(dialect.firtool_extra_args().iter().copied());
|
|
}
|
|
args.write_interned_args(firtool_extra_args);
|
|
}
|
|
|
|
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<str>> {
|
|
Some(job.output_dir())
|
|
}
|
|
|
|
fn job_kind_name() -> Interned<str> {
|
|
"unadjusted-verilog".intern()
|
|
}
|
|
|
|
fn subcommand_hidden() -> bool {
|
|
true
|
|
}
|
|
|
|
fn run_even_if_cached_arg_name() -> Interned<str> {
|
|
"firtool-run-even-if-cached".intern()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct VerilogJobKind;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Args)]
|
|
#[non_exhaustive]
|
|
pub struct VerilogJobArgs {}
|
|
|
|
impl ToArgs for VerilogJobArgs {
|
|
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
|
let Self {} = self;
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct VerilogJob {
|
|
output_dir: Interned<str>,
|
|
unadjusted_verilog_file: Interned<str>,
|
|
main_verilog_file: Interned<str>,
|
|
}
|
|
|
|
impl VerilogJob {
|
|
pub fn output_dir(&self) -> Interned<str> {
|
|
self.output_dir
|
|
}
|
|
pub fn unadjusted_verilog_file(&self) -> Interned<str> {
|
|
self.unadjusted_verilog_file
|
|
}
|
|
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 {
|
|
type Args = VerilogJobArgs;
|
|
type Job = VerilogJob;
|
|
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<UnadjustedVerilog>>;
|
|
|
|
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 VerilogJobArgs {} = args;
|
|
Ok(VerilogJob {
|
|
output_dir: dependencies.base_job().output_dir(),
|
|
unadjusted_verilog_file: dependencies
|
|
.job
|
|
.job
|
|
.additional_job_data()
|
|
.unadjusted_verilog_file(),
|
|
main_verilog_file: dependencies.base_job().file_with_ext("v"),
|
|
})
|
|
})
|
|
}
|
|
|
|
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
|
[JobItemName::Path {
|
|
path: job.unadjusted_verilog_file,
|
|
}][..]
|
|
.intern()
|
|
}
|
|
|
|
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
|
[
|
|
JobItemName::Path {
|
|
path: job.main_verilog_file,
|
|
},
|
|
JobItemName::DynamicPaths {
|
|
source_job_name: self.name(),
|
|
},
|
|
][..]
|
|
.intern()
|
|
}
|
|
|
|
fn name(self) -> Interned<str> {
|
|
"verilog".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 input = std::fs::read_to_string(job.unadjusted_verilog_file())?;
|
|
let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
|
|
let file_separator_suffix = "\" ----- 8< -----\n\n";
|
|
let mut input = &*input;
|
|
let main_verilog_file = job.main_verilog_file();
|
|
let mut file_name = Some(main_verilog_file);
|
|
let mut additional_outputs = Vec::new();
|
|
loop {
|
|
let (chunk, next_file_name) = if let Some((chunk, rest)) =
|
|
input.split_once(file_separator_prefix)
|
|
{
|
|
let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else {
|
|
bail!(
|
|
"parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}"
|
|
);
|
|
};
|
|
input = rest;
|
|
let next_file_name =
|
|
interned_known_utf8_path_buf_method(job.output_dir, |v| v.join(next_file_name));
|
|
additional_outputs.push(next_file_name);
|
|
(chunk, Some(next_file_name))
|
|
} else {
|
|
(mem::take(&mut input), None)
|
|
};
|
|
let Some(file_name) = mem::replace(&mut file_name, next_file_name) else {
|
|
break;
|
|
};
|
|
std::fs::write(&file_name, chunk)?;
|
|
}
|
|
Ok(vec![
|
|
JobItem::Path {
|
|
path: main_verilog_file,
|
|
},
|
|
JobItem::DynamicPaths {
|
|
paths: additional_outputs,
|
|
source_job_name: self.name(),
|
|
},
|
|
])
|
|
}
|
|
}
|
|
|
|
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|
|
[
|
|
DynJobKind::new(ExternalCommandJobKind::<UnadjustedVerilog>::new()),
|
|
DynJobKind::new(VerilogJobKind),
|
|
]
|
|
}
|