From 807f5541a7223a4db65b563e7984e21313528658 Mon Sep 17 00:00:00 2001 From: ok <59588824+jku20@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:31:31 -0400 Subject: [PATCH] [fud2] Additional Syntax to Rhai DSL (#2203) ## Description This pull request adds new syntax for defining ops which punts most of the work of the DSL to Rhai. That is, the PR defines new syntax to define ops in Rhai by directly associating a sequence of shell commands with ops. Variables and setups can be done using Rhai's scripting instead of variables and multiple rules in ninja. This leaves only very simple, one rule for each op, ninja files. As currently written, this style of op creation may be easier to write and read, however, it may also generate less readable ninja files. ## Example of New Syntax ``` export const verilog_state = state("verilog", ["sv", "v"]); export const calyx_state = state("calyx", ["futil"]); export const verilog_noverify = state("verilog-noverify", ["sv"]); fn calyx_setup() { // Rhai syntax for object maps. #{ calyx_base: config("calyx.base"), calyx_exe: config_or("calyx.exe", "${calyx_base}/target/debug/calyx"), args: config_or("calyx.args", ""), } } defop calyx_to_verilog(c: calyx_state) >> v: verilog_state { let s = calyx_setup(); shell("${s.calyx_exe} -l ${s.calyx_base} -b verilog ${s.args} ${c} > ${v}"); } defop calyx_noverify(c: calyx_state) >> v: verilog_noverify { let s = calyx_setup(); shell("${s.calyx_exe} -l ${s.calyx_base} -b verilog ${s.args} --disable-verify ${c} > ${v}"); } ``` The strings passed to `shell` can be constructed with a function to remove some repetition if desired. While this style of creating ops does clash with the current style, they should still be able to coexist. ## TODO - [x] Implement `defop` syntax. - [x] Implement `shell` function. - [x] Implement `config` and `config_or` function. - [x] Make the two types of substitution more distinct. --- Cargo.lock | 1 + fud2/Cargo.toml | 1 + fud2/fud-core/src/cli.rs | 36 +- fud2/fud-core/src/exec/driver.rs | 10 +- fud2/fud-core/src/run.rs | 102 +++- fud2/fud-core/src/script/error.rs | 42 +- fud2/fud-core/src/script/plugin.rs | 445 +++++++++++++++++- fud2/src/main.rs | 7 +- fud2/tests/scripts/defop/test.rhai | 38 ++ ...ests__test@calyx_through_cider_to_dat.snap | 50 ++ ...alyx_through_firrtl_to_verilog-refmem.snap | 49 ++ ...sts__test@calyx_through_icarus_to_dat.snap | 50 ++ ...sts__test@calyx_through_icarus_to_vcd.snap | 49 ++ ...sts__test@calyx_through_interp_to_dat.snap | 60 +++ ...__test@calyx_through_verilator_to_dat.snap | 54 +++ ...__test@calyx_through_verilator_to_vcd.snap | 53 +++ ...__test@calyx_through_xrt-trace_to_vcd.snap | 76 +++ .../tests__test@calyx_through_xrt_to_dat.snap | 76 +++ .../tests__test@calyx_to_cider-debug.snap | 51 ++ .../snapshots/tests__test@calyx_to_debug.snap | 59 +++ .../tests__test@calyx_to_verilog.snap | 23 + .../tests__test@dahlia_to_calyx.snap | 15 + .../snapshots/tests__test@mrxl_to_calyx.snap | 15 + .../snapshots/tests__test@state0_state1.snap | 16 + ...state0_state1_state2_to_state3_state4.snap | 20 + .../tests__test@state0_state1_to_state2.snap | 17 + ...sts__test@state0_through_t4_to_state1.snap | 17 + ...sts__test@state0_through_t5_to_state1.snap | 17 + .../tests__test@state0_to_state1-2.snap | 16 + .../tests__test@state0_to_state1.snap | 16 + .../tests__test@state0_to_state2_state1.snap | 18 + fud2/tests/tests.rs | 156 ++++-- 32 files changed, 1596 insertions(+), 59 deletions(-) create mode 100644 fud2/tests/scripts/defop/test.rhai create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_interp_to_dat.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_to_debug.snap create mode 100644 fud2/tests/snapshots/tests__test@calyx_to_verilog.snap create mode 100644 fud2/tests/snapshots/tests__test@dahlia_to_calyx.snap create mode 100644 fud2/tests/snapshots/tests__test@mrxl_to_calyx.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_state1.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_to_state1-2.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_to_state1.snap create mode 100644 fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap diff --git a/Cargo.lock b/Cargo.lock index 81173e8323..ce5c097edc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,6 +1243,7 @@ name = "fud" version = "0.0.2" dependencies = [ "anyhow", + "figment", "fud-core", "include_dir", "insta", diff --git a/fud2/Cargo.toml b/fud2/Cargo.toml index 92f0a3e8b3..51d1822428 100644 --- a/fud2/Cargo.toml +++ b/fud2/Cargo.toml @@ -33,3 +33,4 @@ path = "src/main.rs" [dev-dependencies] insta = "1.36.0" itertools.workspace = true +figment = { version = "0.10.12", features = ["toml"] } diff --git a/fud2/fud-core/src/cli.rs b/fud2/fud-core/src/cli.rs index 5f75fdc9e2..7e9635a04c 100644 --- a/fud2/fud-core/src/cli.rs +++ b/fud2/fud-core/src/cli.rs @@ -314,7 +314,26 @@ fn get_resource(driver: &Driver, cmd: GetResource) -> anyhow::Result<()> { bail!("unknown resource file {}", cmd.filename); } -pub fn cli(driver: &Driver) -> anyhow::Result<()> { +/// Given the name of a Driver, returns a config based on that name and CLI arguments. +pub fn config_from_cli(name: &str) -> anyhow::Result { + let args: FakeArgs = argh::from_env(); + let mut config = config::load_config(name); + + // Use `--set` arguments to override configuration values. + for set in args.set { + let mut parts = set.splitn(2, '='); + let key = parts.next().unwrap(); + let value = parts + .next() + .ok_or(anyhow!("--set arguments must be in key=value form"))?; + let dict = figment::util::nest(key, value.into()); + config = config.merge(figment::providers::Serialized::defaults(dict)); + } + + Ok(config) +} + +pub fn cli(driver: &Driver, config: &figment::Figment) -> anyhow::Result<()> { let args: FakeArgs = argh::from_env(); // Configure logging. @@ -345,7 +364,7 @@ pub fn cli(driver: &Driver) -> anyhow::Result<()> { let plan = driver.plan(req).ok_or(anyhow!("could not find path"))?; // Configure. - let mut run = Run::new(driver, plan); + let mut run = Run::new(driver, plan, config.clone()); // Override some global config options. if let Some(keep) = args.keep { @@ -355,19 +374,6 @@ pub fn cli(driver: &Driver) -> anyhow::Result<()> { run.global_config.verbose = verbose; } - // Use `--set` arguments to override configuration values. - for set in args.set { - let mut parts = set.splitn(2, '='); - let key = parts.next().unwrap(); - let value = parts - .next() - .ok_or(anyhow!("--set arguments must be in key=value form"))?; - let dict = figment::util::nest(key, value.into()); - run.config_data = run - .config_data - .merge(figment::providers::Serialized::defaults(dict)); - } - // Execute. match args.mode { Mode::ShowPlan => run.show(), diff --git a/fud2/fud-core/src/exec/driver.rs b/fud2/fud-core/src/exec/driver.rs index 1ed4d796d9..c4805ca7ee 100644 --- a/fud2/fud-core/src/exec/driver.rs +++ b/fud2/fud-core/src/exec/driver.rs @@ -1,5 +1,5 @@ use super::{OpRef, Operation, Request, Setup, SetupRef, State, StateRef}; -use crate::{config, run, script, utils}; +use crate::{run, script, utils}; use camino::{Utf8Path, Utf8PathBuf}; use cranelift_entity::PrimaryMap; use rand::distributions::{Alphanumeric, DistString}; @@ -439,15 +439,11 @@ impl DriverBuilder { } /// Load any plugin scripts specified in the configuration file. - pub fn load_plugins(mut self) -> Self { + pub fn load_plugins(mut self, config_data: &figment::Figment) -> Self { // pull out things from self that we need let plugin_dir = self.scripts_dir.take(); let plugin_files = self.script_files.take(); - // TODO: Let's try to avoid loading/parsing the configuration file here and - // somehow reusing it from wherever we do that elsewhere. - let config = config::load_config(&self.name); - let mut runner = script::ScriptRunner::new(self); // add system plugins @@ -469,7 +465,7 @@ impl DriverBuilder { // add user plugins defined in config if let Ok(plugins) = - config.extract_inner::>("plugins") + config_data.extract_inner::>("plugins") { runner.add_files(plugins.into_iter()); } diff --git a/fud2/fud-core/src/run.rs b/fud2/fud-core/src/run.rs index afb44e34b4..c0978b9fa8 100644 --- a/fud2/fud-core/src/run.rs +++ b/fud2/fud-core/src/run.rs @@ -87,6 +87,79 @@ impl EmitBuild for EmitBuildFn { } } +/// A config variable. +#[derive(Debug, Clone)] +pub enum ConfigVar { + /// The key for the config variable. + Required(String), + /// The key for the config variable followed by the value it should be if the key is not found. + Optional(String, String), +} + +/// The data required to emit a single, simple op whose body is composed of an ordered set of +/// commands. +pub struct RulesOp { + /// The name of the rule generated. + pub rule_name: String, + + /// The shell commands emitted by to the generated rule. + /// Each of these must be run in order in a context where the variables in each cmd are + /// supplied. In particular, this means that variables of the form "$[i|o]" are in + /// scope. + pub cmds: Vec, + + /// Variables to be emitted + pub config_vars: Vec, +} + +/// Given the `index` of a file in the list of input/output files and if the file is an +/// `input`, returns a valid Ninja variable name. +pub fn io_file_var_name(index: usize, input: bool) -> String { + if input { + format!("i{}", index) + } else { + format!("o{}", index) + } +} + +impl EmitBuild for RulesOp { + fn build( + &self, + emitter: &mut StreamEmitter, + inputs: &[&str], + outputs: &[&str], + ) -> EmitResult { + // Write the Ninja file. + let cmd = self.cmds.join(" && "); + emitter.rule(&self.rule_name, &cmd)?; + let in_vars = inputs + .iter() + .enumerate() + .map(|(k, &v)| (io_file_var_name(k, true), v)); + let out_vars = outputs + .iter() + .enumerate() + .map(|(k, &v)| (io_file_var_name(k, false), v)); + let vars: Vec<_> = in_vars.chain(out_vars).collect(); + + for var in &self.config_vars { + match var { + ConfigVar::Required(k) => emitter.config_var(k, k)?, + ConfigVar::Optional(k, d) => emitter.config_var_or(k, k, d)?, + } + } + + emitter.build_cmd_with_args( + outputs, + &self.rule_name, + inputs, + &[], + &vars, + )?; + Ok(()) + } +} + // TODO make this unnecessary... /// A simple `build` emitter that just runs a Ninja rule. pub struct EmitRuleBuild { @@ -126,9 +199,12 @@ pub struct Run<'a> { } impl<'a> Run<'a> { - pub fn new(driver: &'a Driver, plan: Plan) -> Self { - let config_data = config::load_config(&driver.name); - Self::with_config(driver, plan, config_data) + pub fn new( + driver: &'a Driver, + plan: Plan, + config: figment::Figment, + ) -> Self { + Self::with_config(driver, plan, config) } pub fn with_config( @@ -507,6 +583,26 @@ impl Emitter { Ok(()) } + /// Emit a Ninja build command with variable list. + /// + /// Here `variables` is an association list, the first element of each tuple a key and the + /// second a value. + pub fn build_cmd_with_args( + &mut self, + targets: &[&str], + rule: &str, + deps: &[&str], + implicit_deps: &[&str], + variables: &[(String, &str)], + ) -> std::io::Result<()> { + self.build_cmd(targets, rule, deps, implicit_deps)?; + for (key, value) in variables { + self.arg(key, value)?; + } + writeln!(self.out)?; + Ok(()) + } + /// Emit a Ninja comment. pub fn comment(&mut self, text: &str) -> std::io::Result<()> { writeln!(self.out, "# {}", text)?; diff --git a/fud2/fud-core/src/script/error.rs b/fud2/fud-core/src/script/error.rs index bf2374c0b5..4bd84f8629 100644 --- a/fud2/fud-core/src/script/error.rs +++ b/fud2/fud-core/src/script/error.rs @@ -10,13 +10,40 @@ pub(super) struct RhaiSystemError { #[derive(Debug)] pub(super) enum RhaiSystemErrorKind { - ErrorSetupRef(String), + SetupRef(String), + StateRef(String), + BeganOp(String, String), + NoOp, } impl RhaiSystemError { pub(super) fn setup_ref(v: rhai::Dynamic) -> Self { Self { - kind: RhaiSystemErrorKind::ErrorSetupRef(v.to_string()), + kind: RhaiSystemErrorKind::SetupRef(v.to_string()), + position: rhai::Position::NONE, + } + } + + pub(super) fn state_ref(v: rhai::Dynamic) -> Self { + Self { + kind: RhaiSystemErrorKind::StateRef(v.to_string()), + position: rhai::Position::NONE, + } + } + + pub(super) fn began_op(old_name: &str, new_name: &str) -> Self { + Self { + kind: RhaiSystemErrorKind::BeganOp( + old_name.to_string(), + new_name.to_string(), + ), + position: rhai::Position::NONE, + } + } + + pub(super) fn no_op() -> Self { + Self { + kind: RhaiSystemErrorKind::NoOp, position: rhai::Position::NONE, } } @@ -30,9 +57,18 @@ impl RhaiSystemError { impl Display for RhaiSystemError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.kind { - RhaiSystemErrorKind::ErrorSetupRef(v) => { + RhaiSystemErrorKind::SetupRef(v) => { write!(f, "Unable to construct SetupRef: `{v:?}`") } + RhaiSystemErrorKind::StateRef(v) => { + write!(f, "Unable to construct StateRef: `{v:?}`") + } + RhaiSystemErrorKind::BeganOp(old_name, new_name) => { + write!(f, "Unable to build two ops at once: trying to build `{new_name:?}` but already building `{old_name:?}`") + } + RhaiSystemErrorKind::NoOp => { + write!(f, "Unable to find current op being built. Consider calling start_op_stmts earlier in the program.") + } } } } diff --git a/fud2/fud-core/src/script/plugin.rs b/fud2/fud-core/src/script/plugin.rs index 95a97f5a37..632e24db80 100644 --- a/fud2/fud-core/src/script/plugin.rs +++ b/fud2/fud-core/src/script/plugin.rs @@ -1,9 +1,11 @@ +use rhai::{Dynamic, ImmutableString, ParseError, Position}; + use crate::{ exec::{SetupRef, StateRef}, DriverBuilder, }; use std::{ - cell::RefCell, + cell::{RefCell, RefMut}, collections::HashMap, path::{Path, PathBuf}, rc::Rc, @@ -16,12 +18,29 @@ use super::{ resolver::Resolver, }; +/// The signature and implementation of an operation specified in Rhai. +struct RhaiOp { + /// Operation name. + name: String, + /// Inputs states of the op. + input_states: Vec, + /// Output states of the op. + output_states: Vec, + /// An ordered list of the commands run when this op is required. + cmds: Vec, + /// A list of the values required from the config. + config_vars: Vec, +} + #[derive(Clone)] struct ScriptContext { builder: Rc>, path: Rc, ast: Rc, setups: Rc>>, + + /// An op currently being built. `None` means no op is currently being built. + cur_op: Rc>>, } impl ScriptContext { @@ -72,6 +91,182 @@ impl ScriptContext { *self.setups.borrow().get(fnptr.fn_name()).unwrap() } + + /// Begins building an op. This fails if an op is already being built or `input` or `output` + /// are not arrays of `StateRef`. + fn begin_op( + &self, + pos: Position, + name: &str, + inputs: rhai::Array, + outputs: rhai::Array, + ) -> RhaiResult<()> { + let inputs = inputs + .into_iter() + .map(|i| match i.clone().try_cast::() { + Some(state) => Ok(state), + None => Err(RhaiSystemError::state_ref(i).with_pos(pos).into()), + }) + .collect::>>()?; + let outputs = outputs + .into_iter() + .map(|i| match i.clone().try_cast::() { + Some(state) => Ok(state), + None => Err(RhaiSystemError::state_ref(i).with_pos(pos).into()), + }) + .collect::>>()?; + + let mut cur_op = self.cur_op.borrow_mut(); + match *cur_op { + None => { + *cur_op = Some(RhaiOp { + name: name.to_string(), + input_states: inputs, + output_states: outputs, + cmds: vec![], + config_vars: vec![], + }); + Ok(()) + } + Some(RhaiOp { + name: ref old_name, + input_states: _, + output_states: _, + cmds: _, + config_vars: _, + }) => Err(RhaiSystemError::began_op(old_name, name) + .with_pos(pos) + .into()), + } + } + + /// Adds a shell command to the `cur_op`.Returns and error if `begin_op` has not been called + /// before this `end_op` and after any previous `end_op` + fn add_shell(&self, pos: Position, cmd: String) -> RhaiResult<()> { + let mut cur_op = self.cur_op.borrow_mut(); + match *cur_op { + Some(ref mut op_sig) => { + op_sig.cmds.push(cmd); + Ok(()) + } + None => Err(RhaiSystemError::no_op().with_pos(pos).into()), + } + } + + /// Adds a config var. Returns an error if `begin_op` has not been called + /// before this `end_op` and after any previous `end_op`. + fn add_config_var( + &self, + pos: Position, + var: crate::run::ConfigVar, + ) -> RhaiResult<()> { + let mut cur_op = self.cur_op.borrow_mut(); + match *cur_op { + Some(ref mut op_sig) => { + op_sig.config_vars.push(var); + Ok(()) + } + None => Err(RhaiSystemError::no_op().with_pos(pos).into()), + } + } + + /// Collects an op currently being built and adds it to `bld`. Returns and error if `begin_op` + /// has not been called before this `end_op` and after any previous `end_op`. + fn end_op( + &self, + pos: Position, + mut bld: RefMut, + ) -> RhaiResult<()> { + let mut cur_op = self.cur_op.borrow_mut(); + match *cur_op { + Some(RhaiOp { + ref name, + ref input_states, + ref output_states, + ref cmds, + ref config_vars, + }) => { + // Create the emitter. + let cmds = cmds.clone(); + let op_name = name.clone(); + let config_vars = config_vars.clone(); + let op_emitter = crate::run::RulesOp { + rule_name: op_name, + cmds, + config_vars, + }; + + // Add the op. + bld.add_op(name, &[], input_states, output_states, op_emitter); + + // Now no op is being built. + *cur_op = None; + Ok(()) + } + None => Err(RhaiSystemError::no_op().with_pos(pos).into()), + } + } +} + +/// All nodes in the parsing state machine. +#[derive(Debug, Copy, Clone)] +enum ParseNode { + DefopS, + IdentS, + OpenParenS, + IdentP1, + ColonP1, + ExprP1, + CommaP1, + CloseParenP1, + ArrowS, + IdentP2, + ColonP2, + ExprP2, + CommaP2, + BlockS, +} + +/// All of the state of the parser. +#[derive(Debug, Clone)] +struct ParseState { + node: ParseNode, + inputs: usize, + outputs: usize, +} + +impl ParseState { + pub fn new() -> Self { + Self { + node: ParseNode::DefopS, + inputs: 0, + outputs: 0, + } + } + + pub fn node(&self, node: ParseNode) -> Self { + Self { + node, + inputs: self.inputs, + outputs: self.outputs, + } + } + + pub fn inputs(&self, inputs: usize) -> Self { + Self { + node: self.node, + inputs, + outputs: self.outputs, + } + } + + pub fn outputs(&self, outputs: usize) -> Self { + Self { + node: self.node, + inputs: self.inputs, + outputs, + } + } } pub struct ScriptRunner { @@ -94,6 +289,7 @@ impl ScriptRunner { this.reg_state(); this.reg_get_state(); this.reg_get_setup(); + this.reg_defop_syntax_nop(); this } @@ -242,12 +438,253 @@ impl ScriptRunner { ); } + /// Registers a Rhai function which starts the parser listening for shell commands, how an op + /// does its transformation. + fn reg_start_op_stmts(&mut self, sctx: ScriptContext) { + self.engine.register_fn( + "start_op_stmts", + move |ctx: rhai::NativeCallContext, + name: &str, + inputs: rhai::Array, + outputs: rhai::Array| + -> RhaiResult<_> { + sctx.begin_op(ctx.position(), name, inputs, outputs) + }, + ); + } + + /// Registers a Rhai function which adds shell commands to be used by an op. + fn reg_shell(&mut self, sctx: ScriptContext) { + self.engine.register_fn( + "shell", + move |ctx: rhai::NativeCallContext, cmd: &str| -> RhaiResult<_> { + sctx.add_shell(ctx.position(), cmd.to_string()) + }, + ); + } + + /// Registers a Rhai function for getting values from the config file. + fn reg_config(&mut self, sctx: ScriptContext) { + self.engine.register_fn( + "config", + move |ctx: rhai::NativeCallContext, key: &str| -> RhaiResult<_> { + sctx.add_config_var( + ctx.position(), + crate::run::ConfigVar::Required(key.to_string()), + )?; + Ok(format!("${{{}}}", key)) + }, + ); + } + + /// Registers a Rhai function for getting values from the config file or using a provided + /// string if the key is not found. + fn reg_config_or(&mut self, sctx: ScriptContext) { + self.engine.register_fn( + "config_or", + move |ctx: rhai::NativeCallContext, + key: &str, + default: &str| + -> RhaiResult<_> { + sctx.add_config_var( + ctx.position(), + crate::run::ConfigVar::Optional( + key.to_string(), + default.to_string(), + ), + )?; + Ok(format!("${{{}}}", key)) + }, + ); + } + + /// Registers a Rhai function which stops the parser listening to shell commands and adds the + /// created op to `self.builder`. + fn reg_end_op_stmts(&mut self, sctx: ScriptContext) { + let bld = Rc::clone(&self.builder); + self.engine.register_fn( + "end_op_stmts", + move |ctx: rhai::NativeCallContext| -> RhaiResult<_> { + sctx.end_op(ctx.position(), bld.borrow_mut()) + }, + ); + } + + /// A parse function to add custom syntax for defining ops to rhai. + fn parse_defop( + symbols: &[ImmutableString], + look_ahead: &str, + state: &mut Dynamic, + ) -> Result, ParseError> { + if symbols.len() == 1 { + *state = Dynamic::from(ParseState::new()); + } + let s = state.clone_cast::(); + match s.node { + ParseNode::DefopS => { + *state = Dynamic::from(s.node(ParseNode::IdentS)); + Ok(Some("$ident$".into())) + } + ParseNode::IdentS => { + *state = Dynamic::from(s.node(ParseNode::OpenParenS)); + Ok(Some("(".into())) + } + ParseNode::OpenParenS => { + *state = Dynamic::from(s.node(ParseNode::IdentP1)); + Ok(Some("$ident$".into())) + } + ParseNode::IdentP1 => { + *state = Dynamic::from( + s.node(ParseNode::ColonP1).inputs(s.inputs + 1), + ); + Ok(Some(":".into())) + } + ParseNode::ColonP1 => { + *state = Dynamic::from(s.node(ParseNode::ExprP1)); + Ok(Some("$expr$".into())) + } + ParseNode::ExprP1 => { + if look_ahead == "," { + *state = Dynamic::from(s.node(ParseNode::CommaP1)); + Ok(Some(",".into())) + } else { + *state = Dynamic::from(s.node(ParseNode::CloseParenP1)); + Ok(Some(")".into())) + } + } + ParseNode::CommaP1 => { + *state = Dynamic::from(s.node(ParseNode::IdentP1)); + Ok(Some("$ident$".into())) + } + ParseNode::CloseParenP1 => { + *state = Dynamic::from(s.node(ParseNode::ArrowS)); + Ok(Some(">>".into())) + } + ParseNode::ArrowS => { + *state = Dynamic::from(s.node(ParseNode::IdentP2)); + Ok(Some("$ident$".into())) + } + ParseNode::IdentP2 => { + *state = Dynamic::from( + s.node(ParseNode::ColonP2).outputs(s.outputs + 1), + ); + Ok(Some(":".into())) + } + ParseNode::ColonP2 => { + *state = Dynamic::from(s.node(ParseNode::ExprP2)); + Ok(Some("$expr$".into())) + } + ParseNode::ExprP2 => { + if look_ahead == "," { + *state = Dynamic::from(s.node(ParseNode::CommaP2)); + Ok(Some(",".into())) + } else { + *state = Dynamic::from(s.node(ParseNode::BlockS)); + Ok(Some("$block$".into())) + } + } + ParseNode::CommaP2 => { + *state = Dynamic::from(s.node(ParseNode::IdentP2)); + Ok(Some("$ident$".into())) + } + ParseNode::BlockS => Ok(None), + } + } + + /// Registers custom syntax for defining op without actually defining the op. + fn reg_defop_syntax_nop(&mut self) { + self.engine.register_custom_syntax_with_state_raw( + "defop", + Self::parse_defop, + false, + move |_context, _inputs, _state| Ok(().into()), + ); + } + + /// Registers a custom syntax for creating ops using `start_op_stmts` and `end_op_stmts`. + fn reg_defop_syntax(&mut self, sctx: ScriptContext) { + let bld = Rc::clone(&self.builder); + self.engine.register_custom_syntax_with_state_raw( + "defop", + Self::parse_defop, + true, + move |context, inputs, state| { + let state = state.clone_cast::(); + // Collect name of op and input/output states. + let op_name = + inputs.first().unwrap().get_string_value().unwrap(); + let input_names: Vec<_> = inputs + .iter() + .skip(1) + .step_by(2) + .take(state.inputs) + .map(|n| { + Dynamic::from(n.get_string_value().unwrap().to_string()) + }) + .collect(); + let input_states = inputs + .iter() + .skip(2) + .step_by(2) + .take(state.inputs) + .map(|s| s.eval_with_context(context)) + .collect::>>()?; + let output_names: Vec<_> = inputs + .iter() + .skip(1 + 2 * state.inputs) + .step_by(2) + .take(state.outputs) + .map(|n| { + Dynamic::from(n.get_string_value().unwrap().to_string()) + }) + .collect(); + let output_states: Vec<_> = inputs + .iter() + .skip(2 + 2 * state.inputs) + .step_by(2) + .take(state.outputs) + .map(|s| s.eval_with_context(context)) + .collect::>>()?; + let body = inputs.last().unwrap(); + + let orig_scope_size = context.scope().len(); + + for (i, name) in input_names.clone().into_iter().enumerate() { + context.scope_mut().push( + name.into_string().unwrap(), + format!("${}", crate::run::io_file_var_name(i, true)), + ); + } + + for (i, name) in output_names.clone().into_iter().enumerate() { + context.scope_mut().push( + name.into_string().unwrap(), + format!("${}", crate::run::io_file_var_name(i, false)), + ); + } + + // Position to note error. + let op_pos = inputs.first().unwrap().position(); + + // Begin listening for `shell` functions. Execute definition body and collect into + // an op. + sctx.begin_op(op_pos, op_name, input_states, output_states)?; + let _ = body.eval_with_context(context)?; + let res = + sctx.end_op(op_pos, bld.borrow_mut()).map(Dynamic::from); + context.scope_mut().rewind(orig_scope_size); + res + }, + ); + } + fn script_context(&self, path: PathBuf) -> ScriptContext { ScriptContext { builder: Rc::clone(&self.builder), path: Rc::new(path), ast: Rc::new(self.rhai_functions.clone()), setups: Rc::clone(&self.setups), + cur_op: Rc::new(None.into()), } } @@ -255,6 +692,12 @@ impl ScriptRunner { let sctx = self.script_context(path.to_path_buf()); self.reg_rule(sctx.clone()); self.reg_op(sctx.clone()); + self.reg_start_op_stmts(sctx.clone()); + self.reg_shell(sctx.clone()); + self.reg_end_op_stmts(sctx.clone()); + self.reg_defop_syntax(sctx.clone()); + self.reg_config(sctx.clone()); + self.reg_config_or(sctx.clone()); self.engine .module_resolver() diff --git a/fud2/src/main.rs b/fud2/src/main.rs index 4f05686a71..419939e729 100644 --- a/fud2/src/main.rs +++ b/fud2/src/main.rs @@ -36,11 +36,14 @@ fn main() -> anyhow::Result<()> { }); } + // Get config values from cli. + let config = cli::config_from_cli(&bld.name)?; + #[cfg(feature = "migrate_to_scripts")] { - bld = bld.load_plugins(); + bld = bld.load_plugins(&config); } let driver = bld.build(); - cli::cli(&driver) + cli::cli(&driver, &config) } diff --git a/fud2/tests/scripts/defop/test.rhai b/fud2/tests/scripts/defop/test.rhai new file mode 100644 index 0000000000..676a403d8d --- /dev/null +++ b/fud2/tests/scripts/defop/test.rhai @@ -0,0 +1,38 @@ +const state0 = state("state0", []); +const state1 = state("state1", []); +const state2 = state("state2", []); +const state3 = state("state3", []); +const state4 = state("state4", []); + +// simple defop +defop t0(s0: state0) >> s1: state1 { + shell("echo uwu"); +} + +// defop combining states +defop t1(s0: state0, s1: state1) >> s2: state2 { + shell("echo owo"); +} + +// defop splitting states +defop t2(s0: state0) >> s1: state1, s2: state2 { + shell("echo -_-"); +} + +// testing input and output variables +defop t3(s0: state0, s1: state1, s2: state2) >> s3: state3, s4: state4 { + shell(`echo inputs ${s0} ${s1} ${s2}`); + shell(`echo outputs ${s3} ${s4}`); +} + +// testing config works +defop t4(s0: state0) >> s1: state1 { + let c0 = config("c0"); + shell(`echo ${c0}`); +} + +// testing config_or works +defop t5(s0: state0) >> s1: state1 { + let c0 = config_or("this-config-better-not-exist", "gholdengo"); + shell(`echo ${c0}`); +} diff --git a/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap new file mode 100644 index 0000000000..1f8b731cea --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap @@ -0,0 +1,50 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> dat through cider" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +cider-exe = $calyx-base/target/debug/cider +cider-converter = $calyx-base/target/debug/cider-data-converter +rule run-cider-debug + command = $cider-exe -l $calyx-base --data data.dump $in debug || true + pool = console +rule run-cider + command = $cider-exe -l $calyx-base --data data.dump $in > $out +rule dump-to-interp + command = $cider-converter --to cider $in > $out +rule interp-to-dump + command = $cider-converter --to json $in > $out +build data.dump: dump-to-interp $sim_data | $cider-converter + +build pseudo_cider: calyx-with-flags _from_stdin_calyx.futil +build interp_out.dump: run-cider pseudo_cider | data.dump +build _to_stdout_dat.json: interp-to-dump interp_out.dump | $sim_data $cider-converter + +default _to_stdout_dat.json diff --git a/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap b/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap new file mode 100644 index 0000000000..5b22cb0ebf --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap @@ -0,0 +1,49 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> verilog-refmem through firrtl" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +rule ref-to-external + command = sed 's/ref /@external /g' $in > $out +rule external-to-ref + command = sed 's/@external([0-9]*)/ref/g' $in | sed 's/@external/ref/g' > $out +gen-testbench-script = $calyx-base/tools/firrtl/generate-testbench.py +build memories.sv: get-rsrc +rule generate-refmem-testbench + command = python3 $gen-testbench-script $in > $out +rule dummy + command = sh -c 'cat $$0' $in > $out + +firrtl-exe = /test/bin/firrtl +rule firrtl + command = $firrtl-exe -i $in -o $out -X sverilog +build primitives-for-firrtl.sv: get-rsrc +rule add-verilog-primitives + command = cat primitives-for-firrtl.sv $in > $out + +build external.futil: ref-to-external _from_stdin_calyx.futil +build ref.futil: external-to-ref _from_stdin_calyx.futil +build memory-info.json: yxi external.futil +build tb.sv: generate-refmem-testbench memory-info.json +build tmp-out.fir: calyx ref.futil + backend = firrtl + args = --emit-primitive-extmodules +build firrtl.fir: dummy tmp-out.fir tb.sv +build partial.sv: firrtl firrtl.fir +build _to_stdout_verilog-refmem.sv: add-verilog-primitives partial.sv | primitives-for-firrtl.sv + +default _to_stdout_verilog-refmem.sv diff --git a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap new file mode 100644 index 0000000000..2a1a35ed98 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap @@ -0,0 +1,50 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> dat through icarus" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +iverilog = iverilog +rule icarus-compile-standalone-tb + command = $iverilog -g2012 -o $out tb.sv $in +rule icarus-compile-custom-tb + command = $iverilog -g2012 -o $out tb.sv memories.sv $in + +build verilog-noverify.sv: calyx _from_stdin_calyx.futil + backend = verilog + args = --disable-verify +build sim.exe: icarus-compile-standalone-tb verilog-noverify.sv | tb.sv +build sim.log: sim-run sim.exe $datadir + bin = sim.exe + args = +NOTRACE=1 +build _to_stdout_dat.json: json-data $datadir sim.log | json-dat.py + +default _to_stdout_dat.json diff --git a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap new file mode 100644 index 0000000000..85827059d6 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap @@ -0,0 +1,49 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> vcd through icarus" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +iverilog = iverilog +rule icarus-compile-standalone-tb + command = $iverilog -g2012 -o $out tb.sv $in +rule icarus-compile-custom-tb + command = $iverilog -g2012 -o $out tb.sv memories.sv $in + +build verilog-noverify.sv: calyx _from_stdin_calyx.futil + backend = verilog + args = --disable-verify +build sim.exe: icarus-compile-standalone-tb verilog-noverify.sv | tb.sv +build sim.log _to_stdout_vcd.vcd: sim-run sim.exe $datadir + bin = sim.exe + args = +NOTRACE=0 +OUT=_to_stdout_vcd.vcd + +default _to_stdout_vcd.vcd diff --git a/fud2/tests/snapshots/tests__test@calyx_through_interp_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_interp_to_dat.snap new file mode 100644 index 0000000000..713d53f60d --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_interp_to_dat.snap @@ -0,0 +1,60 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> dat through interp" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +cider-exe = $calyx-base/target/debug/cider +cider-converter = $calyx-base/target/debug/cider-data-converter +rule cider + command = $cider-exe -l $calyx-base --raw --data data.json $in > $out +rule cider-debug + command = $cider-exe -l $calyx-base --data data.json $in debug || true + pool = console +build interp-dat.py: get-rsrc +python = python3 +rule dat-to-interp + command = $python interp-dat.py --to-interp $in +rule interp-to-dat + command = $python interp-dat.py --from-interp $in $sim_data > $out +build data.json: dat-to-interp $sim_data | interp-dat.py +rule run-cider + command = $cider-exe -l $calyx-base --data data.dump $in flat > $out +rule dump-to-interp + command = $cider-converter --to cider $in > $out +rule interp-to-dump + command = $cider-converter --to json $in > $out +build data.dump: dump-to-interp $sim_data | $cider-converter + +build interp_out.json: cider _from_stdin_calyx.futil | data.json +build _to_stdout_dat.json: interp-to-dat interp_out.json | $sim_data interp-dat.py + +default _to_stdout_dat.json diff --git a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap new file mode 100644 index 0000000000..184bb8a24f --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap @@ -0,0 +1,54 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> dat through verilator" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +verilator = verilator +cycle-limit = 500000000 +rule verilator-compile-standalone-tb + command = $verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule verilator-compile-custom-tb + command = $verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule cp + command = cp $in $out + +build verilog.sv: calyx _from_stdin_calyx.futil + backend = verilog +build verilator-out/VTOP: verilator-compile-standalone-tb verilog.sv | tb.sv + out-dir = verilator-out +build sim.exe: cp verilator-out/VTOP +build sim.log: sim-run sim.exe $datadir + bin = sim.exe + args = +NOTRACE=1 +build _to_stdout_dat.json: json-data $datadir sim.log | json-dat.py + +default _to_stdout_dat.json diff --git a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap new file mode 100644 index 0000000000..07ab0b6544 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap @@ -0,0 +1,53 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> vcd through verilator" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +verilator = verilator +cycle-limit = 500000000 +rule verilator-compile-standalone-tb + command = $verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule verilator-compile-custom-tb + command = $verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule cp + command = cp $in $out + +build verilog.sv: calyx _from_stdin_calyx.futil + backend = verilog +build verilator-out/VTOP: verilator-compile-standalone-tb verilog.sv | tb.sv + out-dir = verilator-out +build sim.exe: cp verilator-out/VTOP +build sim.log _to_stdout_vcd.vcd: sim-run sim.exe $datadir + bin = sim.exe + args = +NOTRACE=0 +OUT=_to_stdout_vcd.vcd + +default _to_stdout_vcd.vcd diff --git a/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap new file mode 100644 index 0000000000..6531aaf4f2 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap @@ -0,0 +1,76 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> vcd through xrt-trace" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +vivado-dir = /test/xilinx/vivado +vitis-dir = /test/xilinx/vitis +build gen_xo.tcl: get-rsrc +build get-ports.py: get-rsrc +python = python3 +rule gen-xo + command = $vivado-dir/bin/vivado -mode batch -source gen_xo.tcl -tclargs $out `$python get-ports.py kernel.xml` + pool = console +xilinx-mode = hw_emu +platform = xilinx_u50_gen3x16_xdma_201920_3 +rule compile-xclbin + command = $vitis-dir/bin/v++ -g -t $xilinx-mode --platform $platform --save-temps --profile.data all:all:all --profile.exec all:all:all -lo $out $in + pool = console + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +rule emconfig + command = $vitis-dir/bin/emconfigutil --platform $platform +build emconfig.json: emconfig +xrt-dir = /test/xilinx/xrt +rule xclrun + command = bash -c 'source $vitis-dir/settings64.sh ; source $xrt-dir/setup.sh ; XRT_INI_PATH=$xrt_ini EMCONFIG_PATH=. XCL_EMULATION_MODE=$xilinx-mode $python -m fud.xclrun --out $out $in' + pool = console +rule echo + command = echo $contents > $out +build pre_sim.tcl: echo | + contents = open_vcd\\nlog_vcd *\\n +build post_sim.tcl: echo | + contents = close_vcd\\n + +build main.sv: calyx _from_stdin_calyx.futil + backend = verilog + args = --synthesis -p external +build toplevel.v: calyx _from_stdin_calyx.futil + backend = xilinx +build kernel.xml: calyx _from_stdin_calyx.futil + backend = xilinx-xml +build xo.xo: gen-xo | main.sv toplevel.v kernel.xml gen_xo.tcl get-ports.py +build xclbin.xclbin: compile-xclbin xo.xo +build xrt_trace.ini: get-rsrc +build _to_stdout_vcd.vcd: xclrun xclbin.xclbin $sim_data | emconfig.json pre_sim.tcl post_sim.tcl xrt_trace.ini + xrt_ini = xrt_trace.ini + +default _to_stdout_vcd.vcd diff --git a/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap new file mode 100644 index 0000000000..5aa1580b4b --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap @@ -0,0 +1,76 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> dat through xrt" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +vivado-dir = /test/xilinx/vivado +vitis-dir = /test/xilinx/vitis +build gen_xo.tcl: get-rsrc +build get-ports.py: get-rsrc +python = python3 +rule gen-xo + command = $vivado-dir/bin/vivado -mode batch -source gen_xo.tcl -tclargs $out `$python get-ports.py kernel.xml` + pool = console +xilinx-mode = hw_emu +platform = xilinx_u50_gen3x16_xdma_201920_3 +rule compile-xclbin + command = $vitis-dir/bin/v++ -g -t $xilinx-mode --platform $platform --save-temps --profile.data all:all:all --profile.exec all:all:all -lo $out $in + pool = console + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +rule emconfig + command = $vitis-dir/bin/emconfigutil --platform $platform +build emconfig.json: emconfig +xrt-dir = /test/xilinx/xrt +rule xclrun + command = bash -c 'source $vitis-dir/settings64.sh ; source $xrt-dir/setup.sh ; XRT_INI_PATH=$xrt_ini EMCONFIG_PATH=. XCL_EMULATION_MODE=$xilinx-mode $python -m fud.xclrun --out $out $in' + pool = console +rule echo + command = echo $contents > $out +build pre_sim.tcl: echo | + contents = open_vcd\\nlog_vcd *\\n +build post_sim.tcl: echo | + contents = close_vcd\\n + +build main.sv: calyx _from_stdin_calyx.futil + backend = verilog + args = --synthesis -p external +build toplevel.v: calyx _from_stdin_calyx.futil + backend = xilinx +build kernel.xml: calyx _from_stdin_calyx.futil + backend = xilinx-xml +build xo.xo: gen-xo | main.sv toplevel.v kernel.xml gen_xo.tcl get-ports.py +build xclbin.xclbin: compile-xclbin xo.xo +build xrt.ini: get-rsrc +build _to_stdout_dat.json: xclrun xclbin.xclbin $sim_data | emconfig.json xrt.ini + xrt_ini = xrt.ini + +default _to_stdout_dat.json diff --git a/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap b/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap new file mode 100644 index 0000000000..a88b6ace90 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap @@ -0,0 +1,51 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> cider-debug" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +build tb.sv: get-rsrc + +cider-exe = $calyx-base/target/debug/cider +cider-converter = $calyx-base/target/debug/cider-data-converter +rule run-cider-debug + command = $cider-exe -l $calyx-base --data data.dump $in debug || true + pool = console +rule run-cider + command = $cider-exe -l $calyx-base --data data.dump $in > $out +rule dump-to-interp + command = $cider-converter --to cider $in > $out +rule interp-to-dump + command = $cider-converter --to json $in > $out +build data.dump: dump-to-interp $sim_data | $cider-converter + +build pseudo_cider: calyx-with-flags _from_stdin_calyx.futil +build _to_stdout_cider-debug: run-cider-debug pseudo_cider | data.dump + +default _to_stdout_cider-debug diff --git a/fud2/tests/snapshots/tests__test@calyx_to_debug.snap b/fud2/tests/snapshots/tests__test@calyx_to_debug.snap new file mode 100644 index 0000000000..8c65bdb7e6 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_to_debug.snap @@ -0,0 +1,59 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> debug" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +python = python3 +build json-dat.py: get-rsrc +rule hex-data + command = $python json-dat.py --from-json $in $out +rule json-data + command = $python json-dat.py --to-json $out $in +sim_data = /test/data.json +datadir = sim_data +build $datadir: hex-data $sim_data | json-dat.py +rule sim-run + command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out +cycle-limit = 500000000 + +build tb.sv: get-rsrc + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +cider-exe = $calyx-base/target/debug/cider +cider-converter = $calyx-base/target/debug/cider-data-converter +rule cider + command = $cider-exe -l $calyx-base --raw --data data.json $in > $out +rule cider-debug + command = $cider-exe -l $calyx-base --data data.json $in debug || true + pool = console +build interp-dat.py: get-rsrc +python = python3 +rule dat-to-interp + command = $python interp-dat.py --to-interp $in +rule interp-to-dat + command = $python interp-dat.py --from-interp $in $sim_data > $out +build data.json: dat-to-interp $sim_data | interp-dat.py +rule run-cider + command = $cider-exe -l $calyx-base --data data.dump $in flat > $out +rule dump-to-interp + command = $cider-converter --to cider $in > $out +rule interp-to-dump + command = $cider-converter --to json $in > $out +build data.dump: dump-to-interp $sim_data | $cider-converter + +build _to_stdout_debug: cider-debug _from_stdin_calyx.futil | data.json + +default _to_stdout_debug diff --git a/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap b/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap new file mode 100644 index 0000000000..af413d8098 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap @@ -0,0 +1,23 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: calyx -> verilog" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out + +build _to_stdout_verilog.sv: calyx _from_stdin_calyx.futil + backend = verilog + +default _to_stdout_verilog.sv diff --git a/fud2/tests/snapshots/tests__test@dahlia_to_calyx.snap b/fud2/tests/snapshots/tests__test@dahlia_to_calyx.snap new file mode 100644 index 0000000000..9906ddd097 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@dahlia_to_calyx.snap @@ -0,0 +1,15 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: dahlia -> calyx" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +dahlia-exe = /test/bin/dahlia +rule dahlia-to-calyx + command = $dahlia-exe -b calyx --lower -l error $in -o $out + +build _to_stdout_calyx.futil: dahlia-to-calyx _from_stdin_dahlia.fuse + +default _to_stdout_calyx.futil diff --git a/fud2/tests/snapshots/tests__test@mrxl_to_calyx.snap b/fud2/tests/snapshots/tests__test@mrxl_to_calyx.snap new file mode 100644 index 0000000000..fbd9d680b1 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@mrxl_to_calyx.snap @@ -0,0 +1,15 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: mrxl -> calyx" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +mrxl-exe = mrxl +rule mrxl-to-calyx + command = $mrxl-exe $in > $out + +build _to_stdout_calyx.futil: mrxl-to-calyx _from_stdin_mrxl.mrxl + +default _to_stdout_calyx.futil diff --git a/fud2/tests/snapshots/tests__test@state0_state1.snap b/fud2/tests/snapshots/tests__test@state0_state1.snap new file mode 100644 index 0000000000..b2cd496a36 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_state1.snap @@ -0,0 +1,16 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state1" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t0 + command = echo uwu +build _to_stdout_state1: t0 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + + +default _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap b/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap new file mode 100644 index 0000000000..01c59e594a --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap @@ -0,0 +1,20 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 state1 state2 -> state3 state4" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t3 + command = echo inputs $i0 $i1 $i2 && echo outputs $o0 $o1 +build _to_stdout_state3 _to_stdout_state4: t3 _from_stdin_state0 _from_stdin_state1 _from_stdin_state2 + i0 = _from_stdin_state0 + i1 = _from_stdin_state1 + i2 = _from_stdin_state2 + o0 = _to_stdout_state3 + o1 = _to_stdout_state4 + + +default _to_stdout_state3 +default _to_stdout_state4 diff --git a/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap b/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap new file mode 100644 index 0000000000..1ca3828b59 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap @@ -0,0 +1,17 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 state1 -> state2" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t1 + command = echo owo +build _to_stdout_state2: t1 _from_stdin_state0 _from_stdin_state1 + i0 = _from_stdin_state0 + i1 = _from_stdin_state1 + o0 = _to_stdout_state2 + + +default _to_stdout_state2 diff --git a/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap new file mode 100644 index 0000000000..2541b82a8a --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap @@ -0,0 +1,17 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state1 through t4" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t4 + command = echo ${c0} +c0 = v1 +build _to_stdout_state1: t4 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + + +default _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap new file mode 100644 index 0000000000..b50dc74348 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap @@ -0,0 +1,17 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state1 through t5" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t5 + command = echo ${this-config-better-not-exist} +this-config-better-not-exist = gholdengo +build _to_stdout_state1: t5 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + + +default _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_to_state1-2.snap b/fud2/tests/snapshots/tests__test@state0_to_state1-2.snap new file mode 100644 index 0000000000..b2cd496a36 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_to_state1-2.snap @@ -0,0 +1,16 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state1" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t0 + command = echo uwu +build _to_stdout_state1: t0 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + + +default _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_to_state1.snap new file mode 100644 index 0000000000..b2cd496a36 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_to_state1.snap @@ -0,0 +1,16 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state1" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t0 + command = echo uwu +build _to_stdout_state1: t0 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + + +default _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap b/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap new file mode 100644 index 0000000000..ff06d4ce3b --- /dev/null +++ b/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap @@ -0,0 +1,18 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: state0 -> state2 state1" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t2 + command = echo -_- +build _to_stdout_state1 _to_stdout_state2: t2 _from_stdin_state0 + i0 = _from_stdin_state0 + o0 = _to_stdout_state1 + o1 = _to_stdout_state2 + + +default _to_stdout_state2 +default _to_stdout_state1 diff --git a/fud2/tests/tests.rs b/fud2/tests/tests.rs index b0433217b7..5b0a4d9271 100644 --- a/fud2/tests/tests.rs +++ b/fud2/tests/tests.rs @@ -1,6 +1,10 @@ +use figment::providers::Format as _; use fud_core::{ config::default_config, - exec::{plan::LegacyPlanner, Plan, Request, IO}, + exec::{ + plan::{EnumeratePlanner, FindPlan, LegacyPlanner}, + Plan, Request, IO, + }, run::Run, Driver, DriverBuilder, }; @@ -16,8 +20,27 @@ fn test_driver() -> Driver { #[cfg(feature = "migrate_to_scripts")] fn test_driver() -> Driver { let mut bld = DriverBuilder::new("fud2-plugins"); + let config = figment::Figment::new(); bld.scripts_dir(manifest_dir_macros::directory_path!("scripts")); - bld.load_plugins().build() + bld.load_plugins(&config).build() +} + +fn driver_from_path_with_config( + path: &str, + config: figment::Figment, +) -> Driver { + let mut bld = DriverBuilder::new("fud2-plugins"); + let path = format!( + "{}/{}", + manifest_dir_macros::directory_path!("tests/scripts"), + path + ); + bld.scripts_dir(&path); + bld.load_plugins(&config).build() +} + +fn driver_from_path(path: &str) -> Driver { + driver_from_path_with_config(path, figment::Figment::new()) } trait InstaTest: Sized { @@ -75,7 +98,8 @@ impl InstaTest for Plan { .merge(("xilinx.vivado", "/test/xilinx/vivado")) .merge(("xilinx.vitis", "/test/xilinx/vitis")) .merge(("xilinx.xrt", "/test/xilinx/xrt")) - .merge(("dahlia", "/test/bin/dahlia")); + .merge(("dahlia", "/test/bin/dahlia")) + .merge(("c0", "v1")); let run = Run::with_config(driver, self, config); let mut buf = vec![]; run.emit(&mut buf).unwrap(); @@ -91,11 +115,17 @@ impl InstaTest for Plan { impl InstaTest for Request { fn desc(&self, driver: &Driver) -> String { - let mut desc = format!( - "emit request: {} -> {}", - driver.states[self.start_states[0]].name, - driver.states[self.end_states[0]].name - ); + let start_str = self + .start_states + .iter() + .map(|&state| &driver.states[state].name) + .join(" "); + let end_str = &self + .end_states + .iter() + .map(|&state| &driver.states[state].name) + .join(" "); + let mut desc = format!("emit request: {} -> {}", start_str, end_str); if !self.through.is_empty() { desc.push_str(" through"); for op in &self.through { @@ -107,13 +137,26 @@ impl InstaTest for Request { } fn slug(&self, driver: &Driver) -> String { - let mut desc = driver.states[self.start_states[0]].name.to_string(); - for op in &self.through { - desc.push('_'); - desc.push_str(&driver.ops[*op].name); + let mut desc = self + .start_states + .iter() + .map(|&state| &driver.states[state].name) + .join("_"); + if !self.through.is_empty() { + desc.push_str("_through"); + for op in &self.through { + desc.push('_'); + desc.push_str(&driver.ops[*op].name); + } } - desc.push('_'); - desc.push_str(&driver.states[self.end_states[0]].name); + desc.push_str("_to_"); + desc.push_str( + &self + .end_states + .iter() + .map(|&state| &driver.states[state].name) + .join("_"), + ); desc } @@ -123,23 +166,36 @@ impl InstaTest for Request { } } -fn request( +fn request_with_planner( driver: &Driver, - start: &str, - end: &str, + start: &[&str], + end: &[&str], through: &[&str], + planner: impl FindPlan + 'static, ) -> Request { fud_core::exec::Request { start_files: vec![], - start_states: vec![driver.get_state(start).unwrap()], + start_states: start + .iter() + .map(|s| driver.get_state(s).unwrap()) + .collect(), end_files: vec![], - end_states: vec![driver.get_state(end).unwrap()], + end_states: end.iter().map(|s| driver.get_state(s).unwrap()).collect(), through: through.iter().map(|s| driver.get_op(s).unwrap()).collect(), workdir: ".".into(), - planner: Box::new(LegacyPlanner {}), + planner: Box::new(planner), } } +fn request( + driver: &Driver, + start: &[&str], + end: &[&str], + through: &[&str], +) -> Request { + request_with_planner(driver, start, end, through, LegacyPlanner {}) +} + #[test] fn all_ops() { let driver = test_driver(); @@ -199,13 +255,14 @@ fn list_ops() { #[test] fn calyx_to_verilog() { let driver = test_driver(); - request(&driver, "calyx", "verilog", &[]).test(&driver); + request(&driver, &["calyx"], &["verilog"], &[]).test(&driver); } #[test] fn calyx_via_firrtl() { let driver = test_driver(); - request(&driver, "calyx", "verilog-refmem", &["firrtl"]).test(&driver); + request(&driver, &["calyx"], &["verilog-refmem"], &["firrtl"]) + .test(&driver); } #[test] @@ -213,7 +270,7 @@ fn sim_tests() { let driver = test_driver(); for dest in &["dat", "vcd"] { for sim in &["icarus", "verilator"] { - request(&driver, "calyx", dest, &[sim]).test(&driver); + request(&driver, &["calyx"], &[dest], &[sim]).test(&driver); } } } @@ -221,21 +278,64 @@ fn sim_tests() { #[test] fn cider_tests() { let driver = test_driver(); - request(&driver, "calyx", "dat", &["cider"]).test(&driver); - request(&driver, "calyx", "cider-debug", &[]).test(&driver); + request(&driver, &["calyx"], &["dat"], &["cider"]).test(&driver); + request(&driver, &["calyx"], &["cider-debug"], &[]).test(&driver); } #[test] fn xrt_tests() { let driver = test_driver(); - request(&driver, "calyx", "dat", &["xrt"]).test(&driver); - request(&driver, "calyx", "vcd", &["xrt-trace"]).test(&driver); + request(&driver, &["calyx"], &["dat"], &["xrt"]).test(&driver); + request(&driver, &["calyx"], &["vcd"], &["xrt-trace"]).test(&driver); } #[test] fn frontend_tests() { let driver = test_driver(); for frontend in &["dahlia", "mrxl"] { - request(&driver, frontend, "calyx", &[]).test(&driver); + request(&driver, &[frontend], &["calyx"], &[]).test(&driver); } } + +#[test] +fn simple_defops() { + let driver = driver_from_path("defop"); + request(&driver, &["state0"], &["state1"], &[]).test(&driver); + request_with_planner( + &driver, + &["state0", "state1"], + &["state2"], + &[], + EnumeratePlanner {}, + ) + .test(&driver); + request_with_planner( + &driver, + &["state0"], + &["state2", "state1"], + &[], + EnumeratePlanner {}, + ) + .test(&driver); + request_with_planner( + &driver, + &["state0", "state1", "state2"], + &["state3", "state4"], + &[], + EnumeratePlanner {}, + ) + .test(&driver); +} + +#[test] +fn config() { + let config = figment::Figment::from({ + let source = r#" + c0 = "v0" + "#; + figment::providers::Toml::string(source) + }); + let driver = driver_from_path_with_config("defop", config); + request(&driver, &["state0"], &["state1"], &["t4"]).test(&driver); + request(&driver, &["state0"], &["state1"], &["t5"]).test(&driver); +}