diff --git a/Cargo.lock b/Cargo.lock index b4ab7c4fd9..cb35d054f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,6 +1160,7 @@ dependencies = [ "cranelift-entity", "env_logger", "figment", + "itertools 0.11.0", "log", "once_cell", "pathdiff", diff --git a/fud2/Cargo.toml b/fud2/Cargo.toml index 85d9af2a51..86a5e070ea 100644 --- a/fud2/Cargo.toml +++ b/fud2/Cargo.toml @@ -12,6 +12,10 @@ readme = "README.md" categories = ["build-tool"] description = "Compiler driver for the Calyx infrastructure" +[features] +migrate_to_scripts = [] +default = [] + [dependencies] fud-core = { path = "fud-core", version = "0.0.2" } anyhow.workspace = true diff --git a/fud2/fud-core/Cargo.toml b/fud2/fud-core/Cargo.toml index bf221dc426..8cb27186d0 100644 --- a/fud2/fud-core/Cargo.toml +++ b/fud2/fud-core/Cargo.toml @@ -22,4 +22,5 @@ env_logger.workspace = true rhai = "1.18.0" once_cell = "1.19.0" ariadne = "0.4.1" +itertools.workspace = true rand = "0.8.5" diff --git a/fud2/fud-core/src/exec/driver.rs b/fud2/fud-core/src/exec/driver.rs index d4b288803e..738c58a91d 100644 --- a/fud2/fud-core/src/exec/driver.rs +++ b/fud2/fud-core/src/exec/driver.rs @@ -3,7 +3,7 @@ use crate::{config, run, script, utils}; use camino::{Utf8Path, Utf8PathBuf}; use cranelift_entity::{PrimaryMap, SecondaryMap}; use rand::distributions::{Alphanumeric, DistString}; -use std::{collections::HashMap, error::Error, fmt::Display}; +use std::{collections::HashMap, error::Error, ffi::OsStr, fmt::Display}; #[derive(PartialEq)] enum Destination { @@ -246,6 +246,8 @@ pub struct DriverBuilder { ops: PrimaryMap, rsrc_dir: Option, rsrc_files: Option, + scripts_dir: Option, + script_files: Option, } #[derive(Debug)] @@ -278,6 +280,8 @@ impl DriverBuilder { ops: Default::default(), rsrc_dir: None, rsrc_files: None, + scripts_dir: None, + script_files: None, } } @@ -373,26 +377,51 @@ impl DriverBuilder { self.rsrc_files = Some(files); } + pub fn scripts_dir(&mut self, path: &str) { + self.scripts_dir = Some(path.into()); + } + + pub fn script_files(&mut self, files: FileData) { + self.script_files = Some(files); + } + /// Load any plugin scripts specified in the configuration file. - pub fn load_plugins(self) -> Self { - // Get a list of plugins (paths to Rhai scripts) from the config file, if any. + pub fn load_plugins(mut self) -> 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 plugin_files = - match config.extract_inner::>("plugins") { - Ok(v) => v, - Err(_) => { - // No plugins to load. - return self; - } - }; - let mut bld = self; - for path in plugin_files { - bld = script::ScriptRunner::run_file(bld, path.as_path()); + let mut runner = script::ScriptRunner::new(self); + + // add system plugins + if let Some(plugin_dir) = plugin_dir { + runner.add_files( + std::fs::read_dir(plugin_dir) + .unwrap() + // filter out invalid paths + .filter_map(|dir_entry| dir_entry.map(|p| p.path()).ok()) + // filter out paths that don't have `.rhai` extension + .filter(|p| p.extension() == Some(OsStr::new("rhai"))), + ); + } + + // add static plugins (where string is included in binary) + if let Some(plugin_files) = plugin_files { + runner.add_static_files(plugin_files.into_iter()); } - bld + + // add user plugins defined in config + if let Ok(plugins) = + config.extract_inner::>("plugins") + { + runner.add_files(plugins.into_iter()); + } + + runner.run() } pub fn build(self) -> Driver { diff --git a/fud2/fud-core/src/script/exec_scripts.rs b/fud2/fud-core/src/script/exec_scripts.rs index f35afa3dff..c12a54118f 100644 --- a/fud2/fud-core/src/script/exec_scripts.rs +++ b/fud2/fud-core/src/script/exec_scripts.rs @@ -119,9 +119,9 @@ impl RhaiEmitter { todo!() } - fn external_path(&mut self, path: &str) -> Utf8PathBuf { + fn external_path(&mut self, path: &str) -> String { let utf8_path = Utf8PathBuf::from(path); - self.0.borrow().external_path(&utf8_path) + self.0.borrow().external_path(&utf8_path).into_string() } fn arg(&mut self, name: &str, value: &str) -> RhaiResult<()> { @@ -162,10 +162,10 @@ thread_local! { }); } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(super) struct RhaiSetupCtx { - pub path: PathBuf, - pub ast: rhai::AST, + pub path: Rc, + pub ast: Rc, pub name: String, } @@ -179,7 +179,7 @@ impl EmitSetup for RhaiSetupCtx { &self.name, (rhai_emit.clone(),), ) - .report(&self.path) + .report(self.path.as_ref()) }); })?; @@ -202,7 +202,7 @@ impl EmitBuild for RhaiSetupCtx { &self.name, (rhai_emit.clone(), input.to_string(), output.to_string()), ) - .report(&self.path) + .report(self.path.as_ref()) }); })?; diff --git a/fud2/fud-core/src/script/mod.rs b/fud2/fud-core/src/script/mod.rs index 680571f6f1..23e4244777 100644 --- a/fud2/fud-core/src/script/mod.rs +++ b/fud2/fud-core/src/script/mod.rs @@ -2,5 +2,6 @@ mod error; mod exec_scripts; mod plugin; mod report; +mod resolver; pub use plugin::ScriptRunner; diff --git a/fud2/fud-core/src/script/plugin.rs b/fud2/fud-core/src/script/plugin.rs index 3f3ddc483f..c55ef00efd 100644 --- a/fud2/fud-core/src/script/plugin.rs +++ b/fud2/fud-core/src/script/plugin.rs @@ -12,12 +12,14 @@ use super::{ error::RhaiSystemError, exec_scripts::{to_rhai_err, to_str_slice, RhaiResult, RhaiSetupCtx}, report::RhaiReport, + resolver::Resolver, }; +#[derive(Clone)] struct ScriptContext { - builder: DriverBuilder, - path: PathBuf, - ast: rhai::AST, + builder: Rc>, + path: Rc, + ast: Rc, } impl ScriptContext { @@ -25,7 +27,7 @@ impl ScriptContext { /// an array of actual references to setups. The array might contain string names /// for the setups, or it might be function references that define those setups. fn setups_array( - &mut self, + &self, ctx: &rhai::NativeCallContext, setups: rhai::Array, ) -> RhaiResult> { @@ -33,14 +35,12 @@ impl ScriptContext { .into_iter() .map(|s| match s.clone().try_cast::() { Some(fnptr) => { - // TODO: Do we really need to clone stuff here, or can we continue to thread through - // the `Rc`? let rctx = RhaiSetupCtx { - path: self.path.clone(), - ast: self.ast.clone(), + path: Rc::clone(&self.path), + ast: Rc::new(self.ast.clone_functions_only()), name: fnptr.fn_name().to_string(), }; - Ok(self.builder.add_setup( + Ok(self.builder.borrow_mut().add_setup( &format!("{} (plugin)", fnptr.fn_name()), rctx, )) @@ -59,73 +59,92 @@ impl ScriptContext { } pub struct ScriptRunner { - ctx: Rc>, + builder: Rc>, engine: rhai::Engine, + rhai_functions: rhai::AST, + resolver: Option, } impl ScriptRunner { - fn from_file(builder: DriverBuilder, path: &Path) -> Self { - // Compile the script's source code. - let engine = rhai::Engine::new(); - let ast = engine.compile_file(path.into()).unwrap(); - - // TODO: Consider removing the clones here. We can probably just recover the stuff. - Self { - ctx: Rc::new(RefCell::new(ScriptContext { - builder, - path: path.into(), - ast: ast.clone(), - })), - engine, + pub fn new(builder: DriverBuilder) -> Self { + let mut this = Self { + builder: Rc::new(RefCell::new(builder)), + engine: rhai::Engine::new(), + rhai_functions: rhai::AST::empty(), + resolver: Some(Resolver::default()), + }; + this.reg_state(); + this.reg_get_state(); + this.reg_get_setup(); + this + } + + pub fn add_files( + &mut self, + files: impl Iterator, + ) -> &mut Self { + for f in files { + let ast = self.engine.compile_file(f.clone()).unwrap(); + let functions = + self.resolver.as_mut().unwrap().register_path(f, ast); + self.rhai_functions = self.rhai_functions.merge(&functions); + } + self + } + + pub fn add_static_files( + &mut self, + static_files: impl Iterator, + ) -> &mut Self { + for (name, data) in static_files { + let ast = self + .engine + .compile(String::from_utf8(data.to_vec()).unwrap()) + .unwrap(); + let functions = + self.resolver.as_mut().unwrap().register_data(name, ast); + self.rhai_functions = self.rhai_functions.merge(&functions); } + self } - /// Obtain the wrapped `DriverBuilder`. Panic if other references (from the - /// script, for example) still exist. - fn unwrap_builder(self) -> DriverBuilder { + fn into_builder(self) -> DriverBuilder { std::mem::drop(self.engine); // Drop references to the context. - Rc::into_inner(self.ctx) + Rc::into_inner(self.builder) .expect("script references still live") .into_inner() - .builder } fn reg_state(&mut self) { - let sctx = self.ctx.clone(); + let bld = Rc::clone(&self.builder); self.engine.register_fn( "state", move |name: &str, extensions: rhai::Array| { let v = to_str_slice(&extensions); let v = v.iter().map(|x| &**x).collect::>(); - sctx.borrow_mut().builder.state(name, &v) + bld.borrow_mut().state(name, &v) }, ); } fn reg_get_state(&mut self) { - let sctx = self.ctx.clone(); + let bld = Rc::clone(&self.builder); self.engine .register_fn("get_state", move |state_name: &str| { - sctx.borrow() - .builder - .find_state(state_name) - .map_err(to_rhai_err) + bld.borrow().find_state(state_name).map_err(to_rhai_err) }); } fn reg_get_setup(&mut self) { - let sctx = self.ctx.clone(); + let bld = Rc::clone(&self.builder); self.engine .register_fn("get_setup", move |setup_name: &str| { - sctx.borrow() - .builder - .find_setup(setup_name) - .map_err(to_rhai_err) + bld.borrow().find_setup(setup_name).map_err(to_rhai_err) }); } - fn reg_rule(&mut self) { - let sctx = self.ctx.clone(); + fn reg_rule(&mut self, sctx: ScriptContext) { + let bld = Rc::clone(&self.builder); self.engine.register_fn::<_, 4, true, OpRef, true, _>( "rule", move |ctx: rhai::NativeCallContext, @@ -133,15 +152,14 @@ impl ScriptRunner { input: StateRef, output: StateRef, rule_name: &str| { - let mut sctx = sctx.borrow_mut(); let setups = sctx.setups_array(&ctx, setups)?; - Ok(sctx.builder.rule(&setups, input, output, rule_name)) + Ok(bld.borrow_mut().rule(&setups, input, output, rule_name)) }, ); } - fn reg_op(&mut self) { - let sctx = self.ctx.clone(); + fn reg_op(&mut self, sctx: ScriptContext) { + let bld = Rc::clone(&self.builder); self.engine.register_fn::<_, 5, true, OpRef, true, _>( "op", move |ctx: rhai::NativeCallContext, @@ -150,42 +168,55 @@ impl ScriptRunner { input: StateRef, output: StateRef, build: rhai::FnPtr| { - let mut sctx = sctx.borrow_mut(); let setups = sctx.setups_array(&ctx, setups)?; let rctx = RhaiSetupCtx { path: sctx.path.clone(), - ast: sctx.ast.clone(), + ast: Rc::new(sctx.ast.clone_functions_only()), name: build.fn_name().to_string(), }; - Ok(sctx.builder.add_op(name, &setups, input, output, rctx)) + Ok(bld.borrow_mut().add_op(name, &setups, input, output, rctx)) }, ); } - /// Register all the builder functions in the engine. - fn register(&mut self) { - self.reg_state(); - self.reg_get_state(); - self.reg_get_setup(); - self.reg_rule(); - self.reg_op(); + fn script_context(&self, path: PathBuf) -> ScriptContext { + ScriptContext { + builder: Rc::clone(&self.builder), + path: Rc::new(path), + ast: Rc::new(self.rhai_functions.clone()), + } } - /// Run the script. - fn run(&self) { - // TODO this whole dance feels unnecessary... - let (ast, path) = { - let sctx = self.ctx.borrow(); - (sctx.ast.clone(), sctx.path.clone()) - }; - self.engine.run_ast(&ast).report(path); + fn run_file(&mut self, path: &Path) { + let sctx = self.script_context(path.to_path_buf()); + self.reg_rule(sctx.clone()); + self.reg_op(sctx.clone()); + + self.engine + .module_resolver() + .resolve( + &self.engine, + None, + path.to_str().unwrap(), + rhai::Position::NONE, + ) + .report(path); } - /// Execute a script from a file, adding to the builder. - pub fn run_file(builder: DriverBuilder, path: &Path) -> DriverBuilder { - let mut runner = ScriptRunner::from_file(builder, path); - runner.register(); - runner.run(); - runner.unwrap_builder() + pub fn run(mut self) -> DriverBuilder { + // take ownership of the resolver + let resolver = self.resolver.take().unwrap(); + // grab the paths from the resolver + let paths = resolver.paths(); + // set our engine to use this resolver + self.engine.set_module_resolver(resolver); + + // run all the paths we've registered + for p in paths { + self.run_file(p.as_path()); + } + + // transform self back into a DriverBuilder + self.into_builder() } } diff --git a/fud2/fud-core/src/script/report.rs b/fud2/fud-core/src/script/report.rs index a44b5278dc..98d2bab69d 100644 --- a/fud2/fud-core/src/script/report.rs +++ b/fud2/fud-core/src/script/report.rs @@ -24,11 +24,10 @@ impl RhaiReport for rhai::Position { len: usize, msg: S, ) { - let source = - fs::read_to_string(path.as_ref()).expect("Failed to open file"); + let source = fs::read_to_string(path.as_ref()); let name = path.as_ref().to_str().unwrap(); - if let Some(line) = self.line() { + if let (Some(line), Ok(source)) = (self.line(), source) { let position = self.position().unwrap_or(1); // translate a line offset into a char offset let line_offset = source @@ -53,48 +52,64 @@ impl RhaiReport for rhai::Position { .eprint((name, Source::from(source))) .unwrap() } else { - eprintln!("Failed to load plugin {name}"); - eprintln!(" {}", msg.as_ref()); + eprintln!("Failed to load plugin: {name}"); + let pos_str = if self.is_none() { + "".to_string() + } else { + format!(" @ {self}") + }; + eprintln!(" {}{pos_str}", msg.as_ref()); } } } -impl RhaiReport for RhaiResult { +impl RhaiReport for EvalAltResult { fn report_raw, S: AsRef>( &self, path: P, _len: usize, _msg: S, ) { - if let Err(e) = self { - match &**e { - EvalAltResult::ErrorVariableNotFound(variable, pos) => { - pos.report_raw(&path, variable.len(), "Undefined variable") - } - EvalAltResult::ErrorFunctionNotFound(msg, pos) => { - let (fn_name, args) = - msg.split_once(' ').unwrap_or((msg, "")); - pos.report_raw( - &path, - fn_name.len(), - format!("{fn_name} {args}"), - ) - } - EvalAltResult::ErrorSystem(msg, err) - if err.is::() => - { - let e = err.downcast_ref::().unwrap(); - let msg = if msg.is_empty() { - format!("{err}") - } else { - format!("{msg}: {err}") - }; - e.position.report_raw(&path, 0, msg) - } - // for errors that we don't have custom processing, just point - // to the beginning of the error, and use the error Display as message - e => e.position().report_raw(&path, 0, format!("{e}")), + match &self { + EvalAltResult::ErrorVariableNotFound(variable, pos) => { + pos.report_raw(&path, variable.len(), "Undefined variable") + } + EvalAltResult::ErrorFunctionNotFound(msg, pos) => { + let (fn_name, args) = msg.split_once(' ').unwrap_or((msg, "")); + pos.report_raw( + &path, + fn_name.len(), + format!("{fn_name} {args}"), + ) + } + EvalAltResult::ErrorSystem(msg, err) + if err.is::() => + { + let e = err.downcast_ref::().unwrap(); + let msg = if msg.is_empty() { + format!("{err}") + } else { + format!("{msg}: {err}") + }; + e.position.report_raw(&path, 0, msg) } + EvalAltResult::ErrorInModule(path, err, _) => err.report(path), + // for errors that we don't have custom processing, just point + // to the beginning of the error, and use the error Display as message + e => e.position().report_raw(&path, 0, format!("{e}")), + } + } +} + +impl RhaiReport for RhaiResult { + fn report_raw, S: AsRef>( + &self, + path: P, + len: usize, + msg: S, + ) { + if let Err(e) = self { + (**e).report_raw(path, len, msg); } } } diff --git a/fud2/fud-core/src/script/resolver.rs b/fud2/fud-core/src/script/resolver.rs new file mode 100644 index 0000000000..023dd73f1f --- /dev/null +++ b/fud2/fud-core/src/script/resolver.rs @@ -0,0 +1,160 @@ +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + error::Error, + fmt::Display, + path::PathBuf, + rc::Rc, +}; + +use itertools::Itertools; +use rhai::EvalAltResult; + +use super::{exec_scripts::RhaiResult, report::RhaiReport}; + +#[derive(Default)] +pub(super) struct Resolver { + files: Vec<(PathBuf, rhai::AST)>, + modules: RefCell>>, + failed: RefCell>, +} + +#[derive(Debug)] +enum ResolverError { + Failed(String), + Unknown(String), +} + +impl Display for ResolverError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ResolverError::Failed(m) => write!(f, "Loading `{m}` failed."), + ResolverError::Unknown(m) => write!(f, "`{m}` was not found."), + } + } +} + +impl Error for ResolverError {} + +impl From for Box { + fn from(value: ResolverError) -> Box { + Box::new(EvalAltResult::ErrorSystem(String::new(), Box::new(value))) + } +} + +impl Resolver { + pub fn register_path( + &mut self, + path: PathBuf, + ast: rhai::AST, + ) -> rhai::AST { + // let stem = path.file_stem().unwrap().into(); + let functions = ast.clone_functions_only(); + self.files.push((path, ast)); + + functions + } + + pub fn register_data( + &mut self, + name: &'static str, + ast: rhai::AST, + ) -> rhai::AST { + // TODO: normalize the name somehow + self.register_path(PathBuf::from(name), ast) + } + + pub fn paths(&self) -> Vec { + self.files + .iter() + .map(|(path, _)| path.clone()) + .sorted() + .collect() + } + + fn resolve_name(&self, name: &str) -> Option<&rhai::AST> { + self.files + .iter() + .find(|(path, _)| { + // if name is directly equal to registered path + // or name is equal to the path stem, then return + // that ast + Some(name) == path.to_str() + || Some(name) == path.file_stem().and_then(|os| os.to_str()) + }) + .map(|(_, ast)| ast) + } + + fn normalize_name(&self, name: &str) -> String { + PathBuf::from(name) + .file_stem() + .and_then(|x| x.to_str()) + .map(ToString::to_string) + .unwrap() + } + + fn get(&self, path: &str) -> Option> { + let name = self.normalize_name(path); + self.modules.borrow().get(&name).map(Rc::clone) + } + + fn insert(&self, path: &str, module: rhai::Module) -> Rc { + let rc_mod = Rc::new(module); + let name = self.normalize_name(path); + self.modules.borrow_mut().insert(name, Rc::clone(&rc_mod)); + rc_mod + } + + fn did_fail(&self, path: &str) -> RhaiResult<()> { + let name = self.normalize_name(path); + if self.failed.borrow().contains(&name) { + Err(Box::new(EvalAltResult::ErrorSystem( + "Failed module loading".to_string(), + Box::new(ResolverError::Failed(format!("{path:?}"))), + ))) + } else { + Ok(()) + } + } + + fn add_failed(&self, path: &str) { + let name = self.normalize_name(path); + self.failed.borrow_mut().insert(name); + } +} + +impl rhai::ModuleResolver for Resolver { + fn resolve( + &self, + engine: &rhai::Engine, + _source: Option<&str>, + name: &str, + _pos: rhai::Position, + ) -> RhaiResult> { + let path_buf = PathBuf::from(name); + + // if this path has already failed, don't try loading it again + self.did_fail(name)?; + + // return the module of a path if we have already loaded it + if let Some(module) = self.get(name) { + Ok(module) + } else { + // otherwise, make a new module, cache it, and return it + self.resolve_name(name) + .ok_or(ResolverError::Unknown(name.to_string()).into()) + .and_then(|ast| { + rhai::Module::eval_ast_as_new( + rhai::Scope::new(), + ast, + engine, + ) + }) + .map(|m| self.insert(name, m)) + .inspect_err(|e| { + e.report(&path_buf); + self.add_failed(name) + }) + } + } +} diff --git a/fud2/plugins/calyx.rhai b/fud2/plugins/calyx.rhai deleted file mode 100644 index 1ac3147581..0000000000 --- a/fud2/plugins/calyx.rhai +++ /dev/null @@ -1,18 +0,0 @@ -fn calyx_setup(e) { - e.config_var("calyx-base", "calyx.base"); - e.config_var_or("calyx-exe", "calyx.exe", "$calyx-base/target/debug/calyx"); - e.config_var_or("args", "calyx.args", ""); - e.rule("calyx", "$calyx-exe -l $calyx-base -b $backend $args $in > $out"); - e.rule("calyx-pass", "$calyx-exe -l $calyx-base -p $pass $args $in > $out"); -} - -op( - "calyx2-to-verilog", - [calyx_setup], - get_state("calyx"), - get_state("verilog"), - |e, input, output| { - e.build_cmd([output], "calyx", [input], []) ; - e.arg("backend", "verilog"); - } -) diff --git a/fud2/plugins/mrxl.rhai b/fud2/plugins/mrxl.rhai deleted file mode 100644 index 54487f4884..0000000000 --- a/fud2/plugins/mrxl.rhai +++ /dev/null @@ -1,8 +0,0 @@ -let mrxl = state("mrxl", ["mrxl"]); - -fn mrxl_setup(e) { - e.var_("mrxl-exe", "mrxl"); - e.rule("mrxl-to-calyx", "$mrxl-exe $in > $out"); -} - -rule([mrxl_setup], mrxl, get_state("calyx"), "mrxl-to-calyx"); diff --git a/fud2/plugins/sim.rhai b/fud2/plugins/sim.rhai deleted file mode 100644 index effcc7dc11..0000000000 --- a/fud2/plugins/sim.rhai +++ /dev/null @@ -1,50 +0,0 @@ -// This file provides fud2 operations for simulating Calyx - -fn sim_setup(e) { - e.config_var_or("python", "python", "python3"); - e.rsrc("json-dat.py"); - e.rule("hex-data", "$python json-dat.py --from-json $in $out"); - e.rule("json-data", "$python json-dat.py --to-json $out $in"); - - // The input data file. `sim.data` is a required option. - let data_name = e.config_val("sim.data"); - let data_path = e.external_path(data_name); - e.var_("sim_data", data_path); - - e.var_("datadir", "sim_data"); - e.build_cmd(["$datadir"], "hex-data", ["$sim_data"], ["json-dat.py"]); - - // Rule for simulation execution - e.rule("sim-run", "./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out"); - - e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000"); -} - -let simulator = get_state("sim"); -let dat = get_state("dat"); -let vcd = get_state("vcd"); - -op( - "simulate2", - [sim_setup], - simulator, - dat, - |e, input, output| { - e.build_cmd(["sim.log"], "sim-run", [input, "$datadir"], []); - e.arg("bin", input); - e.arg("args", "+NOTRACE=1"); - e.build_cmd([output], "json-data", ["$datadir", "sim.log"], ["json-dat.py"]); - } -); - -op( - "trace2", - [sim_setup], - simulator, - vcd, - |e, input, output| { - e.build_cmd(["sim.log", output], "sim-run", [input, "$datadir"], []); - e.arg("bin", input); - e.arg("args", `+NOTRACE=0 +OUT=${output}`); - } -); diff --git a/fud2/plugins/axi.rhai b/fud2/scripts/axi.rhai similarity index 79% rename from fud2/plugins/axi.rhai rename to fud2/scripts/axi.rhai index d37f6e6601..3f225efd6f 100644 --- a/fud2/plugins/axi.rhai +++ b/fud2/scripts/axi.rhai @@ -1,4 +1,17 @@ -let calyx = get_state("calyx"); +import "calyx" as c; + +export const yxi = state("yxi", ["yxi"]); + +op( + "calyx-to-yxi", + [c::calyx_setup], + c::calyx_state, + yxi, + |e, input, output| { + e.build_cmd([output], "calyx", [input], []); + e.arg("backend", "yxi"); + }, +); fn wrapper_setup(e) { e.config_var_or("axi-generator", "axi.generator", "$calyx-base/yxi/axi-calyx/axi-generator.py"); @@ -41,9 +54,9 @@ fn axi_wrapped_op(e, input, output) { } op( - "axi-wrapped2", - [get_setup("Calyx compiler"), wrapper_setup], - calyx, - calyx, + "axi-wrapped", + [c::calyx_setup, wrapper_setup], + c::calyx_state, + c::calyx_state, axi_wrapped_op ); diff --git a/fud2/scripts/calyx.rhai b/fud2/scripts/calyx.rhai new file mode 100644 index 0000000000..13829e2e24 --- /dev/null +++ b/fud2/scripts/calyx.rhai @@ -0,0 +1,37 @@ +export const verilog_state = state("verilog", ["sv", "v"]); +export const verilog_noverify = state("verilog-noverify", ["sv"]); +export const calyx_state = state("calyx", ["futil"]); + +export let calyx_setup = calyx_setup; +fn calyx_setup(e) { + e.config_var("calyx-base", "calyx.base"); + e.config_var_or("calyx-exe", "calyx.exe", "$calyx-base/target/debug/calyx"); + e.config_var_or("args", "calyx.args", ""); + e.rule("calyx", "$calyx-exe -l $calyx-base -b $backend $args $in > $out"); + e.rule("calyx-pass", "$calyx-exe -l $calyx-base -p $pass $args $in > $out"); +} + +op( + "calyx-to-verilog", + [calyx_setup], + calyx_state, + verilog_state, + |e, input, output| { + e.build_cmd([output], "calyx", [input], []) ; + e.arg("backend", "verilog"); + } +); + + +op( + "calyx-noverify", + [calyx_setup], + calyx_state, + verilog_noverify, + |e, input, output| { + // Icarus requires a special --disable-verify version of Calyx code. + e.build_cmd([output], "calyx", [input], []); + e.arg("backend", "verilog"); + e.arg("args", "--disable-verify"); + }, +); diff --git a/fud2/scripts/cider.rhai b/fud2/scripts/cider.rhai new file mode 100644 index 0000000000..ee7ed505ee --- /dev/null +++ b/fud2/scripts/cider.rhai @@ -0,0 +1,108 @@ +import "rtl_sim" as sim; +import "testbench" as tb; +import "calyx" as c; + +let dbg = state("debug", []); + +fn cider_setup(e) { + e.config_var_or( + "cider-exe", + "cider.exe", + "$calyx-base/target/debug/cider", + ); + e.config_var_or( + "cider-converter", + "cider-converter.exe", + "$calyx-base/target/debug/cider-data-converter", + ); + e.rule( + "cider", + "$cider-exe -l $calyx-base --raw --data data.json $in > $out", + ); + e.rule( + "cider-debug", + "$cider-exe -l $calyx-base --data data.json $in debug || true", + ); + e.arg("pool", "console"); + + // TODO Can we reduce the duplication around and `$python`? + e.rsrc("interp-dat.py"); + e.config_var_or("python", "python", "python3"); + e.rule("dat-to-interp", "$python interp-dat.py --to-interp $in"); + e.rule( + "interp-to-dat", + "$python interp-dat.py --from-interp $in $sim_data > $out", + ); + e.build_cmd( + ["data.json"], + "dat-to-interp", + ["$sim_data"], + ["interp-dat.py"], + ); + + e.rule( + "cider2", + "$cider-exe -l $calyx-base --data data.dump $in flat > $out", + ); + + e.rule("dump-to-interp", "$cider-converter --to cider $in > $out"); + e.rule("interp-to-dump", "$cider-converter --to json $in > $out"); + e.build_cmd( + ["data.dump"], + "dump-to-interp", + ["$sim_data"], + ["$cider-converter"], + ); +} + +op( + "interp", + [ + sim::sim_setup, + tb::standalone_setup, + c::calyx_setup, + cider_setup, + ], + c::calyx_state, + sim::dat, + |e, input, output| { + let out_file = "interp_out.json"; + e.build_cmd([out_file], "cider", [input], ["data.json"]); + e.build_cmd( + [output], + "interp-to-dat", + [out_file], + ["$sim_data", "interp-dat.py"], + ); + }, +); +op( + "interp-flat", + [sim::sim_setup, c::calyx_setup, cider_setup], + c::calyx_state, + sim::dat, + |e, input, output| { + let out_file = "interp_out.dump"; + e.build_cmd([out_file], "cider2", [input], ["data.dump"]); + e.build_cmd( + [output], + "interp-to-dump", + [out_file], + ["$sim_data", "$cider-converter"], + ); + }, +); +op( + "debug", + [ + sim::sim_setup, + tb::standalone_setup, + c::calyx_setup, + cider_setup, + ], + c::calyx_state, + dbg, + |e, input, output| { + e.build_cmd([output], "cider-debug", [input], ["data.json"]); + }, +); diff --git a/fud2/plugins/dahlia-to-calyx.rhai b/fud2/scripts/dahlia-to-calyx.rhai similarity index 52% rename from fud2/plugins/dahlia-to-calyx.rhai rename to fud2/scripts/dahlia-to-calyx.rhai index 0db0247ac7..f3cefd073a 100644 --- a/fud2/plugins/dahlia-to-calyx.rhai +++ b/fud2/scripts/dahlia-to-calyx.rhai @@ -1,8 +1,10 @@ -let dahlia = state("dahlia", ["fuse"]); +import "calyx" as c; + +export const dahlia = state("dahlia", ["fuse"]); fn dahlia_setup(e) { e.config_var("dahlia-exe", "dahlia"); e.rule("dahlia-to-calyx", "$dahlia-exe -b calyx --lower -l error $in -o $out"); } -rule([dahlia_setup], dahlia, get_state("calyx"), "dahlia-to-calyx"); +rule([dahlia_setup], dahlia, c::calyx_state, "dahlia-to-calyx"); diff --git a/fud2/scripts/firrtl.rhai b/fud2/scripts/firrtl.rhai new file mode 100644 index 0000000000..401d92cf9a --- /dev/null +++ b/fud2/scripts/firrtl.rhai @@ -0,0 +1,175 @@ +import "calyx" as c; +import "testbench" as tb; + +// setup for FIRRTL-implemented primitives +fn firrtl_primitives_setup(e) { + // Produce FIRRTL with FIRRTL-defined primitives. + e.var_( + "gen-firrtl-primitives-script", + "$calyx-base/tools/firrtl/generate-firrtl-with-primitives.py", + ); + e.rule( + "generate-firrtl-with-primitives", + "python3 $gen-firrtl-primitives-script $in > $out", + ); +} + +fn calyx_to_firrtl_helper(e, input, output, firrtl_primitives) { + // Temporary Calyx where all refs are converted into external (FIXME: fix YXI to emit for ref as well?) + let only_externals_calyx = "external.futil"; + // Temporary Calyx where all externals are converted into refs (for FIRRTL backend) + let only_refs_calyx = "ref.futil"; + // JSON with memory information created by YXI + let memories_json = "memory-info.json"; + // Custom testbench (same name as standalone testbench) + let testbench = "tb.sv"; + // Holds contents of file we want to output. Gets cat-ed via final dummy command + let tmp_out = "tmp-out.fir"; + // Convert ref into external to get YXI working (FIXME: fix YXI to emit for ref as well?) + e.build_cmd([only_externals_calyx], "ref-to-external", [input], []); + // Convert external to ref to get FIRRTL backend working + e.build_cmd([only_refs_calyx], "external-to-ref", [input], []); + + // Get YXI to generate JSON for testbench generation + e.build_cmd([memories_json], "calyx", [only_externals_calyx], []); + e.arg("backend", "yxi"); + // generate custom testbench + e.build_cmd( + [testbench], + "generate-refmem-testbench", + [memories_json], + [], + ); + + if firrtl_primitives { + let core_program_firrtl = "core.fir"; + + // Obtain FIRRTL of core program + e.build_cmd( + [core_program_firrtl], + "calyx", + [only_refs_calyx], + [], + ); + e.arg("backend", "firrtl"); + e.arg("args", "--synthesis"); + + // Obtain primitive uses JSON for metaprogramming + let primitive_uses_json = "primitive-uses.json"; + e.build_cmd( + [primitive_uses_json], + "calyx", + [only_refs_calyx], + [], + ); + e.arg("backend", "primitive-uses"); + e.arg("args", "--synthesis"); + + // run metaprogramming script to get FIRRTL with primitives + e.build_cmd( + [tmp_out], + "generate-firrtl-with-primitives", + [core_program_firrtl, primitive_uses_json], + [], + ); + } else { + // emit extmodule declarations to use Verilog primitive implementations + e.build_cmd([tmp_out], "calyx", [only_refs_calyx], []); + e.arg("backend", "firrtl"); + e.arg("args", "--emit-primitive-extmodules"); + } + + // dummy command to make sure custom testbench is created but not emitted as final answer + e.build_cmd([output], "dummy", [tmp_out, testbench], []); +} + +// Calyx to FIRRTL. +let firrtl = state("firrtl", ["fir"]); // using Verilog primitives +let firrtl_with_primitives = state("firrtl-with-primitives", ["fir"]); // using FIRRTL primitives + +// let calyx = get_state("calyx"); +// let calyx_setup = get_setup("Calyx compiler"); +// let custom_testbench_setup = get_setup("Custom Testbench Setup"); + +op( + // use Verilog + "calyx-to-firrtl", + [c::calyx_setup, tb::custom_setup], + c::calyx_state, + firrtl, + |e, input, output| calyx_to_firrtl_helper(e, input, output, false), +); + +op( + "firrtl-with-primitives", + [c::calyx_setup, firrtl_primitives_setup, tb::custom_setup], + c::calyx_state, + firrtl_with_primitives, + |e, input, output| calyx_to_firrtl_helper(e, input, output, true), +); + +// The FIRRTL compiler. +fn firrtl_setup(e) { + e.config_var("firrtl-exe", "firrtl.exe"); + e.rule("firrtl", "$firrtl-exe -i $in -o $out -X sverilog"); + + e.rsrc("primitives-for-firrtl.sv"); + // adding Verilog implementations of primitives to FIRRTL --> Verilog compiled code + e.rule( + "add-verilog-primitives", + "cat primitives-for-firrtl.sv $in > $out", + ); +} + +fn firrtl_compile_helper(e, input, output, firrtl_primitives) { + if firrtl_primitives { + e.build_cmd([output], "firrtl", [input], []); + } else { + let tmp_verilog = "partial.sv"; + e.build_cmd([tmp_verilog], "firrtl", [input], []); + e.build_cmd( + [output], + "add-verilog-primitives", + [tmp_verilog], + ["primitives-for-firrtl.sv"], + ); + } +} + +// FIRRTL --> Verilog compilation using Verilog primitive implementations for Verilator +op( + "firrtl", + [firrtl_setup], + firrtl, + tb::verilog_refmem, + |e, input, output| firrtl_compile_helper(e, input, output, false), +); + +// FIRRTL --> Verilog compilation using Verilog primitive implementations for Icarus +// This is a bit of a hack, but the Icarus-friendly "noverify" state is identical for this path +// (since FIRRTL compilation doesn't come with verification). +op( + "firrtl-noverify", + [firrtl_setup], + firrtl, + tb::verilog_refmem_noverify, + |e, input, output| firrtl_compile_helper(e, input, output, false), +); + +// FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Verilator +op( + "firrtl-with-primitives", + [firrtl_setup], + firrtl_with_primitives, + tb::verilog_refmem, + |e, input, output| firrtl_compile_helper(e, input, output, true), +); + +// FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Icarus +op( + "firrtl-with-primitives-noverify", + [firrtl_setup], + firrtl_with_primitives, + tb::verilog_refmem_noverify, + |e, input, output| firrtl_compile_helper(e, input, output, true), +); diff --git a/fud2/scripts/icarus.rhai b/fud2/scripts/icarus.rhai new file mode 100644 index 0000000000..4ca954a96e --- /dev/null +++ b/fud2/scripts/icarus.rhai @@ -0,0 +1,46 @@ +import "calyx" as c; +import "rtl_sim" as sim; +import "testbench" as tb; + +fn icarus_setup(e) { + e.var_("iverilog", "iverilog"); + e.rule( + "icarus-compile-standalone-tb", + "$iverilog -g2012 -o $out tb.sv $in", + ); + e.rule( + "icarus-compile-custom-tb", + "$iverilog -g2012 -o $out tb.sv memories.sv $in", + ); +} + + +op( + "icarus", + [sim::sim_setup, tb::standalone_setup, icarus_setup], + c::verilog_noverify, + sim::simulator, + |e, input, output| { + e.build_cmd( + [output], + "icarus-compile-standalone-tb", + [input], + ["tb.sv"], + ); + }, +); + +op( + "icarus-refmem", + [sim::sim_setup, icarus_setup], + tb::verilog_refmem_noverify, + sim::simulator, + |e, input, output| { + e.build_cmd( + [output], + "icarus-compile-custom-tb", + [input], + ["tb.sv", "memories.sv"], + ); + }, +); diff --git a/fud2/scripts/mrxl.rhai b/fud2/scripts/mrxl.rhai new file mode 100644 index 0000000000..efc0aaff9d --- /dev/null +++ b/fud2/scripts/mrxl.rhai @@ -0,0 +1,10 @@ +import "calyx" as c; + +export const mrxl_state = state("mrxl", ["mrxl"]); + +fn mrxl_setup(e) { + e.var_("mrxl-exe", "mrxl"); + e.rule("mrxl-to-calyx", "$mrxl-exe $in > $out"); +} + +rule([mrxl_setup], mrxl_state, c::calyx_state, "mrxl-to-calyx"); diff --git a/fud2/scripts/primitive_uses.rhai b/fud2/scripts/primitive_uses.rhai new file mode 100644 index 0000000000..9c96ae1e14 --- /dev/null +++ b/fud2/scripts/primitive_uses.rhai @@ -0,0 +1,14 @@ +import "calyx" as c; + +let primitive_uses_json = state("primitive-uses-json", ["json"]); + +op( + "primitive-uses", + [c::calyx_setup], + c::calyx_state, + primitive_uses_json, + |e, input, output| { + e.build_cmd([output], "calyx", [input], []); + e.arg("backend", "primitive-uses"); + }, +); diff --git a/fud2/scripts/rtl_sim.rhai b/fud2/scripts/rtl_sim.rhai new file mode 100644 index 0000000000..0882292d38 --- /dev/null +++ b/fud2/scripts/rtl_sim.rhai @@ -0,0 +1,67 @@ +// This file provides fud2 operations for simulating Calyx + +export const dat = state("dat", ["json"]); +export const vcd = state("vcd", ["vcd"]); +export const simulator = state("sim", ["exe"]); + +export const sim_setup = sim_setup; + +fn sim_setup(e) { + // Data conversion to and from JSON. + e.config_var_or("python", "python", "python3"); + e.rsrc("json-dat.py"); + e.rule("hex-data", "$python json-dat.py --from-json $in $out"); + e.rule("json-data", "$python json-dat.py --to-json $out $in"); + + // The input data file. `sim.data` is required. + let data_name = e.config_val("sim.data"); + let data_path = e.external_path(data_name); + e.var_("sim_data", data_path); + + // Produce the data directory. + e.var_("datadir", "sim_data"); + e.build_cmd( + ["$datadir"], + "hex-data", + ["$sim_data"], + ["json-dat.py"], + ); + + // Rule for simulation execution. + e.rule( + "sim-run", + "./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out", + ); + + // More shared configuration. + e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000"); +} + +op( + "simulate", + [sim_setup], + simulator, + dat, + |e, input, output| { + e.build_cmd(["sim.log"], "sim-run", [input, "$datadir"], []); + e.arg("bin", input); + e.arg("args", "+NOTRACE=1"); + e.build_cmd( + [output], + "json-data", + ["$datadir", "sim.log"], + ["json-dat.py"], + ); + }, +); + +op("trace", [sim_setup], simulator, vcd, |e, input, output| { + e.build_cmd( + ["sim.log", output], + "sim-run", + [input, "$datadir"], + [], + ); + e.arg("bin", input); + e.arg("args", `+NOTRACE=0 +OUT=${output}`); +}); diff --git a/fud2/scripts/testbench.rhai b/fud2/scripts/testbench.rhai new file mode 100644 index 0000000000..c489d65e9f --- /dev/null +++ b/fud2/scripts/testbench.rhai @@ -0,0 +1,34 @@ +// Defines testbenches +export const verilog_refmem = state("verilog-refmem", ["sv"]); +export const verilog_refmem_noverify = state("verilog-refmem-noverify", ["sv"]); + +// [Default] Setup for using rsrc/tb.sv as testbench (and managing memories within the design) +export const standalone_setup = standalone_testbench_setup; +fn standalone_testbench_setup(e) { + // Standalone Verilog testbench. + e.rsrc("tb.sv"); +} + +// [Needs YXI backend compiled] Setup for creating a custom testbench (needed for FIRRTL) +export const custom_setup = custom_testbench_setup; +fn custom_testbench_setup(e) { + // Convert all ref cells to @external (FIXME: YXI should work for both?) + e.rule("ref-to-external", "sed 's/ref /@external /g' $in > $out"); + + // Convert all @external cells to ref (FIXME: we want to deprecate @external) + e.rule("external-to-ref", "sed 's/@external([0-9]*)/ref/g' $in | sed 's/@external/ref/g' > $out"); + + e.var_( + "gen-testbench-script", + "$calyx-base/tools/firrtl/generate-testbench.py", + ); + e.rsrc("memories.sv"); // Memory primitives. + + e.rule( + "generate-refmem-testbench", + "python3 $gen-testbench-script $in > $out", + ); + + // dummy rule to force ninja to build the testbench + e.rule("dummy", "sh -c 'cat $$0' $in > $out"); +}; diff --git a/fud2/scripts/verilator.rhai b/fud2/scripts/verilator.rhai new file mode 100644 index 0000000000..574e4cb958 --- /dev/null +++ b/fud2/scripts/verilator.rhai @@ -0,0 +1,55 @@ +import "rtl_sim" as sim; +import "testbench" as tb; +import "calyx" as c; + +fn verilator_setup(e) { + e.config_var_or("verilator", "verilator.exe", "verilator"); + e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000"); + e.rule( + "verilator-compile-standalone-tb", + "$verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir", + ); + e.rule( + "verilator-compile-custom-tb", + "$verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir", + ); + e.rule("cp", "cp $in $out"); +} + +fn verilator_build(e, input, output, standalone_tb) { + let out_dir = "verilator-out"; + let sim_bin = `${out_dir}/VTOP`; + if standalone_testbench { + e.build_cmd( + [sim_bin], + "verilator-compile-standalone-tb", + [input], + ["tb.sv"], + ); + } else { + e.build_cmd( + [sim_bin], + "verilator-compile-custom-tb", + [input], + ["tb.sv", "memories.sv"], + ); + } + e.arg("out-dir", out_dir); + e.build("cp", sim_bin, output); +} + +op( + "verilator", + [sim::sim_setup, tb::standalone_setup, verilator_setup], + c::verilog_state, + sim::simulator, + |e, input, output| { verilator_build(e, input, output, true) } +); + +op( + "verilator-refmem", + [sim::sim_setup, tb::standalone_setup, verilator_setup], + tb::verilog_refmem, + sim::simulator, + |e, input, output| { verilator_build(e, input, output, false) } +); diff --git a/fud2/scripts/xilinx.rhai b/fud2/scripts/xilinx.rhai new file mode 100644 index 0000000000..79515e9f99 --- /dev/null +++ b/fud2/scripts/xilinx.rhai @@ -0,0 +1,140 @@ +import "calyx" as c; +import "rtl_sim" as sim; +import "testbench" as tb; + +let xo = state("xo", ["xo"]); +let xclbin = state("xclbin", ["xclbin"]); + +fn xilinx_setup(e) { + // Locations for Vivado and Vitis installations. + e.config_var("vivado-dir", "xilinx.vivado"); + e.config_var("vitis-dir", "xilinx.vitis"); + + // Package a Verilog program as an `.xo` file. + e.rsrc("gen_xo.tcl"); + e.rsrc("get-ports.py"); + e.config_var_or("python", "python", "python3"); + e.rule( + "gen-xo", + "$vivado-dir/bin/vivado -mode batch -source gen_xo.tcl -tclargs $out `$python get-ports.py kernel.xml`" + ); + e.arg("pool", "console"); // Lets Ninja stream the tool output "live." + + // Compile an `.xo` file to an `.xclbin` file, which is where the actual EDA work occurs. + e.config_var_or("xilinx-mode", "xilinx.mode", "hw_emu"); + e.config_var_or("platform", "xilinx.device", "xilinx_u50_gen3x16_xdma_201920_3"); + e.rule( + "compile-xclbin", + "$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" + ); + e.arg("pool", "console"); +}; + +op( + "xo", + [c::calyx_setup, xilinx_setup], + c::calyx_state, + xo, + |e, input, output| { + // Emit the Verilog itself in "synthesis mode." + e.build_cmd(["main.sv"], "calyx", [input], []); + e.arg("backend", "verilog"); + e.arg("args", "--synthesis -p external"); + + // Extra ingredients for the `.xo` package. + e.build_cmd(["toplevel.v"], "calyx", [input], []); + e.arg("backend", "xilinx"); + e.build_cmd(["kernel.xml"], "calyx", [input], []); + e.arg("backend", "xilinx-xml"); + + // Package the `.xo`. + e.build_cmd( + [output], + "gen-xo", + [], + [ + "main.sv", + "toplevel.v", + "kernel.xml", + "gen_xo.tcl", + "get-ports.py", + ], + ); + }, +); + +op("xclbin", [xilinx_setup], xo, xclbin, |e, input, output| { + e.build_cmd([output], "compile-xclbin", [input], []); +}); + +// Xilinx execution. +// TODO Only does `hw_emu` for now... +fn xrt_setup(e) { + // Generate `emconfig.json`. + e.rule("emconfig", "$vitis-dir/bin/emconfigutil --platform $platform"); + e.build_cmd(["emconfig.json"], "emconfig", [], []); + + // Execute via the `xclrun` tool. + e.config_var("xrt-dir", "xilinx.xrt"); + e.rule( + "xclrun", + "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'" + ); + e.arg("pool", "console"); + + // "Pre-sim" and "post-sim" scripts for simulation. + e.rule("echo", "echo $contents > $out"); + e.build_cmd(["pre_sim.tcl"], "echo", [""], [""]); + e.arg("contents", "open_vcd\\\\nlog_vcd *\\\\n"); + e.build_cmd(["post_sim.tcl"], "echo", [""], [""]); + e.arg("contents", "close_vcd\\\\n"); +}; + +op( + "xrt", + [ + xilinx_setup, + sim::sim_setup, + tb::standalone_setup, + xrt_setup, + ], + xclbin, + sim::dat, + |e, input, output| { + e.rsrc("xrt.ini"); + e.build_cmd( + [output], + "xclrun", + [input, "$sim_data"], + ["emconfig.json", "xrt.ini"], + ); + e.arg("xrt_ini", "xrt.ini"); + }, +); + +op( + "xrt-trace", + [ + xilinx_setup, + sim::sim_setup, + tb::standalone_setup, + xrt_setup, + ], + xclbin, + sim::dat, + |e, input, output| { + e.rsrc("xrt_trace.ini"); + e.build_cmd( + [output], // TODO not the VCD, yet... + "xclrun", + [input, "$sim_data"], + [ + "emconfig.json", + "pre_sim.tcl", + "post_sim.tcl", + "xrt_trace.ini", + ], + ); + e.arg("xrt_ini", "xrt_trace.ini"); + }, +); diff --git a/fud2/src/main.rs b/fud2/src/main.rs index 27bf8e8132..4f05686a71 100644 --- a/fud2/src/main.rs +++ b/fud2/src/main.rs @@ -1,24 +1,46 @@ -use fud2::build_driver; use fud_core::{cli, DriverBuilder}; fn main() -> anyhow::Result<()> { let mut bld = DriverBuilder::new("fud2"); - build_driver(&mut bld); + + #[cfg(not(feature = "migrate_to_scripts"))] + fud2::build_driver(&mut bld); // In debug mode, get resources from the source directory. #[cfg(debug_assertions)] - bld.rsrc_dir(manifest_dir_macros::directory_path!("rsrc")); + { + bld.rsrc_dir(manifest_dir_macros::directory_path!("rsrc")); + + #[cfg(feature = "migrate_to_scripts")] + bld.scripts_dir(manifest_dir_macros::directory_path!("scripts")); + } // In release mode, embed resources into the binary. #[cfg(not(debug_assertions))] - bld.rsrc_files({ - const DIR: include_dir::Dir = - include_dir::include_dir!("$CARGO_MANIFEST_DIR/rsrc"); - DIR.files() - .map(|file| (file.path().to_str().unwrap(), file.contents())) - .collect() - }); - - let driver = bld.load_plugins().build(); + { + bld.rsrc_files({ + const DIR: include_dir::Dir = + include_dir::include_dir!("$CARGO_MANIFEST_DIR/rsrc"); + DIR.files() + .map(|file| (file.path().to_str().unwrap(), file.contents())) + .collect() + }); + + #[cfg(feature = "migrate_to_scripts")] + bld.script_files({ + const DIR: include_dir::Dir = + include_dir::include_dir!("$CARGO_MANIFEST_DIR/scripts"); + DIR.files() + .map(|file| (file.path().to_str().unwrap(), file.contents())) + .collect() + }); + } + + #[cfg(feature = "migrate_to_scripts")] + { + bld = bld.load_plugins(); + } + + let driver = bld.build(); cli::cli(&driver) }