Repository: calyxir/calyx (language: rust)

Issue:
Add Verilog reserved word: `wait`
Here's an example program, where we name a cell `wait`:
```
# making a cell named "wait"
import "primitives/core.futil";

component main() -> () {
  cells {
    wait = std_reg(32);
    add = std_add(32);
  }
  wires {
   static<1> group write {
     add.left = wait.out;
     add.right = 32'd1;
     wait.write_en = 1'b1;
     wait.in = add.out;
   }
  }
  control {
      write;
  }
}
```
Running this with `fud exec <file_name>.futil --to dat --through verilog` yields
```
[fud] ERROR: `/opt/verilator/bin/verilator --trace /tmp/tmpcsy0je3b /scratch/ad739/calyx/fud/icarus/tb.sv --binary --top-module toplevel --Mdir /tmp/tmpq176rkir -fno-inline>&2' failed:
=====STDERR=====
%Error: /tmp/tmpcsy0je3b:355:3: syntax error, unexpected wait, expecting IDENTIFIER or randomize
  355 | ) wait (
      |   ^~~~
%Error: Exiting due to 1 error(s)

=====STDOUT=====
```
Perhaps this kind of code should produce a Calyx error instead of a Verilator one? This way the Calyx isn't producing malformed Verilog code.
Floating-point conversion error
There is a bug in the tests provided by https://github.com/calyxir/calyx/pull/2318.

[tests/correctness/ieee754-float/parse-and-print.futil.data](https://github.com/calyxir/calyx/pull/2318/files#diff-e4146a309a9876672f57eb53e44b7c9042a5a222c53eb6894ff48df0c6a950fd) provides the input value `15.1239`. However, at the end of the test, the value has changed to `15.12` in [tests/correctness/ieee754-float/parse-and-print.expect](https://github.com/calyxir/calyx/pull/2318/files#diff-5d85d972c197f71bd7796802102b6e470a03f2b8cf64f277a0f8208dffcaca99).

This is even though a 32-bit IEEE float should be able to represent `15.1239`. So there appears to be a bug, most likely in how the data is converted back after the simulation.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 1fcd4e4769..c31a656d1a 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,15 +17,17 @@ jobs:
       - name: Setup mdBook
         uses: peaceiris/actions-mdbook@v1
         with:
-          mdbook-version: 'latest'
+          mdbook-version: "latest"
+      - name: Install callouts preprocessor
+        run: cargo install --git https://github.com/ToolmanP/rs-mdbook-callouts --rev 83898e352a961fc65044e04c864141c8b5481722
       - name: mdbook
         run: mdbook build
       - name: Install Rust stable
         uses: actions-rs/toolchain@v1
         with:
-            toolchain: 1.66.0
-            override: true
-            components: rustfmt, clippy
+          toolchain: 1.66.0
+          override: true
+          components: rustfmt, clippy
       - name: Build source documentation
         uses: actions-rs/cargo@v1
         with:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 421a82a0bb..1a9f9f5963 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -123,7 +123,7 @@ jobs:
       working-directory: /home/calyx/interp/tests
       run: |
         # Run the remaining tests
-        runt -x '(numeric types correctness and parsing)|(tcam testing)|(../../tests/correctness/pipelined-mac.futil)' -d -o fail
+        runt -x '(numeric types correctness and parsing)' -d -o fail
 
     - name: Source code tests
       uses: actions-rs/cargo@v1
diff --git a/Cargo.lock b/Cargo.lock
index 1fd61fa404..7eba776379 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,54 +66,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "anstream"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
-dependencies = [
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
-dependencies = [
- "anstyle",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "anyhow"
 version = "1.0.80"
@@ -139,7 +91,7 @@ dependencies = [
  "argh_shared",
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -175,7 +127,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -206,7 +158,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -217,14 +169,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "baa"
-version = "0.6.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfafaa7f46ae31982d3e55dad223ebb884a5da626aba403c66673ce9d1831f53"
+checksum = "9aca4f2e283a3de6f3c40834fc27668303e33662676e90254b5168271209ef95"
 dependencies = [
  "fraction",
  "num-bigint",
  "serde",
- "smallvec",
 ]
 
 [[package]]
@@ -248,29 +199,6 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
-[[package]]
-name = "bindgen"
-version = "0.68.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
-dependencies = [
- "bitflags 2.4.2",
- "cexpr",
- "clang-sys",
- "lazy_static",
- "lazycell",
- "log",
- "peeking_take_while",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn 2.0.52",
- "which",
-]
-
 [[package]]
 name = "bit-set"
 version = "0.5.3"
@@ -320,35 +248,26 @@ dependencies = [
 ]
 
 [[package]]
-name = "btor2i"
-version = "0.1.0"
-dependencies = [
- "bitvec",
- "btor2tools",
- "clap 4.4.18",
- "num-bigint",
- "num-integer",
- "num-traits",
- "tempfile",
- "thiserror",
-]
-
-[[package]]
-name = "btor2tools"
-version = "1.1.0"
-source = "git+https://github.com/obhalerao/btor2tools.rs#a01b2ebc85ee4489860069b196b2ab886d06f43a"
+name = "bon"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869"
 dependencies = [
- "btor2tools-sys",
- "thiserror",
+ "bon-macros",
+ "rustversion",
 ]
 
 [[package]]
-name = "btor2tools-sys"
-version = "1.1.0"
-source = "git+https://github.com/obhalerao/btor2tools-sys#e5d1c44220cb5a02b30b538c5ade70102109904a"
+name = "bon-macros"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663"
 dependencies = [
- "bindgen",
- "copy_dir",
+ "darling 0.20.10",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -554,15 +473,6 @@ version = "1.0.88"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
 
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom 7.1.3",
-]
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -634,6 +544,7 @@ dependencies = [
  "argh",
  "interp",
  "itertools 0.11.0",
+ "nom 7.1.3",
  "num-bigint",
  "num-rational",
  "num-traits",
@@ -643,17 +554,6 @@ dependencies = [
  "thiserror",
 ]
 
-[[package]]
-name = "clang-sys"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
-dependencies = [
- "glob",
- "libc",
- "libloading",
-]
-
 [[package]]
 name = "clap"
 version = "2.34.0"
@@ -665,46 +565,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "clap"
-version = "4.4.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
-dependencies = [
- "clap_builder",
- "clap_derive",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.4.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.52",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
-
 [[package]]
 name = "clipboard-win"
 version = "4.5.0"
@@ -716,12 +576,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "colorchoice"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
-
 [[package]]
 name = "component_cells"
 version = "0.7.1"
@@ -777,15 +631,6 @@ dependencies = [
  "tiny-keccak",
 ]
 
-[[package]]
-name = "copy_dir"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3"
-dependencies = [
- "walkdir",
-]
-
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.6"
@@ -815,7 +660,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
 dependencies = [
  "atty",
  "cast",
- "clap 2.34.0",
+ "clap",
  "criterion-plot",
  "csv",
  "itertools 0.10.5",
@@ -961,12 +806,12 @@ dependencies = [
 
 [[package]]
 name = "darling"
-version = "0.20.8"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
 dependencies = [
- "darling_core 0.20.8",
- "darling_macro 0.20.8",
+ "darling_core 0.20.10",
+ "darling_macro 0.20.10",
 ]
 
 [[package]]
@@ -979,22 +824,22 @@ dependencies = [
  "ident_case",
  "proc-macro2",
  "quote",
- "strsim",
+ "strsim 0.10.0",
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "darling_core"
-version = "0.20.8"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
 dependencies = [
  "fnv",
  "ident_case",
  "proc-macro2",
  "quote",
- "strsim",
- "syn 2.0.52",
+ "strsim 0.11.1",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -1010,13 +855,13 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.20.8"
+version = "0.20.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
- "darling_core 0.20.8",
+ "darling_core 0.20.10",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -1356,7 +1201,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -1425,12 +1270,6 @@ version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
 [[package]]
 name = "half"
 version = "1.8.3"
@@ -1498,15 +1337,6 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
-[[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 = "httparse"
 version = "1.8.0"
@@ -1623,7 +1453,7 @@ dependencies = [
  "argh",
  "baa",
  "bitvec",
- "btor2i",
+ "bon",
  "calyx-frontend",
  "calyx-ir",
  "calyx-opt",
@@ -1703,28 +1533,12 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
-[[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.153"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
 
-[[package]]
-name = "libloading"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
-dependencies = [
- "cfg-if",
- "windows-targets 0.52.4",
-]
-
 [[package]]
 name = "libm"
 version = "0.2.8"
@@ -1801,7 +1615,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2045,12 +1859,6 @@ dependencies = [
  "camino",
 ]
 
-[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
 [[package]]
 name = "percent-encoding"
 version = "2.3.1"
@@ -2110,7 +1918,7 @@ dependencies = [
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2151,7 +1959,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2218,16 +2026,6 @@ dependencies = [
  "unicode-segmentation",
 ]
 
-[[package]]
-name = "prettyplease"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5"
-dependencies = [
- "proc-macro2",
- "syn 2.0.52",
-]
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.78"
@@ -2486,7 +2284,7 @@ checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2495,12 +2293,6 @@ version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
 
-[[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.31"
@@ -2603,7 +2395,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2625,7 +2417,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2693,10 +2485,10 @@ version = "3.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d"
 dependencies = [
- "darling 0.20.8",
+ "darling 0.20.10",
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2719,12 +2511,6 @@ dependencies = [
  "lazy_static",
 ]
 
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
 [[package]]
 name = "signal-hook"
 version = "0.3.17"
@@ -2860,6 +2646,12 @@ 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"
@@ -2876,7 +2668,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -2919,9 +2711,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.52"
+version = "2.0.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3014,7 +2806,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3117,7 +2909,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3219,7 +3011,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3247,7 +3039,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
 
 [[package]]
@@ -3508,18 +3300,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[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"
@@ -3755,5 +3535,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.52",
+ "syn 2.0.58",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index d509d9a1fd..be30dce2d8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,6 @@ members = [
     "fud2",
     "fud2/fud-core",
     "tools/data-conversion",
-    "tools/btor2/btor2i",
     "tools/calyx-pass-explorer",
     "tools/cider-data-converter",
     "tools/component_cells",
diff --git a/book.toml b/book.toml
index 3f9f2ad6e9..3537fbb393 100644
--- a/book.toml
+++ b/book.toml
@@ -5,4 +5,6 @@ multilingual = false
 src = "docs"
 title = "Calyx Documentation"
 [output.html]
-mathjax-support = true
\ No newline at end of file
+mathjax-support = true
+[preprocessor.callouts] # https://github.com/ToolmanP/rs-mdbook-callouts
+
diff --git a/calyx-ir/src/reserved_names.rs b/calyx-ir/src/reserved_names.rs
index 721eb1709e..9c73ef60ae 100644
--- a/calyx-ir/src/reserved_names.rs
+++ b/calyx-ir/src/reserved_names.rs
@@ -137,4 +137,6 @@ pub const RESERVED_NAMES: &[&str] = &[
     "wor",
     "xnor",
     "xor",
+    "wait",
+    "break",
 ];
diff --git a/calyx-opt/src/passes/profiler_instrumentation.rs b/calyx-opt/src/passes/profiler_instrumentation.rs
index 829861ec91..631cdb3eb3 100644
--- a/calyx-opt/src/passes/profiler_instrumentation.rs
+++ b/calyx-opt/src/passes/profiler_instrumentation.rs
@@ -44,18 +44,22 @@ impl Visitor for ProfilerInstrumentation {
             .iter()
             .map(|group| group.borrow().name())
             .collect::<Vec<_>>();
+        let comp_name = comp.name;
         // for each group, construct a instrumentation cell and instrumentation assignment
         let mut asgn_and_cell = Vec::with_capacity(group_names.len());
         {
             let mut builder = ir::Builder::new(comp, sigs);
             let one = builder.add_constant(1, 1);
             for group_name in group_names.into_iter() {
-                let name = format!("{}_probe", group_name);
+                // store group and component name (differentiate between groups of the same name under different components)
+                let name = format!("{}__{}_probe", group_name, comp_name);
                 let inst_cell = builder.add_primitive(name, "std_wire", &[1]);
                 let asgn: [ir::Assignment<ir::Nothing>; 1] = build_assignments!(
                     builder;
                     inst_cell["in"] = ? one["out"];
                 );
+                // the probes should be @control because they should have value 0 whenever the corresponding group is not active.
+                inst_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
                 inst_cell.borrow_mut().add_attribute(BoolAttr::Protected, 1);
                 asgn_and_cell.push((asgn[0].clone(), inst_cell));
             }
diff --git a/calyx-py/calyx/numeric_types.py b/calyx-py/calyx/numeric_types.py
index da5c901ed0..87a569ad36 100644
--- a/calyx-py/calyx/numeric_types.py
+++ b/calyx-py/calyx/numeric_types.py
@@ -7,6 +7,7 @@
 from decimal import Decimal, getcontext
 import math
 import logging as log
+import struct
 
 
 class InvalidNumericType(Exception):
@@ -335,3 +336,34 @@ def bitnum_to_fixed(bitnum: Bitnum, int_width: int) -> FixedPoint:
         int_width=int_width,
         is_signed=bitnum.is_signed,
     )
+
+
+@dataclass
+class IEEE754Float(NumericType):
+    """Represents a floating point number."""
+
+    def __init__(self, value: str, width: int, is_signed: bool):
+        super().__init__(value, width, is_signed)
+
+        assert width in [32, 64], "Floating point numbers must be either 32 or 64 bits."
+
+        if self.bit_string_repr is None and self.hex_string_repr is None:
+            # The decimal representation was passed in.
+            packed = struct.pack("!f", float(self.string_repr))
+            unpacked = struct.unpack(">I", packed)[0]
+            self.bit_string_repr = f"{unpacked:0{self.width}b}"
+            self.uint_repr = int(self.bit_string_repr, 2)
+            self.hex_string_repr = np.base_repr(self.uint_repr, 16)
+
+    def as_str(self):
+        float_value = struct.unpack(
+            "!f", int(self.bit_string_repr, 2).to_bytes(4, byteorder="big")
+        )[0]
+        if self.width == 32:
+            return str(np.float32(float_value))
+        elif self.width == 64:
+            return str(np.float64(float_value))
+        else:
+            raise InvalidNumericType(
+                f"Unsupported width: {self.width} for floating point."
+            )
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 55a50136b9..fd7f126fdc 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -60,6 +60,7 @@
 - [`exp` Generator](./tools/exp-generator.md)
 - [Editor Highlighting](./tools/editor-highlighting.md)
 - [Language Server](./tools/language-server.md)
+- [Visualizing Compiler Passes](./dev/calyx-pass-explorer.md)
 
 ----
 [Contributors](./contributors.md)
diff --git a/docs/contributors.md b/docs/contributors.md
index 285c9de034..13a5262b97 100644
--- a/docs/contributors.md
+++ b/docs/contributors.md
@@ -15,6 +15,7 @@ Here is a list of all the people who have worked on Calyx:
 - Andrew Butt
 - [Anshuman Mohan](https://www.cs.cornell.edu/~amohan/)
 - [Ayaka Yorihiro](https://ayakayorihiro.github.io/)
+- [Ethan Uppal](https://www.ethanuppal.com)
 
 **Previous Contributors**
 
diff --git a/docs/dev/calyx-pass-explorer.md b/docs/dev/calyx-pass-explorer.md
new file mode 100644
index 0000000000..8ff04e6016
--- /dev/null
+++ b/docs/dev/calyx-pass-explorer.md
@@ -0,0 +1,121 @@
+# Visualizing Compiler Passes
+
+Working on the compiler can be daunting, especially when there are lots of
+complicated (and simple) passes that turn your original calyx source into
+something hardly recognizable.
+Sometimes the best way to learn how a pass works (or to debug an existing pass)
+is just to run it on code and see what happens.
+
+Enter [`calyx-pass-explorer`](https://github.com/calyxir/calyx/tree/main/tools/calyx-pass-explorer).
+It's a command line tool that provides an interactive interface for visualizing
+how different passes affect the source code.
+
+> ![Example running of the tool](https://raw.githubusercontent.com/calyxir/calyx/main/tools/calyx-pass-explorer/example_v0.0.0.png)
+> _The above image depicts the tool's interface in v0.0.0.
+> As of writing this documentation, it's on v0.2.1._
+
+Check out the [source code](https://github.com/calyxir/calyx/tree/main/tools/calyx-pass-explorer/src) if you're interested.
+
+## Usage
+
+> Take a look at the [user manual](https://github.com/calyxir/calyx/blob/main/tools/calyx-pass-explorer/manual.md) for detailed information.
+> This section will serve as a basic overview.
+
+First, we have to get a calyx file to work with.
+Let's use this one:
+
+```
+import "primitives/core.futil";
+import "primitives/memories/comb.futil";
+
+component main(@go go: 1) -> (@done done: 1) {
+  cells {
+    @external(1) in_mem = comb_mem_d1(32, 1, 32);
+    @external(1) out_mem = comb_mem_d1(32, 1, 32);
+    i0 = std_reg(32);
+  }
+  wires {
+    group d1 {
+      in_mem.addr0 = 32'd0;
+      i0.in = in_mem.read_data;
+      i0.write_en = 1'b1;
+      d1[done] = i0.done;
+    }
+    static<1> group s2 {
+      in_mem.addr0 = 32'd0;
+      out_mem.write_data = i0.out;
+      out_mem.write_en = 1'b1;
+    }
+  }
+  control {
+    seq {
+      seq {
+        d1;
+        s2;
+      }
+    }
+  }
+}
+```
+
+It's a simple program that reads in a value from the memory `in_mem` into the register
+`i0`, and then from `i0` into the memory `out_mem`.
+Incidentally, I used this file to test my implementation of the
+[`@fast` attribute](https://github.com/calyxir/calyx/pull/2118); I wrote this
+tool to help develop it!
+
+We'll first run `calyx-pass-explorer example0.futil`.
+You should get something horrific like
+
+![Lots of random text output that doesn't make sense](./assets/horrific-interface.png)
+
+> [!TIP]
+> If you get this message:
+> ![Calyx executable could not be found](./assets/calyx-missing.png)
+> You should setup `fud` or pass the path explicitly with `-e`, as suggested.
+> However, we're going to update this later to look at `fud2` as well because
+> `fud` is now officially deprecated.
+
+The first few lines are readable, but after that, there's a lot of calyx
+standard library components we don't really care too much about.
+What we really want is to focus on what happens to, _e.g._, the `main` component.
+To do that, we just pass `-c main` (or `--component main`) as a flag:
+
+![Running the tool and visualizing how the well-formed pass affects the main
+component](./assets/well-formed.png)
+
+That's a lot better, but it's still quite a bit of information.
+Let's break it down.
+
+- At the top, we have the instructions.
+  You primary use the keyboard to interact with the tool (but you can scroll through the
+text with a mouse if there's a lot of it).
+- Then, we have the list of passes.
+  Yellow passes are incoming passes: these are the ones for which the diff is
+shown.
+  In other words, we're seeing the result of applying the `well-formed` pass to
+  the original input.
+  Purple passes are upcoming -- they can be applied after we apply the current
+one.
+  Green passes are those we've already applied, and gray ones are those we've
+skipped.
+- Finally, we have the diff of the incoming pass applied to the current code.
+  In particular, the code hasn't been edited yet -- it won't be, until we press
+  `a` to accept.
+
+### Breakpoints
+
+Often, you're interested in one pass that is far into the set of passes.
+There are two options to help you do that:
+
+1. `-b` or `--breakpoint` takes in a pass name and lets you automatically accept
+   (see next option) passes until you arrive at the specified pass.
+2. `-d` or `--disable-pass` skips a pass when reaching a breakpoint.
+   You can pass this option multiple times.
+
+## How it works
+
+The tool creates a temporary directory where it stores the results of applying
+passes.
+The [`PassExplorer`](https://github.com/calyxir/calyx/blob/main/tools/calyx-pass-explorer/src/pass_explorer.rs) `struct` handles how passes are applied and updates this directory's contents.
+I wrote a custom [scrollback buffer](https://github.com/calyxir/calyx/blob/main/tools/calyx-pass-explorer/src/scrollback_buffer.rs) to accommodate the specific its TUI needs.
diff --git a/docs/github.md b/docs/github.md
index 013c48ec08..bb303bb3c3 100644
--- a/docs/github.md
+++ b/docs/github.md
@@ -1,9 +1,11 @@
 # Contributing to Calyx
 
 ## Github Workflow
+
 The current home of the Calyx repo can be found [here][calyx_repo]. As with many
 large projects, we protect the main branch of the repo so that updates can only
 be made via pull requests. So the development cycle tends to look like:
+
 ```
 checkout main -> develop code -> open PR -> revise -> merge PR
 ```
@@ -20,7 +22,12 @@ there will be extensive merge conflicts due to the squash and merge tactic. For
 this reason we always recommend creating branches off of the main branch if you
 intend to have them merged into it.
 
+## Local Development
+
+Once you've [setup a local installation](./intro.md) for contributing, you can setup git hooks with `/bin/sh setup_hooks.sh`.
+
 ### CI Behavior
+
 The CI runs a number of tests including ensuring that Rust and Python code has
 been formatted. For Python we use the [Black](https://github.com/psf/black) formatter and for Rust we use the
 standard `cargo fmt`.
@@ -33,6 +40,5 @@ within Rust to suppress the lint.
 If changes are made to the `Dockerfile` then the CI will automatically rebuild
 the Docker image and run your tests on it.
 
-
 [calyx_repo]: https://github.com/calyxir/calyx
 [clippy]: https://github.com/rust-lang/rust-clippy
diff --git a/docs/install_tools.sh b/docs/install_tools.sh
new file mode 100644
index 0000000000..b584270e22
--- /dev/null
+++ b/docs/install_tools.sh
@@ -0,0 +1,4 @@
+#/bin/sh
+
+cargo install mdbook
+cargo install --git https://github.com/ToolmanP/rs-mdbook-callouts --rev 83898e352a961fc65044e04c864141c8b5481722
diff --git a/docs/intro.md b/docs/intro.md
index 447f2cd20c..d2ae5a166d 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -31,9 +31,11 @@ First, install [Rust][rust].
 This should automatically install `cargo`.
 
 If you want just to play with the compiler, install the [`calyx` crate][calyx-crate]:
+
 ```
 cargo install calyx
 ```
+
 This will install the `calyx` binary which can optimize and compile Calyx programs. You will still need the [`primitives/core.futil`][core-lib] and [its accompanying Verilog file](https://github.com/calyxir/calyx/blob/master/primitives/core.sv) library to compile most programs.
 
 ### Installing from Source (to use and extend Calyx)
@@ -42,29 +44,43 @@ First, install [Rust][rust].
 This should automatically install `cargo`.
 
 Clone the repository:
+
 ```
 git clone https://github.com/calyxir/calyx.git
 ```
+
 Then build the compiler:
+
 ```
 cargo build
 ```
 
 You can build and run the compiler with:
+
 ```
 cargo build # Builds the compiler
 ./target/debug/calyx --help # Executes the compiler binary
 ```
 
 We recommend installing the git hooks to run linting and formatting checks before each commit:
+
 ```shell
 /bin/sh setup_hooks.sh
 ```
 
+You can build the docs by installing `mdbook` and the callouts preprocessor:
+
+```sh
+/bin/sh docs/install_tools.sh
+```
+
+Then, run `mdbook serve` from the project root.
+
 ## Running Core Tests
 
 The core test suite tests the Calyx compiler's various passes.
 Install the following tools:
+
   1. [runt][] hosts our testing infrastructure. Install with:
   `cargo install runt`
   2. [jq][] is a command-line JSON processor. Install with:
@@ -73,10 +89,13 @@ Install the following tools:
      * Other platforms: [JQ installation][jq-install]
 
 Build the compiler:
+
 ```
 cargo build
 ```
+
 Then run the core tests with:
+
 ```
 runt -i core
 ```
@@ -91,24 +110,31 @@ backends to simplify running Calyx programs.
 Start at the root of the repository.
 
 Install [Flit][]:
+
 ```
 pip3 install flit
 ```
 
 Install [`calyx-py`](builder/calyx-py.md):
+
 ```
 cd calyx-py && flit install -s && cd -
 ```
 
 Install `fud`:
+
 ```
 flit -f fud/pyproject.toml install -s --deps production
 ```
+
 Configure `fud`:
+
 ```
 fud config --create global.root <full path to Calyx repository>
 ```
+
 Check the `fud` configuration:
+
 ```
 fud check
 ```
@@ -133,12 +159,14 @@ Some missing tools are again expected; just pay attention to the report for `sta
 
 It is worth saying a little about the alternatives.
 You could consider:
+
 1. [Setting up Verilator][fud-verilator] for faster performance, which is good for long-running simulations.
 2. Using the [interpreter][] to avoid RTL simulation altogether.
 
 ## Running a Hardware Design
 
 You're all set to run a Calyx hardware design now. Run the following command:
+
 ```
 fud e examples/tutorial/language-tutorial-iterate.futil \
   -s verilog.data examples/tutorial/data.json \
@@ -155,28 +183,19 @@ Congratulations! You've simulated your first hardware design with Calyx.
 
 ## Where to go next?
 
-- [How can I setup syntax highlighting in my editor?](./tools/editor-highlighting.md)
-- [How does the language work?](./tutorial/language-tut.md)
-- [How do I install Calyx frontends?](./running-calyx/fud/index.html#dahlia-fronted)
-- [Where can I see further examples with `fud`?](./running-calyx/fud/examples.md)
-- [How do I write a frontend for Calyx?](./tutorial/frontend-tut.md)
-
+* [How can I setup syntax highlighting in my editor?](./tools/editor-highlighting.md)
+* [How does the language work?](./tutorial/language-tut.md)
+* [How do I install Calyx frontends?](./running-calyx/fud/index.html#dahlia-fronted)
+* [Where can I see further examples with `fud`?](./running-calyx/fud/examples.md)
+* [How do I write a frontend for Calyx?](./tutorial/frontend-tut.md)
 
 [rust]: https://doc.rust-lang.org/cargo/getting-started/installation.html
 [runt]: https://github.com/rachitnigam/runt
-[vcdump]: https://github.com/sgpthomas/vcdump
 [verilator]: https://www.veripool.org/wiki/verilator
-[verilator-install]: https://www.veripool.org/projects/verilator/wiki/Installing
 [icarus verilog]: http://iverilog.icarus.com
 [jq]: https://stedolan.github.io/jq/
 [jq-install]: https://stedolan.github.io/jq/
-[frontends]: ./frontends/index.md
-[calyx-py]: ./calyx-py.md
 [flit]: https://flit.readthedocs.io/en/latest/
-[vcd]: https://en.wikipedia.org/wiki/Value_change_dump
-[dahlia]: https://github.com/cucapra/dahlia
-[dahlia-install]: https://github.com/cucapra/dahlia#set-it-up
-[sbt]: https://www.scala-sbt.org/download.html
 [interpreter]: ./running-calyx/interpreter.md
 [homebrew]: https://brew.sh
 [fud-icarus]: ./running-calyx/fud/index.md#icarus-verilog
diff --git a/docs/libraries/core.md b/docs/libraries/core.md
index db0f2ad1c6..c1d1499826 100644
--- a/docs/libraries/core.md
+++ b/docs/libraries/core.md
@@ -13,7 +13,7 @@ such as registers and basic bitwise operations.
 
 ---
 
-## Numerical Operators
+## State Elements
 
 ### `std_reg<WIDTH>`
 
@@ -28,11 +28,56 @@ A `WIDTH`-wide register.
 **Outputs:**
 
 - `out: WIDTH` - The value contained in the register.
-- `done: 1` - The register's done signal. Set high for one cycle after writing a
-  new value.
+- `done: 1` - The register's done signal. Set high for one cycle after writing 
+  a new value.
+
+---
+
+### `std_skid_buffer<WIDTH>`
+
+A `WIDTH`-wide non-pipelined skid buffer. Used to ensure data is not lost 
+during handshakes.
+
+**Inputs:**
+
+- `in: WIDTH` - An input value to the skid buffer `WIDTH`-bits.
+- `i_valid: 1` - The one bit input valid signal. Indicates that the data 
+  provided on the `in` wire is valid.
+- `i_ready: 1` - The one bit input ready signal. Indicates that the follower is
+  ready to recieve data from the `out` wire.
+
+**Outputs:**
+
+- `out: WIDTH` - The value contained in the register.
+- `o_valid: 1` - The one bit output valid signal. Indicates that the data 
+  provided on the `out` wire is valid.
+- `o_ready: 1` - The one bit output ready signal. Indicates that the skid buffer
+  is ready to recieve data on the `in` wire.
 
 ---
 
+### `std_bypass_reg<WIDTH>`
+
+A `WIDTH`-wide bypass register.
+
+**Inputs:**
+
+- `in: WIDTH` - An input value to the bypass register `WIDTH`-bits.
+- `write_en: 1` - The one bit write enabled signal. Indicates that the bypass
+  register should store the value on the `in` wire.
+
+**Outputs:**
+
+- `out: WIDTH` - The value of the bypass register. When `write_en` is asserted 
+  the value of `in` is bypassed to `out`. Otherwise `out` is equal to the last 
+  value written to the register.
+- `done: 1` - The bypass register's done signal. Set high for one cycle after
+  `write_en` is asserted.
+
+---
+
+## Numerical Operators
+
 ### `std_const<WIDTH,VAL>`
 
 A constant WIDTH-bit value with value VAL.
diff --git a/docs/new-pass.md b/docs/new-pass.md
index 3c87224e2f..0bfa5aef08 100644
--- a/docs/new-pass.md
+++ b/docs/new-pass.md
@@ -2,6 +2,7 @@
 
 All passes in the compiler are stored in the `calyx/src/passes` directory.
 To add a new pass, we need to do a couple of things:
+
 1. Define a pass struct and implement the required traits.
 2. Expose the pass using in the `passes` module.
 3. Register the pass in the compiler.
@@ -11,6 +12,7 @@ To add a new pass, we need to do a couple of things:
 ## Defining a Pass Struct
 
 We first define a [Rust structure][struct] that will manage the state of the pass:
+
 ```rust
 pub struct NewPass;
 ```
@@ -37,6 +39,7 @@ Furthermore, it also allows us to control the order in which components are visi
 ### Component Iteration Order
 
 The [`Order`][order] struct allows us to control the order in which components are visited:
+
 - `Post`: Iterate the subcomponents of a component before the component itself.
 - `Pre`: Iterate the subcomponents of a component after the component itself.
 - `No`: Iterate the components in any order.
@@ -47,6 +50,7 @@ Most passes will attempt to transform the structural part of the program (`wires
 The `Visitor` trait is flexible enough to allow all of these patterns and efficiently traverse the program.
 
 For a control program like this:
+
 ```
 seq {
     one;
@@ -56,6 +60,7 @@ seq {
 ```
 
 The following sequence of `Visitor` methods are called:
+
 ```
 - start
 - start_seq
@@ -80,11 +85,13 @@ The final step is to register the pass in the compiler.
 We use the [`PassManager`][pass-manager] to register the pass defined in the `default_passes.rs` file.
 
 Registering a pass is as simple as calling the register pass:
+
 ```rust
 pm.register_pass::<NewPass>();
 ```
 
 Once done, the pass is accessible from the command line:
+
 ```bash
 cargo run -- -p new-pass <file>
 ```
@@ -97,11 +104,14 @@ For example, if `NewPass` needs to run before the compilation passes, we can add
 ## Some Useful Links
 
 The compiler has a ton of shared infrastructure that can be useful:
+
 - [`ir::Context`][context]: The top-level data structure that holds a complete Calyx program.
 - [Rewriter][]: Helps with consistent renaming of ports, cells, groups, and comb groups in a component.
 - [`analysis`][analysis]: Provides a number of useful analysis that can be used within a pass.
 - IR macros: Macros useful for adding cells ([`structure!`][structure]), guards ([`guard!`][guard]) and assignments ([`build_assignments!`][build-assigns]) to component.
 
+Also, check out how to [visualize passes](./dev/calyx-pass-explorer.md) for development and debugging.
+
 [pass-opts]: ./compiler.md#providing-pass-options
 [control]: ./lang/ref.md#the-control-operators
 [struct]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html
@@ -115,4 +125,5 @@ The compiler has a ton of shared infrastructure that can be useful:
 [build-assigns]: https://docs.rs/calyx-ir/latest/calyx_ir/macro.build_assignments.html
 [guard]: https://docs.rs/calyx-ir/latest/calyx_ir/macro.guard.html
 [structure]: https://docs.rs/calyx-ir/latest/calyx_ir/macro.structure.html
-[context]: https://docs.rs/calyx-ir/latest/calyx_ir/struct.Context.html
\ No newline at end of file
+[context]: https://docs.rs/calyx-ir/latest/calyx_ir/struct.Context.html
+
diff --git a/docs/tools/sv2btor.md b/docs/tools/sv2btor.md
deleted file mode 100644
index 4544aaa21c..0000000000
--- a/docs/tools/sv2btor.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# `sv2btor` 
-
-The `sv2btor` tool is a tool that leverages `yosys` and
-`verible` to translate the SystemVerilog files into BTOR2. 
-
-# Usage
-
-```bash
-Usage: sv2btor.py <PATH_TO_YOSYS_EXECUTABLE>  <PATH_TO_VERIBLE_VERILOG_SYNTAX>  <OUTPUT_DIR>  <VERILOG_FILE [VERILOG_FILE [...]]>
-```
-
-# Installation
-To run this tool, you need to have `yosys` and `verible-verilog-syntax` installed. You will also need the `anytree` python package.
-
-- You can install `yosys` by following the instructions [here](https://github.com/YosysHQ/yosys).
-- You can install `verible-verilog-syntax` by following the instructions [here](https://github.com/chipsalliance/verible). Note that we only need the standalone `verible-verilog-syntax` executable, the rest of the tools are optional.
-- You can install `anytree` by running `pip install anytree`.
-
diff --git a/fud/fud/stages/futil.py b/fud/fud/stages/futil.py
index 5842cef76a..bc7ca58793 100644
--- a/fud/fud/stages/futil.py
+++ b/fud/fud/stages/futil.py
@@ -22,15 +22,18 @@ def defaults():
         return {}
 
     def known_opts(self):
-        return ["flags", "exec", "file_extensions"]
+        return ["lib_path", "flags", "exec", "file_extensions"]
 
     def _define_steps(self, input, builder, config):
         calyx_exec = config["stages", self.name, "exec"]
+        lib_path = unwrap_or(
+            config.get(("stages", self.name, "lib_path")), config["global", cfg.ROOT]
+        )
         cmd = " ".join(
             [
                 calyx_exec,
                 "-l",
-                config["global", cfg.ROOT],
+                lib_path,
                 self.flags,
                 unwrap_or(config["stages", self.name, "flags"], ""),
             ]
diff --git a/fud/fud/stages/verilator/json_to_dat.py b/fud/fud/stages/verilator/json_to_dat.py
index a988f62ff3..933511e3f0 100644
--- a/fud/fud/stages/verilator/json_to_dat.py
+++ b/fud/fud/stages/verilator/json_to_dat.py
@@ -1,9 +1,11 @@
-import simplejson as sjson
-import numpy as np
-from calyx.numeric_types import FixedPoint, Bitnum, InvalidNumericType
+import logging as log
 from pathlib import Path
+
+import numpy as np
+import simplejson as sjson
+from calyx.numeric_types import Bitnum, FixedPoint, IEEE754Float, InvalidNumericType
+
 from fud.errors import Malformed
-import logging as log
 
 
 def float_to_fixed(value: float, N: int) -> float:
@@ -14,7 +16,7 @@ def float_to_fixed(value: float, N: int) -> float:
     return round(value * w) / float(w)
 
 
-def parse_dat(path, args):
+def parse_dat(path, is_bn, args):
     """Parses a number with the given numeric type
     arguments from the array at the given `path`.
     """
@@ -34,12 +36,15 @@ def parse(hex_value: str):
         hex_value = f"0x{hex_value}"
         if "int_width" in args:
             return FixedPoint(hex_value, **args).str_value()
-        else:
+        elif is_bn:
             bn = Bitnum(hex_value, **args)
             if bn.is_undef:
                 return bn.str_value()
             else:
                 return int(bn.str_value())
+        else:
+            fp = IEEE754Float(hex_value, **args)
+            return float(fp.as_str())
 
     with path.open("r") as f:
         lines = []
@@ -90,12 +95,17 @@ def provided(x, y):
         )
 
 
-def convert(x, round: bool, is_signed: bool, width: int, int_width=None):
+def convert(x, round: bool, is_signed: bool, width: int, is_bn: bool, int_width=None):
     with_prefix = False
+
     # If `int_width` is not defined, then this is a `Bitnum`
     if int_width is None:
-        return Bitnum(x, width, is_signed).hex_string(with_prefix)
+        if is_bn:
+            return Bitnum(x, width, is_signed).hex_string(with_prefix)
+        else:
+            return IEEE754Float(x, width, is_signed).hex_string(with_prefix)
 
+    # Otherwise, this is a fixed-point number.
     try:
         return FixedPoint(x, width, int_width, is_signed).hex_string(with_prefix)
     except InvalidNumericType as error:
@@ -133,14 +143,16 @@ def convert2dat(output_dir, data, extension, round: bool):
         numeric_type = format["numeric_type"]
         is_signed = format["is_signed"]
 
-        if numeric_type not in {"bitnum", "fixed_point"}:
-            raise InvalidNumericType('Fud only supports "fixed_point" and "bitnum".')
+        if numeric_type not in {"bitnum", "fixed_point", "ieee754_float"}:
+            raise InvalidNumericType(
+                'Fud only supports "fixed_point", "bitnum", and "ieee754_float".'
+            )
 
         is_fp = numeric_type == "fixed_point"
         if is_fp:
             width, int_width = parse_fp_widths(format)
         else:
-            # `Bitnum`s only have a bit width.
+            # `Bitnum`s and `FloatingPoint`s only have a bit width
             width = format["width"]
             int_width = None
 
@@ -154,7 +166,8 @@ def convert2dat(output_dir, data, extension, round: bool):
 
         with path.open("w") as f:
             for v in arr.flatten():
-                f.write(convert(v, round, is_signed, width, int_width) + "\n")
+                is_bn = numeric_type == "bitnum"
+                f.write(convert(v, round, is_signed, width, is_bn, int_width) + "\n")
 
         shape[k]["shape"] = list(arr.shape)
         shape[k]["numeric_type"] = numeric_type
@@ -185,8 +198,9 @@ def convert2json(input_dir, extension):
         # for building the FixedPoint or Bitnum classes.
         args = form.copy()
         del args["shape"]
+        is_bn = args["numeric_type"] == "bitnum"
         del args["numeric_type"]
-        arr = parse_dat(path, args)
+        arr = parse_dat(path, is_bn, args)
         if form["shape"] == [0]:
             raise Malformed(
                 "Data format shape",
diff --git a/fud2/Cargo.toml b/fud2/Cargo.toml
index 4ca0d8b058..466046b231 100644
--- a/fud2/Cargo.toml
+++ b/fud2/Cargo.toml
@@ -13,10 +13,6 @@ readme = "README.md"
 categories = ["build-tool"]
 description = "Compiler driver for the Calyx infrastructure"
 
-[features]
-migrate_to_scripts = []
-default = ["migrate_to_scripts"]
-
 [dependencies]
 fud-core = { path = "fud-core", version = "0.0.2", features = ["egg_planner"] }
 anyhow.workspace = true
diff --git a/fud2/fud-core/src/cli.rs b/fud2/fud-core/src/cli.rs
index cc96458085..2743b6581f 100644
--- a/fud2/fud-core/src/cli.rs
+++ b/fud2/fud-core/src/cli.rs
@@ -142,7 +142,7 @@ pub enum Subcommand<T: CliExt> {
 
 #[derive(FromArgs)]
 /// A generic compiler driver.
-pub struct FakeArgs<T: CliExt> {
+pub struct FudArgs<T: CliExt> {
     #[argh(subcommand)]
     pub sub: Option<Subcommand<T>>,
 
@@ -190,6 +190,10 @@ pub struct FakeArgs<T: CliExt> {
     #[argh(switch, short = 'v')]
     verbose: Option<bool>,
 
+    /// quiet mode
+    #[argh(switch, short = 'q')]
+    quiet: bool,
+
     /// log level for debugging fud internal
     #[argh(option, long = "log", default = "log::LevelFilter::Warn")]
     pub log_level: log::LevelFilter,
@@ -229,7 +233,7 @@ fn get_states_with_errors(
 
 fn from_states<T: CliExt>(
     driver: &Driver,
-    args: &FakeArgs<T>,
+    args: &FudArgs<T>,
 ) -> anyhow::Result<Vec<StateRef>> {
     get_states_with_errors(
         driver,
@@ -243,7 +247,7 @@ fn from_states<T: CliExt>(
 
 fn to_state<T: CliExt>(
     driver: &Driver,
-    args: &FakeArgs<T>,
+    args: &FudArgs<T>,
 ) -> anyhow::Result<Vec<StateRef>> {
     get_states_with_errors(
         driver,
@@ -257,7 +261,7 @@ fn to_state<T: CliExt>(
 
 fn get_request<T: CliExt>(
     driver: &Driver,
-    args: &FakeArgs<T>,
+    args: &FudArgs<T>,
 ) -> anyhow::Result<Request> {
     // The default working directory (if not specified) depends on the mode.
     let workdir = args.dir.clone().unwrap_or_else(|| match args.mode {
@@ -407,7 +411,7 @@ impl<T: CliExt> CliStart<T> for T {
 fn config_from_cli_ext<T: CliExt>(
     name: &str,
 ) -> anyhow::Result<figment::Figment> {
-    let args: FakeArgs<T> = argh::from_env();
+    let args: FudArgs<T> = argh::from_env();
     let mut config = config::load_config(name);
 
     // Use `--set` arguments to override configuration values.
@@ -428,7 +432,7 @@ fn cli_ext<T: CliExt>(
     driver: &Driver,
     config: &figment::Figment,
 ) -> anyhow::Result<()> {
-    let args: FakeArgs<T> = argh::from_env();
+    let args: FudArgs<T> = argh::from_env();
     // Configure logging.
     env_logger::Builder::new()
         .format_timestamp(None)
@@ -479,8 +483,8 @@ fn cli_ext<T: CliExt>(
         Mode::ShowDot => run.show_dot(),
         Mode::EmitNinja => run.emit_to_stdout()?,
         Mode::Generate => run.emit_to_dir(&workdir)?.keep(),
-        Mode::Run => run.emit_and_run(&workdir, false)?,
-        Mode::Cmds => run.emit_and_run(&workdir, true)?,
+        Mode::Run => run.emit_and_run(&workdir, false, args.quiet)?,
+        Mode::Cmds => run.emit_and_run(&workdir, true, false)?,
     }
 
     Ok(())
diff --git a/fud2/fud-core/src/run.rs b/fud2/fud-core/src/run.rs
index c61993e80d..a77d8e8fe7 100644
--- a/fud2/fud-core/src/run.rs
+++ b/fud2/fud-core/src/run.rs
@@ -339,7 +339,12 @@ impl<'a> Run<'a> {
     /// Emit `build.ninja` to a temporary directory and then actually execute Ninja.
     ///
     /// If `print_cmds` is true, Ninja will print commands it is to run instead of executing them.
-    pub fn emit_and_run(&self, dir: &Utf8Path, print_cmds: bool) -> EmitResult {
+    pub fn emit_and_run(
+        &self,
+        dir: &Utf8Path,
+        print_cmds: bool,
+        quiet_mode: bool,
+    ) -> EmitResult {
         // Emit the Ninja file.
         let dir = self.emit_to_dir(dir)?;
 
@@ -375,7 +380,11 @@ impl<'a> Run<'a> {
             cmd.arg("-tcommands");
         }
 
-        cmd.stdout(std::io::stderr()); // Send Ninja's stdout to our stderr.
+        if !quiet_mode {
+            cmd.stdout(std::io::stderr()); // Send Ninja's stdout to our stderr.
+        } else {
+            cmd.stdout(std::process::Stdio::null());
+        }
         let status = cmd.status().map_err(ninja_cmd_io_error)?;
 
         // Emit to stdout, only when Ninja succeeded.
diff --git a/fud2/fud-core/src/script/plugin.rs b/fud2/fud-core/src/script/plugin.rs
index 608d0caf09..6f5bb3ca17 100644
--- a/fud2/fud-core/src/script/plugin.rs
+++ b/fud2/fud-core/src/script/plugin.rs
@@ -495,10 +495,16 @@ impl ScriptRunner {
         static_files: impl Iterator<Item = (&'static str, &'static [u8])>,
     ) -> &mut Self {
         for (name, data) in static_files {
-            let ast = self
-                .engine
-                .compile(String::from_utf8(data.to_vec()).unwrap())
-                .unwrap();
+            let file = String::from_utf8(data.to_vec()).unwrap();
+            let compile_res = self.engine.compile(file);
+
+            let ast = match compile_res {
+                Ok(ast) => ast,
+                Err(e) => {
+                    let msg = format!("Failed to parse `{name}': {e}",);
+                    panic!("{msg}");
+                }
+            };
             let functions =
                 self.resolver.as_mut().unwrap().register_data(name, ast);
             self.rhai_functions = self.rhai_functions.merge(&functions);
diff --git a/fud2/scripts/calyx.rhai b/fud2/scripts/calyx.rhai
index 83162e51cf..7a65961c2c 100644
--- a/fud2/scripts/calyx.rhai
+++ b/fud2/scripts/calyx.rhai
@@ -6,14 +6,16 @@ export let calyx_setup = calyx_setup;
 fn calyx_setup(e) {
    e.config_var("calyx-base", "calyx.base");
    e.config_var_or("calyx-exe", "calyx.exe", "$calyx-base/target/debug/calyx");
+   e.config_var_or("calyx-lib-path", "calyx.lib_path", "$calyx-base");
    e.config_var_or("args", "calyx.args", "");
-   e.rule("calyx", "$calyx-exe -l $calyx-base -b $backend $args $in > $out");
-   e.rule("calyx-pass", "$calyx-exe -l $calyx-base -p $pass $args $in > $out");
 
-   e.config_var_or("flags", "calyx.flags", "-p none");
+   e.rule("calyx", "$calyx-exe -l $calyx-lib-path -b $backend $args $in > $out");
+   e.rule("calyx-pass", "$calyx-exe -l $calyx-lib-path -p $pass $args $in > $out");
+
+   e.config_var_or("cider-calyx-passes", "cider.calyx-passes", "-p none");
    e.rule(
-       "calyx-with-flags",
-       "$calyx-exe -l $calyx-base $flags $args $in > $out",
+       "calyx-cider",
+       "$calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out",
    );
 }
 
diff --git a/fud2/scripts/cider.rhai b/fud2/scripts/cider.rhai
index c705457864..328afc9bc4 100644
--- a/fud2/scripts/cider.rhai
+++ b/fud2/scripts/cider.rhai
@@ -16,39 +16,56 @@ fn cider_setup(e) {
         "cider-converter.exe",
         "$calyx-base/target/debug/cider-data-converter",
     );
+    e.config_var_or("converter-flags", "cider.converter-flags", "");
+    e.config_var_or("cider-flags", "cider.flags", "");
+
+    let has_data = !e.config_or("sim.data", "").is_empty();
+
+    if has_data {
+        e.var_("data", "--data data.dump");
+
+        // copied from rtl_sim.rhai, we only want to do this when the sim.data
+        // flag is present
+        let data_name = e.config_val("sim.data");
+        let data_path = e.external_path(data_name);
+        e.var_("sim_data", data_path);
+    } else {
+        e.var_("data", "")
+    }
+
     e.rule(
         "run-cider-debug",
-        "$cider-exe -l $calyx-base --data data.dump $in debug || true",
+        "$cider-exe -l $calyx-base $data $cider-flags $in debug || true",
     );
     e.arg("pool", "console");
 
-    e.config_var_or("converter-flags", "cider.converter-flags", "");
-    e.config_var_or("cider-flags", "cider.flags", "");
-
     e.rule(
         "run-cider",
-        "$cider-exe -l $calyx-base --data data.dump $cider-flags $in > $out",
+        "$cider-exe -l $calyx-base $data $cider-flags $in > $out",
     );
 
-    e.rule("dump-to-interp", "$cider-converter --to cider $converter-flags $in > $out");
     e.rule("interp-to-dump", "$cider-converter --to json $converter-flags $in > $out");
-    e.build_cmd(
-        ["data.dump"],
-        "dump-to-interp",
-        ["$sim_data"],
-        ["$cider-converter"],
-    );
+
+    if has_data {
+        e.rule("dump-to-interp", "$cider-converter --to cider $converter-flags $in > $out");
+        e.build_cmd(
+            ["data.dump"],
+            "dump-to-interp",
+            ["$sim_data"],
+            ["$cider-converter"],
+        );
+    }
 }
 
 op(
     "calyx-to-cider",
-    [sim::sim_setup, c::calyx_setup],
+    [c::calyx_setup],
     c::calyx_state,
     cider_state,
     |e, input, output| {
         e.build_cmd(
             [output],
-            "calyx-with-flags",
+            "calyx-cider",
             [input],
             [],
         );
@@ -58,22 +75,23 @@ op(
 
 op(
     "cider",
-    [sim::sim_setup, c::calyx_setup, cider_setup],
+    [c::calyx_setup, cider_setup],
     cider_state,
     sim::dat,
     |e, input, output| {
         let out_file = "interp_out.dump";
+        let dependencies = if e.config_or("sim.data", "").is_empty() { [] } else { ["data.dump"] };
         e.build_cmd(
             [out_file],
             "run-cider",
             [input],
-            ["data.dump"],
+            dependencies,
         );
         e.build_cmd(
             [output],
             "interp-to-dump",
             [out_file],
-            ["$sim_data", "$cider-converter"],
+            ["$cider-converter"],
         );
     },
 );
@@ -81,7 +99,6 @@ op(
 op(
     "debug",
     [
-        sim::sim_setup,
         tb::standalone_setup,
         c::calyx_setup,
         cider_setup,
@@ -89,6 +106,7 @@ op(
     cider_state,
     dbg,
     |e, input, output| {
-        e.build_cmd([output], "run-cider-debug", [input], ["data.dump"]);
+        let dependencies = if e.config_or("sim.data", "").is_empty() { [] } else { ["data.dump"] };
+        e.build_cmd([output], "run-cider-debug", [input], dependencies);
     },
 );
diff --git a/fud2/scripts/cocotb-axi.rhai b/fud2/scripts/cocotb-axi.rhai
index 3672e12a27..f22511d5f1 100644
--- a/fud2/scripts/cocotb-axi.rhai
+++ b/fud2/scripts/cocotb-axi.rhai
@@ -11,12 +11,36 @@ fn cocotb_setup(e) {
     let data_path = e.external_path(data_name);
     e.var_("sim_data", data_path);
 
+    let waves = e.config_constrained_or("waves", ["true", "false"], "false");
+    if waves == "true" {
+        //adds lines based on what is needed for icarus fst output.
+        e.rule("iverilog-fst-sed",
+        `sed '/\/\/ COMPONENT END: wrapper/c\` + "ifdef COCOTB_SIM\n  initial begin\n" + `    \$$dumpfile ("$fst_file_name");` + "\n    " + `\$$dumpvars (0, wrapper);` + "\n    1;\n  end\n`endif\n" + `\/\/ COMPONENT END: wrapper' $in > $out`);
+    }
+
+    e.var_("cocotb-args", if waves == "true" {"WAVES=1"} else {""});
+    e.rule("make-cocotb", "make DATA_PATH=$sim_data VERILOG_SOURCE=$in COCOTB_LOG_LEVEL=CRITICAL $cocotb-args > $out");
+
     // Cocotb is wants files relative to the location of the makefile.
     // This is annoying to calculate on the fly, so we just copy necessary files to the build directory
     e.rule("copy", "cp $in $out");
-    e.rule("make-cocotb", "make DATA_PATH=$sim_data VERILOG_SOURCE=$in COCOTB_LOG_LEVEL=CRITICAL > $out");
     // This cleans up the extra `make` cruft, leaving what is in between `{` and `}.`
-    e.rule("cleanup-cocotb", "sed -n '/Output:/,/make\\[1\\]/{/Output:/d;/make\\[1\\]/d;p}' $in > $out");
+    e.rule(
+      "cleanup-cocotb", `sed -n '/Output:/,/make\[1\]/{/Output:/d;/make\[1\]/d;p}' $in > $out`
+    );
+}
+
+fn basename(s) {
+    let out = "";
+    let dot_idx = s.len() - 1;
+    while s[dot_idx] != "." && dot_idx >= 0 {
+        dot_idx -= 1;
+    }
+    if dot_idx != 0 {
+        s.truncate(dot_idx)
+    } else {
+        s
+    }
 }
 
 op(
@@ -43,6 +67,18 @@ op(
             ["$cocotb-makefile-dir/run_axi_test.py"],
             [],
         );
+        let waves = e.config_constrained_or(
+            "waves",
+            ["true", "false"],
+            "false",
+        );
+        let vcd_file_name = `${basename(input)}.fst`;
+        let make_in = input;
+        if waves == "true" {
+            make_in = "dumpvars.v";
+            e.build_cmd([make_in], "iverilog-fst-sed", input, []);
+            e.arg("fst_file_name", vcd_file_name);
+        }
         e.build_cmd(
             ["tmp.dat"],
             "make-cocotb",
diff --git a/fud2/scripts/jq.rhai b/fud2/scripts/jq.rhai
new file mode 100644
index 0000000000..aba1649689
--- /dev/null
+++ b/fud2/scripts/jq.rhai
@@ -0,0 +1,11 @@
+import "rtl_sim" as sim;
+
+export const jq_state = state("jq", ["jq"]);
+
+defop dat_to_jq(json: sim::dat) >> out: jq_state {
+    let expr = config_or("jq.expr", ".");
+    let jq = config_or("jq.exe", "jq");
+    let flags = config_or("jq.flags", "");
+
+    shell(`${jq} '${expr}' ${flags} ${json} > ${out}`);
+}
\ No newline at end of file
diff --git a/fud2/scripts/profiler.rhai b/fud2/scripts/profiler.rhai
new file mode 100644
index 0000000000..7fe78e7dee
--- /dev/null
+++ b/fud2/scripts/profiler.rhai
@@ -0,0 +1,86 @@
+import "calyx" as c;
+import "verilator" as v;
+import "rtl_sim" as sim;
+
+export const instrumented_verilog = state("verilog-instrumented", ["sv"]);
+export const instrumented_sim = state("sim-instrumented", ["exe"]);
+export const instrumented_vcd = state("vcd-instrumented", ["vcd"]);
+export const flamegraph = state("flamegraph", ["svg"]);
+
+fn profiling_setup(e) {
+    e.var_("cells", "cells.json");
+    e.var_("tdcc-json", "fsm.json"); // might not be necessary if we get rid of fsms?
+
+    // series of passes after instrumentation?
+    e.config_var_or("passes", "profiler.compilation-passes", "all"); // set passes="no-opt" to run without optimizations
+
+    // rules for preprocessing
+
+    e.config_var_or("component_cells", "component_cells", "$calyx-base/target/debug/component_cells");
+    e.rule("component-cells", "$component_cells -l $calyx-base $in > $out");
+
+    // rules for postprocessing
+
+    // script to process vcd and attribute active cycles to every group/cell
+    e.var_("parse-vcd-script", "$calyx-base/tools/profiler/parse-vcd.py");
+    e.rule("parse-vcd", "python3 $parse-vcd-script $in $tdcc-json $cells summary.csv $out");
+
+    // script to produce trace and visuals
+    e.var_("create-visuals-script", "$calyx-base/tools/profiler/create-visuals.py");
+    e.rule("create-visuals", "python3 $create-visuals-script $in $cells timeline.json fsm-timeline.json $out fsm-flame.folded frequency.folded components.folded fsm-components.folded");
+
+    e.config_var("flamegraph-script", "flamegraph.script");
+    e.rule("produce-flame-graph", "$flamegraph-script $in > $out");
+
+    // Standalone Verilog testbench. copied from testbench
+    e.rsrc("tb.sv");
+
+}
+
+fn calyx_to_flamegraph(e, input, output) {
+    // instrument calyx and produce verilog
+    let instrumented_verilog = "instrumented.sv";
+    e.build_cmd(["$cells"], "component-cells", [input], []);
+    e.build_cmd([instrumented_verilog], "calyx", [input], []);
+    e.arg("backend", "verilog");
+    e.arg("args", " -p static-inline -p compile-static -p compile-repeat -p par-to-seq -p compile-invoke -p profiler-instrumentation -p $passes -x tdcc:dump-fsm-json=fsm.json");
+
+    let instrumented_sim = "instrumented.exe";
+    // verilog --> sim; adapted from verilator::verilator_build()
+    let verilator_out_dir = "verilator-out";
+    let sim_bin = `${verilator_out_dir}/Vtoplevel`;
+    e.build_cmd(
+      [sim_bin],
+      "verilator-compile-standalone-tb",
+      [instrumented_verilog],
+      ["tb.sv"],
+    );
+    e.arg("out-dir", verilator_out_dir);
+    e.build("cp", sim_bin, instrumented_sim);
+
+    let instrumented_vcd = "instrumented.vcd";
+    // sim --> vcd; adapted from rtl_sim
+    e.build_cmd(
+        ["sim.log", instrumented_vcd],
+        "sim-run",
+        [instrumented_sim, "$datadir"],
+        [],
+    );
+    e.arg("bin", instrumented_sim);
+    e.arg("args", `+NOTRACE=0 +OUT=${instrumented_vcd}`);
+
+    // vcd --> flamegraph
+    let elems_profiled_json = "elems-profiled.json";
+    let flamegraph_folded = "flamegraph.folded";
+    e.build_cmd([elems_profiled_json], "parse-vcd", [instrumented_vcd], []);
+    e.build_cmd([flamegraph_folded], "create-visuals", [elems_profiled_json], ["$cells"]);
+    e.build_cmd([output], "produce-flame-graph", [flamegraph_folded], []);
+}
+
+op(
+    "profiler",
+    [c::calyx_setup, profiling_setup, v::verilator_setup, sim::sim_setup],
+    c::calyx_state,
+    flamegraph,
+    |e, input, output| calyx_to_flamegraph(e, input, output)
+);
diff --git a/fud2/src/lib.rs b/fud2/src/lib.rs
index a714236bf0..b99e337a23 100644
--- a/fud2/src/lib.rs
+++ b/fud2/src/lib.rs
@@ -1,906 +1,2 @@
 mod cli_pyenv;
 pub use cli_pyenv::Fud2CliExt;
-
-use std::str::FromStr;
-
-use fud_core::{
-    exec::{SetupRef, StateRef},
-    run::{EmitResult, StreamEmitter},
-    utils::basename,
-    DriverBuilder,
-};
-
-fn setup_calyx(
-    bld: &mut DriverBuilder,
-    verilog: StateRef,
-) -> (StateRef, SetupRef) {
-    let calyx = bld.state("calyx", &["futil"]);
-    let calyx_setup = bld.setup("Calyx compiler", |e| {
-        e.config_var("calyx-base", "calyx.base")?;
-        e.config_var_or(
-            "calyx-exe",
-            "calyx.exe",
-            "$calyx-base/target/debug/calyx",
-        )?;
-        e.config_var_or("args", "calyx.args", "")?;
-        e.rule(
-            "calyx",
-            "$calyx-exe -l $calyx-base -b $backend $args $in > $out",
-        )?;
-        e.rule(
-            "calyx-pass",
-            "$calyx-exe -l $calyx-base -p $pass $args $in > $out",
-        )?;
-
-        e.config_var_or("flags", "calyx.flags", "-p none")?;
-
-        e.rule(
-            "calyx-with-flags",
-            "$calyx-exe -l $calyx-base $flags $args $in > $out",
-        )?;
-
-        Ok(())
-    });
-    bld.op(
-        "calyx-to-verilog",
-        &[calyx_setup],
-        calyx,
-        verilog,
-        |e, input, output| {
-            e.build_cmd(&[output[0]], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "verilog")?;
-            Ok(())
-        },
-    );
-    (calyx, calyx_setup)
-}
-
-fn setup_dahlia(
-    bld: &mut DriverBuilder,
-    calyx: StateRef,
-) -> (StateRef, SetupRef) {
-    let dahlia = bld.state("dahlia", &["fuse"]);
-    let dahlia_setup = bld.setup("Dahlia compiler", |e| {
-        e.config_var("dahlia-exe", "dahlia")?;
-        e.rule(
-            "dahlia-to-calyx",
-            "$dahlia-exe -b calyx --lower -l error $in -o $out",
-        )?;
-        Ok(())
-    });
-    bld.rule(&[dahlia_setup], dahlia, calyx, "dahlia-to-calyx");
-    (dahlia, dahlia_setup)
-}
-
-fn setup_mrxl(
-    bld: &mut DriverBuilder,
-    calyx: StateRef,
-) -> (StateRef, SetupRef) {
-    let mrxl = bld.state("mrxl", &["mrxl"]);
-    let mrxl_setup = bld.setup("MrXL compiler", |e| {
-        e.var("mrxl-exe", "mrxl")?;
-        e.rule("mrxl-to-calyx", "$mrxl-exe $in > $out")?;
-        Ok(())
-    });
-    bld.rule(&[mrxl_setup], mrxl, calyx, "mrxl-to-calyx");
-    (mrxl, mrxl_setup)
-}
-
-pub fn build_driver(bld: &mut DriverBuilder) {
-    // The verilog state
-    let verilog = bld.state("verilog", &["sv", "v"]);
-    // Calyx.
-    let (calyx, calyx_setup) = setup_calyx(bld, verilog);
-    // Dahlia.
-    setup_dahlia(bld, calyx);
-    // MrXL.
-    setup_mrxl(bld, calyx);
-
-    // Shared machinery for RTL simulators.
-    let dat = bld.state("dat", &["json"]);
-    let vcd = bld.state("vcd", &["vcd"]);
-    let simulator = bld.state("sim", &["exe"]);
-    let sim_setup = bld.setup("RTL simulation", |e| {
-        // Data conversion to and from JSON.
-        e.config_var_or("python", "python", "python3")?;
-        e.rsrc("json-dat.py")?;
-        e.rule("hex-data", "$python json-dat.py --from-json $in $out")?;
-        e.rule("json-data", "$python json-dat.py --to-json $out $in")?;
-
-        // The input data file. `sim.data` is required.
-        let data_name = e.config_val("sim.data")?;
-        let data_path = e.external_path(data_name.as_ref());
-        e.var("sim_data", data_path.as_str())?;
-
-        // Produce the data directory.
-        e.var("datadir", "sim_data")?;
-        e.build_cmd(
-            &["$datadir"],
-            "hex-data",
-            &["$sim_data"],
-            &["json-dat.py"],
-        )?;
-
-        // Rule for simulation execution.
-        e.rule(
-            "sim-run",
-            "./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out",
-        )?;
-
-        // More shared configuration.
-        e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000")?;
-
-        Ok(())
-    });
-    bld.op(
-        "simulate",
-        &[sim_setup],
-        simulator,
-        dat,
-        |e, input, output| {
-            e.build_cmd(&["sim.log"], "sim-run", &[input[0], "$datadir"], &[])?;
-            e.arg("bin", input[0])?;
-            e.arg("args", "+NOTRACE=1")?;
-            e.build_cmd(
-                &[output[0]],
-                "json-data",
-                &["$datadir", "sim.log"],
-                &["json-dat.py"],
-            )?;
-            Ok(())
-        },
-    );
-    bld.op("trace", &[sim_setup], simulator, vcd, |e, input, output| {
-        e.build_cmd(
-            &["sim.log", output[0]],
-            "sim-run",
-            &[input[0], "$datadir"],
-            &[],
-        )?;
-        e.arg("bin", input[0])?;
-        e.arg("args", &format!("+NOTRACE=0 +OUT={}", output[0]))?;
-        Ok(())
-    });
-
-    // The "verilog_refmem" states are variants of the other Verilog states that use the external testbench style.
-    // "refmem" refers to the fact that their memories are external, meaning that they need to be linked with
-    // a testbench that will provide those memories.
-    let verilog_refmem = bld.state("verilog-refmem", &["sv"]);
-    let verilog_refmem_noverify = bld.state("verilog-refmem-noverify", &["sv"]);
-
-    // Icarus Verilog.
-    let verilog_noverify = bld.state("verilog-noverify", &["sv", "v"]);
-    let icarus_setup = bld.setup("Icarus Verilog", |e| {
-        e.var("iverilog", "iverilog")?;
-        e.rule(
-            "icarus-compile-standalone-tb",
-            "$iverilog -g2012 -o $out tb.sv $in",
-        )?;
-        e.rule(
-            "icarus-compile-custom-tb",
-            "$iverilog -g2012 -o $out tb.sv memories.sv $in",
-        )?;
-        Ok(())
-    });
-    // [Default] Setup for using rsrc/tb.sv as testbench (and managing memories within the design)
-    let standalone_testbench_setup =
-        bld.setup("Standalone Testbench Setup", |e| {
-            // Standalone Verilog testbench.
-            e.rsrc("tb.sv")?;
-
-            Ok(())
-        });
-    let custom_testbench_setup = bld.setup("Custom Testbench Setup", |e| {
-        // Convert all ref cells to @external (FIXME: YXI should work for both?)
-        e.rule("ref-to-external", "sed 's/ref /@external /g' $in > $out")?;
-
-        e.var(
-            "gen-testbench-script",
-            "$calyx-base/tools/firrtl/generate-testbench.py",
-        )?;
-        e.rsrc("memories.sv")?; // Memory primitives.
-
-        e.rule(
-            "generate-refmem-testbench",
-            "python3 $gen-testbench-script $in > $out",
-        )?;
-
-        // dummy rule to force ninja to build the testbench
-        e.rule("dummy", "sh -c 'cat $$0' $in > $out")?;
-
-        Ok(())
-    });
-    bld.op(
-        "calyx-noverify",
-        &[calyx_setup],
-        calyx,
-        verilog_noverify,
-        |e, input, output| {
-            // Icarus requires a special --disable-verify version of Calyx code.
-            e.build_cmd(&[output[0]], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "verilog")?;
-            e.arg("args", "--disable-verify")?;
-            Ok(())
-        },
-    );
-
-    bld.op(
-        "icarus",
-        &[sim_setup, standalone_testbench_setup, icarus_setup],
-        verilog_noverify,
-        simulator,
-        |e, input, output| {
-            e.build_cmd(
-                &[output[0]],
-                "icarus-compile-standalone-tb",
-                &[input[0]],
-                &["tb.sv"],
-            )?;
-            Ok(())
-        },
-    );
-    bld.op(
-        "icarus-refmem",
-        &[sim_setup, icarus_setup],
-        verilog_refmem_noverify,
-        simulator,
-        |e, input, output| {
-            e.build_cmd(
-                &[output[0]],
-                "icarus-compile-custom-tb",
-                &[input[0]],
-                &["tb.sv", "memories.sv"],
-            )?;
-            Ok(())
-        },
-    );
-
-    // primitive-uses backend
-    let primitive_uses_json = bld.state("primitive-uses-json", &["json"]);
-    bld.op(
-        "primitive-uses",
-        &[calyx_setup],
-        calyx,
-        primitive_uses_json,
-        |e, input, output| {
-            e.build_cmd(&[output[0]], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "primitive-uses")?;
-            Ok(())
-        },
-    );
-
-    // Verilator.
-    let verilator_setup = bld.setup("Verilator", |e| {
-        e.config_var_or("verilator", "verilator.exe", "verilator")?;
-        e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000")?;
-        e.rule(
-            "verilator-compile-standalone-tb",
-            "$verilator $in tb.sv --trace --binary --top-module toplevel -fno-inline -Mdir $out-dir",
-        )?;
-        e.rule(
-            "verilator-compile-custom-tb",
-            "$verilator $in tb.sv memories.sv --trace --binary --top-module toplevel -fno-inline -Mdir $out-dir",
-        )?;
-        e.rule("cp", "cp $in $out")?;
-        Ok(())
-    });
-    fn verilator_build(
-        e: &mut StreamEmitter,
-        input: &str,
-        output: &str,
-        standalone_testbench: bool,
-    ) -> EmitResult {
-        let out_dir = "verilator-out";
-        let sim_bin = format!("{}/Vtoplevel", out_dir);
-        if standalone_testbench {
-            e.build_cmd(
-                &[&sim_bin],
-                "verilator-compile-standalone-tb",
-                &[input],
-                &["tb.sv"],
-            )?;
-        } else {
-            e.build_cmd(
-                &[&sim_bin],
-                "verilator-compile-custom-tb",
-                &[input],
-                &["tb.sv", "memories.sv"],
-            )?;
-        }
-        e.arg("out-dir", out_dir)?;
-        e.build("cp", &sim_bin, output)?;
-        Ok(())
-    }
-
-    bld.op(
-        "verilator",
-        &[sim_setup, standalone_testbench_setup, verilator_setup],
-        verilog,
-        simulator,
-        |e, input, output| verilator_build(e, input[0], output[0], true),
-    );
-
-    bld.op(
-        "verilator-refmem",
-        &[sim_setup, custom_testbench_setup, verilator_setup],
-        verilog_refmem,
-        simulator,
-        |e, input, output| verilator_build(e, input[0], output[0], false),
-    );
-
-    // Interpreter.
-    let debug = bld.state("cider-debug", &[]); // A pseudo-state.
-                                               // A pseudo-state for cider input
-    let cider_state = bld.state("cider", &[]);
-
-    let cider_setup = bld.setup("Cider interpreter", |e| {
-        e.config_var_or(
-            "cider-exe",
-            "cider.exe",
-            "$calyx-base/target/debug/cider",
-        )?;
-        e.config_var_or(
-            "cider-converter",
-            "cider-converter.exe",
-            "$calyx-base/target/debug/cider-data-converter",
-        )?;
-        e.rule(
-            "run-cider-debug",
-            "$cider-exe -l $calyx-base --data data.dump $in debug || true",
-        )?;
-        e.arg("pool", "console")?;
-
-        e.rule(
-            "run-cider",
-            "$cider-exe -l $calyx-base --data data.dump $in > $out",
-        )?;
-
-        e.rule("dump-to-interp", "$cider-converter --to cider $in > $out")?;
-        e.rule("interp-to-dump", "$cider-converter --to json $in > $out")?;
-        e.build_cmd(
-            &["data.dump"],
-            "dump-to-interp",
-            &["$sim_data"],
-            &["$cider-converter"],
-        )?;
-
-        Ok(())
-    });
-    bld.op(
-        "calyx-to-cider",
-        &[sim_setup, calyx_setup],
-        calyx,
-        cider_state,
-        |e, input, _output| {
-            e.build_cmd(
-                &["cider-input.futil"],
-                "calyx-with-flags",
-                input,
-                &[],
-            )?;
-            Ok(())
-        },
-    );
-
-    bld.op(
-        "cider",
-        &[sim_setup, calyx_setup, cider_setup],
-        cider_state,
-        dat,
-        |e, _input, output| {
-            let out_file = "interp_out.dump";
-            e.build_cmd(
-                &[out_file],
-                "run-cider",
-                &["cider-input.futil"],
-                &["data.dump"],
-            )?;
-            e.build_cmd(
-                &[output[0]],
-                "interp-to-dump",
-                &[out_file],
-                &["$sim_data", "$cider-converter"],
-            )?;
-            Ok(())
-        },
-    );
-    bld.op(
-        "cider-debug",
-        &[
-            sim_setup,
-            standalone_testbench_setup,
-            calyx_setup,
-            cider_setup,
-        ],
-        calyx,
-        debug,
-        |e, input, output| {
-            e.build_cmd(
-                &[output[0]],
-                "run-cider-debug",
-                &[input[0]],
-                &["data.dump"],
-            )?;
-            Ok(())
-        },
-    );
-
-    // Xilinx compilation.
-    let xo = bld.state("xo", &["xo"]);
-    let xclbin = bld.state("xclbin", &["xclbin"]);
-    let xilinx_setup = bld.setup("Xilinx tools", |e| {
-        // Locations for Vivado and Vitis installations.
-        e.config_var("vivado-dir", "xilinx.vivado")?;
-        e.config_var("vitis-dir", "xilinx.vitis")?;
-
-        // Package a Verilog program as an `.xo` file.
-        e.rsrc("gen_xo.tcl")?;
-        e.rsrc("get-ports.py")?;
-        e.config_var_or("python", "python", "python3")?;
-        e.rule("gen-xo", "$vivado-dir/bin/vivado -mode batch -source gen_xo.tcl -tclargs $out `$python get-ports.py kernel.xml`")?;
-        e.arg("pool", "console")?;  // Lets Ninja stream the tool output "live."
-
-        // Compile an `.xo` file to an `.xclbin` file, which is where the actual EDA work occurs.
-        e.config_var_or("xilinx-mode", "xilinx.mode", "hw_emu")?;
-        e.config_var_or("platform", "xilinx.device", "xilinx_u50_gen3x16_xdma_201920_3")?;
-        e.rule("compile-xclbin", "$vitis-dir/bin/v++ -g -t $xilinx-mode --platform $platform --save-temps --profile.data all:all:all --profile.exec all:all:all -lo $out $in")?;
-        e.arg("pool", "console")?;
-
-        Ok(())
-    });
-    bld.op(
-        "xo",
-        &[calyx_setup, xilinx_setup],
-        calyx,
-        xo,
-        |e, input, output| {
-            // Emit the Verilog itself in "synthesis mode."
-            e.build_cmd(&["main.sv"], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "verilog")?;
-            e.arg("args", "--synthesis -p external")?;
-
-            // Extra ingredients for the `.xo` package.
-            e.build_cmd(&["toplevel.v"], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "xilinx")?;
-            e.build_cmd(&["kernel.xml"], "calyx", &[input[0]], &[])?;
-            e.arg("backend", "xilinx-xml")?;
-
-            // Package the `.xo`.
-            e.build_cmd(
-                &[output[0]],
-                "gen-xo",
-                &[],
-                &[
-                    "main.sv",
-                    "toplevel.v",
-                    "kernel.xml",
-                    "gen_xo.tcl",
-                    "get-ports.py",
-                ],
-            )?;
-            Ok(())
-        },
-    );
-    bld.op("xclbin", &[xilinx_setup], xo, xclbin, |e, input, output| {
-        e.build_cmd(&[output[0]], "compile-xclbin", &[input[0]], &[])?;
-        Ok(())
-    });
-
-    // Xilinx execution.
-    // TODO Only does `hw_emu` for now...
-    let xrt_setup = bld.setup("Xilinx execution via XRT", |e| {
-        // Generate `emconfig.json`.
-        e.rule("emconfig", "$vitis-dir/bin/emconfigutil --platform $platform")?;
-        e.build_cmd(&["emconfig.json"], "emconfig", &[], &[])?;
-
-        // Execute via the `xclrun` tool.
-        e.config_var("xrt-dir", "xilinx.xrt")?;
-        e.rule("xclrun", "bash -c 'source $vitis-dir/settings64.sh ; source $xrt-dir/setup.sh ; XRT_INI_PATH=$xrt_ini EMCONFIG_PATH=. XCL_EMULATION_MODE=$xilinx-mode $python -m fud.xclrun --out $out $in'")?;
-        e.arg("pool", "console")?;
-
-        // "Pre-sim" and "post-sim" scripts for simulation.
-        e.rule("echo", "echo $contents > $out")?;
-        e.build_cmd(&["pre_sim.tcl"], "echo", &[""], &[""])?;
-        e.arg("contents", "open_vcd\\\\nlog_vcd *\\\\n")?;
-        e.build_cmd(&["post_sim.tcl"], "echo", &[""], &[""])?;
-        e.arg("contents", "close_vcd\\\\n")?;
-
-        Ok(())
-    });
-    bld.op(
-        "xrt",
-        &[
-            xilinx_setup,
-            sim_setup,
-            standalone_testbench_setup,
-            xrt_setup,
-        ],
-        xclbin,
-        dat,
-        |e, input, output| {
-            e.rsrc("xrt.ini")?;
-            e.build_cmd(
-                &[output[0]],
-                "xclrun",
-                &[input[0], "$sim_data"],
-                &["emconfig.json", "xrt.ini"],
-            )?;
-            e.arg("xrt_ini", "xrt.ini")?;
-            Ok(())
-        },
-    );
-    bld.op(
-        "xrt-trace",
-        &[
-            xilinx_setup,
-            sim_setup,
-            standalone_testbench_setup,
-            xrt_setup,
-        ],
-        xclbin,
-        vcd,
-        |e, input, output| {
-            e.rsrc("xrt_trace.ini")?;
-            e.build_cmd(
-                &[output[0]], // TODO not the VCD, yet...
-                "xclrun",
-                &[input[0], "$sim_data"],
-                &[
-                    "emconfig.json",
-                    "pre_sim.tcl",
-                    "post_sim.tcl",
-                    "xrt_trace.ini",
-                ],
-            )?;
-            e.arg("xrt_ini", "xrt_trace.ini")?;
-            Ok(())
-        },
-    );
-
-    let yxi_setup = bld.setup("YXI setup", |e| {
-        e.config_var_or("yxi", "yxi", "$calyx-base/target/debug/yxi")?;
-        e.rule("yxi", "$yxi -l $calyx-base $in > $out")?;
-        Ok(())
-    });
-
-    let yxi = bld.state("yxi", &["yxi"]);
-    bld.op(
-        "calyx-to-yxi",
-        &[calyx_setup, yxi_setup],
-        calyx,
-        yxi,
-        |e, input, output| {
-            e.build_cmd(output, "yxi", input, &[])?;
-            Ok(())
-        },
-    );
-
-    let wrapper_setup = bld.setup("YXI and AXI generation", |e| {
-        // Define a `gen-axi` rule that invokes our Python code generator program.
-        // For now point to standalone axi-generator.py. Can maybe turn this into a rsrc file?
-        let dynamic =
-            e.config_constrained_or("dynamic", vec!["true", "false"], "false")?;
-        let generator_path = if FromStr::from_str(&dynamic)
-            .expect("The dynamic flag should be either 'true' or 'false'.")
-        {
-            "$calyx-base/yxi/axi-calyx/dynamic-axi-generator.py"
-        } else {
-            "$calyx-base/yxi/axi-calyx/axi-generator.py"
-        };
-        e.config_var_or("axi-generator", "axi.generator", generator_path)?;
-        e.config_var_or("python", "python", "python3")?;
-
-        e.rule("gen-axi", "$python $axi-generator $in > $out")?;
-
-        // Define a simple `combine` rule that just concatenates any numer of files.
-        e.rule("combine", "cat $in > $out")?;
-
-        e.rule(
-            "remove-imports",
-            "sed '1,/component main/{/component main/!d; }' $in > $out",
-        )?;
-        Ok(())
-    });
-    bld.op(
-        "axi-wrapped",
-        &[calyx_setup, yxi_setup, wrapper_setup],
-        calyx,
-        calyx,
-        |e, input, output| {
-            // Generate the YXI file.
-            // no extension
-            let file_name = basename(input[0]);
-
-            // Get yxi file from main compute program.
-            let tmp_yxi = format!("{}.yxi", file_name);
-            e.build_cmd(&[&tmp_yxi], "yxi", input, &[])?;
-
-            // Generate the AXI wrapper.
-            let refified_calyx = format!("refified_{}.futil", file_name);
-            e.build_cmd(&[&refified_calyx], "calyx-pass", &[input[0]], &[])?;
-            e.arg("pass", "external-to-ref")?;
-
-            let axi_wrapper = "axi_wrapper.futil";
-            e.build_cmd(&[axi_wrapper], "gen-axi", &[&tmp_yxi], &[])?;
-
-            // Generate no-imports version of the refified calyx.
-            let no_imports_calyx = format!("no_imports_{}", refified_calyx);
-            e.build_cmd(
-                &[&no_imports_calyx],
-                "remove-imports",
-                &[&refified_calyx],
-                &[],
-            )?;
-
-            // Combine the original Calyx and the wrapper.
-            e.build_cmd(
-                &[output[0]],
-                "combine",
-                &[axi_wrapper, &no_imports_calyx],
-                &[],
-            )?;
-            Ok(())
-        },
-    );
-
-    let cocotb_setup = bld.setup("cocotb", |e| {
-        e.config_var_or("cocotb-makefile-dir", "cocotb.makefile-dir", "$calyx-base/yxi/axi-calyx/cocotb")?;
-        // TODO (nate): this is duplicated from the sim_setup above. Can this be shared?
-        // The input data file. `sim.data` is required.
-        let data_name = e.config_val("sim.data")?;
-        let data_path = e.external_path(data_name.as_ref());
-        e.var("sim_data", data_path.as_str())?;
-
-        // Cocotb wants files relative to the location of the makefile.
-        // This is annoying to calculate on the fly, so we just copy necessary files to the build directory
-        e.rule("copy", "cp $in $out")?;
-
-        let waves = e.config_constrained_or("waves", vec!["true", "false"], "false")?;
-        let waves = FromStr::from_str(&waves).expect("The 'waves' flag should be either 'true' or 'false'.");
-        if waves{
-            //adds lines based on what is needed for icarus fst output.
-            e.rule("iverilog-fst-sed",
-            r#"sed '/\/\/ COMPONENT END: wrapper/c\`ifdef COCOTB_SIM\n  initial begin\n    \$$dumpfile ("$fst_file_name");\n    \$$dumpvars (0, wrapper);\n    #1;\n  end\n`endif\n\/\/ COMPONENT END: wrapper' $in > $out"#)?;
-        }
-
-e.var("cocotb-args", if waves {"WAVES=1"} else {""})?;
-
-        e.rule("make-cocotb", "make DATA_PATH=$sim_data VERILOG_SOURCE=$in COCOTB_LOG_LEVEL=CRITICAL $cocotb-args > $out")?;
-        // This cleans up the extra `make` and `FST warning` cruft, leaving what is in between `{` and `}.`
-        e.rule("cleanup-cocotb", r#"sed -n '/Output:/,/make\[1\]/{/Output:/d;/make\[1\]/d;p}' $in | sed -n ':a;N;$$!ba;s/^[^{]*{\(.*\)}[^}]*$$/\1/p' | sed '1d;$$d' > $out"#)?;
-        Ok(())
-    });
-
-    let cocotb_axi = bld.state("cocotb-axi", &["dat"]);
-    // Example invocation: `fud2 <path to axi wrapped verilog> --from verilog-noverify --to cocotb-axi --set sim.data=<path to .data/json file>`
-    bld.op(
-        "calyx-to-cocotb-axi",
-        &[calyx_setup, cocotb_setup],
-        verilog_noverify,
-        cocotb_axi,
-        |e, input, output| {
-            e.build_cmd(
-                &["Makefile"],
-                "copy",
-                &["$cocotb-makefile-dir/Makefile"],
-                &[],
-            )?;
-            e.build_cmd(
-                &["axi_test.py"],
-                "copy",
-                &["$cocotb-makefile-dir/axi_test.py"],
-                &[],
-            )?;
-            e.build_cmd(
-                &["run_axi_test.py"],
-                "copy",
-                &["$cocotb-makefile-dir/run_axi_test.py"],
-                &[],
-            )?;
-            let waves = e.config_constrained_or(
-                "waves",
-                vec!["true", "false"],
-                "false",
-            )?;
-            let waves = FromStr::from_str(&waves)
-                .expect("The 'waves' flag should be either 'true' or 'false'.");
-
-            let vcd_file_name = format!("{}.fst", basename(input[0]));
-            let mut make_in = input[0];
-            if waves {
-                make_in = "dumpvars.v";
-                e.build_cmd(&[make_in], "iverilog-fst-sed", input, &[])?;
-                e.arg("fst_file_name", &vcd_file_name)?;
-            }
-            e.build_cmd(
-                &["tmp.dat"],
-                "make-cocotb",
-                &[make_in],
-                &["Makefile", "axi_test.py", "run_axi_test.py"],
-            )?;
-            e.build_cmd(output, "cleanup-cocotb", &["tmp.dat"], &[])?;
-
-            Ok(())
-        },
-    );
-
-    // setup for FIRRTL-implemented primitives
-    let firrtl_primitives_setup = bld.setup("FIRRTL with primitives", |e| {
-        // Produce FIRRTL with FIRRTL-defined primitives.
-        e.var(
-            "gen-firrtl-primitives-script",
-            "$calyx-base/tools/firrtl/generate-firrtl-with-primitives.py",
-        )?;
-        e.rule(
-            "generate-firrtl-with-primitives",
-            "python3 $gen-firrtl-primitives-script $in > $out",
-        )?;
-
-        Ok(())
-    });
-
-    fn calyx_to_firrtl_helper(
-        e: &mut StreamEmitter,
-        input: &str,
-        output: &str,
-        firrtl_primitives: bool, // Use FIRRTL primitive implementations?
-    ) -> EmitResult {
-        // Temporary Calyx where all refs are converted into external (FIXME: fix YXI to emit for ref as well?)
-        let only_externals_calyx = "external.futil";
-        // JSON with memory information created by YXI
-        let memories_json = "memory-info.json";
-        // Custom testbench (same name as standalone testbench)
-        let testbench = "tb.sv";
-        // Holds contents of file we want to output. Gets cat-ed via final dummy command
-        let tmp_out = "tmp-out.fir";
-        // Convert ref into external to get YXI working (FIXME: fix YXI to emit for ref as well?)
-        e.build_cmd(&[only_externals_calyx], "ref-to-external", &[input], &[])?;
-
-        // Get YXI to generate JSON for testbench generation
-        e.build_cmd(&[memories_json], "yxi", &[only_externals_calyx], &[])?;
-        // generate custom testbench
-        e.build_cmd(
-            &[testbench],
-            "generate-refmem-testbench",
-            &[memories_json],
-            &[],
-        )?;
-
-        if firrtl_primitives {
-            let core_program_firrtl = "core.fir";
-
-            // Obtain FIRRTL of core program
-            e.build_cmd(&[core_program_firrtl], "calyx", &[input], &[])?;
-            e.arg("backend", "firrtl")?;
-            e.arg("args", "-p external-to-ref -p all --synthesis")?;
-
-            // Obtain primitive uses JSON for metaprogramming
-            let primitive_uses_json = "primitive-uses.json";
-            e.build_cmd(&[primitive_uses_json], "calyx", &[input], &[])?;
-            e.arg("backend", "primitive-uses")?;
-            e.arg("args", "-p external-to-ref -p all --synthesis")?;
-
-            // run metaprogramming script to get FIRRTL with primitives
-            e.build_cmd(
-                &[tmp_out],
-                "generate-firrtl-with-primitives",
-                &[core_program_firrtl, primitive_uses_json],
-                &[],
-            )?;
-        } else {
-            // emit extmodule declarations to use Verilog primitive implementations
-            e.build_cmd(&[tmp_out], "calyx", &[input], &[])?;
-            e.arg("backend", "firrtl")?;
-            e.arg(
-                "args",
-                "-p external-to-ref -p all --emit-primitive-extmodules",
-            )?;
-        }
-
-        // dummy command to make sure custom testbench is created but not emitted as final answer
-        e.build_cmd(&[output], "dummy", &[tmp_out, testbench], &[])?;
-
-        Ok(())
-    }
-
-    // Calyx to FIRRTL.
-    let firrtl = bld.state("firrtl", &["fir"]); // using Verilog primitives
-    let firrtl_with_primitives = bld.state("firrtl-with-primitives", &["fir"]); // using FIRRTL primitives
-    bld.op(
-        // use Verilog
-        "calyx-to-firrtl",
-        &[calyx_setup, custom_testbench_setup, yxi_setup],
-        calyx,
-        firrtl,
-        |e, input, output| {
-            calyx_to_firrtl_helper(e, input[0], output[0], false)
-        },
-    );
-
-    bld.op(
-        "firrtl-with-primitives",
-        &[
-            calyx_setup,
-            yxi_setup,
-            firrtl_primitives_setup,
-            custom_testbench_setup,
-        ],
-        calyx,
-        firrtl_with_primitives,
-        |e, input, output| calyx_to_firrtl_helper(e, input[0], output[0], true),
-    );
-
-    // The FIRRTL compiler.
-    let firrtl_setup = bld.setup("Firrtl to Verilog compiler", |e| {
-        // NOTE: Recommend CIRCT firtool version 1.75.0
-        e.config_var("firrtl-exe", "firrtl.firtool")?;
-        e.rule(
-            "firrtl",
-            "$firrtl-exe $in -o $out --disable-all-randomization",
-        )?;
-
-        e.rsrc("primitives-for-firrtl.sv")?;
-        // adding Verilog implementations of primitives to FIRRTL --> Verilog compiled code
-        e.rule(
-            "add-verilog-primitives",
-            "cat primitives-for-firrtl.sv $in > $out",
-        )?;
-
-        Ok(())
-    });
-
-    fn firrtl_compile_helper(
-        e: &mut StreamEmitter,
-        input: &str,
-        output: &str,
-        firrtl_primitives: bool,
-    ) -> EmitResult {
-        if firrtl_primitives {
-            e.build_cmd(&[output], "firrtl", &[input], &[])?;
-        } else {
-            let tmp_verilog = "partial.sv";
-            e.build_cmd(&[tmp_verilog], "firrtl", &[input], &[])?;
-            e.build_cmd(
-                &[output],
-                "add-verilog-primitives",
-                &[tmp_verilog],
-                &["primitives-for-firrtl.sv"],
-            )?;
-        }
-        Ok(())
-    }
-    // FIRRTL --> Verilog compilation using Verilog primitive implementations for Verilator
-    bld.op(
-        "firrtl",
-        &[firrtl_setup],
-        firrtl,
-        verilog_refmem,
-        |e, input, output| firrtl_compile_helper(e, input[0], output[0], false),
-    );
-    // FIRRTL --> Verilog compilation using Verilog primitive implementations for Icarus
-    // This is a bit of a hack, but the Icarus-friendly "noverify" state is identical for this path
-    // (since FIRRTL compilation doesn't come with verification).
-    bld.op(
-        "firrtl-noverify",
-        &[firrtl_setup],
-        firrtl,
-        verilog_refmem_noverify,
-        |e, input, output| firrtl_compile_helper(e, input[0], output[0], false),
-    );
-    // FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Verilator
-    bld.op(
-        "firrtl-with-primitives",
-        &[firrtl_setup],
-        firrtl_with_primitives,
-        verilog_refmem,
-        |e, input, output| firrtl_compile_helper(e, input[0], output[0], true),
-    );
-    // FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Icarus
-    bld.op(
-        "firrtl-with-primitives-noverify",
-        &[firrtl_setup],
-        firrtl_with_primitives,
-        verilog_refmem_noverify,
-        |e, input, output| firrtl_compile_helper(e, input[0], output[0], true),
-    );
-}
diff --git a/fud2/src/main.rs b/fud2/src/main.rs
index 76c5772876..538611658d 100644
--- a/fud2/src/main.rs
+++ b/fud2/src/main.rs
@@ -4,15 +4,11 @@ use fud_core::{cli::CliStart, DriverBuilder};
 fn main() -> anyhow::Result<()> {
     let mut bld = DriverBuilder::new("fud2");
 
-    #[cfg(not(feature = "migrate_to_scripts"))]
-    fud2::build_driver(&mut bld);
-
     // In debug mode, get resources from the source directory.
     #[cfg(debug_assertions)]
     {
         bld.rsrc_dir(manifest_dir_macros::directory_path!("rsrc"));
 
-        #[cfg(feature = "migrate_to_scripts")]
         bld.scripts_dir(manifest_dir_macros::directory_path!("scripts"));
     }
 
@@ -27,7 +23,6 @@ fn main() -> anyhow::Result<()> {
                 .collect()
         });
 
-        #[cfg(feature = "migrate_to_scripts")]
         bld.script_files({
             const DIR: include_dir::Dir =
                 include_dir::include_dir!("$CARGO_MANIFEST_DIR/scripts");
@@ -40,10 +35,7 @@ fn main() -> anyhow::Result<()> {
     // Get config values from cli.
     let config = Fud2CliExt::config_from_cli(&bld.name)?;
 
-    #[cfg(feature = "migrate_to_scripts")]
-    {
-        bld = bld.load_plugins(&config)?;
-    }
+    bld = bld.load_plugins(&config)?;
 
     let driver = bld.build();
     Fud2CliExt::cli(&driver, &config)
diff --git a/interp/Cargo.toml b/interp/Cargo.toml
index eed12da8d4..8d99d1a848 100644
--- a/interp/Cargo.toml
+++ b/interp/Cargo.toml
@@ -43,11 +43,10 @@ calyx-utils = { path = "../calyx-utils", features = ["serialize"] }
 calyx-opt = { path = "../calyx-opt" }
 calyx-frontend = { path = "../calyx-frontend" }
 
-btor2i = { path = "../tools/btor2/btor2i" }
-
 ciborium = "0.2.2"
-baa = { version = "0.6.0", features = ["bigint", "serde1", "fraction1"] }
+baa = { version = "0.14.0", features = ["bigint", "serde1", "fraction1"] }
 fst-writer = "0.2.1"
+bon = "2.3"
 
 [dev-dependencies]
 proptest = "1.0.0"
diff --git a/interp/src/configuration.rs b/interp/src/configuration.rs
index 548418b75d..9bd8baf7b7 100644
--- a/interp/src/configuration.rs
+++ b/interp/src/configuration.rs
@@ -1,101 +1,50 @@
+use bon::Builder;
+
 // this can be a copy type because it's just a bunch of bools
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(Debug, Default, Clone, Copy, Builder)]
 /// Configuration struct which controls runtime behavior
 pub struct Config {
-    /// enables/disables "sloppy" interpretation which returns 0 for invalid indicies
-    /// rather than erroring
-    pub allow_invalid_memory_access: bool,
-    /// upgrades overflow/underflow warnings into errors
-    pub error_on_overflow: bool,
-    /// permits "sloppy" interpretation with parallel blocks
-    pub allow_par_conflicts: bool,
-    /// suppresses warnings
-    pub quiet: bool,
     /// dump registers as single entry memories
     pub dump_registers: bool,
     /// dumps all memories rather than just external ones
     pub dump_all_memories: bool,
 }
 
-#[derive(Default)]
-/// A builder for [`Config`] struct.
-///
-/// ```
-/// # use interp::configuration::ConfigBuilder;
-/// let config = ConfigBuilder::new()
-///     .quiet(false)
-///     .allow_invalid_memory_access(true)
-///     .dump_all_memories(true)
-///     .build();
-/// assert_eq!(config.quiet, false);
-/// assert_eq!(config.allow_invalid_memory_access, true);
-/// assert_eq!(config.dump_all_memories, true);
-/// assert_eq!(config.dump_registers, false);
-/// ```
-pub struct ConfigBuilder {
-    allow_invalid_memory_access: Option<bool>,
-    error_on_overflow: Option<bool>,
-    allow_par_conflicts: Option<bool>,
-    quiet: Option<bool>,
-    dump_registers: Option<bool>,
-    dump_all_memories: Option<bool>,
+/// Configuration struct containing options affecting the simulation time
+/// decisions.
+#[derive(Debug, Default, Clone, Copy, Builder)]
+pub struct RuntimeConfig {
+    /// enables data race checking
+    pub check_data_race: bool,
+    /// enables debug logging
+    pub debug_logging: bool,
+    /// suppresses warnings
+    pub quiet: bool,
+    /// enables/disables "sloppy" interpretation which returns 0 for invalid indices
+    /// rather than erroring. (Currently defunct)
+    pub allow_invalid_memory_access: bool,
+    /// upgrades overflow/underflow warnings into errors (currently defunct)
+    pub error_on_overflow: bool,
+    /// Check undefined guards
+    pub undef_guard_check: bool,
 }
 
-impl ConfigBuilder {
-    /// Create a new [`ConfigBuilder`] with all options unset. This is the same
-    /// as calling [`ConfigBuilder::default`].
-    #[inline]
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    /// Sets the quiet flag to the given value.
-    pub fn quiet(mut self, value: bool) -> Self {
-        self.quiet = Some(value);
-        self
-    }
-
-    /// Sets the `allow_invalid_memory_access` flag to the given value.
-    pub fn allow_invalid_memory_access(mut self, value: bool) -> Self {
-        self.allow_invalid_memory_access = Some(value);
-        self
-    }
-
-    /// Sets the `error_on_overflow` flag to the given value.
-    pub fn error_on_overflow(mut self, value: bool) -> Self {
-        self.error_on_overflow = Some(value);
-        self
-    }
-
-    /// Sets the `allow_par_conflicts` flag to the given value.
-    pub fn allow_par_conflicts(mut self, value: bool) -> Self {
-        self.allow_par_conflicts = Some(value);
-        self
-    }
-
-    /// Sets the `dump_registers` flag to the given value.
-    pub fn dump_registers(mut self, value: bool) -> Self {
-        self.dump_registers = Some(value);
-        self
-    }
-    /// Sets the `dump_all_memories` flag to the given value.
-    pub fn dump_all_memories(mut self, value: bool) -> Self {
-        self.dump_all_memories = Some(value);
-        self
-    }
-
-    /// Builds a [`Config`] from the current state of the [`ConfigBuilder`]. For
-    /// any unset options, the default value will be used.
-    pub fn build(self) -> Config {
-        Config {
-            allow_par_conflicts: self.allow_par_conflicts.unwrap_or_default(),
-            error_on_overflow: self.error_on_overflow.unwrap_or_default(),
-            quiet: self.quiet.unwrap_or_default(),
-            allow_invalid_memory_access: self
-                .allow_invalid_memory_access
-                .unwrap_or_default(),
-            dump_registers: self.dump_registers.unwrap_or_default(),
-            dump_all_memories: self.dump_all_memories.unwrap_or_default(),
+impl RuntimeConfig {
+    pub fn get_logging_config(&self) -> LoggingConfig {
+        LoggingConfig {
+            quiet: self.quiet,
+            debug_logging: self.debug_logging,
         }
     }
 }
+
+/// Configuration struct describing what settings a logger should be created
+/// with.
+pub struct LoggingConfig {
+    /// Whether or not to silence non-error messages. Will be overridden by
+    /// `debug_logging` if set to true.
+    pub quiet: bool,
+    /// Whether or not to enable debug logging. If set to true, will override
+    /// `quiet`.
+    pub debug_logging: bool,
+}
diff --git a/interp/src/debugger/commands/command_parser.rs b/interp/src/debugger/commands/command_parser.rs
index 0dda9c6d8e..c48179d1a9 100644
--- a/interp/src/debugger/commands/command_parser.rs
+++ b/interp/src/debugger/commands/command_parser.rs
@@ -1,5 +1,8 @@
-use super::core::{
-    Command, ParsedBreakPointID, ParsedGroupName, PrintMode, WatchPosition,
+use super::{
+    core::{
+        Command, ParsedBreakPointID, ParsedGroupName, PrintMode, WatchPosition,
+    },
+    PrintCommand,
 };
 use baa::WidthInt;
 use pest_consume::{match_nodes, Error, Parser};
@@ -7,7 +10,7 @@ use pest_consume::{match_nodes, Error, Parser};
 type ParseResult<T> = std::result::Result<T, Error<Rule>>;
 type Node<'i> = pest_consume::Node<'i, Rule, ()>;
 
-use crate::{errors::InterpreterResult, serialization::PrintCode};
+use crate::{errors::CiderResult, serialization::PrintCode};
 
 // include the grammar file so that Cargo knows to rebuild this file on grammar changes
 const _GRAMMAR: &str = include_str!("commands.pest");
@@ -21,10 +24,15 @@ impl CommandParser {
     fn EOI(_input: Node) -> ParseResult<()> {
         Ok(())
     }
+
     fn code_calyx(_input: Node) -> ParseResult<()> {
         Ok(())
     }
 
+    fn code_nodes(_input: Node) -> ParseResult<()> {
+        Ok(())
+    }
+
     // ----------------------
 
     fn help(_input: Node) -> ParseResult<Command> {
@@ -60,8 +68,9 @@ impl CommandParser {
 
     fn comm_where(input: Node) -> ParseResult<Command> {
         Ok(match_nodes!(input.into_children();
-            [code_calyx(_)] => Command::PrintPC(true),
-            [] => Command::PrintPC(false),
+            [code_calyx(_)] => Command::PrintPC(PrintCommand::PrintCalyx),
+            [code_nodes(_)] => Command::PrintPC(PrintCommand::PrintNodes),
+            [] => Command::PrintPC(PrintCommand::Normal),
         ))
     }
 
@@ -287,7 +296,7 @@ impl CommandParser {
 }
 
 /// Parse the given string into a debugger command.
-pub fn parse_command(input_str: &str) -> InterpreterResult<Command> {
+pub fn parse_command(input_str: &str) -> CiderResult<Command> {
     let inputs = CommandParser::parse(Rule::command, input_str)?;
     let input = inputs.single()?;
     Ok(CommandParser::command(input)?)
diff --git a/interp/src/debugger/commands/commands.pest b/interp/src/debugger/commands/commands.pest
index 799971648c..7a1920c680 100644
--- a/interp/src/debugger/commands/commands.pest
+++ b/interp/src/debugger/commands/commands.pest
@@ -14,6 +14,7 @@ pc_s       = { ^"s" }
 pc_ufx     = { ^"u." ~ num }
 pc_sfx     = { ^"s." ~ num }
 code_calyx = { ^"calyx" }
+code_nodes = {^"nodes"}
 
 print_code =  {
     "\\" ~ (pc_ufx | pc_sfx | pc_s | pc_un)
@@ -67,7 +68,7 @@ disable_watch = { (^"disable-watch " | ^"disw ") ~ brk_id+ }
 
 exit = { ^"exit" | ^"quit" }
 
-comm_where = { (^"where" | "pc") ~ (code_calyx)? }
+comm_where = { (^"where" | "pc") ~ (code_calyx | code_nodes)? }
 
 explain = { ^"explain" }
 
diff --git a/interp/src/debugger/commands/core.rs b/interp/src/debugger/commands/core.rs
index 46f86279bb..37148ef8a0 100644
--- a/interp/src/debugger/commands/core.rs
+++ b/interp/src/debugger/commands/core.rs
@@ -299,6 +299,13 @@ impl From<(Vec<Path>, Option<PrintCode>, PrintMode)> for PrintTuple {
     }
 }
 
+// Different types of printing commands
+pub enum PrintCommand {
+    Normal,
+    PrintCalyx,
+    PrintNodes,
+}
+
 /// A command that can be sent to the debugger.
 pub enum Command {
     /// Advance the execution by a given number of steps (cycles).
@@ -345,7 +352,7 @@ pub enum Command {
         PrintMode,
     ),
     /// Print the current program counter
-    PrintPC(bool),
+    PrintPC(PrintCommand),
     /// Show command examples
     Explain,
     /// Restart the debugger from the beginning of the execution. Command history, breakpoints, watchpoints, etc. are preserved.
diff --git a/interp/src/debugger/debugger_core.rs b/interp/src/debugger/debugger_core.rs
index 863d42b4aa..e9fc878c27 100644
--- a/interp/src/debugger/debugger_core.rs
+++ b/interp/src/debugger/debugger_core.rs
@@ -5,8 +5,11 @@ use super::{
     source::structures::NewSourceMap,
 };
 use crate::{
-    debugger::{source::SourceMap, unwrap_error_message},
-    errors::{InterpreterError, InterpreterResult},
+    configuration::RuntimeConfig,
+    debugger::{
+        commands::PrintCommand, source::SourceMap, unwrap_error_message,
+    },
+    errors::{CiderError, CiderResult},
     flatten::{
         flat_ir::prelude::GroupIdx,
         setup_simulation_with_metadata,
@@ -86,7 +89,7 @@ impl OwnedDebugger {
     pub fn from_file(
         file: &FilePath,
         lib_path: &FilePath,
-    ) -> InterpreterResult<(Self, NewSourceMap)> {
+    ) -> CiderResult<(Self, NewSourceMap)> {
         let (ctx, map) = setup_simulation_with_metadata(
             &Some(PathBuf::from(file)),
             lib_path,
@@ -94,7 +97,7 @@ impl OwnedDebugger {
         )?;
 
         let debugger: Debugger<Rc<Context>> =
-            Self::new(Rc::new(ctx), &None, &None, false)?;
+            Self::new(Rc::new(ctx), &None, &None, RuntimeConfig::default())?;
 
         Ok((debugger, map))
     }
@@ -106,13 +109,13 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
         program_context: C,
         data_file: &Option<std::path::PathBuf>,
         wave_file: &Option<std::path::PathBuf>,
-        check_data_races: bool,
-    ) -> InterpreterResult<Self> {
+        runtime_config: RuntimeConfig,
+    ) -> CiderResult<Self> {
         let mut interpreter = Simulator::build_simulator(
             program_context.clone(),
             data_file,
             wave_file,
-            check_data_races,
+            runtime_config,
         )?;
         interpreter.converge()?;
 
@@ -155,7 +158,7 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
     }
 
     // Go to next step
-    pub fn step(&mut self, n: u32) -> InterpreterResult<ProgramStatus> {
+    pub fn step(&mut self, n: u32) -> CiderResult<ProgramStatus> {
         self.do_step(n)?;
 
         Ok(self.status())
@@ -173,7 +176,7 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
         self.manipulate_breakpoint(Command::Delete(parsed_bp_ids));
     }
     #[inline]
-    fn do_step(&mut self, n: u32) -> InterpreterResult<()> {
+    fn do_step(&mut self, n: u32) -> CiderResult<()> {
         for _ in 0..n {
             self.interpreter.step()?;
         }
@@ -181,7 +184,7 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
         Ok(())
     }
 
-    fn do_continue(&mut self) -> InterpreterResult<()> {
+    fn do_continue(&mut self) -> CiderResult<()> {
         self.debugging_context
             .set_current_time(self.interpreter.get_currently_running_groups());
 
@@ -237,7 +240,7 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
     pub fn main_loop(
         mut self,
         info: Option<DebuggerInfo>,
-    ) -> InterpreterResult<DebuggerReturnStatus> {
+    ) -> CiderResult<DebuggerReturnStatus> {
         let (input_stream, dbg_ctx) = info
             .map(|x| (Some(x.input_stream), Some(x.ctx)))
             .unwrap_or_else(|| (None, None));
@@ -268,9 +271,9 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
                     c
                 }
                 Err(e) => match *e {
-                    InterpreterError::InvalidCommand(_)
-                    | InterpreterError::UnknownCommand(_)
-                    | InterpreterError::ParseError(_) => {
+                    CiderError::InvalidCommand(_)
+                    | CiderError::UnknownCommand(_)
+                    | CiderError::ParseError(_) => {
                         println!("Error: {}", e.red().bold());
                         err_count += 1;
                         if err_count == 3 {
@@ -367,10 +370,15 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
                 Command::InfoWatch => self
                     .debugging_context
                     .print_watchpoints(self.interpreter.env()),
-                Command::PrintPC(_override_flag) => {
-                    self.interpreter.print_pc();
-                }
 
+                Command::PrintPC(print_mode) => match print_mode {
+                    PrintCommand::Normal | PrintCommand::PrintCalyx => {
+                        self.interpreter.print_pc();
+                    }
+                    PrintCommand::PrintNodes => {
+                        self.interpreter.print_pc_string();
+                    }
+                },
                 Command::Explain => {
                     print!("{}", Command::get_explain_string())
                 }
@@ -393,9 +401,9 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
             let comm = match comm {
                 Ok(c) => c,
                 Err(e) => match *e {
-                    InterpreterError::InvalidCommand(_)
-                    | InterpreterError::UnknownCommand(_)
-                    | InterpreterError::ParseError(_) => {
+                    CiderError::InvalidCommand(_)
+                    | CiderError::UnknownCommand(_)
+                    | CiderError::ParseError(_) => {
                         println!("Error: {}", e.red().bold());
                         continue;
                     }
@@ -491,7 +499,7 @@ impl<C: AsRef<Context> + Clone> Debugger<C> {
     fn do_step_over(
         &mut self,
         target: super::commands::ParsedGroupName,
-    ) -> Result<(), crate::errors::BoxedInterpreterError> {
+    ) -> Result<(), crate::errors::BoxedCiderError> {
         let target = match target.lookup_group(self.program_context.as_ref()) {
             Ok(v) => v,
             Err(e) => {
diff --git a/interp/src/debugger/io_utils.rs b/interp/src/debugger/io_utils.rs
index 9c59fae7ce..6ad58ee0e8 100644
--- a/interp/src/debugger/io_utils.rs
+++ b/interp/src/debugger/io_utils.rs
@@ -1,6 +1,6 @@
 use super::commands::parse_command;
 use super::commands::Command;
-use crate::errors::InterpreterResult;
+use crate::errors::CiderResult;
 use rustyline::Editor;
 use std::collections::VecDeque;
 
@@ -12,13 +12,13 @@ pub struct Input {
 }
 
 impl Input {
-    pub fn new() -> InterpreterResult<Self> {
+    pub fn new() -> CiderResult<Self> {
         Ok(Self {
             buffer: Editor::new()?,
             command_buffer: VecDeque::default(),
         })
     }
-    pub fn next_command(&mut self) -> InterpreterResult<Command> {
+    pub fn next_command(&mut self) -> CiderResult<Command> {
         if !self.command_buffer.is_empty() {
             return Ok(self.command_buffer.pop_front().unwrap());
         }
diff --git a/interp/src/debugger/source/metadata_parser.rs b/interp/src/debugger/source/metadata_parser.rs
index ffa38f9bcb..74fb70366b 100644
--- a/interp/src/debugger/source/metadata_parser.rs
+++ b/interp/src/debugger/source/metadata_parser.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
 
 use pest_consume::{match_nodes, Error, Parser};
 
-use crate::errors::InterpreterResult;
+use crate::errors::CiderResult;
 
 use super::structures::{NamedTag, SourceMap};
 
@@ -87,7 +87,7 @@ impl MetadataParser {
     }
 }
 
-pub fn parse_metadata(input_str: &str) -> InterpreterResult<SourceMap> {
+pub fn parse_metadata(input_str: &str) -> CiderResult<SourceMap> {
     let inputs = MetadataParser::parse(Rule::metadata, input_str)?;
     let input = inputs.single()?;
     Ok(MetadataParser::metadata(input)?)
diff --git a/interp/src/debugger/source/new_parser.rs b/interp/src/debugger/source/new_parser.rs
index 170b13fee5..bc3a7f810c 100644
--- a/interp/src/debugger/source/new_parser.rs
+++ b/interp/src/debugger/source/new_parser.rs
@@ -1,5 +1,5 @@
 use super::structures::{GroupContents, NewSourceMap};
-use crate::errors::InterpreterResult;
+use crate::errors::CiderResult;
 use pest_consume::{match_nodes, Error, Parser};
 use std::collections::HashMap;
 type ParseResult<T> = std::result::Result<T, Error<Rule>>;
@@ -47,7 +47,7 @@ impl MetadataParser {
     }
 }
 
-pub fn parse_metadata(input_str: &str) -> InterpreterResult<NewSourceMap> {
+pub fn parse_metadata(input_str: &str) -> CiderResult<NewSourceMap> {
     let inputs = MetadataParser::parse(Rule::metadata, input_str)?;
     let input = inputs.single()?;
     Ok(MetadataParser::metadata(input)?)
diff --git a/interp/src/debugger/source/structures.rs b/interp/src/debugger/source/structures.rs
index 44dc3a34bf..c6aeed1af2 100644
--- a/interp/src/debugger/source/structures.rs
+++ b/interp/src/debugger/source/structures.rs
@@ -1,7 +1,7 @@
 //! This module contains the data structures used by the debugger for source mapping
 use std::{collections::HashMap, fs, path::PathBuf};
 
-use crate::errors::InterpreterResult;
+use crate::errors::CiderResult;
 
 #[derive(Hash, PartialEq, Eq, Debug, Clone)]
 pub struct NamedTag(u64, String);
@@ -67,9 +67,7 @@ impl SourceMap {
             .or_else(|| self.0.get(&NamedTag(key.0, "".to_string())))
     }
 
-    pub fn from_file(
-        path: &Option<PathBuf>,
-    ) -> InterpreterResult<Option<Self>> {
+    pub fn from_file(path: &Option<PathBuf>) -> CiderResult<Option<Self>> {
         if let Some(path) = path {
             let v = fs::read(path)?;
             let file_contents = std::str::from_utf8(&v)?;
@@ -81,7 +79,7 @@ impl SourceMap {
         }
     }
 
-    pub fn from_string<S>(input: S) -> InterpreterResult<Self>
+    pub fn from_string<S>(input: S) -> CiderResult<Self>
     where
         S: AsRef<str>,
     {
diff --git a/interp/src/errors.rs b/interp/src/errors.rs
index 4bb14f1095..43b1e2c775 100644
--- a/interp/src/errors.rs
+++ b/interp/src/errors.rs
@@ -1,70 +1,67 @@
-use crate::flatten::{
-    flat_ir::{
-        base::{AssignmentWinner, ComponentIdx, GlobalCellIdx, GlobalPortIdx},
-        prelude::AssignedValue,
+use crate::{
+    flatten::{
+        flat_ir::{
+            base::{
+                AssignmentIdx, AssignmentWinner, ComponentIdx, GlobalCellIdx,
+                GlobalPortIdx,
+            },
+            prelude::AssignedValue,
+        },
+        structures::environment::{clock::ClockError, Environment},
     },
-    structures::environment::{clock::ClockError, Environment},
+    serialization::Shape,
 };
-use baa::{BitVecOps, BitVecValue};
-use calyx_ir::Id;
+use baa::BitVecOps;
 use calyx_utils::{Error as CalyxError, MultiError as CalyxMultiError};
 use owo_colors::OwoColorize;
 use rustyline::error::ReadlineError;
 use thiserror::Error;
 
-/// A type alias for a result with an [BoxedInterpreterError] as the error type
-pub type InterpreterResult<T> = Result<T, BoxedInterpreterError>;
+use std::fmt::Write;
+
+/// A type alias for a result with an [BoxedCiderError] as the error type
+pub type CiderResult<T> = Result<T, BoxedCiderError>;
 
 /// A wrapper type for [InterpreterError]. This exists to allow a smaller return
 /// size for results since the error type is large.
-pub struct BoxedInterpreterError(Box<InterpreterError>);
+pub struct BoxedCiderError(Box<CiderError>);
 
-impl BoxedInterpreterError {
+impl BoxedCiderError {
     /// Get a mutable reference to the inner error
-    pub fn inner_mut(&mut self) -> &mut InterpreterError {
+    pub fn inner_mut(&mut self) -> &mut CiderError {
         &mut self.0
     }
-
-    pub fn prettify_message<
-        C: AsRef<crate::flatten::structures::context::Context> + Clone,
-    >(
-        mut self,
-        env: &Environment<C>,
-    ) -> Self {
-        self.0 = Box::new(self.0.prettify_message(env));
-        self
-    }
 }
 
-impl std::fmt::Display for BoxedInterpreterError {
+impl std::fmt::Display for BoxedCiderError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&*self.0, f)
     }
 }
 
-impl std::fmt::Debug for BoxedInterpreterError {
+impl std::fmt::Debug for BoxedCiderError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&self, f)
     }
 }
 
-impl std::error::Error for BoxedInterpreterError {
+impl std::error::Error for BoxedCiderError {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         self.0.source()
     }
 }
 
-impl std::ops::Deref for BoxedInterpreterError {
-    type Target = InterpreterError;
+impl std::ops::Deref for BoxedCiderError {
+    type Target = CiderError;
 
     fn deref(&self) -> &Self::Target {
         &self.0
     }
 }
 
-impl<T> From<T> for BoxedInterpreterError
+impl<T> From<T> for BoxedCiderError
 where
-    T: Into<InterpreterError>,
+    T: Into<CiderError>,
 {
     fn from(e: T) -> Self {
         Self(Box::new(T::into(e)))
@@ -74,7 +71,7 @@ where
 /// An enum representing the different types of errors that can occur during
 /// simulation and debugging
 #[derive(Error)]
-pub enum InterpreterError {
+pub enum CiderError {
     /// The given debugger command is invalid/malformed
     #[error("invalid command - {0}")]
     InvalidCommand(String),
@@ -122,31 +119,6 @@ pub enum InterpreterError {
     #[error("no main component")]
     MissingMainComponent,
 
-    #[error(
-        "conflicting assigns
-        1. {a1}
-        2. {a2}
-    "
-    )]
-    FlatConflictingAssignments {
-        target: GlobalPortIdx,
-        a1: AssignedValue,
-        a2: AssignedValue,
-    },
-
-    /// A currently defunct error type for cross branch conflicts
-    #[error(
-        "par assignments not disjoint: {parent_id}.{port_id}
-    1. {v1:?}
-    2. {v2:?}"
-    )]
-    ParOverlap {
-        port_id: Id,
-        parent_id: Id,
-        v1: BitVecValue,
-        v2: BitVecValue,
-    },
-
     #[error("{mem_dim} Memory given initialization data with invalid dimension.
     When flattened, expected {expected} entries, but the memory was supplied with {given} entries instead.
     Please ensure that the dimensions of your input memories match their initialization data in the supplied data file")]
@@ -156,28 +128,64 @@ pub enum InterpreterError {
         given: usize,
     },
 
-    #[error("invalid memory access to memory {}. Given index ({}) but memory has dimension ({})", name, access.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "), dims.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))]
-    InvalidMemoryAccess {
-        access: Vec<u64>,
-        dims: Vec<u64>,
-        name: Id,
-    },
-
-    // TODO (Griffin): Make this error message better please
-    #[error("Computation has under/overflowed its bounds")]
-    OverflowError,
-
     /// A wrapper for IO errors
     #[error(transparent)]
     IOError(#[from] std::io::Error),
 
-    /// The error for attempting to write `undef` values to a register or
-    /// memory. Contains the name of the register or memory as a string
-    //TODO Griffin: Make this more descriptive
-    #[error(
-        "Attempted to write an undefined value to register or memory named \"{0:?}\""
-    )]
-    UndefinedWrite(GlobalCellIdx),
+    /// A wrapper for serialization errors
+    #[error(transparent)]
+    SerializationError(#[from] crate::serialization::SerializationError),
+
+    /// A nonspecific error, used for arbitrary messages
+    #[error("{0}")]
+    GenericError(String),
+}
+
+pub type RuntimeResult<T> = Result<T, BoxedRuntimeError>;
+
+#[derive(Debug, Error)]
+#[error(transparent)]
+pub struct BoxedRuntimeError(#[from] Box<RuntimeError>);
+
+impl<Inner: Into<RuntimeError>> From<Inner> for BoxedRuntimeError {
+    fn from(value: Inner) -> Self {
+        Self(Box::new(value.into()))
+    }
+}
+
+impl BoxedRuntimeError {
+    pub fn prettify_message<
+        C: AsRef<crate::flatten::structures::context::Context> + Clone,
+    >(
+        self,
+        env: &Environment<C>,
+    ) -> CiderError {
+        self.0.prettify_message(env)
+    }
+}
+
+#[derive(Error, Debug)]
+#[error(
+    "conflicting assigns
+        1. {a1}
+        2. {a2}
+    "
+)]
+pub struct ConflictingAssignments {
+    pub target: GlobalPortIdx,
+    pub a1: AssignedValue,
+    pub a2: AssignedValue,
+}
+
+#[derive(Error, Debug)]
+pub enum RuntimeError {
+    #[error(transparent)]
+    ClockError(#[from] ClockError),
+
+    #[error("Some guards are undefined: {0:?}")]
+    UndefinedGuardError(
+        Vec<(GlobalCellIdx, AssignmentIdx, Vec<GlobalPortIdx>)>,
+    ),
 
     /// The error for attempting to write to an undefined memory address. This
     /// is distinct from writing to an out of bounds address.
@@ -197,51 +205,62 @@ pub enum InterpreterError {
     #[error("Attempted to undefine a defined port \"{0:?}\"")]
     UndefiningDefinedPort(GlobalPortIdx),
 
-    /// A wrapper for serialization errors
-    #[error(transparent)]
-    SerializationError(#[from] crate::serialization::SerializationError),
+    /// The error for attempting to write `undef` values to a register or
+    /// memory. Contains the name of the register or memory as a string
+    //TODO Griffin: Make this more descriptive
+    #[error(
+        "Attempted to write an undefined value to register or memory named \"{0:?}\""
+    )]
+    UndefinedWrite(GlobalCellIdx),
 
-    #[error(transparent)]
-    ClockError(#[from] ClockError),
+    #[error("invalid memory access to memory. Given index ({}) but memory has dimension ", access.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "))]
+    InvalidMemoryAccess {
+        access: Vec<u64>,
+        dims: Shape,
+        idx: GlobalCellIdx,
+    },
 
-    /// A nonspecific error, used for arbitrary messages
-    #[error("{0}")]
-    GenericError(String),
+    // TODO (Griffin): Make this error message better please
+    #[error("Computation has under/overflowed its bounds")]
+    OverflowError,
+
+    #[error(transparent)]
+    ConflictingAssignments(ConflictingAssignments),
 }
 
 // this is silly but needed to make the program print something sensible when returning
 // a result from `main`
-impl std::fmt::Debug for InterpreterError {
+impl std::fmt::Debug for CiderError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&self, f)
     }
 }
 
-impl From<CalyxError> for InterpreterError {
+impl From<CalyxError> for CiderError {
     fn from(e: CalyxError) -> Self {
         Self::CompilerError(Box::new(e))
     }
 }
 
-impl From<CalyxMultiError> for InterpreterError {
+impl From<CalyxMultiError> for CiderError {
     fn from(e: CalyxMultiError) -> Self {
         Self::CompilerMultiError(Box::new(e))
     }
 }
 
-impl From<std::str::Utf8Error> for InterpreterError {
+impl From<std::str::Utf8Error> for CiderError {
     fn from(err: std::str::Utf8Error) -> Self {
         CalyxError::invalid_file(err.to_string()).into()
     }
 }
 
-impl InterpreterError {
+impl RuntimeError {
     pub fn prettify_message<
         C: AsRef<crate::flatten::structures::context::Context> + Clone,
     >(
         self,
         env: &Environment<C>,
-    ) -> Self {
+    ) -> CiderError {
         fn assign_to_string<C: AsRef<crate::flatten::structures::context::Context> + Clone>(
             assign: &AssignedValue,
             env: &Environment<C>,
@@ -252,7 +271,7 @@ impl InterpreterError {
             match assign.winner() {
                 AssignmentWinner::Cell => ("Cell".to_string(), None),
                 AssignmentWinner::Implicit => ("Implicit".to_string(), None),
-                AssignmentWinner::Assign(idx) => {
+                AssignmentWinner::Assign(idx, _) => {
                     let (comp, loc) =
                         env.ctx().find_assignment_definition(*idx);
 
@@ -280,7 +299,7 @@ impl InterpreterError {
         }
 
         match self {
-            InterpreterError::FlatConflictingAssignments { target, a1, a2 } => {
+            RuntimeError::ConflictingAssignments(ConflictingAssignments { target, a1, a2 }) => {
                 let (a1_str, a1_source) = assign_to_string(&a1, env);
                 let (a2_str, a2_source) = assign_to_string(&a2, env);
 
@@ -295,22 +314,38 @@ impl InterpreterError {
 
                 let target = env.get_full_name(target);
 
-                InterpreterError::GenericError(
+                CiderError::GenericError(
                     format!("conflicting assignments to port \"{target}\":\n 1. assigned {a1_v} by {a1_str}{a1_source}\n 2. assigned {a2_v} by {a2_str}{a2_source}")
                 )
             }
-            InterpreterError::UndefinedWrite(c) => InterpreterError::GenericError(format!("Attempted to write an undefined value to register or memory named \"{}\"", env.get_full_name(c))),
-            InterpreterError::UndefinedWriteAddr(c) => InterpreterError::GenericError(format!("Attempted to write to an undefined memory address in memory named \"{}\"", env.get_full_name(c))),
-            InterpreterError::UndefinedReadAddr(c) => InterpreterError::GenericError(format!("Attempted to read from an undefined memory address from memory named \"{}\"", env.get_full_name(c))),
-            InterpreterError::ClockError(clk) => {
+            RuntimeError::UndefinedWrite(c) => CiderError::GenericError(format!("Attempted to write an undefined value to register or memory named \"{}\"", env.get_full_name(c))),
+            RuntimeError::UndefinedWriteAddr(c) => CiderError::GenericError(format!("Attempted to write to an undefined memory address in memory named \"{}\"", env.get_full_name(c))),
+            RuntimeError::UndefinedReadAddr(c) => CiderError::GenericError(format!("Attempted to read from an undefined memory address from memory named \"{}\"", env.get_full_name(c))),
+            RuntimeError::ClockError(clk) => {
                 match clk {
-                    ClockError::ReadWrite(c) => InterpreterError::GenericError(format!("Concurrent read & write to the same register/memory {}", env.get_full_name(c).underline())),
-                    ClockError::WriteWrite(c) => InterpreterError::GenericError(format!("Concurrent writes to the same register/memory {}", env.get_full_name(c).underline())),
-                    _ => InterpreterError::ClockError(clk),
+                    ClockError::ReadWrite(c) => CiderError::GenericError(format!("Concurrent read & write to the same register/memory {}", env.get_full_name(c).underline())),
+                    ClockError::WriteWrite(c) => CiderError::GenericError(format!("Concurrent writes to the same register/memory {}", env.get_full_name(c).underline())),
+                    c => CiderError::GenericError(format!("Unexpected clock error: {c:?}")),
                 }
             }
-            InterpreterError::UndefiningDefinedPort(p) => InterpreterError::GenericError(format!("Attempted to undefine a defined port \"{}\"", env.get_full_name(p))),
-            e => e,
+            RuntimeError::UndefiningDefinedPort(p) => CiderError::GenericError(format!("Attempted to undefine a defined port \"{}\"", env.get_full_name(p))),
+            RuntimeError::UndefinedGuardError(v) => {
+                let mut message = String::from("Some guards contained undefined values after convergence:\n");
+                for (cell, assign, ports) in v {
+                    writeln!(message, "({}) in assignment {}", env.get_full_name(cell), env.ctx().printer().print_assignment(env.get_component_idx(cell).unwrap(), assign).bold()).unwrap();
+                    for port in ports {
+                        writeln!(message, "    {} is undefined", env.get_full_name(port).yellow()).unwrap();
+                    }
+                    writeln!(message).unwrap()
+                }
+
+                CiderError::GenericError(message)
+            }
+            RuntimeError::InvalidMemoryAccess { access, dims, idx } => {
+                CiderError::GenericError(format!("Invalid memory access to memory named \"{}\". Given index ({}) but memory has dimension {}", env.get_full_name(idx), access.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", "), dims.as_string()))
+            },
+            RuntimeError::OverflowError => todo!(),
+
         }
     }
 }
diff --git a/interp/src/flatten/flat_ir/base.rs b/interp/src/flatten/flat_ir/base.rs
index 6326adfe86..93b8c100cc 100644
--- a/interp/src/flatten/flat_ir/base.rs
+++ b/interp/src/flatten/flat_ir/base.rs
@@ -22,7 +22,7 @@ use baa::{BitVecOps, BitVecValue};
 pub struct ComponentIdx(u32);
 impl_index!(ComponentIdx);
 
-/// An index for auxillary definition information for cells. This is used to
+/// An index for auxiliary definition information for cells. This is used to
 /// index into the [`SecondaryContext`][]
 ///
 /// [`SecondaryContext`]: crate::flatten::structures::context::SecondaryContext::local_cell_defs
@@ -30,7 +30,7 @@ impl_index!(ComponentIdx);
 pub struct CellDefinitionIdx(u32);
 impl_index!(CellDefinitionIdx);
 
-/// An index for auxillary definition information for ports. This is used to
+/// An index for auxiliary definition information for ports. This is used to
 /// index into the [`SecondaryContext`][]
 ///
 /// [`SecondaryContext`]: crate::flatten::structures::context::SecondaryContext::local_port_defs
@@ -38,7 +38,7 @@ impl_index!(CellDefinitionIdx);
 pub struct PortDefinitionIdx(u32);
 impl_index!(PortDefinitionIdx);
 
-/// An index for auxillary definition information for ref cells. This is used to
+/// An index for auxiliary definition information for ref cells. This is used to
 /// index into the [`SecondaryContext`][]
 ///
 /// [`SecondaryContext`]: crate::flatten::structures::context::SecondaryContext::ref_cell_defs
@@ -46,7 +46,7 @@ impl_index!(PortDefinitionIdx);
 pub struct RefCellDefinitionIdx(u32);
 impl_index!(RefCellDefinitionIdx);
 
-/// An index for auxillary definition information for ref ports. This is used to
+/// An index for auxiliary definition information for ref ports. This is used to
 /// index into the [`SecondaryContext`][]
 ///
 /// [`SecondaryContext`]: crate::flatten::structures::context::SecondaryContext::ref_port_defs
@@ -118,7 +118,7 @@ impl_index!(LocalRefCellOffset);
 /// Enum used in assignments to encapsulate the different types of port
 /// references these are always relative to a component's base-point and must be
 /// converted to global references when used.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum PortRef {
     /// A port belonging to a non-ref cell/group in the current component or the
     /// component itself
@@ -400,13 +400,13 @@ pub enum AssignmentWinner {
     /// source
     Implicit,
     /// The assignment that produced this value.
-    Assign(AssignmentIdx),
+    Assign(AssignmentIdx, GlobalCellIdx),
 }
 
 impl AssignmentWinner {
     #[must_use]
     pub fn as_assign(&self) -> Option<&AssignmentIdx> {
-        if let Self::Assign(v) = self {
+        if let Self::Assign(v, _c) = self {
             Some(v)
         } else {
             None
@@ -414,9 +414,15 @@ impl AssignmentWinner {
     }
 }
 
-impl From<AssignmentIdx> for AssignmentWinner {
-    fn from(v: AssignmentIdx) -> Self {
-        Self::Assign(v)
+impl From<(AssignmentIdx, GlobalCellIdx)> for AssignmentWinner {
+    fn from((v, c): (AssignmentIdx, GlobalCellIdx)) -> Self {
+        Self::Assign(v, c)
+    }
+}
+
+impl From<(GlobalCellIdx, AssignmentIdx)> for AssignmentWinner {
+    fn from((c, v): (GlobalCellIdx, AssignmentIdx)) -> Self {
+        Self::Assign(v, c)
     }
 }
 
@@ -552,7 +558,7 @@ pub struct PortValue(Option<AssignedValue>);
 
 impl std::fmt::Display for PortValue {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:?}", self.0)
+        write!(f, "{}", self.format_value(PrintCode::Unsigned))
     }
 }
 
@@ -724,6 +730,8 @@ where
     pub parent: ComponentIdx,
     /// The prototype of the cell
     pub prototype: CellPrototype,
+    /// Whether the cell is marked with `@data`
+    pub is_data: bool,
 }
 
 impl<C> CellDefinitionInfo<C>
@@ -736,12 +744,14 @@ where
         ports: IndexRange<C>,
         parent: ComponentIdx,
         prototype: CellPrototype,
+        is_data: bool,
     ) -> Self {
         Self {
             name,
             ports,
             parent,
             prototype,
+            is_data,
         }
     }
 }
diff --git a/interp/src/flatten/flat_ir/cell_prototype.rs b/interp/src/flatten/flat_ir/cell_prototype.rs
index d7feea753d..05acd4c3d6 100644
--- a/interp/src/flatten/flat_ir/cell_prototype.rs
+++ b/interp/src/flatten/flat_ir/cell_prototype.rs
@@ -403,6 +403,30 @@ impl CellPrototype {
                         c_type: ConstantType::Primitive,
                     }
                 }
+                "std_float_const" => {
+                    get_params![params;
+                        value: "VALUE",
+                        width: "WIDTH",
+                        rep: "REP"
+                    ];
+
+                    debug_assert_eq!(
+                        rep, 0,
+                        "Only supported floating point representation is IEEE."
+                    );
+                    debug_assert!(
+                        width == 32 || width == 64,
+                        "Only 32 and 64 bit floats are supported."
+                    );
+
+                    // we can treat floating point constants like any other constant since the
+                    // frontend already converts the number to bits for us
+                    Self::Constant {
+                        value,
+                        width: width.try_into().unwrap(),
+                        c_type: ConstantType::Primitive,
+                    }
+                }
                 n @ ("std_add" | "std_sadd") => {
                     get_params![params; width: "WIDTH"];
 
diff --git a/interp/src/flatten/flat_ir/component.rs b/interp/src/flatten/flat_ir/component.rs
index 1625e618ed..3999a7f83e 100644
--- a/interp/src/flatten/flat_ir/component.rs
+++ b/interp/src/flatten/flat_ir/component.rs
@@ -7,13 +7,35 @@ use crate::flatten::structures::{
 
 use super::{control::structures::ControlIdx, prelude::*};
 
+/// Stores the definitions created under a component.
+///
+/// # Note
+/// In most cases, this is different that what is directly defined by the
+/// component as this also includes ports/cells defined by sub-components. The
+/// exceptions are the groups and comb groups which only includes those defined
+/// directly by the component.
+///
+/// For cases where only the direct definitions are needed use the offset maps
+/// in the auxiliary component info.
 #[derive(Debug, Clone)]
 pub struct DefinitionRanges {
+    /// The entire range of cells defined by this component and any
+    /// sub-component instances it contains
     cells: IndexRange<CellDefinitionIdx>,
+    /// The entire range of ports defined by this component and any
+    /// sub-component instances it contains
     ports: IndexRange<PortDefinitionIdx>,
+    /// The entire range of ref-cells defined by this component and any
+    /// sub-component instances it contains
     ref_cells: IndexRange<RefCellDefinitionIdx>,
+    /// The entire range of ref-ports defined by this component and any
+    /// sub-component instances it contains
     ref_ports: IndexRange<RefPortDefinitionIdx>,
+    /// The entire range of groups defined by this component. Does not include
+    /// sub-component instances.
     groups: IndexRange<GroupIdx>,
+    /// The entire range of comb-groups defined by this component. Does not
+    /// include sub-component instances.
     comb_groups: IndexRange<CombGroupIdx>,
 }
 
@@ -176,19 +198,20 @@ impl ComponentCore {
 pub struct AuxiliaryComponentInfo {
     /// Name of the component.
     pub name: Identifier,
-    /// The input/output signature of this component. This could probably be
-    /// rolled into a single range, or specialized construct but this is
-    /// probably fine for now.
+    /// The input ports of this component
     pub signature_in: SignatureRange,
+    /// The output ports of this component
     pub signature_out: SignatureRange,
-
     /// the definitions created by this component
     pub definitions: DefinitionRanges,
-
+    /// A map from local port offsets to their definition indices.
     pub port_offset_map: SparseMap<LocalPortOffset, PortDefinitionIdx>,
+    /// A map from ref port offsets to their definition indices
     pub ref_port_offset_map:
         SparseMap<LocalRefPortOffset, RefPortDefinitionIdx>,
+    /// A map from local cell offsets to their definition indices
     pub cell_offset_map: SparseMap<LocalCellOffset, CellDefinitionIdx>,
+    /// A map from ref cell offsets to their definition indices
     pub ref_cell_offset_map:
         SparseMap<LocalRefCellOffset, RefCellDefinitionIdx>,
 }
diff --git a/interp/src/flatten/flat_ir/control/translator.rs b/interp/src/flatten/flat_ir/control/translator.rs
index bd82720721..5efa5d284c 100644
--- a/interp/src/flatten/flat_ir/control/translator.rs
+++ b/interp/src/flatten/flat_ir/control/translator.rs
@@ -1,3 +1,5 @@
+use std::collections::HashSet;
+
 use ahash::{HashMap, HashMapExt};
 use calyx_ir::{self as cir, NumAttr, RRC};
 use itertools::Itertools;
@@ -123,7 +125,7 @@ fn translate_guard(
     // I'm just gonna opt for this. It's a onetime cost, so I'm not particularly
     // worried about it
     let mut search_stack = vec![idx];
-    let mut read_ports = vec![];
+    let mut read_ports: HashSet<PortRef> = HashSet::new();
 
     while let Some(idx) = search_stack.pop() {
         match &interp_ctx.guards[idx] {
@@ -140,17 +142,19 @@ fn translate_guard(
                 search_stack.push(*guard_idx);
             }
             Guard::Comp(_port_comp, port_ref, port_ref1) => {
-                read_ports.push(*port_ref);
-                read_ports.push(*port_ref1);
+                read_ports.insert(*port_ref);
+                read_ports.insert(*port_ref1);
             }
             Guard::Port(port_ref) => {
-                read_ports.push(*port_ref);
+                read_ports.insert(*port_ref);
             }
         }
     }
 
     if !read_ports.is_empty() {
-        interp_ctx.guard_read_map.insert_value(idx, read_ports);
+        interp_ctx
+            .guard_read_map
+            .insert_value(idx, read_ports.into_iter().collect());
     }
 
     idx
@@ -161,17 +165,30 @@ fn translate_component(
     ctx: &mut Context,
     component_id_map: &mut ComponentMapper,
 ) -> ComponentIdx {
-    let mut auxillary_component_info = AuxiliaryComponentInfo::new_with_name(
+    let mut auxiliary_component_info = AuxiliaryComponentInfo::new_with_name(
         ctx.secondary.string_table.insert(comp.name),
     );
 
     let layout = compute_local_layout(
         comp,
         ctx,
-        &mut auxillary_component_info,
+        &mut auxiliary_component_info,
         component_id_map,
     );
 
+    // Continuous Assignments
+    let cont_assignment_base = ctx.primary.assignments.peek_next_idx();
+    for assign in &comp.continuous_assignments {
+        let assign_new =
+            translate_assignment(assign, &mut ctx.primary, &layout.port_map);
+        ctx.primary.assignments.push(assign_new);
+    }
+
+    let continuous_assignments = IndexRange::new(
+        cont_assignment_base,
+        ctx.primary.assignments.peek_next_idx(),
+    );
+
     // Translate the groups
     let mut group_map = HashMap::with_capacity(comp.groups.len());
 
@@ -183,7 +200,7 @@ fn translate_component(
         let k = ctx.primary.groups.push(group_idx);
         group_map.insert(group.as_raw(), k);
     }
-    auxillary_component_info
+    auxiliary_component_info
         .set_group_range(group_base, ctx.primary.groups.peek_next_idx());
 
     let comb_group_base = ctx.primary.comb_groups.peek_next_idx();
@@ -197,7 +214,7 @@ fn translate_component(
         let k = ctx.primary.comb_groups.push(comb_grp_idx);
         comb_group_map.insert(comb_grp.as_raw(), k);
     }
-    auxillary_component_info.set_comb_group_range(
+    auxiliary_component_info.set_comb_group_range(
         comb_group_base,
         ctx.primary.comb_groups.peek_next_idx(),
     );
@@ -207,19 +224,6 @@ fn translate_component(
         groups: group_map,
     };
 
-    // Continuous Assignments
-    let cont_assignment_base = ctx.primary.assignments.peek_next_idx();
-    for assign in &comp.continuous_assignments {
-        let assign_new =
-            translate_assignment(assign, &mut ctx.primary, &layout.port_map);
-        ctx.primary.assignments.push(assign_new);
-    }
-
-    let continuous_assignments = IndexRange::new(
-        cont_assignment_base,
-        ctx.primary.assignments.peek_next_idx(),
-    );
-
     let ctrl_ref = comp.control.borrow();
 
     // do some memory slight of hand to pass the owned version rather than a ref
@@ -232,7 +236,7 @@ fn translate_component(
     let ctrl_idx_start = taken_control.peek_next_idx();
 
     let argument_tuple =
-        (group_mapper, layout, taken_ctx, auxillary_component_info);
+        (group_mapper, layout, taken_ctx, auxiliary_component_info);
 
     let control: Option<ControlIdx> =
         if matches!(*ctrl_ref, cir::Control::Empty(_)) {
@@ -250,7 +254,7 @@ fn translate_component(
     let ctrl_idx_end = taken_control.peek_next_idx();
 
     // unwrap all the stuff packed into the argument tuple
-    let (_, layout, mut taken_ctx, auxillary_component_info) = argument_tuple;
+    let (_, layout, mut taken_ctx, auxiliary_component_info) = argument_tuple;
 
     // put stuff back
     taken_ctx.primary.control = taken_control;
@@ -303,7 +307,7 @@ fn translate_component(
     let ctrl_ref = ctx.primary.components.push(comp_core);
     ctx.secondary
         .comp_aux_info
-        .insert(ctrl_ref, auxillary_component_info);
+        .insert(ctrl_ref, auxiliary_component_info);
 
     component_id_map.insert(comp.name, ctrl_ref);
     ctrl_ref
@@ -324,15 +328,13 @@ fn insert_port(
         }
         ContainmentType::Local => {
             let borrow = port.borrow();
-            let is_control = borrow.has_attribute(calyx_ir::NumAttr::Go)
-                || borrow.has_attribute(calyx_ir::NumAttr::Done)
-                || borrow.has_attribute(calyx_ir::BoolAttr::Control)
-                || (borrow.direction == calyx_ir::Direction::Inout);
+            let is_data = borrow.has_attribute(calyx_ir::BoolAttr::Data);
 
             let idx_definition = secondary_ctx.push_local_port(
                 id,
                 port.borrow().width as usize,
-                is_control,
+                is_data,
+                borrow.direction.clone(),
             );
             let local_offset = aux.port_offset_map.insert(idx_definition);
             local_offset.into()
@@ -366,6 +368,7 @@ fn insert_cell(
             range,
             comp_id,
             create_cell_prototype(cell, comp_id_map),
+            cell_ref.get_attribute(calyx_ir::BoolAttr::Data).is_some(),
         );
         let cell_offset = aux.cell_offset_map.insert(cell_def);
         layout.cell_map.insert(cell.as_raw(), cell_offset.into());
@@ -387,6 +390,7 @@ fn insert_cell(
             range,
             comp_id,
             create_cell_prototype(cell, comp_id_map),
+            cell_ref.get_attribute(calyx_ir::BoolAttr::Data).is_some(),
         );
         let cell_offset = aux.ref_cell_offset_map.insert(ref_cell_def);
         layout.cell_map.insert(cell.as_raw(), cell_offset.into());
@@ -554,12 +558,12 @@ fn is_primitive(cell_ref: &std::cell::Ref<cir::Cell>) -> bool {
 impl FlattenTree for cir::Guard<cir::Nothing> {
     type Output = Guard;
     type IdxType = GuardIdx;
-    type AuxillaryData = PortMapper;
+    type AuxiliaryData = PortMapper;
 
     fn process_element<'data>(
         &'data self,
         mut handle: SingleHandle<'_, 'data, Self, Self::IdxType, Self::Output>,
-        aux: &Self::AuxillaryData,
+        aux: &Self::AuxiliaryData,
     ) -> Self::Output {
         match self {
             cir::Guard::Or(a, b) => {
@@ -586,12 +590,12 @@ impl FlattenTree for cir::Control {
 
     type IdxType = ControlIdx;
 
-    type AuxillaryData = (GroupMapper, Layout, Context, AuxiliaryComponentInfo);
+    type AuxiliaryData = (GroupMapper, Layout, Context, AuxiliaryComponentInfo);
 
     fn process_element<'data>(
         &'data self,
         mut handle: SingleHandle<'_, 'data, Self, Self::IdxType, Self::Output>,
-        aux: &Self::AuxillaryData,
+        aux: &Self::AuxiliaryData,
     ) -> Self::Output {
         let (group_map, layout, ctx, comp_info) = aux;
         match self {
diff --git a/interp/src/flatten/flat_ir/flatten_trait.rs b/interp/src/flatten/flat_ir/flatten_trait.rs
index cb46088339..41d0cf0b33 100644
--- a/interp/src/flatten/flat_ir/flatten_trait.rs
+++ b/interp/src/flatten/flat_ir/flatten_trait.rs
@@ -86,12 +86,12 @@ where
 pub trait FlattenTree: Sized {
     type Output;
     type IdxType: IndexRef;
-    type AuxillaryData;
+    type AuxiliaryData;
 
     fn process_element<'data>(
         &'data self,
         handle: SingleHandle<'_, 'data, Self, Self::IdxType, Self::Output>,
-        aux: &Self::AuxillaryData,
+        aux: &Self::AuxiliaryData,
     ) -> Self::Output;
 }
 
@@ -103,7 +103,7 @@ pub fn flatten_tree<In, Idx, Out, Aux>(
 ) -> Idx
 where
     Idx: IndexRef,
-    In: FlattenTree<Output = Out, IdxType = Idx, AuxillaryData = Aux>,
+    In: FlattenTree<Output = Out, IdxType = Idx, AuxiliaryData = Aux>,
 {
     let mut handle = VecHandle::new(vec, root_node, base);
     let mut root_node_idx: Option<Idx> = None;
diff --git a/interp/src/flatten/primitives/btor2_prim.rs b/interp/src/flatten/primitives/btor2_prim.rs
deleted file mode 100644
index 8d45e18060..0000000000
--- a/interp/src/flatten/primitives/btor2_prim.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use btor2i::program::Btor2Program;
-
-use crate::flatten::flat_ir::prelude::{AssignedValue, GlobalPortIdx};
-use crate::flatten::primitives::prim_trait::{Primitive, UpdateResult};
-use crate::flatten::primitives::{declare_ports, ports};
-use crate::flatten::structures::environment::PortMap;
-
-use baa::{BitVecValue, WidthInt};
-// use std::env;
-
-use std::cell::RefCell;
-use std::collections::HashMap;
-
-pub struct MyBtor2Add<'a> {
-    program: RefCell<Btor2Program<'a>>,
-    base_port: GlobalPortIdx,
-    width: WidthInt, // do stuff
-}
-
-impl<'a> MyBtor2Add<'a> {
-    declare_ports![ LEFT:0, RIGHT:1, OUT:2 ];
-    pub fn new(base: GlobalPortIdx, width: WidthInt) -> Self {
-        Self {
-            program: RefCell::new(Btor2Program::new(
-                "../tools/btor2/core/std_add.btor",
-            )),
-            base_port: base,
-            width,
-        }
-    }
-}
-
-impl<'a> Primitive for MyBtor2Add<'a> {
-    fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
-        ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, out: Self::OUT];
-        let input_map = HashMap::from([
-            (
-                "left".to_string(),
-                port_map[left].as_u64().unwrap_or(0).to_string(),
-            ),
-            (
-                "right".to_string(),
-                port_map[right].as_u64().unwrap_or(0).to_string(),
-            ),
-        ]);
-        match self.program.borrow_mut().run(input_map) {
-            Ok(output_map) => Ok(port_map.insert_val(
-                out,
-                AssignedValue::cell_value(BitVecValue::from_u64(
-                    output_map["out"],
-                    self.width,
-                )),
-            )?),
-            Err(msg) => {
-                panic!("{}", msg);
-            }
-        }
-    }
-
-    fn has_stateful(&self) -> bool {
-        false
-    }
-}
diff --git a/interp/src/flatten/primitives/combinational.rs b/interp/src/flatten/primitives/combinational.rs
index 9c174f9f6c..8df70e4352 100644
--- a/interp/src/flatten/primitives/combinational.rs
+++ b/interp/src/flatten/primitives/combinational.rs
@@ -11,6 +11,7 @@ use baa::{BitVecOps, BitVecValue};
 
 use super::prim_trait::UpdateResult;
 
+#[derive(Clone)]
 pub struct StdConst {
     value: BitVecValue,
     out: GlobalPortIdx,
@@ -44,8 +45,13 @@ impl Primitive for StdConst {
     fn has_stateful(&self) -> bool {
         false
     }
+
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
 }
 
+#[derive(Clone)]
 pub struct StdMux {
     base: GlobalPortIdx,
 }
@@ -65,7 +71,7 @@ impl Primitive for StdMux {
             port_map[cond].as_bool().map(|c| if c { tru } else { fal });
 
         if winning_idx.is_some() && port_map[winning_idx.unwrap()].is_def() {
-            Ok(port_map.insert_val(
+            Ok(port_map.insert_val_general(
                 out,
                 AssignedValue::cell_value(
                     port_map[winning_idx.unwrap()].val().unwrap().clone(),
@@ -80,6 +86,10 @@ impl Primitive for StdMux {
     fn has_stateful(&self) -> bool {
         false
     }
+
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
 }
 
 comb_primitive!(StdNot(input [0]) -> (out [1]) {
@@ -275,6 +285,7 @@ comb_primitive!(StdUnsynSmod[WIDTH](left [0], right [1]) -> (out [2]) {
     Ok(Some(BitVecValue::from_big_int(&res, WIDTH)))
 });
 
+#[derive(Clone)]
 pub struct StdUndef(GlobalPortIdx);
 
 impl StdUndef {
@@ -288,4 +299,8 @@ impl Primitive for StdUndef {
         port_map.write_undef(self.0)?;
         Ok(UpdateStatus::Unchanged)
     }
+
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
 }
diff --git a/interp/src/flatten/primitives/macros.rs b/interp/src/flatten/primitives/macros.rs
index bb1dd95e92..bf75c6f1b4 100644
--- a/interp/src/flatten/primitives/macros.rs
+++ b/interp/src/flatten/primitives/macros.rs
@@ -74,7 +74,7 @@ macro_rules! comb_primitive {
 
 
                 #[allow(non_snake_case)]
-                let exec_func = |$($($param: u32,)+)? $($port: &$crate::flatten::flat_ir::prelude::PortValue),+| ->$crate::errors::InterpreterResult<Option<baa::BitVecValue>>  {
+                let exec_func = |$($($param: u32,)+)? $($port: &$crate::flatten::flat_ir::prelude::PortValue),+| ->$crate::errors::RuntimeResult<Option<baa::BitVecValue>>  {
                     $execute
                 };
 
@@ -102,6 +102,12 @@ macro_rules! comb_primitive {
                 false
             }
 
+            fn clone_boxed(&self) -> Box<dyn $crate::flatten::primitives::Primitive> {
+                Box::new(Self {
+                    base_port: self.base_port,
+                    $($($param: self.$param,)+)?
+                })
+            }
         }
     };
 
diff --git a/interp/src/flatten/primitives/mod.rs b/interp/src/flatten/primitives/mod.rs
index 935e2add53..25c5c0706a 100644
--- a/interp/src/flatten/primitives/mod.rs
+++ b/interp/src/flatten/primitives/mod.rs
@@ -1,4 +1,3 @@
-pub mod btor2_prim;
 mod builder;
 pub mod combinational;
 pub(crate) mod macros;
diff --git a/interp/src/flatten/primitives/prim_trait.rs b/interp/src/flatten/primitives/prim_trait.rs
index 80f4852fc0..199adce455 100644
--- a/interp/src/flatten/primitives/prim_trait.rs
+++ b/interp/src/flatten/primitives/prim_trait.rs
@@ -1,5 +1,5 @@
 use crate::{
-    errors::InterpreterResult,
+    errors::RuntimeResult,
     flatten::{
         flat_ir::base::GlobalPortIdx,
         structures::{
@@ -102,7 +102,7 @@ impl std::ops::BitOrAssign for UpdateStatus {
     }
 }
 
-pub type UpdateResult = InterpreterResult<UpdateStatus>;
+pub type UpdateResult = RuntimeResult<UpdateStatus>;
 
 pub trait Primitive {
     fn exec_comb(&self, _port_map: &mut PortMap) -> UpdateResult {
@@ -134,6 +134,8 @@ pub trait Primitive {
     fn dump_memory_state(&self) -> Option<Vec<u8>> {
         None
     }
+
+    fn clone_boxed(&self) -> Box<dyn Primitive>;
 }
 
 pub trait RaceDetectionPrimitive: Primitive {
@@ -158,10 +160,13 @@ pub trait RaceDetectionPrimitive: Primitive {
     /// Get a reference to the underlying primitive. Unfortunately cannot add an
     /// optional default implementation due to size rules
     fn as_primitive(&self) -> &dyn Primitive;
+
+    fn clone_boxed_rd(&self) -> Box<dyn RaceDetectionPrimitive>;
 }
 
 /// An empty primitive implementation used for testing. It does not do anything
 /// and has no ports of any kind
+#[derive(Clone, Copy)]
 pub struct DummyPrimitive;
 
 impl DummyPrimitive {
@@ -170,4 +175,8 @@ impl DummyPrimitive {
     }
 }
 
-impl Primitive for DummyPrimitive {}
+impl Primitive for DummyPrimitive {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(*self)
+    }
+}
diff --git a/interp/src/flatten/primitives/stateful/math.rs b/interp/src/flatten/primitives/stateful/math.rs
index e22d85e92b..c8fb091e48 100644
--- a/interp/src/flatten/primitives/stateful/math.rs
+++ b/interp/src/flatten/primitives/stateful/math.rs
@@ -10,6 +10,7 @@ use crate::flatten::{
 use baa::{BitVecOps, BitVecValue, WidthInt};
 use num_traits::Euclid;
 
+#[derive(Clone)]
 pub struct StdMultPipe<const DEPTH: usize> {
     base_port: GlobalPortIdx,
     pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>,
@@ -32,13 +33,17 @@ impl<const DEPTH: usize> StdMultPipe<DEPTH> {
 }
 
 impl<const DEPTH: usize> Primitive for StdMultPipe<DEPTH> {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port; out: Self::OUT, done: Self::DONE];
 
         let out_changed =
             port_map.write_exact_unchecked(out, self.current_output.clone());
 
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             done,
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -90,7 +95,7 @@ impl<const DEPTH: usize> Primitive for StdMultPipe<DEPTH> {
             self.done_is_high = false;
         }
 
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             done,
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -106,6 +111,7 @@ impl<const DEPTH: usize> Primitive for StdMultPipe<DEPTH> {
     }
 }
 
+#[derive(Clone)]
 pub struct StdDivPipe<const DEPTH: usize, const SIGNED: bool> {
     base_port: GlobalPortIdx,
     pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>,
@@ -132,6 +138,10 @@ impl<const DEPTH: usize, const SIGNED: bool> StdDivPipe<DEPTH, SIGNED> {
 impl<const DEPTH: usize, const SIGNED: bool> Primitive
     for StdDivPipe<DEPTH, SIGNED>
 {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port;
                out_quot: Self::OUT_QUOTIENT,
@@ -253,6 +263,10 @@ impl<const IS_FIXED_POINT: bool> Sqrt<IS_FIXED_POINT> {
 }
 
 impl<const IS_FIXED_POINT: bool> Primitive for Sqrt<IS_FIXED_POINT> {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port; out: Self::OUT, done: Self::DONE];
 
@@ -309,6 +323,7 @@ impl<const IS_FIXED_POINT: bool> Primitive for Sqrt<IS_FIXED_POINT> {
     }
 }
 
+#[derive(Clone)]
 pub struct FxpMultPipe<const DEPTH: usize> {
     base_port: GlobalPortIdx,
     pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>,
@@ -339,13 +354,17 @@ impl<const DEPTH: usize> FxpMultPipe<DEPTH> {
 }
 
 impl<const DEPTH: usize> Primitive for FxpMultPipe<DEPTH> {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port; out: Self::OUT, done: Self::DONE];
 
         let out_changed =
             port_map.write_exact_unchecked(out, self.current_output.clone());
 
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             done,
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -406,7 +425,7 @@ impl<const DEPTH: usize> Primitive for FxpMultPipe<DEPTH> {
             self.done_is_high = false;
         }
 
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             done,
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -422,6 +441,7 @@ impl<const DEPTH: usize> Primitive for FxpMultPipe<DEPTH> {
     }
 }
 
+#[derive(Clone)]
 pub struct FxpDivPipe<const DEPTH: usize, const SIGNED: bool> {
     base_port: GlobalPortIdx,
     pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>,
@@ -460,6 +480,10 @@ impl<const DEPTH: usize, const SIGNED: bool> FxpDivPipe<DEPTH, SIGNED> {
 impl<const DEPTH: usize, const SIGNED: bool> Primitive
     for FxpDivPipe<DEPTH, SIGNED>
 {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port;
                out_quot: Self::OUT_QUOTIENT,
diff --git a/interp/src/flatten/primitives/stateful/memories.rs b/interp/src/flatten/primitives/stateful/memories.rs
index de5bd12a6f..8365907f26 100644
--- a/interp/src/flatten/primitives/stateful/memories.rs
+++ b/interp/src/flatten/primitives/stateful/memories.rs
@@ -1,7 +1,7 @@
 use itertools::Itertools;
 
 use crate::{
-    errors::InterpreterError,
+    errors::{RuntimeError, RuntimeResult},
     flatten::{
         flat_ir::{
             base::GlobalCellIdx,
@@ -27,6 +27,7 @@ use crate::{
 
 use baa::{BitVecOps, BitVecValue, WidthInt};
 
+#[derive(Clone)]
 pub struct StdReg {
     base_port: GlobalPortIdx,
     internal_state: ValueWithClock,
@@ -55,6 +56,10 @@ impl StdReg {
 }
 
 impl Primitive for StdReg {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult {
         ports![&self.base_port;
             input: Self::IN,
@@ -67,33 +72,33 @@ impl Primitive for StdReg {
         let done_port = if port_map[reset].as_bool().unwrap_or_default() {
             self.internal_state.value =
                 BitVecValue::zero(self.internal_state.value.width());
-            port_map.insert_val(
+            port_map.insert_val_general(
                 done,
                 AssignedValue::cell_value(BitVecValue::fals()),
             )?
         } else if port_map[write_en].as_bool().unwrap_or_default() {
             self.internal_state.value = port_map[input]
                 .as_option()
-                .ok_or(InterpreterError::UndefinedWrite(self.global_idx))?
+                .ok_or(RuntimeError::UndefinedWrite(self.global_idx))?
                 .val()
                 .clone();
 
             self.done_is_high = true;
 
-            port_map.insert_val(
+            port_map.insert_val_general(
                 done,
                 AssignedValue::cell_value(BitVecValue::tru()),
             )?
         } else {
             self.done_is_high = false;
-            port_map.insert_val(
+            port_map.insert_val_general(
                 done,
                 AssignedValue::cell_value(BitVecValue::fals()),
             )?
         };
 
         Ok(done_port
-            | port_map.insert_val(
+            | port_map.insert_val_general(
                 out_idx,
                 AssignedValue::cell_value(self.internal_state.value.clone())
                     .with_clocks(self.internal_state.clocks),
@@ -105,12 +110,12 @@ impl Primitive for StdReg {
             done: Self::DONE,
             out_idx: Self::OUT];
 
-        let out_signal = port_map.insert_val(
+        let out_signal = port_map.insert_val_general(
             out_idx,
             AssignedValue::cell_value(self.internal_state.value.clone())
                 .with_clocks(self.internal_state.clocks),
         )?;
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             done,
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -139,6 +144,10 @@ impl Primitive for StdReg {
 }
 
 impl RaceDetectionPrimitive for StdReg {
+    fn clone_boxed_rd(&self) -> Box<dyn RaceDetectionPrimitive> {
+        Box::new(self.clone())
+    }
+
     fn as_primitive(&self) -> &dyn Primitive {
         self
     }
@@ -176,6 +185,7 @@ impl RaceDetectionPrimitive for StdReg {
     }
 }
 
+#[derive(Clone)]
 pub struct MemDx<const SEQ: bool> {
     shape: Shape,
 }
@@ -197,11 +207,62 @@ impl<const SEQ: bool> MemDx<SEQ> {
         SEQ_ADDR3: 5, COMB_ADDR3: 3
     ];
 
+    pub fn addr_as_vec(
+        &self,
+        port_map: &PortMap,
+        base_port: GlobalPortIdx,
+    ) -> Option<Vec<u64>> {
+        let (addr0, addr1, addr2, addr3) = if SEQ {
+            ports![&base_port;
+                addr0: Self::SEQ_ADDR0,
+                addr1: Self::SEQ_ADDR1,
+                addr2: Self::SEQ_ADDR2,
+                addr3: Self::SEQ_ADDR3
+            ];
+            (addr0, addr1, addr2, addr3)
+        } else {
+            ports![&base_port;
+                addr0: Self::COMB_ADDR0,
+                addr1: Self::COMB_ADDR1,
+                addr2: Self::COMB_ADDR2,
+                addr3: Self::COMB_ADDR3
+            ];
+
+            (addr0, addr1, addr2, addr3)
+        };
+
+        Some(match self.shape {
+            Shape::D1(..) => vec![port_map[addr0].as_u64().unwrap()],
+            Shape::D2(..) => {
+                let a0 = port_map[addr0].as_u64()? as usize;
+                let a1 = port_map[addr1].as_u64()? as usize;
+
+                vec![a0 as u64, a1 as u64]
+            }
+            Shape::D3(..) => {
+                let a0 = port_map[addr0].as_u64()? as usize;
+                let a1 = port_map[addr1].as_u64()? as usize;
+                let a2 = port_map[addr2].as_u64()? as usize;
+
+                vec![a0 as u64, a1 as u64, a2 as u64]
+            }
+            Shape::D4(..) => {
+                let a0 = port_map[addr0].as_u64()? as usize;
+                let a1 = port_map[addr1].as_u64()? as usize;
+                let a2 = port_map[addr2].as_u64()? as usize;
+                let a3 = port_map[addr3].as_u64()? as usize;
+
+                vec![a0 as u64, a1 as u64, a2 as u64, a3 as u64]
+            }
+        })
+    }
+
     pub fn calculate_addr(
         &self,
         port_map: &PortMap,
         base_port: GlobalPortIdx,
-    ) -> Option<usize> {
+        cell_idx: GlobalCellIdx,
+    ) -> RuntimeResult<Option<usize>> {
         let (addr0, addr1, addr2, addr3) = if SEQ {
             ports![&base_port;
                 addr0: Self::SEQ_ADDR0,
@@ -221,6 +282,36 @@ impl<const SEQ: bool> MemDx<SEQ> {
             (addr0, addr1, addr2, addr3)
         };
 
+        let option: Option<usize> =
+            self.compute_address(port_map, addr0, addr1, addr2, addr3);
+
+        if let Some(v) = option {
+            if v >= self.shape.size() {
+                Err(RuntimeError::InvalidMemoryAccess {
+                    access: self.addr_as_vec(port_map, base_port).unwrap(),
+                    dims: self.shape.clone(),
+                    idx: cell_idx,
+                }
+                .into())
+            } else {
+                Ok(Some(v))
+            }
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn compute_address(
+        &self,
+        port_map: &crate::flatten::structures::indexed_map::IndexedMap<
+            GlobalPortIdx,
+            PortValue,
+        >,
+        addr0: GlobalPortIdx,
+        addr1: GlobalPortIdx,
+        addr2: GlobalPortIdx,
+        addr3: GlobalPortIdx,
+    ) -> Option<usize> {
         match self.shape {
             Shape::D1(_d0_size) => port_map[addr0].as_u64().map(|v| v as usize),
             Shape::D2(_d0_size, d1_size) => {
@@ -308,6 +399,7 @@ impl<const SEQ: bool> MemDx<SEQ> {
     }
 }
 
+#[derive(Clone)]
 pub struct CombMem {
     base_port: GlobalPortIdx,
     internal_state: Vec<ValueWithClock>,
@@ -427,15 +519,23 @@ impl CombMem {
 }
 
 impl Primitive for CombMem {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
-        let addr = self.addresser.calculate_addr(port_map, self.base_port);
+        let addr: Option<usize> = self
+            .addresser
+            .calculate_addr(port_map, self.base_port, self.global_idx)
+            .unwrap_or_default();
+
         let read_data = self.read_data();
 
         let read =
             if addr.is_some() && addr.unwrap() < self.internal_state.len() {
                 let addr = addr.unwrap();
 
-                port_map.insert_val(
+                port_map.insert_val_general(
                     read_data,
                     AssignedValue::cell_value(
                         self.internal_state[addr].value.clone(),
@@ -450,7 +550,7 @@ impl Primitive for CombMem {
                 UpdateStatus::Unchanged
             };
 
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             self.done(),
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -466,26 +566,30 @@ impl Primitive for CombMem {
         let reset = port_map[self.reset_port()].as_bool().unwrap_or_default();
         let write_en = port_map[self.write_en()].as_bool().unwrap_or_default();
 
-        let addr = self.addresser.calculate_addr(port_map, self.base_port);
+        let addr = self.addresser.calculate_addr(
+            port_map,
+            self.base_port,
+            self.global_idx,
+        )?;
         let (read_data, done) = (self.read_data(), self.done());
 
         let done = if write_en && !reset {
-            let addr = addr
-                .ok_or(InterpreterError::UndefinedWriteAddr(self.global_idx))?;
+            let addr =
+                addr.ok_or(RuntimeError::UndefinedWriteAddr(self.global_idx))?;
 
             let write_data = port_map[self.write_data()]
                 .as_option()
-                .ok_or(InterpreterError::UndefinedWrite(self.global_idx))?;
+                .ok_or(RuntimeError::UndefinedWrite(self.global_idx))?;
             self.internal_state[addr].value = write_data.val().clone();
             self.done_is_high = true;
-            port_map.insert_val(done, AssignedValue::cell_b_high())?
+            port_map.insert_val_general(done, AssignedValue::cell_b_high())?
         } else {
             self.done_is_high = false;
-            port_map.insert_val(done, AssignedValue::cell_b_low())?
+            port_map.insert_val_general(done, AssignedValue::cell_b_low())?
         };
 
         if let Some(addr) = addr {
-            Ok(port_map.insert_val(
+            Ok(port_map.insert_val_general(
                 read_data,
                 AssignedValue::cell_value(
                     self.internal_state[addr].value.clone(),
@@ -520,6 +624,10 @@ impl Primitive for CombMem {
 }
 
 impl RaceDetectionPrimitive for CombMem {
+    fn clone_boxed_rd(&self) -> Box<dyn RaceDetectionPrimitive> {
+        Box::new(self.clone())
+    }
+
     fn as_primitive(&self) -> &dyn Primitive {
         self
     }
@@ -555,20 +663,28 @@ impl RaceDetectionPrimitive for CombMem {
         thread_map: &ThreadMap,
     ) -> UpdateResult {
         let thread = self.infer_thread(port_map);
-        if let Some(addr) =
-            self.addresser.calculate_addr(port_map, self.base_port)
-        {
+        if let Some(addr) = self.addresser.calculate_addr(
+            port_map,
+            self.base_port,
+            self.global_idx,
+        )? {
             if addr < self.internal_state.len() {
-                let thread =
-                    thread.expect("Could not infer thread id for seq mem");
-                let thread_clock = thread_map.unwrap_clock_id(thread);
-
-                let val = &self.internal_state[addr];
-
-                if port_map[self.write_en()].as_bool().unwrap_or_default() {
-                    val.clocks
-                        .check_write(thread_clock, clock_map)
-                        .map_err(|e| e.add_cell_info(self.global_idx))?;
+                if let Some(thread) = thread {
+                    let thread_clock = thread_map.unwrap_clock_id(thread);
+
+                    let val = &self.internal_state[addr];
+
+                    if port_map[self.write_en()].as_bool().unwrap_or_default() {
+                        val.clocks
+                            .check_write(thread_clock, clock_map)
+                            .map_err(|e| e.add_cell_info(self.global_idx))?;
+                    }
+                } else if addr != 0
+                    || port_map[self.write_en()].as_bool().unwrap_or_default()
+                {
+                    // HACK: if the addr is 0, we're reading, and the thread
+                    // can't be determined then we assume the read is not real
+                    panic!("unable to determine thread for comb mem");
                 }
             }
         }
@@ -577,6 +693,7 @@ impl RaceDetectionPrimitive for CombMem {
     }
 }
 
+#[derive(Clone)]
 pub struct SeqMem {
     base_port: GlobalPortIdx,
     internal_state: Vec<ValueWithClock>,
@@ -715,8 +832,12 @@ impl SeqMem {
 }
 
 impl Primitive for SeqMem {
+    fn clone_boxed(&self) -> Box<dyn Primitive> {
+        Box::new(self.clone())
+    }
+
     fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult {
-        let done_signal = port_map.insert_val(
+        let done_signal = port_map.insert_val_general(
             self.done(),
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
@@ -728,7 +849,7 @@ impl Primitive for SeqMem {
         let out_signal = if port_map[self.read_data()].is_undef()
             && self.read_out.is_def()
         {
-            port_map.insert_val(
+            port_map.insert_val_general(
                 self.read_data(),
                 self.read_out.as_option().unwrap().clone(),
             )?
@@ -746,7 +867,11 @@ impl Primitive for SeqMem {
         let content_en = port_map[self.content_enable()]
             .as_bool()
             .unwrap_or_default();
-        let addr = self.addresser.calculate_addr(port_map, self.base_port);
+        let addr = self.addresser.calculate_addr(
+            port_map,
+            self.base_port,
+            self.global_idx,
+        )?;
 
         if reset {
             self.done_is_high = false;
@@ -754,16 +879,16 @@ impl Primitive for SeqMem {
         } else if content_en && write_en {
             self.done_is_high = true;
             self.read_out = PortValue::new_undef();
-            let addr_actual = addr
-                .ok_or(InterpreterError::UndefinedWriteAddr(self.global_idx))?;
+            let addr_actual =
+                addr.ok_or(RuntimeError::UndefinedWriteAddr(self.global_idx))?;
             let write_data = port_map[self.write_data()]
                 .as_option()
-                .ok_or(InterpreterError::UndefinedWrite(self.global_idx))?;
+                .ok_or(RuntimeError::UndefinedWrite(self.global_idx))?;
             self.internal_state[addr_actual].value = write_data.val().clone();
         } else if content_en {
             self.done_is_high = true;
-            let addr_actual = addr
-                .ok_or(InterpreterError::UndefinedReadAddr(self.global_idx))?;
+            let addr_actual =
+                addr.ok_or(RuntimeError::UndefinedReadAddr(self.global_idx))?;
             self.read_out = PortValue::new_cell(
                 self.internal_state[addr_actual].value.clone(),
             );
@@ -771,15 +896,15 @@ impl Primitive for SeqMem {
             self.done_is_high = false;
         }
 
-        let done_changed = port_map.insert_val(
+        let done_changed = port_map.insert_val_general(
             self.done(),
             AssignedValue::cell_value(if self.done_is_high {
                 BitVecValue::tru()
             } else {
                 BitVecValue::fals()
             }),
-        );
-        Ok(done_changed?
+        )?;
+        Ok(done_changed
             | port_map
                 .write_exact_unchecked(self.read_data(), self.read_out.clone()))
     }
@@ -814,6 +939,10 @@ impl Primitive for SeqMem {
 }
 
 impl RaceDetectionPrimitive for SeqMem {
+    fn clone_boxed_rd(&self) -> Box<dyn RaceDetectionPrimitive> {
+        Box::new(self.clone())
+    }
+
     fn as_primitive(&self) -> &dyn Primitive {
         self
     }
@@ -834,9 +963,11 @@ impl RaceDetectionPrimitive for SeqMem {
         thread_map: &ThreadMap,
     ) -> UpdateResult {
         let thread = self.infer_thread(port_map);
-        if let Some(addr) =
-            self.addresser.calculate_addr(port_map, self.base_port)
-        {
+        if let Some(addr) = self.addresser.calculate_addr(
+            port_map,
+            self.base_port,
+            self.global_idx,
+        )? {
             if addr < self.internal_state.len() {
                 let thread_clock =
                     thread.map(|thread| thread_map.unwrap_clock_id(thread));
diff --git a/interp/src/flatten/primitives/utils.rs b/interp/src/flatten/primitives/utils.rs
index 6b91e17a3c..51ab75fd58 100644
--- a/interp/src/flatten/primitives/utils.rs
+++ b/interp/src/flatten/primitives/utils.rs
@@ -38,6 +38,7 @@ pub(crate) fn int_sqrt(i: &BigUint) -> BigUint {
 }
 
 /// A shift buffer of a fixed size
+#[derive(Clone)]
 pub struct ShiftBuffer<T, const N: usize> {
     buffer: VecDeque<Option<T>>,
 }
diff --git a/interp/src/flatten/setup.rs b/interp/src/flatten/setup.rs
index 72b99e0ee1..6a10994a5f 100644
--- a/interp/src/flatten/setup.rs
+++ b/interp/src/flatten/setup.rs
@@ -3,9 +3,7 @@ use calyx_ir as ir;
 use calyx_opt::pass_manager::PassManager;
 use std::path::{Path, PathBuf};
 
-use crate::{
-    debugger::source::structures::NewSourceMap, errors::InterpreterResult,
-};
+use crate::{debugger::source::structures::NewSourceMap, errors::CiderResult};
 
 use super::structures::context::Context;
 
@@ -15,7 +13,7 @@ fn do_setup(
     lib_path: &Path,
     skip_verification: bool,
     gen_metadata: bool,
-) -> InterpreterResult<(Context, InterpreterResult<NewSourceMap>)> {
+) -> CiderResult<(Context, CiderResult<NewSourceMap>)> {
     // Construct IR
     let ws = frontend::Workspace::construct(file, lib_path)?;
     let mut ctx = ir::from_ast::ast_to_ir(ws)?;
@@ -39,10 +37,10 @@ fn do_setup(
                 crate::debugger::source::new_parser::parse_metadata(metadata)
             })
             .unwrap_or_else(|| {
-                Err(crate::errors::InterpreterError::MissingMetaData.into())
+                Err(crate::errors::CiderError::MissingMetaData.into())
             })
     } else {
-        Err(crate::errors::InterpreterError::MissingMetaData.into())
+        Err(crate::errors::CiderError::MissingMetaData.into())
     };
 
     // general setup
@@ -56,7 +54,7 @@ pub fn setup_simulation(
     file: &Option<PathBuf>,
     lib_path: &Path,
     skip_verification: bool,
-) -> InterpreterResult<Context> {
+) -> CiderResult<Context> {
     let (ctx, _) = do_setup(file, lib_path, skip_verification, false)?;
     Ok(ctx)
 }
@@ -70,7 +68,7 @@ pub fn setup_simulation_with_metadata(
     file: &Option<PathBuf>,
     lib_path: &Path,
     skip_verification: bool,
-) -> InterpreterResult<(Context, NewSourceMap)> {
+) -> CiderResult<(Context, NewSourceMap)> {
     let (ctx, mapping) = do_setup(file, lib_path, skip_verification, true)?;
     Ok((ctx, mapping?))
 }
diff --git a/interp/src/flatten/structures/context.rs b/interp/src/flatten/structures/context.rs
index 5ed19d5e48..87960482a0 100644
--- a/interp/src/flatten/structures/context.rs
+++ b/interp/src/flatten/structures/context.rs
@@ -1,5 +1,7 @@
 use std::ops::Index;
 
+use calyx_ir::Direction;
+
 use crate::flatten::flat_ir::{
     cell_prototype::CellPrototype,
     component::{
@@ -105,8 +107,10 @@ pub struct PortDefinitionInfo {
     pub name: Identifier,
     /// The width of the port
     pub width: usize,
-    /// Whether the port is control
-    pub is_control: bool,
+    /// Whether the port is data
+    pub is_data: bool,
+    /// The direction of the port
+    pub direction: Direction,
 }
 
 #[derive(Debug)]
@@ -181,12 +185,14 @@ impl SecondaryContext {
         &mut self,
         name: Identifier,
         width: usize,
-        is_control: bool,
+        is_data: bool,
+        direction: Direction,
     ) -> PortDefinitionIdx {
         self.local_port_defs.push(PortDefinitionInfo {
             name,
             width,
-            is_control,
+            is_data,
+            direction,
         })
     }
 
@@ -202,9 +208,10 @@ impl SecondaryContext {
         ports: IndexRange<LocalPortOffset>,
         parent: ComponentIdx,
         prototype: CellPrototype,
+        is_data: bool,
     ) -> CellDefinitionIdx {
         self.local_cell_defs
-            .push(CellInfo::new(name, ports, parent, prototype))
+            .push(CellInfo::new(name, ports, parent, prototype, is_data))
     }
 
     /// Insert a new reference cell definition into the context and return its index
@@ -214,9 +221,10 @@ impl SecondaryContext {
         ports: IndexRange<LocalRefPortOffset>,
         parent: ComponentIdx,
         prototype: CellPrototype,
+        is_data: bool,
     ) -> RefCellDefinitionIdx {
         self.ref_cell_defs
-            .push(RefCellInfo::new(name, ports, parent, prototype))
+            .push(RefCellInfo::new(name, ports, parent, prototype, is_data))
     }
 }
 
diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs
index a7b9f54af6..7b7360a57b 100644
--- a/interp/src/flatten/structures/environment/env.rs
+++ b/interp/src/flatten/structures/environment/env.rs
@@ -7,9 +7,9 @@ use super::{
     program_counter::{ControlTuple, PcMaps, ProgramCounter, WithEntry},
     traverser::{Path, TraversalError},
 };
-use crate::flatten::structures::environment::wave::WaveWriter;
 use crate::{
-    errors::{BoxedInterpreterError, InterpreterError, InterpreterResult},
+    configuration::{LoggingConfig, RuntimeConfig},
+    errors::{BoxedCiderError, CiderResult, ConflictingAssignments},
     flatten::{
         flat_ir::{
             base::{
@@ -37,6 +37,10 @@ use crate::{
     logging,
     serialization::{DataDump, MemoryDeclaration, PrintCode},
 };
+use crate::{
+    errors::{RuntimeError, RuntimeResult},
+    flatten::structures::environment::wave::WaveWriter,
+};
 use ahash::HashSet;
 use ahash::HashSetExt;
 use ahash::{HashMap, HashMapExt};
@@ -44,7 +48,7 @@ use baa::{BitVecOps, BitVecValue};
 use itertools::Itertools;
 use owo_colors::OwoColorize;
 
-use slog::warn;
+use slog::{info, warn, Logger};
 use std::fmt::Debug;
 use std::fmt::Write;
 
@@ -53,12 +57,9 @@ pub type PortMap = IndexedMap<GlobalPortIdx, PortValue>;
 impl PortMap {
     /// Essentially asserts that the port given is undefined, it errors out if
     /// the port is defined and otherwise does nothing
-    pub fn write_undef(
-        &mut self,
-        target: GlobalPortIdx,
-    ) -> InterpreterResult<()> {
+    pub fn write_undef(&mut self, target: GlobalPortIdx) -> RuntimeResult<()> {
         if self[target].is_def() {
-            Err(InterpreterError::UndefiningDefinedPort(target).into())
+            Err(RuntimeError::UndefiningDefinedPort(target).into())
         } else {
             Ok(())
         }
@@ -93,7 +94,7 @@ impl PortMap {
         &mut self,
         target: GlobalPortIdx,
         val: AssignedValue,
-    ) -> InterpreterResult<UpdateStatus> {
+    ) -> Result<UpdateStatus, ConflictingAssignments> {
         match self[target].as_option() {
             // unchanged
             Some(t) if *t == val => Ok(UpdateStatus::Unchanged),
@@ -105,14 +106,11 @@ impl PortMap {
                 // other way around
                     && !(*t.winner() == AssignmentWinner::Implicit) =>
             {
-                InterpreterResult::Err(
-                    InterpreterError::FlatConflictingAssignments {
-                        target,
-                        a1: t.clone(),
-                        a2: val,
-                    }
-                    .into(),
-                )
+                Err(ConflictingAssignments {
+                    target,
+                    a1: t.clone(),
+                    a2: val,
+                })
             }
             // changed
             Some(_) | None => {
@@ -122,11 +120,23 @@ impl PortMap {
         }
     }
 
+    /// Identical to `insert_val` but returns a `RuntimeError` instead of a
+    /// `ConflictingAssignments` error. This should be used inside of primitives
+    /// while the latter is used in the general simulation flow.
+    pub fn insert_val_general(
+        &mut self,
+        target: GlobalPortIdx,
+        val: AssignedValue,
+    ) -> RuntimeResult<UpdateStatus> {
+        self.insert_val(target, val)
+            .map_err(|e| RuntimeError::ConflictingAssignments(e).into())
+    }
+
     pub fn set_done(
         &mut self,
         target: GlobalPortIdx,
         done_bool: bool,
-    ) -> InterpreterResult<UpdateStatus> {
+    ) -> RuntimeResult<UpdateStatus> {
         self.insert_val(
             target,
             AssignedValue::cell_value(if done_bool {
@@ -135,6 +145,7 @@ impl PortMap {
                 BitVecValue::fals()
             }),
         )
+        .map_err(|e| RuntimeError::ConflictingAssignments(e).into())
     }
 }
 
@@ -145,6 +156,7 @@ pub(crate) type RefPortMap =
     IndexedMap<GlobalRefPortIdx, Option<GlobalPortIdx>>;
 pub(crate) type AssignmentRange = IndexRange<AssignmentIdx>;
 
+#[derive(Clone)]
 pub(crate) struct ComponentLedger {
     pub(crate) index_bases: BaseIndices,
     pub(crate) comp_id: ComponentIdx,
@@ -181,6 +193,24 @@ pub(crate) enum CellLedger {
     Component(ComponentLedger),
 }
 
+impl Clone for CellLedger {
+    fn clone(&self) -> Self {
+        match self {
+            Self::Primitive { cell_dyn } => Self::Primitive {
+                cell_dyn: cell_dyn.clone_boxed(),
+            },
+            Self::RaceDetectionPrimitive { cell_dyn } => {
+                Self::RaceDetectionPrimitive {
+                    cell_dyn: cell_dyn.clone_boxed_rd(),
+                }
+            }
+            Self::Component(component_ledger) => {
+                Self::Component(component_ledger.clone())
+            }
+        }
+    }
+}
+
 impl From<ComponentLedger> for CellLedger {
     fn from(v: ComponentLedger) -> Self {
         Self::Component(v)
@@ -291,7 +321,7 @@ impl PinnedPorts {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Environment<C: AsRef<Context> + Clone> {
     /// A map from global port IDs to their current values.
     ports: PortMap,
@@ -317,9 +347,7 @@ pub struct Environment<C: AsRef<Context> + Clone> {
     pub(super) ctx: C,
 
     memory_header: Option<Vec<MemoryDeclaration>>,
-
-    /// Whether to perform data race checking
-    check_data_race: bool,
+    logger: Logger,
 }
 
 impl<C: AsRef<Context> + Clone> Environment<C> {
@@ -329,6 +357,11 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
     pub fn ctx(&self) -> &Context {
         self.ctx.as_ref()
     }
+
+    pub fn pc_iter(&self) -> impl Iterator<Item = &ControlPoint> {
+        self.pc.iter().map(|(_, x)| x)
+    }
+
     /// Returns the full name and port list of each cell in the context
     pub fn iter_cells(
         &self,
@@ -390,7 +423,8 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
     pub fn new(
         ctx: C,
         data_map: Option<DataDump>,
-        check_data_races: bool,
+        check_race: bool,
+        logging_conf: LoggingConfig,
     ) -> Self {
         let root = ctx.as_ref().entry_point;
         let aux = &ctx.as_ref().secondary[root];
@@ -414,12 +448,17 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
             memory_header: None,
             pinned_ports: PinnedPorts::new(),
             control_ports: HashMap::new(),
-            check_data_race: check_data_races,
+            logger: logging::initialize_logger(logging_conf),
         };
 
         let root_node = CellLedger::new_comp(root, &env);
         let root_cell = env.cells.push(root_node);
-        env.layout_component(root_cell, &data_map, &mut HashSet::new());
+        env.layout_component(
+            root_cell,
+            &data_map,
+            &mut HashSet::new(),
+            check_race,
+        );
 
         let root_thread = ThreadMap::root_thread();
         env.clocks[root_clock].increment(&root_thread);
@@ -466,6 +505,7 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
         comp: GlobalCellIdx,
         data_map: &Option<DataDump>,
         memories_initialized: &mut HashSet<String>,
+        check_race: bool,
     ) {
         // for mutability reasons, see note in `[Environment::new]`
         let ctx = self.ctx.clone();
@@ -491,7 +531,10 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
             let def_idx = comp_aux.port_offset_map[sig_port];
             let info = &self.ctx.as_ref().secondary[def_idx];
             let idx = self.ports.push(PortValue::new_undef());
-            if info.is_control {
+
+            // the direction attached to the port is reversed for the signature.
+            // We only want to add the input ports to the control ports list.
+            if !info.is_data && info.direction != calyx_ir::Direction::Input {
                 self.control_ports
                     .insert(idx, info.width.try_into().unwrap());
             }
@@ -540,10 +583,12 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
                         idx
                     );
                     let def_idx = comp_aux.port_offset_map[port];
-                    let info = &self.ctx.as_ref().secondary[def_idx];
-                    if info.is_control {
+                    let port_info = &self.ctx.as_ref().secondary[def_idx];
+                    if !(port_info.direction == calyx_ir::Direction::Output
+                        || port_info.is_data && info.is_data)
+                    {
                         self.control_ports
-                            .insert(idx, info.width.try_into().unwrap());
+                            .insert(idx, port_info.width.try_into().unwrap());
                     }
                 }
                 let cell_dyn = primitives::build_primitive(
@@ -553,7 +598,7 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
                     self.ctx.as_ref(),
                     data_map,
                     memories_initialized,
-                    self.check_data_race.then_some(&mut self.clocks),
+                    check_race.then_some(&mut self.clocks),
                 );
                 let cell = self.cells.push(cell_dyn);
 
@@ -572,7 +617,12 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
                 );
 
                 // layout sub-component but don't include the data map
-                self.layout_component(cell, &None, memories_initialized);
+                self.layout_component(
+                    cell,
+                    &None,
+                    memories_initialized,
+                    check_race,
+                );
             }
         }
 
@@ -580,7 +630,7 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
             for dec in data.header.memories.iter() {
                 if !memories_initialized.contains(&dec.name) {
                     // TODO griffin: maybe make this an error?
-                    warn!(logging::root(), "Initialization was provided for memory {} but no such memory exists in the entrypoint component.", dec.name);
+                    warn!(self.logger, "Initialization was provided for memory {} but no such memory exists in the entrypoint component.", dec.name);
                 }
             }
         }
@@ -652,6 +702,15 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
         })
     }
 
+    /// Given a cell idx, return the component definition that this cell is an
+    /// instance of. Return None if the cell is not a component instance.
+    pub fn get_component_idx(
+        &self,
+        cell: GlobalCellIdx,
+    ) -> Option<ComponentIdx> {
+        self.cells[cell].as_comp().map(|x| x.comp_id)
+    }
+
     // ===================== Environment print implementations =====================
 
     pub fn _print_env(&self) {
@@ -803,6 +862,17 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
         }
     }
 
+    pub fn print_pc_string(&self) {
+        let ctx = self.ctx.as_ref();
+        for node in self.pc_iter() {
+            println!(
+                "{}: {}",
+                self.get_full_name(node.comp),
+                node.string_path(ctx)
+            );
+        }
+    }
+
     fn get_name_from_cell_and_parent(
         &self,
         parent: GlobalCellIdx,
@@ -1250,22 +1320,44 @@ impl<C: AsRef<Context> + Clone> Environment<C> {
     }
 }
 
+/// The core functionality of a simulator. Clonable.
+#[derive(Clone)]
+pub struct BaseSimulator<C: AsRef<Context> + Clone> {
+    env: Environment<C>,
+    conf: RuntimeConfig,
+}
+
 /// A wrapper struct for the environment that provides the functions used to
 /// simulate the actual program.
 ///
 /// This is just to keep the simulation logic under a different namespace than
 /// the environment to avoid confusion
 pub struct Simulator<C: AsRef<Context> + Clone> {
-    env: Environment<C>,
+    base: BaseSimulator<C>,
     wave: Option<WaveWriter>,
 }
 
 impl<C: AsRef<Context> + Clone> Simulator<C> {
-    pub fn new(
-        env: Environment<C>,
+    pub fn build_simulator(
+        ctx: C,
+        data_file: &Option<std::path::PathBuf>,
         wave_file: &Option<std::path::PathBuf>,
-    ) -> Self {
-        // open the wave form file and declare all signals
+        runtime_config: RuntimeConfig,
+    ) -> Result<Self, BoxedCiderError> {
+        let data_dump = data_file
+            .as_ref()
+            .map(|path| {
+                let mut file = std::fs::File::open(path)?;
+                DataDump::deserialize(&mut file)
+            })
+            // flip to a result of an option
+            .map_or(Ok(None), |res| res.map(Some))?;
+        let env = Environment::new(
+            ctx,
+            data_dump,
+            runtime_config.check_data_race,
+            runtime_config.get_logging_config(),
+        );
         let wave =
             wave_file.as_ref().map(|p| match WaveWriter::open(p, &env) {
                 Ok(w) => w,
@@ -1273,9 +1365,124 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                     todo!("deal more gracefully with error: {err:?}")
                 }
             });
-        let mut output = Self { env, wave };
-        output.set_root_go_high();
-        output
+        Ok(Self {
+            base: BaseSimulator::new(env, runtime_config),
+            wave,
+        })
+    }
+
+    pub fn is_done(&self) -> bool {
+        self.base.is_done()
+    }
+
+    pub fn step(&mut self) -> CiderResult<()> {
+        self.base.step()
+    }
+
+    pub fn converge(&mut self) -> CiderResult<()> {
+        self.base.converge()
+    }
+
+    pub fn get_currently_running_groups(
+        &self,
+    ) -> impl Iterator<Item = GroupIdx> + '_ {
+        self.base.get_currently_running_groups()
+    }
+
+    pub fn is_group_running(&self, group_idx: GroupIdx) -> bool {
+        self.base.is_group_running(group_idx)
+    }
+
+    pub fn print_pc(&self) {
+        self.base.print_pc();
+    }
+
+    pub fn format_cell_state(
+        &self,
+        cell_idx: GlobalCellIdx,
+        print_code: PrintCode,
+        name: Option<&str>,
+    ) -> Option<String> {
+        self.base.format_cell_state(cell_idx, print_code, name)
+    }
+
+    pub fn format_cell_ports(
+        &self,
+        cell_idx: GlobalCellIdx,
+        print_code: PrintCode,
+        name: Option<&str>,
+    ) -> String {
+        self.base.format_cell_ports(cell_idx, print_code, name)
+    }
+
+    pub fn format_port_value(
+        &self,
+        port_idx: GlobalPortIdx,
+        print_code: PrintCode,
+    ) -> String {
+        self.base.format_port_value(port_idx, print_code)
+    }
+
+    pub fn traverse_name_vec(
+        &self,
+        name: &[String],
+    ) -> Result<Path, TraversalError> {
+        self.base.traverse_name_vec(name)
+    }
+
+    pub fn get_full_name<N>(&self, nameable: N) -> String
+    where
+        N: GetFullName<C>,
+    {
+        self.base.get_full_name(nameable)
+    }
+
+    pub(crate) fn env(&self) -> &Environment<C> {
+        self.base.env()
+    }
+
+    /// Evaluate the entire program
+    pub fn run_program(&mut self) -> CiderResult<()> {
+        if self.base.conf.debug_logging {
+            info!(self.base.env().logger, "Starting program execution");
+        }
+
+        match self.base.run_program_inner(self.wave.as_mut()) {
+            Ok(_) => {
+                if self.base.conf.debug_logging {
+                    info!(self.base.env().logger, "Finished program execution");
+                }
+                Ok(())
+            }
+            Err(e) => {
+                if self.base.conf.debug_logging {
+                    slog::error!(
+                        self.base.env().logger,
+                        "Program execution failed with error: {}",
+                        e.red()
+                    );
+                }
+                Err(e)
+            }
+        }
+    }
+
+    pub fn dump_memories(
+        &self,
+        dump_registers: bool,
+        all_mems: bool,
+    ) -> DataDump {
+        self.base.dump_memories(dump_registers, all_mems)
+    }
+
+    pub fn print_pc_string(&self) {
+        self.base.print_pc_string()
+    }
+}
+
+impl<C: AsRef<Context> + Clone> BaseSimulator<C> {
+    pub fn new(env: Environment<C>, conf: RuntimeConfig) -> Self {
+        Self { env, conf }
     }
 
     pub(crate) fn env(&self) -> &Environment<C> {
@@ -1295,27 +1502,6 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         self.env
     }
 
-    pub fn build_simulator(
-        ctx: C,
-        data_file: &Option<std::path::PathBuf>,
-        wave_file: &Option<std::path::PathBuf>,
-        check_races: bool,
-    ) -> Result<Self, BoxedInterpreterError> {
-        let data_dump = data_file
-            .as_ref()
-            .map(|path| {
-                let mut file = std::fs::File::open(path)?;
-                DataDump::deserialize(&mut file)
-            })
-            // flip to a result of an option
-            .map_or(Ok(None), |res| res.map(Some))?;
-
-        Ok(Simulator::new(
-            Environment::new(ctx, data_dump, check_races),
-            wave_file,
-        ))
-    }
-
     pub fn is_group_running(&self, group_idx: GroupIdx) -> bool {
         self.env.is_group_running(group_idx)
     }
@@ -1350,6 +1536,10 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         self.env.print_pc()
     }
 
+    pub fn print_pc_string(&self) {
+        self.env.print_pc_string()
+    }
+
     /// Pins the port with the given name to the given value. This may only be
     /// used for input ports on the entrypoint component (excluding the go port)
     /// and will panic if used otherwise. Intended for external use.
@@ -1375,7 +1565,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
 }
 
 // =========================== simulation functions ===========================
-impl<C: AsRef<Context> + Clone> Simulator<C> {
+impl<C: AsRef<Context> + Clone> BaseSimulator<C> {
     #[inline]
     fn lookup_global_port_id(&self, port: GlobalPortRef) -> GlobalPortIdx {
         match port {
@@ -1602,7 +1792,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
     }
 
     //
-    pub fn converge(&mut self) -> InterpreterResult<()> {
+    pub fn converge(&mut self) -> CiderResult<()> {
         self.undef_all_ports();
         self.set_root_go_high();
         // set the pinned values
@@ -1613,7 +1803,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         for (comp, id) in self.env.pc.finished_comps() {
             let done_port = self.env.get_comp_done(*comp);
             let v = PortValue::new_implicit(BitVecValue::tru());
-            self.env.ports[done_port] = if self.env.check_data_race {
+            self.env.ports[done_port] = if self.conf.check_data_race {
                 v.with_thread(id.expect("finished comps should have a thread"))
             } else {
                 v
@@ -1669,7 +1859,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                     self.env.ports[go] =
                         PortValue::new_implicit(BitVecValue::tru())
                             .with_thread_optional(
-                                if self.env.check_data_race {
+                                if self.conf.check_data_race {
                                     assert!(thread.is_some());
                                     thread
                                 } else {
@@ -1715,13 +1905,13 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         let assigns_bundle = self.get_assignments(self.env.pc.node_slice());
 
         self.simulate_combinational(&assigns_bundle)
-            .map_err(|e| e.prettify_message(&self.env))
+            .map_err(|e| e.prettify_message(&self.env).into())
     }
 
-    pub fn step(&mut self) -> InterpreterResult<()> {
+    pub fn step(&mut self) -> CiderResult<()> {
         self.converge()?;
 
-        let out: Result<(), BoxedInterpreterError> = {
+        let out: Result<(), BoxedCiderError> = {
             let mut result = Ok(());
             for cell in self.env.cells.values_mut() {
                 match cell {
@@ -1747,7 +1937,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                     CellLedger::Component(_) => {}
                 }
             }
-            result
+            result.map_err(|e| e.prettify_message(&self.env).into())
         };
 
         self.env.pc.clear_finished_comps();
@@ -1759,11 +1949,13 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         let mut removed = vec![];
 
         for (i, node) in vecs.iter_mut().enumerate() {
-            let keep_node = self.evaluate_control_node(
-                node,
-                &mut new_nodes,
-                (&mut par_map, &mut with_map, &mut repeat_map),
-            )?;
+            let keep_node = self
+                .evaluate_control_node(
+                    node,
+                    &mut new_nodes,
+                    (&mut par_map, &mut with_map, &mut repeat_map),
+                )
+                .map_err(|e| e.prettify_message(&self.env))?;
             if !keep_node {
                 removed.push(i);
             }
@@ -1780,7 +1972,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         // insert all the new nodes from the par into the program counter
         self.env.pc.vec_mut().extend(new_nodes);
 
-        out.map_err(|err| err.prettify_message(&self.env))
+        out
     }
 
     fn evaluate_control_node(
@@ -1788,7 +1980,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         node: &mut ControlTuple,
         new_nodes: &mut Vec<ControlTuple>,
         maps: PcMaps,
-    ) -> InterpreterResult<bool> {
+    ) -> RuntimeResult<bool> {
         let (node_thread, node) = node;
         let (par_map, with_map, repeat_map) = maps;
         let comp_go = self.env.get_comp_go(node.comp);
@@ -1828,7 +2020,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
 
                     if *count == 0 {
                         par_map.remove(node);
-                        if self.env.check_data_race {
+                        if self.conf.check_data_race {
                             let thread =
                                 thread.expect("par nodes should have a thread");
 
@@ -1861,7 +2053,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                         ),
                     );
                     new_nodes.extend(par.stms().iter().map(|x| {
-                        let thread = if self.env.check_data_race {
+                        let thread = if self.conf.check_data_race {
                             let thread =
                                 thread.expect("par nodes should have a thread");
 
@@ -1898,7 +2090,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                         (thread, node.new_retain_comp(*x))
                     }));
 
-                    if self.env.check_data_race {
+                    if self.conf.check_data_race {
                         let thread =
                             thread.expect("par nodes should have a thread");
                         let clock = self.env.thread_map.unwrap_clock_id(thread);
@@ -1978,7 +2170,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
          // either we are not a par node, or we are the last par node
          (!matches!(&self.env.ctx.as_ref().primary[node.control_node_idx], ControlNode::Par(_)) || !par_map.contains_key(node))
         {
-            if self.env.check_data_race {
+            if self.conf.check_data_race {
                 assert!(
                     thread.is_some(),
                     "finished comps should have a thread"
@@ -2004,7 +2196,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         with_map: &mut HashMap<ControlPoint, WithEntry>,
         node: &mut ControlPoint,
         thread: Option<ThreadIdx>,
-    ) -> InterpreterResult<bool> {
+    ) -> RuntimeResult<bool> {
         let target = GlobalPortRef::from_local(
             w.cond_port(),
             &self.env.cells[node.comp].unwrap_comp().index_bases,
@@ -2015,7 +2207,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
             GlobalPortRef::Ref(r) => self.env.ref_ports[r]
                 .expect("While condition (ref) is undefined"),
         };
-        if self.env.check_data_race {
+        if self.conf.check_data_race {
             if let Some(clocks) = self.env.ports[idx].clocks() {
                 let read_clock =
                     self.env.thread_map.unwrap_clock_id(thread.unwrap());
@@ -2059,7 +2251,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
         node: &mut ControlPoint,
         thread: Option<ThreadIdx>,
         i: &If,
-    ) -> InterpreterResult<bool> {
+    ) -> RuntimeResult<bool> {
         if i.cond_group().is_some() && with_map.get(node).unwrap().entered {
             with_map.remove(node);
             Ok(node.mutate_into_next(self.env.ctx.as_ref()))
@@ -2078,7 +2270,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                     .expect("If condition (ref) is undefined"),
             };
 
-            if self.env.check_data_race {
+            if self.conf.check_data_race {
                 if let Some(clocks) = self.env.ports[idx].clocks() {
                     let read_clock =
                         self.env.thread_map.unwrap_clock_id(thread.unwrap());
@@ -2116,18 +2308,20 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
             .unwrap_or_default()
     }
 
-    /// Evaluate the entire program
-    pub fn run_program(&mut self) -> InterpreterResult<()> {
+    pub fn run_program_inner(
+        &mut self,
+        mut wave: Option<&mut WaveWriter>,
+    ) -> Result<(), BoxedCiderError> {
         let mut time = 0;
         while !self.is_done() {
-            if let Some(wave) = self.wave.as_mut() {
+            if let Some(wave) = wave.as_mut() {
                 wave.write_values(time, &self.env.ports)?;
             }
             // self.print_pc();
-            self.step().map_err(|e| e.prettify_message(&self.env))?;
+            self.step()?;
             time += 1;
         }
-        if let Some(wave) = self.wave.as_mut() {
+        if let Some(wave) = wave {
             wave.write_values(time, &self.env.ports)?;
         }
         Ok(())
@@ -2190,10 +2384,14 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
     fn simulate_combinational(
         &mut self,
         assigns_bundle: &[ScheduledAssignments],
-    ) -> InterpreterResult<()> {
+    ) -> RuntimeResult<()> {
         let mut has_changed = true;
         let mut have_zeroed_control_ports = false;
 
+        if self.conf.debug_logging {
+            info!(self.env.logger, "Started combinational convergence");
+        }
+
         while has_changed {
             has_changed = false;
 
@@ -2254,7 +2452,23 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                                 .get_global_port_idx(&assign.src, *active_cell);
                             let val = &self.env.ports[port];
 
-                            if self.env.check_data_race {
+                            if self.conf.debug_logging {
+                                info!(
+                                    self.env.logger,
+                                    "Assignment fired in {}: {}\n     wrote {}",
+                                    self.env.get_full_name(active_cell),
+                                    self.ctx()
+                                        .printer()
+                                        .print_assignment(
+                                            ledger.comp_id,
+                                            assign_idx
+                                        )
+                                        .yellow(),
+                                    val.bold()
+                                );
+                            }
+
+                            if self.conf.check_data_race {
                                 if let Some(clocks) = val.clocks() {
                                     // skip checking clocks for continuous assignments
                                     if !is_cont {
@@ -2304,14 +2518,40 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                             }
 
                             if let Some(v) = val.as_option() {
-                                let changed = self.env.ports.insert_val(
+                                let result = self.env.ports.insert_val(
                                     dest,
                                     AssignedValue::new(
                                         v.val().clone(),
-                                        assign_idx,
+                                        (assign_idx, *active_cell),
                                     )
                                     .with_thread_optional(thread),
-                                )?;
+                                );
+
+                                let changed = match result {
+                                    Ok(update) => update,
+                                    Err(e) => {
+                                        match e.a1.winner() {
+                                            AssignmentWinner::Assign(assignment_idx, global_cell_idx) => {
+                                                let assign = &self.env.ctx.as_ref().primary[*assignment_idx];
+                                                if !self
+                                                .evaluate_guard(assign.guard, *global_cell_idx)
+                                                .unwrap_or_default() {
+                                                    // the prior assignment is
+                                                    // no longer valid so we
+                                                    // replace it with the new
+                                                    // one
+                                                    let target = self.get_global_port_idx(&assign.dst, *global_cell_idx);
+                                                    self.env.ports[target] = e.a2.into();
+
+                                                    UpdateStatus::Changed
+                                                } else {
+                                                    return Err(RuntimeError::ConflictingAssignments(e).into());
+                                                }
+                                            },
+                                            _ => return Err(RuntimeError::ConflictingAssignments(e).into()),
+                                        }
+                                    }
+                                };
 
                                 has_changed |= changed.as_bool();
                             }
@@ -2327,7 +2567,7 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                             }
                         }
 
-                        if self.env.check_data_race {
+                        if self.conf.check_data_race {
                             if let Some(read_ports) = self
                                 .env
                                 .ctx
@@ -2413,9 +2653,78 @@ impl<C: AsRef<Context> + Clone> Simulator<C> {
                         self.env.ports[*port] =
                             PortValue::new_implicit(BitVecValue::zero(*width));
                         has_changed = true;
+
+                        if self.conf.debug_logging {
+                            info!(
+                                self.env.logger,
+                                "Control port {} has been implicitly set to zero",
+                                self.env.get_full_name(*port)
+                            );
+                        }
+                    }
+                }
+            }
+        }
+
+        if self.conf.undef_guard_check {
+            let mut error_v = vec![];
+            for bundle in assigns_bundle.iter() {
+                let ledger =
+                    self.env.cells[bundle.active_cell].as_comp().unwrap();
+                let go = bundle
+                    .interface_ports
+                    .as_ref()
+                    .map(|x| &ledger.index_bases + x.go);
+                let done = bundle
+                    .interface_ports
+                    .as_ref()
+                    .map(|x| &ledger.index_bases + x.done);
+
+                if !done
+                    .and_then(|done| self.env.ports[done].as_bool())
+                    .unwrap_or_default()
+                    && go
+                        .and_then(|go| self.env.ports[go].as_bool())
+                        .unwrap_or(true)
+                {
+                    for assign in bundle.assignments.iter() {
+                        let guard_idx = self.ctx().primary[assign].guard;
+                        if self
+                            .evaluate_guard(guard_idx, bundle.active_cell)
+                            .is_none()
+                        {
+                            let inner_v = self
+                                .ctx()
+                                .primary
+                                .guard_read_map
+                                .get(guard_idx)
+                                .unwrap()
+                                .iter()
+                                .filter_map(|p| {
+                                    let p = self.get_global_port_idx(
+                                        p,
+                                        bundle.active_cell,
+                                    );
+                                    if self.env.ports[p].is_undef() {
+                                        Some(p)
+                                    } else {
+                                        None
+                                    }
+                                })
+                                .collect_vec();
+
+                            error_v.push((bundle.active_cell, assign, inner_v))
+                        }
                     }
                 }
             }
+            if !error_v.is_empty() {
+                return Err(RuntimeError::UndefinedGuardError(error_v).into());
+            }
+        }
+
+        if self.conf.debug_logging {
+            info!(self.env.logger, "Finished combinational convergence");
         }
 
         Ok(())
@@ -2626,26 +2935,30 @@ impl<C: AsRef<Context> + Clone> GetFullName<C> for GlobalCellIdx {
 
 impl<C: AsRef<Context> + Clone> GetFullName<C> for GlobalPortIdx {
     fn get_full_name(&self, env: &Environment<C>) -> String {
-        let (parent_path, _) = env.get_parent_path_from_port(*self).unwrap();
-        let path_str = env.format_path(&parent_path);
+        if let Some((parent_path, _)) = env.get_parent_path_from_port(*self) {
+            let path_str = env.format_path(&parent_path);
 
-        let immediate_parent = parent_path.last().unwrap();
-        let comp = if env.cells[*immediate_parent].as_comp().is_some() {
-            *immediate_parent
-        } else {
-            // get second-to-last parent
-            parent_path[parent_path.len() - 2]
-        };
+            let immediate_parent = parent_path.last().unwrap();
+            let comp = if env.cells[*immediate_parent].as_comp().is_some() {
+                *immediate_parent
+            } else {
+                // get second-to-last parent
+                parent_path[parent_path.len() - 2]
+            };
 
-        let ledger = env.cells[comp].as_comp().unwrap();
+            let ledger = env.cells[comp].as_comp().unwrap();
 
-        let local_offset = *self - &ledger.index_bases;
-        let comp_def = &env.ctx().secondary[ledger.comp_id];
-        let port_def_idx = &comp_def.port_offset_map[local_offset];
-        let port_def = &env.ctx().secondary[*port_def_idx];
-        let name = env.ctx().lookup_name(port_def.name);
+            let local_offset = *self - &ledger.index_bases;
+            let comp_def = &env.ctx().secondary[ledger.comp_id];
+            let port_def_idx = &comp_def.port_offset_map[local_offset];
+            let port_def = &env.ctx().secondary[*port_def_idx];
+            let name = env.ctx().lookup_name(port_def.name);
 
-        format!("{path_str}.{name}")
+            format!("{path_str}.{name}")
+        } else {
+            // TODO griffin: this is a hack plz fix
+            "<unable to get full name>".to_string()
+        }
     }
 }
 
diff --git a/interp/src/flatten/structures/environment/mod.rs b/interp/src/flatten/structures/environment/mod.rs
index f5133e74af..40808bfbea 100644
--- a/interp/src/flatten/structures/environment/mod.rs
+++ b/interp/src/flatten/structures/environment/mod.rs
@@ -5,7 +5,7 @@ mod program_counter;
 mod traverser;
 mod wave;
 
-pub use env::{Environment, PortMap, Simulator};
+pub use env::{BaseSimulator, Environment, PortMap, Simulator};
 pub use traverser::{Path, PathError, PathResolution};
 
 pub(crate) use env::CellLedger;
diff --git a/interp/src/flatten/structures/environment/program_counter.rs b/interp/src/flatten/structures/environment/program_counter.rs
index 3ce817a844..918303141e 100644
--- a/interp/src/flatten/structures/environment/program_counter.rs
+++ b/interp/src/flatten/structures/environment/program_counter.rs
@@ -59,6 +59,67 @@ impl ControlPoint {
             false
         }
     }
+
+    /// Returns a string showing the path from the root node to input node. This
+    /// path is displayed in the minimal metadata path syntax.
+    pub fn string_path(&self, ctx: &Context) -> String {
+        let path = SearchPath::find_path_from_root(self.control_node_idx, ctx);
+        let mut path_vec = path.path;
+
+        // Remove first element since we know it is a root
+        path_vec.remove(0);
+        let mut string_path = String::new();
+        string_path.push('.');
+        let control_map = &ctx.primary.control;
+        let mut count = -1;
+        let mut body = false;
+        let mut if_branches: HashMap<ControlIdx, String> = HashMap::new();
+        for search_node in path_vec {
+            // The control_idx should exist in the map, so we shouldn't worry about it
+            // exploding. First SearchNode is root, hence "."
+            let control_idx = search_node.node;
+            let control_node = control_map.get(control_idx).unwrap();
+            match control_node {
+                // These are terminal nodes
+                // ControlNode::Empty(_) => "empty",
+                // ControlNode::Invoke(_) => "invoke",
+                // ControlNode::Enable(_) => "enable",
+
+                // These have unbounded children
+                // ControlNode::Seq(_) => "seq",
+                // ControlNode::Par(_) => "par",
+
+                // Special cases
+                ControlNode::If(if_node) => {
+                    if_branches.insert(if_node.tbranch(), String::from("t"));
+                    if_branches.insert(if_node.tbranch(), String::from("f"));
+                }
+                ControlNode::While(_) => {
+                    body = true;
+                }
+                ControlNode::Repeat(_) => {
+                    body = true;
+                }
+                _ => {}
+            };
+
+            let control_type = if body {
+                body = false;
+                count = -1;
+                String::from("b")
+            } else if if_branches.contains_key(&control_idx) {
+                let (_, branch) =
+                    if_branches.get_key_value(&control_idx).unwrap();
+                branch.clone()
+            } else {
+                count += 1;
+                count.to_string()
+            };
+
+            string_path = string_path + "-" + &control_type;
+        }
+        string_path
+    }
 }
 
 #[derive(Debug, Clone)]
@@ -375,7 +436,7 @@ impl WithEntry {
 
 /// The program counter for the whole program execution. Wraps over a vector of
 /// the active leaf statements for each component instance.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub(crate) struct ProgramCounter {
     vec: Vec<ControlTuple>,
     par_map: HashMap<ControlPoint, ChildCount>,
diff --git a/interp/src/flatten/structures/environment/wave.rs b/interp/src/flatten/structures/environment/wave.rs
index 1019433a09..9f7096dc24 100644
--- a/interp/src/flatten/structures/environment/wave.rs
+++ b/interp/src/flatten/structures/environment/wave.rs
@@ -18,7 +18,7 @@ pub enum WaveError {
 
 pub type Result<T> = std::result::Result<T, WaveError>;
 
-impl From<WaveError> for crate::errors::InterpreterError {
+impl From<WaveError> for crate::errors::CiderError {
     fn from(value: WaveError) -> Self {
         Self::GenericError(value.to_string())
     }
diff --git a/interp/src/flatten/structures/indexed_map.rs b/interp/src/flatten/structures/indexed_map.rs
index 137788b09b..e3f24c3150 100644
--- a/interp/src/flatten/structures/indexed_map.rs
+++ b/interp/src/flatten/structures/indexed_map.rs
@@ -4,7 +4,7 @@ use std::{
     ops::{self, Index},
 };
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct IndexedMap<K, D>
 where
     K: IndexRef,
@@ -143,6 +143,7 @@ where
         Self::new()
     }
 }
+
 #[allow(dead_code)]
 pub struct IndexedMapRangeIterator<'range, 'data, K, D>
 where
diff --git a/interp/src/flatten/structures/sparse_map.rs b/interp/src/flatten/structures/sparse_map.rs
index fc4fcfab60..0481dfa73a 100644
--- a/interp/src/flatten/structures/sparse_map.rs
+++ b/interp/src/flatten/structures/sparse_map.rs
@@ -118,7 +118,7 @@ where
     }
 }
 
-/// An analogue to [AuxillaryMap](super::indexed_map::AuxillaryMap) for sparse
+/// An analogue to [AuxiliaryMap](super::indexed_map::AuxiliaryMap) for sparse
 /// maps. This is used to store extra information that is only applicable to a
 /// subset of the indices in a primary map.
 #[derive(Debug, Clone)]
diff --git a/interp/src/flatten/structures/thread.rs b/interp/src/flatten/structures/thread.rs
index 938990dbae..8ca8a5bc9e 100644
--- a/interp/src/flatten/structures/thread.rs
+++ b/interp/src/flatten/structures/thread.rs
@@ -9,7 +9,7 @@ use super::{
 pub struct ThreadIdx(NonZeroU32);
 impl_index_nonzero!(ThreadIdx);
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ThreadInfo {
     parent: Option<ThreadIdx>,
     clock_id: ClockIdx,
@@ -25,7 +25,7 @@ impl ThreadInfo {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ThreadMap {
     map: IndexedMap<ThreadIdx, ThreadInfo>,
 }
diff --git a/interp/src/logging.rs b/interp/src/logging.rs
index 111f892674..387f5ffaf2 100644
--- a/interp/src/logging.rs
+++ b/interp/src/logging.rs
@@ -1,5 +1,3 @@
-use once_cell::sync::OnceCell;
-
 // re-export for convenience
 pub use slog::Logger;
 #[allow(unused_imports)]
@@ -7,38 +5,28 @@ pub(crate) use slog::{debug, error, info, o, trace, warn};
 
 use slog::{Drain, Level};
 
-static ROOT_LOGGER: OnceCell<Logger> = OnceCell::new();
-
-pub fn initialize_default_logger() {
-    initialize_logger(true);
-}
+use crate::configuration::LoggingConfig;
 
-pub fn initialize_logger(quiet: bool) {
+pub fn initialize_logger(conf: LoggingConfig) -> Logger {
     let decorator = slog_term::TermDecorator::new().stderr().build();
     let drain = slog_term::FullFormat::new(decorator).build();
-    let filter_level = if quiet { Level::Error } else { Level::Trace };
+    let filter_level = if conf.quiet && !conf.debug_logging {
+        Level::Error
+    } else {
+        Level::Trace
+    };
     let drain = drain.filter_level(filter_level).fuse();
 
-    let drain = slog_async::Async::new(drain).build().fuse();
+    // TODO griffin: make this configurable
+    let drain = slog_async::Async::new(drain).chan_size(1024).build().fuse();
 
     let logger = slog::Logger::root(drain, o!());
 
-    #[allow(unused_must_use)]
-    {
-        ROOT_LOGGER.set(logger);
+    if conf.quiet && conf.debug_logging {
+        warn!(
+            logger,
+            "Quiet mode ignored because debug logging is enabled"
+        )
     }
-}
-
-pub fn root() -> &'static Logger {
-    ROOT_LOGGER.get().unwrap_or_else(|| {
-        initialize_default_logger();
-        ROOT_LOGGER.get().unwrap()
-    })
-}
-
-/// Utility method for creating subloggers for components/primitives/etc. This
-/// is the preferred method for getting a logger. Initializes the source key with
-/// the supplied name.
-pub fn new_sublogger<S: AsRef<str>>(source_name: S) -> Logger {
-    root().new(o!("source" => String::from(source_name.as_ref())))
+    logger
 }
diff --git a/interp/src/main.rs b/interp/src/main.rs
index bcb7858839..6bc3058f13 100644
--- a/interp/src/main.rs
+++ b/interp/src/main.rs
@@ -6,11 +6,10 @@ use calyx_utils::OutputFile;
 use interp::{
     configuration,
     debugger::{Debugger, DebuggerInfo, DebuggerReturnStatus},
-    errors::InterpreterResult,
+    errors::CiderResult,
     flatten::structures::environment::Simulator,
 };
 
-use slog::warn;
 use std::{
     io::stdout,
     path::{Path, PathBuf},
@@ -51,14 +50,11 @@ pub struct Opts {
     /// rather than erroring
     allow_invalid_memory_access: bool,
 
-    #[argh(switch, long = "allow-par-conflicts")]
-    /// enables "sloppy" par simulation which allows parallel overlap when values agree
-    allow_par_conflicts: bool,
     #[argh(switch, long = "error-on-overflow")]
     /// upgrades [over | under]flow warnings to errors
     error_on_overflow: bool,
     /// silence warnings
-    #[argh(switch, short = 'q', long = "--quiet")]
+    #[argh(switch, short = 'q', long = "quiet")]
     quiet: bool,
 
     /// dump registers as single entry memories
@@ -68,6 +64,14 @@ pub struct Opts {
     #[argh(switch, long = "all-memories")]
     dump_all_memories: bool,
 
+    /// enables debug logging
+    #[argh(switch, long = "debug-logging")]
+    debug_logging: bool,
+
+    /// enable undefined guard check
+    #[argh(switch, long = "undef-guard-check")]
+    undef_guard_check: bool,
+
     /// optional wave file output path
     #[argh(option, long = "wave-file")]
     pub wave_file: Option<PathBuf>,
@@ -98,25 +102,22 @@ struct CommandInterpret {}
 struct CommandDebug {}
 
 /// Interpret a group from a Calyx program
-fn main() -> InterpreterResult<()> {
+fn main() -> CiderResult<()> {
     let opts: Opts = argh::from_env();
 
-    let config = configuration::ConfigBuilder::new()
-        .quiet(opts.quiet)
-        .allow_invalid_memory_access(opts.allow_invalid_memory_access)
-        .error_on_overflow(opts.error_on_overflow)
-        .allow_par_conflicts(opts.allow_par_conflicts)
+    let config = configuration::Config::builder()
         .dump_registers(opts.dump_registers)
         .dump_all_memories(opts.dump_all_memories)
         .build();
 
-    interp::logging::initialize_logger(config.quiet);
-
-    let log = interp::logging::root();
-
-    if config.allow_par_conflicts {
-        warn!(log, "You have enabled Par conflicts. This is not recommended and is usually a bad idea")
-    }
+    let runtime_config = configuration::RuntimeConfig::builder()
+        .check_data_race(opts.check_data_race)
+        .debug_logging(opts.debug_logging)
+        .quiet(opts.quiet)
+        .allow_invalid_memory_access(opts.allow_invalid_memory_access)
+        .error_on_overflow(opts.error_on_overflow)
+        .undef_guard_check(opts.undef_guard_check)
+        .build();
 
     let command = opts.mode.unwrap_or(Command::Interpret(CommandInterpret {}));
     let i_ctx = interp::flatten::setup_simulation(
@@ -131,7 +132,7 @@ fn main() -> InterpreterResult<()> {
                 &i_ctx,
                 &opts.data_file,
                 &opts.wave_file,
-                opts.check_data_race,
+                runtime_config,
             )?;
 
             sim.run_program()?;
@@ -149,7 +150,7 @@ fn main() -> InterpreterResult<()> {
                     &i_ctx,
                     &opts.data_file,
                     &opts.wave_file,
-                    opts.check_data_race,
+                    runtime_config,
                 )?;
 
                 let result = debugger.main_loop(info)?;
diff --git a/interp/src/serialization/data_dump.rs b/interp/src/serialization/data_dump.rs
index 5a95ffedfd..d676814c50 100644
--- a/interp/src/serialization/data_dump.rs
+++ b/interp/src/serialization/data_dump.rs
@@ -55,6 +55,10 @@ pub enum FormatInfo {
         int_width: u32,
         frac_width: u32,
     },
+    IEEFloat {
+        signed: bool,
+        width: u32,
+    },
 }
 
 impl FormatInfo {
@@ -62,6 +66,7 @@ impl FormatInfo {
         match self {
             FormatInfo::Bitnum { signed, .. } => *signed,
             FormatInfo::Fixed { signed, .. } => *signed,
+            FormatInfo::IEEFloat { signed, .. } => *signed,
         }
     }
 
@@ -73,6 +78,7 @@ impl FormatInfo {
                 frac_width,
                 ..
             } => *int_width + *frac_width,
+            FormatInfo::IEEFloat { width, .. } => *width,
         }
     }
 }
@@ -142,6 +148,10 @@ impl MemoryDeclaration {
         self.format.width()
     }
 
+    pub fn bytes_per_entry(&self) -> u32 {
+        self.format.width().div_ceil(8)
+    }
+
     pub fn signed(&self) -> bool {
         self.format.signed()
     }
@@ -166,6 +176,17 @@ impl DataHeader {
             .iter()
             .fold(0, |acc, mem| acc + mem.byte_count())
     }
+
+    pub fn serialize(&self) -> Result<Vec<u8>, SerializationError> {
+        let mut header_str = Vec::new();
+        ciborium::ser::into_writer(&self, &mut header_str)?;
+        Ok(header_str)
+    }
+
+    pub fn deserialize(data: &[u8]) -> Result<Self, SerializationError> {
+        let header: Self = ciborium::from_reader(data)?;
+        Ok(header)
+    }
 }
 
 #[derive(Debug, PartialEq)]
@@ -231,13 +252,11 @@ impl DataDump {
         self.push_memory(declaration, data)
     }
 
-    // TODO Griffin: handle the errors properly
-    pub fn serialize(
+    pub fn serialize<W: std::io::Write>(
         &self,
-        writer: &mut dyn std::io::Write,
+        mut writer: W,
     ) -> Result<(), SerializationError> {
-        let mut header_str = Vec::new();
-        ciborium::ser::into_writer(&self.header, &mut header_str)?;
+        let header_str = self.header.serialize()?;
         writer.write_all(&Self::MAGIC_NUMBER)?;
 
         let len_bytes: u32 = header_str
@@ -251,9 +270,8 @@ impl DataDump {
         Ok(())
     }
 
-    // TODO Griffin: handle the errors properly
-    pub fn deserialize(
-        reader: &mut dyn std::io::Read,
+    pub fn deserialize<R: std::io::Read>(
+        mut reader: R,
     ) -> Result<Self, SerializationError> {
         let mut magic_number = [0u8; 4];
         reader.read_exact(&mut magic_number).map_err(|e| {
@@ -285,7 +303,7 @@ impl DataDump {
                 SerializationError::IoError(e)
             }
         })?;
-        let header: DataHeader = ciborium::from_reader(raw_header.as_slice())?;
+        let header = DataHeader::deserialize(&raw_header)?;
 
         let mut data: Vec<u8> = Vec::with_capacity(header.data_size());
 
diff --git a/interp/src/serialization/formatting.rs b/interp/src/serialization/formatting.rs
index ed41367c2a..55166958fc 100644
--- a/interp/src/serialization/formatting.rs
+++ b/interp/src/serialization/formatting.rs
@@ -8,7 +8,7 @@ use baa::{BitVecOps, BitVecValue, WidthInt};
 
 /// An enum wrapping over a tuple representing the shape of a multi-dimensional
 /// array
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub enum Shape {
     D1(usize),
     D2(usize, usize),
@@ -31,6 +31,17 @@ impl Shape {
             Shape::D4(d0, d1, d2, d3) => d0 * d1 * d2 * d3,
         }
     }
+
+    pub fn as_string(&self) -> String {
+        match self {
+            Shape::D1(d0) => format!("({})", d0),
+            Shape::D2(d0, d1) => format!("({}, {})", d0, d1),
+            Shape::D3(d0, d1, d2) => format!("({}, {}, {})", d0, d1, d2),
+            Shape::D4(d0, d1, d2, d3) => {
+                format!("({}, {}, {}, {})", d0, d1, d2, d3)
+            }
+        }
+    }
 }
 impl From<usize> for Shape {
     fn from(u: usize) -> Self {
diff --git a/primitives/binary_operators.sv b/primitives/binary_operators.sv
index 1cfe5beabe..7ba413f0d8 100644
--- a/primitives/binary_operators.sv
+++ b/primitives/binary_operators.sv
@@ -146,7 +146,7 @@ module std_fp_div_pipe #(
         running <= running;
     end
 
-    always_comb begin
+    always @* begin
       if (acc >= {1'b0, right}) begin
         acc_next = acc - right;
         {acc_next, quotient_next} = {acc_next[WIDTH-1:0], quotient, 1'b1};
diff --git a/primitives/core.futil b/primitives/core.futil
index 99e5127f87..ef1c5195e8 100644
--- a/primitives/core.futil
+++ b/primitives/core.futil
@@ -8,7 +8,7 @@ extern "core.sv" {
   comb primitive std_bit_slice<"share"=1>[IN_WIDTH, START_IDX, END_IDX, OUT_WIDTH](@data in: IN_WIDTH) -> (out: OUT_WIDTH);
 
 
-  /// Logical operators
+  /// Logical Operators
   comb primitive std_not<"share"=1>[WIDTH](@data in: WIDTH) -> (out: WIDTH);
   comb primitive std_and<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH);
   comb primitive std_or<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH);
@@ -24,4 +24,28 @@ extern "core.sv" {
   comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1);
   comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH);
   comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH);
+
+  // Skid Buffers
+  primitive std_skid_buffer<"share"=1>[WIDTH](
+    @data in: WIDTH,
+    i_valid : 1,
+    i_ready : 1,
+    @clk clk: 1,
+    @reset reset: 1
+  ) -> (
+    @stable out: WIDTH,
+    o_valid : 1,
+    o_ready : 1
+  );
+
+  // Bypass Register
+  primitive std_bypass_reg<"share"=1>[WIDTH](
+    @data in: WIDTH,
+    @go write_en: 1,
+    @clk clk: 1,
+    @reset reset: 1
+  ) -> (
+    @stable out: WIDTH,
+    @done done: 1
+  );
 }
diff --git a/primitives/core.sv b/primitives/core.sv
index 227fd06046..d9ddc9d7ab 100644
--- a/primitives/core.sv
+++ b/primitives/core.sv
@@ -215,7 +215,7 @@ module std_bit_slice #(
    input wire logic [IN_WIDTH-1:0] in,
    output logic [OUT_WIDTH-1:0] out
 );
-    assign out = in[END_IDX:START_IDX];
+  assign out = in[END_IDX:START_IDX];
 
   `ifdef VERILATOR
     always_comb begin
@@ -230,3 +230,71 @@ module std_bit_slice #(
   `endif
 
 endmodule
+
+module std_skid_buffer #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic i_valid,
+    input wire logic i_ready,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic o_valid,
+    output logic o_ready
+);
+  logic [WIDTH-1:0] val;
+  logic bypass_rg;
+  always @(posedge clk) begin
+    // Reset  
+    if (reset) begin      
+      // Internal Registers
+      val <= '0;     
+      bypass_rg <= 1'b1;
+    end   
+    // Out of reset
+    else begin      
+      // Bypass state      
+      if (bypass_rg) begin         
+        if (!i_ready && i_valid) begin
+          val <= in;          // Data skid happened, store to buffer
+          bypass_rg <= 1'b0;  // To skid mode  
+        end 
+      end 
+      // Skid state
+      else begin         
+        if (i_ready) begin
+          bypass_rg <= 1'b1;  // Back to bypass mode           
+        end
+      end
+    end
+  end
+
+  assign o_ready = bypass_rg;
+  assign out = bypass_rg ? in : val;
+  assign o_valid = bypass_rg ? i_valid : 1'b1;
+endmodule
+
+module std_bypass_reg #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic write_en,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic done
+);
+  logic [WIDTH-1:0] val;
+  assign out = write_en ? in : val;
+
+  always_ff @(posedge clk) begin
+    if (reset) begin
+      val <= 0;
+      done <= 0;
+    end else if (write_en) begin
+      val <= in;
+      done <= 1'd1;
+    end else done <= 1'd0;
+  end
+endmodule
diff --git a/runt.toml b/runt.toml
index 6c1ac6e1fa..c5b421b234 100644
--- a/runt.toml
+++ b/runt.toml
@@ -213,16 +213,16 @@ paths = [
   "tests/correctness/ref-cells/*.futil",
   "tests/correctness/sync/*.futil",
   "tests/correctness/static-interface/*.futil",
+  "tests/correctness/ieee754-float/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s verilog.data {}.data \
-         -s calyx.exec './target/debug/calyx' \
-         -s verilog.cycle_limit 500 \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through verilator \
+     -s sim.data={}.data \
+     -s calyx.exec='./target/debug/calyx' \
+     -s verilog.cycle_limit=500 \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -235,15 +235,14 @@ paths = [
   "tests/correctness/static-interface/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags '-x tdcc:one-hot-cutoff=500 -d static-promotion' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags='-x tdcc:one-hot-cutoff=500 -d static-promotion' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -256,15 +255,14 @@ paths = [
   "tests/correctness/static-interface/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags '-x tdcc:duplicate-cutoff=0 -d static-promotion' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags='-x tdcc:duplicate-cutoff=0 -d static-promotion' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -273,17 +271,17 @@ name = "correctness static timing"
 paths = [
   "tests/correctness/*.futil",
   "tests/correctness/ref-cells/*.futil",
+  "tests/correctness/skid-buffers/*.futil",
   "tests/correctness/static-interface/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -291,15 +289,14 @@ timeout = 120
 name = "correctness static timing one-hot encoding"
 paths = ["tests/correctness/static-interface/*.futil"]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags '-x static-fsm-opts:one-hot-cutoff=500' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags='-x static-fsm-opts:one-hot-cutoff=500' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -308,15 +305,14 @@ timeout = 120
 name = "correctness nested"
 paths = ["tests/correctness/*.futil"]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s verilog.data {}.data \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags ' --nested' \
-         -s verilog.cycle_limit 500 \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s sim.data={}.data \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags=' --nested' \
+     -s verilog.cycle_limit=500 \
+     -s jq.expr=".memories" \
+     {} -q
 """
 timeout = 120
 
@@ -330,14 +326,13 @@ paths = [
   "tests/correctness/group-static-promotion/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags '-p all -d group2invoke' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags='-p all -d group2invoke' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     {} -q
 """
 timeout = 120
 
@@ -350,14 +345,13 @@ paths = [
   "tests/correctness/group-static-promotion/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through verilog \
-         --through dat \
-         -s calyx.exec './target/debug/calyx' \
-         -s calyx.flags '-p all -d group2invoke -x compile-static:one-hot-cutoff=500' \
-         -s verilog.cycle_limit 500 \
-         -s verilog.data {}.data \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s calyx.exec='./target/debug/calyx' \
+     -s calyx.flags='-p all -d group2invoke -x compile-static:one-hot-cutoff=500' \
+     -s verilog.cycle_limit=500 \
+     -s sim.data={}.data \
+     {} -q
 """
 timeout = 120
 
@@ -369,37 +363,34 @@ paths = [
   "tests/correctness/numeric-types/fixed-point/*.futil",
 ]
 cmd = """
-fud exec --from calyx --to jq \
-         --through dat \
-         --through verilog \
-         -s verilog.data {}.data \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 
 [[tests]]
 name = "correctness test of static islands without static promotion"
 paths = ["tests/correctness/static-islands/*.futil"]
 cmd = """
-fud exec --from calyx --to jq \
-         --through dat \
-         --through verilog \
-         -s verilog.data {}.data \
-         -s calyx.flags "-d static-promotion" \
-         -s jq.expr ".memories" \
-         {} -q
+fud2 --from calyx --to jq \
+    --through icarus \
+    -s sim.data={}.data \
+    -s calyx.flags="-d static-promotion" \
+    -s jq.expr=".memories" \
+    {} -q
 """
 
 [[tests]]
 name = "[frontend] tcam testing"
 paths = ["tests/correctness/tcam/*.futil"]
 cmd = """
-fud exec --from calyx --to jq \
-      --through verilog \
-      --through dat \
-      -s verilog.data {}.data \
-      -s jq.expr ".memories" \
-      {} -q
+fud2 --from calyx --to jq \
+     --through icarus \
+     -s sim.data={}.data \
+     -s jq.expr=".memories" \
+     {} -q
 """
 
 [[tests]]
diff --git a/tools/btor2/btor2i/Cargo.toml b/tools/btor2/btor2i/Cargo.toml
deleted file mode 100644
index f4c33be76c..0000000000
--- a/tools/btor2/btor2i/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "btor2i"
-version = "0.1.0"
-authors = ["Justin Ngai <jn537@cornell.edu>", "Sanjit Basker <sb866@cornell.edu>","Omkar Bhalerao <opb7@cornell.edu>"]
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-clap = { version = "4.4.7", features = ["derive"] }
-num-bigint = "0.4.4"
-num-traits = "0.2.17"
-bitvec = "1.0.1"
-tempfile = "3.8.1"
-thiserror = "1.0.50"
-num-integer = "0.1.45"
-btor2tools = { git = "https://github.com/obhalerao/btor2tools.rs" }
\ No newline at end of file
diff --git a/tools/btor2/btor2i/Makefile b/tools/btor2/btor2i/Makefile
deleted file mode 100644
index 6e2314837e..0000000000
--- a/tools/btor2/btor2i/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-TESTS := ./test/*.btor2
-
-.PHONY: install
-install:
-	RUSTFLAGS="-C target-cpu=native" cargo install --path .
-
-.PHONY: test
-test:
-	turnt $(TESTS)
-
-.PHONY: benchmarks
-benchmarks:
-	python3 brench-pipeless/brench.py benchmark.toml
-
-# This is primarily used for running examples and debuging a bril program
-.PHONY: example
-example:
-	bril2json < ../benchmarks/sqrt.bril | cargo run
diff --git a/tools/btor2/btor2i/README.md b/tools/btor2/btor2i/README.md
deleted file mode 100644
index 91d6ad0c8b..0000000000
--- a/tools/btor2/btor2i/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# btor2i
-
-`btor2i` is a faster interpreter for BTOR2 written in rust.
-
-It is available as both a command-line interface and a rust library.
-
-## Command-line interface
-
-Using `cargo`; run `cargo install --path .` and make sure `$HOME/.cargo/bin` is 
-on your path.  
-
-Run `btor2i --help` for all of the supported flags.
-
-## Rust interface [WIP]
-
-`btor2i` can also be used in your rust code which may be advantageous. Add `btor2i`
- to your `Cargo.toml` with:
-
-```toml
-[dependencies.btor2i]
-version      = "0.1.0"
-path         = "../btor2i"
-```
-
-Check out `cargo doc --open` for exposed functions. 
-
-## Contributing
-
-Issues and PRs are welcome. For pull requests, make sure to run the [Turnt](https://github.com/cucapra/turnt)
-test harness with `make test` and `make benchmark`. The `make test` output is in 
-[TAP](https://testanything.org/) format and can be prettified with TAP consumers,
-like [Faucet](https://github.com/ljharb/faucet). There is also `.github/workflows/rust.yaml` 
-which will format your code and check  that it is conforming with clippy.
diff --git a/tools/btor2/btor2i/benchmark.toml b/tools/btor2/btor2i/benchmark.toml
deleted file mode 100644
index 51a72d1a07..0000000000
--- a/tools/btor2/btor2i/benchmark.toml
+++ /dev/null
@@ -1,37 +0,0 @@
-extract = 'Time elapsed: (\d+) µs'
-timeout = 60
-benchmarks = 'test/core/combo/*.btor2'
-
-[runs.baseline]
-pipeline = [
-    "cargo run -r -- -p -f {file_path} {args}",
-]
-[runs.10] 
-pipeline = [
-    "cargo run -r -- -p -n 10 -f {file_path} {args}",
-]
-
-[runs.100] 
-pipeline = [
-    "cargo run -r -- -p -n 100 -f {file_path} {args}",
-]
-
-[runs.1000]
-pipeline = [
-    "cargo run -r -- -p -n 1000 -f {file_path} {args}",
-]
-
-[runs.10000]
-pipeline = [
-    "cargo run -r -- -p -n 10000 -f {file_path} {args}",
-]
-
-[runs.100000]
-pipeline = [
-    "cargo run -r -- -p -n 100000 -f {file_path} {args}",
-]
-
-[runs.1000000]
-pipeline = [
-    "cargo run -r -- -p -n 1000000 -f {file_path} {args}",
-]
diff --git a/tools/btor2/btor2i/src/bvec.rs b/tools/btor2/btor2i/src/bvec.rs
deleted file mode 100644
index bd3faedf89..0000000000
--- a/tools/btor2/btor2i/src/bvec.rs
+++ /dev/null
@@ -1,718 +0,0 @@
-use std::{convert::From, fmt::Display, ops::Neg, ops::Rem};
-
-use bitvec::prelude::*;
-use num_bigint::{BigInt, BigUint};
-use num_integer::Integer;
-use num_traits::{One, Zero};
-
-#[derive(Debug, Clone)]
-pub struct BitVector {
-    bits: BitVec<usize, Lsb0>,
-}
-
-impl BitVector {
-    /// the value 0, of width `len`
-    pub fn zeros(len: usize) -> Self {
-        BitVector {
-            bits: BitVec::repeat(false, len),
-        }
-    }
-
-    /// the value 1, of width `len`
-    pub fn one(len: usize) -> Self {
-        let mut bits = BitVec::repeat(false, len);
-        bits.set(0, true);
-        BitVector { bits }
-    }
-
-    /// the value -1, of width `len`
-    pub fn ones(len: usize) -> Self {
-        BitVector {
-            bits: BitVec::repeat(true, len),
-        }
-    }
-
-    pub fn from_bool(b: bool) -> Self {
-        let mut bits: BitVec = BitVec::new();
-        bits.push(b);
-        BitVector { bits }
-    }
-
-    pub fn width(&self) -> usize {
-        self.bits.len()
-    }
-
-    /// sign-extend `bv` by `w` bits
-    pub fn sign_extend(bv: &BitVector, w: usize) -> Self {
-        let mut other_vec = BitVec::new();
-        for bit in bv.bits.iter() {
-            other_vec.push(*bit);
-        }
-        for _ in bv.bits.len()..bv.bits.len() + w {
-            other_vec.push(*bv.bits.last().as_deref().unwrap());
-        }
-        BitVector { bits: other_vec }
-    }
-
-    /// zero-extend `bv` by `w`
-    pub fn zero_extend(bv: &BitVector, w: usize) -> Self {
-        let mut other_vec = BitVec::new();
-        for bit in bv.bits.iter() {
-            other_vec.push(*bit);
-        }
-        for _ in 0..w {
-            other_vec.push(false);
-        }
-        BitVector { bits: other_vec }
-    }
-
-    /// keep bits `l` thru `u` (inclusive, 0-indexed) of `bv`
-    pub fn slice(bv: &BitVector, l: usize, u: usize) -> Self {
-        let mut other_vec = BitVec::new();
-        for i in (l)..(u + 1) {
-            other_vec.push(bv.bits[i]);
-        }
-
-        BitVector { bits: other_vec }
-    }
-
-    /// bitwise not
-    pub fn not(bv: &BitVector) -> Self {
-        let bits = bv.bits.clone();
-        BitVector { bits: !bits }
-    }
-
-    /// increment
-    pub fn inc(bv: &BitVector) -> Self {
-        let mut missing: usize = 0;
-        while missing < bv.bits.len() && bv.bits[missing] {
-            missing += 1
-        }
-        if missing == bv.bits.len() {
-            BitVector::zeros(bv.bits.len())
-        } else {
-            let mut ans = bv.clone();
-            ans.bits.set(missing, true);
-            for i in 0..missing {
-                ans.bits.set(i, false);
-            }
-            ans
-        }
-    }
-
-    pub fn inc2(bv: &BitVector) -> Self {
-        match bv.bits.first_zero() {
-            Some(missing) => {
-                let mut ans = bv.clone();
-                ans.bits.set(missing, true);
-                for i in 0..missing {
-                    ans.bits.set(i, false);
-                }
-                ans
-            }
-            None => BitVector::zeros(bv.bits.len()),
-        }
-    }
-
-    /// decrement
-    pub fn dec(bv: &BitVector) -> Self {
-        let mut present: usize = 0;
-        while present < bv.bits.len() && !bv.bits[present] {
-            present += 1
-        }
-        if present == bv.bits.len() {
-            BitVector::ones(bv.bits.len())
-        } else {
-            let mut ans = bv.clone();
-            ans.bits.set(present, false);
-            for i in 0..present {
-                ans.bits.set(i, true);
-            }
-            ans
-        }
-    }
-
-    pub fn dec2(bv: &BitVector) -> Self {
-        match bv.bits.first_one() {
-            Some(present) => {
-                let mut ans = bv.clone();
-                ans.bits.set(present, false);
-                for i in 0..present {
-                    ans.bits.set(i, true);
-                }
-                ans
-            }
-            None => BitVector::ones(bv.bits.len()),
-        }
-    }
-
-    /// two's complement negation
-    pub fn neg(bv: &BitVector) -> Self {
-        BitVector::inc(&BitVector::not(bv))
-    }
-
-    pub fn redand(bv: &BitVector) -> bool {
-        bv.bits.all()
-    }
-
-    pub fn redor(bv: &BitVector) -> bool {
-        bv.bits.any()
-    }
-
-    pub fn redxor(bv: &BitVector) -> bool {
-        bv.bits.count_ones() % 2 == 1
-    }
-
-    pub fn iff(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.bits.any() == bv2.bits.any()
-    }
-
-    pub fn implies(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv2.bits.any() | (!bv1.bits.any())
-    }
-
-    pub fn equals(bv1: &BitVector, bv2: &BitVector) -> bool {
-        if bv1.bits.count_ones() != bv2.bits.count_ones() {
-            return false;
-        }
-        for i in bv1.bits.iter_ones() {
-            if !(bv2.bits[i]) {
-                return false;
-            }
-        }
-        bv1.bits.len() == bv2.bits.len()
-    }
-
-    pub fn neq(bv1: &BitVector, bv2: &BitVector) -> bool {
-        !BitVector::equals(bv1, bv2)
-    }
-
-    pub fn to_usize(&self) -> usize {
-        let mut ans: usize = 0;
-        for i in 0..self.bits.len() {
-            if self.bits[i] {
-                ans += 1 << i;
-            }
-        }
-        ans
-    }
-
-    // perhaps a better implementation of this would be
-    // to construct the vector of bytes and pass that to from_[signed]_bytes
-    fn to_bigint(&self) -> BigInt {
-        if self.bits.is_empty() {
-            Zero::zero()
-        } else if self.bits[self.bits.len() - 1] {
-            if self.bits.count_ones() == 1 {
-                // handle min int separately
-                let inc = BitVector::inc(self);
-                return inc.to_bigint().checked_sub(&One::one()).unwrap();
-            } else {
-                // negations are fast because big int is sign-magnitude
-                let neg = BitVector::neg(self);
-                return neg.to_bigint().neg();
-            }
-        } else {
-            let mut ans: BigInt = Zero::zero();
-            for i in 0..self.bits.len() {
-                ans.set_bit(i.try_into().unwrap(), self.bits[i])
-            }
-            return ans;
-        }
-    }
-
-    fn to_biguint(&self) -> BigUint {
-        let mut ans: BigUint = Zero::zero();
-        for i in 0..self.bits.len() {
-            ans.set_bit(i.try_into().unwrap(), self.bits[i])
-        }
-        ans
-    }
-
-    pub fn from_bigint(b: BigInt, width: usize) -> Self {
-        let mut bits = BitVec::new();
-        for i in 0..width {
-            bits.push(b.bit(i.try_into().unwrap()));
-        }
-
-        BitVector { bits }
-    }
-
-    fn from_biguint(b: BigUint, width: usize) -> Self {
-        let mut bits = BitVec::new();
-        for i in 0..width {
-            bits.push(b.bit(i.try_into().unwrap()));
-        }
-
-        BitVector { bits }
-    }
-
-    pub fn sgt(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_bigint() > bv2.to_bigint()
-    }
-
-    pub fn ugt(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_biguint() > bv2.to_biguint()
-    }
-
-    pub fn sgte(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_bigint() >= bv2.to_bigint()
-    }
-
-    pub fn ugte(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_biguint() >= bv2.to_biguint()
-    }
-
-    pub fn slt(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_bigint() < bv2.to_bigint()
-    }
-
-    pub fn ult(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_biguint() < bv2.to_biguint()
-    }
-
-    pub fn slte(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_bigint() <= bv2.to_bigint()
-    }
-
-    pub fn ulte(bv1: &BitVector, bv2: &BitVector) -> bool {
-        bv1.to_biguint() <= bv2.to_biguint()
-    }
-
-    pub fn and(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let mut bits = bv1.bits.clone();
-        bits &= bv2.bits.as_bitslice();
-        BitVector { bits }
-    }
-
-    pub fn nand(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let mut bits = bv1.bits.clone();
-        bits &= bv2.bits.as_bitslice();
-        BitVector { bits: !bits }
-    }
-
-    pub fn nor(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::not(&BitVector::or(bv1, bv2))
-    }
-
-    pub fn or(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let mut bits = bv1.bits.clone();
-        bits |= bv2.bits.as_bitslice();
-        BitVector { bits }
-    }
-
-    pub fn xnor(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::not(&BitVector::xor(bv1, bv2))
-    }
-
-    pub fn xor(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let mut bits = bv1.bits.clone();
-        bits ^= bv2.bits.as_bitslice();
-        BitVector { bits }
-    }
-
-    /// rotate index 1 towards index 0
-    pub fn rol(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let len = bv1.bits.len();
-        let rotate_amount = bv2.to_usize();
-        let mut bits = bitvec![0; len];
-        for i in 0..len {
-            bits.set(i, bv1.bits[(i + rotate_amount) % len]);
-        }
-        BitVector { bits }
-    }
-
-    /// rotate index 1 away from index 0
-    pub fn ror(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let len = bv1.bits.len();
-        let rotate_amount = bv2.to_usize();
-        let mut bits = bitvec![0; len];
-        for i in 0..len {
-            bits.set((i + rotate_amount) % len, bv1.bits[i]);
-        }
-        BitVector { bits }
-    }
-
-    pub fn sll(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let len = bv1.bits.len();
-        let shift_amount = bv2.to_usize();
-        let mut bits = bitvec![0; len];
-        for i in shift_amount..len {
-            bits.set(i, bv1.bits[i - shift_amount]);
-        }
-        BitVector { bits }
-    }
-
-    pub fn sra(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let len = bv1.bits.len();
-        let shift_amount = bv2.to_usize();
-        let b = *bv1.bits.last().unwrap();
-        let mut bits = BitVec::repeat(b, len);
-        for i in 0..(len - shift_amount) {
-            bits.set(i, bv1.bits[i + shift_amount]);
-        }
-        BitVector { bits }
-    }
-
-    pub fn srl(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let len = bv1.bits.len();
-        let shift_amount = bv2.to_usize();
-        let mut bits = BitVec::repeat(false, len);
-        for i in 0..(len - shift_amount) {
-            bits.set(i, bv1.bits[i + shift_amount]);
-        }
-        BitVector { bits }
-    }
-
-    pub fn add(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_biguint(
-            bv1.to_biguint() + (bv2.to_biguint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn mul(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_biguint(
-            bv1.to_biguint() * (bv2.to_biguint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn sub(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_bigint(
-            bv1.to_bigint() - (&bv2.to_bigint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn udiv(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_biguint(
-            bv1.to_biguint() / bv2.to_biguint(),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn urem(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_biguint(
-            bv1.to_biguint().rem(&bv2.to_biguint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn sdiv(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_bigint(
-            bv1.to_bigint()
-                .checked_div(&bv2.to_bigint())
-                .unwrap_or(BitVector::ones(bv1.bits.len()).to_bigint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn smod(bv1: &BitVector, bv2: &BitVector) -> Self {
-        BitVector::from_bigint(
-            bv1.to_bigint().mod_floor(&bv2.to_bigint()),
-            bv1.bits.len(),
-        )
-    }
-
-    pub fn srem(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let bv1i = bv1.to_bigint();
-        let bv2i = bv2.to_bigint();
-        let ans = bv1i.mod_floor(&bv2i);
-        if bv1i.sign() != bv2i.sign() && !bv1i.is_zero() && !bv2i.is_zero() {
-            BitVector::from_bigint(ans - bv2i, bv1.bits.len())
-        } else {
-            BitVector::from_bigint(ans, bv1.bits.len())
-        }
-    }
-
-    pub fn saddo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn uaddo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn sdivo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn smulo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn umulo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn ssubo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn usubo(_bv1: &BitVector, _bv2: &BitVector) -> bool {
-        todo!()
-    }
-
-    pub fn concat(bv1: &BitVector, bv2: &BitVector) -> Self {
-        let mut bits = BitVec::new();
-        bits.reserve(bv1.bits.len() + bv2.bits.len());
-        for i in 0..bv1.bits.len() {
-            bits.push(bv1.bits[i]);
-        }
-        for i in 0..bv2.bits.len() {
-            bits.push(bv2.bits[i]);
-        }
-        BitVector { bits }
-    }
-
-    pub fn ite(cond: &BitVector, bv1: &BitVector, bv2: &BitVector) -> Self {
-        assert!(cond.bits.len() == 1);
-        if cond.bits[0] {
-            bv1.clone()
-        } else {
-            bv2.clone()
-        }
-    }
-
-    pub fn from_int(val: usize, len: usize) -> Self {
-        let mut bits = BitVec::new();
-        for i in 0..len {
-            bits.push((val >> i) & 1 == 1);
-        }
-        BitVector { bits }
-    }
-}
-
-impl From<usize> for BitVector {
-    fn from(i: usize) -> Self {
-        let bitvec = BitVec::from_element(i);
-        BitVector { bits: bitvec }
-    }
-}
-
-impl From<Vec<bool>> for BitVector {
-    fn from(v: Vec<bool>) -> Self {
-        let mut bits = BitVec::new();
-        for bit in v.iter() {
-            bits.push(*bit);
-        }
-        BitVector { bits }
-    }
-}
-
-impl Display for BitVector {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let chunked_bits = self
-            .bits
-            .iter()
-            .map(|bit| if *bit { '1' } else { '0' })
-            .collect::<Vec<_>>()
-            .chunks_mut(4)
-            .rev()
-            .map(|chunk| {
-                chunk.reverse();
-                chunk.iter().collect::<String>()
-            })
-            .collect::<Vec<_>>()
-            .join(" ");
-        write!(
-            f,
-            "BitVector(length: {}; bits: {})",
-            self.bits.len(),
-            chunked_bits
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn naive_test_eq(bv1: &BitVector, bv2: &BitVector) -> bool {
-        for i in bv1.bits.iter_ones() {
-            if !(bv2.bits[i]) {
-                return false;
-            }
-        }
-        for i in bv2.bits.iter_ones() {
-            if !(bv1.bits[i]) {
-                return false;
-            }
-        }
-        bv1.bits.len() == bv2.bits.len()
-    }
-
-    #[test]
-    /// checks internal representation (no actual logic)
-    fn test_helpers() {
-        let bv = BitVector::from(vec![true, false, true, true]);
-        let bv_7 = BitVector::ones(4);
-        let bv_7_2 = BitVector::from(vec![true, true, true, true]);
-        assert!(bv.bits[0]);
-        assert!(!bv.bits[1]);
-        assert!(bv.bits[2]);
-        assert!(bv.bits[3]);
-        assert!(!naive_test_eq(&bv, &bv_7));
-        assert!(naive_test_eq(&bv_7, &bv_7_2));
-        println!(
-            "{}",
-            BitVector::from(vec![
-                true, false, true, true, false, true, true, false, false,
-                false, false
-            ])
-        );
-    }
-
-    #[test]
-    fn test_slices() {
-        let bv_3 = BitVector::from(vec![true, true, false]);
-        let bv_5 = BitVector::from(vec![true, false, true]);
-
-        let bv_3_longer =
-            BitVector::from(vec![true, true, false, false, false]);
-
-        assert!(naive_test_eq(
-            &BitVector::sign_extend(&bv_3, 2),
-            &bv_3_longer,
-        ));
-        assert!(naive_test_eq(
-            &BitVector::zero_extend(&bv_3, 2),
-            &bv_3_longer,
-        ));
-
-        assert!(naive_test_eq(
-            &BitVector::sign_extend(&bv_5, 2),
-            &BitVector::from(vec![true, false, true, true, true]),
-        ));
-        assert!(naive_test_eq(
-            &BitVector::zero_extend(&bv_5, 3),
-            &BitVector::from(vec![true, false, true, false, false, false]),
-        ));
-
-        assert!(naive_test_eq(
-            &BitVector::slice(&bv_5, 0, 0),
-            &BitVector::from(vec![true]),
-        ));
-        assert!(naive_test_eq(&BitVector::slice(&bv_5, 0, 2), &bv_5));
-        assert!(naive_test_eq(
-            &BitVector::slice(&bv_3_longer, 1, 4),
-            &BitVector::from(vec![true, false, false, false]),
-        ));
-    }
-
-    #[test]
-    fn test_unary() {
-        let bv_0 = BitVector::from(vec![false, false]);
-        let bv_1 = BitVector::from(vec![true, false]);
-        let bv_2 = BitVector::from(vec![false, true]);
-        let bv_3 = BitVector::from(vec![true, true]);
-
-        assert!(naive_test_eq(&BitVector::inc(&bv_0), &bv_1));
-        assert!(naive_test_eq(&BitVector::inc(&bv_1), &bv_2));
-        assert!(naive_test_eq(&BitVector::inc(&bv_2), &bv_3));
-        assert!(naive_test_eq(&BitVector::inc(&bv_3), &bv_0));
-
-        assert!(naive_test_eq(&BitVector::dec(&bv_1), &bv_0));
-        assert!(naive_test_eq(&BitVector::dec(&bv_2), &bv_1));
-        assert!(naive_test_eq(&BitVector::dec(&bv_3), &bv_2));
-        assert!(naive_test_eq(&BitVector::dec(&bv_0), &bv_3));
-
-        assert!(naive_test_eq(&BitVector::not(&bv_0), &bv_3));
-        assert!(naive_test_eq(&BitVector::not(&bv_1), &bv_2));
-
-        // pairs add to 4
-        assert!(naive_test_eq(&BitVector::neg(&bv_0), &bv_0));
-        assert!(naive_test_eq(&BitVector::neg(&bv_1), &bv_3));
-        assert!(naive_test_eq(&BitVector::neg(&bv_2), &bv_2));
-        assert!(naive_test_eq(&BitVector::neg(&bv_3), &bv_1));
-
-        assert!(BitVector::redand(&bv_3));
-        assert!(!BitVector::redand(&bv_1));
-        assert!(!BitVector::redand(&bv_2));
-        assert!(!BitVector::redand(&bv_0));
-
-        assert!(!BitVector::redor(&bv_0));
-        assert!(BitVector::redor(&bv_1));
-        assert!(BitVector::redor(&bv_2));
-        assert!(BitVector::redor(&bv_3));
-
-        assert!(!BitVector::redxor(&bv_0));
-        assert!(BitVector::redxor(&bv_1));
-        assert!(BitVector::redxor(&bv_2));
-        assert!(!BitVector::redxor(&bv_3));
-
-        assert!(naive_test_eq(
-            &BitVector::neg(&BitVector::neg(&BitVector::neg(&BitVector::neg(
-                &bv_3
-            )))),
-            &bv_3,
-        ));
-        assert!(naive_test_eq(
-            &BitVector::not(&BitVector::not(&BitVector::not(&BitVector::not(
-                &bv_2
-            )))),
-            &bv_2,
-        ));
-        assert!(naive_test_eq(
-            &BitVector::inc(&BitVector::dec(&BitVector::dec(&BitVector::inc(
-                &bv_2
-            )))),
-            &bv_2,
-        ));
-    }
-
-    #[test]
-    fn test_unsigned_arithmetic_small() {
-        let max = 128;
-        let size = 7;
-
-        let mut unsigned_numbers: Vec<BitVector> = Vec::new();
-        unsigned_numbers.push(BitVector::zeros(size));
-        for _i in 1..max {
-            unsigned_numbers
-                .push(BitVector::inc(unsigned_numbers.last().unwrap()));
-        }
-
-        for i in 0..max {
-            for j in 0..max {
-                let sum =
-                    BitVector::add(&unsigned_numbers[i], &unsigned_numbers[j]);
-                // let diff = BitVector::sub(&unsigned_numbers[i], &unsigned_numbers[j]);
-                let prod =
-                    BitVector::mul(&unsigned_numbers[i], &unsigned_numbers[j]);
-
-                // implementation-specific, behavior should be undefined in second case
-                let _sub_index = if i >= j { i - j } else { i + max - j };
-
-                assert!(naive_test_eq(&sum, &unsigned_numbers[(i + j) % max]));
-                // assert!(naive_test_eq(&diff, &unsigned_numbers[sub_index % max]));
-                assert!(naive_test_eq(&prod, &unsigned_numbers[(i * j) % max]));
-                if i < j {
-                    assert!(BitVector::ult(
-                        &unsigned_numbers[i],
-                        &unsigned_numbers[j]
-                    ));
-                }
-                if i <= j {
-                    assert!(BitVector::ulte(
-                        &unsigned_numbers[i],
-                        &unsigned_numbers[j]
-                    ));
-                }
-                if i > j {
-                    assert!(BitVector::ugt(
-                        &unsigned_numbers[i],
-                        &unsigned_numbers[j]
-                    ));
-                }
-                if i >= j {
-                    assert!(BitVector::ugte(
-                        &unsigned_numbers[i],
-                        &unsigned_numbers[j]
-                    ));
-                }
-            }
-        }
-    }
-}
diff --git a/tools/btor2/btor2i/src/cli.rs b/tools/btor2/btor2i/src/cli.rs
deleted file mode 100644
index b6e8bca7e4..0000000000
--- a/tools/btor2/btor2i/src/cli.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use clap::Parser;
-
-#[derive(Parser)]
-#[command(about, version, author)] // keeps the cli synced with Cargo.toml
-#[command(allow_hyphen_values(true))]
-pub struct CLI {
-    /// The BTOR2 file to run. stdin is assumed if file is not provided
-    #[arg(short, long, action)]
-    pub file: Option<String>,
-
-    /// Profile mode
-    #[arg(short, long, default_value = "false")]
-    pub profile: bool,
-
-    /// The number of times to repeat the simulation (used for profiling)
-    #[arg(short, long, default_value = "1")]
-    pub num_repeat: usize,
-
-    /// Inputs for the main function
-    #[arg(action)]
-    pub inputs: Vec<String>,
-}
diff --git a/tools/btor2/btor2i/src/error.rs b/tools/btor2/btor2i/src/error.rs
deleted file mode 100644
index 62d562e753..0000000000
--- a/tools/btor2/btor2i/src/error.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// use std::fmt::Display;
-use thiserror::Error;
-
-// Having the #[error(...)] for all variants derives the Display trait as well
-#[derive(Error, Debug)]
-pub enum InterpError {
-    #[error("Expected `{0}` function arguments, found `{1}`")]
-    BadNumFuncArgs(usize, usize), // (expected, actual)
-
-    #[error("Expected `{0}` instruction arguments, found `{1}`")]
-    BadNumArgs(usize, usize), // (expected, actual)
-
-    #[error("{0} is not a valid argment name")]
-    BadFuncArgName(String), // (expected, actual)
-
-    #[error("Expected int args, found `{0}`")]
-    BadFuncArgType(String), // (actual)
-
-    #[error("Expected {0} with width {1}, found `{2}`")]
-    BadFuncArgWidth(String, usize, usize), // (name, expected, actual)
-
-    #[error("Not currently supported: `{0}`")]
-    Unsupported(String), // (feature)
-}
-
-impl InterpError {
-    // #[must_use]
-    // pub fn add_pos(self, pos: Option<Position>) -> PositionalInterpError {
-    //   // TODO: Support PositionalInterpError in the future
-    // }
-}
-
-pub type InterpResult<T> = Result<T, InterpError>;
diff --git a/tools/btor2/btor2i/src/interp.rs b/tools/btor2/btor2i/src/interp.rs
deleted file mode 100644
index 12a1e0e44e..0000000000
--- a/tools/btor2/btor2i/src/interp.rs
+++ /dev/null
@@ -1,544 +0,0 @@
-use crate::bvec::BitVector;
-use crate::error;
-use crate::error::InterpError;
-use crate::shared_env::SharedEnvironment;
-use btor2tools::Btor2Line;
-use btor2tools::Btor2SortContent;
-use btor2tools::Btor2SortTag;
-use num_bigint::BigInt;
-use num_traits::Num;
-use std::collections::HashMap;
-use std::fmt;
-use std::slice::Iter;
-use std::vec;
-
-// TODO: eventually remove pub and make a seperate pub function as a main entry point to the interpreter, for now this is main.rs
-#[derive(Debug)]
-pub struct Environment {
-    // Maps sid/nid to value
-    // TODO: valid programs should not have the same identifier in both sets, but we don't currently check that
-    // TODO: perhaps could opportunistically free mappings if we know they won't be used again
-    // TODO: consider indirect mapping of output string -> id in env
-    env: Vec<Value>,
-    args: HashMap<String, usize>,
-    output: HashMap<String, Value>,
-}
-
-impl Environment {
-    pub fn new(size: usize) -> Self {
-        Self {
-            // Allocate a larger stack size so the interpreter needs to allocate less often
-            env: vec![Value::default(); size],
-            args: HashMap::new(),
-            output: HashMap::new(),
-        }
-    }
-
-    pub fn get(&self, idx: usize) -> &Value {
-        // A BTOR2 program is well formed when, dynamically, every variable is defined before its use.
-        // If this is violated, this will return Value::Uninitialized and the whole interpreter will come crashing down.
-        self.env.get(idx).unwrap()
-    }
-
-    pub fn set(&mut self, idx: usize, val: Value) {
-        self.env[idx] = val;
-    }
-
-    pub fn get_output(&self) -> &HashMap<String, Value> {
-        &self.output
-    }
-}
-
-impl fmt::Display for Environment {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        // iterate over self.args in order and print them
-
-        writeln!(f, "Arguments:")?;
-        let mut sorted_args = self.args.iter().collect::<Vec<_>>();
-        sorted_args.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
-        sorted_args.iter().try_for_each(|(name, val)| {
-            writeln!(f, "{}: {}", name, val)?;
-            Ok(())
-        })?;
-
-        write!(f, "\nEnvironment:\n")?;
-
-        // don't print uninitialized values
-        self.env.iter().enumerate().try_for_each(|(idx, val)| {
-            writeln!(f, "{}: {}", idx, val)?;
-            Ok(())
-        })?;
-
-        write!(f, "\nOutput:\n")?;
-        self.output.iter().try_for_each(|(name, val)| {
-            writeln!(f, "{}: {}", name, val)?;
-            Ok(())
-        })?;
-
-        Ok(())
-    }
-}
-
-// TODO: eventually remove pub and make a seperate pub function as a main entry point to the interpreter, for now this is main.rs
-#[derive(Debug, Default, Clone)]
-pub enum Value {
-    BitVector(BitVector),
-    // TODO: Add support for <STATE>
-    // TODO: Add support for <ARRAY>
-    #[default]
-    Uninitialized,
-}
-
-impl fmt::Display for Value {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Value::BitVector(bv) => write!(f, "{}", bv.to_usize()),
-            Value::Uninitialized => write!(f, "_"),
-        }
-    }
-}
-
-pub fn interpret(
-    mut prog_iterator: Iter<Btor2Line>,
-    env: &mut SharedEnvironment,
-) -> Result<(), InterpError> {
-    prog_iterator.try_for_each(|line| {
-        match line.tag() {
-            // core
-            btor2tools::Btor2Tag::Sort => Ok(()), // skip - sort information is handled by the parser
-            btor2tools::Btor2Tag::Const => eval_const_op(env, line, 2),
-            btor2tools::Btor2Tag::Constd => eval_const_op(env, line, 10),
-            btor2tools::Btor2Tag::Consth => eval_const_op(env, line, 16),
-            btor2tools::Btor2Tag::Input => Ok(()), // handled in parse_inputs
-            btor2tools::Btor2Tag::Output => Ok(()), // handled in extract_output
-            btor2tools::Btor2Tag::One => {
-                eval_literals_op(env, line, SharedEnvironment::one)
-            }
-            btor2tools::Btor2Tag::Ones => {
-                eval_literals_op(env, line, SharedEnvironment::ones)
-            }
-            btor2tools::Btor2Tag::Zero => {
-                eval_literals_op(env, line, SharedEnvironment::zero)
-            }
-
-            // indexed
-            btor2tools::Btor2Tag::Sext => {
-                eval_unary_op(env, line, SharedEnvironment::sext)
-            }
-            btor2tools::Btor2Tag::Uext => {
-                eval_unary_op(env, line, SharedEnvironment::uext)
-            }
-            btor2tools::Btor2Tag::Slice => eval_slice_op(env, line),
-
-            // unary
-            btor2tools::Btor2Tag::Not => {
-                eval_unary_op(env, line, SharedEnvironment::not)
-            }
-            btor2tools::Btor2Tag::Inc => {
-                eval_unary_op(env, line, SharedEnvironment::inc)
-            }
-            btor2tools::Btor2Tag::Dec => {
-                eval_unary_op(env, line, SharedEnvironment::dec)
-            }
-            btor2tools::Btor2Tag::Neg => {
-                eval_unary_op(env, line, SharedEnvironment::neg)
-            }
-            btor2tools::Btor2Tag::Redand => {
-                eval_unary_op(env, line, SharedEnvironment::redand)
-            }
-            btor2tools::Btor2Tag::Redor => {
-                eval_unary_op(env, line, SharedEnvironment::redor)
-            }
-            btor2tools::Btor2Tag::Redxor => {
-                eval_unary_op(env, line, SharedEnvironment::redxor)
-            }
-
-            // binary - boolean
-            btor2tools::Btor2Tag::Iff => {
-                eval_binary_op(env, line, SharedEnvironment::iff)
-            }
-            btor2tools::Btor2Tag::Implies => {
-                eval_binary_op(env, line, SharedEnvironment::implies)
-            }
-            btor2tools::Btor2Tag::Eq => {
-                eval_binary_op(env, line, SharedEnvironment::eq)
-            }
-            btor2tools::Btor2Tag::Neq => {
-                eval_binary_op(env, line, SharedEnvironment::neq)
-            }
-
-            // binary - (un)signed inequality
-            btor2tools::Btor2Tag::Sgt => {
-                eval_binary_op(env, line, SharedEnvironment::sgt)
-            }
-            btor2tools::Btor2Tag::Sgte => {
-                eval_binary_op(env, line, SharedEnvironment::sgte)
-            }
-            btor2tools::Btor2Tag::Slt => {
-                eval_binary_op(env, line, SharedEnvironment::slt)
-            }
-            btor2tools::Btor2Tag::Slte => {
-                eval_binary_op(env, line, SharedEnvironment::slte)
-            }
-            btor2tools::Btor2Tag::Ugt => {
-                eval_binary_op(env, line, SharedEnvironment::ugt)
-            }
-            btor2tools::Btor2Tag::Ugte => {
-                eval_binary_op(env, line, SharedEnvironment::ugte)
-            }
-            btor2tools::Btor2Tag::Ult => {
-                eval_binary_op(env, line, SharedEnvironment::ult)
-            }
-            btor2tools::Btor2Tag::Ulte => {
-                eval_binary_op(env, line, SharedEnvironment::ulte)
-            }
-
-            // binary - bit-wise
-            btor2tools::Btor2Tag::And => {
-                eval_binary_op(env, line, SharedEnvironment::and)
-            }
-            btor2tools::Btor2Tag::Nand => {
-                eval_binary_op(env, line, SharedEnvironment::nand)
-            }
-            btor2tools::Btor2Tag::Nor => {
-                eval_binary_op(env, line, SharedEnvironment::nor)
-            }
-            btor2tools::Btor2Tag::Or => {
-                eval_binary_op(env, line, SharedEnvironment::or)
-            }
-            btor2tools::Btor2Tag::Xnor => {
-                eval_binary_op(env, line, SharedEnvironment::xnor)
-            }
-            btor2tools::Btor2Tag::Xor => {
-                eval_binary_op(env, line, SharedEnvironment::xor)
-            }
-
-            // binary - rotate, shift
-            btor2tools::Btor2Tag::Rol => {
-                eval_binary_op(env, line, SharedEnvironment::rol)
-            }
-            btor2tools::Btor2Tag::Ror => {
-                eval_binary_op(env, line, SharedEnvironment::ror)
-            }
-            btor2tools::Btor2Tag::Sll => {
-                eval_binary_op(env, line, SharedEnvironment::sll)
-            }
-            btor2tools::Btor2Tag::Sra => {
-                eval_binary_op(env, line, SharedEnvironment::sra)
-            }
-            btor2tools::Btor2Tag::Srl => {
-                eval_binary_op(env, line, SharedEnvironment::srl)
-            }
-
-            // binary - arithmetic
-            btor2tools::Btor2Tag::Add => {
-                eval_binary_op(env, line, SharedEnvironment::add)
-            }
-            btor2tools::Btor2Tag::Mul => {
-                eval_binary_op(env, line, SharedEnvironment::mul)
-            }
-            btor2tools::Btor2Tag::Sdiv => {
-                eval_binary_op(env, line, SharedEnvironment::sdiv)
-            }
-            btor2tools::Btor2Tag::Udiv => {
-                eval_binary_op(env, line, SharedEnvironment::udiv)
-            }
-            btor2tools::Btor2Tag::Smod => {
-                eval_binary_op(env, line, SharedEnvironment::smod)
-            }
-            btor2tools::Btor2Tag::Srem => {
-                eval_binary_op(env, line, SharedEnvironment::srem)
-            }
-            btor2tools::Btor2Tag::Urem => {
-                eval_binary_op(env, line, SharedEnvironment::urem)
-            }
-            btor2tools::Btor2Tag::Sub => {
-                eval_binary_op(env, line, SharedEnvironment::sub)
-            }
-
-            // binary - overflow
-            btor2tools::Btor2Tag::Saddo => {
-                eval_binary_op(env, line, SharedEnvironment::saddo)
-            }
-            btor2tools::Btor2Tag::Uaddo => {
-                eval_binary_op(env, line, SharedEnvironment::uaddo)
-            }
-            btor2tools::Btor2Tag::Sdivo => {
-                eval_binary_op(env, line, SharedEnvironment::sdivo)
-            }
-            // btor2tools::Btor2Tag::Udivo => Ok(()),    Unsigned division never overflows :D
-            btor2tools::Btor2Tag::Smulo => {
-                eval_binary_op(env, line, SharedEnvironment::smulo)
-            }
-            btor2tools::Btor2Tag::Umulo => {
-                eval_binary_op(env, line, SharedEnvironment::umulo)
-            }
-            btor2tools::Btor2Tag::Ssubo => {
-                eval_binary_op(env, line, SharedEnvironment::ssubo)
-            }
-            btor2tools::Btor2Tag::Usubo => {
-                eval_binary_op(env, line, SharedEnvironment::usubo)
-            }
-
-            // binary - concat
-            btor2tools::Btor2Tag::Concat => {
-                eval_binary_op(env, line, SharedEnvironment::concat)
-            }
-
-            // ternary - conditional
-            btor2tools::Btor2Tag::Ite => {
-                eval_ternary_op(env, line, SharedEnvironment::ite)
-            }
-
-            // Unsupported: arrays, state, assertions
-            btor2tools::Btor2Tag::Bad
-            | btor2tools::Btor2Tag::Constraint
-            | btor2tools::Btor2Tag::Fair
-            | btor2tools::Btor2Tag::Init
-            | btor2tools::Btor2Tag::Justice
-            | btor2tools::Btor2Tag::Next
-            | btor2tools::Btor2Tag::State
-            | btor2tools::Btor2Tag::Read
-            | btor2tools::Btor2Tag::Write => Err(
-                error::InterpError::Unsupported(format!("{:?}", line.tag())),
-            ),
-        }
-    })
-}
-
-/// Handles the `const`, `constd`, and `consth` statements.
-fn eval_const_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-    radix: u32,
-) -> Result<(), error::InterpError> {
-    match line.constant() {
-        Some(cstr) => match cstr.to_str() {
-            Ok(str) => {
-                let nstring = str.to_string();
-                let intval = BigInt::from_str_radix(&nstring, radix).unwrap();
-
-                match line.sort().tag() {
-                    Btor2SortTag::Bitvec => {
-                        if let Btor2SortContent::Bitvec { width } =
-                            line.sort().content()
-                        {
-                            let bool_vec = (0..width)
-                                .map(|i| intval.bit(i as u64))
-                                .collect::<Vec<_>>();
-
-                            env.const_(line.id().try_into().unwrap(), bool_vec);
-                        }
-                        Ok(())
-                    }
-                    Btor2SortTag::Array => {
-                        Err(error::InterpError::Unsupported(format!(
-                            "{:?}",
-                            line.sort().tag()
-                        )))
-                    }
-                }
-            }
-            Err(_e) => Err(error::InterpError::BadFuncArgType(
-                "Bad value in constant".to_string(),
-            )),
-        },
-        None => Err(error::InterpError::BadFuncArgType(
-            "No value in constant".to_string(),
-        )),
-    }
-}
-
-/// Handle the `one`, `ones` and `zero` statements.
-fn eval_literals_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-    literal_init: fn(&mut SharedEnvironment, i1: usize),
-) -> Result<(), error::InterpError> {
-    match line.sort().tag() {
-        Btor2SortTag::Bitvec => {
-            literal_init(env, line.id().try_into().unwrap());
-            Ok(())
-        }
-        Btor2SortTag::Array => Err(error::InterpError::Unsupported(format!(
-            "{:?}",
-            line.sort().tag()
-        ))),
-    }
-}
-
-/// Handles the `slice` statements.
-fn eval_slice_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-) -> Result<(), error::InterpError> {
-    let sort = line.sort();
-    match sort.tag() {
-        Btor2SortTag::Bitvec => {
-            assert_eq!(line.args().len(), 3);
-            let arg1_line = line.args()[0] as usize;
-            let u = line.args()[1] as usize;
-            let l = line.args()[2] as usize;
-            if let Btor2SortContent::Bitvec { width } = line.sort().content() {
-                if (u - l) + 1 != width as usize {
-                    return Err(error::InterpError::Unsupported(format!(
-                        "Slicing of {:?} is not supported",
-                        arg1_line
-                    )));
-                }
-                env.slice(u, l, arg1_line, line.id().try_into().unwrap());
-                Ok(())
-            } else {
-                Err(error::InterpError::Unsupported(format!(
-                    "Slicing of {:?} is not supported",
-                    arg1_line
-                )))
-            }
-        }
-        Btor2SortTag::Array => Err(error::InterpError::Unsupported(format!(
-            "{:?}",
-            line.sort().tag()
-        ))),
-    }
-}
-
-/// Handle all the unary operators.
-fn eval_unary_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-    unary_fn: fn(&mut SharedEnvironment, usize, usize),
-) -> Result<(), error::InterpError> {
-    let sort = line.sort();
-    match sort.tag() {
-        Btor2SortTag::Bitvec => {
-            assert_eq!(line.args().len(), 1);
-            let arg1_line = line.args()[0] as usize;
-            unary_fn(env, arg1_line, line.id().try_into().unwrap());
-            Ok(())
-        }
-        Btor2SortTag::Array => Err(error::InterpError::Unsupported(format!(
-            "{:?}",
-            line.sort().tag()
-        ))),
-    }
-}
-
-/// Handles all the binary operators.
-fn eval_binary_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-    binary_fn: fn(&mut SharedEnvironment, usize, usize, usize),
-) -> Result<(), error::InterpError> {
-    let sort = line.sort();
-    match sort.tag() {
-        Btor2SortTag::Bitvec => {
-            assert_eq!(line.args().len(), 2);
-            let arg1_line = line.args()[0] as usize;
-            let arg2_line = line.args()[1] as usize;
-
-            binary_fn(env, arg1_line, arg2_line, line.id().try_into().unwrap());
-            Ok(())
-        }
-        Btor2SortTag::Array => Err(error::InterpError::Unsupported(format!(
-            "{:?}",
-            line.sort().tag()
-        ))),
-    }
-}
-
-fn eval_ternary_op(
-    env: &mut SharedEnvironment,
-    line: &btor2tools::Btor2Line,
-    ternary_fn: fn(&mut SharedEnvironment, usize, usize, usize, usize),
-) -> Result<(), error::InterpError> {
-    assert_eq!(line.args().len(), 3);
-    let arg1_line = line.args()[0] as usize;
-    let arg2_line = line.args()[1] as usize;
-    let arg3_line = line.args()[2] as usize;
-    ternary_fn(
-        env,
-        arg1_line,
-        arg2_line,
-        arg3_line,
-        line.id().try_into().unwrap(),
-    );
-    Ok(())
-}
-
-// TODO: eventually remove pub and make a seperate pub function as a main entry point to the interpreter, for now this is main.rs
-pub fn parse_inputs(
-    env: &mut SharedEnvironment,
-    lines: &[Btor2Line],
-    inputs: &[String],
-) -> Result<(), InterpError> {
-    // create input name to line no. and sort map
-    let mut input_map = HashMap::new();
-    lines.iter().for_each(|line| {
-        if let btor2tools::Btor2Tag::Input = line.tag() {
-            let input_name =
-                line.symbol().unwrap().to_string_lossy().into_owned();
-            if let Btor2SortContent::Bitvec { width } = line.sort().content() {
-                input_map.insert(
-                    input_name,
-                    (
-                        usize::try_from(line.id()).unwrap(),
-                        usize::try_from(width).unwrap(),
-                    ),
-                );
-            }
-        }
-    });
-
-    if input_map.is_empty() && inputs.is_empty() {
-        Ok(())
-    } else if inputs.len() != input_map.len() {
-        Err(InterpError::BadNumFuncArgs(input_map.len(), inputs.len()))
-    } else {
-        inputs.iter().try_for_each(|input| {
-            // arg in the form "x=1", extract variable name and value
-            let mut split = input.split('=');
-            let arg_name = split.next().unwrap();
-            let arg_val = split.next().unwrap();
-
-            if !input_map.contains_key(arg_name) {
-                return Err(InterpError::BadFuncArgName(arg_name.to_string()));
-            }
-
-            let (idx, width) = input_map.get(arg_name).unwrap();
-
-            // input must begins with 0b
-            if arg_val.starts_with("0b") {
-                let arg_as_bin = arg_val
-                    .trim_start_matches("0b")
-                    .chars()
-                    .map(|c| c == '1')
-                    .collect::<Vec<_>>();
-
-                if arg_as_bin.len() > *width {
-                    return Err(InterpError::BadFuncArgWidth(
-                        arg_name.to_string(),
-                        *width,
-                        arg_as_bin.len(),
-                    ));
-                }
-
-                // pad with 0s if necessary
-                let arg_as_bin = if arg_as_bin.len() < *width {
-                    let mut arg_as_bin = arg_as_bin;
-                    arg_as_bin.resize(*width, false);
-                    arg_as_bin
-                } else {
-                    arg_as_bin
-                };
-
-                env.set_vec(*idx, arg_as_bin);
-
-                Ok(())
-            } else {
-                Err(InterpError::BadFuncArgType(
-                    "Input must be in binary format".to_string(),
-                ))
-            }
-        })
-    }
-}
diff --git a/tools/btor2/btor2i/src/lib.rs b/tools/btor2/btor2i/src/lib.rs
deleted file mode 100644
index 03dac751a9..0000000000
--- a/tools/btor2/btor2i/src/lib.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub mod bvec;
-pub mod cli;
-pub mod error;
-pub mod interp;
-pub mod program;
-pub mod shared_env;
diff --git a/tools/btor2/btor2i/src/main.rs b/tools/btor2/btor2i/src/main.rs
deleted file mode 100644
index 7f0b50fab7..0000000000
--- a/tools/btor2/btor2i/src/main.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-pub mod bvec;
-pub mod cli;
-pub mod error;
-pub mod interp;
-pub mod shared_env;
-
-use btor2tools::Btor2Parser;
-use clap::Parser;
-use error::InterpResult;
-use std::io;
-use std::path::Path;
-use std::time::Instant;
-use tempfile::NamedTempFile;
-
-fn main() -> InterpResult<()> {
-    let start = Instant::now();
-    let args = cli::CLI::parse();
-
-    let btor2_file = match args.file {
-        None => {
-            // If no file is provided, we assume stdin
-            let mut tmp = NamedTempFile::new().unwrap();
-            io::copy(&mut io::stdin(), &mut tmp).unwrap();
-            tmp.path().to_path_buf()
-        }
-        Some(input_file_path) => {
-            Path::new(input_file_path.as_str()).to_path_buf()
-        }
-    };
-
-    // Parse and store the btor2 file as Vec<Btor2Line>
-    let mut parser = Btor2Parser::new();
-    let btor2_lines =
-        parser.read_lines(&btor2_file).unwrap().collect::<Vec<_>>();
-
-    // take the btor2lines and convert them into normal lines
-
-    for _ in 0..args.num_repeat {
-        // Collect node sorts
-        let node_sorts = btor2_lines
-            .iter()
-            .map(|line| match line.tag() {
-                btor2tools::Btor2Tag::Sort | btor2tools::Btor2Tag::Output => 0,
-                _ => match line.sort().content() {
-                    btor2tools::Btor2SortContent::Bitvec { width } => {
-                        usize::try_from(width).unwrap()
-                    }
-                    btor2tools::Btor2SortContent::Array { .. } => 0, // TODO: handle arrays
-                },
-            })
-            .collect::<Vec<_>>();
-
-        // Init environment
-        // let mut env = interp::Environment::new(btor2_lines.len() + 1);
-        let mut s_env = shared_env::SharedEnvironment::new(node_sorts);
-
-        // Parse inputs
-        match interp::parse_inputs(&mut s_env, &btor2_lines, &args.inputs) {
-            Ok(()) => {}
-            Err(e) => {
-                eprintln!("{}", e);
-                std::process::exit(1);
-            }
-        };
-
-        // Main interpreter loop
-        interp::interpret(btor2_lines.iter(), &mut s_env)?;
-
-        // Print result of execution
-        if !args.profile {
-            println!("{}", s_env);
-
-            // Extract outputs
-            btor2_lines.iter().for_each(|line| {
-                if let btor2tools::Btor2Tag::Output = line.tag() {
-                    let output_name =
-                        line.symbol().unwrap().to_string_lossy().into_owned();
-                    let src_node_idx = line.args()[0] as usize;
-                    let output_val = s_env.get(src_node_idx);
-
-                    println!("{}: {}", output_name, output_val);
-                }
-            });
-        }
-    }
-
-    // print to stderr the time it took to run
-    let duration = start.elapsed();
-    eprintln!("Time elapsed: {} µs", duration.as_micros());
-
-    Ok(())
-}
diff --git a/tools/btor2/btor2i/src/program.rs b/tools/btor2/btor2i/src/program.rs
deleted file mode 100644
index 53035d8f58..0000000000
--- a/tools/btor2/btor2i/src/program.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-use crate::interp;
-use crate::shared_env;
-
-use btor2tools::Btor2Line;
-use btor2tools::Btor2Parser;
-use std::collections::HashMap;
-use std::path::Path;
-
-use bitvec::prelude::*;
-
-pub type BitString = BitVec<usize, Lsb0>;
-
-fn slice_to_u64(slice: &BitSlice) -> u64 {
-    let mut ans = 0;
-    for i in 0..slice.len() {
-        if slice[i] {
-            ans += 1 << i;
-        }
-    }
-    ans
-}
-
-// crappy thing that makes it work: no longer store lines, instead pass in reference to file path
-pub struct Btor2Program<'a> {
-    parser: Btor2Parser,
-    path: &'a Path,
-    // lines: Option<Vec<Btor2Line<'a>>>,
-}
-
-// impl Default for Btor2Program {
-//     fn default() -> Self {
-//         Self::new()
-//     }
-// }
-
-impl<'a> Btor2Program<'a> {
-    pub fn new(path: &'a str) -> Self {
-        Btor2Program {
-            parser: Btor2Parser::new(),
-            path: Path::new(path),
-        }
-    }
-
-    // pub fn load(&mut self, input_file: &str) -> Result<(), &str> {
-    //     // Parse and store the btor2 file as Vec<Btor2Line>
-    //     let input_path = Path::new(input_file);
-    //     let btor2_lines_opt = self.parser.read_lines(input_path);
-    //     match btor2_lines_opt {
-    //         Err(e) => {
-    //             eprintln!("{}", e);
-    //             Err("Input file not found.")
-    //         }
-    //         Ok(btor2_lines) => {
-    //             // self.lines = Option::Some(btor2_lines.collect::<Vec<_>>());
-    //             Ok(())
-    //         }
-    //     }
-    // }
-
-    pub fn run(
-        &mut self,
-        inputs: HashMap<String, String>,
-    ) -> Result<HashMap<String, u64>, &str> {
-        let btor2_lines: &Vec<Btor2Line<'_>> = &self
-            .parser
-            .read_lines(self.path)
-            .as_ref()
-            .unwrap()
-            .collect::<Vec<_>>();
-        let mut inputs_vec = Vec::new();
-        for (name, val) in &inputs {
-            inputs_vec.push(format!("{}={} ", name, val));
-        }
-
-        let node_sorts = btor2_lines
-            .iter()
-            .map(|line| match line.tag() {
-                btor2tools::Btor2Tag::Sort | btor2tools::Btor2Tag::Output => 0,
-                _ => match line.sort().content() {
-                    btor2tools::Btor2SortContent::Bitvec { width } => {
-                        usize::try_from(width).unwrap()
-                    }
-                    btor2tools::Btor2SortContent::Array { .. } => 0, // TODO: handle arrays
-                },
-            })
-            .collect::<Vec<_>>();
-
-        let mut s_env = shared_env::SharedEnvironment::new(node_sorts);
-
-        // Parse inputs
-        match interp::parse_inputs(&mut s_env, btor2_lines, &inputs_vec) {
-            Ok(()) => {}
-            Err(e) => {
-                eprintln!("{}", e);
-                return Err("Inputs invalid.");
-            }
-        };
-
-        // Main interpreter loop
-        let result = interp::interpret(btor2_lines.iter(), &mut s_env);
-        match result {
-            Ok(()) => {}
-            Err(e) => {
-                eprintln!("{}", e);
-                return Err("Runtime error in BTOR2 program.");
-            }
-        }
-
-        let mut output_map = HashMap::new();
-
-        btor2_lines.iter().for_each(|line| {
-            if let btor2tools::Btor2Tag::Output = line.tag() {
-                let output_name =
-                    line.symbol().unwrap().to_string_lossy().into_owned();
-                let src_node_idx = line.args()[0] as usize;
-                let output_val = s_env.get(src_node_idx);
-
-                output_map.insert(output_name, slice_to_u64(output_val));
-            }
-        });
-
-        Ok(output_map)
-    }
-}
diff --git a/tools/btor2/btor2i/src/shared_env.rs b/tools/btor2/btor2i/src/shared_env.rs
deleted file mode 100644
index bfa0d3fdf7..0000000000
--- a/tools/btor2/btor2i/src/shared_env.rs
+++ /dev/null
@@ -1,746 +0,0 @@
-use num_integer::Integer;
-use num_traits::{One, Zero};
-use std::cmp::Ordering;
-use std::fmt;
-use std::ops::Rem;
-
-use bitvec::prelude::*;
-use num_bigint::{BigInt, BigUint};
-use std::iter::once;
-
-#[derive(Debug)]
-pub struct SharedEnvironment {
-    shared_bits: BitVec<usize, Lsb0>, // RI: integers are little-endian
-    offsets: Vec<usize>,              // offsets[i] = start of node i
-}
-
-impl fmt::Display for SharedEnvironment {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "\nEnvironment:\n")?;
-
-        for i in 0..self.offsets.len() - 1 {
-            if self.offsets[i] == self.offsets[i + 1] {
-                writeln!(f, "{} : _", i)?;
-            } else if self.offsets[i + 1] - self.offsets[i]
-                > (usize::BITS).try_into().unwrap()
-            {
-                writeln!(f, "{} : too large to display", i)?;
-            } else {
-                writeln!(
-                    f,
-                    "{} : {}",
-                    i,
-                    SharedEnvironment::slice_to_usize(
-                        &self.shared_bits[self.offsets[i]..self.offsets[i + 1]]
-                    )
-                )?;
-            }
-        }
-
-        Ok(())
-    }
-}
-
-impl SharedEnvironment {
-    pub fn new(node_sorts: Vec<usize>) -> Self {
-        let offsets = once(&0usize)
-            .chain(once(&0usize))
-            .chain(node_sorts.iter())
-            .scan(0usize, |state, &x| {
-                *state += x;
-                Some(*state)
-            })
-            .collect::<Vec<_>>();
-        let shared_bits = BitVec::repeat(false, *offsets.last().unwrap());
-        SharedEnvironment {
-            shared_bits,
-            offsets,
-        }
-    }
-
-    /// Sets the bitslice corresponding to the node at with node_id `idx`
-    pub fn set(&mut self, idx: usize, value: &BitSlice) {
-        self.shared_bits[self.offsets[idx]..self.offsets[idx + 1]]
-            .copy_from_bitslice(value);
-    }
-
-    /// Sets the bitslice corresponding to the node at with node_id `idx`, used for inputs
-    pub fn set_vec(&mut self, idx: usize, value: Vec<bool>) {
-        for i in self.offsets[idx]..self.offsets[idx + 1] {
-            self.shared_bits.set(i, value[i - self.offsets[idx]]);
-        }
-    }
-
-    /// Returns the bitslice corresponding to the node at with node_id `idx`
-    pub fn get(&mut self, idx: usize) -> &BitSlice {
-        &self.shared_bits[self.offsets[idx]..self.offsets[idx + 1]]
-    }
-
-    pub fn sext(&mut self, i1: usize, i2: usize) {
-        let old_start = self.offsets[i1];
-        let old_end = self.offsets[i1 + 1];
-        let new_start = self.offsets[i2];
-        let new_end = self.offsets[i2 + 1];
-        let first_bit = self.shared_bits[old_start];
-        self.shared_bits.copy_within(old_start..old_end, new_start);
-        self.shared_bits[new_start + (old_end - old_start)..new_end]
-            .fill(first_bit);
-    }
-
-    pub fn uext(&mut self, i1: usize, i2: usize) {
-        let old_start = self.offsets[i1];
-        let old_end = self.offsets[i1 + 1];
-        let new_start = self.offsets[i2];
-        let new_end = self.offsets[i2 + 1];
-        self.shared_bits.copy_within(old_start..old_end, new_start);
-        self.shared_bits[new_start + (old_end - old_start)..new_end]
-            .fill(false);
-    }
-
-    pub fn slice(&mut self, u: usize, l: usize, i1: usize, i2: usize) {
-        let old_start = self.offsets[i1];
-        let new_start = self.offsets[i2];
-        self.shared_bits
-            .copy_within(old_start + l..old_start + u + 1, new_start);
-    }
-
-    pub fn not(&mut self, i1: usize, i2: usize) {
-        let old_start = self.offsets[i1];
-        let old_end = self.offsets[i1 + 1];
-        let new_start = self.offsets[i2];
-        let new_end = self.offsets[i2 + 1];
-        let mut rhs = BitVec::repeat(true, old_end - old_start);
-        rhs ^= &self.shared_bits[old_start..old_end];
-        self.shared_bits[new_start..new_end]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn and(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs &= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn nand(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs &= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        rhs = !rhs;
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn nor(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs |= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        rhs = !rhs;
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn or(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs |= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn xnor(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs ^= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        rhs = !rhs;
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn xor(&mut self, i1: usize, i2: usize, i3: usize) {
-        let mut rhs = BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        rhs ^= &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .copy_from_bitslice(rhs.as_bitslice());
-    }
-
-    pub fn concat(&mut self, i1: usize, i2: usize, i3: usize) {
-        let start1 = self.offsets[i1];
-        let end1 = self.offsets[i1 + 1];
-        let start2 = self.offsets[i2];
-        let end2 = self.offsets[i2 + 1];
-        let start3 = self.offsets[i3];
-        self.shared_bits.copy_within(start1..end1, start3);
-        self.shared_bits
-            .copy_within(start2..end2, start3 + end1 - start1);
-    }
-
-    fn slice_to_bigint(slice: &BitSlice) -> BigInt {
-        if slice.is_empty() {
-            Zero::zero()
-        } else if slice[slice.len() - 1] {
-            let z: BigInt = Zero::zero();
-            let o: BigInt = One::one();
-            let mut ans = z - o;
-            for i in 0..slice.len() {
-                ans.set_bit(i.try_into().unwrap(), slice[i])
-            }
-            ans
-        } else {
-            let mut ans: BigInt = Zero::zero();
-            for i in 0..slice.len() {
-                ans.set_bit(i.try_into().unwrap(), slice[i])
-            }
-            ans
-        }
-    }
-
-    fn slice_to_biguint(slice: &BitSlice) -> BigUint {
-        let mut ans: BigUint = Zero::zero();
-        for i in 0..slice.len() {
-            ans.set_bit(i.try_into().unwrap(), slice[i])
-        }
-        ans
-    }
-
-    fn slice_to_usize(slice: &BitSlice) -> usize {
-        let mut ans: usize = 0;
-        for i in 0..slice.len() {
-            if slice[i] {
-                ans += 1 << i;
-            }
-        }
-        ans
-    }
-
-    pub fn inc(&mut self, i1: usize, i2: usize) {
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i2],
-        );
-        let dest =
-            self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]].as_mut();
-        match dest.first_zero() {
-            Some(i) => {
-                dest[i..i + 1].fill(true);
-                dest[..i].fill(false);
-            }
-            None => {
-                dest.fill(false);
-            }
-        }
-    }
-
-    pub fn dec(&mut self, i1: usize, i2: usize) {
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i2],
-        );
-        let dest =
-            self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]].as_mut();
-        match dest.first_one() {
-            Some(i) => {
-                dest[i..i + 1].fill(false);
-                dest[..i].fill(true);
-            }
-            None => {
-                dest.fill(true);
-            }
-        }
-    }
-
-    pub fn neg(&mut self, i1: usize, i2: usize) {
-        let bitwise_neg = !BitVec::from_bitslice(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let dest =
-            self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]].as_mut();
-        dest.copy_from_bitslice(&bitwise_neg);
-
-        match dest.first_zero() {
-            Some(i) => {
-                dest[i..i + 1].fill(true);
-                dest[..i].fill(false);
-            }
-            None => {
-                dest.fill(false);
-            }
-        }
-    }
-
-    pub fn redand(&mut self, i1: usize, i2: usize) {
-        let ans =
-            self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]].all();
-        self.shared_bits[self.offsets[i2]..self.offsets[i2] + 1].fill(ans);
-    }
-
-    pub fn redor(&mut self, i1: usize, i2: usize) {
-        let ans =
-            self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]].any();
-        self.shared_bits[self.offsets[i2]..self.offsets[i2] + 1].fill(ans);
-    }
-
-    pub fn redxor(&mut self, i1: usize, i2: usize) {
-        let ans = self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]]
-            .count_ones()
-            % 2
-            == 1;
-        self.shared_bits[self.offsets[i2]..self.offsets[i2] + 1].fill(ans);
-    }
-
-    pub fn iff(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = self.shared_bits[self.offsets[i1]]
-            == self.shared_bits[self.offsets[i2]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn implies(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = !self.shared_bits[self.offsets[i1]]
-            || self.shared_bits[self.offsets[i2]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn eq(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]]
-            == self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn neq(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]]
-            != self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    fn compare_signed(&self, i1: usize, i2: usize) -> Ordering {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        a.cmp(&b)
-    }
-
-    fn compare_unsigned(&self, i1: usize, i2: usize) -> Ordering {
-        let a = &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]];
-        let b = &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]];
-        a.cmp(b)
-    }
-
-    pub fn sgt(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_signed(i1, i2) {
-            Ordering::Less => false,
-            Ordering::Equal => false,
-            Ordering::Greater => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn ugt(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_unsigned(i1, i2) {
-            Ordering::Less => false,
-            Ordering::Equal => false,
-            Ordering::Greater => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn sgte(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_signed(i1, i2) {
-            Ordering::Less => false,
-            Ordering::Equal => true,
-            Ordering::Greater => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn ugte(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_unsigned(i1, i2) {
-            Ordering::Less => false,
-            Ordering::Equal => true,
-            Ordering::Greater => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn slt(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_signed(i1, i2) {
-            Ordering::Greater => false,
-            Ordering::Equal => false,
-            Ordering::Less => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn ult(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_unsigned(i1, i2) {
-            Ordering::Greater => false,
-            Ordering::Equal => false,
-            Ordering::Less => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn slte(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_signed(i1, i2) {
-            Ordering::Greater => false,
-            Ordering::Equal => true,
-            Ordering::Less => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn ulte(&mut self, i1: usize, i2: usize, i3: usize) {
-        let ans = match self.compare_unsigned(i1, i2) {
-            Ordering::Greater => false,
-            Ordering::Equal => true,
-            Ordering::Less => true,
-        };
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(ans);
-    }
-
-    pub fn add(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a + b;
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn mul(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a * b;
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn sdiv(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a / b;
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn udiv(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_biguint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_biguint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a / b;
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn smod(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a.mod_floor(&b);
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn srem(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let mut c = a.mod_floor(&b);
-        if a.sign() != b.sign() && !a.is_zero() && !b.is_zero() {
-            c -= b;
-        }
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn urem(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_biguint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_biguint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a.rem(b);
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn sub(&mut self, i1: usize, i2: usize, i3: usize) {
-        let a = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]],
-        );
-        let b = Self::slice_to_bigint(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        let c = a - b;
-        for i in self.offsets[i3]..self.offsets[i3 + 1] {
-            self.shared_bits[i..i + 1]
-                .fill(c.bit((i - self.offsets[i3]).try_into().unwrap()));
-        }
-    }
-
-    pub fn saddo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn uaddo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn sdivo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn udivo(&mut self, _i1: usize, _i2: usize, i3: usize) {
-        self.shared_bits[self.offsets[i3]..self.offsets[i3] + 1].fill(false);
-    }
-
-    pub fn smulo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn umulo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn ssubo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn usubo(&mut self, _i1: usize, _i2: usize, _i3: usize) {
-        todo!()
-    }
-
-    pub fn ite(&mut self, i1: usize, i2: usize, i3: usize, i4: usize) {
-        if self.shared_bits[self.offsets[i1]] {
-            self.shared_bits.copy_within(
-                self.offsets[i2]..self.offsets[i2 + 1],
-                self.offsets[i4],
-            );
-        } else {
-            self.shared_bits.copy_within(
-                self.offsets[i3]..self.offsets[i3 + 1],
-                self.offsets[i4],
-            );
-        }
-    }
-
-    pub fn rol(&mut self, i1: usize, i2: usize, i3: usize) {
-        let shift_amount = Self::slice_to_usize(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i3],
-        );
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .rotate_right(shift_amount);
-    }
-
-    pub fn ror(&mut self, i1: usize, i2: usize, i3: usize) {
-        let shift_amount = Self::slice_to_usize(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i3],
-        );
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .rotate_left(shift_amount);
-    }
-
-    pub fn sll(&mut self, i1: usize, i2: usize, i3: usize) {
-        let shift_amount = Self::slice_to_usize(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i3],
-        );
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .shift_right(shift_amount);
-    }
-
-    pub fn sra(&mut self, i1: usize, i2: usize, i3: usize) {
-        let shift_amount = Self::slice_to_usize(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i3],
-        );
-        let last_bit = self.shared_bits[self.offsets[i1 + 1] - 1];
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .shift_left(shift_amount);
-        self.shared_bits
-            [self.offsets[i3 + 1] - shift_amount..self.offsets[i3 + 1]]
-            .fill(last_bit);
-    }
-
-    pub fn srl(&mut self, i1: usize, i2: usize, i3: usize) {
-        let shift_amount = Self::slice_to_usize(
-            &self.shared_bits[self.offsets[i2]..self.offsets[i2 + 1]],
-        );
-        self.shared_bits.copy_within(
-            self.offsets[i1]..self.offsets[i1 + 1],
-            self.offsets[i3],
-        );
-        self.shared_bits[self.offsets[i3]..self.offsets[i3 + 1]]
-            .shift_left(shift_amount);
-    }
-
-    pub fn one(&mut self, i1: usize) {
-        self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]].fill(false);
-        self.shared_bits[self.offsets[i1]..self.offsets[i1] + 1].fill(true); // little endian
-    }
-
-    pub fn ones(&mut self, i1: usize) {
-        self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]].fill(true);
-    }
-
-    pub fn zero(&mut self, i1: usize) {
-        self.shared_bits[self.offsets[i1]..self.offsets[i1 + 1]].fill(false);
-    }
-
-    pub fn const_(&mut self, i1: usize, value: Vec<bool>) {
-        for i in self.offsets[i1]..self.offsets[i1 + 1] {
-            self.shared_bits[i..i + 1].fill(value[i - self.offsets[i1]]);
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-
-    #[test]
-    fn test_get_set() {
-        let node_widths = vec![2, 8, 6];
-        let mut s_env = SharedEnvironment::new(node_widths);
-        assert!(s_env.get(1) == bits![0, 0]);
-        assert!(s_env.get(2) == bits![0, 0, 0, 0, 0, 0, 0, 0]);
-        assert!(s_env.get(3) == bits![0, 0, 0, 0, 0, 0]);
-
-        s_env.set(1, bits![0, 1]);
-        s_env.set(2, bits![0, 1, 0, 1, 1, 1, 1, 1]);
-        s_env.set(3, bits![0, 1, 0, 0, 0, 0]);
-
-        assert!(s_env.get(1) == bits![0, 1]);
-        assert!(s_env.get(2) == bits![0, 1, 0, 1, 1, 1, 1, 1]);
-        assert!(s_env.get(3) == bits![0, 1, 0, 0, 0, 0]);
-    }
-
-    #[test]
-    fn test_shift_left() {
-        let node_widths = vec![2, 8, 6, 8, 8, 8, 8, 8];
-        let mut s_env = SharedEnvironment::new(node_widths);
-        assert!(s_env.get(1) == bits![0, 0]);
-        assert!(s_env.get(2) == bits![0, 0, 0, 0, 0, 0, 0, 0]);
-        assert!(s_env.get(3) == bits![0, 0, 0, 0, 0, 0]);
-        s_env.set_vec(1, vec![false, true]);
-        s_env
-            .set_vec(2, vec![false, true, false, true, true, true, true, true]);
-        s_env.set_vec(3, vec![false, true, false, false, false, false]);
-
-        s_env.sll(2, 1, 4);
-        s_env.srl(2, 1, 5);
-        s_env.rol(2, 1, 6);
-        s_env.ror(2, 1, 7);
-        s_env.sra(2, 1, 8);
-        assert!(s_env.get(4) == bits![0, 0, 0, 1, 0, 1, 1, 1]);
-        assert!(s_env.get(5) == bits![0, 1, 1, 1, 1, 1, 0, 0]);
-        assert!(s_env.get(6) == bits![1, 1, 0, 1, 0, 1, 1, 1]);
-        assert!(s_env.get(7) == bits![0, 1, 1, 1, 1, 1, 0, 1]);
-        assert!(s_env.get(8) == bits![0, 1, 1, 1, 1, 1, 1, 1]);
-    }
-
-    #[test]
-    fn test_add_mul() {
-        let node_widths = vec![8, 8, 8, 8, 8];
-        let mut s_env = SharedEnvironment::new(node_widths);
-        s_env.set(1, bits![1, 1, 0, 0, 0, 0, 0, 0]);
-        s_env.set(2, bits![1, 1, 1, 0, 0, 0, 0, 0]);
-        s_env.set(3, bits![1, 1, 1, 1, 1, 0, 0, 0]);
-        s_env.add(1, 3, 4);
-        s_env.mul(1, 2, 5);
-        assert!(s_env.get(4) == bits![0, 1, 0, 0, 0, 1, 0, 0]);
-        assert!(s_env.get(5) == bits![1, 0, 1, 0, 1, 0, 0, 0]);
-    }
-
-    #[test]
-    fn test_bitwise() {
-        let node_widths = vec![4, 4, 4, 4, 4, 4, 4, 4];
-        let mut s_env = SharedEnvironment::new(node_widths);
-        s_env.set(1, bits![0, 1, 0, 1]);
-        s_env.set(2, bits![0, 0, 1, 1]);
-        s_env.and(1, 2, 3);
-        s_env.nand(1, 2, 4);
-        s_env.or(1, 2, 5);
-        s_env.nor(1, 2, 6);
-        s_env.xor(1, 2, 7);
-        s_env.xnor(1, 2, 8);
-        assert!(s_env.get(3) == bits![0, 0, 0, 1]);
-        assert!(s_env.get(4) == bits![1, 1, 1, 0]);
-        assert!(s_env.get(5) == bits![0, 1, 1, 1]);
-        assert!(s_env.get(6) == bits![1, 0, 0, 0]);
-        assert!(s_env.get(7) == bits![0, 1, 1, 0]);
-        assert!(s_env.get(8) == bits![1, 0, 0, 1]);
-    }
-
-    #[test]
-    fn test_comparisons() {
-        let node_widths = vec![4, 4, 1, 1];
-        let mut s_env = SharedEnvironment::new(node_widths);
-        s_env.set(1, bits![0, 1, 0, 1]);
-        s_env.set(2, bits![0, 0, 1, 0]);
-        s_env.sgt(1, 2, 3);
-        s_env.ugt(1, 2, 4);
-        assert!(s_env.get(3) == bits![0]);
-        assert!(s_env.get(4) == bits![1]);
-    }
-}
diff --git a/tools/btor2/core/std_add.btor b/tools/btor2/core/std_add.btor
deleted file mode 100644
index c76e54845f..0000000000
--- a/tools/btor2/core/std_add.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_add.
-1 sort bitvec 32
-2 input 1 left ; temp.sv:5.35-5.39
-3 input 1 right ; temp.sv:6.35-6.40
-4 add 1 2 3
-5 output 4 out ; temp.sv:7.35-7.38
-; end of yosys output
diff --git a/tools/btor2/core/std_and.btor b/tools/btor2/core/std_and.btor
deleted file mode 100644
index 7097df75a7..0000000000
--- a/tools/btor2/core/std_and.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_and.
-1 sort bitvec 32
-2 input 1 left ; core.sv:97.35-97.39
-3 input 1 right ; core.sv:98.35-98.40
-4 and 1 2 3
-5 output 4 out ; core.sv:99.35-99.38
-; end of yosys output
diff --git a/tools/btor2/core/std_cat.btor b/tools/btor2/core/std_cat.btor
deleted file mode 100644
index 56847207b9..0000000000
--- a/tools/btor2/core/std_cat.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_cat.
-1 sort bitvec 32
-2 input 1 left ; core.sv:63.39-63.43
-3 input 1 right ; core.sv:64.40-64.45
-4 sort bitvec 64
-5 concat 4 2 3
-6 output 5 out ; core.sv:65.34-65.37
-; end of yosys output
diff --git a/tools/btor2/core/std_eq.btor b/tools/btor2/core/std_eq.btor
deleted file mode 100644
index 1b4e39b479..0000000000
--- a/tools/btor2/core/std_eq.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_eq.
-1 sort bitvec 32
-2 input 1 left ; core.sv:157.34-157.38
-3 input 1 right ; core.sv:158.34-158.39
-4 sort bitvec 1
-5 eq 4 2 3
-6 output 5 out ; core.sv:159.18-159.21
-; end of yosys output
diff --git a/tools/btor2/core/std_ge.btor b/tools/btor2/core/std_ge.btor
deleted file mode 100644
index 9e55d2f14b..0000000000
--- a/tools/btor2/core/std_ge.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_ge.
-1 sort bitvec 32
-2 input 1 left ; core.sv:177.34-177.38
-3 input 1 right ; core.sv:178.34-178.39
-4 sort bitvec 1
-5 ugte 4 2 3
-6 output 5 out ; core.sv:179.18-179.21
-; end of yosys output
diff --git a/tools/btor2/core/std_gt.btor b/tools/btor2/core/std_gt.btor
deleted file mode 100644
index 9fd09e4a4e..0000000000
--- a/tools/btor2/core/std_gt.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_gt.
-1 sort bitvec 32
-2 input 1 left ; core.sv:137.34-137.38
-3 input 1 right ; core.sv:138.34-138.39
-4 sort bitvec 1
-5 ugt 4 2 3
-6 output 5 out ; core.sv:139.18-139.21
-; end of yosys output
diff --git a/tools/btor2/core/std_le.btor b/tools/btor2/core/std_le.btor
deleted file mode 100644
index c0c9369912..0000000000
--- a/tools/btor2/core/std_le.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_le.
-1 sort bitvec 32
-2 input 1 left ; core.sv:187.34-187.38
-3 input 1 right ; core.sv:188.34-188.39
-4 sort bitvec 1
-5 ulte 4 2 3
-6 output 5 out ; core.sv:189.18-189.21
-; end of yosys output
diff --git a/tools/btor2/core/std_lsh.btor b/tools/btor2/core/std_lsh.btor
deleted file mode 100644
index fa84191dca..0000000000
--- a/tools/btor2/core/std_lsh.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_lsh.
-1 sort bitvec 32
-2 input 1 left ; core.sv:197.35-197.39
-3 input 1 right ; core.sv:198.35-198.40
-4 sll 1 2 3
-5 output 4 out ; core.sv:199.35-199.38
-; end of yosys output
diff --git a/tools/btor2/core/std_lt.btor b/tools/btor2/core/std_lt.btor
deleted file mode 100644
index fcc942c6e9..0000000000
--- a/tools/btor2/core/std_lt.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_lt.
-1 sort bitvec 32
-2 input 1 left ; core.sv:147.34-147.38
-3 input 1 right ; core.sv:148.34-148.39
-4 sort bitvec 1
-5 ult 4 2 3
-6 output 5 out ; core.sv:149.18-149.21
-; end of yosys output
diff --git a/tools/btor2/core/std_mem_d1.btor b/tools/btor2/core/std_mem_d1.btor
deleted file mode 100644
index 34918fe27f..0000000000
--- a/tools/btor2/core/std_mem_d1.btor
+++ /dev/null
@@ -1,97 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module comb_mem_d1.
-1 sort bitvec 4
-2 input 1 addr0 ; core.sv:232.38-232.43
-3 sort bitvec 1
-4 input 3 clk ; core.sv:235.38-235.41
-5 input 3 reset ; core.sv:236.38-236.43
-6 sort bitvec 32
-7 input 6 write_data ; core.sv:233.38-233.48
-8 input 3 write_en ; core.sv:234.38-234.46
-9 state 3
-10 output 9 done ; core.sv:238.38-238.42
-11 sort array 1 6
-12 state 11 mem
-13 read 6 12 2
-14 output 13 read_data ; core.sv:237.38-237.47
-15 const 3 0
-16 const 3 1
-17 ite 3 8 16 15
-18 ite 3 5 15 17
-19 next 3 9 18
-20 input 1
-21 not 3 5
-22 and 3 21 8
-23 ite 1 22 2 20
-24 input 6
-25 ite 6 22 7 24
-26 ite 3 22 16 15
-27 sort bitvec 2
-28 concat 27 26 26
-29 sort bitvec 3
-30 concat 29 26 28
-31 concat 1 26 30
-32 sort bitvec 5
-33 concat 32 26 31
-34 sort bitvec 6
-35 concat 34 26 33
-36 sort bitvec 7
-37 concat 36 26 35
-38 sort bitvec 8
-39 concat 38 26 37
-40 sort bitvec 9
-41 concat 40 26 39
-42 sort bitvec 10
-43 concat 42 26 41
-44 sort bitvec 11
-45 concat 44 26 43
-46 sort bitvec 12
-47 concat 46 26 45
-48 sort bitvec 13
-49 concat 48 26 47
-50 sort bitvec 14
-51 concat 50 26 49
-52 sort bitvec 15
-53 concat 52 26 51
-54 sort bitvec 16
-55 concat 54 26 53
-56 sort bitvec 17
-57 concat 56 26 55
-58 sort bitvec 18
-59 concat 58 26 57
-60 sort bitvec 19
-61 concat 60 26 59
-62 sort bitvec 20
-63 concat 62 26 61
-64 sort bitvec 21
-65 concat 64 26 63
-66 sort bitvec 22
-67 concat 66 26 65
-68 sort bitvec 23
-69 concat 68 26 67
-70 sort bitvec 24
-71 concat 70 26 69
-72 sort bitvec 25
-73 concat 72 26 71
-74 sort bitvec 26
-75 concat 74 26 73
-76 sort bitvec 27
-77 concat 76 26 75
-78 sort bitvec 28
-79 concat 78 26 77
-80 sort bitvec 29
-81 concat 80 26 79
-82 sort bitvec 30
-83 concat 82 26 81
-84 sort bitvec 31
-85 concat 84 26 83
-86 concat 6 26 85
-87 read 6 12 23
-88 not 6 86
-89 and 6 87 88
-90 and 6 25 86
-91 or 6 90 89
-92 write 11 12 23 91
-93 redor 3 86
-94 ite 11 93 92 12
-95 next 11 12 94 mem ; core.sv:241.21-241.24
-; end of yosys output
diff --git a/tools/btor2/core/std_mem_d2.btor b/tools/btor2/core/std_mem_d2.btor
deleted file mode 100644
index 9144f1ebf8..0000000000
--- a/tools/btor2/core/std_mem_d2.btor
+++ /dev/null
@@ -1,104 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module comb_mem_d2.
-1 sort bitvec 4
-2 input 1 addr0 ; core.sv:272.41-272.46
-3 input 1 addr1 ; core.sv:273.41-273.46
-4 sort bitvec 1
-5 input 4 clk ; core.sv:276.41-276.44
-6 input 4 reset ; core.sv:277.41-277.46
-7 sort bitvec 32
-8 input 7 write_data ; core.sv:274.41-274.51
-9 input 4 write_en ; core.sv:275.41-275.49
-10 state 4
-11 output 10 done ; core.sv:279.41-279.45
-12 sort bitvec 8
-13 sort array 12 7
-14 state 13 mem
-15 uext 12 2 4
-16 sort bitvec 5
-17 const 16 10000
-18 uext 12 17 3
-19 mul 12 15 18
-20 uext 12 3 4
-21 add 12 19 20
-22 read 7 14 21
-23 output 22 read_data ; core.sv:278.41-278.50
-24 const 4 0
-25 const 4 1
-26 ite 4 9 25 24
-27 ite 4 6 24 26
-28 next 4 10 27
-29 input 12
-30 not 4 6
-31 and 4 30 9
-32 ite 12 31 21 29
-33 input 7
-34 ite 7 31 8 33
-35 ite 4 31 25 24
-36 sort bitvec 2
-37 concat 36 35 35
-38 sort bitvec 3
-39 concat 38 35 37
-40 concat 1 35 39
-41 concat 16 35 40
-42 sort bitvec 6
-43 concat 42 35 41
-44 sort bitvec 7
-45 concat 44 35 43
-46 concat 12 35 45
-47 sort bitvec 9
-48 concat 47 35 46
-49 sort bitvec 10
-50 concat 49 35 48
-51 sort bitvec 11
-52 concat 51 35 50
-53 sort bitvec 12
-54 concat 53 35 52
-55 sort bitvec 13
-56 concat 55 35 54
-57 sort bitvec 14
-58 concat 57 35 56
-59 sort bitvec 15
-60 concat 59 35 58
-61 sort bitvec 16
-62 concat 61 35 60
-63 sort bitvec 17
-64 concat 63 35 62
-65 sort bitvec 18
-66 concat 65 35 64
-67 sort bitvec 19
-68 concat 67 35 66
-69 sort bitvec 20
-70 concat 69 35 68
-71 sort bitvec 21
-72 concat 71 35 70
-73 sort bitvec 22
-74 concat 73 35 72
-75 sort bitvec 23
-76 concat 75 35 74
-77 sort bitvec 24
-78 concat 77 35 76
-79 sort bitvec 25
-80 concat 79 35 78
-81 sort bitvec 26
-82 concat 81 35 80
-83 sort bitvec 27
-84 concat 83 35 82
-85 sort bitvec 28
-86 concat 85 35 84
-87 sort bitvec 29
-88 concat 87 35 86
-89 sort bitvec 30
-90 concat 89 35 88
-91 sort bitvec 31
-92 concat 91 35 90
-93 concat 7 35 92
-94 read 7 14 32
-95 not 7 93
-96 and 7 94 95
-97 and 7 34 93
-98 or 7 97 96
-99 write 13 14 32 98
-100 redor 4 93
-101 ite 13 100 99 14
-102 next 13 14 101 mem ; core.sv:283.21-283.24
-; end of yosys output
diff --git a/tools/btor2/core/std_mem_d3.btor b/tools/btor2/core/std_mem_d3.btor
deleted file mode 100644
index 2d35a0c1a8..0000000000
--- a/tools/btor2/core/std_mem_d3.btor
+++ /dev/null
@@ -1,112 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module comb_mem_d3.
-1 sort bitvec 4
-2 input 1 addr0 ; core.sv:317.41-317.46
-3 input 1 addr1 ; core.sv:318.41-318.46
-4 input 1 addr2 ; core.sv:319.41-319.46
-5 sort bitvec 1
-6 input 5 clk ; core.sv:322.41-322.44
-7 input 5 reset ; core.sv:323.41-323.46
-8 sort bitvec 32
-9 input 8 write_data ; core.sv:320.41-320.51
-10 input 5 write_en ; core.sv:321.41-321.49
-11 state 5
-12 output 11 done ; core.sv:325.41-325.45
-13 sort bitvec 12
-14 sort array 13 8
-15 state 14 mem
-16 sort bitvec 9
-17 uext 16 2 5
-18 sort bitvec 5
-19 const 18 10000
-20 uext 16 19 4
-21 mul 16 17 20
-22 sort bitvec 23
-23 const 22 00000000000000000000000
-24 concat 8 23 21
-25 uext 8 3 28
-26 add 8 24 25
-27 uext 8 19 27
-28 mul 8 26 27
-29 slice 13 28 11 0
-30 uext 13 4 8
-31 add 13 29 30
-32 read 8 15 31
-33 output 32 read_data ; core.sv:324.41-324.50
-34 const 5 0
-35 const 5 1
-36 ite 5 10 35 34
-37 ite 5 7 34 36
-38 next 5 11 37
-39 input 13
-40 not 5 7
-41 and 5 40 10
-42 ite 13 41 31 39
-43 input 8
-44 ite 8 41 9 43
-45 ite 5 41 35 34
-46 sort bitvec 2
-47 concat 46 45 45
-48 sort bitvec 3
-49 concat 48 45 47
-50 concat 1 45 49
-51 concat 18 45 50
-52 sort bitvec 6
-53 concat 52 45 51
-54 sort bitvec 7
-55 concat 54 45 53
-56 sort bitvec 8
-57 concat 56 45 55
-58 concat 16 45 57
-59 sort bitvec 10
-60 concat 59 45 58
-61 sort bitvec 11
-62 concat 61 45 60
-63 concat 13 45 62
-64 sort bitvec 13
-65 concat 64 45 63
-66 sort bitvec 14
-67 concat 66 45 65
-68 sort bitvec 15
-69 concat 68 45 67
-70 sort bitvec 16
-71 concat 70 45 69
-72 sort bitvec 17
-73 concat 72 45 71
-74 sort bitvec 18
-75 concat 74 45 73
-76 sort bitvec 19
-77 concat 76 45 75
-78 sort bitvec 20
-79 concat 78 45 77
-80 sort bitvec 21
-81 concat 80 45 79
-82 sort bitvec 22
-83 concat 82 45 81
-84 concat 22 45 83
-85 sort bitvec 24
-86 concat 85 45 84
-87 sort bitvec 25
-88 concat 87 45 86
-89 sort bitvec 26
-90 concat 89 45 88
-91 sort bitvec 27
-92 concat 91 45 90
-93 sort bitvec 28
-94 concat 93 45 92
-95 sort bitvec 29
-96 concat 95 45 94
-97 sort bitvec 30
-98 concat 97 45 96
-99 sort bitvec 31
-100 concat 99 45 98
-101 concat 8 45 100
-102 read 8 15 42
-103 not 8 101
-104 and 8 102 103
-105 and 8 44 101
-106 or 8 105 104
-107 write 14 15 42 106
-108 redor 5 101
-109 ite 14 108 107 15
-110 next 14 15 109 mem ; core.sv:329.21-329.24
-; end of yosys output
diff --git a/tools/btor2/core/std_mem_d4.btor b/tools/btor2/core/std_mem_d4.btor
deleted file mode 100644
index 16d6bf7d31..0000000000
--- a/tools/btor2/core/std_mem_d4.btor
+++ /dev/null
@@ -1,117 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module comb_mem_d4.
-1 sort bitvec 4
-2 input 1 addr0 ; core.sv:367.41-367.46
-3 input 1 addr1 ; core.sv:368.41-368.46
-4 input 1 addr2 ; core.sv:369.41-369.46
-5 input 1 addr3 ; core.sv:370.41-370.46
-6 sort bitvec 1
-7 input 6 clk ; core.sv:373.41-373.44
-8 input 6 reset ; core.sv:374.41-374.46
-9 sort bitvec 32
-10 input 9 write_data ; core.sv:371.41-371.51
-11 input 6 write_en ; core.sv:372.41-372.49
-12 state 6
-13 output 12 done ; core.sv:376.41-376.45
-14 sort bitvec 16
-15 sort array 14 9
-16 state 15 mem
-17 sort bitvec 9
-18 uext 17 2 5
-19 sort bitvec 5
-20 const 19 10000
-21 uext 17 20 4
-22 mul 17 18 21
-23 sort bitvec 23
-24 const 23 00000000000000000000000
-25 concat 9 24 22
-26 uext 9 3 28
-27 add 9 25 26
-28 uext 9 20 27
-29 mul 9 27 28
-30 uext 9 4 28
-31 add 9 29 30
-32 uext 9 20 27
-33 mul 9 31 32
-34 slice 14 33 15 0
-35 uext 14 5 12
-36 add 14 34 35
-37 read 9 16 36
-38 output 37 read_data ; core.sv:375.41-375.50
-39 const 6 0
-40 const 6 1
-41 ite 6 11 40 39
-42 ite 6 8 39 41
-43 next 6 12 42
-44 input 14
-45 not 6 8
-46 and 6 45 11
-47 ite 14 46 36 44
-48 input 9
-49 ite 9 46 10 48
-50 ite 6 46 40 39
-51 sort bitvec 2
-52 concat 51 50 50
-53 sort bitvec 3
-54 concat 53 50 52
-55 concat 1 50 54
-56 concat 19 50 55
-57 sort bitvec 6
-58 concat 57 50 56
-59 sort bitvec 7
-60 concat 59 50 58
-61 sort bitvec 8
-62 concat 61 50 60
-63 concat 17 50 62
-64 sort bitvec 10
-65 concat 64 50 63
-66 sort bitvec 11
-67 concat 66 50 65
-68 sort bitvec 12
-69 concat 68 50 67
-70 sort bitvec 13
-71 concat 70 50 69
-72 sort bitvec 14
-73 concat 72 50 71
-74 sort bitvec 15
-75 concat 74 50 73
-76 concat 14 50 75
-77 sort bitvec 17
-78 concat 77 50 76
-79 sort bitvec 18
-80 concat 79 50 78
-81 sort bitvec 19
-82 concat 81 50 80
-83 sort bitvec 20
-84 concat 83 50 82
-85 sort bitvec 21
-86 concat 85 50 84
-87 sort bitvec 22
-88 concat 87 50 86
-89 concat 23 50 88
-90 sort bitvec 24
-91 concat 90 50 89
-92 sort bitvec 25
-93 concat 92 50 91
-94 sort bitvec 26
-95 concat 94 50 93
-96 sort bitvec 27
-97 concat 96 50 95
-98 sort bitvec 28
-99 concat 98 50 97
-100 sort bitvec 29
-101 concat 100 50 99
-102 sort bitvec 30
-103 concat 102 50 101
-104 sort bitvec 31
-105 concat 104 50 103
-106 concat 9 50 105
-107 read 9 16 47
-108 not 9 106
-109 and 9 107 108
-110 and 9 49 106
-111 or 9 110 109
-112 write 15 16 47 111
-113 redor 6 106
-114 ite 15 113 112 16
-115 next 15 16 114 mem ; core.sv:380.21-380.24
-; end of yosys output
diff --git a/tools/btor2/core/std_mux.btor b/tools/btor2/core/std_mux.btor
deleted file mode 100644
index 995a6f3946..0000000000
--- a/tools/btor2/core/std_mux.btor
+++ /dev/null
@@ -1,9 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_mux.
-1 sort bitvec 1
-2 input 1 cond ; core.sv:219.35-219.39
-3 sort bitvec 32
-4 input 3 fal ; core.sv:221.35-221.38
-5 input 3 tru ; core.sv:220.35-220.38
-6 ite 3 2 5 4
-7 output 6 out ; core.sv:222.35-222.38
-; end of yosys output
diff --git a/tools/btor2/core/std_neq.btor b/tools/btor2/core/std_neq.btor
deleted file mode 100644
index a69d287254..0000000000
--- a/tools/btor2/core/std_neq.btor
+++ /dev/null
@@ -1,8 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_neq.
-1 sort bitvec 32
-2 input 1 left ; core.sv:167.34-167.38
-3 input 1 right ; core.sv:168.34-168.39
-4 sort bitvec 1
-5 neq 4 2 3
-6 output 5 out ; core.sv:169.18-169.21
-; end of yosys output
diff --git a/tools/btor2/core/std_not.btor b/tools/btor2/core/std_not.btor
deleted file mode 100644
index a27bcf1ba0..0000000000
--- a/tools/btor2/core/std_not.btor
+++ /dev/null
@@ -1,6 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_not.
-1 sort bitvec 32
-2 input 1 in ; core.sv:88.35-88.37
-3 not 1 2
-4 output 3 out ; core.sv:89.35-89.38
-; end of yosys output
diff --git a/tools/btor2/core/std_or.btor b/tools/btor2/core/std_or.btor
deleted file mode 100644
index 1b8787e357..0000000000
--- a/tools/btor2/core/std_or.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_or.
-1 sort bitvec 32
-2 input 1 left ; core.sv:107.35-107.39
-3 input 1 right ; core.sv:108.35-108.40
-4 or 1 2 3
-5 output 4 out ; core.sv:109.35-109.38
-; end of yosys output
diff --git a/tools/btor2/core/std_pad.btor b/tools/btor2/core/std_pad.btor
deleted file mode 100644
index f4a988eeb9..0000000000
--- a/tools/btor2/core/std_pad.btor
+++ /dev/null
@@ -1,5 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_pad.
-1 sort bitvec 32
-2 input 1 in ; core.sv:38.39-38.41
-3 output 2 out ; core.sv:39.39-39.42
-; end of yosys output
diff --git a/tools/btor2/core/std_reg.btor b/tools/btor2/core/std_reg.btor
deleted file mode 100644
index 98a6b9ebed..0000000000
--- a/tools/btor2/core/std_reg.btor
+++ /dev/null
@@ -1,21 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_reg.
-1 sort bitvec 1
-2 input 1 clk ; temp.sv:7.31-7.34
-3 sort bitvec 32
-4 input 3 in ; temp.sv:5.31-5.33
-5 input 1 reset ; temp.sv:8.31-8.36
-6 input 1 write_en ; temp.sv:6.31-6.39
-7 state 1
-8 output 7 done ; temp.sv:11.31-11.35
-9 state 3
-10 output 9 out ; temp.sv:10.31-10.34
-11 const 1 0
-12 const 1 1
-13 ite 1 6 12 11
-14 ite 1 5 11 13
-15 next 1 7 14
-16 ite 3 6 4 9 ; input if write_en else existing 9 state val
-17 const 3 00000000000000000000000000000000
-18 ite 3 5 17 16 ; 32 bit 0 if reset else 16 
-19 next 3 9 18
-; end of yosys output
diff --git a/tools/btor2/core/std_rsh.btor b/tools/btor2/core/std_rsh.btor
deleted file mode 100644
index fd7ac517cb..0000000000
--- a/tools/btor2/core/std_rsh.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_rsh.
-1 sort bitvec 32
-2 input 1 left ; core.sv:207.35-207.39
-3 input 1 right ; core.sv:208.35-208.40
-4 srl 1 2 3
-5 output 4 out ; core.sv:209.35-209.38
-; end of yosys output
diff --git a/tools/btor2/core/std_slice.btor b/tools/btor2/core/std_slice.btor
deleted file mode 100644
index 4fc2197342..0000000000
--- a/tools/btor2/core/std_slice.btor
+++ /dev/null
@@ -1,5 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_slice.
-1 sort bitvec 32
-2 input 1 in ; core.sv:15.39-15.41
-3 output 2 out ; core.sv:16.39-16.42
-; end of yosys output
diff --git a/tools/btor2/core/std_sub.btor b/tools/btor2/core/std_sub.btor
deleted file mode 100644
index e3c8c5cf8e..0000000000
--- a/tools/btor2/core/std_sub.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_sub.
-1 sort bitvec 32
-2 input 1 left ; core.sv:127.35-127.39
-3 input 1 right ; core.sv:128.35-128.40
-4 sub 1 2 3
-5 output 4 out ; core.sv:129.35-129.38
-; end of yosys output
diff --git a/tools/btor2/core/std_xor.btor b/tools/btor2/core/std_xor.btor
deleted file mode 100644
index a6d9e663b4..0000000000
--- a/tools/btor2/core/std_xor.btor
+++ /dev/null
@@ -1,7 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_xor.
-1 sort bitvec 32
-2 input 1 left ; core.sv:117.35-117.39
-3 input 1 right ; core.sv:118.35-118.40
-4 xor 1 2 3
-5 output 4 out ; core.sv:119.35-119.38
-; end of yosys output
diff --git a/tools/btor2/sv2btor.py b/tools/btor2/sv2btor.py
deleted file mode 100644
index 8f0636c86a..0000000000
--- a/tools/btor2/sv2btor.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-Generates a .btor file for each module in each .sv file in the current directory
-"""
-import os
-import json
-import subprocess
-import sys
-import anytree
-import tempfile
-import verible_verilog_syntax
-
-
-# Adapted from verible examples:
-# https://github.com/chipsalliance/verible/blob/e76eb275b8e6739e9c9edc9e35032b193e0ce187/verilog/tools/syntax/export_json_examples/print_modules.py
-def process_file_data(path: str, data: verible_verilog_syntax.SyntaxData):
-    """Print information about modules found in SystemVerilog file.
-
-    This function uses verible_verilog_syntax.Node methods to find module
-    declarations and specific tokens containing following information:
-
-    * module name
-    * module port names
-    * module parameter names
-    * module imports
-    * module header code
-
-    Args:
-      path: Path to source file (used only for informational purposes)
-      data: Parsing results returned by one of VeribleVerilogSyntax' parse_*
-            methods.
-    """
-    if not data.tree:
-        return
-
-    modules_info = []
-
-    # Collect information about each module declaration in the file
-    for module in data.tree.iter_find_all({"tag": "kModuleDeclaration"}):
-        module_info = {
-            "header_text": "",
-            "name": "",
-            "ports": [],
-            "parameters": [],
-            "imports": [],
-        }
-
-        # Find module header
-        header = module.find({"tag": "kModuleHeader"})
-        if not header:
-            continue
-        module_info["header_text"] = header.text
-
-        # Find module name
-        name = header.find(
-            {"tag": ["SymbolIdentifier", "EscapedIdentifier"]},
-            iter_=anytree.PreOrderIter,
-        )
-        if not name:
-            continue
-        module_info["name"] = name.text
-
-        # Get the list of ports
-        for port in header.iter_find_all({"tag": ["kPortDeclaration", "kPort"]}):
-            port_id = port.find({"tag": ["SymbolIdentifier", "EscapedIdentifier"]})
-            module_info["ports"].append(port_id.text)
-
-        # Get the list of parameters
-        for param in header.iter_find_all({"tag": ["kParamDeclaration"]}):
-            param_id = param.find({"tag": ["SymbolIdentifier", "EscapedIdentifier"]})
-            module_info["parameters"].append(param_id.text)
-
-        # Get the list of imports
-        for pkg in module.iter_find_all({"tag": ["kPackageImportItem"]}):
-            module_info["imports"].append(pkg.text)
-
-        modules_info.append(module_info)
-
-    return modules_info
-
-
-def gen_btor(yosys_executable, sv_filename, modules_info, out_dir="."):
-    """
-    Generates a .btor file for each module in the given .sv file
-    """
-    # create a temporary file (.ys) for the synthesis script
-    _, synthesis_script_path = tempfile.mkstemp(suffix=".ys")
-
-    # modify the synthesis script with a different prep -top for each module
-    for module_info in modules_info:
-        with open(synthesis_script_path, "w") as f:
-            f.write(f"read -sv {sv_filename}\n")
-            f.write(f"prep -top {module_info['name']}\n")
-            f.write(
-                f"write_btor -s {os.path.join(out_dir, module_info['name'])}.btor\n"
-            )
-        f.close()
-
-        # print contents of synthesis script
-        # with open(synthesis_script_path, "r") as f:
-        #     print(f.read())
-
-        # run yosys
-        conversion_process = subprocess.Popen(
-            [yosys_executable, synthesis_script_path],
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-        )
-        btor_out, btor_err = conversion_process.communicate()
-        if btor_err:
-            print(btor_err.decode("utf-8"))
-            return 1
-
-
-def main():
-    if len(sys.argv) < 5:
-        args_desc = [
-            "PATH_TO_YOSYS_EXECUTABLE",
-            "PATH_TO_VERIBLE_VERILOG_SYNTAX",
-            "OUTPUT_DIR",
-            "VERILOG_FILE [VERILOG_FILE [...]]",
-        ]
-        print(f"Usage: {sys.argv[0]} {'  '.join(args_desc)}")
-        return 1
-
-    yosys_path = sys.argv[1]
-    parser_path = sys.argv[2]
-    output_dir = sys.argv[3]
-    file_paths = sys.argv[4:]
-
-    # validate
-    if not os.path.exists(yosys_path):
-        print(f"Error: {yosys_path} does not exist")
-        return 1
-    if not os.path.exists(parser_path):
-        print(f"Error: {parser_path} does not exist")
-        return 1
-    if not os.path.exists(output_dir):
-        os.makedirs(output_dir)
-
-    # if the output directory is not empty, warn the user that it will be overwritten and confirm
-    if os.listdir(output_dir):
-        print(f"Warning: {output_dir} is not empty, and will be overwritten")
-        print("Continue? [y/N]")
-        if input().lower() != "y":
-            return 1
-
-        # clear the output directory
-        for f in os.listdir(output_dir):
-            os.remove(os.path.join(output_dir, f))
-
-    # parse the files
-    parser = verible_verilog_syntax.VeribleVerilogSyntax(executable=parser_path)
-    data = parser.parse_files(file_paths)
-
-    for file_path, file_data in data.items():
-        modules_info = process_file_data(file_path, file_data)
-        gen_btor(
-            yosys_path,
-            file_path,
-            modules_info,
-            output_dir,
-        )
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/tools/btor2/sync/std_sync_reg.btor b/tools/btor2/sync/std_sync_reg.btor
deleted file mode 100644
index c66be0f4fa..0000000000
--- a/tools/btor2/sync/std_sync_reg.btor
+++ /dev/null
@@ -1,108 +0,0 @@
-; BTOR description generated by Yosys 0.33+65 (git sha1 90124dce5, clang 15.0.0 -fPIC -Os) for module std_sync_reg.
-1 sort bitvec 1
-2 input 1 clk ; sync.sv:22.31-22.34
-3 sort bitvec 32
-4 input 3 in_0 ; sync.sv:16.31-16.35
-5 input 3 in_1 ; sync.sv:17.31-17.35
-6 input 1 read_en_0 ; sync.sv:18.31-18.40
-7 input 1 read_en_1 ; sync.sv:19.31-19.40
-8 input 1 reset ; sync.sv:23.31-23.36
-9 input 1 write_en_0 ; sync.sv:20.31-20.41
-10 input 1 write_en_1 ; sync.sv:21.31-21.41
-11 state 3
-12 output 11 out_0 ; sync.sv:25.31-25.36
-13 state 3
-14 output 13 out_1 ; sync.sv:26.31-26.36
-15 state 3
-16 output 15 peek ; sync.sv:31.31-31.35
-17 state 1
-18 output 17 read_done_0 ; sync.sv:29.31-29.42
-19 state 1
-20 output 19 read_done_1 ; sync.sv:30.31-30.42
-21 state 1
-22 output 21 write_done_0 ; sync.sv:27.31-27.43
-23 state 1
-24 output 23 write_done_1 ; sync.sv:28.31-28.43
-25 state 1 is_full
-26 xor 1 6 7
-27 and 1 25 26
-28 and 1 27 6
-29 and 1 6 7
-30 and 1 25 29
-31 state 1 arbiter_r
-32 not 1 31
-33 and 1 30 32
-34 or 1 28 33
-35 uext 1 34 0 READ_0 ; sync.sv:40.79-40.85
-36 and 1 27 7
-37 and 1 30 31
-38 or 1 36 37
-39 uext 1 38 0 READ_1 ; sync.sv:40.87-40.93
-40 uext 1 30 0 READ_MULT ; sync.sv:40.23-40.32
-41 uext 1 27 0 READ_ONE_HOT ; sync.sv:40.9-40.21
-42 not 1 25
-43 xor 1 9 10
-44 and 1 42 43
-45 and 1 44 9
-46 and 1 9 10
-47 and 1 42 46
-48 state 1 arbiter_w
-49 not 1 48
-50 and 1 47 49
-51 or 1 45 50
-52 uext 1 51 0 WRITE_0 ; sync.sv:40.61-40.68
-53 and 1 44 10
-54 and 1 47 48
-55 or 1 53 54
-56 uext 1 55 0 WRITE_1 ; sync.sv:40.70-40.77
-57 uext 1 47 0 WRITE_MULT ; sync.sv:40.49-40.59
-58 uext 1 44 0 WRITE_ONE_HOT ; sync.sv:40.34-40.47
-59 state 3 state
-60 input 3
-61 ite 3 34 59 60
-62 const 3 00000000000000000000000000000000
-63 ite 3 8 62 61
-64 next 3 11 63
-65 input 3
-66 ite 3 38 59 65
-67 ite 3 8 62 66
-68 next 3 13 67
-69 ite 3 55 5 15
-70 ite 3 51 4 69
-71 ite 3 8 62 70
-72 next 3 15 71
-73 const 1 0
-74 const 1 1
-75 ite 1 34 74 73
-76 ite 1 8 73 75
-77 next 1 17 76
-78 ite 1 38 74 73
-79 ite 1 8 73 78
-80 next 1 19 79
-81 ite 1 51 74 73
-82 ite 1 8 73 81
-83 next 1 21 82
-84 ite 1 55 74 73
-85 ite 1 8 73 84
-86 next 1 23 85
-87 or 1 27 30
-88 ite 1 87 73 25
-89 or 1 44 47
-90 ite 1 89 74 88
-91 ite 1 8 73 90
-92 next 1 25 91
-93 ite 1 37 73 31
-94 ite 1 33 74 93
-95 ite 1 8 73 94
-96 next 1 31 95
-97 ite 1 54 73 48
-98 ite 1 50 74 97
-99 ite 1 8 73 98
-100 next 1 48 99
-101 input 3
-102 ite 3 87 101 59
-103 ite 3 55 5 102
-104 ite 3 51 4 103
-105 ite 3 8 62 104
-106 next 3 59 105
-; end of yosys output
diff --git a/tools/btor2/verible_verilog_syntax.py b/tools/btor2/verible_verilog_syntax.py
deleted file mode 100644
index 06332297b8..0000000000
--- a/tools/btor2/verible_verilog_syntax.py
+++ /dev/null
@@ -1,580 +0,0 @@
-# Copyright 2017-2020 The Verible Authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Wrapper for ``verible-verilog-syntax --export_json``"""
-
-import collections
-import json
-import re
-import subprocess
-from typing import Any, Callable, Dict, Iterable, List, Optional, Union
-
-import anytree
-import dataclasses
-
-_CSI_SEQUENCE = re.compile("\033\\[.*?m")
-
-
-def _colorize(formats: List[str], strings: List[str]) -> str:
-    result = ""
-    fi = 0
-    for s in strings:
-        result += f"\033[{formats[fi]}m{s}\033[0m"
-        fi = (fi + 1) % len(formats)
-    return result
-
-
-# Type aliases
-
-CallableFilter = Callable[["Node"], bool]
-KeyValueFilter = Dict[str, Union[str, List[str]]]
-TreeIterator = Union["_TreeIteratorBase", anytree.iterators.AbstractIter]
-
-
-# Custom tree iterators with an option for reverse children iteration
-
-
-class _TreeIteratorBase:
-    def __init__(
-        self,
-        tree: "Node",
-        filter_: Optional[CallableFilter] = None,
-        reverse_children: bool = False,
-    ):
-        self.tree = tree
-        self.reverse_children = reverse_children
-        self.filter_ = filter_ if filter_ else lambda n: True
-
-    def __iter__(self) -> Iterable["Node"]:
-        yield from self._iter_tree(self.tree)
-
-    def _iter_children(self, tree: Optional["Node"]) -> Iterable["Node"]:
-        if not tree or not hasattr(tree, "children"):
-            return []
-        return tree.children if not self.reverse_children else reversed(tree.children)
-
-    def _iter_tree(self, tree: Optional["Node"]) -> Iterable["Node"]:
-        raise NotImplementedError("Subclass must implement '_iter_tree' method")
-
-
-class PreOrderTreeIterator(_TreeIteratorBase):
-    def _iter_tree(self, tree: Optional["Node"]) -> Iterable["Node"]:
-        if self.filter_(tree):
-            yield tree
-        for child in self._iter_children(tree):
-            yield from self._iter_tree(child)
-
-
-class PostOrderTreeIterator(_TreeIteratorBase):
-    def _iter_tree(self, tree: Optional["Node"]) -> Iterable["Node"]:
-        for child in self._iter_children(tree):
-            yield from self._iter_tree(child)
-        if self.filter_(tree):
-            yield tree
-
-
-class LevelOrderTreeIterator(_TreeIteratorBase):
-    def _iter_tree(self, tree: Optional["Node"]) -> Iterable["Node"]:
-        queue = collections.deque([tree])
-        while len(queue) > 0:
-            n = queue.popleft()
-            if self.filter_(n):
-                yield n
-            queue.extend(self._iter_children(n))
-
-
-class Node(anytree.NodeMixin):
-    """Base VeribleVerilogSyntax syntax tree node.
-
-    Attributes:
-      parent (Optional[Node]): Parent node.
-    """
-
-    def __init__(self, parent: Optional["Node"] = None):
-        self.parent = parent
-
-    @property
-    def syntax_data(self) -> Optional["SyntaxData"]:
-        """Parent SyntaxData"""
-        return self.parent.syntax_data if self.parent else None
-
-    @property
-    def start(self) -> Optional[int]:
-        """Byte offset of node's first character in source text"""
-        raise NotImplementedError("Subclass must implement 'start' property")
-
-    @property
-    def end(self) -> Optional[int]:
-        """Byte offset of a character just past the node in source text."""
-        raise NotImplementedError("Subclass must implement 'end' property")
-
-    @property
-    def text(self) -> str:
-        """Source code fragment spanning all tokens in a node."""
-        start = self.start
-        end = self.end
-        sd = self.syntax_data
-        if (
-            (start is not None)
-            and (end is not None)
-            and sd
-            and sd.source_code
-            and end <= len(sd.source_code)
-        ):
-            return sd.source_code[start:end].decode("utf-8")
-        return ""
-
-    def __repr__(self) -> str:
-        return _CSI_SEQUENCE.sub("", self.to_formatted_string())
-
-    def to_formatted_string(self) -> str:
-        """Print node representation formatted for printing in terminal."""
-        return super().__repr__()
-
-
-class BranchNode(Node):
-    """Syntax tree branch node
-
-    Attributes:
-      tag (str): Node tag.
-      children (Optional[Node]): Child nodes.
-    """
-
-    def __init__(
-        self,
-        tag: str,
-        parent: Optional[Node] = None,
-        children: Optional[List[Node]] = None,
-    ):
-        super().__init__(parent)
-        self.tag = tag
-        self.children = children if children is not None else []
-
-    @property
-    def start(self) -> Optional[int]:
-        first_token = self.find(
-            lambda n: isinstance(n, TokenNode), iter_=PostOrderTreeIterator
-        )
-        return first_token.start if first_token else None
-
-    @property
-    def end(self) -> Optional[int]:
-        last_token = self.find(
-            lambda n: isinstance(n, TokenNode),
-            iter_=PostOrderTreeIterator,
-            reverse_children=True,
-        )
-        return last_token.end if last_token else None
-
-    def iter_find_all(
-        self,
-        filter_: Union[CallableFilter, KeyValueFilter, None],
-        max_count: int = 0,
-        iter_: TreeIterator = LevelOrderTreeIterator,
-        **kwargs,
-    ) -> Iterable[Node]:
-        """Iterate all nodes matching specified filter.
-
-        Args:
-          filter_: Describes what to search for. Might be:
-            * Callable taking Node as an argument and returning True for accepted
-              nodes.
-            * Dict mapping Node attribute names to searched value or list of
-              searched values.
-          max_count: Stop searching after finding that many matching nodes.
-          iter_: Tree iterator. Decides in what order nodes are visited.
-
-        Yields:
-          Nodes matching specified filter.
-        """
-
-        def as_list(v):
-            return v if isinstance(v, list) else [v]
-
-        if filter_ and not callable(filter_):
-            filters = filter_
-
-            def f(node):
-                for attr, value in filters.items():
-                    if not hasattr(node, attr):
-                        return False
-                    if getattr(node, attr) not in as_list(value):
-                        return False
-                return True
-
-            filter_ = f
-
-        for node in iter_(self, filter_, **kwargs):
-            yield node
-            max_count -= 1
-            if max_count == 0:
-                break
-
-    def find(
-        self,
-        filter_: Union[CallableFilter, KeyValueFilter, None],
-        iter_: TreeIterator = LevelOrderTreeIterator,
-        **kwargs,
-    ) -> Optional[Node]:
-        """Find node matching specified filter.
-
-        Args:
-          filter_: Describes what to search for. Might be:
-            * Callable taking Node as an argument and returning True for accepted
-              node.
-            * Dict mapping Node attribute names to searched value or list of
-              searched values.
-          iter_: Tree iterator. Decides in what order nodes are visited.
-
-        Returns:
-          First Node matching filter.
-        """
-        return next(
-            self.iter_find_all(filter_, max_count=1, iter_=iter_, **kwargs), None
-        )
-
-    def find_all(
-        self,
-        filter_: Union[CallableFilter, KeyValueFilter, None],
-        max_count: int = 0,
-        iter_: TreeIterator = LevelOrderTreeIterator,
-        **kwargs,
-    ) -> List[Node]:
-        """Find all nodes matching specified filter.
-
-        Args:
-          filter_: Describes what to search for. Might be:
-            * Callable taking Node as an argument and returning True for accepted
-              nodes.
-            * Dict mapping Node attribute names to searched value or list of
-              searched values.
-          max_count: Stop searching after finding that many matching nodes.
-          iter_: Tree iterator. Decides in what order nodes are visited.
-
-        Returns:
-          List of nodes matching specified filter.
-        """
-        return list(
-            self.iter_find_all(filter_, max_count=max_count, iter_=iter_, **kwargs)
-        )
-
-    def to_formatted_string(self) -> str:
-        tag = self.tag if self.tag == repr(self.tag)[1:-1] else repr(self.tag)
-        return _colorize(["37", "1;97"], ["[", tag, "]"])
-
-
-class RootNode(BranchNode):
-    """Syntax tree root node."""
-
-    def __init__(
-        self,
-        tag: str,
-        syntax_data: Optional["SyntaxData"] = None,
-        children: Optional[List[Node]] = None,
-    ):
-        super().__init__(tag, None, children)
-        self._syntax_data = syntax_data
-
-    @property
-    def syntax_data(self) -> Optional["SyntaxData"]:
-        return self._syntax_data
-
-
-class LeafNode(Node):
-    """Syntax tree leaf node.
-
-    This specific class is used for null nodes.
-    """
-
-    @property
-    def start(self) -> None:
-        """Byte offset of token's first character in source text"""
-        return None
-
-    @property
-    def end(self) -> None:
-        """Byte offset of a character just past the token in source text."""
-        return None
-
-    def to_formatted_string(self) -> str:
-        return _colorize(["90"], ["null"])
-
-
-class TokenNode(LeafNode):
-    """Tree node with token data
-
-    Represents single token in a syntax tree.
-
-    Attributes:
-      tag (str): Token tag.
-    """
-
-    def __init__(self, tag: str, start: int, end: int, parent: Optional[Node] = None):
-        super().__init__(parent)
-        self.tag = tag
-        self._start = start
-        self._end = end
-
-    @property
-    def start(self) -> int:
-        return self._start
-
-    @property
-    def end(self) -> int:
-        return self._end
-
-    def to_formatted_string(self) -> str:
-        tag = self.tag if self.tag == repr(self.tag)[1:-1] else repr(self.tag)
-        parts = [
-            _colorize(["37", "1;97"], ["[", tag, "]"]),
-            _colorize(["33", "93"], ["@(", self.start, "-", self.end, ")"]),
-        ]
-        text = self.text
-        if self.tag != text:
-            parts.append(_colorize(["32", "92"], ["'", repr(text)[1:-1], "'"]))
-        return " ".join(parts)
-
-
-class Token:
-    """Token data
-
-    Represents single token in tokens and rawtokens lists.
-
-    Attributes:
-      tag (str): Token tag.
-      start (int): Byte offset of token's first character in source text.
-      end (int): Byte offset of a character just past the token in source text.
-      syntax_data (Optional["SyntaxData"]): Parent SyntaxData.
-    """
-
-    def __init__(
-        self, tag: str, start: int, end: int, syntax_data: Optional["SyntaxData"] = None
-    ):
-        self.tag = tag
-        self.start = start
-        self.end = end
-        self.syntax_data = syntax_data
-
-    @property
-    def text(self) -> str:
-        """Token text in source code."""
-        sd = self.syntax_data
-        if sd and sd.source_code and self.end <= len(sd.source_code):
-            return sd.source_code[self.start : self.end].decode("utf-8")
-        return ""
-
-    def __repr__(self) -> str:
-        return _CSI_SEQUENCE.sub("", self.to_formatted_string())
-
-    def to_formatted_string(self) -> str:
-        tag = self.tag if self.tag == repr(self.tag)[1:-1] else repr(self.tag)
-        parts = [
-            _colorize(["37", "1;97"], ["[", tag, "]"]),
-            _colorize(["33", "93"], ["@(", self.start, "-", self.end, ")"]),
-            _colorize(["32", "92"], ["'", repr(self.text)[1:-1], "'"]),
-        ]
-        return " ".join(parts)
-
-
-@dataclasses.dataclass
-class Error:
-    line: int
-    column: int
-    phase: str
-    message: str = ""
-
-
-@dataclasses.dataclass
-class SyntaxData:
-    source_code: Optional[str] = None
-    tree: Optional[RootNode] = None
-    tokens: Optional[List[Token]] = None
-    rawtokens: Optional[List[Token]] = None
-    errors: Optional[List[Error]] = None
-
-
-class VeribleVerilogSyntax:
-    """``verible-verilog-syntax`` wrapper.
-
-    This class provides methods for running ``verible-verilog-syntax`` and
-    transforming its output into Python data structures.
-
-    Args:
-      executable: path to ``verible-verilog-syntax`` binary.
-    """
-
-    def __init__(self, executable: str = "verible-verilog-syntax"):
-        self.executable = executable
-
-    @staticmethod
-    def _transform_tree(tree, data: SyntaxData, skip_null: bool) -> RootNode:
-        def transform(tree):
-            if tree is None:
-                return None
-            if "children" in tree:
-                children = [
-                    transform(child) or LeafNode()
-                    for child in tree["children"]
-                    if not (skip_null and child is None)
-                ]
-                tag = tree["tag"]
-                return BranchNode(tag, children=children)
-            tag = tree["tag"]
-            start = tree["start"]
-            end = tree["end"]
-            return TokenNode(tag, start, end)
-
-        if "children" not in tree:
-            return None
-
-        children = [
-            transform(child) or LeafNode()
-            for child in tree["children"]
-            if not (skip_null and child is None)
-        ]
-        tag = tree["tag"]
-        return RootNode(tag, syntax_data=data, children=children)
-
-    @staticmethod
-    def _transform_tokens(tokens, data: SyntaxData) -> List[Token]:
-        return [Token(t["tag"], t["start"], t["end"], data) for t in tokens]
-
-    @staticmethod
-    def _transform_errors(tokens) -> List[Error]:
-        return [
-            Error(t["line"], t["column"], t["phase"], t.get("message", None))
-            for t in tokens
-        ]
-
-    def _parse(
-        self, paths: List[str], input_: str = None, options: Dict[str, Any] = None
-    ) -> Dict[str, SyntaxData]:
-        """Common implementation of parse_* methods"""
-        options = {
-            "gen_tree": True,
-            "skip_null": False,
-            "gen_tokens": False,
-            "gen_rawtokens": False,
-            **(options or {}),
-        }
-
-        args = ["-export_json"]
-        if options["gen_tree"]:
-            args.append("-printtree")
-        if options["gen_tokens"]:
-            args.append("-printtokens")
-        if options["gen_rawtokens"]:
-            args.append("-printrawtokens")
-
-        proc = subprocess.run(
-            [self.executable, *args, *paths],
-            stdout=subprocess.PIPE,
-            input=input_,
-            encoding="utf-8",
-            check=False,
-        )
-
-        json_data = json.loads(proc.stdout)
-        data = {}
-        for file_path, file_json in json_data.items():
-            file_data = SyntaxData()
-
-            if file_path == "-":
-                file_data.source_code = input_.encode("utf-8")
-            else:
-                with open(file_path, "rb") as f:
-                    file_data.source_code = f.read()
-
-            if "tree" in file_json:
-                file_data.tree = VeribleVerilogSyntax._transform_tree(
-                    file_json["tree"], file_data, options["skip_null"]
-                )
-
-            if "tokens" in file_json:
-                file_data.tokens = VeribleVerilogSyntax._transform_tokens(
-                    file_json["tokens"], file_data
-                )
-
-            if "rawtokens" in file_json:
-                file_data.rawtokens = VeribleVerilogSyntax._transform_tokens(
-                    file_json["rawtokens"], file_data
-                )
-
-            if "errors" in file_json:
-                file_data.errors = VeribleVerilogSyntax._transform_errors(
-                    file_json["errors"]
-                )
-
-            data[file_path] = file_data
-
-        return data
-
-    def parse_files(
-        self, paths: List[str], options: Dict[str, Any] = None
-    ) -> Dict[str, SyntaxData]:
-        """Parse multiple SystemVerilog files.
-
-        Args:
-          paths: list of paths to files to parse.
-          options: dict with parsing options.
-            Available options:
-              gen_tree (boolean): whether to generate syntax tree.
-              skip_null (boolean): null nodes won't be stored in a tree if True.
-              gen_tokens (boolean): whether to generate tokens list.
-              gen_rawtokens (boolean): whether to generate raw token list.
-            By default only ``gen_tree`` is True.
-
-        Returns:
-          A dict that maps file names to their parsing results in SyntaxData object.
-        """
-        return self._parse(paths, options=options)
-
-    def parse_file(
-        self, path: str, options: Dict[str, Any] = None
-    ) -> Optional[SyntaxData]:
-        """Parse single SystemVerilog file.
-
-        Args:
-          path: path to a file to parse.
-          options: dict with parsing options.
-            Available options:
-              gen_tree (boolean): whether to generate syntax tree.
-              skip_null (boolean): null nodes won't be stored in a tree if True.
-              gen_tokens (boolean): whether to generate tokens list.
-              gen_rawtokens (boolean): whether to generate raw token list.
-            By default only ``gen_tree`` is True.
-
-        Returns:
-          Parsing results in SyntaxData object.
-        """
-        return self._parse([path], options=options).get(path, None)
-
-    def parse_string(
-        self, string: str, options: Dict[str, Any] = None
-    ) -> Optional[SyntaxData]:
-        """Parse a string with SystemVerilog code.
-
-        Args:
-          string: SystemVerilog code to parse.
-          options: dict with parsing options.
-            Available options:
-              gen_tree (boolean): whether to generate syntax tree.
-              skip_null (boolean): null nodes won't be stored in a tree if True.
-              gen_tokens (boolean): whether to generate tokens list.
-              gen_rawtokens (boolean): whether to generate raw token list.
-            By default only ``gen_tree`` is True.
-
-        Returns:
-          Parsing results in SyntaxData object.
-        """
-        return self._parse(["-"], input_=string, options=options).get("-", None)
diff --git a/tools/calyx-pass-explorer/README.md b/tools/calyx-pass-explorer/README.md
index 4c7b7539da..c180bcf67e 100644
--- a/tools/calyx-pass-explorer/README.md
+++ b/tools/calyx-pass-explorer/README.md
@@ -1,4 +1,4 @@
-# calyx-pass
+# calyx-pass-explorer
 
 `calyx-pass-explorer` is a *pass transformation* explorer for calyx.
 You give it an input file and some options, and you can explore how [passes](https://crates.io/crates/calyx-opt) transform the file over time.
@@ -19,14 +19,17 @@ It's been immensely useful for me, and I hope it is for you too!
 
 Navigate to the `calyx-pass-explorer` directory (e.g., `cd tools/calyx-pass-explorer` from the repository root).
 Check the version with
+
 ```shell
 cargo run -- --version
 ```
 
 Then, run
+
 ```shell
 cargo install --path .
 ```
+
 from the current directory.
 
 ## Usage
diff --git a/tools/cider-data-converter/Cargo.toml b/tools/cider-data-converter/Cargo.toml
index a5755b4066..a690023d34 100644
--- a/tools/cider-data-converter/Cargo.toml
+++ b/tools/cider-data-converter/Cargo.toml
@@ -17,6 +17,7 @@ thiserror = "1.0.59"
 num-bigint = { version = "0.4.6" }
 num-rational = { version = "0.4.2" }
 num-traits = { version = "0.2.19" }
+nom = "7.1.3"
 
 [dev-dependencies]
 proptest = "1.0.0"
diff --git a/tools/cider-data-converter/src/converter.rs b/tools/cider-data-converter/src/converter.rs
index 945b00fd01..5250c18257 100644
--- a/tools/cider-data-converter/src/converter.rs
+++ b/tools/cider-data-converter/src/converter.rs
@@ -1,3 +1,5 @@
+use super::json_data::*;
+use interp::serialization::*;
 use itertools::Itertools;
 use num_bigint::{BigInt, BigUint, ToBigInt};
 use num_rational::BigRational;
@@ -5,9 +7,6 @@ use num_traits::{sign::Signed, Num, ToPrimitive};
 use serde_json::Number;
 use std::{collections::HashMap, iter::repeat, str::FromStr};
 
-use super::json_data::*;
-use interp::serialization::*;
-
 fn msb(width: u32) -> u8 {
     let rem = width % 8;
     1u8 << (if rem != 0 { rem - 1 } else { 7 }) // shift to the right by between 0 and 7
@@ -165,66 +164,75 @@ fn unroll_float(
     val: f64,
     format: &interp::serialization::FormatInfo,
     round_float: bool,
-) -> impl Iterator<Item = u8> {
-    if let &interp::serialization::FormatInfo::Fixed {
-        signed,
-        int_width,
-        frac_width,
-    } = format
-    {
-        let rational = float_to_rational(val);
-
-        let frac_part = rational.fract().abs();
-        let frac_log = log2_exact(&frac_part.denom().to_biguint().unwrap());
-
-        let number = if frac_log.is_none() && round_float {
-            let w = BigInt::from(1) << frac_width;
-            let new = (val * w.to_f64().unwrap()).round();
-            new.to_bigint().unwrap()
-        } else if frac_log.is_none() {
-            panic!("Number {val} cannot be represented as a fixed-point number. If you want to approximate the number, set the `round_float` flag to true.");
-        } else {
-            let int_part = rational.to_integer();
-
-            let frac_log = frac_log.unwrap_or_else(|| panic!("unable to round the given value to a value representable with {frac_width} fractional bits"));
-            if frac_log > frac_width {
-                panic!("cannot represent value with {frac_width} fractional bits, requires at least {frac_log} bits");
-            }
+) -> Vec<u8> {
+    match *format {
+        interp::serialization::FormatInfo::Fixed {
+            signed,
+            int_width,
+            frac_width,
+        } =>
+            {
+                let rational = float_to_rational(val);
+
+                let frac_part = rational.fract().abs();
+                let frac_log = log2_exact(&frac_part.denom().to_biguint().unwrap());
+
+                let number = if frac_log.is_none() && round_float {
+                    let w = BigInt::from(1) << frac_width;
+                    let new = (val * w.to_f64().unwrap()).round();
+                    new.to_bigint().unwrap()
+                } else if frac_log.is_none() {
+                    panic!("Number {val} cannot be represented as a fixed-point number. If you want to approximate the number, set the `round_float` flag to true.");
+                } else {
+                    let int_part = rational.to_integer();
 
-            let mut int_log =
-                log2_round_down(&int_part.abs().to_biguint().unwrap());
-            if (BigInt::from(1) << int_log) <= int_part.abs() {
-                int_log += 1;
-            }
-            if signed {
-                int_log += 1;
-            }
+                    let frac_log = frac_log.unwrap_or_else(|| panic!("unable to round the given value to a value representable with {frac_width} fractional bits"));
+                    if frac_log > frac_width {
+                        panic!("cannot represent value with {frac_width} fractional bits, requires at least {frac_log} bits");
+                    }
 
-            if int_log > int_width {
-                let signed_str = if signed { "signed " } else { "" };
+                    let mut int_log =
+                        log2_round_down(&int_part.abs().to_biguint().unwrap());
+                    if (BigInt::from(1) << int_log) <= int_part.abs() {
+                        int_log += 1;
+                    }
+                    if signed {
+                        int_log += 1;
+                    }
 
-                panic!("cannot represent {signed_str}value of {val} with {int_width} integer bits, requires at least {int_log} bits");
-            }
+                    if int_log > int_width {
+                        let signed_str = if signed { "signed " } else { "" };
 
-            rational.numer() << (frac_width - frac_log)
-        };
+                        panic!("cannot represent {signed_str}value of {val} with {int_width} integer bits, requires at least {int_log} bits");
+                    }
 
-        let bit_count = number.bits() + if signed { 1 } else { 0 };
+                    rational.numer() << (frac_width - frac_log)
+                };
 
-        if bit_count > (frac_width + int_width) as u64 {
-            let difference = bit_count - frac_width as u64;
-            panic!("The approximation of the number {val} cannot be represented with {frac_width} fractional bits and {int_width} integer bits. Requires at least {difference} integer bits.");
-        }
+                let bit_count = number.bits() + if signed { 1 } else { 0 };
 
-        sign_extend_vec(
-            number.to_signed_bytes_le(),
-            frac_width + int_width,
-            signed,
-        )
-        .into_iter()
-        .take((frac_width + int_width).div_ceil(8) as usize)
-    } else {
-        panic!("Called unroll_float on a non-fixed point type");
+                if bit_count > (frac_width + int_width) as u64 {
+                    let difference = bit_count - frac_width as u64;
+                    panic!("The approximation of the number {val} cannot be represented with {frac_width} fractional bits and {int_width} integer bits. Requires at least {difference} integer bits.");
+                }
+
+                sign_extend_vec(
+                    number.to_signed_bytes_le(),
+                    frac_width + int_width,
+                    signed,
+                )
+                    .into_iter()
+                    .take((frac_width + int_width).div_ceil(8) as usize)
+                    .collect::<Vec<_>>()
+            }
+        interp::serialization::FormatInfo::IEEFloat { width, .. } => {
+            match width {
+                32 => Vec::from((val as f32).to_le_bytes().as_slice()),
+                64 => Vec::from(val.to_le_bytes().as_slice()),
+                _ => unreachable!("Unsupported width {width}. Only 32 and 64 bit floats are supported.")
+            }
+        }
+        _ => panic!("Called unroll_float on a non-fixed point type"),
     }
 }
 
@@ -281,6 +289,24 @@ fn format_data(declaration: &MemoryDeclaration, data: &[u8]) -> ParseVec {
 
                     Number::from_f64(float).unwrap()
                 }
+                interp::serialization::FormatInfo::IEEFloat {
+                    width,
+                    ..
+                } => {
+                    let value = match width {
+                        32 => {
+                            debug_assert_eq!(chunk.len(), 4);
+                            format!("{}", f32::from_le_bytes(chunk.try_into().unwrap()))
+                        }
+                        64 => {
+                            debug_assert_eq!(chunk.len(), 8);
+                            format!("{}", f64::from_le_bytes(chunk.try_into().unwrap()))
+                        }
+                        _ => unreachable!("Unsupported width {width}. Only 32 and 64 bit floats are supported.")
+                    };
+                    // we need to inject the string directly in order to maintain the correct rounding
+                    Number::from_string_unchecked(value)
+                }
             }
         });
     // sanity check
@@ -392,7 +418,6 @@ mod tests {
         };
 
         let result = unroll_float(float, &format, true);
-        let result = result.collect_vec();
         BigInt::from_signed_bytes_le(&result);
         let parsed_res =
             parse_bytes_fixed(&result, int_width, frac_width, signed);
diff --git a/tools/cider-data-converter/src/dat_parser.rs b/tools/cider-data-converter/src/dat_parser.rs
new file mode 100644
index 0000000000..88f99d6ba6
--- /dev/null
+++ b/tools/cider-data-converter/src/dat_parser.rs
@@ -0,0 +1,133 @@
+use nom::{
+    branch::alt,
+    bytes::complete::{tag, take_while_m_n},
+    character::complete::{anychar, line_ending, multispace0},
+    combinator::{eof, map_res, opt},
+    error::Error,
+    multi::{many1, many_till},
+    sequence::{preceded, tuple},
+    IResult,
+};
+
+fn is_hex_digit(c: char) -> bool {
+    c.is_ascii_hexdigit()
+}
+
+fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
+    u8::from_str_radix(input, 16)
+}
+
+fn parse_hex(input: &str) -> IResult<&str, u8> {
+    map_res(take_while_m_n(1, 2, is_hex_digit), from_hex)(input)
+}
+
+/// Parse a single line of hex characters into a vector of bytes in the order
+/// the characters are given, i.e. reversed.
+fn hex_line(input: &str) -> IResult<&str, LineOrComment> {
+    // strip any leading whitespace
+    let (input, bytes) = preceded(
+        tuple((multispace0, opt(tag("0x")))),
+        many1(parse_hex),
+    )(input)?;
+
+    Ok((input, LineOrComment::Line(bytes)))
+}
+
+fn comment(input: &str) -> IResult<&str, LineOrComment> {
+    // skip any whitespace
+    let (input, _) = multispace0(input)?;
+    let (input, _) = tag("//")(input)?;
+    let (input, _) = many_till(anychar, alt((line_ending, eof)))(input)?;
+    Ok((input, LineOrComment::Comment))
+}
+/// Parse a line which only contains whitespace
+fn empty_line(input: &str) -> IResult<&str, LineOrComment> {
+    // skip any whitespace
+    let (input, _) = multispace0(input)?;
+    Ok((input, LineOrComment::EmptyLine))
+}
+
+pub fn line_or_comment(
+    input: &str,
+) -> Result<LineOrComment, nom::Err<Error<&str>>> {
+    let (_, res) = alt((hex_line, comment, empty_line))(input)?;
+    Ok(res)
+}
+
+#[derive(Debug, PartialEq)]
+pub enum LineOrComment {
+    Line(Vec<u8>),
+    Comment,
+    EmptyLine,
+}
+
+/// Parse a single line of hex characters, or a comment. Returns None if it's a
+/// comment or an empty line and Some(Vec<u8>) if it's a hex line. Panics on a
+/// parse error.
+///
+/// For the fallible version, see `line_or_comment`.
+pub fn unwrap_line_or_comment(input: &str) -> Option<Vec<u8>> {
+    match line_or_comment(input).expect("hex parse failed") {
+        LineOrComment::Line(vec) => Some(vec),
+        LineOrComment::Comment => None,
+        LineOrComment::EmptyLine => None,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_comment() {
+        assert_eq!(comment("// comment"), Ok(("", LineOrComment::Comment)));
+        assert_eq!(comment("// comment\n"), Ok(("", LineOrComment::Comment)));
+    }
+
+    #[test]
+    fn test_hex_line() {
+        assert_eq!(hex_line("0x01"), Ok(("", LineOrComment::Line(vec![1]))));
+        assert_eq!(hex_line("0x02"), Ok(("", LineOrComment::Line(vec![2]))));
+        assert_eq!(hex_line("0x03"), Ok(("", LineOrComment::Line(vec![3]))));
+        assert_eq!(hex_line("0x04"), Ok(("", LineOrComment::Line(vec![4]))));
+        assert_eq!(hex_line("0x05"), Ok(("", LineOrComment::Line(vec![5]))));
+        assert_eq!(hex_line("0x06"), Ok(("", LineOrComment::Line(vec![6]))));
+        assert_eq!(hex_line("0x07"), Ok(("", LineOrComment::Line(vec![7]))));
+        assert_eq!(hex_line("0x08"), Ok(("", LineOrComment::Line(vec![8]))));
+        assert_eq!(hex_line("0x09"), Ok(("", LineOrComment::Line(vec![9]))));
+        assert_eq!(hex_line("0x0a"), Ok(("", LineOrComment::Line(vec![10]))));
+        assert_eq!(hex_line("0x0b"), Ok(("", LineOrComment::Line(vec![11]))));
+        assert_eq!(hex_line("0x0c"), Ok(("", LineOrComment::Line(vec![12]))));
+        assert_eq!(hex_line("0x0d"), Ok(("", LineOrComment::Line(vec![13]))));
+        assert_eq!(hex_line("0x0e"), Ok(("", LineOrComment::Line(vec![14]))));
+        assert_eq!(hex_line("0x0f"), Ok(("", LineOrComment::Line(vec![15]))));
+        assert_eq!(hex_line("0xff"), Ok(("", LineOrComment::Line(vec![255]))));
+        assert_eq!(
+            hex_line("0x00ff"),
+            Ok(("", LineOrComment::Line(vec![0, 255])))
+        );
+    }
+
+    #[test]
+    fn test_from_hex() {
+        assert_eq!(from_hex("0"), Ok(0));
+        assert_eq!(from_hex("1"), Ok(1));
+        assert_eq!(from_hex("2"), Ok(2));
+        assert_eq!(from_hex("3"), Ok(3));
+        assert_eq!(from_hex("4"), Ok(4));
+        assert_eq!(from_hex("5"), Ok(5));
+        assert_eq!(from_hex("6"), Ok(6));
+        assert_eq!(from_hex("7"), Ok(7));
+        assert_eq!(from_hex("8"), Ok(8));
+        assert_eq!(from_hex("9"), Ok(9));
+        assert_eq!(from_hex("a"), Ok(10));
+        assert_eq!(from_hex("b"), Ok(11));
+        assert_eq!(from_hex("c"), Ok(12));
+        assert_eq!(from_hex("d"), Ok(13));
+        assert_eq!(from_hex("e"), Ok(14));
+        assert_eq!(from_hex("f"), Ok(15));
+
+        assert_eq!(from_hex("FF"), Ok(255));
+        assert_eq!(from_hex("ff"), Ok(255));
+    }
+}
diff --git a/tools/cider-data-converter/src/json_data.rs b/tools/cider-data-converter/src/json_data.rs
index 9ef5fc5667..9a5e62660d 100644
--- a/tools/cider-data-converter/src/json_data.rs
+++ b/tools/cider-data-converter/src/json_data.rs
@@ -1,17 +1,19 @@
-use std::{collections::HashMap, num::ParseFloatError, str::FromStr};
-
 use interp::serialization::Dimensions;
 use num_bigint::{BigInt, ParseBigIntError};
-use serde::{self, Deserialize, Serialize};
+use serde::{self, Deserialize, Serialize, Serializer};
 use serde_json::Number;
+use std::collections::BTreeMap;
+use std::{collections::HashMap, num::ParseFloatError, str::FromStr};
 use thiserror::Error;
 
-#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
 #[serde(rename_all = "lowercase")]
 pub enum NumericType {
     Bitnum,
     #[serde(alias = "fixed_point")]
     Fixed,
+    #[serde(alias = "ieee754_float")]
+    IEEE754Float,
 }
 
 #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -46,6 +48,10 @@ impl FormatInfo {
             || self.width.is_some() && self.int_width.is_some()
     }
 
+    pub fn is_floating_point(&self) -> bool {
+        self.numeric_type == NumericType::IEEE754Float
+    }
+
     pub fn int_width(&self) -> Option<u32> {
         if self.int_width.is_some() {
             self.int_width
@@ -99,6 +105,12 @@ impl FormatInfo {
                     frac_width,
                 }
             }
+            NumericType::IEEE754Float => {
+                interp::serialization::FormatInfo::IEEFloat {
+                    signed: self.is_signed,
+                    width: self.width.unwrap(),
+                }
+            }
         }
     }
 }
@@ -227,7 +239,7 @@ impl ParseVec {
     }
 
     pub fn parse(&self, format: &FormatInfo) -> Result<DataVec, ParseError> {
-        if format.is_fixedpt() {
+        if format.is_fixedpt() || format.is_floating_point() {
             match self {
                 ParseVec::D1(v) => {
                     let parsed: Vec<_> = v
@@ -525,10 +537,25 @@ pub struct JsonData(pub HashMap<String, JsonDataEntry>);
 #[serde(untagged)]
 /// A structure meant to mimic the old style of data dump printing.
 pub enum JsonPrintDump {
+    #[serde(serialize_with = "ordered_map")]
     Normal(HashMap<String, ParseVec>),
+    #[serde(serialize_with = "ordered_map")]
     Quoted(HashMap<String, PrintVec>),
 }
 
+/// For use with serde's [serialize_with] attribute
+/// see: https://stackoverflow.com/questions/42723065/how-to-sort-hashmap-keys-when-serializing-with-serde
+fn ordered_map<S, K: Ord + Serialize, V: Serialize>(
+    value: &HashMap<K, V>,
+    serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let ordered: BTreeMap<_, _> = value.iter().collect();
+    ordered.serialize(serializer)
+}
+
 impl JsonPrintDump {
     #[must_use]
     pub fn as_normal(&self) -> Option<&HashMap<String, ParseVec>> {
diff --git a/tools/cider-data-converter/src/lib.rs b/tools/cider-data-converter/src/lib.rs
index 7e1dadc1bd..31092d73e6 100644
--- a/tools/cider-data-converter/src/lib.rs
+++ b/tools/cider-data-converter/src/lib.rs
@@ -1,2 +1,3 @@
 pub mod converter;
+pub mod dat_parser;
 pub mod json_data;
diff --git a/tools/cider-data-converter/src/main.rs b/tools/cider-data-converter/src/main.rs
index e2c5c7099e..a95d910e83 100644
--- a/tools/cider-data-converter/src/main.rs
+++ b/tools/cider-data-converter/src/main.rs
@@ -1,9 +1,13 @@
 use argh::FromArgs;
-use cider_data_converter::{converter, json_data::JsonData};
-use interp::serialization::{self, SerializationError};
+use cider_data_converter::{
+    converter, dat_parser::unwrap_line_or_comment, json_data::JsonData,
+};
+use core::str;
+use interp::serialization::{self, DataDump, SerializationError};
 use std::{
     fs::File,
-    io::{self, Read, Write},
+    io::{self, BufRead, BufReader, BufWriter, Read, Write},
+    iter::repeat,
     path::PathBuf,
     str::FromStr,
 };
@@ -11,6 +15,9 @@ use thiserror::Error;
 
 const JSON_EXTENSION: &str = "data";
 const CIDER_EXTENSION: &str = "dump";
+const DAT_EXTENSION: &str = "dat";
+
+const HEADER_FILENAME: &str = "header";
 
 #[derive(Error)]
 enum CiderDataConverterError {
@@ -28,6 +35,14 @@ enum CiderDataConverterError {
 
     #[error(transparent)]
     DataDumpError(#[from] SerializationError),
+
+    #[error(
+        "Missing output path. This is required for the \"to dat\" conversion"
+    )]
+    MissingDatOutputPath,
+
+    #[error("Output path for \"to dat\" exists but it is a file")]
+    DatOutputPathIsFile,
 }
 
 impl std::fmt::Debug for CiderDataConverterError {
@@ -36,18 +51,27 @@ impl std::fmt::Debug for CiderDataConverterError {
     }
 }
 
-enum Action {
-    ToDataDump,
-    ToJson,
+/// What are we converting the input to
+#[derive(Debug, Clone, Copy)]
+enum Target {
+    /// Cider's Single-file DataDump format
+    DataDump,
+    /// Verilator/icarus directory format
+    Dat,
+    /// Human readable output JSON
+    Json,
 }
 
-impl FromStr for Action {
+impl FromStr for Target {
     type Err = CiderDataConverterError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         match s.to_lowercase().as_str() {
-            "json" => Ok(Action::ToJson),
-            "cider" | "dump" | "data-dump" => Ok(Action::ToDataDump),
+            "json" => Ok(Target::Json),
+            "cider" | "dump" | "data-dump" => Ok(Target::DataDump),
+            "dat" | "verilog-dat" | "verilog" | "verilator" | "icarus" => {
+                Ok(Target::Dat)
+            }
             _ => Err(CiderDataConverterError::BadToArgument(s.to_string())),
         }
     }
@@ -71,59 +95,129 @@ struct Opts {
     /// optional specification of what action to perform. Can be "cider" or
     /// "json". If not provided, the converter will try to guess based on file names
     #[argh(option, short = 't', long = "to")]
-    action: Option<Action>,
+    action: Option<Target>,
 
     /// whether to use quotes around floating point numbers in the output. This
     /// exists solely for backwards compatibility with the old display format.
     #[argh(switch, long = "legacy-quotes")]
     use_quotes: bool,
+
+    /// the file extension to use for the output/input file when parsing to and
+    /// from the dat target. If not provided, the extension is assumed to be .dat
+    #[argh(option, short = 'e', long = "dat-file-extension")]
+    #[argh(default = "String::from(DAT_EXTENSION)")]
+    file_extension: String,
 }
 
 fn main() -> Result<(), CiderDataConverterError> {
     let mut opts: Opts = argh::from_env();
 
-    let mut input: Box<dyn Read> = opts
-        .input_path
-        .as_ref()
-        .map(|path| File::open(path).map(|x| Box::new(x) as Box<dyn Read>))
-        .unwrap_or(Ok(Box::new(io::stdin())))?;
-
-    let mut output: Box<dyn Write> = opts
-        .output_path
-        .as_ref()
-        .map(|path| File::create(path).map(|x| Box::new(x) as Box<dyn Write>))
-        .unwrap_or(Ok(Box::new(io::stdout())))?;
-
     // if no action is specified, try to guess based on file extensions
     if opts.action.is_none()
+        // input is .json
         && (opts.input_path.as_ref().is_some_and(|x| {
             x.extension().map_or(false, |y| y == JSON_EXTENSION)
-        }) || opts.output_path.as_ref().is_some_and(|x| {
+        })
+        // output is .dump
+        || opts.output_path.as_ref().is_some_and(|x| {
             x.extension().map_or(false, |y| y == CIDER_EXTENSION)
         }))
     {
-        opts.action = Some(Action::ToDataDump);
+        opts.action = Some(Target::DataDump);
     } else if opts.action.is_none()
+        // output is .json
         && (opts.output_path.as_ref().is_some_and(|x| {
             x.extension().map_or(false, |x| x == JSON_EXTENSION)
-        }) || opts.input_path.as_ref().is_some_and(|x| {
+        })
+        // input is .dump
+        || opts.input_path.as_ref().is_some_and(|x| {
             x.extension().map_or(false, |x| x == CIDER_EXTENSION)
-        }))
+        })
+        // input is a directory (suggesting a deserialization from dat)
+        || opts.input_path.as_ref().is_some_and(|x| x.is_dir()))
     {
-        opts.action = Some(Action::ToJson);
+        opts.action = Some(Target::Json);
     }
 
     if let Some(action) = opts.action {
         match action {
-            Action::ToDataDump => {
+            Target::DataDump => {
+                let (mut input, mut output) = get_io_handles(&opts)?;
+
                 let parsed_json: JsonData =
                     serde_json::from_reader(&mut input)?;
                 converter::convert_to_data_dump(&parsed_json, opts.round_float)
                     .serialize(&mut output)?;
             }
-            Action::ToJson => {
-                let data_dump =
-                    serialization::DataDump::deserialize(&mut input)?;
+            Target::Json => {
+                let data_dump = if let Some(path) = &opts.input_path {
+                    if path.is_dir() {
+                        // we are converting from a dat directory rather than a
+                        // dump
+
+                        let header = {
+                            let mut header_file =
+                                File::open(path.join(HEADER_FILENAME))?;
+                            let mut raw_header = vec![];
+                            header_file.read_to_end(&mut raw_header)?;
+
+                            serialization::DataHeader::deserialize(&raw_header)?
+                        };
+
+                        let mut data: Vec<u8> = vec![];
+
+                        for mem_dec in &header.memories {
+                            let starting_len = data.len();
+                            let mem_file = BufReader::new(File::open(
+                                path.join(format!(
+                                    "{}.{}",
+                                    mem_dec.name, opts.file_extension
+                                )),
+                            )?);
+
+                            for line in mem_file.lines() {
+                                let line = line?;
+                                if let Some(line_data) =
+                                    unwrap_line_or_comment(&line)
+                                {
+                                    assert!(
+                                        line_data.len()
+                                            <= mem_dec.bytes_per_entry()
+                                                as usize,
+                                        "line data too long"
+                                    );
+
+                                    let padding = (mem_dec.bytes_per_entry()
+                                        as usize)
+                                        - line_data.len();
+
+                                    data.extend(line_data.into_iter().rev());
+                                    data.extend(repeat(0u8).take(padding))
+                                }
+                            }
+
+                            assert_eq!(
+                                data.len() - starting_len,
+                                mem_dec.byte_count()
+                            );
+                        }
+
+                        DataDump { header, data }
+                    } else {
+                        // we are converting from a dump file
+                        serialization::DataDump::deserialize(
+                            &mut get_read_handle(&opts)?,
+                        )?
+                    }
+                } else {
+                    // we are converting from a dump file
+                    serialization::DataDump::deserialize(&mut get_read_handle(
+                        &opts,
+                    )?)?
+                };
+
+                let mut output = get_output_handle(&opts)?;
+
                 let json_data = converter::convert_from_data_dump(
                     &data_dump,
                     opts.use_quotes,
@@ -134,6 +228,55 @@ fn main() -> Result<(), CiderDataConverterError> {
                     serde_json::to_string_pretty(&json_data)?
                 )?;
             }
+            Target::Dat => {
+                let mut input = get_read_handle(&opts)?;
+                let parsed_json: JsonData =
+                    serde_json::from_reader(&mut input)?;
+                let data = converter::convert_to_data_dump(
+                    &parsed_json,
+                    opts.round_float,
+                );
+
+                if let Some(path) = opts.output_path {
+                    if path.exists() && !path.is_dir() {
+                        return Err(
+                            CiderDataConverterError::DatOutputPathIsFile,
+                        );
+                    } else if !path.exists() {
+                        std::fs::create_dir(&path)?;
+                    }
+
+                    let mut header_output =
+                        File::create(path.join(HEADER_FILENAME))?;
+                    header_output.write_all(&data.header.serialize()?)?;
+
+                    for memory in &data.header.memories {
+                        let file = File::create(path.join(format!(
+                            "{}.{}",
+                            memory.name, opts.file_extension
+                        )))?;
+                        let mut writer = BufWriter::new(file);
+                        for bytes in data
+                            .get_data(&memory.name)
+                            .unwrap()
+                            .chunks_exact(memory.bytes_per_entry() as usize)
+                        {
+                            // data file seems to expect lsb on the right
+                            // for the moment electing to print out every byte
+                            // and do so with two hex digits per byte rather
+                            // than truncating leading zeroes. No need to do
+                            // anything fancy here.
+                            for byte in bytes.iter().rev() {
+                                write!(writer, "{byte:02X}")?;
+                            }
+
+                            writeln!(writer)?;
+                        }
+                    }
+                } else {
+                    return Err(CiderDataConverterError::MissingDatOutputPath);
+                }
+            }
         }
     } else {
         // Since we can't guess based on input/output file names and no target
@@ -143,3 +286,34 @@ fn main() -> Result<(), CiderDataConverterError> {
 
     Ok(())
 }
+
+#[allow(clippy::type_complexity)]
+fn get_io_handles(
+    opts: &Opts,
+) -> Result<(Box<dyn Read>, Box<dyn Write>), CiderDataConverterError> {
+    let input = get_read_handle(opts)?;
+    let output = get_output_handle(opts)?;
+    Ok((input, output))
+}
+
+fn get_output_handle(
+    opts: &Opts,
+) -> Result<Box<dyn Write>, CiderDataConverterError> {
+    let output: Box<dyn Write> = opts
+        .output_path
+        .as_ref()
+        .map(|path| File::create(path).map(|x| Box::new(x) as Box<dyn Write>))
+        .unwrap_or(Ok(Box::new(io::stdout())))?;
+    Ok(output)
+}
+
+fn get_read_handle(
+    opts: &Opts,
+) -> Result<Box<dyn Read>, CiderDataConverterError> {
+    let input: Box<dyn Read> = opts
+        .input_path
+        .as_ref()
+        .map(|path| File::open(path).map(|x| Box::new(x) as Box<dyn Read>))
+        .unwrap_or(Ok(Box::new(io::stdin())))?;
+    Ok(input)
+}
diff --git a/tools/component_cells/src/main.rs b/tools/component_cells/src/main.rs
index 8b1be9eb40..67d2f89fe9 100644
--- a/tools/component_cells/src/main.rs
+++ b/tools/component_cells/src/main.rs
@@ -6,6 +6,8 @@ use serde::Serialize;
 use std::path::{Path, PathBuf};
 use std::{collections::HashSet, io};
 
+/// Tool to obtain list of names and original component names for all non-primitive cells in each component.
+
 #[derive(FromArgs)]
 /// Path for library and path for file to read from
 struct Args {
@@ -26,9 +28,6 @@ fn read_path(path: &str) -> Result<PathBuf, String> {
     Ok(Path::new(path).into())
 }
 
-#[derive(Default)]
-pub struct ComponentCellsBackend;
-
 fn main() -> CalyxResult<()> {
     let p: Args = argh::from_env();
 
diff --git a/tools/profiler/create-visuals.py b/tools/profiler/create-visuals.py
index 50061d4daa..1c279380d4 100644
--- a/tools/profiler/create-visuals.py
+++ b/tools/profiler/create-visuals.py
@@ -41,7 +41,13 @@ def get_active_group(self):
         elif len(self.active_groups) == 1:
             return self.active_groups[0]
         else:
-            raise Exception(f'Component {self.component} is parallel! Active groups: {self.active_groups}')
+            # concatenate all parallel active groups
+            sorted_active_groups = list(sorted(self.active_groups))
+            acc = sorted_active_groups[0]
+            for group in sorted_active_groups[1:]:
+                suffix = group.split(".")[-1]
+                acc += "/" + suffix
+            return acc
     
     """
     Returns the identifier of this stack: either the full name of the active group, or the full name of the cell if no groups are active. 
@@ -266,7 +272,6 @@ def create_timeline_stacks(trace, main_component):
     cell_to_stackframe_info["TOP.toplevel"] = (2, 1)
 
     for i in trace:
-        print(trace[i])
         active_this_cycle = set()
         # Start from the bottom up. Parent is the previous stack!
         parent = "MAIN"
diff --git a/tools/profiler/get-profile-counts-info.sh b/tools/profiler/get-profile-counts-info.sh
deleted file mode 100644
index 06c3bc39b3..0000000000
--- a/tools/profiler/get-profile-counts-info.sh
+++ /dev/null
@@ -1,135 +0,0 @@
-# Wrapper script for running TDCC, running simulation, obtaining cycle counts information, and producing flame graphs to visualize
-
-if [ $# -lt 2 ]; then
-    echo "USAGE: bash $0 INPUT_FILE SIM_DATA_JSON [OUT_CSV]"
-    exit
-fi
-
-SCRIPT_DIR=$( cd $( dirname $0 ) && pwd )
-SCRIPT_NAME=$( echo "$0" | rev | cut -d/ -f1 | rev )
-CALYX_DIR=$( dirname $( dirname ${SCRIPT_DIR} ) )
-
-INPUT_FILE=$1
-SIM_DATA_JSON=$2
-name=$( echo "${INPUT_FILE}" | rev | cut -d/ -f1 | rev | cut -d. -f1 )
-DATA_DIR=${SCRIPT_DIR}/data/${name}
-TMP_DIR=${DATA_DIR}/generated-data
-if [ $# -ge 3 ]; then
-    OUT_CSV=$3
-else
-    OUT_CSV=${TMP_DIR}/summary.csv
-fi
-
-FLAMEGRAPH_DIR=${SCRIPT_DIR}/fg-tmp
-
-if [ ! -d ${FLAMEGRAPH_DIR} ]; then
-    (
-	cd ${SCRIPT_DIR}
-	git clone git@github.com:brendangregg/FlameGraph.git fg-tmp
-    )
-fi
-
-TMP_VERILOG=${TMP_DIR}/no-opt-verilog.sv
-FSM_JSON=${TMP_DIR}/fsm.json
-CELLS_JSON=${TMP_DIR}/cells.json
-OUT_JSON=${TMP_DIR}/dump.json
-TIMELINE_VIEW_JSON=${TMP_DIR}/timeline.json
-FSM_TIMELINE_VIEW_JSON=${TMP_DIR}/fsm-timeline.json
-FLAME_GRAPH_FOLDED=${TMP_DIR}/flame.folded
-FSM_FLAME_GRAPH_FOLDED=${TMP_DIR}/fsm-flame.folded
-FREQUENCY_FLAME_GRAPH_FOLDED=${TMP_DIR}/frequency-flame.folded
-COMPONENTS_FOLDED=${TMP_DIR}/components.folded
-FSM_COMPONENTS_FOLDED=${TMP_DIR}/fsm-components.folded
-VCD_FILE=${TMP_DIR}/trace.vcd
-LOGS_DIR=${DATA_DIR}/logs
-if [ -d ${DATA_DIR} ]; then
-    rm -rf ${DATA_DIR} # clean out directory for run each time
-fi
-mkdir -p ${TMP_DIR} ${LOGS_DIR}
-rm -f ${TMP_DIR}/* ${LOGS_DIR}/* # remove data from last run
-
-CALYX_ARGS=" -p static-inline -p compile-static -p compile-repeat -p par-to-seq -p no-opt "
-
-
-# Run TDCC to get the FSM info
-echo "[${SCRIPT_NAME}] Obtaining FSM info from TDCC"
-(
-    cd ${CALYX_DIR}
-    set -o xtrace
-    cargo run -- ${INPUT_FILE} ${CALYX_ARGS} -x tdcc:dump-fsm-json="${FSM_JSON}"
-    set +o xtrace
-) &> ${LOGS_DIR}/gol-tdcc
-
-if [ ! -f ${FSM_JSON} ]; then
-    echo "[${SCRIPT_NAME}] Failed to generate ${FSM_JSON}! Exiting"
-    exit 1
-fi
-
-# Run component-cells backend to get cell information
-echo "[${SCRIPT_NAME}] Obtaining cell information from component-cells backend"
-(
-    cd ${CALYX_DIR}
-    set -o xtrace
-    cargo run --manifest-path tools/component_cells/Cargo.toml ${INPUT_FILE} -o ${CELLS_JSON}
-) &> ${LOGS_DIR}/gol-cells
-
-if [ ! -f ${CELLS_JSON} ]; then
-    echo "[${SCRIPT_NAME}] Failed to generate ${CELLS_JSON}! Exiting"
-    exit 1
-fi
-
-# Run simuation to get VCD
-echo "[${SCRIPT_NAME}] Obtaining VCD file via simulation"
-(
-    set -o xtrace
-    fud2 ${INPUT_FILE} -o ${VCD_FILE} --through verilator -s calyx.args="${CALYX_ARGS}" -s sim.data=${SIM_DATA_JSON}
-    set +o xtrace
-) &> ${LOGS_DIR}/gol-vcd
-
-if [ ! -f ${VCD_FILE} ]; then
-    echo "[${SCRIPT_NAME}] Failed to generate ${VCD_FILE}! Exiting"
-    exit 1
-fi
-
-# Run script to get cycle level counts
-echo "[${SCRIPT_NAME}] Using FSM info and VCD file to obtain cycle level counts"
-(
-    set -o xtrace
-    python3 ${SCRIPT_DIR}/parse-vcd.py ${VCD_FILE} ${FSM_JSON} ${CELLS_JSON} ${OUT_CSV} ${OUT_JSON}
-    set +o xtrace
-) &> ${LOGS_DIR}/gol-process
-
-if [ "$4" == "-d" ]; then
-    cat ${LOGS_DIR}/gol-process | grep -v Writing # exclude lines that show paths
-else
-    tail -3 ${LOGS_DIR}/gol-process | head -2 # last line is the set +o xtrace, which we don't need to show
-fi
-
-echo "[${SCRIPT_NAME}] Writing visualization files"
-(
-    set -o xtrace
-    python3 ${SCRIPT_DIR}/create-visuals.py ${OUT_JSON} ${CELLS_JSON} ${TIMELINE_VIEW_JSON} ${FSM_TIMELINE_VIEW_JSON} ${FLAME_GRAPH_FOLDED} ${FSM_FLAME_GRAPH_FOLDED} ${FREQUENCY_FLAME_GRAPH_FOLDED} ${COMPONENTS_FOLDED} ${FSM_COMPONENTS_FOLDED}
-    set +o xtrace
-) &> ${LOGS_DIR}/gol-visuals
-
-echo "[${SCRIPT_NAME}] Creating flame graph svg"
-(
-    set -o xtrace
-    for opt in "" "--inverted" "--reverse"; do
-	if [ "${opt}" == "" ]; then
-	    filename=flame
-	else
-	    filename=flame"${opt:1}"
-	fi
-	${FLAMEGRAPH_DIR}/flamegraph.pl ${opt} --countname="cycles" ${FLAME_GRAPH_FOLDED} > ${TMP_DIR}/${filename}.svg
-	echo
-	${FLAMEGRAPH_DIR}/flamegraph.pl ${opt} --countname="cycles" ${FSM_FLAME_GRAPH_FOLDED} > ${TMP_DIR}/fsm-${filename}.svg
-	echo
-	${FLAMEGRAPH_DIR}/flamegraph.pl ${opt} --countname="times active" ${FREQUENCY_FLAME_GRAPH_FOLDED} > ${TMP_DIR}/frequency-${filename}.svg
-	echo
-	${FLAMEGRAPH_DIR}/flamegraph.pl ${opt} --countname="times active" ${COMPONENTS_FOLDED} > ${TMP_DIR}/components-${filename}.svg
-	echo
-	${FLAMEGRAPH_DIR}/flamegraph.pl ${opt} --countname="times active" ${FSM_COMPONENTS_FOLDED} > ${TMP_DIR}/fsm-components-${filename}.svg
-    done
-    set +o xtrace
-) &> ${LOGS_DIR}/gol-flamegraph
diff --git a/tools/profiler/parse-vcd.py b/tools/profiler/parse-vcd.py
index ce2dc236d0..8c2d79d7a2 100644
--- a/tools/profiler/parse-vcd.py
+++ b/tools/profiler/parse-vcd.py
@@ -11,7 +11,7 @@ class ProfilingInfo:
     def __init__(self, name, component, fsm_name=None, fsm_values=None, tdcc_group_name=None, is_cell=False):
         self.name = name
         self.fsm_name = fsm_name
-        self.fsm_values = fsm_values
+        self.fsm_values = list(sorted(fsm_values)) if fsm_values is not None else None
         self.total_cycles = 0
         self.closed_segments = [] # Segments will be (start_time, end_time)
         self.current_segment = None
@@ -95,25 +95,20 @@ def end_current_segment(self, curr_clock_cycle):
 
 class VCDConverter(vcdvcd.StreamParserCallbacks):
 
-    def __init__(self, fsms, single_enable_names, tdcc_groups, fsm_group_maps, main_component, cells):
+    def __init__(self, fsms, tdcc_groups, fsm_group_maps, main_component, cells):
         super().__init__()
         self.main_component = main_component
         self.fsms = fsms
-        self.single_enable_names = single_enable_names.keys()
+        self.single_enable_names = set()
         # Recording the first cycle when the TDCC group became active
         self.tdcc_group_active_cycle = {tdcc_group_name : -1 for tdcc_group_name in tdcc_groups}
         # Map from a TDCC group to all FSMs that depend on it. maybe a 1:1 mapping
         self.tdcc_group_to_dep_fsms = tdcc_groups
         # Group name --> ProfilingInfo object
         self.profiling_info = {}
-        self.signal_to_curr_value = {fsm : -1 for fsm in fsms}
         for group in fsm_group_maps:
             # Differentiate FSM versions from ground truth versions
             self.profiling_info[f"{group}FSM"] = ProfilingInfo(group, fsm_group_maps[group]["component"], fsm_group_maps[group]["fsm"], fsm_group_maps[group]["ids"], fsm_group_maps[group]["tdcc-group-name"])
-        for single_enable_group in single_enable_names:
-            self.profiling_info[single_enable_group] = ProfilingInfo(single_enable_group, single_enable_names[single_enable_group])
-            self.signal_to_curr_value[f"{single_enable_group}_go"] = -1
-            self.signal_to_curr_value[f"{single_enable_group}_done"] = -1
         self.cells = set(cells.keys())
         for cell in cells:
             self.profiling_info[cell] = ProfilingInfo(cell, cells[cell], is_cell=True)
@@ -127,9 +122,6 @@ def enddefinitions(self, vcd, signals, cur_sig_vals):
         names = [remove_size_from_name(e[0]) for e in refs]
         signal_id_dict = {sid : [] for sid in vcd.references_to_ids.values()} # one id can map to multiple signal names since wires are connected
 
-        # main_go_name = f"{self.main_component}.go"
-        # signal_id_dict[vcd.references_to_ids[main_go_name]] = [main_go_name]
-
         clock_name = f"{self.main_component}.clk"
         if clock_name not in names:
             print("Can't find the clock? Exiting...")
@@ -154,11 +146,12 @@ def enddefinitions(self, vcd, signals, cur_sig_vals):
             for fsm in self.fsms:
                 if name.startswith(f"{fsm}.out["):
                     signal_id_dict[sid].append(name)
-            for single_enable_group in self.single_enable_names:
-                if name.startswith(f"{single_enable_group}_go.out["):
-                    signal_id_dict[sid].append(name)
-                if name.startswith(f"{single_enable_group}_done.out["):
-                    signal_id_dict[sid].append(name)
+            if "_probe_out" in name: # instrumentation probes are "<group>__<component>_probe"
+                group_component_split = name.split("_probe_out")[0].split("__")
+                group_name = group_component_split[0]
+                self.single_enable_names.add(group_name)
+                self.profiling_info[group_name] = ProfilingInfo(group_name, group_component_split[1])
+                signal_id_dict[sid].append(name)
 
         # don't need to check for signal ids that don't pertain to signals we're interested in
         self.signal_id_to_names = {k:v for k,v in signal_id_dict.items() if len(v) > 0}
@@ -247,11 +240,11 @@ def postprocess(self):
                 if signal_name.endswith(".done") and value == 1: # cells have .go and .done
                     cell = signal_name.split(".done")[0]
                     self.profiling_info[cell].end_current_segment(clock_cycles)
-                if "_go" in signal_name and value == 1:
-                    group = "_".join(signal_name.split("_")[0:-1])
+                if "_probe_out" in signal_name and value == 1: # instrumented group started being active
+                    group = signal_name.split("_probe_out")[0].split("__")[0]
                     self.profiling_info[group].start_new_segment(clock_cycles)
-                elif "_done" in signal_name and value == 1:
-                    group = "_".join(signal_name.split("_")[0:-1])
+                elif "_probe_out" in signal_name and value == 0: # instrumented group stopped being active
+                    group = signal_name.split("_probe_out")[0].split("__")[0]
                     self.profiling_info[group].end_current_segment(clock_cycles)
                 elif "fsm" in signal_name:
                     fsm = ".".join(signal_name.split(".")[0:-1])
@@ -302,9 +295,8 @@ def read_component_cell_names_json(json_file):
     return full_main_component, components_to_cells
 
 # Reads json generated by TDCC (via dump-fsm-json option) to produce initial group information
-def remap_tdcc_json(json_file, components_to_cells):
-    profiling_infos = json.load(open(json_file))
-    group_names = {} # all groups (to record ground truth). Maps to the group's component (needed for stacks)
+def remap_tdcc_json(tdcc_json_file, components_to_cells):
+    profiling_infos = json.load(open(tdcc_json_file))
     cells_to_components = {} # go and done info are needed for cells. cell --> component name
     tdcc_groups = {} # TDCC-generated groups that manage control flow using FSMs. maps to all fsms that map to the tdcc group
     fsm_group_maps = {} # fsm-managed groups info (fsm register, TDCC group that manages fsm, id of group within fsm)
@@ -325,18 +317,13 @@ def remap_tdcc_json(json_file, components_to_cells):
                         if tdcc_group not in tdcc_groups: # Keep track of the TDCC group to figure out when first group starts
                             tdcc_groups[tdcc_group] = set()
                         tdcc_groups[tdcc_group].add(fsm_name)
-                        group_names[group_name] = fsm["component"]
                     else:
                         fsm_group_maps[group_name]["ids"].append(state["id"])  
-        else:
-            component = profiling_info["SingleEnable"]["component"]
-            for cell in components_to_cells[component]: # get all possibilities of cells
-                group_names[cell + "." + profiling_info["SingleEnable"]["group"]] = component
     for component in components_to_cells:
         for cell in components_to_cells[component]:
             cells_to_components[cell] = component
 
-    return fsms, group_names, tdcc_groups, fsm_group_maps, cells_to_components
+    return fsms, tdcc_groups, fsm_group_maps, cells_to_components
 
 def output_result(out_csv, dump_out_json, converter):
     print(f"Total clock cycles: {converter.clock_cycles}")
@@ -372,10 +359,10 @@ def output_result(out_csv, dump_out_json, converter):
     writer.writeheader()
     writer.writerows(csv_acc)
 
-def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json):
+def main(vcd_filename, tdcc_json_file, cells_json_file, out_csv, dump_out_json):
     main_component, components_to_cells = read_component_cell_names_json(cells_json_file)
-    fsms, group_names, tdcc_group_names, fsm_group_maps, cells = remap_tdcc_json(groups_json_file, components_to_cells)
-    converter = VCDConverter(fsms, group_names, tdcc_group_names, fsm_group_maps, main_component, cells)
+    fsms, tdcc_group_names, fsm_group_maps, cells = remap_tdcc_json(tdcc_json_file, components_to_cells)
+    converter = VCDConverter(fsms, tdcc_group_names, fsm_group_maps, main_component, cells)
     vcdvcd.VCDVCD(vcd_filename, callbacks=converter, store_tvs=False)
     converter.postprocess()
     output_result(out_csv, dump_out_json, converter)
@@ -398,6 +385,8 @@ def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json
         ]
         print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}")
         print("TDCC_JSON: Run Calyx with `tdcc:dump-fsm-json` option")
-        print("CELLS_JSON: Run Calyx with `component-cells` backend")
+        print("CELLS_JSON: Run the `component_cells` tool")
+        print("GROUPS_JSON: Run the `component_groups` tool")
         print("If SUMMARY_OUT_CSV is STDOUT, then summary CSV will be printed to stdout")
+        print("DUMP_OUT_JSON: output json file for group-specific")
         sys.exit(-1)


Test patch:
diff --git a/fud2/tests/snapshots/tests__list_ops.snap b/fud2/tests/snapshots/tests__list_ops.snap
index ed1652224d..d199268f20 100644
--- a/fud2/tests/snapshots/tests__list_ops.snap
+++ b/fud2/tests/snapshots/tests__list_ops.snap
@@ -47,6 +47,11 @@ source: fud2/tests/tests.rs
         "dahlia",
         "calyx",
     ),
+    (
+        "dat_to_jq",
+        "dat",
+        "jq",
+    ),
     (
         "debug",
         "cider",
@@ -97,6 +102,11 @@ source: fud2/tests/tests.rs
         "calyx",
         "primitive-uses-json",
     ),
+    (
+        "profiler",
+        "calyx",
+        "flamegraph",
+    ),
     (
         "simulate",
         "sim",
diff --git a/fud2/tests/snapshots/tests__list_states.snap b/fud2/tests/snapshots/tests__list_states.snap
index 8872d6c337..73b29224d8 100644
--- a/fud2/tests/snapshots/tests__list_states.snap
+++ b/fud2/tests/snapshots/tests__list_states.snap
@@ -10,11 +10,16 @@ source: fud2/tests/tests.rs
     "dat",
     "firrtl",
     "firrtl-with-primitives",
+    "flamegraph",
+    "jq",
     "mrxl",
     "primitive-uses-json",
     "sim",
+    "sim-instrumented",
     "vcd",
+    "vcd-instrumented",
     "verilog",
+    "verilog-instrumented",
     "verilog-noverify",
     "verilog-refmem",
     "verilog-refmem-noverify",
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap
index b8dceedaec..df20b48991 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_cider_to_dat.snap
@@ -6,47 +6,37 @@ build-tool = fud2
 rule get-rsrc
   command = $build-tool get-rsrc $out
 
-python = python3
-build json-dat.py: get-rsrc
-rule hex-data
-  command = $python json-dat.py --from-json $in $out
-rule json-data
-  command = $python json-dat.py --to-json $out $in
-sim_data = /test/data.json
-datadir = sim_data
-build $datadir: hex-data $sim_data | json-dat.py
-rule sim-run
-  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
-cycle-limit = 500000000
-
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 cider-exe = $calyx-base/target/debug/cider
 cider-converter = $calyx-base/target/debug/cider-data-converter
-rule run-cider-debug
-  command = $cider-exe -l $calyx-base --data data.dump $in debug || true
-  pool = console
 converter-flags = 
 cider-flags = 
+data = --data data.dump
+sim_data = /test/data.json
+rule run-cider-debug
+  command = $cider-exe -l $calyx-base $data $cider-flags $in debug || true
+  pool = console
 rule run-cider
-  command = $cider-exe -l $calyx-base --data data.dump $cider-flags $in > $out
-rule dump-to-interp
-  command = $cider-converter --to cider $converter-flags $in > $out
+  command = $cider-exe -l $calyx-base $data $cider-flags $in > $out
 rule interp-to-dump
   command = $cider-converter --to json $converter-flags $in > $out
+rule dump-to-interp
+  command = $cider-converter --to cider $converter-flags $in > $out
 build data.dump: dump-to-interp $sim_data | $cider-converter
 
-build pseudo_cider: calyx-with-flags _from_stdin_calyx.futil
+build pseudo_cider: calyx-cider _from_stdin_calyx.futil
 build interp_out.dump: run-cider pseudo_cider | data.dump
-build _to_stdout_dat.json: interp-to-dump interp_out.dump | $sim_data $cider-converter
+build _to_stdout_dat.json: interp-to-dump interp_out.dump | $cider-converter
 
 default _to_stdout_dat.json
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap b/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap
index 3ff1ae5137..f20b813bbd 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_firrtl_to_verilog-refmem.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 yxi = $calyx-base/target/debug/yxi
 rule yxi
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap
index 56cd7ebad5..d57f28a64d 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_dat.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 python = python3
 build json-dat.py: get-rsrc
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap
index 3d0b116ef9..a8977d92fb 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_icarus_to_vcd.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 python = python3
 build json-dat.py: get-rsrc
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap
index 3adf6da679..471b93e34c 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_dat.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 python = python3
 build json-dat.py: get-rsrc
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap
index 4b47721931..9e87d685f1 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_verilator_to_vcd.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 python = python3
 build json-dat.py: get-rsrc
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap b/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap
index 6531aaf4f2..843d1abbcb 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_xrt-trace_to_vcd.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 vivado-dir = /test/xilinx/vivado
 vitis-dir = /test/xilinx/vitis
diff --git a/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap b/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap
index 5aa1580b4b..24879e9050 100644
--- a/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_through_xrt_to_dat.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 vivado-dir = /test/xilinx/vivado
 vitis-dir = /test/xilinx/vitis
diff --git a/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap b/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap
index b2e030ec6f..2cbeac86ae 100644
--- a/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_to_cider-debug.snap
@@ -6,48 +6,38 @@ build-tool = fud2
 rule get-rsrc
   command = $build-tool get-rsrc $out
 
-python = python3
-build json-dat.py: get-rsrc
-rule hex-data
-  command = $python json-dat.py --from-json $in $out
-rule json-data
-  command = $python json-dat.py --to-json $out $in
-sim_data = /test/data.json
-datadir = sim_data
-build $datadir: hex-data $sim_data | json-dat.py
-rule sim-run
-  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
-cycle-limit = 500000000
-
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 build tb.sv: get-rsrc
 
 cider-exe = $calyx-base/target/debug/cider
 cider-converter = $calyx-base/target/debug/cider-data-converter
-rule run-cider-debug
-  command = $cider-exe -l $calyx-base --data data.dump $in debug || true
-  pool = console
 converter-flags = 
 cider-flags = 
+data = --data data.dump
+sim_data = /test/data.json
+rule run-cider-debug
+  command = $cider-exe -l $calyx-base $data $cider-flags $in debug || true
+  pool = console
 rule run-cider
-  command = $cider-exe -l $calyx-base --data data.dump $cider-flags $in > $out
-rule dump-to-interp
-  command = $cider-converter --to cider $converter-flags $in > $out
+  command = $cider-exe -l $calyx-base $data $cider-flags $in > $out
 rule interp-to-dump
   command = $cider-converter --to json $converter-flags $in > $out
+rule dump-to-interp
+  command = $cider-converter --to cider $converter-flags $in > $out
 build data.dump: dump-to-interp $sim_data | $cider-converter
 
-build pseudo_cider: calyx-with-flags _from_stdin_calyx.futil
+build pseudo_cider: calyx-cider _from_stdin_calyx.futil
 build _to_stdout_cider-debug: run-cider-debug pseudo_cider | data.dump
 
 default _to_stdout_cider-debug
diff --git a/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap b/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap
index af413d8098..005a2be78c 100644
--- a/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap
+++ b/fud2/tests/snapshots/tests__test@calyx_to_verilog.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 build _to_stdout_verilog.sv: calyx _from_stdin_calyx.futil
   backend = verilog
diff --git a/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap b/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap
index 98d04f45ed..f97fff8a97 100644
--- a/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap
+++ b/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 yxi = $calyx-base/target/debug/yxi
 rule yxi
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-noverify.snap b/fud2/tests/snapshots/tests__test@plan_calyx-noverify.snap
index 826c669cf1..cfa30dd77c 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-noverify.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-noverify.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 build /output.ext: calyx /input.ext
   backend = verilog
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-to-cider.snap b/fud2/tests/snapshots/tests__test@plan_calyx-to-cider.snap
index d4c031b4a6..af5dfcfaff 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-to-cider.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-to-cider.snap
@@ -6,30 +6,18 @@ build-tool = fud2
 rule get-rsrc
   command = $build-tool get-rsrc $out
 
-python = python3
-build json-dat.py: get-rsrc
-rule hex-data
-  command = $python json-dat.py --from-json $in $out
-rule json-data
-  command = $python json-dat.py --to-json $out $in
-sim_data = /test/data.json
-datadir = sim_data
-build $datadir: hex-data $sim_data | json-dat.py
-rule sim-run
-  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
-cycle-limit = 500000000
-
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
-build /output.ext: calyx-with-flags /input.ext
+build /output.ext: calyx-cider /input.ext
 
 default /output.ext
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-to-cocotb-axi.snap b/fud2/tests/snapshots/tests__test@plan_calyx-to-cocotb-axi.snap
index 59b6022570..6b4775db0f 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-to-cocotb-axi.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-to-cocotb-axi.snap
@@ -8,21 +8,23 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 cocotb-makefile-dir = $calyx-base/yxi/axi-calyx/cocotb
 sim_data = /test/data.json
+cocotb-args = 
+rule make-cocotb
+  command = make DATA_PATH=$sim_data VERILOG_SOURCE=$in COCOTB_LOG_LEVEL=CRITICAL $cocotb-args > $out
 rule copy
   command = cp $in $out
-rule make-cocotb
-  command = make DATA_PATH=$sim_data VERILOG_SOURCE=$in COCOTB_LOG_LEVEL=CRITICAL > $out
 rule cleanup-cocotb
   command = sed -n '/Output:/,/make\[1\]/{/Output:/d;/make\[1\]/d;p}' $in > $out
 
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-to-firrtl.snap b/fud2/tests/snapshots/tests__test@plan_calyx-to-firrtl.snap
index 4f01a5d194..46d9e6cf96 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-to-firrtl.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-to-firrtl.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 yxi = $calyx-base/target/debug/yxi
 rule yxi
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-to-verilog.snap b/fud2/tests/snapshots/tests__test@plan_calyx-to-verilog.snap
index 2082b62aa2..e2a406909a 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-to-verilog.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-to-verilog.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 build /output.ext: calyx /input.ext
   backend = verilog
diff --git a/fud2/tests/snapshots/tests__test@plan_calyx-to-yxi.snap b/fud2/tests/snapshots/tests__test@plan_calyx-to-yxi.snap
index 50bf573b57..5c7b9a255b 100644
--- a/fud2/tests/snapshots/tests__test@plan_calyx-to-yxi.snap
+++ b/fud2/tests/snapshots/tests__test@plan_calyx-to-yxi.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 yxi = $calyx-base/target/debug/yxi
 rule yxi
diff --git a/fud2/tests/snapshots/tests__test@plan_cider.snap b/fud2/tests/snapshots/tests__test@plan_cider.snap
index e819ea9e11..56b2aee9dd 100644
--- a/fud2/tests/snapshots/tests__test@plan_cider.snap
+++ b/fud2/tests/snapshots/tests__test@plan_cider.snap
@@ -6,46 +6,36 @@ build-tool = fud2
 rule get-rsrc
   command = $build-tool get-rsrc $out
 
-python = python3
-build json-dat.py: get-rsrc
-rule hex-data
-  command = $python json-dat.py --from-json $in $out
-rule json-data
-  command = $python json-dat.py --to-json $out $in
-sim_data = /test/data.json
-datadir = sim_data
-build $datadir: hex-data $sim_data | json-dat.py
-rule sim-run
-  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
-cycle-limit = 500000000
-
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 cider-exe = $calyx-base/target/debug/cider
 cider-converter = $calyx-base/target/debug/cider-data-converter
-rule run-cider-debug
-  command = $cider-exe -l $calyx-base --data data.dump $in debug || true
-  pool = console
 converter-flags = 
 cider-flags = 
+data = --data data.dump
+sim_data = /test/data.json
+rule run-cider-debug
+  command = $cider-exe -l $calyx-base $data $cider-flags $in debug || true
+  pool = console
 rule run-cider
-  command = $cider-exe -l $calyx-base --data data.dump $cider-flags $in > $out
-rule dump-to-interp
-  command = $cider-converter --to cider $converter-flags $in > $out
+  command = $cider-exe -l $calyx-base $data $cider-flags $in > $out
 rule interp-to-dump
   command = $cider-converter --to json $converter-flags $in > $out
+rule dump-to-interp
+  command = $cider-converter --to cider $converter-flags $in > $out
 build data.dump: dump-to-interp $sim_data | $cider-converter
 
 build interp_out.dump: run-cider /input.ext | data.dump
-build /output.ext: interp-to-dump interp_out.dump | $sim_data $cider-converter
+build /output.ext: interp-to-dump interp_out.dump | $cider-converter
 
 default /output.ext
diff --git a/fud2/tests/snapshots/tests__test@plan_dat_to_jq.snap b/fud2/tests/snapshots/tests__test@plan_dat_to_jq.snap
new file mode 100644
index 0000000000..2c79949c8b
--- /dev/null
+++ b/fud2/tests/snapshots/tests__test@plan_dat_to_jq.snap
@@ -0,0 +1,19 @@
+---
+source: fud2/tests/tests.rs
+description: "emit plan: dat_to_jq"
+---
+build-tool = fud2
+rule get-rsrc
+  command = $build-tool get-rsrc $out
+
+jq.expr = .
+jq.exe = jq
+jq.flags = 
+rule dat_to_jq_rule_1
+  command = ${jq.exe} '${jq.expr}' ${jq.flags} $i0 > $o0
+build _dat_to_jq_rule_1.fake $o0: dat_to_jq_rule_1 $i0
+  i0 = /input.ext
+  o0 = /output.ext
+
+
+default /output.ext
diff --git a/fud2/tests/snapshots/tests__test@plan_debug.snap b/fud2/tests/snapshots/tests__test@plan_debug.snap
index 4258438c0a..e75b481960 100644
--- a/fud2/tests/snapshots/tests__test@plan_debug.snap
+++ b/fud2/tests/snapshots/tests__test@plan_debug.snap
@@ -6,45 +6,35 @@ build-tool = fud2
 rule get-rsrc
   command = $build-tool get-rsrc $out
 
-python = python3
-build json-dat.py: get-rsrc
-rule hex-data
-  command = $python json-dat.py --from-json $in $out
-rule json-data
-  command = $python json-dat.py --to-json $out $in
-sim_data = /test/data.json
-datadir = sim_data
-build $datadir: hex-data $sim_data | json-dat.py
-rule sim-run
-  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
-cycle-limit = 500000000
-
 build tb.sv: get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 cider-exe = $calyx-base/target/debug/cider
 cider-converter = $calyx-base/target/debug/cider-data-converter
-rule run-cider-debug
-  command = $cider-exe -l $calyx-base --data data.dump $in debug || true
-  pool = console
 converter-flags = 
 cider-flags = 
+data = --data data.dump
+sim_data = /test/data.json
+rule run-cider-debug
+  command = $cider-exe -l $calyx-base $data $cider-flags $in debug || true
+  pool = console
 rule run-cider
-  command = $cider-exe -l $calyx-base --data data.dump $cider-flags $in > $out
-rule dump-to-interp
-  command = $cider-converter --to cider $converter-flags $in > $out
+  command = $cider-exe -l $calyx-base $data $cider-flags $in > $out
 rule interp-to-dump
   command = $cider-converter --to json $converter-flags $in > $out
+rule dump-to-interp
+  command = $cider-converter --to cider $converter-flags $in > $out
 build data.dump: dump-to-interp $sim_data | $cider-converter
 
 build /output.ext: run-cider-debug /input.ext | data.dump
diff --git a/fud2/tests/snapshots/tests__test@plan_firrtl-with-primitives.snap b/fud2/tests/snapshots/tests__test@plan_firrtl-with-primitives.snap
index 344ae279b1..763eed3c5b 100644
--- a/fud2/tests/snapshots/tests__test@plan_firrtl-with-primitives.snap
+++ b/fud2/tests/snapshots/tests__test@plan_firrtl-with-primitives.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 gen-firrtl-primitives-script = $calyx-base/tools/firrtl/generate-firrtl-with-primitives.py
 rule generate-firrtl-with-primitives
diff --git a/fud2/tests/snapshots/tests__test@plan_primitive-uses.snap b/fud2/tests/snapshots/tests__test@plan_primitive-uses.snap
index b831f243c5..035ef64fb7 100644
--- a/fud2/tests/snapshots/tests__test@plan_primitive-uses.snap
+++ b/fud2/tests/snapshots/tests__test@plan_primitive-uses.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 build /output.ext: calyx /input.ext
   backend = primitive-uses
diff --git a/fud2/tests/snapshots/tests__test@plan_profiler.snap b/fud2/tests/snapshots/tests__test@plan_profiler.snap
new file mode 100644
index 0000000000..c1b862f538
--- /dev/null
+++ b/fud2/tests/snapshots/tests__test@plan_profiler.snap
@@ -0,0 +1,70 @@
+---
+source: fud2/tests/tests.rs
+description: "emit plan: profiler"
+---
+build-tool = fud2
+rule get-rsrc
+  command = $build-tool get-rsrc $out
+
+calyx-base = /test/calyx
+calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
+args = 
+rule calyx
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
+rule calyx-pass
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
+
+cells = cells.json
+tdcc-json = fsm.json
+passes = all
+component_cells = $calyx-base/target/debug/component_cells
+rule component-cells
+  command = $component_cells -l $calyx-base $in > $out
+parse-vcd-script = $calyx-base/tools/profiler/parse-vcd.py
+rule parse-vcd
+  command = python3 $parse-vcd-script $in $tdcc-json $cells summary.csv $out
+create-visuals-script = $calyx-base/tools/profiler/create-visuals.py
+rule create-visuals
+  command = python3 $create-visuals-script $in $cells timeline.json fsm-timeline.json $out fsm-flame.folded frequency.folded components.folded fsm-components.folded
+
+verilator = verilator
+cycle-limit = 500000000
+rule verilator-compile-standalone-tb
+  command = $verilator $in tb.sv --trace --binary --top-module toplevel -fno-inline -Mdir $out-dir
+rule verilator-compile-custom-tb
+  command = $verilator $in tb.sv memories.sv --trace --binary --top-module toplevel -fno-inline -Mdir $out-dir
+rule cp
+  command = cp $in $out
+
+python = python3
+build json-dat.py: get-rsrc
+rule hex-data
+  command = $python json-dat.py --from-json $in $out
+rule json-data
+  command = $python json-dat.py --to-json $out $in
+sim_data = /test/data.json
+datadir = sim_data
+build $datadir: hex-data $sim_data | json-dat.py
+rule sim-run
+  command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out
+cycle-limit = 500000000
+
+build $cells: component-cells /input.ext
+build instrumented.sv: calyx /input.ext
+  backend = verilog
+  args =  -p static-inline -p compile-static -p compile-repeat -p par-to-seq -p compile-invoke -p profiler-instrumentation -p $passes -x tdcc:dump-fsm-json=fsm.json
+build verilator-out/Vtoplevel: verilator-compile-standalone-tb instrumented.sv | tb.sv
+  out-dir = verilator-out
+build instrumented.exe: cp verilator-out/Vtoplevel
+build sim.log instrumented.vcd: sim-run instrumented.exe $datadir
+  bin = instrumented.exe
+  args = +NOTRACE=0 +OUT=instrumented.vcd
+build elems-profiled.json: parse-vcd instrumented.vcd
+build flamegraph.folded: create-visuals elems-profiled.json | $cells
+build /output.ext: produce-flame-graph flamegraph.folded
+
+default /output.ext
diff --git a/fud2/tests/snapshots/tests__test@plan_xo.snap b/fud2/tests/snapshots/tests__test@plan_xo.snap
index 3255dbca6b..2859c6ab6b 100644
--- a/fud2/tests/snapshots/tests__test@plan_xo.snap
+++ b/fud2/tests/snapshots/tests__test@plan_xo.snap
@@ -8,14 +8,15 @@ rule get-rsrc
 
 calyx-base = /test/calyx
 calyx-exe = $calyx-base/target/debug/calyx
+calyx-lib-path = $calyx-base
 args = 
 rule calyx
-  command = $calyx-exe -l $calyx-base -b $backend $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -b $backend $args $in > $out
 rule calyx-pass
-  command = $calyx-exe -l $calyx-base -p $pass $args $in > $out
-flags = -p none
-rule calyx-with-flags
-  command = $calyx-exe -l $calyx-base $flags $args $in > $out
+  command = $calyx-exe -l $calyx-lib-path -p $pass $args $in > $out
+cider-calyx-passes = -p none
+rule calyx-cider
+  command = $calyx-exe -l $calyx-lib-path $cider-calyx-passes $args $in > $out
 
 vivado-dir = /test/xilinx/vivado
 vitis-dir = /test/xilinx/vitis
diff --git a/fud2/tests/tests.rs b/fud2/tests/tests.rs
index 3c4f32545e..ab334dab27 100644
--- a/fud2/tests/tests.rs
+++ b/fud2/tests/tests.rs
@@ -10,14 +10,6 @@ use fud_core::{
 };
 use itertools::Itertools;
 
-#[cfg(not(feature = "migrate_to_scripts"))]
-fn test_driver() -> Driver {
-    let mut bld = DriverBuilder::new("fud2");
-    fud2::build_driver(&mut bld);
-    bld.build()
-}
-
-#[cfg(feature = "migrate_to_scripts")]
 fn test_driver() -> Driver {
     let mut bld = DriverBuilder::new("fud2-plugins");
     let config = figment::Figment::new();
@@ -99,6 +91,7 @@ impl InstaTest for Plan {
             .merge(("xilinx.vitis", "/test/xilinx/vitis"))
             .merge(("xilinx.xrt", "/test/xilinx/xrt"))
             .merge(("dahlia", "/test/bin/dahlia"))
+            .merge(("jq.expr", "."))
             .merge(("c0", "v1"));
         let run = Run::with_config(driver, self, config);
         let mut buf = vec![];
diff --git a/interp/tests/complex/unsigned-dot-product.futil b/interp/tests/complex/unsigned-dot-product.futil
index 7f7117b7c9..f30343e2ee 100644
--- a/interp/tests/complex/unsigned-dot-product.futil
+++ b/interp/tests/complex/unsigned-dot-product.futil
@@ -17,7 +17,7 @@ component main() -> () {
     @external mult = std_mult_pipe(32);
   }
   wires {
-    comb group is_less_than<"static"=0> {
+    comb group is_less_than {
       lt0.left = counter.out;
       lt0.right = 3'd4;
     } // Control segment for `counter` < `4`.
@@ -70,13 +70,13 @@ component main() -> () {
       initialize_mem_3[done] = mem0.done & mem1.done ? 1'd1;
     }
 
-    group initialize_counter<"static"=1> {
+    group initialize_counter {
       counter.in = 3'd0;
       counter.write_en = 1'd1;
       initialize_counter[done] = counter.done;
     }
 
-    group incr_counter<"static"=1> {
+    group incr_counter {
       counter.write_en = 1'd1;
       add0.left = counter.out;
       add0.right = 3'd1; // Increment by 1.
@@ -100,7 +100,7 @@ component main() -> () {
       mul[done] = t.done;
     }
 
-    group add<"static"=1> {
+    group add {
       add1.left = t.out;
       add1.right = r_2.out;
       r_2.write_en = 1'd1;
diff --git a/interp/tests/control/while.futil b/interp/tests/control/while.futil
index 92c072e641..9e10b15a6a 100644
--- a/interp/tests/control/while.futil
+++ b/interp/tests/control/while.futil
@@ -19,7 +19,7 @@ component main() -> () {
       cond[done] = lt_reg.done;
     }
 
-    group incr<"static"=1> {
+    group incr {
       i.write_en = 1'b1;
       i.write_data = add.out;
       i.addr0 = 1'd0;
diff --git a/interp/tests/primitives/mem.futil b/interp/tests/primitives/mem.futil
index 951a48503d..0800c6d230 100644
--- a/interp/tests/primitives/mem.futil
+++ b/interp/tests/primitives/mem.futil
@@ -8,14 +8,14 @@ component main() -> () {
   }
 
   wires {
-    group write<"static"=1> {
+    group write {
       mem.write_en = 1'd1;
       mem.addr0 = 1'd0;
       mem.write_data = 32'd9;
       write[done] = mem.done;
     }
 
-    group read<"static"=1> {
+    group read{
       mem.addr0 = 1'd0;
       reg0.write_en = 1'd1;
       reg0.in = mem.read_data;
diff --git a/interp/tests/runt.toml b/interp/tests/runt.toml
index 18c2967faf..b2bf97f7c9 100644
--- a/interp/tests/runt.toml
+++ b/interp/tests/runt.toml
@@ -1,11 +1,13 @@
 ver = "0.4.1"
 
 # Check basic functionality of the interpreter
+# note that due to error printing in fud2 we can't run the error tests on CI
+# through it, so this suite has direct invocations instead
 [[tests]]
 name = "unit"
 paths = ["unit/*.futil"]
 cmd = """
-../../target/debug/cider {} -l ../../ --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+../../target/debug/cider {} -l ../../ --dump-registers | ../../target/debug/cider-data-converter --to json
 """
 timeout = 10
 expect_dir = "unit"
@@ -14,7 +16,10 @@ expect_dir = "unit"
 name = "multi-comp"
 paths = ["multi-comp/*.futil"]
 cmd = """
-../../target/debug/cider {} -l ../../ --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+fud2 --from calyx --to dat \
+         --through cider \
+         -s cider.flags="--dump-registers" \
+         {}
 """
 timeout = 10
 
@@ -32,7 +37,10 @@ name = "complex"
 paths = ["complex/*.futil"]
 
 cmd = """
-../../target/debug/cider {} -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+fud2 --from calyx --to dat \
+         --through cider \
+         -s cider.flags="--dump-registers" \
+         {}
 """
 timeout = 10
 expect_dir = "complex"
@@ -41,7 +49,11 @@ expect_dir = "complex"
 name = "primitives"
 paths = ["primitives/*.futil"]
 cmd = """
-../../target/debug/cider {} -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json | jq  --sort-keys
+fud2 --from calyx --to dat \
+         --through cider \
+         -s calyx.args="--log off" \
+         -s cider.flags="--dump-registers" \
+         {}
 """
 timeout = 10
 expect_dir = "primitives"
@@ -50,7 +62,7 @@ expect_dir = "primitives"
 name = "par to seq"
 paths = ["control/par_reg.futil", "control/par_mem.futil"]
 cmd = """
-../../target/debug/calyx {} -p par-to-seq -l ../../ | ../../target/debug/cider -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+../../target/debug/calyx {} -p par-to-seq -l ../../ | ../../target/debug/cider -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json
 """
 timeout = 10
 expect_dir = "par-to-seq"
@@ -59,7 +71,10 @@ expect_dir = "par-to-seq"
 name = "control"
 paths = ["control/*.futil", "control/iteration/*.futil"]
 cmd = """
-../../target/debug/cider {} -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+fud2 --from calyx --to dat \
+         --through cider \
+         -s cider.flags="--dump-registers" \
+         {}
 """
 timeout = 10
 expect_dir = "control"
@@ -68,7 +83,7 @@ expect_dir = "control"
 name = "invoke"
 paths = ["control/invoke/*.futil"]
 cmd = """
-fud2 {} --from calyx --to dat --through cider -s sim.data={}.data -s calyx.args="--log off" | jq --sort-keys
+fud2 {} --from calyx --to dat --through cider -s sim.data={}.data -s calyx.args="--log off"
 """
 timeout = 10
 
@@ -76,14 +91,16 @@ timeout = 10
 name = "invoke compiled"
 paths = ["control/invoke/*.futil"]
 cmd = """
-fud2 {} --from calyx --to dat --through cider -s calyx.flags=" -p compile-invoke" -s sim.data={}.data -s calyx.args="--log off" | jq --sort-keys
+fud2 {} --from calyx --to dat --through cider \
+ -s cider.calyx-passes=" -p compile-invoke" \
+ -s sim.data={}.data -s calyx.args="--log off"
 """
 
 [[tests]]
 name = "fully structural"
 paths = ["control/*.futil", "control/iteration/*.futil"]
 cmd = """
-../../target/debug/calyx {} -d pre-opt -d post-opt -p simplify-with-control -l ../../ --log off | ../../target/debug/cider -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys
+../../target/debug/calyx {} -d pre-opt -d post-opt -p simplify-with-control -l ../../ --log off | ../../target/debug/cider -l ../../  --dump-registers | ../../target/debug/cider-data-converter --to json
 """
 expect_dir = "control"
 # timeout = 10
@@ -105,7 +122,7 @@ fud2 --from calyx --to dat \
          --through cider \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
-         {} | jq --sort-keys
+         {}
 """
 
 [[tests]]
@@ -115,19 +132,30 @@ cmd = """
 ../../target/debug/cider {} -l ../../  --check-data-race
 """
 
-# [[tests]]
-# name = "correctness lowered"
-# paths = ["../../tests/correctness/*.futil"]
-# cmd = """
-# fud2 --from calyx --to dat \
-#          --through cider \
-#          -s sim.data={}.data \
-#          -s calyx.args="--log off" \
-#          -s calyx.flags="-p all" \
-#          {} | jq --sort-keys
-# """
-# timeout = 60
+[[tests]]
+name = "correctness lowered"
+paths = ["../../tests/correctness/*.futil"]
+cmd = """
+fud2 --from calyx --to dat \
+         --through cider \
+         -s sim.data={}.data \
+         -s calyx.args="--log off" \
+         -s cider.calyx-passes="-p all" \
+         -s cider.flags="--no-verify" \
+         {}
+"""
+timeout = 60
 
+[[tests]]
+name = "correctness ieee754-float"
+paths = ["../../tests/correctness/ieee754-float/*.futil"]
+cmd = """
+fud2 --from calyx --to dat \
+         --through cider \
+         -s sim.data={}.data \
+         -s calyx.args="--log off" \
+         {}
+"""
 
 [[tests]]
 name = "correctness ref cells"
@@ -137,7 +165,7 @@ fud2 --from calyx --to dat \
          --through cider \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
-         {} | jq --sort-keys
+         {}
 """
 
 [[tests]]
@@ -148,8 +176,8 @@ fud2 --from calyx --to dat \
          --through cider \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
-         -s calyx.flags=" -p compile-invoke" \
-         {} | jq --sort-keys
+         -s cider.calyx-passes=" -p compile-invoke" \
+         {}
 """
 
 [[tests]]
@@ -165,7 +193,7 @@ fud2 --from calyx --to dat \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
          -s cider.converter-flags="-r --legacy-quotes" \
-         {} | jq --sort-keys
+         {}
 """
 
 [[tests]]
@@ -176,7 +204,7 @@ fud2 --from calyx --to dat \
       --through cider \
       -s calyx.args="--log off" \
       -s sim.data={}.data \
-      {} | jq --sort-keys
+      {}
 """
 
 [[tests]]
@@ -187,7 +215,7 @@ fud2 --from calyx --to dat \
          --through cider \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
-         {} | jq --sort-keys
+         {}
 """
 
 [[tests]]
@@ -198,7 +226,7 @@ fud2 --from dahlia --to dat \
          --through cider \
          -s sim.data={}.data \
          -s calyx.args="--log off" \
-         {} | jq --sort-keys
+         {}
 """
 timeout = 180
 
@@ -223,7 +251,6 @@ timeout = 180
 #       -s interpreter.flags "--raw " \
 #       -s verilog.data {}.data \
 #       -s jq.expr ".main" \
-#       -s jq.flags "--sort-keys " \
 #       {} -q
 # """
 # expect_dir = "tests/ntt-results/"
@@ -239,7 +266,7 @@ timeout = 180
 #             --through cider \
 #             -s sim.data={}.data \
 #             -s calyx.args="--log off" \
-#        | jq --sort-keys
+#             {}
 # """
 
 # [[tests]]
@@ -250,6 +277,5 @@ timeout = 180
 #             --through interpreter-out \
 #             -s interpreter.flags "--raw " \
 #             -s verilog.data {}.data \
-#             -s jq.flags "--sort-keys " \
 #             -s jq.expr ".main"
 # """
diff --git a/interp/tests/unit/uninitialized-port-returns-zero.expect b/interp/tests/unit/uninitialized-port-returns-zero.expect
index 40f7d1ff70..7ed6839994 100644
--- a/interp/tests/unit/uninitialized-port-returns-zero.expect
+++ b/interp/tests/unit/uninitialized-port-returns-zero.expect
@@ -1,3 +1,5 @@
+---CODE---
+1
 ---STDERR---
 Error: Attempted to write an undefined value to register or memory named "main.reg0"
 Error: Input is not a valid data dump
diff --git a/interp/tests/unit/uninitialized-port-returns-zero.futil b/interp/tests/unit/uninitialized-port-returns-zero.futil
index 26626239f4..cfff26acfd 100644
--- a/interp/tests/unit/uninitialized-port-returns-zero.futil
+++ b/interp/tests/unit/uninitialized-port-returns-zero.futil
@@ -4,8 +4,8 @@ import "primitives/memories/comb.futil";
 
 component main() -> () {
   cells {
-    add = std_add(4);
-    @external reg0 = std_reg(4);
+    @data add = std_add(4);
+    @external @data reg0 = std_reg(4);
   }
 
   wires {
diff --git a/tests/backend/verilog/memory-with-external-attribute.expect b/tests/backend/verilog/memory-with-external-attribute.expect
index 1d01427827..fcab3307a1 100644
--- a/tests/backend/verilog/memory-with-external-attribute.expect
+++ b/tests/backend/verilog/memory-with-external-attribute.expect
@@ -453,7 +453,7 @@ module std_bit_slice #(
    input wire logic [IN_WIDTH-1:0] in,
    output logic [OUT_WIDTH-1:0] out
 );
-    assign out = in[END_IDX:START_IDX];
+  assign out = in[END_IDX:START_IDX];
 
   `ifdef VERILATOR
     always_comb begin
@@ -469,6 +469,74 @@ module std_bit_slice #(
 
 endmodule
 
+module std_skid_buffer #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic i_valid,
+    input wire logic i_ready,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic o_valid,
+    output logic o_ready
+);
+  logic [WIDTH-1:0] val;
+  logic bypass_rg;
+  always @(posedge clk) begin
+    // Reset  
+    if (reset) begin      
+      // Internal Registers
+      val <= '0;     
+      bypass_rg <= 1'b1;
+    end   
+    // Out of reset
+    else begin      
+      // Bypass state      
+      if (bypass_rg) begin         
+        if (!i_ready && i_valid) begin
+          val <= in;          // Data skid happened, store to buffer
+          bypass_rg <= 1'b0;  // To skid mode  
+        end 
+      end 
+      // Skid state
+      else begin         
+        if (i_ready) begin
+          bypass_rg <= 1'b1;  // Back to bypass mode           
+        end
+      end
+    end
+  end
+
+  assign o_ready = bypass_rg;
+  assign out = bypass_rg ? in : val;
+  assign o_valid = bypass_rg ? i_valid : 1'b1;
+endmodule
+
+module std_bypass_reg #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic write_en,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic done
+);
+  logic [WIDTH-1:0] val;
+  assign out = write_en ? in : val;
+
+  always_ff @(posedge clk) begin
+    if (reset) begin
+      val <= 0;
+      done <= 0;
+    end else if (write_en) begin
+      val <= in;
+      done <= 1'd1;
+    end else done <= 1'd0;
+  end
+endmodule
+
 module undef #(
     parameter WIDTH = 32
 ) (
diff --git a/tests/correctness/ieee754-float/parse-and-print.expect b/tests/correctness/ieee754-float/parse-and-print.expect
new file mode 100644
index 0000000000..54d617922a
--- /dev/null
+++ b/tests/correctness/ieee754-float/parse-and-print.expect
@@ -0,0 +1,9 @@
+{
+  "inp": [
+    15.1239
+  ],
+  "out": [
+    15.1239,
+    0.56
+  ]
+}
diff --git a/tests/correctness/ieee754-float/parse-and-print.futil b/tests/correctness/ieee754-float/parse-and-print.futil
new file mode 100644
index 0000000000..6bf4ad77a8
--- /dev/null
+++ b/tests/correctness/ieee754-float/parse-and-print.futil
@@ -0,0 +1,38 @@
+import "primitives/core.futil";
+import "primitives/memories/seq.futil";
+import "primitives/float.futil";
+
+component main() -> () {
+    cells {
+        f = std_float_const(0, 32, 0.56);
+        @external inp = seq_mem_d1(32, 1, 1);
+        @external out = seq_mem_d1(32, 2, 2);
+    }
+
+    wires {
+        group set_in {
+            inp.addr0 = 1'd0;
+            inp.content_en = 1'd1;
+            set_in[done] = inp.done;
+        }
+        group write_from_in {
+            out.content_en = 1'd1;
+            out.addr0 = 2'd0;
+            out.write_en = 1'd1;
+            out.write_data = inp.read_data;
+            write_from_in[done] = out.done;
+        }
+        group write_from_const {
+            out.content_en = 1'd1;
+            out.addr0 = 2'd1;
+            out.write_en = 1'd1;
+            out.write_data = f.out;
+            write_from_const[done] = out.done;
+        }
+    }
+    control {
+        set_in;
+        write_from_in;
+        write_from_const;
+    }
+}
\ No newline at end of file
diff --git a/tests/correctness/ieee754-float/parse-and-print.futil.data b/tests/correctness/ieee754-float/parse-and-print.futil.data
new file mode 100644
index 0000000000..1d1434a948
--- /dev/null
+++ b/tests/correctness/ieee754-float/parse-and-print.futil.data
@@ -0,0 +1,23 @@
+{
+    "inp": {
+        "data": [
+            15.1239
+        ],
+        "format": {
+            "numeric_type": "ieee754_float",
+            "is_signed": false,
+            "width": 32
+        }
+    },
+    "out": {
+        "data": [
+            0.0,
+            0.0
+        ],
+        "format": {
+            "numeric_type": "ieee754_float",
+            "is_signed": false,
+            "width": 32
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/correctness/skid-buffers/bypass-reg.expect b/tests/correctness/skid-buffers/bypass-reg.expect
new file mode 100644
index 0000000000..452b06b031
--- /dev/null
+++ b/tests/correctness/skid-buffers/bypass-reg.expect
@@ -0,0 +1,26 @@
+{
+  "in": [
+    1,
+    2,
+    3,
+    4,
+    5,
+    6,
+    7,
+    8,
+    9,
+    10
+  ],
+  "out": [
+    1,
+    2,
+    3,
+    4,
+    5,
+    5,
+    5,
+    8,
+    9,
+    10
+  ]
+}
diff --git a/tests/correctness/skid-buffers/bypass-reg.futil b/tests/correctness/skid-buffers/bypass-reg.futil
new file mode 100644
index 0000000000..5022dd3fef
--- /dev/null
+++ b/tests/correctness/skid-buffers/bypass-reg.futil
@@ -0,0 +1,64 @@
+import "primitives/core.futil";
+import "primitives/memories/seq.futil";
+
+component main() -> () {
+  cells {
+    @external in = seq_mem_d1(32, 10, 4);
+    b = std_bypass_reg(32);
+    i_reg = std_reg(4);
+    add0 = std_add(4);
+    neq0 = std_neq(4);
+    neq1 = std_neq(4);
+    and0 = std_and(1);
+    @external out = seq_mem_d1(32, 10, 4);
+  }
+
+  wires {
+    static<1> group init {
+      i_reg.write_en = 1'b1;
+      i_reg.in = 4'b0;
+    }
+
+    static<1> group incr {
+      i_reg.write_en = 1'b1;
+      i_reg.in = add0.out;
+      add0.left = i_reg.out;
+      add0.right = 4'b1;
+    }
+
+    static<1> group load {
+      in.addr0 = i_reg.out;
+      in.content_en = 1'b1;
+    }
+
+    static<1> group bypass {
+      neq0.left = i_reg.out;
+      neq0.right = 4'd5;
+      neq1.left = i_reg.out;
+      neq1.right = 4'd6;
+      and0.left = neq0.out;
+      and0.right = neq1.out;
+      b.write_en = and0.out;
+      b.in = in.read_data;
+    }
+
+    static<1> group store {
+      out.write_en = 1'b1;
+      out.content_en = 1'b1;
+      out.addr0 = i_reg.out;
+      out.write_data = b.out;
+    }
+  }
+
+  control {
+    init;
+    static repeat 10 {
+      static seq {
+        load;
+        bypass;
+        store;
+        incr;
+      }
+    }
+  }
+}
diff --git a/tests/correctness/skid-buffers/bypass-reg.futil.data b/tests/correctness/skid-buffers/bypass-reg.futil.data
new file mode 100644
index 0000000000..01e3629795
--- /dev/null
+++ b/tests/correctness/skid-buffers/bypass-reg.futil.data
@@ -0,0 +1,40 @@
+{
+  "in": {
+    "data": [
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      10
+    ],
+    "format": {
+      "numeric_type": "bitnum",
+      "is_signed": false,
+      "width": 32
+    }
+  },
+  "out": {
+    "data": [
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0
+    ],
+    "format": {
+      "numeric_type": "bitnum",
+      "is_signed": false,
+      "width": 32
+    }
+  }
+}
diff --git a/tests/correctness/skid-buffers/skid-buffer.expect b/tests/correctness/skid-buffers/skid-buffer.expect
new file mode 100644
index 0000000000..452b06b031
--- /dev/null
+++ b/tests/correctness/skid-buffers/skid-buffer.expect
@@ -0,0 +1,26 @@
+{
+  "in": [
+    1,
+    2,
+    3,
+    4,
+    5,
+    6,
+    7,
+    8,
+    9,
+    10
+  ],
+  "out": [
+    1,
+    2,
+    3,
+    4,
+    5,
+    5,
+    5,
+    8,
+    9,
+    10
+  ]
+}
diff --git a/tests/correctness/skid-buffers/skid-buffer.futil b/tests/correctness/skid-buffers/skid-buffer.futil
new file mode 100644
index 0000000000..c41f430165
--- /dev/null
+++ b/tests/correctness/skid-buffers/skid-buffer.futil
@@ -0,0 +1,67 @@
+import "primitives/core.futil";
+import "primitives/memories/seq.futil";
+
+component main() -> () {
+  cells {
+    @external in = seq_mem_d1(32, 10, 4);
+    b = std_skid_buffer(32);
+    i_reg = std_reg(4);
+    add0 = std_add(4);
+    neq0 = std_neq(4);
+    neq1 = std_neq(4);
+    and0 = std_and(1);
+    @external out = seq_mem_d1(32, 10, 4);
+  }
+
+  wires {
+    neq0.left = i_reg.out;
+    neq0.right = 4'd5;
+    neq1.left = i_reg.out;
+    neq1.right = 4'd6;
+    and0.left = neq0.out;
+    and0.right = neq1.out;
+
+    b.i_ready = and0.out;
+    b.i_valid = 1'b1;
+
+    static<1> group init {
+      i_reg.write_en = 1'b1;
+      i_reg.in = 4'b0;
+    }
+
+    static<1> group incr {
+      i_reg.write_en = 1'b1;
+      i_reg.in = add0.out;
+      add0.left = i_reg.out;
+      add0.right = 4'b1;
+    }
+
+    static<1> group load {
+      in.addr0 = i_reg.out;
+      in.content_en = 1'b1;
+    }
+
+    static<1> group bypass {
+      b.in = in.read_data;
+    }
+
+    static<1> group store {
+      out.write_en = 1'b1;
+      out.content_en = 1'b1;
+      out.addr0 = i_reg.out;
+      out.write_data = b.out;
+    }
+  }
+
+  control {
+    init;
+    static repeat 10 {
+      static seq {
+        load;
+        bypass;
+        store;
+        incr;
+      }
+    }
+  }
+}
diff --git a/tests/correctness/skid-buffers/skid-buffer.futil.data b/tests/correctness/skid-buffers/skid-buffer.futil.data
new file mode 100644
index 0000000000..01e3629795
--- /dev/null
+++ b/tests/correctness/skid-buffers/skid-buffer.futil.data
@@ -0,0 +1,40 @@
+{
+  "in": {
+    "data": [
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      10
+    ],
+    "format": {
+      "numeric_type": "bitnum",
+      "is_signed": false,
+      "width": 32
+    }
+  },
+  "out": {
+    "data": [
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0,
+      0
+    ],
+    "format": {
+      "numeric_type": "bitnum",
+      "is_signed": false,
+      "width": 32
+    }
+  }
+}
diff --git a/tests/import/a.expect b/tests/import/a.expect
index c348a36e76..5db2002bcb 100644
--- a/tests/import/a.expect
+++ b/tests/import/a.expect
@@ -28,6 +28,8 @@ extern "<ROOT>/calyx/primitives/core.sv" {
   comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1);
   comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH);
   comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH);
+  primitive std_skid_buffer<"share"=1>[WIDTH](@data in: WIDTH, i_valid: 1, i_ready: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, o_valid: 1, o_ready: 1);
+  primitive std_bypass_reg<"share"=1>[WIDTH](@data in: WIDTH, @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, @done done: 1);
 }
 primitive undef<"share"=1>[WIDTH]() -> (out: WIDTH) {
   assign out = 'x;
diff --git a/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect b/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect
index a39b1c5179..3b0d36f229 100644
--- a/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect
+++ b/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect
@@ -335,7 +335,7 @@ module std_fp_div_pipe #(
         running <= running;
     end
 
-    always_comb begin
+    always @* begin
       if (acc >= {1'b0, right}) begin
         acc_next = acc - right;
         {acc_next, quotient_next} = {acc_next[WIDTH-1:0], quotient, 1'b1};
@@ -1180,7 +1180,7 @@ module std_bit_slice #(
    input wire logic [IN_WIDTH-1:0] in,
    output logic [OUT_WIDTH-1:0] out
 );
-    assign out = in[END_IDX:START_IDX];
+  assign out = in[END_IDX:START_IDX];
 
   `ifdef VERILATOR
     always_comb begin
@@ -1196,6 +1196,74 @@ module std_bit_slice #(
 
 endmodule
 
+module std_skid_buffer #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic i_valid,
+    input wire logic i_ready,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic o_valid,
+    output logic o_ready
+);
+  logic [WIDTH-1:0] val;
+  logic bypass_rg;
+  always @(posedge clk) begin
+    // Reset  
+    if (reset) begin      
+      // Internal Registers
+      val <= '0;     
+      bypass_rg <= 1'b1;
+    end   
+    // Out of reset
+    else begin      
+      // Bypass state      
+      if (bypass_rg) begin         
+        if (!i_ready && i_valid) begin
+          val <= in;          // Data skid happened, store to buffer
+          bypass_rg <= 1'b0;  // To skid mode  
+        end 
+      end 
+      // Skid state
+      else begin         
+        if (i_ready) begin
+          bypass_rg <= 1'b1;  // Back to bypass mode           
+        end
+      end
+    end
+  end
+
+  assign o_ready = bypass_rg;
+  assign out = bypass_rg ? in : val;
+  assign o_valid = bypass_rg ? i_valid : 1'b1;
+endmodule
+
+module std_bypass_reg #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic write_en,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic done
+);
+  logic [WIDTH-1:0] val;
+  assign out = write_en ? in : val;
+
+  always_ff @(posedge clk) begin
+    if (reset) begin
+      val <= 0;
+      done <= 0;
+    end else if (write_en) begin
+      val <= in;
+      done <= 1'd1;
+    end else done <= 1'd0;
+  end
+endmodule
+
 module undef #(
     parameter WIDTH = 32
 ) (
diff --git a/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect b/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect
index d6f77e56a9..ac69b673cc 100644
--- a/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect
+++ b/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect
@@ -327,7 +327,7 @@ module std_fp_div_pipe #(
         running <= running;
     end
 
-    always_comb begin
+    always @* begin
       if (acc >= {1'b0, right}) begin
         acc_next = acc - right;
         {acc_next, quotient_next} = {acc_next[WIDTH-1:0], quotient, 1'b1};
@@ -1172,7 +1172,7 @@ module std_bit_slice #(
    input wire logic [IN_WIDTH-1:0] in,
    output logic [OUT_WIDTH-1:0] out
 );
-    assign out = in[END_IDX:START_IDX];
+  assign out = in[END_IDX:START_IDX];
 
   `ifdef VERILATOR
     always_comb begin
@@ -1188,6 +1188,74 @@ module std_bit_slice #(
 
 endmodule
 
+module std_skid_buffer #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic i_valid,
+    input wire logic i_ready,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic o_valid,
+    output logic o_ready
+);
+  logic [WIDTH-1:0] val;
+  logic bypass_rg;
+  always @(posedge clk) begin
+    // Reset  
+    if (reset) begin      
+      // Internal Registers
+      val <= '0;     
+      bypass_rg <= 1'b1;
+    end   
+    // Out of reset
+    else begin      
+      // Bypass state      
+      if (bypass_rg) begin         
+        if (!i_ready && i_valid) begin
+          val <= in;          // Data skid happened, store to buffer
+          bypass_rg <= 1'b0;  // To skid mode  
+        end 
+      end 
+      // Skid state
+      else begin         
+        if (i_ready) begin
+          bypass_rg <= 1'b1;  // Back to bypass mode           
+        end
+      end
+    end
+  end
+
+  assign o_ready = bypass_rg;
+  assign out = bypass_rg ? in : val;
+  assign o_valid = bypass_rg ? i_valid : 1'b1;
+endmodule
+
+module std_bypass_reg #(
+    parameter WIDTH = 32
+)(
+    input wire logic [WIDTH-1:0] in,
+    input wire logic write_en,
+    input wire logic clk,
+    input wire logic reset,
+    output logic [WIDTH-1:0] out,
+    output logic done
+);
+  logic [WIDTH-1:0] val;
+  assign out = write_en ? in : val;
+
+  always_ff @(posedge clk) begin
+    if (reset) begin
+      val <= 0;
+      done <= 0;
+    end else if (write_en) begin
+      val <= in;
+      done <= 1'd1;
+    end else done <= 1'd0;
+  end
+endmodule
+
 module undef #(
     parameter WIDTH = 32
 ) (