From f582013c1b17e13be1f5ea207e6392f6103e66c7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 23 Jul 2024 23:31:23 -0700 Subject: [PATCH] add cli for compiling to verilog --- Cargo.lock | 34 ++++++ crates/fayalite/Cargo.toml | 3 +- crates/fayalite/src/cli.rs | 191 +++++++++++++++++++++++++--------- crates/fayalite/src/firrtl.rs | 8 +- 4 files changed, 183 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 058310b..25ca4db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 23d8bbf..555f7f5 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -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 } diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index 0113c8f..3a07d91 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -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 = std::result::Result; -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 for CliError { - fn from(value: std::io::Error) -> Self { - CliError(CliErrorImpl::IoError(value)) +impl From for CliError { + fn from(value: io::Error) -> Self { + CliError(Report::new(value)) } } pub trait RunPhase { - fn run(self, arg: Arg) -> Result; + type Output; + fn run(&self, arg: Arg) -> Result; } -#[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, } +impl BaseArgs { + pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend { + firrtl::FileBackend { + 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) -> Result { - firrtl::export( - firrtl::FileBackend { - dir_path: self.output, - top_fir_file_stem: self.file_stem, - }, - &top_module, - )?; - Ok(()) + fn run_impl(&self, top_module: Module) -> Result { + 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 RunPhase> for FirrtlArgs where T::Type: BundleType, { - fn run(self, top_module: Module) -> Result { + type Output = FirrtlOutput; + fn run(&self, top_module: Module) -> Result { self.run_impl(top_module.canonical()) } } @@ -86,15 +108,83 @@ impl RunPhase>> for FirrtlArgs where T::Type: BundleType, { - fn run(self, top_module: Interned>) -> Result { + type Output = FirrtlOutput; + fn run(&self, top_module: Interned>) -> Result { 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, +} + +#[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 { + 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 RunPhase for VerilogArgs +where + FirrtlArgs: RunPhase, +{ + type Output = VerilogOutput; + fn run(&self, arg: Arg) -> Result { + 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 RunPhase for Cli where - FirrtlArgs: RunPhase, + FirrtlArgs: RunPhase, { - 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 { + 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(self, top_module: T) -> Result + pub fn run(&self, top_module: T) -> Result<()> where - Self: RunPhase, + Self: RunPhase, { RunPhase::run(self, top_module) } diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index 783f55c..cd53f1d 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -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)?; }