add formal subcommand
This commit is contained in:
		
							parent
							
								
									bb860d54cc
								
							
						
					
					
						commit
						45dbb554d0
					
				
					 4 changed files with 297 additions and 24 deletions
				
			
		
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -252,6 +252,7 @@ dependencies = [ | ||||||
|  "os_pipe", |  "os_pipe", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  |  "tempfile", | ||||||
|  "trybuild", |  "trybuild", | ||||||
|  "which", |  "which", | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ num-traits.workspace = true | ||||||
| os_pipe.workspace = true | os_pipe.workspace = true | ||||||
| serde_json.workspace = true | serde_json.workspace = true | ||||||
| serde.workspace = true | serde.workspace = true | ||||||
|  | tempfile.workspace = true | ||||||
| which.workspace = true | which.workspace = true | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ use clap::{ | ||||||
| }; | }; | ||||||
| use eyre::{eyre, Report}; | use eyre::{eyre, Report}; | ||||||
| use std::{error, ffi::OsString, fmt, io, path::PathBuf, process}; | use std::{error, ffi::OsString, fmt, io, path::PathBuf, process}; | ||||||
|  | use tempfile::TempDir; | ||||||
| 
 | 
 | ||||||
| pub type Result<T = (), E = CliError> = std::result::Result<T, E>; | pub type Result<T = (), E = CliError> = std::result::Result<T, E>; | ||||||
| 
 | 
 | ||||||
