diff --git a/.flake8 b/.flake8 index 1d36346c0d..146ae6054d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] -max-line-length = 88 \ No newline at end of file +max-line-length = 88 +exclude = tools/tb/examples/cocotb/doc_examples_quickstart/test_my_design.py diff --git a/.gitignore b/.gitignore index 5c6840dac6..a3d6035682 100644 --- a/.gitignore +++ b/.gitignore @@ -66,12 +66,16 @@ tools/profiler/inst-check-tmp temp/ +tools/tb/**/*.so +tools/tb/**/*.dylib + +# for running a venv +.venv + # large queue .data and .expect files ignore frontends/queues/tests/**/*.data frontends/queues/tests/**/*.expect -# for running a venv -.venv # emacs *~ diff --git a/Cargo.lock b/Cargo.lock index 7eba776379..0ae02a5a2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +# This fil# This file is automatically @generated by Cargo. version = 3 [[package]] @@ -91,7 +92,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -127,7 +128,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -158,7 +159,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -267,7 +268,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -335,6 +336,30 @@ dependencies = [ "vast", ] +[[package]] +name = "calyx-ffi" +version = "0.7.1" +dependencies = [ + "calyx-ffi-macro", + "calyx-frontend", + "calyx-ir", + "interp", + "paste", + "rand 0.8.5", +] + +[[package]] +name = "calyx-ffi-macro" +version = "0.0.0" +dependencies = [ + "calyx-frontend", + "calyx-ir", + "calyx-utils", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "calyx-frontend" version = "0.7.1" @@ -461,6 +486,16 @@ version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +[[package]] +name = "cargo_toml" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "cast" version = "0.3.0" @@ -839,7 +874,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -861,7 +896,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -1096,6 +1131,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fst-writer" version = "0.2.1" @@ -1201,7 +1242,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -1425,15 +1466,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.36.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a508bf83e6f6f2aa438588ae7ceb558a81030c5762cbfe838180a861cf5dc110" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" dependencies = [ "console", "lazy_static", "linked-hash-map", "similar", - "yaml-rust", ] [[package]] @@ -1539,6 +1579,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] + [[package]] name = "libm" version = "0.2.8" @@ -1606,6 +1656,17 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "makemake" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe265024d547dbfdae86fdb24b3ea7b96aff8cc71947d08b61d0db2ce2829e05" +dependencies = [ + "cargo_toml", + "insta", + "paste", +] + [[package]] name = "manifest-dir-macros" version = "0.1.18" @@ -1615,7 +1676,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -1850,6 +1911,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.1" @@ -1918,7 +1985,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -1959,7 +2026,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2028,9 +2095,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -2073,9 +2140,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2284,7 +2351,7 @@ checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2368,11 +2435,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" -version = "1.0.197" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -2389,13 +2462,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2417,7 +2490,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2488,7 +2561,7 @@ dependencies = [ "darling 0.20.10", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2668,7 +2741,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2711,9 +2784,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -2732,6 +2805,23 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tb" +version = "0.0.0" +dependencies = [ + "argh", + "env_logger", + "figment", + "fs_extra", + "libloading", + "log", + "makemake", + "semver", + "serde", + "tempdir", + "toml", +] + [[package]] name = "tempdir" version = "0.3.7" @@ -2806,7 +2896,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -2909,7 +2999,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -3011,7 +3101,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -3039,7 +3129,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] [[package]] @@ -3490,15 +3580,6 @@ dependencies = [ "tap", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "1.0.1" @@ -3535,5 +3616,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index be30dce2d8..8b8fb08203 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,10 @@ members = [ "tools/component_cells", "tools/yxi", "tools/calyx-writer", + "tools/tb", + "tools/calyx-ffi-macro", + "tools/calyx-ffi", ] -exclude = ["site"] [workspace.package] authors = ["The Calyx Team"] @@ -58,6 +60,8 @@ calyx-ir = { path = "calyx-ir", version = "0.7.1" } calyx-frontend = { path = "calyx-frontend", version = "0.7.1" } calyx-opt = { path = "calyx-opt", version = "0.7.1" } calyx-backend = { path = "calyx-backend", version = "0.7.1" } +figment = { version = "0.10.12", features = ["toml"] } +semver = "1.0.23" [workspace.dependencies.petgraph] version = "0.6" diff --git a/fud2/README.md b/fud2/README.md index a0f0280308..53b47b3a8a 100644 --- a/fud2/README.md +++ b/fud2/README.md @@ -24,4 +24,13 @@ Add the path to the location of the Calyx compiler: base = "" ``` -[fud]: https://docs.calyxir.org/fud/index.html \ No newline at end of file +[fud]: https://docs.calyxir.org/fud/index.html + +### CLI + +- You can pass/override config variables by passing one or more options of the form `--set variable=value`. + +### Writing a new state + +Given a `bld: &mut DriverBuilder`, call `bld.state`, and define appropriate rules via `bld.rule`. +Each rule may require one or more setups; a setup can be obtained through `bld.setup` and may in addition define variables (including those mapped to config file keys) or rules. diff --git a/fud2/fud-core/Cargo.toml b/fud2/fud-core/Cargo.toml index 22ab4148a7..2911eac518 100644 --- a/fud2/fud-core/Cargo.toml +++ b/fud2/fud-core/Cargo.toml @@ -14,7 +14,7 @@ description = "Library for building declarative build tools" argh.workspace = true cranelift-entity = "0.103.0" serde.workspace = true -figment = { version = "0.10.12", features = ["toml"] } +figment.workspace = true pathdiff = { version = "0.2.1", features = ["camino"] } camino = "1.1.6" anyhow.workspace = true diff --git a/fud2/fud-core/src/exec/driver.rs b/fud2/fud-core/src/exec/driver.rs index 34a04bc311..bd8dc873a3 100644 --- a/fud2/fud-core/src/exec/driver.rs +++ b/fud2/fud-core/src/exec/driver.rs @@ -328,6 +328,11 @@ impl DriverBuilder { } } + /// Define a new fud2 state named `name`. Filesnames without an assigned + /// state will be assigned this state if their extension is contained in + /// `extensions`. If two states share an extension, the inferred assignment + /// is undefined. (Assuming this is the behavior; Jeremy said he doesn't + /// remember) pub fn state(&mut self, name: &str, extensions: &[&str]) -> StateRef { self.states.push(State { name: name.to_string(), diff --git a/fud2/fud-core/src/run.rs b/fud2/fud-core/src/run.rs index a77d8e8fe7..b3b9663ad1 100644 --- a/fud2/fud-core/src/run.rs +++ b/fud2/fud-core/src/run.rs @@ -571,13 +571,13 @@ impl Emitter { } } - /// Emit a Ninja variable declaration for `name` based on the configured value for `key`. + /// Emit a Ninja variable declaration that sets `name` to the value bound by `key` in the config file. pub fn config_var(&mut self, name: &str, key: &str) -> EmitResult { self.var(name, &self.config_val(key)?)?; Ok(()) } - /// Emit a Ninja variable declaration for `name` based on the configured value for `key`, or a + /// Emit a Ninja variable declaration that sets `name` to the value bound by `key` in the config file, or a /// default value if it's missing. pub fn config_var_or( &mut self, @@ -588,7 +588,7 @@ impl Emitter { self.var(name, &self.config_or(key, default)) } - /// Emit a Ninja variable declaration. + /// Emit a Ninja variable declaration `name = value`. pub fn var(&mut self, name: &str, value: &str) -> std::io::Result<()> { writeln!(self.out, "{} = {}", name, value) } diff --git a/interp/src/lib.rs b/interp/src/lib.rs index 34b3d98a9d..6c6afe53be 100644 --- a/interp/src/lib.rs +++ b/interp/src/lib.rs @@ -8,3 +8,6 @@ pub mod serialization; mod tests; pub mod flatten; + +// ethan: griffin ok'd this +pub use baa::{BitVecOps, BitVecValue}; diff --git a/tools/calyx-ffi-macro/Cargo.lock b/tools/calyx-ffi-macro/Cargo.lock new file mode 100644 index 0000000000..4d7d0901f2 --- /dev/null +++ b/tools/calyx-ffi-macro/Cargo.lock @@ -0,0 +1,554 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "calyx-ffi-macro" +version = "0.0.0" +dependencies = [ + "calyx-frontend", + "calyx-ir", + "calyx-utils", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "calyx-frontend" +version = "0.7.1" +dependencies = [ + "atty", + "calyx-utils", + "itertools", + "lazy_static", + "linked-hash-map", + "log", + "pest", + "pest_consume", + "pest_derive", + "smallvec", + "strum", + "strum_macros", +] + +[[package]] +name = "calyx-ir" +version = "0.7.1" +dependencies = [ + "calyx-frontend", + "calyx-utils", + "itertools", + "linked-hash-map", + "log", + "petgraph", + "smallvec", + "string-interner", +] + +[[package]] +name = "calyx-utils" +version = "0.7.1" +dependencies = [ + "atty", + "itertools", + "petgraph", + "serde_json", + "string-interner", + "symbol_table", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_consume" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79447402d15d18e7142e14c72f2e63fa3d155be1bc5b70b3ccbb610ac55f536b" +dependencies = [ + "pest", + "pest_consume_macros", + "pest_derive", +] + +[[package]] +name = "pest_consume_macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "string-interner" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e2531d8525b29b514d25e275a43581320d587b86db302b9a7e464bac579648" +dependencies = [ + "cfg-if", + "hashbrown 0.11.2", + "serde", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.87", +] + +[[package]] +name = "symbol_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "828f672b631c220bf6ea8a1d3b82c7d0fc998e5ba8373383d8604bc1e2a6245a" +dependencies = [ + "ahash", + "hashbrown 0.12.3", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/tools/calyx-ffi-macro/Cargo.toml b/tools/calyx-ffi-macro/Cargo.toml new file mode 100644 index 0000000000..e9d1eddc15 --- /dev/null +++ b/tools/calyx-ffi-macro/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "calyx-ffi-macro" +authors.workspace = true +license-file.workspace = true +keywords.workspace = true +repository.workspace = true +readme.workspace = true +description.workspace = true +categories.workspace = true +homepage.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.86" +quote = "1.0.36" +syn = { version = "2.0.69", features = ["full", "visit"] } +calyx-utils.workspace = true +calyx-frontend.workspace = true +calyx-ir.workspace = true +# bigint = "4.4.3" diff --git a/tools/calyx-ffi-macro/src/calyx.rs b/tools/calyx-ffi-macro/src/calyx.rs new file mode 100644 index 0000000000..4039321976 --- /dev/null +++ b/tools/calyx-ffi-macro/src/calyx.rs @@ -0,0 +1,51 @@ +use std::{env, path::PathBuf, rc::Rc}; + +use proc_macro::TokenStream; + +use crate::{parse::CalyxFFIMacroArgs, util}; + +pub struct CalyxComponent { + ctx: Rc, + index: usize, +} + +impl CalyxComponent { + pub fn get(&self) -> &calyx_ir::Component { + &self.ctx.components[self.index] + } +} + +pub fn parse_calyx_file( + args: &CalyxFFIMacroArgs, + file: PathBuf, +) -> Result { + // there has to be a better way to find lib + let home_dir = env::var("HOME").expect("user home not set"); + let mut lib_path = PathBuf::from(home_dir); + lib_path.push(".calyx"); + let ws = + calyx_frontend::Workspace::construct(&Some(file.clone()), &lib_path) + .map_err(|err| { + util::compile_error(&args.src_attr_span, err.message()) + })?; + let ctx = calyx_ir::from_ast::ast_to_ir(ws).map_err(|err| { + util::compile_error(&args.src_attr_span, err.message()) + })?; + + let comp_index = ctx + .components + .iter() + .position(|comp| comp.name == args.comp) + .ok_or(util::compile_error( + &args.comp_attr_span, + format!( + "component '{}' does not exist in '{}'", + args.comp, + args.src.to_string_lossy() + ), + ))?; + Ok(CalyxComponent { + ctx: Rc::new(ctx), + index: comp_index, + }) +} diff --git a/tools/calyx-ffi-macro/src/lib.rs b/tools/calyx-ffi-macro/src/lib.rs new file mode 100644 index 0000000000..a4c7c76948 --- /dev/null +++ b/tools/calyx-ffi-macro/src/lib.rs @@ -0,0 +1,404 @@ +use std::{env, path::PathBuf}; + +use parse::{CalyxFFIMacroArgs, CalyxPortDeclaration}; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, spanned::Spanned}; + +mod calyx; +mod parse; +mod util; + +#[proc_macro_attribute] +pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream { + let source_manifest_dir = PathBuf::from( + env::vars() + .find(|(name, _)| name == "CARGO_MANIFEST_DIR") + .expect("caller of calyx_ffi did not use cargo to build project") + .1, + ); + + let args = parse_macro_input!(attrs as CalyxFFIMacroArgs); + let item_struct = parse_macro_input!(item as syn::ItemStruct); + let name = item_struct.ident; + let given_path = args.src.to_string_lossy().to_string(); + + let mut path = source_manifest_dir; + path.push(given_path); + let path = path; + + // + let comp = calyx::parse_calyx_file(&args, path.clone()); + if let Err(error) = comp { + return error; + } + let comp = comp.unwrap(); + let comp = comp.get(); + // + + let comp_name = + syn::parse_str::(&format!("\"{}\"", comp.name)) + .expect("failed to turn quoted name into string"); + let comp_path = syn::parse_str::(&format!( + "\"{}\"", + path.to_string_lossy() + )) + .expect("failed to turn quoted path into string"); + + let backend_macro = args.backend; + let mut input_names = Vec::new(); + let mut output_names = Vec::new(); + let mut fields = vec![]; + let mut default_field_inits = vec![]; + let mut getters = vec![]; + let mut setters = vec![]; + let mut width_getters = vec![]; + let mut port_names = vec![]; + + for port in comp.signature.borrow().ports() { + let port_name_str = port.borrow().name.to_string(); + let port_name = syn::parse_str::(&port_name_str) + .expect("failed to turn port name into identifier"); + + port_names.push(port_name.clone()); + + let port_width = port.borrow().width; + let width_getter = format_ident!("{}_width", port_name); + width_getters.push(quote! { + pub const fn #width_getter() -> u64 { + #port_width + } + }); + + default_field_inits.push(quote! { + #port_name: calyx_ffi::value_from_u64::<#port_width>(0) + }); + + // idk why input output ports are being flipped?? + match port.borrow().direction.reverse() { + calyx_ir::Direction::Input => { + let setter = format_ident!("set_{}", port_name); + fields.push(quote! { + pub #port_name: calyx_ffi::Value<#port_width> + }); + setters.push(quote! { + pub fn #setter(&mut self, value: u64) { + self.#port_name = calyx_ffi::value_from_u64::<#port_width>(value); + } + }); + input_names.push(port_name); + } + calyx_ir::Direction::Output => { + fields.push(quote! { + #port_name: calyx_ffi::Value<#port_width> + + }); + + let bitvec_getter = format_ident!("{}_bits", port_name); + + getters.push(quote! { + pub fn #port_name(&self) -> u64 { + interp::BitVecOps::to_u64(&self.#port_name).expect("port value wider than 64 bits") + } + + pub const fn #bitvec_getter(&self) -> &calyx_ffi::Value<#port_width> { + &self.#port_name + } + }); + output_names.push(port_name); + } + calyx_ir::Direction::Inout => { + todo!("inout ports not supported yet") + } + } + } + + let struct_def = quote! { + struct #name { + #(#fields,)* + user_data: std::mem::MaybeUninit<#backend_macro!(@user_data_type)> + } + }; + + let impl_block = quote! { + impl #name { + #(#width_getters)* + #(#getters)* + #(#setters)* + } + + impl std::default::Default for #name { + fn default() -> Self { + Self { + #(#default_field_inits),*, + user_data: unsafe { std::mem::MaybeUninit::zeroed() } + } + } + } + + impl std::clone::Clone for #name { + fn clone(&self) -> Self { + Self { + #(#port_names: self.#port_names.clone()),*, + user_data: unsafe { std::mem::MaybeUninit::new(self.user_data.assume_init_ref().clone()) } + } + } + } + + impl CalyxFFIComponent for #name { + fn path(&self) -> &'static str { + #comp_path + } + + fn name(&self) -> &'static str { + #comp_name + } + + fn init(&mut self, context: &calyx_ir::Context) { + #backend_macro!(@init self, context; #(#input_names),*; #(#output_names),*); + } + + fn reset(&mut self) { + #backend_macro!(@reset self; #(#input_names),*; #(#output_names),*); + } + + fn can_tick(&self) -> bool { + #backend_macro!(@can_tick self; #(#input_names),*; #(#output_names),*) + } + + fn tick(&mut self) { + #backend_macro!(@tick self; #(#input_names),*; #(#output_names),*); + } + + fn go(&mut self) { + #backend_macro!(@go self; #(#input_names),*; #(#output_names),*); + } + } + }; + + let mut derive_impls = Vec::new(); + + for derive in args.derives { + let trait_name = derive.name; + + let mut getters = Vec::new(); + for CalyxPortDeclaration(name, width) in derive.outputs { + let name_bits = format_ident!("{}_bits", &name); + + getters.push(quote! { + fn #name_bits(&self) -> &calyx_ffi::Value<#width> { + &self.#name + } + + fn #name(&self) -> u64 { + Self::#name(self) + } + }); + } + + let mut setters = Vec::new(); + for CalyxPortDeclaration(name, width) in derive.inputs { + let name_bits = format_ident!("{}_bits", name); + let setter = format_ident!("set_{}", name); + + setters.push(quote! { + fn #name_bits(&mut self) -> &mut calyx_ffi::Value<#width> { + &mut self.#name + } + + fn #setter(&mut self, value: u64) { + Self::#setter(self, value); + } + }); + } + + derive_impls.push(quote! { + impl #trait_name for #name { + #(#getters)* + #(#setters)* + } + }); + } + + quote! { + #struct_def + #impl_block + #(#derive_impls)* + } + .into() +} + +#[derive(Default)] +struct CalyxFFITestModuleVisitor { + pub wrappers: Vec, + pub tests: Vec, +} + +impl syn::visit::Visit<'_> for CalyxFFITestModuleVisitor { + fn visit_item_fn(&mut self, i: &syn::ItemFn) { + let has_calyx_ffi_test = i + .attrs + .iter() + .any(|attr| attr.path().is_ident("calyx_ffi_test")); + if has_calyx_ffi_test { + let fn_name = &i.sig.ident; + let dut_type = get_ffi_test_dut_type(i) + .expect("calyx_ffi_test should enforce this invariant"); + + self.wrappers.push(syn::parse_quote! { + pub(crate) unsafe fn #fn_name(ffi: &mut CalyxFFI) { + let dut = ffi.new_comp::<#dut_type>(); + let dut_ref = &mut *dut.borrow_mut(); + let dut_pointer = dut_ref as *mut dyn CalyxFFIComponent as *mut _ as *mut #dut_type; + let dut_concrete: &mut #dut_type = &mut *dut_pointer; + super::#fn_name(dut_concrete); + } + }); + self.tests.push(syn::parse_quote! { + #[test] + pub(crate) fn #fn_name() { + let mut ffi = CalyxFFI::new(); + unsafe { + super::calyx_ffi_generated_wrappers::#fn_name(&mut ffi); + } + } + }); + } + } +} + +#[proc_macro_attribute] +pub fn calyx_ffi_tests(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return util::compile_error( + &args.into_iter().next().unwrap().span().into(), + "#[calyx_ffi_tests] takes no arguments".into(), + ); + } + + let mut module = parse_macro_input!(item as syn::ItemMod); + let module_name = &module.ident; + + let mut visitor = CalyxFFITestModuleVisitor::default(); + syn::visit::visit_item_mod(&mut visitor, &module); + let wrappers = visitor.wrappers; + let tests = visitor.tests; + + let test_names = wrappers.iter().map(|test| test.sig.ident.clone()); + let generated_wrappers = quote! { + pub(crate) mod calyx_ffi_generated_wrappers { + use super::*; + + pub(crate) const CALYX_FFI_TESTS: &'static [unsafe fn(&mut CalyxFFI) -> ()] = &[ + #(#test_names),* + ]; + + #(#wrappers)* + } + }; + let generated_wrappers_item: syn::Item = + syn::parse2(generated_wrappers).unwrap(); + + let generated_tests = quote! { + pub(crate) mod calyx_ffi_generated_tests { + use super::*; + + #(#tests)* + } + }; + let generated_tests_item: syn::Item = syn::parse2(generated_tests).unwrap(); + + let items_to_add = vec![generated_wrappers_item, generated_tests_item]; + if let Some((_, ref mut items)) = module.content { + items.extend(items_to_add); + } else { + module.content = Some((syn::token::Brace::default(), items_to_add)); + } + + quote! { + #module + + pub mod calyx_ffi_generated_top { + use super::*; + + pub unsafe fn run_tests(ffi: &mut CalyxFFI) { + for test in #module_name::calyx_ffi_generated_wrappers::CALYX_FFI_TESTS { + test(ffi); + } + } + } + } + .into() +} + +#[proc_macro_attribute] +pub fn calyx_ffi_test(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return util::compile_error( + &args.into_iter().next().unwrap().span().into(), + "#[calyx_ffi_test] takes no arguments".into(), + ); + } + + let mut func = parse_macro_input!(item as syn::ItemFn); + let dut_type = get_ffi_test_dut_type(&func); + let Ok(dut_type) = dut_type else { + return dut_type.err().unwrap(); + }; + + let check_trait_impl = quote! { + { + fn assert_is_calyx_ffi_component() {} + assert_is_calyx_ffi_component::<#dut_type>(); + } + }; + + let check_trait_impl_stmts: syn::Block = syn::parse2(check_trait_impl) + .expect("Failed to parse check_trait_impl as a block"); + + let new_stmts: Vec = check_trait_impl_stmts + .stmts + .iter() + .chain(func.block.stmts.iter()) + .cloned() + .collect(); + + let new_block = syn::Block { + brace_token: func.block.brace_token, + stmts: new_stmts, + }; + func.block = Box::new(new_block); + + quote! { + #func + } + .into() +} + +fn get_ffi_test_dut_type( + func: &syn::ItemFn, +) -> Result<&syn::Type, TokenStream> { + let inputs: Vec<&syn::FnArg> = func.sig.inputs.iter().collect(); + + let bad_sig_msg = "#[calyx_ffi_test] tests must take exactly one argument, namely, a mutable reference to the DUT".into(); + + if inputs.len() != 1 { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + } + let input = inputs.first().unwrap(); + + let syn::FnArg::Typed(pat_ty) = input else { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + }; + + let syn::Type::Reference(syn::TypeReference { + mutability: Some(syn::token::Mut { span: _ }), + ref elem, + .. + }) = *pat_ty.ty + else { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + }; + + Ok(elem) +} diff --git a/tools/calyx-ffi-macro/src/parse.rs b/tools/calyx-ffi-macro/src/parse.rs new file mode 100644 index 0000000000..422b03cea3 --- /dev/null +++ b/tools/calyx-ffi-macro/src/parse.rs @@ -0,0 +1,111 @@ +use std::path::PathBuf; + +use proc_macro2::{Span, TokenTree}; +use syn::{ + bracketed, parenthesized, + parse::{Parse, ParseStream}, +}; + +pub struct CalyxPortDeclaration(pub syn::Ident, pub syn::LitInt); + +impl Parse for CalyxPortDeclaration { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse::()?; + input.parse::()?; + let width = input.parse::()?; + Ok(Self(name, width)) + } +} + +pub struct CalyxInterface { + pub name: syn::Ident, + pub inputs: Vec, + pub outputs: Vec, +} + +impl Parse for CalyxInterface { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse::()?; + let inputs; + let outputs; + parenthesized!(inputs in input); + let inputs = inputs + .parse_terminated(CalyxPortDeclaration::parse, syn::Token![,])? + .into_iter() + .collect(); + input.parse::]>()?; + parenthesized!(outputs in input); + let outputs = outputs + .parse_terminated(CalyxPortDeclaration::parse, syn::Token![,])? + .into_iter() + .collect(); + Ok(Self { + name, + inputs, + outputs, + }) + } +} + +pub struct CalyxFFIMacroArgs { + pub src_attr_span: Span, + pub src: PathBuf, + pub comp_attr_span: Span, + pub comp: String, + pub backend: syn::Path, + pub derives: Vec, +} + +impl Parse for CalyxFFIMacroArgs { + fn parse(input: ParseStream) -> syn::Result { + syn::custom_keyword!(src); + syn::custom_keyword!(comp); + syn::custom_keyword!(backend); + syn::custom_keyword!(derive); + + let src_ident = input.parse::()?; + input.parse::()?; + let src_lit = input.parse::()?.value(); + + input.parse::()?; + + let comp_ident = input.parse::()?; + input.parse::()?; + let comp_lit = input.parse::()?.value(); + + input.parse::()?; + input.parse::()?; + input.parse::()?; + let backend_path = input.parse::()?; + + let _ = input.parse::(); + + let derives = if input.parse::().is_ok() { + input.parse::()?; + let content; + bracketed!(content in input); + content + .parse_terminated(CalyxInterface::parse, syn::Token![,])? + .into_iter() + .collect() + } else { + vec![] + }; + + if !input.is_empty() { + return Err(syn::Error::new_spanned( + input.parse::()?, + "Invalid `calyx_ffi` argument syntax: expected 'src = \"...\", comp = \"...\", extern = ...", + )); + } + + Ok(Self { + src_attr_span: src_ident.span, + src: src_lit.into(), + comp_attr_span: comp_ident.span, + comp: comp_lit, + backend: backend_path, + derives, + }) + } +} diff --git a/tools/calyx-ffi-macro/src/util.rs b/tools/calyx-ffi-macro/src/util.rs new file mode 100644 index 0000000000..882a9e0e6b --- /dev/null +++ b/tools/calyx-ffi-macro/src/util.rs @@ -0,0 +1,6 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; + +pub fn compile_error(span: &Span, msg: String) -> TokenStream { + syn::Error::new(*span, msg).to_compile_error().into() +} diff --git a/tools/calyx-ffi/Cargo.lock b/tools/calyx-ffi/Cargo.lock new file mode 100644 index 0000000000..b980509297 --- /dev/null +++ b/tools/calyx-ffi/Cargo.lock @@ -0,0 +1,1841 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + +[[package]] +name = "argh" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.87", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "btor2i" +version = "0.1.0" +dependencies = [ + "bitvec", + "btor2tools", + "clap", + "num-bigint", + "num-integer", + "num-traits", + "tempfile", + "thiserror", +] + +[[package]] +name = "btor2tools" +version = "1.1.0" +source = "git+https://github.com/obhalerao/btor2tools.rs#8d34a0dc96b447fb008ffbb6f15d1ec947eaefb8" +dependencies = [ + "btor2tools-sys", + "thiserror", +] + +[[package]] +name = "btor2tools-sys" +version = "1.1.0" +source = "git+https://github.com/obhalerao/btor2tools-sys#e5d1c44220cb5a02b30b538c5ade70102109904a" +dependencies = [ + "bindgen", + "copy_dir", +] + +[[package]] +name = "calyx-ffi" +version = "0.0.0" +dependencies = [ + "calyx-ffi-macro", + "calyx-frontend", + "calyx-ir", + "interp", +] + +[[package]] +name = "calyx-ffi-macro" +version = "0.0.0" +dependencies = [ + "calyx-frontend", + "calyx-ir", + "calyx-utils", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "calyx-frontend" +version = "0.7.1" +dependencies = [ + "atty", + "calyx-utils", + "itertools", + "lazy_static", + "linked-hash-map", + "log", + "pest", + "pest_consume", + "pest_derive", + "smallvec", + "strum", + "strum_macros", +] + +[[package]] +name = "calyx-ir" +version = "0.7.1" +dependencies = [ + "calyx-frontend", + "calyx-utils", + "itertools", + "linked-hash-map", + "log", + "petgraph", + "smallvec", + "string-interner", +] + +[[package]] +name = "calyx-opt" +version = "0.7.1" +dependencies = [ + "calyx-ir", + "calyx-utils", + "itertools", + "lazy_static", + "linked-hash-map", + "log", + "petgraph", + "serde", + "serde_json", + "smallvec", +] + +[[package]] +name = "calyx-utils" +version = "0.7.1" +dependencies = [ + "atty", + "itertools", + "petgraph", + "serde", + "serde_json", + "string-interner", + "symbol_table", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fraction" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad18174c73e668720cf9961281ea94a10a8c4003c2b2ad7117252109e5423a3f" +dependencies = [ + "lazy_static", + "num", + "serde", + "serde_derive", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "ibig" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" +dependencies = [ + "cfg-if", + "num-traits", + "rand", + "serde", + "static_assertions", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", +] + +[[package]] +name = "interp" +version = "0.1.1" +dependencies = [ + "ahash 0.8.11", + "argh", + "base64", + "bitvec", + "btor2i", + "calyx-frontend", + "calyx-ir", + "calyx-opt", + "calyx-utils", + "ciborium", + "fraction", + "ibig", + "itertools", + "lazy_static", + "once_cell", + "owo-colors", + "pest", + "pest_consume", + "pest_derive", + "petgraph", + "rustyline", + "serde", + "serde_json", + "serde_with", + "slog", + "slog-async", + "slog-term", + "smallvec", + "thiserror", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_consume" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79447402d15d18e7142e14c72f2e63fa3d155be1bc5b70b3ccbb610ac55f536b" +dependencies = [ + "pest", + "pest_consume_macros", + "pest_derive", +] + +[[package]] +name = "pest_consume_macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8630a7a899cb344ec1c16ba0a6b24240029af34bdc0a21f84e411d7f793f29" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.87", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "rustyline" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-async" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "string-interner" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e2531d8525b29b514d25e275a43581320d587b86db302b9a7e464bac579648" +dependencies = [ + "cfg-if", + "hashbrown 0.11.2", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.87", +] + +[[package]] +name = "symbol_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "828f672b631c220bf6ea8a1d3b82c7d0fc998e5ba8373383d8604bc1e2a6245a" +dependencies = [ + "ahash 0.7.8", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/tools/calyx-ffi/Cargo.toml b/tools/calyx-ffi/Cargo.toml new file mode 100644 index 0000000000..16e87525df --- /dev/null +++ b/tools/calyx-ffi/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "calyx-ffi" +authors.workspace = true +license-file.workspace = true +keywords.workspace = true +repository.workspace = true +readme.workspace = true +description.workspace = true +categories.workspace = true +homepage.workspace = true +edition.workspace = true +version.workspace = true +rust-version.workspace = true + +[dependencies] +calyx-ffi-macro = { path = "../calyx-ffi-macro" } +calyx-frontend.workspace = true +calyx-ir.workspace = true +interp = { path = "../../interp" } +paste = "1.0.15" + +[dev-dependencies] +rand = "0.8.5" diff --git a/tools/calyx-ffi/src/backend.rs b/tools/calyx-ffi/src/backend.rs new file mode 100644 index 0000000000..759f64d89b --- /dev/null +++ b/tools/calyx-ffi/src/backend.rs @@ -0,0 +1,2 @@ +pub mod cider; +pub mod useless; diff --git a/tools/calyx-ffi/src/backend/cider.rs b/tools/calyx-ffi/src/backend/cider.rs new file mode 100644 index 0000000000..a1b98a19e9 --- /dev/null +++ b/tools/calyx-ffi/src/backend/cider.rs @@ -0,0 +1,109 @@ +use calyx_ir::Context; +use interp::{ + configuration::RuntimeConfig, + flatten::{ + flat_ir, + structures::{ + context::Context as CiderContext, + environment::{BaseSimulator, Environment}, + }, + }, + BitVecValue, +}; +use std::rc::Rc; + +#[derive(Clone)] +pub struct CiderFFIBackend { + simulator: BaseSimulator>, +} + +impl CiderFFIBackend { + pub fn from(ctx: &Context, _name: &'static str) -> Self { + // TODO(ethan, maybe griffin): use _name to select the component somehow + let ctx = flat_ir::translate(ctx); + let config = RuntimeConfig::default(); + let enviroment = Environment::new( + Rc::new(ctx), + None, + false, + config.get_logging_config(), + ); + let simulator = BaseSimulator::new(enviroment, config); + Self { simulator } + } + + pub fn write_port(&mut self, name: &'static str, value: &BitVecValue) { + if name == "go" || name == "reset" { + return; + } + self.simulator.pin_value(name, value.clone()); + } + + pub fn read_port(&self, name: &'static str) -> BitVecValue { + self.simulator + .lookup_port_from_string(&String::from(name)) + .expect("wrong port name") + } + + pub fn step(&mut self) { + self.simulator.step().expect( + "this function isn't documented so don't know what went wrong", + ); + } + + pub fn go(&mut self) { + self.simulator + .run_program_inner(None) + .expect("failed to run program"); + self.step(); // since griffin said so + } +} + +/// Runs the component using cider2. +#[macro_export] +macro_rules! cider_ffi_backend { + (@user_data_type) => { + $crate::backend::cider::CiderFFIBackend + }; + (@init $dut:ident, $ctx:expr; $($input:ident),*; $($output:ident),*) => { + $dut.user_data + .write($crate::backend::cider::CiderFFIBackend::from( + $ctx, + $dut.name(), + )); + }; + (@reset $dut:ident; $($input:ident),*; $($output:ident),*) => { + println!("cider_ffi_backend reset. doesn't work LOL"); + // $dut.done = 0; + // $dut.reset = 1; + // for i in 0..5 { + // $dut.tick(); + // } + // $dut.reset = 0; + }; + (@can_tick $dut:ident; $($input:ident),*; $($output:ident),*) => { + true + }; + (@tick $dut:ident; $($input:ident),*; $($output:ident),*) => { + // println!("cider_ffi_backend tick"); + let cider = unsafe { $dut.user_data.assume_init_mut() }; + $( + cider.write_port(stringify!($input), &$dut.$input); + )* + cider.step(); + $( + $dut.$output = cider.read_port(stringify!($output)); + )* + }; + (@go $dut:ident; $($input:ident),*; $($output:ident),*) => { + // println!("cider_ffi_backend go"); + let cider = unsafe { $dut.user_data.assume_init_mut() }; + $( + cider.write_port(stringify!($input), &$dut.$input); + )* + cider.go(); + $( + $dut.$output = cider.read_port(stringify!($output)); + )* + }; +} diff --git a/tools/calyx-ffi/src/backend/useless.rs b/tools/calyx-ffi/src/backend/useless.rs new file mode 100644 index 0000000000..4bc32c4264 --- /dev/null +++ b/tools/calyx-ffi/src/backend/useless.rs @@ -0,0 +1,35 @@ +/// Example FFI backend. +#[macro_export] +macro_rules! useless_ffi_backend { + (@user_data_type) => { + () // unit type + }; + (@init $dut:ident, $ctx:expr; $($input:ident),*; $($output:ident),*) => { + println!("useless_ffi_backend init"); + }; + (@reset $dut:ident; $($input:ident),*; $($output:ident),*) => { + println!("useless_ffi_backend reset"); + $dut.done = interp::BitVecValue::from_u64(0, $dut.done_width() as u32); + $dut.set_reset(1); + for i in 0..5 { + $dut.tick(); + } + $dut.set_reset(0); + }; + (@can_tick $dut:ident; $($input:ident),*; $($output:ident),*) => { + true + }; + (@tick $dut:ident; $($input:ident),*; $($output:ident),*) => { + println!("useless_ffi_backend tick"); + if $dut.done() == 1 { + $dut.set_reset(0); + } + }; + (@go $dut:ident; $($input:ident),*; $($output:ident),*) => { + println!("useless_ffi_backend go"); + $dut.set_go(1); + $dut.set_go(0); + $dut.done = interp::BitVecValue::from_u64(1, $dut.done_width() as u32); + $dut.tick(); + }; +} diff --git a/tools/calyx-ffi/src/lib.rs b/tools/calyx-ffi/src/lib.rs new file mode 100644 index 0000000000..9ba180bc89 --- /dev/null +++ b/tools/calyx-ffi/src/lib.rs @@ -0,0 +1,82 @@ +use calyx_ir::Context; +use std::{ + any, cell::RefCell, collections::HashMap, env, path::PathBuf, rc::Rc, +}; + +pub mod backend; +pub mod prelude; + +/// A non-combinational calyx component. +pub trait CalyxFFIComponent: any::Any { + /// The path to the component source file. Must be a constant expression. + fn path(&self) -> &'static str; + + /// The in-source name of this component. Must be a constant expression. + fn name(&self) -> &'static str; + + /// Internal initialization routine. Do not call! + fn init(&mut self, context: &Context); + + /// Resets this component. + fn reset(&mut self); + + /// Whether this component's backend supports ticking. + fn can_tick(&self) -> bool; + + /// Advances this component by one clock cycle. May not always be available, so check [`has_tick`]([CalyxFFIComponent::has_tick]). + fn tick(&mut self); + + /// Calls this component, blocking until it is done executing. + fn go(&mut self); +} + +pub type CalyxFFIComponentRef = Rc>; + +fn box_calyx_ffi_component( + comp: T, +) -> CalyxFFIComponentRef { + Rc::new(RefCell::new(comp)) +} + +#[derive(Default)] +pub struct CalyxFFI { + contexts: HashMap<&'static str, Context>, +} + +impl CalyxFFI { + pub fn new() -> Self { + Self::default() + } + + /// Constructs a new calyx component of the given type. + /// + /// The `path` implementation for `CalyxFFIComponent` must be a constant + /// expression and should derived via the `calyx_ffi` procedural macro. + pub fn new_comp( + &mut self, + ) -> CalyxFFIComponentRef { + let mut comp = T::default(); + let path = comp.path(); + let context = self.contexts.entry(path).or_insert_with_key(|path| { + // there has to be a better way to find lib + let home_dir = env::var("HOME").expect("user home not set"); + let mut lib_path = PathBuf::from(home_dir); + lib_path.push(".calyx"); + let ws = calyx_frontend::Workspace::construct( + &Some(path.into()), + &lib_path, + ) + .expect("couldn't parse calyx"); + calyx_ir::from_ast::ast_to_ir(ws) + .expect("couldn't construct calyx ir") + }); + comp.init(context); + box_calyx_ffi_component(comp) + } +} + +pub type Value = interp::BitVecValue; + +pub fn value_from_u64(value: u64) -> Value { + Value::from_u64(value, N as u32) +} diff --git a/tools/calyx-ffi/src/prelude.rs b/tools/calyx-ffi/src/prelude.rs new file mode 100644 index 0000000000..425a29c2ec --- /dev/null +++ b/tools/calyx-ffi/src/prelude.rs @@ -0,0 +1,41 @@ +pub use super::{ + value_from_u64, CalyxFFI, CalyxFFIComponent, CalyxFFIComponentRef, Value, +}; +pub use calyx_ffi_macro::{calyx_ffi, calyx_ffi_test, calyx_ffi_tests}; +pub use calyx_ir; +pub use interp; +pub use paste; + +#[macro_export] +macro_rules! declare_interface { + ($name:ident($($input:ident: $input_width:literal),*) + -> ($($output:ident: $output_width:literal),*) + $(impl { + $(fn $fn2:ident(& $self2:ident $(, $arg2:ident: $argty2:ty)* $(,)?) $(-> $ret2:ty)? $body2:block)* + })? + $(mut impl { + $(fn $fn:ident(&mut $self:ident $(, $arg:ident: $argty:ty)* $(,)?) $(-> $ret:ty)? $body:block)* + })? + ) => { + calyx_ffi::prelude::paste::paste! { + pub trait $name: CalyxFFIComponent { + $( + fn [<$input _bits>](&mut self) -> &mut calyx_ffi::Value<$input_width>; + + fn [](&mut self, value: u64); + )* + $( + fn [<$output _bits>](&self) -> &calyx_ffi::Value<$output_width>; + + fn $output(&self) -> u64; + )* + $($( + fn $fn2(&$self2, $($arg2: $argty2),*) $(-> $ret2)* {$body2} + )*)* + $($( + fn $fn(&mut $self, $($arg: $argty),*) $(-> $ret)* {$body} + )*)* + } + } + }; +} diff --git a/tools/calyx-ffi/tests/adder.futil b/tools/calyx-ffi/tests/adder.futil new file mode 100644 index 0000000000..1730a7c5fd --- /dev/null +++ b/tools/calyx-ffi/tests/adder.futil @@ -0,0 +1,23 @@ +import "primitives/core.futil"; + +component main(lhs: 64, rhs: 64) -> (result: 64) { + cells { + adder = std_add(64); + temp = std_reg(64); + } + wires { + group add { + adder.left = lhs; + adder.right = rhs; + temp.in = adder.out; + temp.write_en = 1'b1; + add[done] = temp.done; + } + result = temp.out; + } + control { + add; + } +} + + diff --git a/tools/calyx-ffi/tests/arith_fuzz.rs b/tools/calyx-ffi/tests/arith_fuzz.rs new file mode 100644 index 0000000000..9bc7cdb427 --- /dev/null +++ b/tools/calyx-ffi/tests/arith_fuzz.rs @@ -0,0 +1,74 @@ +use calyx_ffi::prelude::*; + +use calyx_ffi::cider_ffi_backend; + +// not necessary, just to show it off +calyx_ffi::declare_interface! { + In2Out1(lhs: 64, rhs: 64) -> (result: 64) +} + +#[calyx_ffi( + src = "tests/adder.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + In2Out1(lhs: 64, rhs: 64) -> (result: 64) + ] +)] +struct Adder; + +#[calyx_ffi( + src = "tests/subber.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + In2Out1(lhs: 64, rhs: 64) -> (result: 64) + ] +)] +struct Subber; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use std::mem; + + use super::*; + use rand::Rng; + + // inv: the left argument will always be greater than the right + fn fuzz_in2out1 u64>( + comp: &mut I, + oracle: &F, + ) { + comp.reset(); + let mut rng = rand::thread_rng(); + for (mut x, mut y) in (0..100).map(|_| (rng.gen(), rng.gen())) { + if y > x { + mem::swap(&mut x, &mut y); + } + comp.set_lhs(x); + comp.set_rhs(y); + comp.go(); + assert_eq!( + oracle(x, y), + comp.result(), + "component did not evaluate f({}, {}) = {} correctly", + x, + y, + oracle(x, y) + ); + } + } + + #[calyx_ffi_test] + fn test_add(adder: &mut Adder) { + println!("testing adder"); + fuzz_in2out1(adder, &|x, y| x.wrapping_add(y)) + } + + #[calyx_ffi_test] + fn test_sub(subber: &mut Subber) { + println!("testing subber"); + fuzz_in2out1(subber, &|x, y| x - y) + } +} diff --git a/tools/calyx-ffi/tests/fifo.futil b/tools/calyx-ffi/tests/fifo.futil new file mode 100644 index 0000000000..6bc65ba106 --- /dev/null +++ b/tools/calyx-ffi/tests/fifo.futil @@ -0,0 +1,130 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; +import "primitives/binary_operators.futil"; +component fifo(cmd: 1, value: 32) -> () { + cells { + mem = seq_mem_d1(32, 16, 4); + reg_1 = std_reg(4); + reg_2 = std_reg(4); + ref ans = std_reg(32); + ref err = std_reg(1); + reg_3 = std_reg(5); + eq_4 = std_eq(5); + reg_2_incr_1_5 = std_add(4); + reg_3_decr_1_6 = std_sub(5); + eq_7 = std_eq(5); + reg_1_incr_1_8 = std_add(4); + reg_3_incr_1_9 = std_add(5); + cmd_eq_0_10 = std_eq(1); + cmd_eq_1_11 = std_eq(1); + } + wires { + group raise_err { + err.in = 1'd1; + err.write_en = 1'd1; + raise_err[done] = err.done; + } + comb group eq_4_group { + eq_4.left = reg_3.out; + eq_4.right = 5'd0; + } + group read_payload_from_mem_pop { + mem.addr0 = reg_2.out; + mem.content_en = 1'd1; + ans.write_en = mem.done ? 1'd1; + ans.in = mem.done ? mem.read_data; + read_payload_from_mem_pop[done] = ans.done; + } + group reg_2_incr_1_5_group { + reg_2_incr_1_5.left = reg_2.out; + reg_2_incr_1_5.right = 4'd1; + reg_2.write_en = 1'd1; + reg_2.in = reg_2_incr_1_5.out; + reg_2_incr_1_5_group[done] = reg_2.done; + } + group reg_3_decr_1_6_group { + reg_3_decr_1_6.left = reg_3.out; + reg_3_decr_1_6.right = 5'd1; + reg_3.write_en = 1'd1; + reg_3.in = reg_3_decr_1_6.out; + reg_3_decr_1_6_group[done] = reg_3.done; + } + comb group eq_7_group { + eq_7.left = reg_3.out; + eq_7.right = 5'd16; + } + group write_payload_to_mem { + mem.addr0 = reg_1.out; + mem.write_en = 1'd1; + mem.write_data = value; + write_payload_to_mem[done] = mem.done; + mem.content_en = 1'd1; + } + group reg_1_incr_1_8_group { + reg_1_incr_1_8.left = reg_1.out; + reg_1_incr_1_8.right = 4'd1; + reg_1.write_en = 1'd1; + reg_1.in = reg_1_incr_1_8.out; + reg_1_incr_1_8_group[done] = reg_1.done; + } + group reg_3_incr_1_9_group { + reg_3_incr_1_9.left = reg_3.out; + reg_3_incr_1_9.right = 5'd1; + reg_3.write_en = 1'd1; + reg_3.in = reg_3_incr_1_9.out; + reg_3_incr_1_9_group[done] = reg_3.done; + } + cmd_eq_0_10.left = cmd; + cmd_eq_0_10.right = 1'd0; + cmd_eq_1_11.left = cmd; + cmd_eq_1_11.right = 1'd1; + } + control { + par { + if cmd_eq_0_10.out { + if eq_4.out with eq_4_group { + raise_err; + } else { + seq { + read_payload_from_mem_pop; + reg_2_incr_1_5_group; + reg_3_decr_1_6_group; + } + } + } + if cmd_eq_1_11.out { + if eq_7.out with eq_7_group { + raise_err; + } else { + seq { + write_payload_to_mem; + reg_1_incr_1_8_group; + reg_3_incr_1_9_group; + } + } + } + } + } +} +component main(cmd: 1, value: 32) -> (ans: 32, err: 1) { + cells { + ans_reg = std_reg(32); + err_reg = std_reg(1); + queue = fifo(); + } + wires { + ans = ans_reg.out; + err = err_reg.out; + group dummy { + ans_reg.in = ans_reg.out; + ans_reg.write_en = 1'b1; + dummy[done] = ans_reg.done; + } + } + control { + seq { + invoke queue[ans = ans_reg, err = err_reg](cmd = cmd, value = value)(); + dummy; + } + } +} diff --git a/tools/calyx-ffi/tests/fifo.rs b/tools/calyx-ffi/tests/fifo.rs new file mode 100644 index 0000000000..f1199f5406 --- /dev/null +++ b/tools/calyx-ffi/tests/fifo.rs @@ -0,0 +1,69 @@ +use calyx_ffi::cider_ffi_backend; +use calyx_ffi::prelude::*; + +enum QueueCommand { + Pop = 0, + Push = 1, +} + +#[derive(PartialEq, Eq, Debug)] +enum QueueStatus { + Ok = 0, + Err = 1, +} + +calyx_ffi::declare_interface! { + Queue(cmd: 1, value: 32) -> (ans: 32, err: 1) impl { + fn status(&self) -> QueueStatus { + if self.err() == 0 { QueueStatus::Ok } else { QueueStatus::Err } + } + } mut impl { + fn assert_no_error(&mut self) { + assert_eq!(QueueStatus::Ok, self.status(), "queue underflowed or overflowed"); + } + + fn push(&mut self, value: u32) { + self.reset(); + self.set_cmd(QueueCommand::Push as u64); + self.set_value(value as u64); + self.go(); + self.assert_no_error(); + } + + fn pop(&mut self) -> u32 { + self.reset(); + self.set_cmd(QueueCommand::Pop as u64); + self.go(); + self.assert_no_error(); + self.ans() as u32 + } + } +} + +#[calyx_ffi( + src = "tests/fifo.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + Queue(cmd: 1, value: 32) -> (ans: 32, err: 1) + ] +)] +struct Fifo; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use super::*; + + #[calyx_ffi_test] + fn test_fifo(fifo: &mut Fifo) { + println!("testing fifo"); + + fifo.push(1); + fifo.push(2); + assert_eq!(1, fifo.pop()); + fifo.push(3); + assert_eq!(2, fifo.pop()); + assert_eq!(3, fifo.pop()); + } +} diff --git a/tools/calyx-ffi/tests/stack.futil b/tools/calyx-ffi/tests/stack.futil new file mode 100644 index 0000000000..11575c4c0c --- /dev/null +++ b/tools/calyx-ffi/tests/stack.futil @@ -0,0 +1,70 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; +import "primitives/binary_operators.futil"; + +component main(cmd: 1, value: 32) -> (out: 32, length: 4) { + cells { + store = seq_mem_d1(32, 16, 4); + next = std_reg( 4); + incr = std_add( 4); + decr = std_sub( 4); + last = std_reg(32); + test = std_eq(1); + } + wires { + out = last.out; + length = next.out; + + comb group check_cmd { + test.left = cmd; + test.right = 1'b0; + } + + group write_at_next { + store.addr0 = next.out; + store.write_data = value; + store.write_en = 1'b1; + store.content_en = 1'b1; + write_at_next[done] = store.done; + } + group read_from_next { + store.addr0 = next.out; + store.content_en = 1'b1; + last.in = store.read_data; + last.write_en = store.done; + read_from_next[done] = last.done; + } + group increment_next { + incr.left = next.out; + incr.right = 4'd1; + next.in = incr.out; + next.write_en = 1'b1; + increment_next[done] = next.done; + } + group decrement_next { + decr.left = next.out; + decr.right = 4'd1; + next.in = decr.out; + next.write_en = 1'b1; + decrement_next[done] = next.done; + } + } + control { + // if-else is buggy in cider2 + par { + if test.out with check_cmd { + seq { + write_at_next; + increment_next; + } + } + if cmd { + seq { + decrement_next; + read_from_next; + } + } + } + } +} + diff --git a/tools/calyx-ffi/tests/stack.rs b/tools/calyx-ffi/tests/stack.rs new file mode 100644 index 0000000000..8e5b0d60a8 --- /dev/null +++ b/tools/calyx-ffi/tests/stack.rs @@ -0,0 +1,61 @@ +use calyx_ffi::cider_ffi_backend; +use calyx_ffi::prelude::*; + +enum StackCommand { + Push = 0, + Pop = 1, +} + +const STACK_CAPACITY: u64 = 16; + +calyx_ffi::declare_interface! { + Stack(cmd: 1, value: 32) -> (out: 32, length: 4) mut impl { + fn push(&mut self, value: u32,) { + assert!(self.length() < STACK_CAPACITY, "tried to push when length={}", STACK_CAPACITY); + println!("stack has length {} before push", self.length()); + let old_length = self.length(); + self.set_cmd(StackCommand::Push as u64); + self.set_value(value as u64); + self.go(); + assert_eq!(old_length + 1, self.length(), "stack length should increase by 1 on push"); + } + + fn pop(&mut self) -> u32 { + assert!(self.length() > 0, "tried to pop when stack empty"); + println!("stack has length {} before pop", self.length()); + let old_length = self.length(); + self.set_cmd(StackCommand::Pop as u64); + self.go(); + assert_eq!(old_length - 1, self.length(), "stack length should decrease by 1 on pop"); + self.out() as u32 + } + } +} + +#[calyx_ffi( + src = "tests/stack.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + Stack(cmd: 1, value: 32) -> (out: 32, length: 4) + ] +)] +struct ReallyBadStack; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use super::*; + + #[calyx_ffi_test] + fn test_stack(stack: &mut ReallyBadStack) { + println!("testing stack"); + + stack.push(1); + stack.push(2); + assert_eq!(2, stack.pop()); + stack.push(3); + assert_eq!(3, stack.pop()); + assert_eq!(1, stack.pop()); + } +} diff --git a/tools/calyx-ffi/tests/subber.futil b/tools/calyx-ffi/tests/subber.futil new file mode 100644 index 0000000000..260456f938 --- /dev/null +++ b/tools/calyx-ffi/tests/subber.futil @@ -0,0 +1,21 @@ +import "primitives/core.futil"; + +component main(lhs: 64, rhs: 64) -> (result: 64) { + cells { + subber = std_sub(64); + temp = std_reg(64); + } + wires { + group sub { + subber.left = lhs; + subber.right = rhs; + temp.in = subber.out; + temp.write_en = 1'b1; + sub[done] = temp.done; + } + result = temp.out; + } + control { + sub; + } +} diff --git a/tools/tb/.gitignore b/tools/tb/.gitignore new file mode 100644 index 0000000000..5beb86d02e --- /dev/null +++ b/tools/tb/.gitignore @@ -0,0 +1 @@ +tb diff --git a/tools/tb/Cargo.toml b/tools/tb/Cargo.toml new file mode 100644 index 0000000000..fea0e11218 --- /dev/null +++ b/tools/tb/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "tb" +authors.workspace = true +license-file.workspace = true +keywords.workspace = true +repository.workspace = true +readme.workspace = true +description.workspace = true +categories.workspace = true +homepage.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +argh.workspace = true +tempdir = "0.3.7" +figment.workspace = true +serde.workspace = true +semver.workspace = true +libloading = "0.8.4" +log.workspace = true +env_logger.workspace = true +fs_extra = "1.3.0" +makemake = "0.1.3" +toml = "0.8.14" diff --git a/tools/tb/Makefile b/tools/tb/Makefile new file mode 100644 index 0000000000..2dc33d17c0 --- /dev/null +++ b/tools/tb/Makefile @@ -0,0 +1,11 @@ +TARGET = tb + +.PHONY: $(TARGET) +$(TARGET): plugins + cargo build --manifest-path Cargo.toml + printf "../../target/debug/tb \$$@\n" > $@ + chmod u+x $@ + +.PHONY: plugins +plugins: + $(shell which python3 || which python || which pypy3 || which pypy) build_plugins.py diff --git a/tools/tb/README.md b/tools/tb/README.md new file mode 100644 index 0000000000..3134dffa44 --- /dev/null +++ b/tools/tb/README.md @@ -0,0 +1,77 @@ +# tb: The Calyx Testbench Tool + +## Contents + +1. Setup +2. Usage +3. Writing a Plugin + +## Setup + +Run `make plugins` to build the builtin plugins (cocotb, verilator, etc.). + +## Usage + +There are two ways to use `tb`: + +### Directly + +For example, if you make sure to follow the instructions under [`examples/cocotb/doc_examples_quickstart/`](examples/cocotb/doc_examples_quickstart/), + +``` +make +./tb examples/cocotb/doc_examples_quickstart/my_design.sv -t examples/cocotb/doc_examples_quickstart/test_my_design.py --using cocotb +``` + +should run `cocotb` on the input file and harness. + +### Via `fud2` + +> THIS SECTION IS INVALIDATED SINCE I HAVE REMOVED FUD2 SUPPORT FOR THE TIME +> BEING. + +You can follow the above steps but invoke the following command instead. + +``` +fud2 my_design.sv -s tb.test=test_my_design.py -s tb.using=cocotb --to tb +``` + +### Makefile + +I've provided a [Makefile](Makefile) in this directory for local testing. Use `make` to build the `tb` executable locally. + +## Testing Calyx Code + +Your input file should be the calyx file you test and each `-t` test should be a +single-file Rust program using calyx-ffi to declare one or more components in +the file and then to write a single `#[calyx_ffi_tests]` module in which you +write your test functions. + +## Writing a Plugin + +> WE NO LONGER ALLOW DYNAMICALLY LOADING PLUGINS. + +First, setup a simple rust library as you would any other, but **ensure that `lib.crate-type` is `cdylib`**. +Here, we're writing the plugin in `lib.rs`. +Remember to update the `path` in the `dependencies.tb` dependency! + +```toml +[package] +name = "my-tb-plugin" +edition = "2021" # or `edition.workspace = true` + +[lib] +path = "lib.rs" +crate-type = ["cdylib"] + +[dependencies] +tb = { path = "path/to/tb/crate", version = "0.0.0" } +``` + +In the crate, you can write any auxillary code. +However, you'll need to define at least two things: + +1. A type implementing `tb::plugin::Plugin`. +2. A `declare_plugin!` declaration to expose the plugin and its constructor to the outside world. + +It may be helpful to look at the existing plugins for reference. diff --git a/tools/tb/build_plugins.py b/tools/tb/build_plugins.py new file mode 100644 index 0000000000..99d9235095 --- /dev/null +++ b/tools/tb/build_plugins.py @@ -0,0 +1,28 @@ +import os +import subprocess + +for dir_name in os.listdir("plugins"): + dir_path = os.path.join("plugins", dir_name) + if os.path.isdir(dir_path): + subprocess.run( + [ + "cargo", + "build", + "--manifest-path", + os.path.join(dir_path, "Cargo.toml"), + "--release", + "--target-dir", + os.path.join(dir_path, "target"), + ], + check=True, + ) + release_dir = os.path.join(dir_path, "target", "release") + for file_name in os.listdir(release_dir): + file_path = os.path.join(release_dir, file_name) + if os.path.isfile(file_path) and ( + file_name.endswith(".dylib") or file_name.endswith(".so") + ): + dest_path = os.path.join("plugins", file_name) + if os.path.isfile(dest_path): + os.remove(dest_path) + os.rename(file_path, dest_path) diff --git a/tools/tb/examples/calyx-tb/adder.futil b/tools/tb/examples/calyx-tb/adder.futil new file mode 100644 index 0000000000..1730a7c5fd --- /dev/null +++ b/tools/tb/examples/calyx-tb/adder.futil @@ -0,0 +1,23 @@ +import "primitives/core.futil"; + +component main(lhs: 64, rhs: 64) -> (result: 64) { + cells { + adder = std_add(64); + temp = std_reg(64); + } + wires { + group add { + adder.left = lhs; + adder.right = rhs; + temp.in = adder.out; + temp.write_en = 1'b1; + add[done] = temp.done; + } + result = temp.out; + } + control { + add; + } +} + + diff --git a/tools/tb/examples/calyx-tb/main.rs_ b/tools/tb/examples/calyx-tb/main.rs_ new file mode 100644 index 0000000000..0d1c2075a7 --- /dev/null +++ b/tools/tb/examples/calyx-tb/main.rs_ @@ -0,0 +1,25 @@ +// this file is extended with .rs_ instead of .rs to avoid clippy errors + +use calyx_ffi::prelude::*; + +use calyx_ffi::cider_ffi_backend; +#[calyx_ffi( + src = "adder.futil", + comp = "main", + backend = cider_ffi_backend +)] +struct Adder; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + #[calyx_ffi_test] + fn test_add(adder: &mut Adder) { + adder.reset(); + adder.lhs = 4; + adder.rhs = 5; + println!("foo"); + adder.go(); + assert_eq!(9, adder.result()); + } +} diff --git a/tools/tb/examples/cocotb/doc_examples_quickstart/README.md b/tools/tb/examples/cocotb/doc_examples_quickstart/README.md new file mode 100644 index 0000000000..b7547d5738 --- /dev/null +++ b/tools/tb/examples/cocotb/doc_examples_quickstart/README.md @@ -0,0 +1,2 @@ +Taken from https://github.com/cocotb/cocotb/tree/master/examples/doc_examples/quickstart. +Make sure to checkout `v1.8.1` for `cocotb`. diff --git a/tools/tb/examples/cocotb/doc_examples_quickstart/my_design.sv b/tools/tb/examples/cocotb/doc_examples_quickstart/my_design.sv new file mode 100644 index 0000000000..bfa912c018 --- /dev/null +++ b/tools/tb/examples/cocotb/doc_examples_quickstart/my_design.sv @@ -0,0 +1,17 @@ +// https://github.com/cocotb/cocotb/tree/master/examples/doc_examples/quickstart + +// This file is public domain, it can be freely copied without restrictions. +// SPDX-License-Identifier: CC0-1.0 + +module my_design(input logic clk); + +timeunit 1ns; +timeprecision 1ns; + +logic my_signal_1; +logic my_signal_2; + +assign my_signal_1 = 1'bx; +assign my_signal_2 = 0; + +endmodule diff --git a/tools/tb/examples/cocotb/doc_examples_quickstart/test_my_design.py b/tools/tb/examples/cocotb/doc_examples_quickstart/test_my_design.py new file mode 100644 index 0000000000..daaa9996db --- /dev/null +++ b/tools/tb/examples/cocotb/doc_examples_quickstart/test_my_design.py @@ -0,0 +1,52 @@ +# https://github.com/cocotb/cocotb/tree/master/examples/doc_examples/quickstart + +# This file is public domain, it can be freely copied without restrictions. +# SPDX-License-Identifier: CC0-1.0 + +# test_my_design.py (simple) + +import cocotb +from cocotb.triggers import Timer + + +@cocotb.test() +async def my_first_test(dut): + """Try accessing the design.""" + + for cycle in range(10): + dut.clk.value = 0 + await Timer(1, units="ns") + dut.clk.value = 1 + await Timer(1, units="ns") + + dut._log.info("my_signal_1 is %s", dut.my_signal_1.value) + assert dut.my_signal_2.value[0] == 0, "my_signal_2[0] is not 0!" + + +# test_my_design.py (extended) + +import cocotb +from cocotb.triggers import FallingEdge, Timer + + +async def generate_clock(dut): + """Generate clock pulses.""" + + for cycle in range(10): + dut.clk.value = 0 + await Timer(1, units="ns") + dut.clk.value = 1 + await Timer(1, units="ns") + + +@cocotb.test() +async def my_second_test(dut): + """Try accessing the design.""" + + await cocotb.start(generate_clock(dut)) # run the clock "in the background" + + await Timer(5, units="ns") # wait a bit + await FallingEdge(dut.clk) # wait for falling edge/"negedge" + + dut._log.info("my_signal_1 is %s", dut.my_signal_1.value) + assert dut.my_signal_2.value[0] == 0, "my_signal_2[0] is not 0!" diff --git a/tools/tb/examples/verilator/test_our/README.md b/tools/tb/examples/verilator/test_our/README.md new file mode 100644 index 0000000000..43e5c6763d --- /dev/null +++ b/tools/tb/examples/verilator/test_our/README.md @@ -0,0 +1 @@ +Taken from https://veripool.org/guide/latest/example_cc.html#example-c-execution. diff --git a/tools/tb/examples/verilator/test_our/our.v b/tools/tb/examples/verilator/test_our/our.v new file mode 100644 index 0000000000..e124a796f6 --- /dev/null +++ b/tools/tb/examples/verilator/test_our/our.v @@ -0,0 +1,4 @@ +// https://veripool.org/guide/latest/example_cc.html#example-c-execution +module our; + initial begin $display("Hello World"); $finish; end + endmodule diff --git a/tools/tb/examples/verilator/test_our/sim_main.cpp b/tools/tb/examples/verilator/test_our/sim_main.cpp new file mode 100644 index 0000000000..c881991c46 --- /dev/null +++ b/tools/tb/examples/verilator/test_our/sim_main.cpp @@ -0,0 +1,14 @@ +// https://veripool.org/guide/latest/example_cc.html#example-c-execution +#include "Vour.h" +#include "verilated.h" +int main(int argc, char** argv) { + VerilatedContext* contextp = new VerilatedContext; + contextp->commandArgs(argc, argv); + Vour* top = new Vour{contextp}; + while (!contextp->gotFinish()) { + top->eval(); + } + delete top; + delete contextp; + return 0; +} diff --git a/tools/tb/plugins/.gitkeep b/tools/tb/plugins/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/tb/src/builtin_plugins.rs b/tools/tb/src/builtin_plugins.rs new file mode 100644 index 0000000000..3eb32c3d02 --- /dev/null +++ b/tools/tb/src/builtin_plugins.rs @@ -0,0 +1,3 @@ +pub mod calyx; +pub mod cocotb; +pub mod verilator; diff --git a/tools/tb/src/builtin_plugins/calyx.rs b/tools/tb/src/builtin_plugins/calyx.rs new file mode 100644 index 0000000000..fe08275762 --- /dev/null +++ b/tools/tb/src/builtin_plugins/calyx.rs @@ -0,0 +1,129 @@ +use std::fs; +use std::io::{self, Write}; +use std::path::PathBuf; +use std::process::Command; + +use crate::{ + config::Config, error::LocalResult, plugin::Plugin, semver, tempdir, +}; + +#[derive(Default)] +pub struct CalyxTB; + +mod config_keys {} + +const DRIVER_CODE: &str = include_str!("resources/driver.rs"); + +impl Plugin for CalyxTB { + fn name(&self) -> &'static str { + "calyx" + } + + fn version(&self) -> semver::Version { + semver::Version::new(0, 0, 0) + } + + fn setup(&self, _config: &mut Config) -> LocalResult<()> { + Ok(()) + } + + fn run( + &self, + input: String, + tests: &[String], + work_dir: tempdir::TempDir, + _config: &Config, + ) -> LocalResult<()> { + eprintln!( + "recommendation: Run the #[calyx_ffi_tests] as Rust tests directly" + ); + + eprintln!("tb: --using {}: setting up dummy crate", self.name()); + + let mut dut_path = PathBuf::from(work_dir.path()); + dut_path.push(&input); + if let Some(parent) = dut_path.parent() { + fs::create_dir_all(parent)?; + } + fs::copy(&input, dut_path)?; + + let mut calyx_ffi_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + calyx_ffi_path.push("../calyx-ffi"); + + let mut main_file = PathBuf::from(work_dir.path()); + main_file.push("main.rs"); + fs::write(main_file, DRIVER_CODE)?; + + let mut manifest_path = PathBuf::from(work_dir.path()); + manifest_path.push("Cargo.toml"); + + let mut lib_path = PathBuf::from(work_dir.path()); + lib_path.push("lib.rs"); + + let mut manifest = toml::Table::new(); + manifest.insert( + "package".into(), + toml::Value::Table(toml::map::Map::from_iter([ + ("name".to_string(), "test_crate".into()), + ("edition".to_string(), "2021".into()), + ])), + ); + manifest.insert( + "lib".into(), + toml::Value::Table(toml::map::Map::from_iter([( + "path".to_string(), + "lib.rs".into(), + )])), + ); + manifest.insert( + "bin".into(), + vec![toml::Value::Table(toml::map::Map::from_iter([ + ("name".to_string(), "test".into()), + ("path".to_string(), "main.rs".into()), + ]))] + .into(), + ); + manifest.insert( + "dependencies".into(), + toml::Value::Table(toml::map::Map::from_iter([( + "calyx-ffi".to_string(), + toml::Value::Table(toml::map::Map::from_iter([( + "path".to_string(), + calyx_ffi_path.to_string_lossy().to_string().into(), + )])), + )])), + ); + + for test in tests { + fs::write(&manifest_path, manifest.to_string())?; + fs::copy(test, &lib_path)?; + + eprintln!( + "tb: --using {}: building and testing `{}` with `{}`", + self.name(), + input, + test + ); + eprintln!(" (may take a while because `rustc` is slow)"); + + let output = Command::new("cargo") + .arg("expand") + .arg("--lib") + .current_dir(work_dir.path()) + .output()?; + + println!("{}", unsafe { + String::from_utf8_unchecked(output.stdout) + }); + let output = Command::new("cargo") + .arg("run") + .arg("--quiet") + .current_dir(work_dir.path()) + .output()?; + io::stderr().write_all(&output.stderr)?; + io::stdout().write_all(&output.stdout)?; + } + + Ok(()) + } +} diff --git a/tools/tb/src/builtin_plugins/cocotb.rs b/tools/tb/src/builtin_plugins/cocotb.rs new file mode 100644 index 0000000000..2cbd27181e --- /dev/null +++ b/tools/tb/src/builtin_plugins/cocotb.rs @@ -0,0 +1,145 @@ +use std::io::{self, Write}; +use std::process::Command; +use std::{fs, path::Path}; + +use crate::{ + config::{Config, ConfigVarValidator}, + error::{LocalError, LocalResult}, + plugin::Plugin, + semver, tempdir, +}; +use makemake::{emitter::Emitter, makefile::Makefile}; + +/// v1.8.1 cocotb +#[derive(Default)] +pub struct CocoTB; + +mod config_keys { + pub const EXE: &str = "cocotb-config.exe"; + pub const SIM: &str = "sim"; +} + +fn filestem(path_str: &str) -> &str { + let path = Path::new(path_str); + path.file_stem() + .expect("invalid filename") + .to_str() + .expect("invalid unicode") +} + +impl Plugin for CocoTB { + fn name(&self) -> &'static str { + "cocotb" + } + + fn version(&self) -> semver::Version { + semver::Version::new(0, 0, 0) + } + + fn setup(&self, config: &mut Config) -> LocalResult<()> { + config.require( + config_keys::EXE, + Some("cocotb-config"), + "path to cocotb-config executable", + ConfigVarValidator::when(|value| { + if let Some(cmd) = value.as_str() { + let output = Command::new(cmd) + .arg("--version") + .output() + .map_err(LocalError::from).map_err(|_| LocalError::other(format!("{} is not the cocotb-config executable", cmd)))?; + let version = String::from_utf8(output.stdout) + .map_err(|_| LocalError::other(format!("{} is not the cocotb-config executable", cmd)))?; + if version.trim() != "1.8.1" { + Err(LocalError::other("cocotb-config must be version 1.8.1")) + } else { + Ok(()) + } + } else { + Err(LocalError::other( + "the cocotb-config executable path must be specified as a string", + )) + } + }), + ); + + config.require( + config_keys::SIM, + Some("icarus"), + "cocotb simulator", + ConfigVarValidator::when(|value| { + if let Some(sim) = value.as_str() { + let simulators = [ + "icarus", + "verilator", + "vcs", + "riviera", + "activehdl", + "questa", + "modelsim", + "ius", + "xcelium", + "ghdl", + "cvc", + ]; + if simulators.contains(&sim) { + Ok(()) + } else { + Err(LocalError::other("unsupported simulator: see https://docs.cocotb.org/en/stable/simulator_support.html for details")) + } + } else { + Err(LocalError::other( + "the cocotb simulator must be a string", + )) + } + }), + ); + + Ok(()) + } + + fn run( + &self, + input: String, + tests: &[String], + work_dir: tempdir::TempDir, + config: &Config, + ) -> LocalResult<()> { + for test in tests { + // copied from https://github.com/cocotb/cocotb/blob/v1.8.1/examples/doc_examples/quickstart/Makefile + let mut makefile = Makefile::new(); + makefile.comment("This file is public domain, it can be freely copied without restrictions."); + makefile.comment("SPDX-License-Identifier: CC0-1.0"); + makefile.newline(); + makefile.comment("Makefile"); + makefile.newline(); + makefile.comment("defaults"); + makefile.assign_without_overwrite("SIM", "icarus"); + makefile.assign_without_overwrite("TOPLEVEL_LANG", "verilog"); + makefile.append("VERILOG_SOURCES", &input); + makefile.comment("use VHDL_SOURCES for VHDL files"); + makefile.newline(); + makefile.comment("TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file"); + makefile.assign("TOPLEVEL", filestem(&input)); + makefile.newline(); + makefile.comment("MODULE is the basename of the Python test file"); + makefile.assign("MODULE", filestem(test)); + makefile.newline(); + makefile.comment("include cocotb's make rules to take care of the simulator setup"); + makefile.include(format!( + "$(shell {} --makefiles)/Makefile.sim", + config.get(config_keys::EXE)?.as_str().unwrap() + )); + + let mut makefile_path = work_dir.path().to_path_buf(); + makefile_path.push("Makefile"); + fs::write(makefile_path, makefile.build())?; + + let output = + Command::new("make").current_dir(work_dir.path()).output()?; + io::stdout().write_all(&output.stdout)?; + io::stderr().write_all(&output.stderr)?; + } + + Ok(()) + } +} diff --git a/tools/tb/src/builtin_plugins/resources/driver.rs b/tools/tb/src/builtin_plugins/resources/driver.rs new file mode 100644 index 0000000000..bce3015661 --- /dev/null +++ b/tools/tb/src/builtin_plugins/resources/driver.rs @@ -0,0 +1,9 @@ +use calyx_ffi::prelude::*; +use test_crate::calyx_ffi_generated_top::run_tests; + +fn main() { + let mut ffi = CalyxFFI::default(); + unsafe { + run_tests(&mut ffi); + } +} diff --git a/tools/tb/src/builtin_plugins/verilator.rs b/tools/tb/src/builtin_plugins/verilator.rs new file mode 100644 index 0000000000..8f1a3a3457 --- /dev/null +++ b/tools/tb/src/builtin_plugins/verilator.rs @@ -0,0 +1,136 @@ +use std::{ + io::{self, Write}, + process::Command, +}; + +use crate::{ + config::{Config, ConfigVarValidator}, + error::{LocalError, LocalResult}, + plugin::Plugin, + semver, tempdir, +}; + +mod config_keys { + pub const EXE: &str = "exe"; + pub const CFLAGS: &str = "cflags"; + pub const TOP: &str = "top"; + pub const USE_SV: &str = "use-sv"; +} + +#[derive(Default)] +pub struct Verilator; + +impl Verilator { + fn create_build_files( + &self, + input: &str, + test: &str, + work_dir: &tempdir::TempDir, + config: &Config, + ) -> LocalResult { + let mut cmd = + Command::new(config.get(config_keys::EXE)?.as_str().unwrap()); + cmd.current_dir(work_dir.path()); + cmd.args([ + "--cc", "--exe", "--build", "--timing", "-j", "0", "-Wall", input, + test, + ]); + cmd.args([ + "--top-module", + config.get(config_keys::TOP)?.as_str().unwrap(), + ]); + + let cflags = config.get(config_keys::CFLAGS)?; + let cflags = cflags.as_str().unwrap(); + cmd.args(["-CFLAGS", if cflags.is_empty() { "\"\"" } else { cflags }]); + if config.get(config_keys::USE_SV)?.as_str().unwrap() == "true" { + cmd.arg("-sv"); + } + + let output = cmd.output()?; + io::stdout().write_all(&output.stdout)?; + io::stderr().write_all(&output.stderr)?; + + Ok(format!( + "obj_dir/V{}", + config.get(config_keys::TOP)?.as_str().unwrap() + )) + } + + fn execute_harness( + &self, + executable: String, + work_dir: &tempdir::TempDir, + ) -> LocalResult<()> { + let output = Command::new(executable) + .current_dir(work_dir.path()) + .output()?; + io::stdout().write_all(&output.stdout)?; + io::stderr().write_all(&output.stderr)?; + + Ok(()) + } +} + +impl Plugin for Verilator { + fn name(&self) -> &'static str { + "verilator" + } + + fn version(&self) -> semver::Version { + semver::Version::new(0, 0, 0) + } + + fn setup(&self, config: &mut Config) -> LocalResult<()> { + config.require( + config_keys::EXE, + Some("verilator"), + "path to verilator executable", + ConfigVarValidator::default(), + ); + + config.require( + config_keys::CFLAGS, + Some(""), + "passed via -CFLAGS", + ConfigVarValidator::default(), + ); + + config.require( + config_keys::TOP, + Some("main"), + "name of top-level module", + ConfigVarValidator::default(), + ); + + config.require( + config_keys::USE_SV, + Some("true"), + "whether the input is SystemVerilog", + ConfigVarValidator::when(|value| { + value + .as_str() + .filter(|value| ["true", "false"].contains(value)) + .ok_or(LocalError::other("must be true or false")) + .map(|_| ()) + }), + ); + + Ok(()) + } + + fn run( + &self, + input: String, + tests: &[String], + work_dir: tempdir::TempDir, + config: &Config, + ) -> LocalResult<()> { + for test in tests { + let exec = + self.create_build_files(&input, test, &work_dir, config)?; + self.execute_harness(exec, &work_dir)?; + } + Ok(()) + } +} diff --git a/tools/tb/src/cli.rs b/tools/tb/src/cli.rs new file mode 100644 index 0000000000..475b5d1699 --- /dev/null +++ b/tools/tb/src/cli.rs @@ -0,0 +1,69 @@ +use std::{env, path::PathBuf, str::FromStr}; + +use argh::FromArgs; + +use crate::error::{LocalError, LocalResult}; + +pub struct ConfigSet { + pub key: String, + pub value: String, +} + +impl FromStr for ConfigSet { + type Err = LocalError; + + fn from_str(s: &str) -> LocalResult { + s.find('=') + .map(|index| { + let (key, value) = s.split_at(index); + let value = value.chars().skip(1).collect(); + ConfigSet { + key: key.to_string(), + value, + } + }) + .ok_or(LocalError::other("expected syntax 'key=value'")) + } +} + +#[derive(FromArgs, Default)] +/// Test verilog files under various harnesses. +pub struct CLI { + #[argh(positional)] + /// verilog or calyx file + pub input: String, + + #[argh(option, short = 't', long = "test")] + /// test harness + pub tests: Vec, + + #[argh(option, short = 's')] + /// set a config option + pub set: Vec, + + #[argh(option, short = 'u')] + /// the testbench to invoke, e.g., verilator, cocotb, calyx + pub using: String, + + /// path to the config file + #[argh(option, short = 'c')] + pub config: Option, + + #[argh(switch)] + /// displays version information + pub version: bool, +} + +impl CLI { + pub fn from_env() -> Self { + let args: Vec<_> = env::args().collect(); + if args.len() == 2 && matches!(args[1].as_str(), "-v" | "--version") { + Self { + version: true, + ..Default::default() + } + } else { + argh::from_env() + } + } +} diff --git a/tools/tb/src/config.rs b/tools/tb/src/config.rs new file mode 100644 index 0000000000..7bea9ab6bb --- /dev/null +++ b/tools/tb/src/config.rs @@ -0,0 +1,177 @@ +use crate::error::{LocalError, LocalResult}; +use figment::providers::Format; +use figment::value::Value; +use figment::Figment; +use std::fmt::Debug; +use std::path::Path; +use std::rc::Rc; + +pub type ConfigVarValidatorPredicate = fn(&Value) -> LocalResult<()>; + +/// TODO: make this declarative, allow building complex things in some sort of +/// eDSL fashion, with helpers for like "this must be a string", "this must be a +/// command and running it yields this output", etc. +pub struct ConfigVarValidator { + predicates: Vec, +} + +impl ConfigVarValidator { + pub fn when(predicate: ConfigVarValidatorPredicate) -> Self { + Self { + predicates: vec![predicate], + } + } + + pub fn and(mut self, predicate: ConfigVarValidatorPredicate) -> Self { + self.predicates.push(predicate); + self + } + + pub(crate) fn run(&self, value: &Value) -> LocalResult<()> { + self.predicates + .iter() + .try_for_each(|predicate| predicate(value)) + } +} + +impl Default for ConfigVarValidator { + fn default() -> Self { + Self::when(|_| Ok(())) + } +} + +impl Debug for ConfigVarValidator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ConfigVarValidator {{ predicates: vec![{}] }}", + self.predicates + .iter() + .map(|p| format!("{:p}", p)) + .collect::>() + .join(", ") + ) + } +} + +#[derive(Debug, Clone)] +pub struct ConfigVar { + key: String, + description: String, + validator: Rc, +} + +impl ConfigVar { + pub(crate) fn from, T: AsRef>( + key: S, + description: T, + validator: ConfigVarValidator, + ) -> Self { + Self { + key: key.as_ref().to_string(), + description: description.as_ref().to_string(), + validator: Rc::new(validator), + } + } + + pub fn key(&self) -> &String { + &self.key + } + + pub fn description(&self) -> &String { + &self.description + } + + pub fn validate(&self, value: &Value) -> LocalResult<()> { + self.validator.run(value) + } +} + +#[derive(Debug)] +pub enum InvalidConfigVar { + Missing(ConfigVar, Box), + Incorrect(ConfigVar, Box), +} + +pub struct Config { + /// DO NOT USE DIRECTLY. use [`Config::get`] instead. + figment: Figment, + profile: String, // since figment doesn't want to work + required: Vec, +} + +impl Config { + pub fn from, S: AsRef>( + path: P, + profile: S, + ) -> LocalResult { + use figment::providers::Toml; + let toml = Toml::file(path); + Ok(Self { + figment: Figment::from(toml), + profile: profile.as_ref().to_string(), + required: Vec::new(), + }) + } + + pub fn get>(&self, key: S) -> LocalResult { + self.figment + .find_value(&self.fix_key(key)) + .map_err(Into::into) + } + + pub fn require, T: AsRef, V: Into>( + &mut self, + key: S, + default: Option, + description: T, + validator: ConfigVarValidator, + ) { + if let Some(default) = default { + if self.get(key.as_ref()).is_err() { + self.set(&key, default); + } + } + self.required + .push(ConfigVar::from(key, description, validator)); + } + + pub(crate) fn doctor(&self) -> LocalResult<()> { + let mut errors = vec![]; + for required_key in &self.required { + match self.get(&required_key.key) { + Ok(value) => { + if let Err(error) = required_key.validate(&value) { + errors.push(InvalidConfigVar::Incorrect( + required_key.clone(), + Box::new(error), + )) + } + } + Err(error) => errors.push(InvalidConfigVar::Missing( + required_key.clone(), + Box::new(error), + )), + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(LocalError::InvalidConfig(errors)) + } + } + + pub(crate) fn set, V: Into>( + &mut self, + key: S, + value: V, + ) { + let new_figment = std::mem::take(&mut self.figment); + self.figment = + new_figment.join((self.fix_key(key.as_ref()), value.into())); + } + + fn fix_key>(&self, key: S) -> String { + format!("{}.{}", self.profile, key.as_ref()) + } +} diff --git a/tools/tb/src/driver.rs b/tools/tb/src/driver.rs new file mode 100644 index 0000000000..9859003986 --- /dev/null +++ b/tools/tb/src/driver.rs @@ -0,0 +1,176 @@ +use std::{ + collections::HashMap, + io, + path::{Path, PathBuf}, +}; + +use crate::{ + builtin_plugins::{calyx::CalyxTB, cocotb::CocoTB, verilator::Verilator}, + cli::ConfigSet, + config::Config, + error::{LocalError, LocalResult}, + plugin::{Plugin, PluginCreate, PluginRef}, +}; +use libloading::{Library, Symbol}; +use semver::VersionReq; +use tempdir::TempDir; + +#[derive(Default)] +pub struct Driver { + plugins: HashMap, + loaded_libraries: Vec, +} + +impl Driver { + pub fn load(plugin_dirs: &[PathBuf]) -> LocalResult { + let mut new_self = Self::default(); + + let cocotb = Box::new(CocoTB); + let verilator = Box::new(Verilator); + let calyx = Box::new(CalyxTB); + new_self.register(cocotb.name(), cocotb); + new_self.register(verilator.name(), verilator); + new_self.register(calyx.name(), calyx); + + for plugin_dir in plugin_dirs { + match plugin_dir.read_dir().map_err(LocalError::from) { + Ok(library_paths) => { + for library_path in library_paths { + let library_path = + library_path.map_err(LocalError::from)?.path(); + if library_path.is_file() + && library_path + .extension() + .map(|e| e == "so" || e == "dylib") + .unwrap_or_default() + { + let library = + unsafe { Library::new(&library_path).unwrap() }; + new_self.load_plugin(&library_path, library)?; + } + } + } + Err(error) => { + log::warn!( + "Error processing plugin directory {}: {}", + plugin_dir.to_string_lossy(), + error + ) + } + } + } + Ok(new_self) + } + + pub fn register>(&mut self, name: S, tb: PluginRef) { + assert!( + self.plugins.insert(name.as_ref().to_string(), tb).is_none(), + "cannot re-register the same testbench name for a different testbench" + ); + } + + fn load_plugin( + &mut self, + path: &Path, + library: Library, + ) -> LocalResult<()> { + // todo: better way to do this + let req = + VersionReq::parse(&format!(">={}", env!("CARGO_PKG_VERSION"))) + .unwrap(); + + let create_plugin: Symbol = + unsafe { library.get(b"_plugin_create") }.map_err(|_| { + LocalError::other(format!( + "Plugin '{}' must `declare_plugin!`.", + extract_plugin_name(path) + )) + })?; + let boxed_raw = unsafe { create_plugin() }; + let plugin = unsafe { Box::from_raw(boxed_raw) }; + let plugin_version = plugin.version(); + if !req.matches(&plugin_version) { + log::warn!("Skipping loading {} because its version ({}) is not compatible with {}", plugin.name(), plugin_version, req); + return Ok(()); + } + self.register(plugin.name(), plugin); + self.loaded_libraries.push(library); + Ok(()) + } + + pub fn run, P: AsRef>( + &self, + name: S, + config_path: P, + config_sets: Vec, + input: String, + tests: &[String], + ) -> LocalResult<()> { + if let Some(plugin) = self.plugins.get(name.as_ref()) { + let work_dir = + TempDir::new(".calyx-tb").map_err(LocalError::from)?; + let mut config = Config::from(config_path, name)?; + for config_set in config_sets { + config.set(config_set.key, config_set.value); + } + let input = + copy_into(input, &work_dir).map_err(LocalError::from)?; + let mut test_basenames = vec![]; + for test in tests { + test_basenames.push( + copy_into(test, &work_dir).map_err(LocalError::from)?, + ); + } + plugin.setup(&mut config)?; + config.doctor()?; + plugin.run(input, &test_basenames, work_dir, &config) + } else { + Err(LocalError::Other(format!( + "Unknown testbench '{}'", + name.as_ref() + ))) + } + } +} + +fn copy_into>(file: S, work_dir: &TempDir) -> io::Result { + let from_path = PathBuf::from(file.as_ref()); + let basename = from_path + .file_name() + .ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "path ended with '..'") + })? + .to_str() + .ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "invalid unicode") + })? + .to_string(); + let to_path = work_dir.path().join(&basename); + + if from_path.is_dir() { + fs_extra::dir::copy( + from_path, + work_dir.path(), + &fs_extra::dir::CopyOptions::new(), + ) + .map_err(io::Error::other)?; + } else { + fs_extra::file::copy( + from_path, + to_path, + &fs_extra::file::CopyOptions::new(), + ) + .map_err(io::Error::other)?; + } + + Ok(basename) +} + +fn extract_plugin_name(path: &Path) -> &str { + let stem = path + .file_stem() + .expect("invalid library path") + .to_str() + .expect("invalid unicode"); + stem.strip_prefix("lib").unwrap_or(stem) +} diff --git a/tools/tb/src/error.rs b/tools/tb/src/error.rs new file mode 100644 index 0000000000..3f34330505 --- /dev/null +++ b/tools/tb/src/error.rs @@ -0,0 +1,64 @@ +use crate::config::InvalidConfigVar; +use std::{fmt::Display, io}; + +#[derive(Debug)] +pub enum LocalError { + IO(io::Error), + Figment(figment::Error), + InvalidConfig(Vec), + Other(String), +} + +impl LocalError { + pub fn other>(msg: S) -> Self { + Self::Other(msg.as_ref().to_string()) + } +} + +impl From for LocalError { + fn from(value: io::Error) -> Self { + Self::IO(value) + } +} + +impl From for LocalError { + fn from(value: figment::Error) -> Self { + Self::Figment(value) + } +} + +impl Display for LocalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LocalError::IO(io_err) => io_err.fmt(f), + LocalError::Figment(figment_err) => figment_err.fmt(f), + LocalError::InvalidConfig(errors) => { + writeln!(f, "We detected some errors in your config:")?; + for error in errors { + match error { + InvalidConfigVar::Missing(config_var, _) => { + writeln!( + f, + "- missing key '{}': {}", + config_var.key(), + config_var.description() + )?; + } + InvalidConfigVar::Incorrect(config_var, error) => { + writeln!( + f, + "- incorrect key '{}': {}", + config_var.key(), + error + )?; + } + } + } + Ok(()) + } + LocalError::Other(msg) => msg.fmt(f), + } + } +} + +pub type LocalResult = Result; diff --git a/tools/tb/src/lib.rs b/tools/tb/src/lib.rs new file mode 100644 index 0000000000..e943f2b7bd --- /dev/null +++ b/tools/tb/src/lib.rs @@ -0,0 +1,11 @@ +//! Author: Ethan Uppal + +pub mod builtin_plugins; +pub mod cli; +pub mod config; +pub mod driver; +pub mod error; +pub mod plugin; + +pub use semver; +pub use tempdir; diff --git a/tools/tb/src/main.rs b/tools/tb/src/main.rs new file mode 100644 index 0000000000..2b5e453810 --- /dev/null +++ b/tools/tb/src/main.rs @@ -0,0 +1,72 @@ +use std::{ + env, + io::{self, Write}, + path::PathBuf, +}; +use tb::{ + cli::CLI, + driver::Driver, + error::{LocalError, LocalResult}, +}; + +const CONFIG_FILE_NAME: &str = "calyx-tb.toml"; + +fn setup_logging() { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "warn"); + } + if env::var("NO_COLOR").is_err() { + env::set_var("RUST_LOG_STYLE", "always"); + } + + env_logger::builder().format_target(false).init(); +} + +fn run_app(args: CLI) -> LocalResult<()> { + let config_path = match args.config { + Some(config_path) => config_path, + None => { + log::info!( + "No config file specified, using default: {}", + CONFIG_FILE_NAME + ); + let mut config_path = + PathBuf::from(env::var("HOME").expect("user has no $HOME :(")); + config_path.push(".config"); + config_path.push(CONFIG_FILE_NAME); + config_path + } + }; + + if !config_path.exists() { + return Err(LocalError::other(format!( + "missing config file {}", + config_path.to_string_lossy() + ))); + } + + let default_loc = { + let mut default_loc = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + default_loc.push("plugins"); + default_loc + }; + let driver = Driver::load(&[default_loc])?; + driver.run(args.using, config_path, args.set, args.input, &args.tests) +} + +fn main() -> io::Result<()> { + setup_logging(); + + let args = CLI::from_env(); + + if args.version { + println!("{}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + + if let Err(error) = run_app(args) { + write!(&mut io::stderr(), "{}", error)?; + } + + Ok(()) +} diff --git a/tools/tb/src/plugin.rs b/tools/tb/src/plugin.rs new file mode 100644 index 0000000000..a5471102db --- /dev/null +++ b/tools/tb/src/plugin.rs @@ -0,0 +1,42 @@ +use crate::{config::Config, error::LocalResult}; +use semver::Version; +use tempdir::TempDir; + +pub trait Plugin: Send + Sync { + /// A unique name for this plugin. + fn name(&self) -> &'static str; + + /// The version of tb this plugin was built for. + fn version(&self) -> Version; + + /// Declares the configuration for this plugin. + fn setup(&self, config: &mut Config) -> LocalResult<()>; + + /// Runs this plugin's testbench. + /// - `input` is a relative path to the input file in `work_dir`. + /// - `tests` are a relative paths to the testing harnesses in `work_dir`. + fn run( + &self, + input: String, + tests: &[String], + work_dir: TempDir, + config: &Config, + ) -> LocalResult<()>; +} + +pub type PluginRef = Box; + +// https://www.michaelfbryan.com/rust-ffi-guide/dynamic_loading.html +pub type PluginCreate = unsafe fn() -> *mut dyn Plugin; + +/// `declare_plugin!(MyPlugin, MyPlugin::constructor)` exposes `MyPlugin` to the +/// world as constructable by the zero-arity `MyPlugin::constructor`. +#[macro_export] +macro_rules! declare_plugin { + ($plugin_type:ty, $constructor:path) => { + #[no_mangle] + pub extern "C" fn _plugin_create() -> *mut dyn $crate::plugin::Plugin { + Box::into_raw(Box::new($constructor())) + } + }; +}