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); +}