add cli for compiling to verilog
All checks were successful
/ test (push) Successful in 15m23s

This commit is contained in:
Jacob Lifshay 2024-07-23 23:31:23 -07:00
parent 0611044941
commit f582013c1b
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
4 changed files with 183 additions and 53 deletions

34
Cargo.lock generated
View file

@ -199,6 +199,12 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.1"
@ -246,6 +252,7 @@ dependencies = [
"serde",
"serde_json",
"trybuild",
"which",
]
[[package]]
@ -321,6 +328,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
]
[[package]]
name = "indenter"
version = "0.3.3"
@ -596,6 +612,18 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "which"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
dependencies = [
"either",
"home",
"rustix",
"winsafe",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -693,6 +721,12 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wyz"
version = "0.5.1"

View file

@ -21,8 +21,9 @@ num-traits = { workspace = true }
fayalite-proc-macros = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { version = "4.5.9", features = ["derive"] }
clap = { version = "4.5.9", features = ["derive", "env"] }
eyre = "0.6.12"
which = "6.0.1"
[dev-dependencies]
trybuild = { workspace = true }

View file

@ -4,72 +4,93 @@ use crate::{
intern::Interned,
module::Module,
};
use clap::{Args, Parser, Subcommand, ValueHint};
use std::{fmt, path::PathBuf};
enum CliErrorImpl {
IoError(std::io::Error),
}
use clap::{
builder::{OsStringValueParser, TypedValueParser},
Args, Parser, Subcommand, ValueHint,
};
use eyre::{eyre, Report};
use std::{error, ffi::OsString, fmt, io, path::PathBuf, process};
pub type Result<T = (), E = CliError> = std::result::Result<T, E>;
pub struct CliError(CliErrorImpl);
pub struct CliError(Report);
impl fmt::Debug for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
CliErrorImpl::IoError(e) => e.fmt(f),
}
self.0.fmt(f)
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
CliErrorImpl::IoError(e) => e.fmt(f),
}
self.0.fmt(f)
}
}
impl std::error::Error for CliError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0 {
CliErrorImpl::IoError(e) => e.source(),
}
}
}
impl error::Error for CliError {}
impl From<std::io::Error> for CliError {
fn from(value: std::io::Error) -> Self {
CliError(CliErrorImpl::IoError(value))
impl From<io::Error> for CliError {
fn from(value: io::Error) -> Self {
CliError(Report::new(value))
}
}
pub trait RunPhase<Arg> {
fn run(self, arg: Arg) -> Result;
type Output;
fn run(&self, arg: Arg) -> Result<Self::Output>;
}
#[derive(Args)]
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct FirrtlArgs {
/// the directory to put the generated FIRRTL and associated files in
pub struct BaseArgs {
/// the directory to put the generated main output file and associated files in
#[arg(short, long, value_hint = ValueHint::DirPath)]
pub output: PathBuf,
/// the stem of the generated .fir file, e.g. to get foo.fir, pass --file-stem=foo
/// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
#[arg(long)]
pub file_stem: Option<String>,
}
impl FirrtlArgs {
fn run_impl(self, top_module: Module<DynBundle>) -> Result {
firrtl::export(
impl BaseArgs {
pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend {
firrtl::FileBackend {
dir_path: self.output,
top_fir_file_stem: self.file_stem,
},
&top_module,
)?;
Ok(())
dir_path: self.output.clone(),
top_fir_file_stem: self.file_stem.clone(),
}
}
}
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct FirrtlArgs {
#[command(flatten)]
pub base: BaseArgs,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FirrtlOutput {
pub file_stem: String,
}
impl FirrtlOutput {
pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf {
let mut retval = args.base.output.join(&self.file_stem);
retval.set_extension("fir");
retval
}
}
impl FirrtlArgs {
fn run_impl(&self, top_module: Module<DynBundle>) -> Result<FirrtlOutput> {
let firrtl::FileBackend {
top_fir_file_stem, ..
} = firrtl::export(self.base.to_firrtl_file_backend(), &top_module)?;
Ok(FirrtlOutput {
file_stem: top_fir_file_stem.expect(
"export is known to set the file stem from the circuit name if not provided",
),
})
}
}
@ -77,7 +98,8 @@ impl<T: BundleValue> RunPhase<Module<T>> for FirrtlArgs
where
T::Type: BundleType<Value = T>,
{
fn run(self, top_module: Module<T>) -> Result {
type Output = FirrtlOutput;
fn run(&self, top_module: Module<T>) -> Result<Self::Output> {
self.run_impl(top_module.canonical())
}
}
@ -86,15 +108,83 @@ impl<T: BundleValue> RunPhase<Interned<Module<T>>> for FirrtlArgs
where
T::Type: BundleType<Value = T>,
{
fn run(self, top_module: Interned<Module<T>>) -> Result {
type Output = FirrtlOutput;
fn run(&self, top_module: Interned<Module<T>>) -> Result<Self::Output> {
self.run(*top_module)
}
}
#[derive(Subcommand)]
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct VerilogArgs {
#[command(flatten)]
pub firrtl: FirrtlArgs,
#[arg(
long,
default_value = "firtool",
env = "FIRTOOL",
value_hint = ValueHint::CommandName,
value_parser = OsStringValueParser::new().try_map(which::which)
)]
pub firtool: PathBuf,
#[arg(long)]
pub firtool_extra_args: Vec<OsString>,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct VerilogOutput {
pub firrtl: FirrtlOutput,
}
impl VerilogOutput {
pub fn verilog_file(&self, args: &VerilogArgs) -> PathBuf {
let mut retval = args.firrtl.base.output.join(&self.firrtl.file_stem);
retval.set_extension("v");
retval
}
}
impl VerilogArgs {
fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> {
let output = VerilogOutput {
firrtl: firrtl_output,
};
let mut cmd = process::Command::new(&self.firtool);
cmd.arg(output.firrtl.firrtl_file(&self.firrtl))
.arg("-o")
.arg(output.verilog_file(self))
.args(&self.firtool_extra_args)
.current_dir(&self.firrtl.base.output);
let status = cmd.status()?;
if status.success() {
Ok(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
self.firtool.display()
)))
}
}
}
impl<Arg> RunPhase<Arg> for VerilogArgs
where
FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>,
{
type Output = VerilogOutput;
fn run(&self, arg: Arg) -> Result<Self::Output> {
let firrtl_output = self.firrtl.run(arg)?;
self.run_impl(firrtl_output)
}
}
#[derive(Subcommand, Debug)]
enum CliCommand {
/// Generate FIRRTL
Firrtl(FirrtlArgs),
/// Generate Verilog
Verilog(VerilogArgs),
}
/// a simple CLI
@ -145,7 +235,7 @@ enum CliCommand {
/// Ok(())
/// }
/// ```
#[derive(Parser)]
#[derive(Parser, Debug)]
// clear things that would be crate-specific
#[command(name = "Fayalite Simple CLI", about = None, long_about = None)]
pub struct Cli {
@ -169,12 +259,19 @@ impl clap::Subcommand for Cli {
impl<T> RunPhase<T> for Cli
where
FirrtlArgs: RunPhase<T>,
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
{
fn run(self, arg: T) -> Result {
match self.subcommand {
CliCommand::Firrtl(firrtl_args) => firrtl_args.run(arg),
type Output = ();
fn run(&self, arg: T) -> Result<Self::Output> {
match &self.subcommand {
CliCommand::Firrtl(c) => {
c.run(arg)?;
}
CliCommand::Verilog(c) => {
c.run(arg)?;
}
}
Ok(())
}
}
@ -184,9 +281,9 @@ impl Cli {
clap::Parser::parse()
}
/// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`]
pub fn run<T>(self, top_module: T) -> Result
pub fn run<T>(&self, top_module: T) -> Result<()>
where
Self: RunPhase<T>,
Self: RunPhase<T, Output = ()>,
{
RunPhase::run(self, top_module)
}

View file

@ -2015,8 +2015,7 @@ impl<'a> Exporter<'a> {
let ty = self.type_state.ty(reg.ty());
let clk = self.expr(reg.clock_domain().clk.to_dyn(), &definitions, false);
if let Some(init) = reg.init() {
let rst =
self.expr(reg.clock_domain().rst.to_dyn(), &definitions, false);
let rst = self.expr(reg.clock_domain().rst.to_dyn(), &definitions, false);
let init = self.expr(init.to_dyn(), &definitions, false);
writeln!(
body,
@ -2267,9 +2266,8 @@ impl FileBackendTrait for FileBackend {
circuit_name: String,
contents: String,
) -> Result<(), Self::Error> {
let mut path = self
.dir_path
.join(self.top_fir_file_stem.as_deref().unwrap_or(&circuit_name));
let top_fir_file_stem = self.top_fir_file_stem.get_or_insert(circuit_name);
let mut path = self.dir_path.join(top_fir_file_stem);
if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) {
fs::create_dir_all(parent)?;
}