|  | @ -47,22 +48,41 @@ pub trait RunPhase<Arg> { | ||||||
| #[non_exhaustive] | #[non_exhaustive] | ||||||
| pub struct BaseArgs { | pub struct BaseArgs { | ||||||
|     /// the directory to put the generated main output file and associated files in
 |     /// the directory to put the generated main output file and associated files in
 | ||||||
|     #[arg(short, long, value_hint = ValueHint::DirPath)] |     #[arg(short, long, value_hint = ValueHint::DirPath, required = true)] | ||||||
|     pub output: PathBuf, |     pub output: Option<PathBuf>, | ||||||
|     /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
 |     /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
 | ||||||
|     #[arg(long)] |     #[arg(long)] | ||||||
|     pub file_stem: Option<String>, |     pub file_stem: Option<String>, | ||||||
|  |     #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] | ||||||
|  |     pub keep_temp_dir: bool, | ||||||
|     #[arg(skip = false)] |     #[arg(skip = false)] | ||||||
|     pub redirect_output_for_rust_test: bool, |     pub redirect_output_for_rust_test: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl BaseArgs { | impl BaseArgs { | ||||||
|     pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend { |     fn make_firrtl_file_backend(&self) -> Result<(firrtl::FileBackend, Option<TempDir>)> { | ||||||
|         firrtl::FileBackend { |         let (dir_path, temp_dir) = match &self.output { | ||||||
|             dir_path: self.output.clone(), |             Some(output) => (output.clone(), None), | ||||||
|             top_fir_file_stem: self.file_stem.clone(), |             None => { | ||||||
|  |                 let temp_dir = TempDir::new()?; | ||||||
|  |                 if self.keep_temp_dir { | ||||||
|  |                     let temp_dir = temp_dir.into_path(); | ||||||
|  |                     println!("created temporary directory: {}", temp_dir.display()); | ||||||
|  |                     (temp_dir, None) | ||||||
|  |                 } else { | ||||||
|  |                     (temp_dir.path().to_path_buf(), Some(temp_dir)) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         }; | ||||||
|  |         Ok(( | ||||||
|  |             firrtl::FileBackend { | ||||||
|  |                 dir_path, | ||||||
|  |                 top_fir_file_stem: self.file_stem.clone(), | ||||||
|  |                 circuit_name: None, | ||||||
|  |             }, | ||||||
|  |             temp_dir, | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|     /// handles possibly redirecting the command's output for Rust tests
 |     /// handles possibly redirecting the command's output for Rust tests
 | ||||||
|     pub fn run_external_command( |     pub fn run_external_command( | ||||||
|         &self, |         &self, | ||||||
|  | @ -106,25 +126,37 @@ pub struct FirrtlArgs { | ||||||
| #[non_exhaustive] | #[non_exhaustive] | ||||||
| pub struct FirrtlOutput { | pub struct FirrtlOutput { | ||||||
|     pub file_stem: String, |     pub file_stem: String, | ||||||
|  |     pub top_module: String, | ||||||
|  |     pub output_dir: PathBuf, | ||||||
|  |     pub temp_dir: Option<TempDir>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl FirrtlOutput { | impl FirrtlOutput { | ||||||
|     pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf { |     pub fn file_with_ext(&self, ext: &str) -> PathBuf { | ||||||
|         let mut retval = args.base.output.join(&self.file_stem); |         let mut retval = self.output_dir.join(&self.file_stem); | ||||||
|         retval.set_extension("fir"); |         retval.set_extension(ext); | ||||||
|         retval |         retval | ||||||
|     } |     } | ||||||
|  |     pub fn firrtl_file(&self) -> PathBuf { | ||||||
|  |         self.file_with_ext("fir") | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl FirrtlArgs { | impl FirrtlArgs { | ||||||
|     fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> { |     fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> { | ||||||
|  |         let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?; | ||||||
|         let firrtl::FileBackend { |         let firrtl::FileBackend { | ||||||
|             top_fir_file_stem, .. |             top_fir_file_stem, | ||||||
|         } = firrtl::export(self.base.to_firrtl_file_backend(), &top_module, self.export_options)?; |             circuit_name, | ||||||
|  |             dir_path, | ||||||
|  |         } = firrtl::export(file_backend, &top_module, self.export_options)?; | ||||||
|         Ok(FirrtlOutput { |         Ok(FirrtlOutput { | ||||||
|             file_stem: top_fir_file_stem.expect( |             file_stem: top_fir_file_stem.expect( | ||||||
|                 "export is known to set the file stem from the circuit name if not provided", |                 "export is known to set the file stem from the circuit name if not provided", | ||||||
|             ), |             ), | ||||||
|  |             top_module: circuit_name.expect("export is known to set the circuit name"), | ||||||
|  |             output_dir: dir_path, | ||||||
|  |             temp_dir, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -155,7 +187,22 @@ pub enum VerilogDialect { | ||||||
|     Yosys, |     Yosys, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl fmt::Display for VerilogDialect { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         f.write_str(self.as_str()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl VerilogDialect { | 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] { |     pub fn firtool_extra_args(self) -> &'static [&'static str] { | ||||||
|         match self { |         match self { | ||||||
|             VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"], |             VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"], | ||||||
|  | @ -191,6 +238,8 @@ pub struct VerilogArgs { | ||||||
|     /// adapt the generated Verilog for a particular toolchain
 |     /// adapt the generated Verilog for a particular toolchain
 | ||||||
|     #[arg(long)] |     #[arg(long)] | ||||||
|     pub verilog_dialect: Option<VerilogDialect>, |     pub verilog_dialect: Option<VerilogDialect>, | ||||||
|  |     #[arg(long, short = 'g')] | ||||||
|  |     pub debug: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | @ -200,28 +249,37 @@ pub struct VerilogOutput { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl VerilogOutput { | impl VerilogOutput { | ||||||
|     pub fn verilog_file(&self, args: &VerilogArgs) -> PathBuf { |     pub fn verilog_file(&self) -> PathBuf { | ||||||
|         let mut retval = args.firrtl.base.output.join(&self.firrtl.file_stem); |         self.firrtl.file_with_ext("v") | ||||||
|         retval.set_extension("v"); |  | ||||||
|         retval |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl VerilogArgs { | impl VerilogArgs { | ||||||
|     fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> { |     fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> { | ||||||
|  |         let Self { | ||||||
|  |             firrtl, | ||||||
|  |             firtool, | ||||||
|  |             firtool_extra_args, | ||||||
|  |             verilog_dialect, | ||||||
|  |             debug, | ||||||
|  |         } = self; | ||||||
|         let output = VerilogOutput { |         let output = VerilogOutput { | ||||||
|             firrtl: firrtl_output, |             firrtl: firrtl_output, | ||||||
|         }; |         }; | ||||||
|         let mut cmd = process::Command::new(&self.firtool); |         let mut cmd = process::Command::new(firtool); | ||||||
|         cmd.arg(output.firrtl.firrtl_file(&self.firrtl)); |         cmd.arg(output.firrtl.firrtl_file()); | ||||||
|         cmd.arg("-o"); |         cmd.arg("-o"); | ||||||
|         cmd.arg(output.verilog_file(self)); |         cmd.arg(output.verilog_file()); | ||||||
|         if let Some(dialect) = self.verilog_dialect { |         if *debug { | ||||||
|  |             cmd.arg("-g"); | ||||||
|  |             cmd.arg("--preserve-values=named"); | ||||||
|  |         } | ||||||
|  |         if let Some(dialect) = verilog_dialect { | ||||||
|             cmd.args(dialect.firtool_extra_args()); |             cmd.args(dialect.firtool_extra_args()); | ||||||
|         } |         } | ||||||
|         cmd.args(&self.firtool_extra_args); |         cmd.args(firtool_extra_args); | ||||||
|         cmd.current_dir(&self.firrtl.base.output); |         cmd.current_dir(&output.firrtl.output_dir); | ||||||
|         let status = self.firrtl.base.run_external_command(cmd)?; |         let status = firrtl.base.run_external_command(cmd)?; | ||||||
|         if status.success() { |         if status.success() { | ||||||
|             Ok(output) |             Ok(output) | ||||||
|         } else { |         } else { | ||||||
|  | @ -244,12 +302,217 @@ where | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default)] | ||||||
|  | #[non_exhaustive] | ||||||
|  | pub enum FormalMode { | ||||||
|  |     #[default] | ||||||
|  |     BMC, | ||||||
|  |     Prove, | ||||||
|  |     Live, | ||||||
|  |     Cover, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FormalMode { | ||||||
|  |     pub fn as_str(self) -> &'static str { | ||||||
|  |         match self { | ||||||
|  |             FormalMode::BMC => "bmc", | ||||||
|  |             FormalMode::Prove => "prove", | ||||||
|  |             FormalMode::Live => "live", | ||||||
|  |             FormalMode::Cover => "cover", | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl fmt::Display for FormalMode { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         f.write_str(self.as_str()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone)] | ||||||
|  | struct FormalAdjustArgs; | ||||||
|  | 
 | ||||||
|  | impl clap::FromArgMatches for FormalAdjustArgs { | ||||||
|  |     fn from_arg_matches(_matches: &clap::ArgMatches) -> Result<Self, clap::Error> { | ||||||
|  |         Ok(Self) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> { | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl clap::Args for FormalAdjustArgs { | ||||||
|  |     fn augment_args(cmd: clap::Command) -> clap::Command { | ||||||
|  |         cmd.mut_arg("output", |arg| arg.required(false)) | ||||||
|  |             .mut_arg("verilog_dialect", |arg| { | ||||||
|  |                 arg.default_value(VerilogDialect::Yosys.to_string()) | ||||||
|  |                     .hide(true) | ||||||
|  |             }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn augment_args_for_update(cmd: clap::Command) -> clap::Command { | ||||||
|  |         Self::augment_args(cmd) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Parser, Clone)] | ||||||
|  | #[non_exhaustive] | ||||||
|  | pub struct FormalArgs { | ||||||
|  |     #[command(flatten)] | ||||||
|  |     pub verilog: VerilogArgs, | ||||||
|  |     #[arg(
 | ||||||
|  |         long, | ||||||
|  |         default_value = "sby", | ||||||
|  |         env = "SBY", | ||||||
|  |         value_hint = ValueHint::CommandName, | ||||||
|  |         value_parser = OsStringValueParser::new().try_map(which::which) | ||||||
|  |     )] | ||||||
|  |     pub sby: PathBuf, | ||||||
|  |     #[arg(long)] | ||||||
|  |     pub sby_extra_args: Vec<OsString>, | ||||||
|  |     #[arg(long, default_value_t)] | ||||||
|  |     pub mode: FormalMode, | ||||||
|  |     #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] | ||||||
|  |     pub depth: u64, | ||||||
|  |     #[arg(long)] | ||||||
|  |     pub solver: Option<String>, | ||||||
|  |     #[arg(long)] | ||||||
|  |     pub smtbmc_extra_args: Vec<String>, | ||||||
|  |     #[command(flatten)] | ||||||
|  |     _formal_adjust_args: FormalAdjustArgs, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl fmt::Debug for FormalArgs { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         let Self { | ||||||
|  |             verilog, | ||||||
|  |             sby, | ||||||
|  |             sby_extra_args, | ||||||
|  |             mode, | ||||||
|  |             depth, | ||||||
|  |             solver, | ||||||
|  |             smtbmc_extra_args, | ||||||
|  |             _formal_adjust_args: _, | ||||||
|  |         } = self; | ||||||
|  |         f.debug_struct("FormalArgs") | ||||||
|  |             .field("verilog", &verilog) | ||||||
|  |             .field("sby", &sby) | ||||||
|  |             .field("sby_extra_args", &sby_extra_args) | ||||||
|  |             .field("mode", &mode) | ||||||
|  |             .field("depth", &depth) | ||||||
|  |             .field("solver", &solver) | ||||||
|  |             .field("smtbmc_extra_args", &smtbmc_extra_args) | ||||||
|  |             .finish_non_exhaustive() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FormalArgs { | ||||||
|  |     pub const DEFAULT_DEPTH: u64 = 20; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | #[non_exhaustive] | ||||||
|  | pub struct FormalOutput { | ||||||
|  |     pub verilog: VerilogOutput, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FormalOutput { | ||||||
|  |     pub fn sby_file(&self) -> PathBuf { | ||||||
|  |         self.verilog.firrtl.file_with_ext("sby") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl FormalArgs { | ||||||
|  |     fn sby_contents(&self, output: &FormalOutput) -> String { | ||||||
|  |         let Self { | ||||||
|  |             verilog: _, | ||||||
|  |             sby: _, | ||||||
|  |             sby_extra_args: _, | ||||||
|  |             mode, | ||||||
|  |             depth, | ||||||
|  |             smtbmc_extra_args, | ||||||
|  |             solver, | ||||||
|  |             _formal_adjust_args: _, | ||||||
|  |         } = self; | ||||||
|  |         struct OptArg<T>(Option<T>); | ||||||
|  |         impl<T: fmt::Display> fmt::Display for OptArg<T> { | ||||||
|  |             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |                 if let Some(v) = &self.0 { | ||||||
|  |                     f.write_str(" ")?; | ||||||
|  |                     v.fmt(f) | ||||||
|  |                 } else { | ||||||
|  |                     Ok(()) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         let space_solver = OptArg(solver.as_ref()); | ||||||
|  |         let smtbmc_options = smtbmc_extra_args.join(" "); | ||||||
|  |         let verilog_file = output | ||||||
|  |             .verilog | ||||||
|  |             .verilog_file() | ||||||
|  |             .into_os_string() | ||||||
|  |             .into_string() | ||||||
|  |             .ok() | ||||||
|  |             .expect("verilog file path is not UTF-8"); | ||||||
|  |         let top_module = &output.verilog.firrtl.top_module; | ||||||
|  |         format!( | ||||||
|  |             "[options]\n\ | ||||||
|  |             mode {mode}\n\ | ||||||
|  |             depth {depth}\n\ | ||||||
|  |             wait on\n\ | ||||||
|  |             \n\ | ||||||
|  |             [engines]\n\ | ||||||
|  |             smtbmc{space_solver} -- -- {smtbmc_options}\n\ | ||||||
|  |             \n\ | ||||||
|  |             [script]\n\ | ||||||
|  |             read_verilog -sv -formal {verilog_file}\n\ | ||||||
|  |             prep -top {top_module}\n | ||||||
|  |             " | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     fn run_impl(&self, verilog_output: VerilogOutput) -> Result<FormalOutput> { | ||||||
|  |         let output = FormalOutput { | ||||||
|  |             verilog: verilog_output, | ||||||
|  |         }; | ||||||
|  |         let sby_file = output.sby_file(); | ||||||
|  |         std::fs::write(&sby_file, self.sby_contents(&output))?; | ||||||
|  |         let mut cmd = process::Command::new(&self.sby); | ||||||
|  |         cmd.arg("-f"); | ||||||
|  |         cmd.arg(sby_file); | ||||||
|  |         cmd.args(&self.sby_extra_args); | ||||||
|  |         cmd.current_dir(&output.verilog.firrtl.output_dir); | ||||||
|  |         let status = self.verilog.firrtl.base.run_external_command(cmd)?; | ||||||
|  |         if status.success() { | ||||||
|  |             Ok(output) | ||||||
|  |         } else { | ||||||
|  |             Err(CliError(eyre!( | ||||||
|  |                 "running {} failed: {status}", | ||||||
|  |                 self.sby.display() | ||||||
|  |             ))) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<Arg> RunPhase<Arg> for FormalArgs | ||||||
|  | where | ||||||
|  |     VerilogArgs: RunPhase<Arg, Output = VerilogOutput>, | ||||||
|  | { | ||||||
|  |     type Output = FormalOutput; | ||||||
|  |     fn run(&self, arg: Arg) -> Result<Self::Output> { | ||||||
|  |         let verilog_output = self.verilog.run(arg)?; | ||||||
|  |         self.run_impl(verilog_output) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[derive(Subcommand, Debug)] | #[derive(Subcommand, Debug)] | ||||||
| enum CliCommand { | enum CliCommand { | ||||||
|     /// Generate FIRRTL
 |     /// Generate FIRRTL
 | ||||||
|     Firrtl(FirrtlArgs), |     Firrtl(FirrtlArgs), | ||||||
|     /// Generate Verilog
 |     /// Generate Verilog
 | ||||||
|     Verilog(VerilogArgs), |     Verilog(VerilogArgs), | ||||||
|  |     /// Run a formal proof
 | ||||||
|  |     Formal(FormalArgs), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// a simple CLI
 | /// a simple CLI
 | ||||||
|  | @ -335,6 +598,9 @@ where | ||||||
|             CliCommand::Verilog(c) => { |             CliCommand::Verilog(c) => { | ||||||
|                 c.run(arg)?; |                 c.run(arg)?; | ||||||
|             } |             } | ||||||
|  |             CliCommand::Formal(c) => { | ||||||
|  |                 c.run(arg)?; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2311,6 +2311,7 @@ impl<T: ?Sized + FileBackendTrait> FileBackendTrait for &'_ mut T { | ||||||
| #[non_exhaustive] | #[non_exhaustive] | ||||||
| pub struct FileBackend { | pub struct FileBackend { | ||||||
|     pub dir_path: PathBuf, |     pub dir_path: PathBuf, | ||||||
|  |     pub circuit_name: Option<String>, | ||||||
|     pub top_fir_file_stem: Option<String>, |     pub top_fir_file_stem: Option<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -2318,6 +2319,7 @@ impl FileBackend { | ||||||
|     pub fn new(dir_path: impl AsRef<Path>) -> Self { |     pub fn new(dir_path: impl AsRef<Path>) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             dir_path: dir_path.as_ref().to_owned(), |             dir_path: dir_path.as_ref().to_owned(), | ||||||
|  |             circuit_name: None, | ||||||
|             top_fir_file_stem: None, |             top_fir_file_stem: None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -2353,7 +2355,10 @@ impl FileBackendTrait for FileBackend { | ||||||
|         circuit_name: String, |         circuit_name: String, | ||||||
|         contents: String, |         contents: String, | ||||||
|     ) -> Result<(), Self::Error> { |     ) -> Result<(), Self::Error> { | ||||||
|         let top_fir_file_stem = self.top_fir_file_stem.get_or_insert(circuit_name); |         let top_fir_file_stem = self | ||||||
|  |             .top_fir_file_stem | ||||||
|  |             .get_or_insert_with(|| circuit_name.clone()); | ||||||
|  |         self.circuit_name = Some(circuit_name); | ||||||
|         let mut path = self.dir_path.join(top_fir_file_stem); |         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()) { |         if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) { | ||||||
|             fs::create_dir_all(parent)?; |             fs::create_dir_all(parent)?; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue