diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1fcd4e4769..e689a741d3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,15 +17,15 @@ 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 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: 1.66.0 - override: true - components: rustfmt, clippy + components: rustfmt, clippy - name: Build source documentation uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 33263866cf..a5077b678f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -13,10 +13,8 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Install stable - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: 1.76.0 - override: true components: rustfmt, clippy - name: Check formatting uses: actions-rs/cargo@v1 diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index a569ddd371..da58255702 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -14,8 +14,6 @@ jobs: - uses: actions/setup-node@v4 - name: Install Rust stable uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - name: Install wasm-pack and wasm-bindgen-cli uses: taiki-e/install-action@wasm-pack with: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 421a82a0bb..1a195e4c5b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,10 +18,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Install Rust stable + - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - name: Build calyx dev run: cargo build - name: Check calyx build @@ -109,33 +107,37 @@ jobs: git checkout -f $GITHUB_SHA git clean -fd + - name: Checkout toolchain configuration + uses: actions/checkout@v4 + with: + sparse-checkout: | + rust-toolchain.toml + sparse-checkout-cone-mode: false + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Build uses: actions-rs/cargo@v1 with: command: build - args: --all --all-features --manifest-path /home/calyx/interp/Cargo.toml - - - name: Set location of cider binary - run: | - fud c stages.interpreter.exec /home/calyx/target/debug/cider + args: --all --all-features --manifest-path /home/calyx/cider/Cargo.toml - name: Runt tests - working-directory: /home/calyx/interp/tests + working-directory: /home/calyx/cider/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 with: command: test - args: --manifest-path /home/calyx/interp/Cargo.toml + args: --manifest-path /home/calyx/cider/Cargo.toml - name: Source code doc tests uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path /home/calyx/interp/Cargo.toml --doc + args: --manifest-path /home/calyx/cider/Cargo.toml --doc compiler: name: Test Compiler @@ -161,6 +163,16 @@ jobs: git checkout -f $GITHUB_SHA git clean -fd + - name: Checkout toolchain configuration + uses: actions/checkout@v4 + with: + sparse-checkout: | + rust-toolchain.toml + sparse-checkout-cone-mode: false + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install calyx-py, MrXL, and queues working-directory: /home/calyx run: | @@ -228,6 +240,16 @@ jobs: git checkout -f $GITHUB_SHA git clean -fd + - name: Checkout toolchain configuration + uses: actions/checkout@v4 + with: + sparse-checkout: | + rust-toolchain.toml + sparse-checkout-cone-mode: false + + - name: Install Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Build uses: actions-rs/cargo@v1 with: diff --git a/.gitignore b/.gitignore index 5c6840dac6..2356e62366 100644 --- a/.gitignore +++ b/.gitignore @@ -61,8 +61,8 @@ tools/btor2/btor2i/build/ tools/profiler/data tools/profiler/meta-logs tools/profiler/fg-tmp -tools/profiler/handmade-flame-graphs/*/*.svg -tools/profiler/inst-check-tmp +*.svg +*.folded temp/ @@ -75,3 +75,5 @@ frontends/queues/tests/**/*.expect # emacs *~ + +!docs/dev/assets/*.png diff --git a/Cargo.lock b/Cargo.lock index 1fd61fa404..3037b460b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.7.8" @@ -66,54 +72,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 +97,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -175,7 +133,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -206,7 +164,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -217,14 +175,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "baa" -version = "0.6.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfafaa7f46ae31982d3e55dad223ebb884a5da626aba403c66673ce9d1831f53" +checksum = "40d4492a0c39a5286a28e99628afceb95566c04fe265b1cabac90ef11bae34ab" dependencies = [ "fraction", "num-bigint", "serde", - "smallvec", ] [[package]] @@ -237,7 +194,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.2", "object", "rustc-demangle", ] @@ -248,29 +205,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" @@ -298,18 +232,6 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -320,36 +242,33 @@ dependencies = [ ] [[package]] -name = "btor2i" -version = "0.1.0" +name = "bon" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97493a391b4b18ee918675fb8663e53646fd09321c58b46afa04e8ce2499c869" dependencies = [ - "bitvec", - "btor2tools", - "clap 4.4.18", - "num-bigint", - "num-integer", - "num-traits", - "tempfile", - "thiserror", + "bon-macros", + "rustversion", ] [[package]] -name = "btor2tools" -version = "1.1.0" -source = "git+https://github.com/obhalerao/btor2tools.rs#a01b2ebc85ee4489860069b196b2ab886d06f43a" +name = "bon-macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2af3eac944c12cdf4423eab70d310da0a8e5851a18ffb192c0a5e3f7ae1663" dependencies = [ - "btor2tools-sys", - "thiserror", + "darling 0.20.10", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] -name = "btor2tools-sys" -version = "1.1.0" -source = "git+https://github.com/obhalerao/btor2tools-sys#e5d1c44220cb5a02b30b538c5ade70102109904a" -dependencies = [ - "bindgen", - "copy_dir", -] +name = "boxcar" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed" [[package]] name = "bumpalo" @@ -509,6 +428,7 @@ name = "calyx-utils" version = "0.7.1" dependencies = [ "atty", + "boxcar", "itertools 0.11.0", "petgraph", "serde", @@ -554,15 +474,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" @@ -611,20 +522,56 @@ dependencies = [ "half 2.4.1", ] +[[package]] +name = "cider" +version = "0.1.1" +dependencies = [ + "ahash 0.8.10", + "argh", + "baa", + "bon", + "calyx-frontend", + "calyx-ir", + "calyx-opt", + "calyx-utils", + "ciborium", + "fraction", + "fst-writer", + "itertools 0.11.0", + "num-bigint", + "num-traits", + "owo-colors", + "pest", + "pest_consume", + "pest_derive", + "petgraph", + "proptest", + "rustyline", + "serde", + "serde_json", + "serde_with 1.14.0", + "slog", + "slog-async", + "slog-term", + "smallvec", + "thiserror 1.0.64", +] + [[package]] name = "cider-dap" version = "0.1.0" dependencies = [ "argh", + "baa", + "cider", "dap", - "interp", "owo-colors", "serde", "serde_json", "slog", "slog-async", "slog-term", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -632,26 +579,16 @@ name = "cider-data-converter" version = "0.1.0" dependencies = [ "argh", - "interp", + "cider", "itertools 0.11.0", + "nom 7.1.3", "num-bigint", "num-rational", "num-traits", "proptest", "serde", "serde_json", - "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", + "thiserror 1.0.64", ] [[package]] @@ -665,46 +602,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 +613,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 +668,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 +697,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", - "clap 2.34.0", + "clap", "criterion-plot", "csv", "itertools 0.10.5", @@ -941,12 +823,12 @@ dependencies = [ [[package]] name = "dap" -version = "0.3.1-alpha1" -source = "git+https://github.com/sztomi/dap-rs?tag=v0.3.1-alpha1#0a989dcd8bdd45233dca4af728120fa19566c416" +version = "0.4.1-alpha1" +source = "git+https://github.com/sztomi/dap-rs?tag=v0.4.1-alpha1#44c0aea6151f37b5411ab71d7d355fa3fc9b1ece" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -961,12 +843,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 +861,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.90", ] [[package]] @@ -1010,13 +892,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.90", ] [[package]] @@ -1130,7 +1012,7 @@ dependencies = [ "smallvec", "symbol_table 0.2.0", "symbolic_expressions", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -1241,9 +1123,9 @@ dependencies = [ [[package]] name = "fraction" -version = "0.11.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad18174c73e668720cf9961281ea94a10a8c4003c2b2ad7117252109e5423a3f" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" dependencies = [ "lazy_static", "num", @@ -1253,13 +1135,13 @@ dependencies = [ [[package]] name = "fst-writer" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70529890a0a27d43374a430d7faf7634ef8df0ae775bb2836d92468eb74d61f" +checksum = "1ae6363689a1b6772c72f9b2123781d4657a327210dd0e65f1c47138344da15b" dependencies = [ "lz4_flex", - "thiserror", - "twox-hash", + "miniz_oxide 0.8.2", + "thiserror 2.0.8", ] [[package]] @@ -1306,12 +1188,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.3.30" @@ -1356,7 +1232,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -1425,12 +1301,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 +1368,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" @@ -1615,44 +1476,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "interp" -version = "0.1.1" -dependencies = [ - "ahash 0.8.10", - "argh", - "baa", - "bitvec", - "btor2i", - "calyx-frontend", - "calyx-ir", - "calyx-opt", - "calyx-utils", - "ciborium", - "fraction", - "fst-writer", - "itertools 0.11.0", - "lazy_static", - "num-bigint", - "num-traits", - "once_cell", - "owo-colors", - "pest", - "pest_consume", - "pest_derive", - "petgraph", - "proptest", - "rustyline", - "serde", - "serde_json", - "serde_with 1.14.0", - "slog", - "slog-async", - "slog-term", - "smallvec", - "thiserror", -] - [[package]] name = "is-terminal" version = "0.4.12" @@ -1703,28 +1526,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 +1608,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -1825,6 +1632,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.10" @@ -1889,9 +1705,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -1914,9 +1730,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", "serde", @@ -1939,9 +1755,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -2045,12 +1861,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" @@ -2064,7 +1874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.64", "ucd-trie", ] @@ -2110,7 +1920,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2151,7 +1961,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2218,21 +2028,11 @@ 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" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2282,12 +2082,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "radix_trie" version = "0.2.1" @@ -2411,7 +2205,7 @@ checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -2486,7 +2280,7 @@ checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2495,12 +2289,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 +2391,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2625,7 +2413,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2693,10 +2481,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.90", ] [[package]] @@ -2719,12 +2507,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 +2642,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 +2664,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -2919,9 +2707,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2934,12 +2722,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempdir" version = "0.3.7" @@ -3003,7 +2785,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +dependencies = [ + "thiserror-impl 2.0.8", ] [[package]] @@ -3014,7 +2805,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -3117,7 +2919,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -3219,7 +3021,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -3247,7 +3049,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -3508,18 +3310,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" @@ -3701,15 +3491,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "yaml-rust" version = "0.4.5" @@ -3755,5 +3536,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index d509d9a1fd..a798706209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,14 +8,13 @@ members = [ "calyx-stdlib", "calyx-backend", "calyx-lsp", - "interp", + "cider", "web/rust", "tools/data_gen", "cider-dap", "fud2", "fud2/fud-core", "tools/data-conversion", - "tools/btor2/btor2i", "tools/calyx-pass-explorer", "tools/cider-data-converter", "tools/component_cells", @@ -35,7 +34,7 @@ categories = ["compilers"] homepage = "https://calyxir.org" edition = "2021" version = "0.7.1" -rust-version = "1.67" +rust-version = "1.80" [workspace.dependencies] itertools = "0.11" @@ -59,6 +58,7 @@ calyx-ir = { path = "calyx-ir", version = "0.7.1" } calyx-frontend = { path = "calyx-frontend", version = "0.7.1" } calyx-opt = { path = "calyx-opt", version = "0.7.1" } calyx-backend = { path = "calyx-backend", version = "0.7.1" } +baa = { version = "0.16.0", features = ["bigint", "serde1", "fraction1"] } [workspace.dependencies.petgraph] version = "0.6" 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-backend/src/backend_opt.rs b/calyx-backend/src/backend_opt.rs index 000e368d78..f28bedbb68 100644 --- a/calyx-backend/src/backend_opt.rs +++ b/calyx-backend/src/backend_opt.rs @@ -62,8 +62,8 @@ impl FromStr for BackendOpt { } /// Convert `BackendOpt` to a string -impl ToString for BackendOpt { - fn to_string(&self) -> String { +impl std::fmt::Display for BackendOpt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Mlir => "mlir", Self::Resources => "resources", @@ -76,6 +76,6 @@ impl ToString for BackendOpt { Self::PrimitiveUses => "primitive-uses", Self::None => "none", } - .to_string() + .fmt(f) } } diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index dda26a31b4..0f0371af53 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -419,7 +419,7 @@ fn cell_instance(cell: &ir::Cell) -> Option { ); } else { param_binding.iter().for_each(|(name, value)| { - if *value > (std::i32::MAX as u64) { + if *value > (i32::MAX as u64) { panic!( "Parameter value {} for `{}` cannot be represented using 32 bits", value, diff --git a/calyx-backend/src/xilinx/xml.rs b/calyx-backend/src/xilinx/xml.rs index 6301836d3f..ddd764c119 100644 --- a/calyx-backend/src/xilinx/xml.rs +++ b/calyx-backend/src/xilinx/xml.rs @@ -11,7 +11,7 @@ pub struct XilinxXmlBackend; /// The root element of the `kernel.xml` file that describes an `.xo` package for the /// Xilinx toolchain, as documented [in the Vitis user guide][ug]. /// -/// [ug]: https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File +/// [ug]: https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/RTL-Kernel-XML-File #[derive(Serialize)] #[serde(rename = "root")] struct Root<'a> { diff --git a/calyx-frontend/src/parser.rs b/calyx-frontend/src/parser.rs index ce1044e934..13264e9543 100644 --- a/calyx-frontend/src/parser.rs +++ b/calyx-frontend/src/parser.rs @@ -57,10 +57,14 @@ impl CalyxParser { })?; // Add a new file to the position table let string_content = std::str::from_utf8(content)?.to_string(); - let file = GlobalPositionTable::as_mut() - .add_file(path.to_string_lossy().to_string(), string_content); + let file = GlobalPositionTable::add_file( + path.to_string_lossy().to_string(), + string_content, + ); let user_data = UserData { file }; - let content = GlobalPositionTable::as_ref().get_source(file); + + let content = GlobalPositionTable::get_source(file); + // Parse the file let inputs = CalyxParser::parse_with_userdata(Rule::file, content, user_data) @@ -93,17 +97,17 @@ impl CalyxParser { )) })?; // Save the input string to the position table - let file = - GlobalPositionTable::as_mut().add_file("".to_string(), buf); + let file = GlobalPositionTable::add_file("".to_string(), buf); let user_data = UserData { file }; - let contents = GlobalPositionTable::as_ref().get_source(file); + let content = GlobalPositionTable::get_source(file); + // Parse the input let inputs = - CalyxParser::parse_with_userdata(Rule::file, contents, user_data) + CalyxParser::parse_with_userdata(Rule::file, content, user_data) .map_err(|e| { - calyx_utils::Error::parse_error(e.variant.message()) - .with_pos(&Self::error_span(&e, file)) - })?; + calyx_utils::Error::parse_error(e.variant.message()) + .with_pos(&Self::error_span(&e, file)) + })?; let input = inputs.single().map_err(|e| { calyx_utils::Error::parse_error(e.variant.message()) .with_pos(&Self::error_span(&e, file)) @@ -118,11 +122,7 @@ impl CalyxParser { fn get_span(node: &Node) -> GPosIdx { let ud = node.user_data(); let sp = node.as_span(); - let pos = GlobalPositionTable::as_mut().add_pos( - ud.file, - sp.start(), - sp.end(), - ); + let pos = GlobalPositionTable::add_pos(ud.file, sp.start(), sp.end()); GPosIdx(pos) } @@ -131,7 +131,7 @@ impl CalyxParser { pest::error::InputLocation::Pos(off) => (off, off + 1), pest::error::InputLocation::Span((start, end)) => (start, end), }; - let pos = GlobalPositionTable::as_mut().add_pos(file, start, end); + let pos = GlobalPositionTable::add_pos(file, start, end); GPosIdx(pos) } diff --git a/calyx-ir/src/guard.rs b/calyx-ir/src/guard.rs index 287adbd229..9b1edad4b7 100644 --- a/calyx-ir/src/guard.rs +++ b/calyx-ir/src/guard.rs @@ -2,7 +2,7 @@ use crate::Printer; use super::{NumAttr, Port, RRC}; use calyx_utils::Error; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::mem; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; use std::{cmp::Ordering, hash::Hash, rc::Rc}; @@ -11,9 +11,9 @@ use std::{cmp::Ordering, hash::Hash, rc::Rc}; #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Nothing; -impl ToString for Nothing { - fn to_string(&self) -> String { - "".to_string() +impl Display for Nothing { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "") } } @@ -62,12 +62,12 @@ pub struct StaticTiming { interval: (u64, u64), } -impl ToString for StaticTiming { - fn to_string(&self) -> String { +impl Display for StaticTiming { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.interval.0 + 1 == self.interval.1 { - format!("%{}", self.interval.0) + write!(f, "%{}", self.interval.0) } else { - format!("%[{}:{}]", self.interval.0, self.interval.1) + write!(f, "%[{}:{}]", self.interval.0, self.interval.1) } } } diff --git a/calyx-ir/src/printer.rs b/calyx-ir/src/printer.rs index f8da0fc4c8..e7388481ef 100644 --- a/calyx-ir/src/printer.rs +++ b/calyx-ir/src/printer.rs @@ -759,9 +759,9 @@ impl Printer { } /// Generate a String-based representation for a guard. - pub fn guard_str(guard: &ir::Guard) -> String + pub fn guard_str(guard: &ir::Guard) -> String where - T: Eq, + T: Eq + ToString, { match &guard { ir::Guard::And(l, r) | ir::Guard::Or(l, r) => { 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/analysis/domination_analysis/dominator_map.rs b/calyx-opt/src/analysis/domination_analysis/dominator_map.rs index 89d2354fea..5ee5c58cb5 100644 --- a/calyx-opt/src/analysis/domination_analysis/dominator_map.rs +++ b/calyx-opt/src/analysis/domination_analysis/dominator_map.rs @@ -23,21 +23,22 @@ const END_ID: ir::Attribute = ir::Attribute::Internal(ir::InternalAttr::END_ID); /// - While Guards /// - If Guards /// - "End" If nodes, representing the place we're at in the program after the if -/// statement has just finished. This doesn't correspond to any actual Calyx code, but is -/// just a conceptualization we use to reason about domination. +/// statement has just finished. This doesn't correspond to any actual Calyx code, but is +/// just a conceptualization we use to reason about domination. +/// /// Note that seqs and pars will *not* be included in the domination map. /// /// Here is the algorithm we use to build the domination map. /// - Start with an emtpy map. /// - Visit each node n in the control program, and set: /// - dom(n) = {U dom(p) for each predecessor p of n} U {n}. In other words, take the -/// dominators of each predecessor of n, and union them together. Then add n to -/// this set, and set this set as the dominators of n. +/// dominators of each predecessor of n, and union them together. Then add n to +/// this set, and set this set as the dominators of n. /// - (Another clarification): by "predecessors" of node n we mean the set of nodes -/// that could be the most recent node executed when n begins to execute. +/// that could be the most recent node executed when n begins to execute. /// - If we visit every node of the control program and the map has not changed, -/// then we are done. If it has changed, then we visit each node again to repeat -/// the process. +/// then we are done. If it has changed, then we visit each node again to repeat +/// the process. /// /// The reason why we can take the union (rather than intersection) of the /// dominators of each predecessor is because we know each predecessor of each @@ -46,19 +47,19 @@ const END_ID: ir::Attribute = ir::Attribute::Internal(ir::InternalAttr::END_ID); /// our algorithm to deal with them. /// /// 1) The While Guard -/// The last node(s) in the while body are predecessor(s) of the while guard but -/// are not guaranteed to be executed. So, we can think of the while guard's -/// predecessors as being split in two groups: the "body predecessors" that are not guaranteed to -/// be executed before the while guard and the "outside predecessors" that are -/// outside the body of the while loop and are guaranteed to be executed before -/// the while loop guard. -/// Here we take: -/// dom(while guard) = U(dom(outside preds)) U {while guard} +/// The last node(s) in the while body are predecessor(s) of the while guard but +/// are not guaranteed to be executed. So, we can think of the while guard's +/// predecessors as being split in two groups: the "body predecessors" that are not guaranteed to +/// be executed before the while guard and the "outside predecessors" that are +/// outside the body of the while loop and are guaranteed to be executed before +/// the while loop guard. +/// Here we take: +/// dom(while guard) = U(dom(outside preds)) U {while guard} /// /// Justification: /// dom(while guard) is a subset of U(dom(outside preds)) U {while guard} /// Suppose n dominates the while guard. Every path to the while guard must end in -/// 1) outside pred -> while guard OR 2) body pred -> while guard. But for choice 2) +/// (1) outside pred -> while guard OR (2) body pred -> while guard. But for choice 2) /// we know the path was really something like outside pred -> while guard -> body /// -> while guard... body -> while guard. Since n dominates the while guard /// we know that it *cannot* be in the while body. Therefore, since every path to the @@ -72,10 +73,10 @@ const END_ID: ir::Attribute = ir::Attribute::Internal(ir::InternalAttr::END_ID); /// n dominates the while guard. /// /// 2) "End Node" of If Statements -/// In this case, *neither* of the predecessor sets (the set in the tbranch or -/// the set in the fbranch) are guaranteed to be executed. -/// Here we take: -/// dom(end node) = dom(if guard) U {end node}. +/// In this case, *neither* of the predecessor sets (the set in the tbranch or +/// the set in the fbranch) are guaranteed to be executed. +/// Here we take: +/// dom(end node) = dom(if guard) U {end node}. /// /// Justification: /// dom(end node) is a subset of dom(if guard) U {end node}. diff --git a/calyx-opt/src/analysis/graph.rs b/calyx-opt/src/analysis/graph.rs index 72c2dacdf5..0c9d9b3225 100644 --- a/calyx-opt/src/analysis/graph.rs +++ b/calyx-opt/src/analysis/graph.rs @@ -5,7 +5,7 @@ use petgraph::{ visit::EdgeRef, Direction::{Incoming, Outgoing}, }; -use std::fmt::Write; +use std::fmt::{Display, Write}; use std::{collections::HashMap, rc::Rc}; type Node = RRC; @@ -271,8 +271,8 @@ impl GraphAnalysis { } } -impl ToString for GraphAnalysis { - fn to_string(&self) -> String { +impl Display for GraphAnalysis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut out = String::new(); for idx in self.graph.node_indices() { let src_port = self.graph[idx].borrow(); @@ -293,6 +293,6 @@ impl ToString for GraphAnalysis { ) .expect("Failed to write to ScheduleConflicts string"); } - out + out.fmt(f) } } diff --git a/calyx-opt/src/analysis/graph_coloring.rs b/calyx-opt/src/analysis/graph_coloring.rs index cd36af4027..5ceea831be 100644 --- a/calyx-opt/src/analysis/graph_coloring.rs +++ b/calyx-opt/src/analysis/graph_coloring.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use petgraph::algo; use std::{ collections::{BTreeMap, HashMap}, + fmt::Display, hash::Hash, }; @@ -216,8 +217,8 @@ where } } -impl ToString for GraphColoring { - fn to_string(&self) -> String { - self.graph.to_string() +impl Display for GraphColoring { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.graph.to_string().fmt(f) } } diff --git a/calyx-opt/src/analysis/inference_analysis.rs b/calyx-opt/src/analysis/inference_analysis.rs index a2f2b209c2..ddd612f221 100644 --- a/calyx-opt/src/analysis/inference_analysis.rs +++ b/calyx-opt/src/analysis/inference_analysis.rs @@ -535,10 +535,11 @@ impl InferenceAnalysis { /// "Fixes Up" the component. In particular: /// 1. Removes @promotable annotations for any groups that write to any - /// `updated_components`. + /// `updated_components`. /// 2. Try to re-infer groups' latencies. /// 3. Removes all @promotable annotation from the control program. /// 4. Re-infers the @promotable annotations for any groups or control. + /// /// Note that this only fixes up the component's ``internals''. /// It does *not* fix the component's signature. pub fn fixup_timing(&self, comp: &mut ir::Component) { diff --git a/calyx-opt/src/analysis/static_tree.rs b/calyx-opt/src/analysis/static_tree.rs index 0c7d3b7b11..e89a3f05ca 100644 --- a/calyx-opt/src/analysis/static_tree.rs +++ b/calyx-opt/src/analysis/static_tree.rs @@ -367,13 +367,13 @@ impl SingleNode { /// Therefore we take in a bunch of data structures to keep track of coloring: /// - `coloring` that maps group names -> colors, /// - `colors_to_max_values` which maps colors -> (max latency, max_num_repeats) - /// (we need to make sure that when we instantiate a color, - /// we give enough bits to support the maximum latency/num_repeats that will be - /// used for that color) + /// (we need to make sure that when we instantiate a color, + /// we give enough bits to support the maximum latency/num_repeats that will be + /// used for that color) /// - `colors_to_fsm` - /// which maps colors to (fsm_register, iter_count_register): fsm_register counts - /// up for a single iteration, iter_count_register counts the number of iterations - /// that have passed. + /// which maps colors to (fsm_register, iter_count_register): fsm_register counts + /// up for a single iteration, iter_count_register counts the number of iterations + /// that have passed. /// /// Note that it is not always necessary to instantiate one or both registers (e.g., /// if num_repeats == 1 then you don't need an iter_count_register). @@ -1279,6 +1279,7 @@ impl SingleNode { /// - if `global_view` is true, then you have to include the iteration /// count register in the assignment's guard. /// - if `global_view` is false, then you dont' have to include it + /// /// `ignore_timing`: remove static timing guards instead of transforming them /// into an FSM query. Note that in order to do this, the timing guard must /// equal %[0:1], otherwise we will throw an error. This option is here diff --git a/calyx-opt/src/analysis/variable_detection.rs b/calyx-opt/src/analysis/variable_detection.rs index 3cc112b6fa..97c21decfb 100644 --- a/calyx-opt/src/analysis/variable_detection.rs +++ b/calyx-opt/src/analysis/variable_detection.rs @@ -10,6 +10,7 @@ impl VariableDetection { /// - among write to state_shareable components, there is only one write /// - has `@go` port equal to `1'd1` /// - has `g[done] = cell.done` + /// /// Returns the name of the cell if such a group is detected, /// otherwise returns `None`. pub fn variable_like( diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index 1280eada97..dc2f6fdbeb 100644 --- a/calyx-opt/src/default_passes.rs +++ b/calyx-opt/src/default_passes.rs @@ -79,6 +79,7 @@ impl PassManager { pm.register_pass::()?; pm.register_pass::()?; + // instrumentation pass to collect profiling information pm.register_pass::()?; //add metadata @@ -155,6 +156,20 @@ impl PassManager { ["validate", "pre-opt", "compile", "post-opt", "lower",] ); + // profiler flow for pass explorer access + register_alias!( + pm, + "profiler", + [ + StaticInliner, + CompileStatic, + CompileRepeat, + CompileInvoke, + ProfilerInstrumentation, + "all" + ] + ); + // Compilation flow with no optimizations enables register_alias!( pm, diff --git a/calyx-opt/src/passes/cell_share.rs b/calyx-opt/src/passes/cell_share.rs index fe85667807..dc163be240 100644 --- a/calyx-opt/src/passes/cell_share.rs +++ b/calyx-opt/src/passes/cell_share.rs @@ -282,12 +282,12 @@ impl CellShare { /// - use [ScheduleConflicts] to find groups/invokes that run in parallel with each other /// - for each tuple combination of cells that return true on cell_filter(), c1 and c2 /// - first determine if their live ranges overlap. If so, then insert a conflict between -/// c1 and c2 +/// c1 and c2 /// - if c1 and c2 don't have overlapping live ranges, check if c1 and c2 are ever -/// live at within the same par block, and they are live at different children -/// of the par block. If the parent par is not static, then add a conflict. -/// If the parent par is static, then we can use the static_par_timing analysis -/// to check whether the cells' liveness actually overlaps. +/// live at within the same par block, and they are live at different children +/// of the par block. If the parent par is not static, then add a conflict. +/// If the parent par is static, then we can use the static_par_timing analysis +/// to check whether the cells' liveness actually overlaps. /// - perform graph coloring using `self.ordering` to define the order of the greedy coloring /// - use coloring to rewrite group assignments, continuous assignments, and conditional ports. impl Visitor for CellShare { diff --git a/calyx-opt/src/passes/collapse_control.rs b/calyx-opt/src/passes/collapse_control.rs index 71c7f73a61..3a5190274b 100644 --- a/calyx-opt/src/passes/collapse_control.rs +++ b/calyx-opt/src/passes/collapse_control.rs @@ -36,7 +36,6 @@ use calyx_ir::{self as ir, GetAttributes, LibrarySignatures}; /// 3. Collapses nested `static seq` in the same way as 1 /// 4. Collapses nested `static par` in the same way as 2 /// 5. Collapses `static repeat`: -/// Collapse /// ``` /// static repeat 0 { ** body ** } /// ``` diff --git a/calyx-opt/src/passes/group_to_invoke.rs b/calyx-opt/src/passes/group_to_invoke.rs index 4acdb50020..33fb9269ea 100644 --- a/calyx-opt/src/passes/group_to_invoke.rs +++ b/calyx-opt/src/passes/group_to_invoke.rs @@ -13,9 +13,9 @@ use std::rc::Rc; /// /// For a group to meet the requirements of this pass, it must /// 1. Only write to one non-combinational component (all other writes must be -/// to combinational primitives) +/// to combinational primitives) /// 2. That component is *not* a ref cell, nor does it have the external attribute, -/// nor is it This Component +/// nor is it This Component /// 3. Assign component.go = 1'd1 /// 4. Assign group[done] = component.done pub struct GroupToInvoke { diff --git a/calyx-opt/src/passes/lower_guards.rs b/calyx-opt/src/passes/lower_guards.rs index fccb9ebad3..3748cbec9f 100644 --- a/calyx-opt/src/passes/lower_guards.rs +++ b/calyx-opt/src/passes/lower_guards.rs @@ -145,12 +145,11 @@ impl Visitor for LowerGuards { .component .get_groups_mut() .drain() - .map(|group| { + .inspect(|group| { let assigns = group.borrow_mut().assignments.drain(..).collect(); let new_assigns = lower_assigns(assigns, &mut builder); group.borrow_mut().assignments = new_assigns; - group }) .into(); builder.component.set_groups(groups); @@ -174,12 +173,11 @@ impl Visitor for LowerGuards { .component .comb_groups .drain() - .map(|group| { + .inspect(|group| { let assigns = group.borrow_mut().assignments.drain(..).collect(); let new_assigns = lower_assigns(assigns, &mut builder); group.borrow_mut().assignments = new_assigns; - group }) .into(); builder.component.comb_groups = comb_groups; diff --git a/calyx-opt/src/passes/profiler_instrumentation.rs b/calyx-opt/src/passes/profiler_instrumentation.rs index 829861ec91..1e23aad8e6 100644 --- a/calyx-opt/src/passes/profiler_instrumentation.rs +++ b/calyx-opt/src/passes/profiler_instrumentation.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; + use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor}; -use calyx_ir::{self as ir, build_assignments, BoolAttr}; +use calyx_ir::{self as ir, BoolAttr, Guard, Id, Nothing, NumAttr}; use calyx_utils::CalyxResult; /// Adds probe wires to each group to detect when a group is active. @@ -38,35 +40,236 @@ impl Visitor for ProfilerInstrumentation { sigs: &ir::LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { - // collect names of all groups (to construct group-specific cells) + let delimiter = "___"; + let mut acc = 0; + let comp_name = comp.name; + let mut structural_enable_map: HashMap< + Id, + Vec<(Id, ir::Guard)>, + > = HashMap::new(); + // groups to cells (from non-primitive components) that they invoked + let mut cell_invoke_map: HashMap> = HashMap::new(); + // groups to primitives that they invoked + let mut primitive_invoke_map: HashMap< + Id, + Vec<(Id, ir::Guard)>, + > = HashMap::new(); + // child_group --> [(parent_group, Guard)] let group_names = comp .groups .iter() .map(|group| group.borrow().name()) .collect::>(); - // for each group, construct a instrumentation cell and instrumentation assignment - let mut asgn_and_cell = Vec::with_capacity(group_names.len()); + // iterate and check for structural enables and for cell invokes + for group_ref in comp.groups.iter() { + let group = &group_ref.borrow(); + let mut primitive_vec: Vec<(Id, ir::Guard)> = Vec::new(); + for assigment_ref in group.assignments.iter() { + let dst_borrow = assigment_ref.dst.borrow(); + if let ir::PortParent::Group(parent_group_ref) = + &dst_borrow.parent + { + if dst_borrow.name == "go" { + // found an invocation of go + let invoked_group_name = + parent_group_ref.upgrade().borrow().name(); + let guard = *(assigment_ref.guard.clone()); + match structural_enable_map.get_mut(&invoked_group_name) + { + Some(vec_ref) => { + vec_ref.push((group.name(), guard)) + } + None => { + structural_enable_map.insert( + invoked_group_name, + vec![(group.name(), guard)], + ); + } + } + acc += 1; // really sad hack + } + } + if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent { + match cell_ref.upgrade().borrow().prototype.clone() { + calyx_ir::CellType::Primitive { + name: _, + param_binding: _, + is_comb, + latency: _, + } => { + let cell_name = cell_ref.upgrade().borrow().name(); + // don't need to profile for combinational primitives, and if the port isn't a go port. + if !is_comb & dst_borrow.has_attribute(NumAttr::Go) + { + let guard = Guard::and( + *(assigment_ref.guard.clone()), + Guard::port(ir::rrc( + assigment_ref.src.borrow().clone(), + )), + ); + primitive_vec.push((cell_name, guard)); + } + } + calyx_ir::CellType::Component { name: _ } => { + if dst_borrow.name == "go" { + let cell_name = + cell_ref.upgrade().borrow().name(); + match cell_invoke_map.get_mut(&group.name()) { + Some(vec_ref) => { + vec_ref.push(cell_name); + } + None => { + cell_invoke_map.insert( + group.name(), + vec![cell_name], + ); + } + } + } + } + _ => (), + } + } + } + primitive_invoke_map + .insert(group_ref.borrow().name(), primitive_vec); + } + // build probe and assignments for every group + all structural invokes + let mut builder = ir::Builder::new(comp, sigs); + let one = builder.add_constant(1, 1); + let mut group_name_assign_and_cell = Vec::with_capacity(acc); { - let mut builder = ir::Builder::new(comp, sigs); - let one = builder.add_constant(1, 1); + // probe and assignments for group (this group is currently active) for group_name in group_names.into_iter() { - let name = format!("{}_probe", group_name); - let inst_cell = builder.add_primitive(name, "std_wire", &[1]); - let asgn: [ir::Assignment; 1] = build_assignments!( - builder; - inst_cell["in"] = ? one["out"]; + // store group and component name (differentiate between groups of the same name under different components) + let name = format!( + "{}{}{}_group_probe", + group_name, delimiter, comp_name ); - inst_cell.borrow_mut().add_attribute(BoolAttr::Protected, 1); - asgn_and_cell.push((asgn[0].clone(), inst_cell)); + let probe_cell = builder.add_primitive(name, "std_wire", &[1]); + let probe_asgn: ir::Assignment = builder + .build_assignment( + probe_cell.borrow().get("in"), + one.borrow().get("out"), + Guard::True, + ); + // the probes should be @control because they should have value 0 whenever the corresponding group is not active. + probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1); + probe_cell + .borrow_mut() + .add_attribute(BoolAttr::Protected, 1); + group_name_assign_and_cell + .push((group_name, probe_asgn, probe_cell)); + } + // probe and assignments for primitive invocations (this group is activating a primitive) + for (group, primitive_invs) in primitive_invoke_map.iter() { + for (primitive_cell_name, guard) in primitive_invs.iter() { + let probe_cell_name = format!( + "{}{}{}{}{}_primitive_probe", + primitive_cell_name, + delimiter, + group, + delimiter, + comp_name + ); + let probe_cell = builder.add_primitive( + probe_cell_name, + "std_wire", + &[1], + ); + probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1); + probe_cell + .borrow_mut() + .add_attribute(BoolAttr::Protected, 1); + let probe_asgn: ir::Assignment = builder + .build_assignment( + probe_cell.borrow().get("in"), + one.borrow().get("out"), + guard.clone(), + ); + group_name_assign_and_cell + .push((*group, probe_asgn, probe_cell)); + } + } + // probe and assignments for structural enables (this group is structurally enabling a child group) + for (invoked_group_name, parent_groups) in + structural_enable_map.iter() + { + for (parent_group, guard) in parent_groups.iter() { + let probe_cell_name = format!( + "{}{}{}{}{}_se_probe", + invoked_group_name, + delimiter, + parent_group, + delimiter, + comp_name + ); + let probe_cell = builder.add_primitive( + probe_cell_name, + "std_wire", + &[1], + ); + probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1); + probe_cell + .borrow_mut() + .add_attribute(BoolAttr::Protected, 1); + let probe_asgn: ir::Assignment = builder + .build_assignment( + probe_cell.borrow().get("in"), + one.borrow().get("out"), + guard.clone(), + ); + group_name_assign_and_cell.push(( + *parent_group, + probe_asgn, + probe_cell, + )); + } + } + // probe cell and assignments for structural cell invocations (the group is structurally invoking a cell.) + for (invoker_group, invoked_cells) in cell_invoke_map.iter() { + for invoked_cell in invoked_cells { + let probe_cell_name = format!( + "{}{}{}{}{}_cell_probe", + invoked_cell, + delimiter, + invoker_group, + delimiter, + comp_name + ); + let probe_cell = builder.add_primitive( + probe_cell_name, + "std_wire", + &[1], + ); + probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1); + probe_cell + .borrow_mut() + .add_attribute(BoolAttr::Protected, 1); + // NOTE: this probe is active for the duration of the whole group. Hence, it may be active even when the cell itself is inactive. + let probe_asgn: ir::Assignment = builder + .build_assignment( + probe_cell.borrow().get("in"), + one.borrow().get("out"), + Guard::True, + ); + group_name_assign_and_cell.push(( + *invoker_group, + probe_asgn, + probe_cell, + )); + } } } - // add cells and assignments - for (group, (asgn, inst_cell)) in - comp.groups.iter().zip(asgn_and_cell.into_iter()) - { - group.borrow_mut().assignments.push(asgn); - comp.cells.add(inst_cell); + // ugh so ugly + for group in comp.groups.iter() { + for (group_name, asgn, cell) in group_name_assign_and_cell.iter() { + if group.borrow().name() == group_name { + group.borrow_mut().assignments.push(asgn.clone()); + comp.cells.add(cell.to_owned()); + } + } } - Ok(Action::Stop) + Ok(Action::Continue) } } diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 00a64c323c..637475f69c 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -21,12 +21,12 @@ const APPROX_WHILE_REPEAT_SIZE: u64 = 3; /// /// Promotion occurs the following policies: /// 1. ``Threshold'': How large the island must be. We have three const -/// defined as heuristics to measure approximately how big each control program -/// is. It must be larger than that threshold. +/// defined as heuristics to measure approximately how big each control program +/// is. It must be larger than that threshold. /// 2. ``Cycle limit": The maximum number of cycles the island can be when we -/// promote it. +/// promote it. /// 3. ``If Diff Limit": The maximum difference in latency between if statments -/// that we can tolerate to promote it. +/// that we can tolerate to promote it. /// pub struct StaticPromotion { /// An InferenceAnalysis object so that we can re-infer the latencies of diff --git a/calyx-opt/src/passes/well_formed.rs b/calyx-opt/src/passes/well_formed.rs index 32d14bb63d..a5634a278b 100644 --- a/calyx-opt/src/passes/well_formed.rs +++ b/calyx-opt/src/passes/well_formed.rs @@ -541,13 +541,13 @@ impl Visitor for WellFormed { { Err(Error::malformed_structure(format!( "Static Timing Guard has improper interval: `{}`", - static_timing.to_string() + static_timing )) .with_pos(&assign.attributes)) } else if static_timing.get_interval().1 > group_latency { Err(Error::malformed_structure(format!( "Static Timing Guard has interval `{}`, which is out of bounds since its static group has latency {}", - static_timing.to_string(), + static_timing, group_latency )) .with_pos(&assign.attributes)) diff --git a/calyx-opt/src/passes_experimental/sync/compile_sync_without_sync_reg.rs b/calyx-opt/src/passes_experimental/sync/compile_sync_without_sync_reg.rs index cb175fde38..d99ed5ac7c 100644 --- a/calyx-opt/src/passes_experimental/sync/compile_sync_without_sync_reg.rs +++ b/calyx-opt/src/passes_experimental/sync/compile_sync_without_sync_reg.rs @@ -64,7 +64,7 @@ impl BarrierMap { } fn insert_shared_wire(&mut self, builder: &mut ir::Builder, idx: &u64) { - if self.0.get(idx).is_none() { + if !self.0.contains_key(idx) { structure!(builder; let s = prim std_wire(1); ); diff --git a/calyx-opt/src/traversal/construct.rs b/calyx-opt/src/traversal/construct.rs index 65042598a2..8e88a36297 100644 --- a/calyx-opt/src/traversal/construct.rs +++ b/calyx-opt/src/traversal/construct.rs @@ -106,7 +106,7 @@ impl std::fmt::Display for ParseVal { } write!(f, "]") } - ParseVal::OutStream(o) => write!(f, "{}", o.to_string()), + ParseVal::OutStream(o) => write!(f, "{}", o), } } } diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index 22f23c2fc2..fd3c41023c 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -132,18 +132,6 @@ def output( """ return self._port_with_attributes(name, size, False, attribute_literals) - def output_with_attributes( - self, - name: str, - size: int, - attribute_literals: List[Union[str, Tuple[str, int]]], - ) -> ExprBuilder: - """Declare an output port on the component with attributes. - - Returns an expression builder for the port. - """ - return self._port_with_attributes(name, size, False, attribute_literals) - def attribute(self, name: str, value: int) -> None: """Declare an attribute on the component.""" self.component.attributes.append(ast.CompAttribute(name, value)) 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/calyx-utils/Cargo.toml b/calyx-utils/Cargo.toml index da438c0d4c..1632a1109b 100644 --- a/calyx-utils/Cargo.toml +++ b/calyx-utils/Cargo.toml @@ -23,3 +23,4 @@ string-interner.workspace = true itertools.workspace = true petgraph.workspace = true symbol_table = { version = "0.3", features = ["global"] } +boxcar = "0.2.7" diff --git a/calyx-utils/src/lib.rs b/calyx-utils/src/lib.rs index 5a9242797c..5ff50868ad 100644 --- a/calyx-utils/src/lib.rs +++ b/calyx-utils/src/lib.rs @@ -18,7 +18,5 @@ pub use math::bits_needed_for; pub use namegenerator::NameGenerator; pub use out_file::OutputFile; pub use pos_string::PosString; -pub use position::{ - FileIdx, GPosIdx, GlobalPositionTable, PosIdx, PositionTable, WithPos, -}; +pub use position::{FileIdx, GPosIdx, GlobalPositionTable, PosIdx, WithPos}; pub use weight_graph::{BoolIdx, Idx, WeightGraph}; diff --git a/calyx-utils/src/out_file.rs b/calyx-utils/src/out_file.rs index 16ac5ff654..c3edac848c 100644 --- a/calyx-utils/src/out_file.rs +++ b/calyx-utils/src/out_file.rs @@ -1,4 +1,5 @@ use std::{ + fmt::Display, io::{self, BufWriter}, path::PathBuf, str::FromStr, @@ -48,14 +49,16 @@ impl FromStr for OutputFile { } } -impl ToString for OutputFile { - fn to_string(&self) -> String { - match self { - OutputFile::Stdout => "-".to_string(), - OutputFile::Stderr => "".to_string(), - OutputFile::Null => "".to_string(), - OutputFile::File { path, .. } => path.to_str().unwrap().to_string(), - } +impl Display for OutputFile { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = match self { + OutputFile::Stdout => "-", + OutputFile::Stderr => "", + OutputFile::Null => "", + OutputFile::File { path, .. } => path.to_str().unwrap(), + }; + + string.fmt(f) } } diff --git a/calyx-utils/src/pos_string.rs b/calyx-utils/src/pos_string.rs index 90c0b96913..d5cc617dac 100644 --- a/calyx-utils/src/pos_string.rs +++ b/calyx-utils/src/pos_string.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{fmt::Display, path::PathBuf}; use crate::{GPosIdx, WithPos}; @@ -30,9 +30,9 @@ impl From for PathBuf { } } -impl ToString for PosString { - fn to_string(&self) -> String { - self.data.to_string() +impl Display for PosString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.data.fmt(f) } } diff --git a/calyx-utils/src/position.rs b/calyx-utils/src/position.rs index 2b74e99a24..8692710061 100644 --- a/calyx-utils/src/position.rs +++ b/calyx-utils/src/position.rs @@ -1,7 +1,8 @@ //! Definitions for tracking source position information of Calyx programs +use std::{cmp, fmt::Write, sync::LazyLock}; + use itertools::Itertools; -use std::{cmp, fmt::Write, mem, sync}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Handle to a position in a [PositionTable] @@ -16,9 +17,9 @@ pub struct FileIdx(u32); /// A source program file struct File { /// Name of the file - name: String, + name: Box, /// The source code of the file - source: String, + source: Box, } struct PosData { @@ -32,11 +33,11 @@ struct PosData { } /// Source position information for a Calyx program. -pub struct PositionTable { +struct PositionTable { /// The source files of the program - files: Vec, + files: boxcar::Vec, /// Mapping from indexes to position data - indices: Vec, + indices: boxcar::Vec, } impl Default for PositionTable { @@ -51,9 +52,9 @@ impl PositionTable { /// Create a new position table where the first file and first position are unknown pub fn new() -> Self { - let mut table = PositionTable { - files: Vec::new(), - indices: Vec::new(), + let table = PositionTable { + files: boxcar::Vec::new(), + indices: boxcar::Vec::new(), }; table.add_file("unknown".to_string(), "".to_string()); let pos = table.add_pos(FileIdx(0), 0, 0); @@ -62,10 +63,12 @@ impl PositionTable { } /// Add a new file to the position table - pub fn add_file(&mut self, name: String, source: String) -> FileIdx { - let file = File { name, source }; - let file_idx = self.files.len(); - self.files.push(file); + pub fn add_file(&self, name: String, source: String) -> FileIdx { + let file = File { + name: name.into(), + source: source.into(), + }; + let file_idx = self.files.push(file); FileIdx(file_idx as u32) } @@ -79,15 +82,9 @@ impl PositionTable { } /// Add a new position to the position table - pub fn add_pos( - &mut self, - file: FileIdx, - start: usize, - end: usize, - ) -> PosIdx { + pub fn add_pos(&self, file: FileIdx, start: usize, end: usize) -> PosIdx { let pos = PosData { file, start, end }; - let pos_idx = self.indices.len(); - self.indices.push(pos); + let pos_idx = self.indices.push(pos); PosIdx(pos_idx as u32) } @@ -99,28 +96,27 @@ impl PositionTable { /// The global position table pub struct GlobalPositionTable; +static GPOS_TABLE: LazyLock = LazyLock::new(PositionTable::new); + impl GlobalPositionTable { - /// Return reference to a global [PositionTable] - pub fn as_mut() -> &'static mut PositionTable { - static mut SINGLETON: mem::MaybeUninit = - mem::MaybeUninit::uninit(); - static ONCE: sync::Once = sync::Once::new(); - - // SAFETY: - // - writing to the singleton is OK because we only do it one time - // - the ONCE guarantees that SINGLETON is init'ed before assume_init_ref - unsafe { - ONCE.call_once(|| { - SINGLETON.write(PositionTable::new()); - assert!(PositionTable::UNKNOWN == GPosIdx::UNKNOWN.0) - }); - SINGLETON.assume_init_mut() - } + fn get_pos(pos: PosIdx) -> &'static PosData { + GPOS_TABLE.get_pos(pos) + } + + fn get_file_data(file: FileIdx) -> &'static File { + GPOS_TABLE.get_file_data(file) + } + + pub fn get_source(file: FileIdx) -> &'static str { + GPOS_TABLE.get_source(file) + } + + pub fn add_file(name: String, source: String) -> FileIdx { + GPOS_TABLE.add_file(name, source) } - /// Return an immutable reference to the global position table - pub fn as_ref() -> &'static PositionTable { - Self::as_mut() + pub fn add_pos(file: FileIdx, start: usize, end: usize) -> PosIdx { + GPOS_TABLE.add_pos(file, start, end) } } @@ -153,9 +149,8 @@ impl GPosIdx { /// 2. start position of the first line in span /// 3. line number of the span fn get_lines(&self) -> (Vec<&str>, usize, usize) { - let table = GlobalPositionTable::as_ref(); - let pos_d = table.get_pos(self.0); - let file = &table.get_file_data(pos_d.file).source; + let pos_d = GlobalPositionTable::get_pos(self.0); + let file = &*GlobalPositionTable::get_file_data(pos_d.file).source; let lines = file.split('\n').collect_vec(); let mut pos: usize = 0; @@ -187,10 +182,9 @@ impl GPosIdx { /// returns: /// 1. the name of the file the span is in /// 2. the (inclusive) range of lines within the span - pub fn get_line_num(&self) -> (&String, (usize, usize)) { - let table = GlobalPositionTable::as_ref(); - let pos_data = table.get_pos(self.0); - let file_name = &table.get_file_data(pos_data.file).name; + pub fn get_line_num(&self) -> (&str, (usize, usize)) { + let pos_data = GlobalPositionTable::get_pos(self.0); + let file_name = &GlobalPositionTable::get_file_data(pos_data.file).name; let (buf, _, line_num) = self.get_lines(); //reformat to return the range (inclusive) let rng = (line_num, line_num + buf.len() - 1); @@ -199,8 +193,7 @@ impl GPosIdx { /// Format this position with the error message `err_msg` pub fn format_raw>(&self, err_msg: S) -> String { - let table = GlobalPositionTable::as_ref(); - let pos_d = table.get_pos(self.0); + let pos_d = GlobalPositionTable::get_pos(self.0); let (lines, pos, linum) = self.get_lines(); let mut buf = String::new(); @@ -228,9 +221,8 @@ impl GPosIdx { /// Format this position with filename header and the error message `err_msg` pub fn format>(&self, err_msg: S) -> String { - let table = GlobalPositionTable::as_ref(); - let pos_d = table.get_pos(self.0); - let name = &table.get_file_data(pos_d.file).name; + let pos_d = GlobalPositionTable::get_pos(self.0); + let name = &*GlobalPositionTable::get_file_data(pos_d.file).name; let mut buf = name.to_string(); writeln!(buf).unwrap(); @@ -239,9 +231,8 @@ impl GPosIdx { } pub fn get_location(&self) -> (&str, usize, usize) { - let table = GlobalPositionTable::as_ref(); - let pos_d = table.get_pos(self.0); - let name = &table.get_file_data(pos_d.file).name; + let pos_d = GlobalPositionTable::get_pos(self.0); + let name = &*GlobalPositionTable::get_file_data(pos_d.file).name; (name, pos_d.start, pos_d.end) } diff --git a/calyx-utils/src/weight_graph.rs b/calyx-utils/src/weight_graph.rs index e6ca364453..b5e72d5a59 100644 --- a/calyx-utils/src/weight_graph.rs +++ b/calyx-utils/src/weight_graph.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use petgraph::matrix_graph::{MatrixGraph, NodeIndex, UnMatrix, Zero}; use petgraph::visit::IntoEdgeReferences; -use std::{collections::HashMap, hash::Hash}; +use std::{collections::HashMap, fmt::Display, hash::Hash}; /// Index into a [WeightGraph] pub type Idx = NodeIndex; @@ -126,14 +126,13 @@ where } } -impl ToString for WeightGraph { - fn to_string(&self) -> String { +impl Display for WeightGraph { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let rev_map = self.reverse_index(); let keys: Vec<_> = self.index_map.keys().collect(); let nodes = keys .iter() - .enumerate() - .map(|(_idx, key)| { + .map(|key| { format!( " {} [label=\"{}\"];", key.to_string(), @@ -154,6 +153,6 @@ impl ToString for WeightGraph { }) .collect::>() .join("\n"); - format!("graph {{ \n{}\n{}\n }}", nodes, edges) + write!(f, "graph {{ \n{}\n{}\n }}", nodes, edges) } } diff --git a/cider-dap/Cargo.toml b/cider-dap/Cargo.toml index 2a16b698f4..b9eccc0be3 100644 --- a/cider-dap/Cargo.toml +++ b/cider-dap/Cargo.toml @@ -6,17 +6,18 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dap = { git = "https://github.com/sztomi/dap-rs", tag = "v0.3.1-alpha1" } +dap = { git = "https://github.com/sztomi/dap-rs", tag = "v0.4.1-alpha1" } thiserror = "1.*" serde_json = "1.0" -serde.workspace= true +serde.workspace = true owo-colors = "^3.5" argh = "0.1" slog = "2.7.0" slog-term = "2.8.0" slog-async = "2.7.0" +baa.workspace = true -interp = { path = "../interp" } +cider = { path = "../cider" } [[bin]] name = "cider-dap" diff --git a/cider-dap/calyxDebug/package.json b/cider-dap/calyxDebug/package.json index 1937774c7f..b29cfae2f0 100644 --- a/cider-dap/calyxDebug/package.json +++ b/cider-dap/calyxDebug/package.json @@ -1,8 +1,8 @@ { "name": "cider-dap", - "displayName": "Cider", + "displayName": "Cider DAP", "version": "0.0.1", - "publisher": "Cider", + "publisher": "Capra @ Cornell", "description": "A debug adapter for Calyx files", "author": { "name": "...", diff --git a/cider-dap/src/adapter.rs b/cider-dap/src/adapter.rs index 469e8449fb..54b623b54a 100644 --- a/cider-dap/src/adapter.rs +++ b/cider-dap/src/adapter.rs @@ -1,25 +1,30 @@ use crate::error::AdapterResult; +use baa::BitVecOps; +use cider::debugger::commands::ParsedGroupName; +use cider::debugger::source::structures::NewSourceMap; +use cider::debugger::{OwnedDebugger, StoppedReason}; +use cider::flatten::flat_ir::base::{GlobalCellIdx, PortValue}; +use dap::events::{Event, OutputEventBody, StoppedEventBody}; use dap::types::{ - Breakpoint, Scope, Source, SourceBreakpoint, StackFrame, Thread, Variable, + self, Breakpoint, Scope, Source, SourceBreakpoint, StackFrame, Thread, + Variable, }; -use interp::debugger::commands::ParsedGroupName; -use interp::debugger::source::structures::NewSourceMap; -use interp::debugger::OwnedDebugger; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; pub struct MyAdapter { #[allow(dead_code)] debugger: OwnedDebugger, - break_count: Counter, + _break_count: Counter, thread_count: Counter, stack_count: Counter, breakpoints: HashSet, - stack_frames: Vec, // This field is a placeholder - threads: Vec, // This field is a placeholder - object_references: HashMap>, + stack_frames: Vec, + threads: Vec, // This field is a placeholder + object_references: HashMap>, source: String, ids: NewSourceMap, + frames_to_cmpts: HashMap, //stores mapping from frame ids to component idx } impl MyAdapter { @@ -28,7 +33,7 @@ impl MyAdapter { OwnedDebugger::from_file(&PathBuf::from(path), &std_path).unwrap(); Ok(MyAdapter { debugger, - break_count: Counter::new(), + _break_count: Counter::new(), thread_count: Counter::new(), stack_count: Counter::new(), breakpoints: HashSet::new(), @@ -37,6 +42,7 @@ impl MyAdapter { object_references: HashMap::new(), source: path.to_string(), ids: metadata, + frames_to_cmpts: HashMap::new(), }) } /// function to deal with setting breakpoints and updating debugger accordingly @@ -76,7 +82,7 @@ impl MyAdapter { let name = self.ids.lookup_line(source_point.line as u64); let breakpoint = make_breakpoint( - self.break_count.increment().into(), + Some(source_point.line), name.is_some(), Some(path.clone()), Some(source_point.line), @@ -115,7 +121,7 @@ impl MyAdapter { self.debugger.delete_breakpoints(to_debugger); } - ///Creates a thread using the parameter name. + /// Creates a thread using the parameter name. pub fn create_thread(&mut self, name: String) -> Thread { //how do we attach the thread to the program let thread = Thread { @@ -131,38 +137,46 @@ impl MyAdapter { self.threads.clone() } - //Returns a dummy stack frame, set to change. - pub fn create_stack(&mut self) -> Vec { - let frame = StackFrame { - id: self.stack_count.increment(), - // Maybe automate the name in the future? - name: String::from("Frame"), - source: Some(Source { - name: None, - path: Some(self.source.clone()), - source_reference: None, - presentation_hint: None, - origin: None, - sources: None, - adapter_data: None, - checksums: None, - }), - line: 1, - column: 0, - end_line: None, - end_column: None, - can_restart: None, - instruction_pointer_reference: None, - module_id: None, - presentation_hint: None, - }; - self.stack_frames.push(frame); - // Return all stack frames + /// returns all frames (components) in program + pub fn get_stack(&mut self) -> Vec { + if self.stack_frames.is_empty() { + self.create_stack(); + } self.stack_frames.clone() } - pub fn clone_stack(&self) -> Vec { - self.stack_frames.clone() + /// creates call stack where each frame is a component. Adds frames to current + /// call stack + fn create_stack(&mut self) { + let components = self.debugger.get_components(); + //turn the names into stack frames, ignore lines for right now + for (idx, comp) in components { + let frame = StackFrame { + id: self.stack_count.increment(), + // Maybe automate the name in the future? + name: String::from(comp), + source: Some(Source { + name: None, + path: Some(self.source.clone()), + source_reference: None, + presentation_hint: None, + origin: None, + sources: None, + adapter_data: None, + checksums: None, + }), + line: 1, // need to get this to be line component starts on + column: 0, + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + }; + self.frames_to_cmpts.insert(frame.id, idx); + self.stack_frames.push(frame); + } } pub fn next_line(&mut self, _thread: i64) -> bool { @@ -199,26 +213,34 @@ impl MyAdapter { Some(p) => { let out: Vec = p .iter() - .map(|x| Variable { - name: String::from(x), - value: String::from("1"), - type_field: None, - presentation_hint: None, - evaluate_name: None, - variables_reference: 0, - named_variables: None, - indexed_variables: None, - memory_reference: None, + .map(|(nam, val)| { + let valu = val + .as_option() + .map(|x| x.val().to_u64().unwrap()) + .unwrap_or_default(); + Variable { + name: String::from(nam), + value: valu.to_string(), + type_field: None, + presentation_hint: None, + evaluate_name: None, + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + } }) .collect(); out } } } - // return cells in calyx context (later should hopefully just return ones w current component) - pub fn get_scopes(&mut self, _frame: i64) -> Vec { + // return cells in calyx context + // todo: return only cells in current stack frame (component) + pub fn get_scopes(&mut self, frame: i64) -> Vec { let mut out_vec = vec![]; - let cell_names = self.debugger.get_cells(); + let component = self.frames_to_cmpts[&frame]; + let cell_names = self.debugger.get_comp_cells(component); let mut var_ref_count = 1; for (name, ports) in cell_names { self.object_references.insert(var_ref_count, ports); @@ -244,8 +266,56 @@ impl MyAdapter { } pub fn on_pause(&mut self) { + //self.debugger.pause(); self.object_references.clear(); } + + pub fn on_continue(&mut self, thread_id: i64) -> Event { + dbg!("continue - adapter"); + let result = self.debugger.cont(); + match result { + // honestly not sure if this is right behavior, still unsure what an output event IS lol. + Err(e) => Event::Output(OutputEventBody { + category: Some(types::OutputEventCategory::Stderr), + output: e.to_string(), + group: Some(types::OutputEventGroup::Start), + variables_reference: None, + source: None, + line: None, + column: None, + data: None, + }), + Ok(reason) => match reason { + StoppedReason::Done => Event::Terminated(None), + StoppedReason::Breakpoint(names) => { + let bp_lines: Vec = names + .into_iter() + .map(|x| self.ids.lookup(&x).unwrap().start_line as i64) + .collect(); + dbg!(&bp_lines); + //in map add adjusting stack frame lines + Event::Stopped(StoppedEventBody { + reason: types::StoppedEventReason::Breakpoint, + description: Some(String::from("hit breakpoint")), + thread_id: Some(thread_id), + preserve_focus_hint: None, + all_threads_stopped: Some(true), + text: None, + hit_breakpoint_ids: Some(bp_lines), + }) + } + StoppedReason::PauseReq => Event::Stopped(StoppedEventBody { + reason: types::StoppedEventReason::Pause, + description: Some(String::from("Paused")), + thread_id: Some(thread_id), + preserve_focus_hint: None, + all_threads_stopped: Some(true), + text: None, + hit_breakpoint_ids: None, + }), + }, + } + } } /// Simple struct used to keep an index of the breakpoints used. diff --git a/cider-dap/src/main.rs b/cider-dap/src/main.rs index 1f8131a987..1aca426dc5 100644 --- a/cider-dap/src/main.rs +++ b/cider-dap/src/main.rs @@ -108,7 +108,6 @@ where // Not sure if we need it // Make VSCode send disassemble request supports_stepping_granularity: Some(true), - supports_single_thread_execution_requests: Some(true), ..Default::default() })); server.respond(rsp)?; @@ -149,10 +148,8 @@ where // Construct the adapter let mut adapter = MyAdapter::new(program_path, std_path)?; - // Currently, we need two threads to run the debugger and step through, - // not sure why but would be good to look into for the future. + // one thread idk why but it works let thread = &adapter.create_thread(String::from("Main")); //does not seem as though this does anything - let thread2 = &adapter.create_thread(String::from("Thread 1")); // Notify server of first thread server.send_event(Event::Thread(ThreadEventBody { @@ -160,12 +157,6 @@ where thread_id: thread.id, }))?; - //Notify server of second thread - server.send_event(Event::Thread(ThreadEventBody { - reason: types::ThreadEventReason::Started, - thread_id: thread2.id, - }))?; - // Return the adapter instead of running the server Ok(adapter) } @@ -241,12 +232,7 @@ fn run_server( } // Send StackTrace, may be useful to make it more robust in the future Command::StackTrace(_args) => { - // Create new frame if empty, SUBJECT TO CHANGE - let frames = if adapter.clone_stack().is_empty() { - adapter.create_stack() - } else { - adapter.clone_stack() - }; + let frames = adapter.get_stack(); let rsp = req.success(ResponseBody::StackTrace(StackTraceResponse { stack_frames: frames, @@ -255,14 +241,16 @@ fn run_server( server.respond(rsp)?; } // Continue the debugger - Command::Continue(_args) => { + Command::Continue(args) => { // need to run debugger, ngl not really sure how to implement this functionality // run debugger until breakpoint or paused -> maybe have a process to deal w running debugger? + let stopped = adapter.on_continue(args.thread_id); let rsp = req.success(ResponseBody::Continue(ContinueResponse { - all_threads_continued: None, + all_threads_continued: Some(true), })); server.respond(rsp)?; + server.send_event(stopped)?; } // Send a Stopped event with reason Pause Command::Pause(args) => { @@ -347,19 +335,13 @@ fn run_server( server.send_event(stopped)?; } Command::Scopes(args) => { - //variables go in here most likely - //just get stuff displaying then figure out how to pretty it up - let frame_id = args.frame_id; let rsp = req.success(ResponseBody::Scopes(ScopesResponse { scopes: adapter.get_scopes(frame_id), })); - info!(logger, "responded with {rsp:?}"); server.respond(rsp)?; } Command::Variables(args) => { - info!(logger, "variables req"); - // never happening idk why let var_ref = args.variables_reference; let rsp = req.success(ResponseBody::Variables(VariablesResponse { diff --git a/interp/Cargo.toml b/cider/Cargo.toml similarity index 76% rename from interp/Cargo.toml rename to cider/Cargo.toml index eed12da8d4..9ea4cc89cf 100644 --- a/interp/Cargo.toml +++ b/cider/Cargo.toml @@ -1,10 +1,9 @@ [package] -name = "interp" +name = "cider" version = "0.1.1" authors = ["The Calyx authors"] edition = "2021" -rust-version = "1.73" - +rust-version.workspace = true [[bin]] name = "cider" @@ -16,17 +15,15 @@ path = "src/main.rs" [dependencies] smallvec = { workspace = true, features = ["union", "const_generics"] } serde = { workspace = true, features = ["derive", "rc"] } -lazy_static.workspace = true itertools.workspace = true pest.workspace = true pest_derive.workspace = true pest_consume.workspace = true argh.workspace = true owo-colors = "^3.5" -bitvec = "1.0" serde_json = "1.0" rustyline = "=10.0.0" -fraction = { version = "0.11.0", features = ["with-serde-support"] } +fraction = { version = "0.15.3", features = ["with-serde-support"] } thiserror = "1.0.26" slog = "2.7.0" slog-term = "2.8.0" @@ -35,7 +32,6 @@ ahash = "0.8.3" num-bigint = "0.4.6" num-traits = "0.2.19" -once_cell = "1.9.0" petgraph = "0.6.3" calyx-ir = { path = "../calyx-ir" } @@ -43,11 +39,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"] } -fst-writer = "0.2.1" +baa.workspace = true +fst-writer = "0.2.3" +bon = "2.3" [dev-dependencies] proptest = "1.0.0" diff --git a/interp/README.md b/cider/README.md similarity index 100% rename from interp/README.md rename to cider/README.md diff --git a/interp/proptest-regressions/tests/values.txt b/cider/proptest-regressions/tests/values.txt similarity index 100% rename from interp/proptest-regressions/tests/values.txt rename to cider/proptest-regressions/tests/values.txt diff --git a/interp/src/as_raw.rs b/cider/src/as_raw.rs similarity index 90% rename from interp/src/as_raw.rs rename to cider/src/as_raw.rs index dada77d2df..a14ac7d812 100644 --- a/interp/src/as_raw.rs +++ b/cider/src/as_raw.rs @@ -17,12 +17,12 @@ impl AsRaw for *const T { } } -impl<'a, T> AsRaw for &Ref<'a, T> { +impl AsRaw for &Ref<'_, T> { fn as_raw(&self) -> *const T { self as &T as *const T } } -impl<'a, T> AsRaw for Ref<'a, T> { +impl AsRaw for Ref<'_, T> { fn as_raw(&self) -> *const T { self as &T as *const T } diff --git a/cider/src/configuration.rs b/cider/src/configuration.rs new file mode 100644 index 0000000000..9bd8baf7b7 --- /dev/null +++ b/cider/src/configuration.rs @@ -0,0 +1,50 @@ +use bon::Builder; + +// this can be a copy type because it's just a bunch of bools +#[derive(Debug, Default, Clone, Copy, Builder)] +/// Configuration struct which controls runtime behavior +pub struct Config { + /// dump registers as single entry memories + pub dump_registers: bool, + /// dumps all memories rather than just external ones + pub dump_all_memories: 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 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/cider/src/debugger/commands/command_parser.rs similarity index 94% rename from interp/src/debugger/commands/command_parser.rs rename to cider/src/debugger/commands/command_parser.rs index 0dda9c6d8e..c48179d1a9 100644 --- a/interp/src/debugger/commands/command_parser.rs +++ b/cider/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 = std::result::Result>; 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 { @@ -60,8 +68,9 @@ impl CommandParser { fn comm_where(input: Node) -> ParseResult { 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 { +pub fn parse_command(input_str: &str) -> CiderResult { 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/cider/src/debugger/commands/commands.pest similarity index 95% rename from interp/src/debugger/commands/commands.pest rename to cider/src/debugger/commands/commands.pest index 799971648c..7a1920c680 100644 --- a/interp/src/debugger/commands/commands.pest +++ b/cider/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/cider/src/debugger/commands/core.rs similarity index 91% rename from interp/src/debugger/commands/core.rs rename to cider/src/debugger/commands/core.rs index 46f86279bb..48eba4734c 100644 --- a/interp/src/debugger/commands/core.rs +++ b/cider/src/debugger/commands/core.rs @@ -1,7 +1,6 @@ //! This module contains the core data structures and commands used by the debugger use itertools::{self, Itertools}; -use lazy_static::lazy_static; use owo_colors::OwoColorize; use std::{ fmt::{Display, Write}, @@ -299,6 +298,44 @@ impl From<(Vec, Option, PrintMode)> for PrintTuple { } } +/// ParseNodes enum is used to represent what child to traverse with respect to +/// the current ControlIdx. +/// Body defines that we should go into the body of a while or repeat. +/// Offset defines which child to go to. +/// If defines whether we should go to the true or false branch next +#[derive(Debug, PartialEq, Clone)] +pub enum ParseNodes { + Body, + Offset(u32), + If(bool), +} +pub struct ParsePath { + nodes: Vec, +} + +impl ParsePath { + pub fn new(nodes: Vec) -> ParsePath { + ParsePath { nodes } + } + + pub fn get_path(&self) -> Vec { + self.nodes.clone() + } +} + +impl FromIterator for ParsePath { + fn from_iter>(iter: I) -> Self { + ParsePath::new(iter.into_iter().collect()) + } +} + +// 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 +382,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. @@ -365,7 +402,7 @@ impl Command { invocation: names, description: message, .. - } in COMMAND_INFO.iter() + } in get_command_info().iter() { writeln!(out, " {: <30}{}", names.join(", "), message.green()) .unwrap(); @@ -381,7 +418,9 @@ impl Command { invocation, description, usage_example, - } in COMMAND_INFO.iter().filter(|x| !x.usage_example.is_empty()) + } in get_command_info() + .iter() + .filter(|x| !x.usage_example.is_empty()) { writeln!(out).unwrap(); writeln!(out, "{}", invocation.join(", ")).unwrap(); @@ -400,11 +439,11 @@ impl Command { // I wouldn't recommend looking at this -lazy_static! { - /// A (lazy) static list of [CommandInfo] objects used for the help and - /// explain messages - static ref COMMAND_INFO: Vec = { - vec![ +use std::sync::LazyLock; +/// A (lazy) static list of [CommandInfo] objects used for the help and +/// explain messages. Access via [get_command_info] +static COMMAND_INFO: LazyLock> = LazyLock::new(|| { + [ // step CIBuilder::new().invocation("step") .invocation("s") @@ -484,7 +523,6 @@ lazy_static! { .description("Disable target watchpoint") .usage("> disable-watch 4") .usage("> disable-watch do_mult").build(), - // explain CIBuilder::new().invocation("explain") .description("Show examples of commands which take arguments").build(), @@ -494,15 +532,21 @@ lazy_static! { CIBuilder::new().invocation("exit") .invocation("quit") .description("Exit the debugger").build(), - ] - }; + ].into() +}); + +/// Returns the list of [CommandInfo] objects used for the help and explain +/// messages +#[inline] +fn get_command_info() -> &'static [CommandInfo] { + &COMMAND_INFO } #[derive(Clone, Debug)] struct CommandInfo { - invocation: Vec, + invocation: Box<[CommandName]>, description: Description, - usage_example: Vec, + usage_example: Box<[UsageExample]>, } // type shenanigans @@ -589,9 +633,9 @@ where impl CommandInfoBuilder { fn build(self) -> CommandInfo { CommandInfo { - invocation: self.invocation, + invocation: self.invocation.into(), description: self.description.unwrap(), - usage_example: self.usage_example, + usage_example: self.usage_example.into(), } } } diff --git a/interp/src/debugger/commands/mod.rs b/cider/src/debugger/commands/mod.rs similarity index 91% rename from interp/src/debugger/commands/mod.rs rename to cider/src/debugger/commands/mod.rs index 452108f4ea..19f20de743 100644 --- a/interp/src/debugger/commands/mod.rs +++ b/cider/src/debugger/commands/mod.rs @@ -1,6 +1,7 @@ //! This module contains the structures for the debugger commands pub(crate) mod command_parser; pub mod core; +mod path_parser; pub use command_parser::parse_command; pub use core::Command; diff --git a/cider/src/debugger/commands/path_parser.pest b/cider/src/debugger/commands/path_parser.pest new file mode 100644 index 0000000000..f95eaff145 --- /dev/null +++ b/cider/src/debugger/commands/path_parser.pest @@ -0,0 +1,13 @@ +root = { "." } + +separator = { "-" } + +body = { "b" } + +num = { ASCII_DIGIT+ } + +branch = {"t" | "f"} + +clause = { separator ~ (body | num | branch) } + +path = { SOI ~ root ~ clause* ~ EOI } diff --git a/cider/src/debugger/commands/path_parser.rs b/cider/src/debugger/commands/path_parser.rs new file mode 100644 index 0000000000..5739da65b9 --- /dev/null +++ b/cider/src/debugger/commands/path_parser.rs @@ -0,0 +1,116 @@ +use super::{core::ParseNodes, ParsePath}; + +use pest_consume::{match_nodes, Error, Parser}; + +type ParseResult = std::result::Result>; +type Node<'i> = pest_consume::Node<'i, Rule, ()>; + +// include the grammar file so that Cargo knows to rebuild this file on grammar changes +const _GRAMMAR: &str = include_str!("path_parser.pest"); + +#[derive(Parser)] +#[grammar = "debugger/commands/path_parser.pest"] + +pub struct PathParser; + +#[pest_consume::parser] +impl PathParser { + fn EOI(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn root(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn body(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn separator(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn num(input: Node) -> ParseResult { + input + .as_str() + .parse::() + .map_err(|_| input.error("Expected non-negative number")) + } + + fn branch(input: Node) -> ParseResult { + let b = input.as_str(); + let result = b != "f"; + Ok(result) + } + + fn clause(input: Node) -> ParseResult { + Ok(match_nodes!(input.into_children(); + [separator(_), num(n)] => ParseNodes::Offset(n), + [separator(_), body(_)] => ParseNodes::Body, + [separator(_), branch(b)] => ParseNodes::If(b) + )) + } + + fn path(input: Node) -> ParseResult { + Ok(match_nodes!(input.into_children(); + [root(_), clause(c).., EOI(_)] => ParsePath::from_iter(c), + )) + } +} + +// Parse the path +#[allow(dead_code)] +pub fn parse_path(input_str: &str) -> Result>> { + let entries = PathParser::parse(Rule::path, input_str)?; + let entry = entries.single()?; + + PathParser::path(entry).map_err(Box::new) +} + +#[cfg(test)] +#[test] +fn root() { + let path = parse_path(".").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), Vec::new()) +} + +#[test] +fn body() { + let path = parse_path(".-b").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), vec![ParseNodes::Body]) +} + +#[test] +fn branch() { + let path = parse_path(".-f").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), vec![ParseNodes::If(false)]) +} + +#[test] +fn offset() { + let path = parse_path(".-0-1").unwrap(); + dbg!(path.get_path()); + assert_eq!( + path.get_path(), + vec![ParseNodes::Offset(0), ParseNodes::Offset(1)] + ) +} + +#[test] +fn multiple() { + let path = parse_path(".-0-1-b-t").unwrap(); + dbg!(path.get_path()); + assert_eq!( + path.get_path(), + vec![ + ParseNodes::Offset(0), + ParseNodes::Offset(1), + ParseNodes::Body, + ParseNodes::If(true) + ] + ) +} diff --git a/interp/src/debugger/debugger_core.rs b/cider/src/debugger/debugger_core.rs similarity index 85% rename from interp/src/debugger/debugger_core.rs rename to cider/src/debugger/debugger_core.rs index 863d42b4aa..c89c2058d0 100644 --- a/interp/src/debugger/debugger_core.rs +++ b/cider/src/debugger/debugger_core.rs @@ -5,10 +5,16 @@ 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::{BoxedCiderError, CiderError, CiderResult}, flatten::{ - flat_ir::prelude::GroupIdx, + flat_ir::{ + base::{GlobalCellIdx, PortValue}, + prelude::GroupIdx, + }, setup_simulation_with_metadata, structures::{ context::Context, @@ -65,6 +71,12 @@ pub enum DebuggerReturnStatus { Exit, } +pub enum StoppedReason { + Done, + Breakpoint(Vec<(String, String)>), //adapter then looks up line + PauseReq, +} + /// The interactive Calyx debugger. The debugger itself is run with the /// [Debugger::main_loop] function while this struct holds auxiliary /// information used to coordinate the debugging process. @@ -86,7 +98,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 +106,7 @@ impl OwnedDebugger { )?; let debugger: Debugger> = - Self::new(Rc::new(ctx), &None, &None, false)?; + Self::new(Rc::new(ctx), &None, &None, RuntimeConfig::default())?; Ok((debugger, map)) } @@ -106,13 +118,13 @@ impl + Clone> Debugger { program_context: C, data_file: &Option, wave_file: &Option, - check_data_races: bool, - ) -> InterpreterResult { + runtime_config: RuntimeConfig, + ) -> CiderResult { let mut interpreter = Simulator::build_simulator( program_context.clone(), data_file, wave_file, - check_data_races, + runtime_config, )?; interpreter.converge()?; @@ -129,33 +141,47 @@ impl + Clone> Debugger { status: self .interpreter .get_currently_running_groups() - .map(|x| { - let group_name = - self.program_context.as_ref().lookup_name(x).clone(); - let parent_comp = self - .program_context - .as_ref() - .get_component_from_group(x); - let parent_name = self - .program_context - .as_ref() - .lookup_name(parent_comp) - .clone(); - (parent_name, group_name) - }) + .map(|x| self.grp_idx_to_name(x)) .collect(), done: self.interpreter.is_done(), } } - pub fn get_cells( + fn grp_idx_to_name(&self, x: GroupIdx) -> (String, String) { + let group_name = self.program_context.as_ref().lookup_name(x).clone(); + let parent_comp = + self.program_context.as_ref().get_component_from_group(x); + let parent_name = self + .program_context + .as_ref() + .lookup_name(parent_comp) + .clone(); + (parent_name, group_name) + } + + pub fn get_all_cells( &self, - ) -> impl Iterator)> + '_ { + ) -> impl Iterator)> + '_ { self.interpreter.env().iter_cells() } + /// Get cell names and port values for the component specified by cmp_idx + pub fn get_comp_cells( + &self, + cmp_idx: GlobalCellIdx, + ) -> impl Iterator)> + '_ { + // component idx -> global cell idx + self.interpreter.env().iter_cmpt_cells(cmp_idx) + } + /// Get all components in the environment + pub fn get_components( + &self, + ) -> impl Iterator + '_ { + //this gets the names AND idx, now how to get the lines T.T + self.interpreter.env().iter_compts() + } // Go to next step - pub fn step(&mut self, n: u32) -> InterpreterResult { + pub fn step(&mut self, n: u32) -> CiderResult { self.do_step(n)?; Ok(self.status()) @@ -172,8 +198,25 @@ impl + Clone> Debugger { .collect_vec(); self.manipulate_breakpoint(Command::Delete(parsed_bp_ids)); } + + pub fn cont(&mut self) -> Result { + self.do_continue()?; //need to error handle + let bps = self + .debugging_context + .hit_breakpoints() + .map(|x| self.grp_idx_to_name(x)) + .collect_vec(); + if self.interpreter.is_done() { + Ok(StoppedReason::Done) + } else if !bps.is_empty() { + Ok(StoppedReason::Breakpoint(bps)) + } else { + unreachable!() + } + } + #[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 +224,7 @@ impl + Clone> Debugger { 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 +280,7 @@ impl + Clone> Debugger { pub fn main_loop( mut self, info: Option, - ) -> InterpreterResult { + ) -> CiderResult { let (input_stream, dbg_ctx) = info .map(|x| (Some(x.input_stream), Some(x.ctx))) .unwrap_or_else(|| (None, None)); @@ -268,9 +311,9 @@ impl + Clone> Debugger { 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 +410,15 @@ impl + Clone> Debugger { 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 +441,9 @@ impl + Clone> Debugger { 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 +539,7 @@ impl + Clone> Debugger { 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/debugging_context/context.rs b/cider/src/debugger/debugging_context/context.rs similarity index 98% rename from interp/src/debugger/debugging_context/context.rs rename to cider/src/debugger/debugging_context/context.rs index 7f317d3aaa..6856f5ae05 100644 --- a/interp/src/debugger/debugging_context/context.rs +++ b/cider/src/debugger/debugging_context/context.rs @@ -648,10 +648,8 @@ impl DebuggingContext { let watchpoint_indicies = self.watchpoints.get_by_group(x).unwrap(); match watchpoint_indicies { - WatchPointIndices::Before(x) => return x.iter(), - WatchPointIndices::Both { before, .. } => { - return before.iter() - } + WatchPointIndices::Before(x) => x.iter(), + WatchPointIndices::Both { before, .. } => before.iter(), // this is stupid but works _ => [].iter(), } @@ -665,10 +663,8 @@ impl DebuggingContext { let watchpoint_indicies = self.watchpoints.get_by_group(x).unwrap(); match watchpoint_indicies { - WatchPointIndices::After(x) => return x.iter(), - WatchPointIndices::Both { after, .. } => { - return after.iter() - } + WatchPointIndices::After(x) => x.iter(), + WatchPointIndices::Both { after, .. } => after.iter(), // this is stupid but works _ => [].iter(), } diff --git a/interp/src/debugger/debugging_context/mod.rs b/cider/src/debugger/debugging_context/mod.rs similarity index 100% rename from interp/src/debugger/debugging_context/mod.rs rename to cider/src/debugger/debugging_context/mod.rs diff --git a/interp/src/debugger/io_utils.rs b/cider/src/debugger/io_utils.rs similarity index 81% rename from interp/src/debugger/io_utils.rs rename to cider/src/debugger/io_utils.rs index 9c59fae7ce..6ad58ee0e8 100644 --- a/interp/src/debugger/io_utils.rs +++ b/cider/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 { + pub fn new() -> CiderResult { Ok(Self { buffer: Editor::new()?, command_buffer: VecDeque::default(), }) } - pub fn next_command(&mut self) -> InterpreterResult { + pub fn next_command(&mut self) -> CiderResult { if !self.command_buffer.is_empty() { return Ok(self.command_buffer.pop_front().unwrap()); } diff --git a/interp/src/debugger/macros.rs b/cider/src/debugger/macros.rs similarity index 100% rename from interp/src/debugger/macros.rs rename to cider/src/debugger/macros.rs diff --git a/interp/src/debugger/mod.rs b/cider/src/debugger/mod.rs similarity index 93% rename from interp/src/debugger/mod.rs rename to cider/src/debugger/mod.rs index cf36eecfa2..eb32fb7a8e 100644 --- a/interp/src/debugger/mod.rs +++ b/cider/src/debugger/mod.rs @@ -7,6 +7,7 @@ pub mod source; pub use debugger_core::{ Debugger, DebuggerInfo, DebuggerReturnStatus, OwnedDebugger, ProgramStatus, + StoppedReason, }; pub(crate) use macros::unwrap_error_message; diff --git a/interp/src/debugger/source/metadata.pest b/cider/src/debugger/source/metadata.pest similarity index 100% rename from interp/src/debugger/source/metadata.pest rename to cider/src/debugger/source/metadata.pest diff --git a/interp/src/debugger/source/metadata_parser.rs b/cider/src/debugger/source/metadata_parser.rs similarity index 95% rename from interp/src/debugger/source/metadata_parser.rs rename to cider/src/debugger/source/metadata_parser.rs index ffa38f9bcb..74fb70366b 100644 --- a/interp/src/debugger/source/metadata_parser.rs +++ b/cider/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 { +pub fn parse_metadata(input_str: &str) -> CiderResult { let inputs = MetadataParser::parse(Rule::metadata, input_str)?; let input = inputs.single()?; Ok(MetadataParser::metadata(input)?) diff --git a/interp/src/debugger/source/mod.rs b/cider/src/debugger/source/mod.rs similarity index 100% rename from interp/src/debugger/source/mod.rs rename to cider/src/debugger/source/mod.rs diff --git a/interp/src/debugger/source/new_metadata.pest b/cider/src/debugger/source/new_metadata.pest similarity index 100% rename from interp/src/debugger/source/new_metadata.pest rename to cider/src/debugger/source/new_metadata.pest diff --git a/interp/src/debugger/source/new_parser.rs b/cider/src/debugger/source/new_parser.rs similarity index 96% rename from interp/src/debugger/source/new_parser.rs rename to cider/src/debugger/source/new_parser.rs index 170b13fee5..bc3a7f810c 100644 --- a/interp/src/debugger/source/new_parser.rs +++ b/cider/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 = std::result::Result>; @@ -47,7 +47,7 @@ impl MetadataParser { } } -pub fn parse_metadata(input_str: &str) -> InterpreterResult { +pub fn parse_metadata(input_str: &str) -> CiderResult { 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/cider/src/debugger/source/structures.rs similarity index 92% rename from interp/src/debugger/source/structures.rs rename to cider/src/debugger/source/structures.rs index 44dc3a34bf..c6aeed1af2 100644 --- a/interp/src/debugger/source/structures.rs +++ b/cider/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, - ) -> InterpreterResult> { + pub fn from_file(path: &Option) -> CiderResult> { 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(input: S) -> InterpreterResult + pub fn from_string(input: S) -> CiderResult where S: AsRef, { diff --git a/interp/src/errors.rs b/cider/src/errors.rs similarity index 59% rename from interp/src/errors.rs rename to cider/src/errors.rs index 4bb14f1095..192ef33443 100644 --- a/interp/src/errors.rs +++ b/cider/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 = Result; +use std::fmt::Write; + +/// A type alias for a result with an [BoxedCiderError] as the error type +pub type CiderResult = Result; /// 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); +pub struct BoxedCiderError(Box); -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 + Clone, - >( - mut self, - env: &Environment, - ) -> 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 From for BoxedInterpreterError +impl From for BoxedCiderError where - T: Into, + T: Into, { 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::>().join(", "), dims.iter().map(|x| x.to_string()).collect::>().join(", "))] - InvalidMemoryAccess { - access: Vec, - dims: Vec, - 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 = Result; + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct BoxedRuntimeError(#[from] Box); + +impl> From for BoxedRuntimeError { + fn from(value: Inner) -> Self { + Self(Box::new(value.into())) + } +} + +impl BoxedRuntimeError { + pub fn prettify_message< + C: AsRef + Clone, + >( + self, + env: &Environment, + ) -> 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)>, + ), /// 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::>().join(", "))] + InvalidMemoryAccess { + access: Vec, + 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(Box), } // 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 for InterpreterError { +impl From for CiderError { fn from(e: CalyxError) -> Self { Self::CompilerError(Box::new(e)) } } -impl From for InterpreterError { +impl From for CiderError { fn from(e: CalyxMultiError) -> Self { Self::CompilerMultiError(Box::new(e)) } } -impl From for InterpreterError { +impl From 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 + Clone, >( self, env: &Environment, - ) -> Self { + ) -> CiderError { fn assign_to_string + Clone>( assign: &AssignedValue, env: &Environment, @@ -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,8 @@ impl InterpreterError { } match self { - InterpreterError::FlatConflictingAssignments { target, a1, a2 } => { + RuntimeError::ConflictingAssignments(boxed_err) => { + let ConflictingAssignments { target, a1, a2 } = *boxed_err; let (a1_str, a1_source) = assign_to_string(&a1, env); let (a2_str, a2_source) = assign_to_string(&a2, env); @@ -295,22 +315,50 @@ 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, num) => { + if let Some(entry_number) = num { + CiderError::GenericError(format!("Concurrent read & write to the same memory {} in slot {}", env.get_full_name(c).underline(), entry_number)) + } else { + CiderError::GenericError(format!("Concurrent read & write to the same register {}", env.get_full_name(c).underline())) + } + }, + ClockError::WriteWrite(c, num) => { + if let Some(entry_number) = num { + CiderError::GenericError(format!("Concurrent writes to the same memory {} in slot {}", env.get_full_name(c).underline(), entry_number)) + } else { + CiderError::GenericError(format!("Concurrent writes to the same register {}", 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::>().join(", "), dims.as_string())) + }, + RuntimeError::OverflowError => todo!(), + } } } diff --git a/interp/src/flatten/flat_ir/base.rs b/cider/src/flatten/flat_ir/base.rs similarity index 87% rename from interp/src/flatten/flat_ir/base.rs rename to cider/src/flatten/flat_ir/base.rs index 6326adfe86..bc9b2be456 100644 --- a/interp/src/flatten/flat_ir/base.rs +++ b/cider/src/flatten/flat_ir/base.rs @@ -13,6 +13,7 @@ use crate::{ serialization::PrintCode, }; use baa::{BitVecOps, BitVecValue}; +use std::collections::HashSet; // making these all u32 for now, can give the macro an optional type as the // second arg to contract or expand as needed @@ -22,7 +23,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 +31,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 +39,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 +47,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 +119,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 +401,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,20 +415,38 @@ impl AssignmentWinner { } } -impl From 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) } } /// A struct representing a value that has been assigned to a port. It wraps a /// concrete value and the "winner" which assigned it. -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct AssignedValue { val: BitVecValue, winner: AssignmentWinner, thread: Option, clocks: Option, + propagate_clocks: bool, + transitive_clocks: Option>, +} + +impl AssignedValue { + pub fn eq_no_transitive_clocks(&self, other: &Self) -> bool { + self.val == other.val + && self.winner == other.winner + && self.thread == other.thread + && self.clocks == other.clocks + && self.propagate_clocks == other.propagate_clocks + } } impl std::fmt::Debug for AssignedValue { @@ -436,6 +455,8 @@ impl std::fmt::Debug for AssignedValue { .field("val", &self.val.to_bit_str()) .field("winner", &self.winner) .field("thread", &self.thread) + .field("clocks", &self.clocks) + .field("propagate_clocks", &self.propagate_clocks) .finish() } } @@ -455,9 +476,37 @@ impl AssignedValue { winner: winner.into(), thread: None, clocks: None, + propagate_clocks: false, + transitive_clocks: None, } } + /// Adds a clock to the set of transitive reads associated with this value + pub fn add_transitive_clock(&mut self, clock_pair: ClockPair) { + self.transitive_clocks + .get_or_insert_with(Default::default) + .insert(clock_pair); + } + + pub fn add_transitive_clocks>( + &mut self, + clocks: I, + ) { + self.transitive_clocks + .get_or_insert_with(Default::default) + .extend(clocks); + } + + pub fn iter_transitive_clocks( + &self, + ) -> impl Iterator + '_ { + self.transitive_clocks + .as_ref() + .map(|set| set.iter().copied()) + .into_iter() + .flatten() + } + pub fn with_thread(mut self, thread: ThreadIdx) -> Self { self.thread = Some(thread); self @@ -473,6 +522,31 @@ impl AssignedValue { self } + pub fn with_clocks_optional( + mut self, + clock_pair: Option, + ) -> Self { + self.clocks = clock_pair; + self + } + + pub fn with_transitive_clocks_opt( + mut self, + clocks: Option>, + ) -> Self { + self.transitive_clocks = clocks; + self + } + + pub fn with_propagate_clocks(mut self) -> Self { + self.propagate_clocks = true; + self + } + + pub fn set_propagate_clocks(&mut self, propagate_clocks: bool) { + self.propagate_clocks = propagate_clocks; + } + /// Returns true if the two AssignedValues do not have the same winner pub fn has_conflict_with(&self, other: &Self) -> bool { self.winner != other.winner @@ -491,49 +565,34 @@ impl AssignedValue { /// A utility constructor which returns a new implicitly assigned value with /// a one bit high value pub fn implicit_bit_high() -> Self { - Self { - val: BitVecValue::tru(), - winner: AssignmentWinner::Implicit, - thread: None, - clocks: None, - } + Self::new(BitVecValue::new_true(), AssignmentWinner::Implicit) } /// A utility constructor which returns an [`AssignedValue`] with the given /// value and a [`AssignmentWinner::Cell`] as the winner #[inline] pub fn cell_value(val: BitVecValue) -> Self { - Self { - val, - winner: AssignmentWinner::Cell, - thread: None, - clocks: None, - } + Self::new(val, AssignmentWinner::Cell) } /// A utility constructor which returns an [`AssignedValue`] with the given /// value and a [`AssignmentWinner::Implicit`] as the winner #[inline] pub fn implicit_value(val: BitVecValue) -> Self { - Self { - val, - winner: AssignmentWinner::Implicit, - thread: None, - clocks: None, - } + Self::new(val, AssignmentWinner::Implicit) } /// A utility constructor which returns an [`AssignedValue`] with a one bit /// high value and a [`AssignmentWinner::Cell`] as the winner #[inline] pub fn cell_b_high() -> Self { - Self::cell_value(BitVecValue::tru()) + Self::cell_value(BitVecValue::new_true()) } /// A utility constructor which returns an [`AssignedValue`] with a one bit /// low value and a [`AssignmentWinner::Cell`] as the winner #[inline] pub fn cell_b_low() -> Self { - Self::cell_value(BitVecValue::fals()) + Self::cell_value(BitVecValue::new_false()) } pub fn thread(&self) -> Option { @@ -543,6 +602,14 @@ impl AssignedValue { pub fn clocks(&self) -> Option<&ClockPair> { self.clocks.as_ref() } + + pub fn transitive_clocks(&self) -> Option<&HashSet> { + self.transitive_clocks.as_ref() + } + + pub fn propagate_clocks(&self) -> bool { + self.propagate_clocks + } } #[derive(Debug, Clone, Default)] @@ -552,7 +619,7 @@ pub struct PortValue(Option); 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)) } } @@ -575,6 +642,18 @@ impl PortValue { self.0.as_ref() } + /// Returns a mutable reference to the underlying [`AssignedValue`] if it is + /// defined. Otherwise returns `None`. + pub fn as_option_mut(&mut self) -> Option<&mut AssignedValue> { + self.0.as_mut() + } + + /// Returns the underlying [`AssignedValue`] if it is defined. Otherwise + /// returns `None`. + pub fn into_option(self) -> Option { + self.0 + } + pub fn with_thread(mut self, thread: ThreadIdx) -> Self { if let Some(val) = self.0.as_mut() { val.thread = Some(thread); @@ -589,6 +668,10 @@ impl PortValue { self } + pub fn transitive_clocks(&self) -> Option<&HashSet> { + self.0.as_ref().and_then(|x| x.transitive_clocks()) + } + /// If the value is defined, returns the value cast to a boolean. Otherwise /// returns `None`. It will panic if the given value is not one bit wide. pub fn as_bool(&self) -> Option { @@ -724,6 +807,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 CellDefinitionInfo @@ -736,12 +821,14 @@ where ports: IndexRange, 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/cider/src/flatten/flat_ir/cell_prototype.rs similarity index 96% rename from interp/src/flatten/flat_ir/cell_prototype.rs rename to cider/src/flatten/flat_ir/cell_prototype.rs index d7feea753d..05acd4c3d6 100644 --- a/interp/src/flatten/flat_ir/cell_prototype.rs +++ b/cider/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/cider/src/flatten/flat_ir/component.rs similarity index 73% rename from interp/src/flatten/flat_ir/component.rs rename to cider/src/flatten/flat_ir/component.rs index 1625e618ed..40a97e282c 100644 --- a/interp/src/flatten/flat_ir/component.rs +++ b/cider/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, + /// The entire range of ports defined by this component and any + /// sub-component instances it contains ports: IndexRange, + /// The entire range of ref-cells defined by this component and any + /// sub-component instances it contains ref_cells: IndexRange, + /// The entire range of ref-ports defined by this component and any + /// sub-component instances it contains ref_ports: IndexRange, + /// The entire range of groups defined by this component. Does not include + /// sub-component instances. groups: IndexRange, + /// The entire range of comb-groups defined by this component. Does not + /// include sub-component instances. comb_groups: IndexRange, } @@ -57,21 +79,119 @@ impl Default for DefinitionRanges { } /// A structure which contains the basic information about a component -/// definition needed during simulation. +/// definition needed during simulation. This is for standard (non-combinational) +/// components #[derive(Debug)] pub struct ComponentCore { /// The control program for this component. pub control: Option, /// The set of assignments that are always active. pub continuous_assignments: IndexRange, - /// True iff component is combinational - pub is_comb: bool, /// The go port for this component pub go: LocalPortOffset, /// The done port for this component pub done: LocalPortOffset, } +#[derive(Debug)] +pub struct CombComponentCore { + /// The set of assignments that are always active. + pub continuous_assignments: IndexRange, +} + +impl CombComponentCore { + pub fn contains_assignment( + &self, + assign: AssignmentIdx, + ) -> Option { + self.continuous_assignments + .contains(assign) + .then_some(AssignmentDefinitionLocation::ContinuousAssignment) + } +} + +#[derive(Debug)] +pub enum PrimaryComponentInfo { + Comb(CombComponentCore), + Standard(ComponentCore), +} + +impl PrimaryComponentInfo { + pub fn contains_assignment( + &self, + ctx: &Context, + assign: AssignmentIdx, + ) -> Option { + match self { + PrimaryComponentInfo::Comb(info) => { + info.contains_assignment(assign) + } + PrimaryComponentInfo::Standard(info) => { + info.contains_assignment(ctx, assign) + } + } + } + + #[must_use] + pub fn as_standard(&self) -> Option<&ComponentCore> { + if let Self::Standard(v) = self { + Some(v) + } else { + None + } + } + + pub fn unwrap_standard(&self) -> &ComponentCore { + match self { + PrimaryComponentInfo::Standard(v) => v, + _ => panic!("Expected a standard component"), + } + } + + #[must_use] + pub fn as_comb(&self) -> Option<&CombComponentCore> { + if let Self::Comb(v) = self { + Some(v) + } else { + None + } + } + + /// Returns `true` if the primary component info is [`Comb`]. + /// + /// [`Comb`]: PrimaryComponentInfo::Comb + #[must_use] + pub fn is_comb(&self) -> bool { + matches!(self, Self::Comb(..)) + } + + pub fn continuous_assignments(&self) -> IndexRange { + match self { + PrimaryComponentInfo::Comb(info) => info.continuous_assignments, + PrimaryComponentInfo::Standard(info) => info.continuous_assignments, + } + } + + pub fn control(&self) -> Option { + match self { + PrimaryComponentInfo::Comb(_) => None, + PrimaryComponentInfo::Standard(info) => info.control, + } + } +} + +impl From for PrimaryComponentInfo { + fn from(v: ComponentCore) -> Self { + Self::Standard(v) + } +} + +impl From for PrimaryComponentInfo { + fn from(v: CombComponentCore) -> Self { + Self::Comb(v) + } +} + pub enum AssignmentDefinitionLocation { /// The assignment is contained in a comb group CombGroup(CombGroupIdx), @@ -176,19 +296,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, + /// A map from ref port offsets to their definition indices pub ref_port_offset_map: SparseMap, + /// A map from local cell offsets to their definition indices pub cell_offset_map: SparseMap, + /// A map from ref cell offsets to their definition indices pub ref_cell_offset_map: SparseMap, } @@ -366,4 +487,4 @@ impl IdxSkipSizes { } } -pub type ComponentMap = IndexedMap; +pub type ComponentMap = IndexedMap; diff --git a/interp/src/flatten/flat_ir/control/mod.rs b/cider/src/flatten/flat_ir/control/mod.rs similarity index 100% rename from interp/src/flatten/flat_ir/control/mod.rs rename to cider/src/flatten/flat_ir/control/mod.rs diff --git a/interp/src/flatten/flat_ir/control/structures.rs b/cider/src/flatten/flat_ir/control/structures.rs similarity index 100% rename from interp/src/flatten/flat_ir/control/structures.rs rename to cider/src/flatten/flat_ir/control/structures.rs diff --git a/interp/src/flatten/flat_ir/control/translator.rs b/cider/src/flatten/flat_ir/control/translator.rs similarity index 91% rename from interp/src/flatten/flat_ir/control/translator.rs rename to cider/src/flatten/flat_ir/control/translator.rs index bd82720721..408f89e1c5 100644 --- a/interp/src/flatten/flat_ir/control/translator.rs +++ b/cider/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; @@ -7,7 +9,10 @@ use crate::{ flatten::{ flat_ir::{ cell_prototype::{CellPrototype, ConstantType}, - component::{AuxiliaryComponentInfo, ComponentCore}, + component::{ + AuxiliaryComponentInfo, CombComponentCore, ComponentCore, + PrimaryComponentInfo, + }, flatten_trait::{flatten_tree, FlattenTree, SingleHandle}, prelude::{ Assignment, AssignmentIdx, CellRef, CombGroup, CombGroupIdx, @@ -123,7 +128,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 = HashSet::new(); while let Some(idx) = search_stack.pop() { match &interp_ctx.guards[idx] { @@ -140,17 +145,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 +168,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 +203,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 +217,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 +227,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 +239,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 = if matches!(*ctrl_ref, cir::Control::Empty(_)) { @@ -250,7 +257,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; @@ -285,25 +292,41 @@ fn translate_component( .find_all_with_attr(NumAttr::Done) .collect_vec(); - // Will need to rethink this at some point - if go_ports.len() != 1 || done_ports.len() != 1 { - todo!("handle multiple go and done ports"); - } - let go_port = &go_ports[0]; - let done_port = &done_ports[0]; - - let comp_core = ComponentCore { - control, - continuous_assignments, - is_comb: comp.is_comb, - go: *layout.port_map[&go_port.as_raw()].unwrap_local(), - done: *layout.port_map[&done_port.as_raw()].unwrap_local(), + let comp_info: PrimaryComponentInfo = if comp.is_comb { + assert!( + go_ports.is_empty() && done_ports.is_empty(), + "malformed comb component: {}", + comp.name + ); + + PrimaryComponentInfo::Comb(CombComponentCore { + continuous_assignments, + }) + } else { + let go_port = &go_ports[0]; + let done_port = &done_ports[0]; + + // Will need to rethink this at some point + if go_ports.len() > 1 || done_ports.len() > 1 { + todo!( + "handle multiple go and done ports. On component: {}", + comp.name + ); + } + + let comp_core = ComponentCore { + control, + continuous_assignments, + go: *layout.port_map[&go_port.as_raw()].unwrap_local(), + done: *layout.port_map[&done_port.as_raw()].unwrap_local(), + }; + comp_core.into() }; - let ctrl_ref = ctx.primary.components.push(comp_core); + let ctrl_ref = ctx.primary.components.push(comp_info); 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 +347,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 +387,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 +409,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 +577,12 @@ fn is_primitive(cell_ref: &std::cell::Ref) -> bool { impl FlattenTree for cir::Guard { 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 +609,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/control/utils.rs b/cider/src/flatten/flat_ir/control/utils.rs similarity index 100% rename from interp/src/flatten/flat_ir/control/utils.rs rename to cider/src/flatten/flat_ir/control/utils.rs diff --git a/interp/src/flatten/flat_ir/flatten_trait.rs b/cider/src/flatten/flat_ir/flatten_trait.rs similarity index 93% rename from interp/src/flatten/flat_ir/flatten_trait.rs rename to cider/src/flatten/flat_ir/flatten_trait.rs index cb46088339..4aa500c567 100644 --- a/interp/src/flatten/flat_ir/flatten_trait.rs +++ b/cider/src/flatten/flat_ir/flatten_trait.rs @@ -74,7 +74,7 @@ where handle: &'a mut VecHandle<'outer, In, Idx, Out>, } -impl<'a, 'outer, In, Idx, Out> SingleHandle<'a, 'outer, In, Idx, Out> +impl<'outer, In, Idx, Out> SingleHandle<'_, 'outer, In, Idx, Out> where Idx: IndexRef, { @@ -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( ) -> Idx where Idx: IndexRef, - In: FlattenTree, + In: FlattenTree, { let mut handle = VecHandle::new(vec, root_node, base); let mut root_node_idx: Option = None; diff --git a/interp/src/flatten/flat_ir/identifier.rs b/cider/src/flatten/flat_ir/identifier.rs similarity index 100% rename from interp/src/flatten/flat_ir/identifier.rs rename to cider/src/flatten/flat_ir/identifier.rs diff --git a/interp/src/flatten/flat_ir/mod.rs b/cider/src/flatten/flat_ir/mod.rs similarity index 100% rename from interp/src/flatten/flat_ir/mod.rs rename to cider/src/flatten/flat_ir/mod.rs diff --git a/interp/src/flatten/flat_ir/wires/core.rs b/cider/src/flatten/flat_ir/wires/core.rs similarity index 100% rename from interp/src/flatten/flat_ir/wires/core.rs rename to cider/src/flatten/flat_ir/wires/core.rs diff --git a/interp/src/flatten/flat_ir/wires/guards.rs b/cider/src/flatten/flat_ir/wires/guards.rs similarity index 100% rename from interp/src/flatten/flat_ir/wires/guards.rs rename to cider/src/flatten/flat_ir/wires/guards.rs diff --git a/interp/src/flatten/flat_ir/wires/mod.rs b/cider/src/flatten/flat_ir/wires/mod.rs similarity index 100% rename from interp/src/flatten/flat_ir/wires/mod.rs rename to cider/src/flatten/flat_ir/wires/mod.rs diff --git a/interp/src/flatten/mod.rs b/cider/src/flatten/mod.rs similarity index 100% rename from interp/src/flatten/mod.rs rename to cider/src/flatten/mod.rs diff --git a/interp/src/flatten/primitives/builder.rs b/cider/src/flatten/primitives/builder.rs similarity index 100% rename from interp/src/flatten/primitives/builder.rs rename to cider/src/flatten/primitives/builder.rs diff --git a/interp/src/flatten/primitives/combinational.rs b/cider/src/flatten/primitives/combinational.rs similarity index 87% rename from interp/src/flatten/primitives/combinational.rs rename to cider/src/flatten/primitives/combinational.rs index 9c174f9f6c..3da8b99194 100644 --- a/interp/src/flatten/primitives/combinational.rs +++ b/cider/src/flatten/primitives/combinational.rs @@ -4,13 +4,17 @@ use crate::flatten::{ all_defined, comb_primitive, declare_ports, ports, prim_trait::UpdateStatus, utils::floored_division, Primitive, }, - structures::environment::PortMap, + structures::{ + environment::PortMap, + index_trait::{IndexRef, SplitIndexRange}, + }, }; use baa::{BitVecOps, BitVecValue}; use super::prim_trait::UpdateResult; +#[derive(Clone)] pub struct StdConst { value: BitVecValue, out: GlobalPortIdx, @@ -33,39 +37,44 @@ impl Primitive for StdConst { }) } - fn exec_cycle(&mut self, _port_map: &mut PortMap) -> UpdateResult { - Ok(UpdateStatus::Unchanged) - } - - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { true } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new(self.out, self.out, (self.out.index() + 1).into()) + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } } +#[derive(Clone)] pub struct StdMux { - base: GlobalPortIdx, + base_port: GlobalPortIdx, } impl StdMux { - declare_ports![ COND: 0, TRU: 1, FAL:2, OUT: 3]; + declare_ports![ COND: 0, TRU: 1 | FAL:2, OUT: 3 ]; pub fn new(base: GlobalPortIdx) -> Self { - Self { base } + Self { base_port: base } } } impl Primitive for StdMux { fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { - ports![&self.base; cond: Self::COND, tru: Self::TRU, fal: Self::FAL, out: Self::OUT]; + ports![&self.base_port; cond: Self::COND, tru: Self::TRU, fal: Self::FAL, out: Self::OUT]; let winning_idx = 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(), @@ -77,9 +86,17 @@ impl Primitive for StdMux { } } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } } comb_primitive!(StdNot(input [0]) -> (out [1]) { @@ -275,6 +292,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 +306,12 @@ impl Primitive for StdUndef { port_map.write_undef(self.0)?; Ok(UpdateStatus::Unchanged) } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new(self.0, self.0, (self.0.index() + 1).into()) + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } } diff --git a/interp/src/flatten/primitives/macros.rs b/cider/src/flatten/primitives/macros.rs similarity index 53% rename from interp/src/flatten/primitives/macros.rs rename to cider/src/flatten/primitives/macros.rs index bb1dd95e92..21084c770e 100644 --- a/interp/src/flatten/primitives/macros.rs +++ b/cider/src/flatten/primitives/macros.rs @@ -1,12 +1,70 @@ +// taken from https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + +macro_rules! count_tts { + ($($tts:tt)*) => {0usize $(+ $crate::flatten::primitives::macros::replace_expr!($tts 1usize))*}; +} +// -- https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html + +pub(crate) use count_tts; +pub(crate) use replace_expr; + macro_rules! ports { ($base:expr; $( $port:ident : $offset:expr ),+ ) => { $(let $port: $crate::flatten::flat_ir::prelude::GlobalPortIdx = ($crate::flatten::structures::index_trait::IndexRef::index($base) + $offset).into();)+ } } +/// Declare a list of ports for the primitive with their offsets given relative +/// to the cell's base port.A vertical bar is used to separate the input and +/// output ports +/// +/// ## NOTE: These must be given in ascending order. And the base port of the struct must be named `base_port` +/// +/// ```ignore +/// // Example +/// // declare COND, TRU, FAL as input ports +/// // declare OUT as output port +/// declare_ports![ COND: 0, TRU: 1, FAL:2 | OUT: 3]; +/// ``` macro_rules! declare_ports { - ($( $port:ident : $offset:expr ),+ $(,)? ) => { + ($( $input_port:ident : $input_offset:literal ),+ $(,)? | $( $output_port:ident : $output_offset:literal ),+ $(,)? ) => { + $( + #[allow(non_upper_case_globals)] + const $input_port: usize = $input_offset; // this is a usize because it encodes the position of the port! + )+ + + $( + #[allow(non_upper_case_globals)] + const $output_port: usize = $output_offset; // this is a usize because it encodes the position of the port! + )+ + + // determine the offset of the first and last output ports + const SPLIT_IDX: usize = [$($output_offset),+][0]; + const END_IDX: usize = ([$($output_offset),+][$crate::flatten::primitives::macros::count_tts!($($output_offset)+) - 1]) + 1; + + #[inline] + pub fn get_signature(&self) -> $crate::flatten::structures::index_trait::SplitIndexRange<$crate::flatten::flat_ir::prelude::GlobalPortIdx> { + use $crate::flatten::structures::index_trait::IndexRef; + + $crate::flatten::structures::index_trait::SplitIndexRange::new(self.base_port, + $crate::flatten::flat_ir::prelude::GlobalPortIdx::new(self.base_port.index() + Self::SPLIT_IDX), + $crate::flatten::flat_ir::prelude::GlobalPortIdx::new(self.base_port.index() + Self::END_IDX), + ) + } + } +} + +/// Declare a list of ports for the primitive with their offsets given relative +/// to the cell's base port. Unlike `declare_ports` this does not generate a +/// `get_signature` function or distinguish between input and output ports. +macro_rules! declare_ports_no_signature { + ($( $port:ident : $offset:literal ),+ $(,)? ) => { $( #[allow(non_upper_case_globals)] const $port: usize = $offset; // this is a usize because it encodes the position of the port! @@ -27,6 +85,7 @@ macro_rules! make_getters { } pub(crate) use declare_ports; +pub(crate) use declare_ports_no_signature; pub(crate) use make_getters; pub(crate) use ports; @@ -46,8 +105,7 @@ macro_rules! comb_primitive { impl $name { - $crate::flatten::primitives::macros::declare_ports![$($port: $port_idx),+]; - $crate::flatten::primitives::macros::declare_ports![$out_port: $out_port_idx,]; + $crate::flatten::primitives::macros::declare_ports![$($port: $port_idx),+ | $out_port: $out_port_idx]; #[allow(non_snake_case)] pub fn new( @@ -74,7 +132,7 @@ macro_rules! comb_primitive { #[allow(non_snake_case)] - let exec_func = |$($($param: u32,)+)? $($port: &$crate::flatten::flat_ir::prelude::PortValue),+| ->$crate::errors::InterpreterResult> { + let exec_func = |$($($param: u32,)+)? $($port: &$crate::flatten::flat_ir::prelude::PortValue),+| ->$crate::errors::RuntimeResult> { $execute }; @@ -98,10 +156,20 @@ macro_rules! comb_primitive { } } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + fn get_ports(&self) -> $crate::flatten::structures::index_trait::SplitIndexRange<$crate::flatten::flat_ir::prelude::GlobalPortIdx> { + self.get_signature() + } + + fn clone_boxed(&self) -> Box { + Box::new(Self { + base_port: self.base_port, + $($($param: self.$param,)+)? + }) + } } }; diff --git a/interp/src/flatten/primitives/mod.rs b/cider/src/flatten/primitives/mod.rs similarity index 90% rename from interp/src/flatten/primitives/mod.rs rename to cider/src/flatten/primitives/mod.rs index 935e2add53..25c5c0706a 100644 --- a/interp/src/flatten/primitives/mod.rs +++ b/cider/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/cider/src/flatten/primitives/prim_trait.rs similarity index 84% rename from interp/src/flatten/primitives/prim_trait.rs rename to cider/src/flatten/primitives/prim_trait.rs index 80f4852fc0..4a94324485 100644 --- a/interp/src/flatten/primitives/prim_trait.rs +++ b/cider/src/flatten/primitives/prim_trait.rs @@ -1,9 +1,10 @@ use crate::{ - errors::InterpreterResult, + errors::RuntimeResult, flatten::{ flat_ir::base::GlobalPortIdx, structures::{ environment::{clock::ClockMap, PortMap}, + index_trait::SplitIndexRange, thread::ThreadMap, }, }, @@ -102,22 +103,22 @@ impl std::ops::BitOrAssign for UpdateStatus { } } -pub type UpdateResult = InterpreterResult; +pub type UpdateResult = RuntimeResult; pub trait Primitive { fn exec_comb(&self, _port_map: &mut PortMap) -> UpdateResult { Ok(UpdateStatus::Unchanged) } - fn exec_cycle(&mut self, _port_map: &mut PortMap) -> UpdateResult { - Ok(UpdateStatus::Unchanged) + fn exec_cycle(&mut self, _port_map: &mut PortMap) -> RuntimeResult<()> { + Ok(()) } - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { true } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { true } @@ -134,6 +135,15 @@ pub trait Primitive { fn dump_memory_state(&self) -> Option> { None } + + fn get_ports(&self) -> SplitIndexRange; + + /// Returns `true` if this primitive only has a combinational part + fn is_combinational(&self) -> bool { + self.has_comb_path() && !self.has_stateful_path() + } + + fn clone_boxed(&self) -> Box; } pub trait RaceDetectionPrimitive: Primitive { @@ -151,23 +161,13 @@ pub trait RaceDetectionPrimitive: Primitive { port_map: &mut PortMap, _clock_map: &mut ClockMap, _thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { self.exec_cycle(port_map) } /// Get a reference to the underlying primitive. Unfortunately cannot add an /// optional default implementation due to size rules fn as_primitive(&self) -> &dyn Primitive; -} - -/// An empty primitive implementation used for testing. It does not do anything -/// and has no ports of any kind -pub struct DummyPrimitive; -impl DummyPrimitive { - pub fn new_dyn() -> Box { - Box::new(Self) - } + fn clone_boxed_rd(&self) -> Box; } - -impl Primitive for DummyPrimitive {} diff --git a/interp/src/flatten/primitives/stateful/math.rs b/cider/src/flatten/primitives/stateful/math.rs similarity index 84% rename from interp/src/flatten/primitives/stateful/math.rs rename to cider/src/flatten/primitives/stateful/math.rs index e22d85e92b..132445ecaa 100644 --- a/interp/src/flatten/primitives/stateful/math.rs +++ b/cider/src/flatten/primitives/stateful/math.rs @@ -1,15 +1,19 @@ -use crate::flatten::{ - flat_ir::prelude::*, - primitives::{ - declare_ports, ports, - prim_trait::*, - utils::{floored_division, int_sqrt, ShiftBuffer}, +use crate::{ + errors::RuntimeResult, + flatten::{ + flat_ir::prelude::*, + primitives::{ + declare_ports, ports, + prim_trait::*, + utils::{floored_division, int_sqrt, ShiftBuffer}, + }, + structures::{environment::PortMap, index_trait::SplitIndexRange}, }, - structures::environment::PortMap, }; use baa::{BitVecOps, BitVecValue, WidthInt}; use num_traits::Euclid; +#[derive(Clone)] pub struct StdMultPipe { base_port: GlobalPortIdx, pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>, @@ -19,7 +23,7 @@ pub struct StdMultPipe { } impl StdMultPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT: 5, DONE: 6]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT: 5, DONE: 6]; pub fn new(base_port: GlobalPortIdx, width: u32) -> Self { Self { base_port, @@ -32,25 +36,29 @@ impl StdMultPipe { } impl Primitive for StdMultPipe { + fn clone_boxed(&self) -> Box { + 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() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; Ok(out_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -90,22 +98,26 @@ impl Primitive for StdMultPipe { self.done_is_high = false; } - let done_signal = port_map.insert_val( + port_map.insert_val_general( done, AssignedValue::cell_value(if self.done_is_high { - BitVecValue::tru() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; - Ok( - port_map.write_exact_unchecked(out, self.current_output.clone()) - | done_signal, - ) + port_map.write_exact_unchecked(out, self.current_output.clone()); + + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } +#[derive(Clone)] pub struct StdDivPipe { base_port: GlobalPortIdx, pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>, @@ -116,7 +128,7 @@ pub struct StdDivPipe { } impl StdDivPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT_QUOTIENT: 5, OUT_REMAINDER: 6, DONE: 7]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT_QUOTIENT: 5, OUT_REMAINDER: 6, DONE: 7]; pub fn new(base_port: GlobalPortIdx, width: u32) -> Self { Self { base_port, @@ -132,6 +144,10 @@ impl StdDivPipe { impl Primitive for StdDivPipe { + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { ports![&self.base_port; out_quot: Self::OUT_QUOTIENT, @@ -148,7 +164,7 @@ impl Primitive Ok(quot_changed | rem_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -216,13 +232,15 @@ impl Primitive self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let quot_changed = port_map - .write_exact_unchecked(out_quot, self.output_quotient.clone()); - let rem_changed = port_map - .write_exact_unchecked(out_rem, self.output_remainder.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out_quot, self.output_quotient.clone()); + port_map.write_exact_unchecked(out_rem, self.output_remainder.clone()); - Ok(quot_changed | rem_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } @@ -236,7 +254,7 @@ pub struct Sqrt { } impl Sqrt { - declare_ports!(_CLK: 0, RESET: 1, GO: 2, IN: 3, OUT: 4, DONE: 5); + declare_ports!(_CLK: 0, RESET: 1, GO: 2, IN: 3, | OUT: 4, DONE: 5); pub fn new( base_port: GlobalPortIdx, width: u32, @@ -253,6 +271,10 @@ impl Sqrt { } impl Primitive for Sqrt { + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { ports![&self.base_port; out: Self::OUT, done: Self::DONE]; @@ -263,7 +285,7 @@ impl Primitive for Sqrt { Ok(out_changed | done_changed) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; reset: Self::RESET, go: Self::GO, @@ -301,14 +323,18 @@ impl Primitive for Sqrt { self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let out_changed = - port_map.write_exact_unchecked(out, self.output.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out, self.output.clone()); - Ok(out_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } +#[derive(Clone)] pub struct FxpMultPipe { base_port: GlobalPortIdx, pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>, @@ -319,7 +345,7 @@ pub struct FxpMultPipe { } impl FxpMultPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT: 5, DONE: 6]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT: 5, DONE: 6]; pub fn new( base_port: GlobalPortIdx, int_width: u32, @@ -339,25 +365,29 @@ impl FxpMultPipe { } impl Primitive for FxpMultPipe { + fn clone_boxed(&self) -> Box { + 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() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; Ok(out_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -406,22 +436,25 @@ impl Primitive for FxpMultPipe { self.done_is_high = false; } - let done_signal = port_map.insert_val( + port_map.insert_val_general( done, AssignedValue::cell_value(if self.done_is_high { - BitVecValue::tru() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; + port_map.write_exact_unchecked(out, self.current_output.clone()); + + Ok(()) + } - Ok( - port_map.write_exact_unchecked(out, self.current_output.clone()) - | done_signal, - ) + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } +#[derive(Clone)] pub struct FxpDivPipe { base_port: GlobalPortIdx, pipeline: ShiftBuffer<(PortValue, PortValue), DEPTH>, @@ -433,7 +466,7 @@ pub struct FxpDivPipe { } impl FxpDivPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT_REMAINDER: 5, OUT_QUOTIENT: 6, DONE: 7]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT_REMAINDER: 5, OUT_QUOTIENT: 6, DONE: 7]; pub fn new( base_port: GlobalPortIdx, int_width: u32, @@ -460,6 +493,10 @@ impl FxpDivPipe { impl Primitive for FxpDivPipe { + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { ports![&self.base_port; out_quot: Self::OUT_QUOTIENT, @@ -476,7 +513,7 @@ impl Primitive Ok(quot_changed | rem_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -546,12 +583,14 @@ impl Primitive self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let quot_changed = port_map - .write_exact_unchecked(out_quot, self.output_quotient.clone()); - let rem_changed = port_map - .write_exact_unchecked(out_rem, self.output_remainder.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out_quot, self.output_quotient.clone()); + port_map.write_exact_unchecked(out_rem, self.output_remainder.clone()); - Ok(quot_changed | rem_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } diff --git a/interp/src/flatten/primitives/stateful/memories.rs b/cider/src/flatten/primitives/stateful/memories.rs similarity index 65% rename from interp/src/flatten/primitives/stateful/memories.rs rename to cider/src/flatten/primitives/stateful/memories.rs index de5bd12a6f..c8e396f371 100644 --- a/interp/src/flatten/primitives/stateful/memories.rs +++ b/cider/src/flatten/primitives/stateful/memories.rs @@ -1,24 +1,24 @@ use itertools::Itertools; use crate::{ - errors::InterpreterError, + errors::{RuntimeError, RuntimeResult}, flatten::{ flat_ir::{ base::GlobalCellIdx, prelude::{AssignedValue, GlobalPortIdx, PortValue}, }, primitives::{ - declare_ports, make_getters, ports, + declare_ports, declare_ports_no_signature, make_getters, ports, prim_trait::{RaceDetectionPrimitive, UpdateResult, UpdateStatus}, utils::infer_thread_id, Primitive, }, structures::{ environment::{ - clock::{new_clock, ClockMap, ValueWithClock}, + clock::{new_clock_pair, ClockMap, ValueWithClock}, PortMap, }, - index_trait::IndexRef, + index_trait::{IndexRef, SplitIndexRange}, thread::{ThreadIdx, ThreadMap}, }, }, @@ -27,6 +27,7 @@ use crate::{ use baa::{BitVecOps, BitVecValue, WidthInt}; +#[derive(Clone)] pub struct StdReg { base_port: GlobalPortIdx, internal_state: ValueWithClock, @@ -35,7 +36,7 @@ pub struct StdReg { } impl StdReg { - declare_ports![IN: 0, WRITE_EN: 1, _CLK: 2, RESET: 3, OUT: 4, DONE: 5]; + declare_ports![IN: 0, WRITE_EN: 1, _CLK: 2, RESET: 3, | OUT: 4, DONE: 5]; pub fn new( base_port: GlobalPortIdx, @@ -43,8 +44,10 @@ impl StdReg { width: u32, clocks: &mut Option<&mut ClockMap>, ) -> Self { - let internal_state = - ValueWithClock::zero(width, new_clock(clocks), new_clock(clocks)); + let internal_state = ValueWithClock::zero( + width, + new_clock_pair(clocks, global_idx, None), + ); Self { base_port, global_idx, @@ -55,7 +58,11 @@ impl StdReg { } impl Primitive for StdReg { - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; input: Self::IN, write_en: Self::WRITE_EN, @@ -64,40 +71,40 @@ impl Primitive for StdReg { done: Self::DONE ]; - let done_port = if port_map[reset].as_bool().unwrap_or_default() { + 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()), + AssignedValue::cell_value(BitVecValue::new_false()), )? } 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()), + AssignedValue::cell_value(BitVecValue::new_true()), )? } else { self.done_is_high = false; - port_map.insert_val( + port_map.insert_val_general( done, - AssignedValue::cell_value(BitVecValue::fals()), + AssignedValue::cell_value(BitVecValue::new_false()), )? }; - Ok(done_port - | port_map.insert_val( - out_idx, - AssignedValue::cell_value(self.internal_state.value.clone()) - .with_clocks(self.internal_state.clocks), - )?) + port_map.insert_val_general( + out_idx, + AssignedValue::cell_value(self.internal_state.value.clone()) + .with_clocks(self.internal_state.clocks), + )?; + Ok(()) } fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { @@ -105,17 +112,17 @@ 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() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; @@ -136,9 +143,17 @@ impl Primitive for StdReg { fn dump_memory_state(&self) -> Option> { Some(self.internal_state.value.clone().to_bytes_le()) } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() + } } impl RaceDetectionPrimitive for StdReg { + fn clone_boxed_rd(&self) -> Box { + Box::new(self.clone()) + } + fn as_primitive(&self) -> &dyn Primitive { self } @@ -148,7 +163,7 @@ impl RaceDetectionPrimitive for StdReg { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { ports![&self.base_port; input: Self::IN, write_en: Self::WRITE_EN, @@ -169,13 +184,14 @@ impl RaceDetectionPrimitive for StdReg { self.internal_state .clocks .check_write(current_clock_idx, clock_map) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| e.add_cell_info(self.global_idx, None))?; } self.exec_cycle(port_map) } } +#[derive(Clone)] pub struct MemDx { shape: Shape, } @@ -190,18 +206,69 @@ impl MemDx { } } - declare_ports![ + declare_ports_no_signature![ SEQ_ADDR0: 2, COMB_ADDR0: 0, SEQ_ADDR1: 3, COMB_ADDR1: 1, SEQ_ADDR2: 4, COMB_ADDR2: 2, SEQ_ADDR3: 5, COMB_ADDR3: 3 ]; + pub fn addr_as_vec( + &self, + port_map: &PortMap, + base_port: GlobalPortIdx, + ) -> Option> { + 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 { + cell_idx: GlobalCellIdx, + ) -> RuntimeResult> { let (addr0, addr1, addr2, addr3) = if SEQ { ports![&base_port; addr0: Self::SEQ_ADDR0, @@ -221,6 +288,36 @@ impl MemDx { (addr0, addr1, addr2, addr3) }; + let option: Option = + 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 { 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 +405,7 @@ impl MemDx { } } +#[derive(Clone)] pub struct CombMem { base_port: GlobalPortIdx, internal_state: Vec, @@ -320,7 +418,7 @@ pub struct CombMem { global_idx: GlobalCellIdx, } impl CombMem { - declare_ports![ + declare_ports_no_signature![ WRITE_DATA:0, WRITE_EN: 1, _CLK: 2, @@ -350,11 +448,10 @@ impl CombMem { { let shape = size.into(); let mut internal_state = Vec::with_capacity(shape.size()); - for _ in 0..shape.size() { + for i in 0_usize..shape.size() { internal_state.push(ValueWithClock::zero( width, - new_clock(clocks), - new_clock(clocks), + new_clock_pair(clocks, global_idx, Some(i.try_into().unwrap())), )); } @@ -387,8 +484,16 @@ impl CombMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) - .map(|x| { - ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + .enumerate() + .map(|(i, x)| { + ValueWithClock::new( + x, + new_clock_pair( + clocks, + global_idx, + Some(i.try_into().unwrap()), + ), + ) }) .collect_vec(); @@ -427,15 +532,23 @@ impl CombMem { } impl Primitive for CombMem { + fn clone_boxed(&self) -> Box { + 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 = 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,52 +563,56 @@ 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() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; Ok(done_signal | read) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { // These two behave like false when undefined 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))?; + if write_en && !reset { + 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( + port_map.insert_val_general( read_data, AssignedValue::cell_value( self.internal_state[addr].value.clone(), ) .with_clocks(self.internal_state[addr].clocks), - )? | done) + )?; } else { port_map.write_undef(read_data)?; - Ok(done) } + Ok(()) } fn serialize(&self, code: Option) -> Serializable { @@ -517,9 +634,21 @@ impl Primitive for CombMem { fn dump_memory_state(&self) -> Option> { Some(self.dump_data()) } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new( + self.base_port, + self.read_data(), + (self.done().index() + 1).into(), + ) + } } impl RaceDetectionPrimitive for CombMem { + fn clone_boxed_rd(&self) -> Box { + Box::new(self.clone()) + } + fn as_primitive(&self) -> &dyn Primitive { self } @@ -553,22 +682,35 @@ impl RaceDetectionPrimitive for CombMem { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { 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, + Some(addr.try_into().unwrap()), + ) + })?; + } + } 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 +719,38 @@ impl RaceDetectionPrimitive for CombMem { } } +#[derive(Copy, Clone, Debug)] +enum MemOut { + /// Points to a valid address in the memory + Valid(usize), + /// Output is zero, but not a memory address + Zero, + /// Output is undefined + Undef, +} + +impl MemOut { + fn is_def(&self) -> bool { + match self { + MemOut::Valid(_) | MemOut::Zero => true, + MemOut::Undef => false, + } + } + + fn get_value(&self, data: &[ValueWithClock]) -> PortValue { + match self { + MemOut::Valid(addr) => { + PortValue::new_cell(data[*addr].value.clone()) + } + MemOut::Zero => { + PortValue::new_cell(BitVecValue::zero(data[0].value.width())) + } + MemOut::Undef => PortValue::new_undef(), + } + } +} + +#[derive(Clone)] pub struct SeqMem { base_port: GlobalPortIdx, internal_state: Vec, @@ -584,10 +758,10 @@ pub struct SeqMem { // TODO griffin: This bool is unused in the actual struct and should either // be removed or _allow_invalid_access: bool, - width: u32, addresser: MemDx, done_is_high: bool, - read_out: PortValue, + // memory index which is currently latched + read_out: MemOut, } impl SeqMem { @@ -601,11 +775,10 @@ impl SeqMem { ) -> Self { let shape = size.into(); let mut internal_state = Vec::with_capacity(shape.size()); - for _ in 0..shape.size() { + for i in 0_usize..shape.size() { internal_state.push(ValueWithClock::zero( width, - new_clock(clocks), - new_clock(clocks), + new_clock_pair(clocks, global_idx, Some(i.try_into().unwrap())), )); } @@ -613,10 +786,9 @@ impl SeqMem { base_port: base, internal_state, _allow_invalid_access: allow_invalid, - width, addresser: MemDx::new(shape), done_is_high: false, - read_out: PortValue::new_undef(), + read_out: MemOut::Undef, global_idx, } } @@ -639,8 +811,16 @@ impl SeqMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) - .map(|x| { - ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + .enumerate() + .map(|(i, x)| { + ValueWithClock::new( + x, + new_clock_pair( + clocks, + global_idx, + Some(i.try_into().unwrap()), + ), + ) }) .collect_vec(); @@ -654,22 +834,21 @@ impl SeqMem { base_port, internal_state, _allow_invalid_access: allow_invalid, - width, addresser: MemDx::new(size), done_is_high: false, - read_out: PortValue::new_undef(), + read_out: MemOut::Undef, global_idx, } } - declare_ports![ + declare_ports_no_signature![ _CLK: 0, RESET: 1, ]; // these port offsets are placed after the address ports and so need the end // of the address base to work correctly. - declare_ports![ + declare_ports_no_signature![ CONTENT_ENABLE: 0, WRITE_ENABLE: 1, WRITE_DATA: 2, @@ -715,22 +894,29 @@ impl SeqMem { } impl Primitive for SeqMem { + fn clone_boxed(&self) -> Box { + 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() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), )?; 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(), + self.read_out + .get_value(&self.internal_state) + .into_option() + .unwrap(), )? } else { UpdateStatus::Unchanged @@ -739,56 +925,60 @@ impl Primitive for SeqMem { Ok(done_signal | out_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { let reset = port_map[self.reset()].as_bool().unwrap_or_default(); let write_en = port_map[self.write_enable()].as_bool().unwrap_or_default(); 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; - self.read_out = PortValue::new_cell(BitVecValue::zero(self.width)); + self.read_out = MemOut::Zero; } 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))?; + self.read_out = MemOut::Undef; + 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))?; - self.read_out = PortValue::new_cell( - self.internal_state[addr_actual].value.clone(), - ); + let addr_actual = + addr.ok_or(RuntimeError::UndefinedReadAddr(self.global_idx))?; + self.read_out = MemOut::Valid(addr_actual); } else { self.done_is_high = false; } - let done_changed = port_map.insert_val( + port_map.insert_val_general( self.done(), AssignedValue::cell_value(if self.done_is_high { - BitVecValue::tru() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), + )?; + port_map.write_exact_unchecked( + self.read_data(), + self.read_out.get_value(&self.internal_state), ); - Ok(done_changed? - | port_map - .write_exact_unchecked(self.read_data(), self.read_out.clone())) + Ok(()) } - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { false } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { true } @@ -811,9 +1001,21 @@ impl Primitive for SeqMem { fn dump_memory_state(&self) -> Option> { Some(self.dump_data()) } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new( + self.base_port, + self.read_data(), + (self.done().index() + 1).into(), + ) + } } impl RaceDetectionPrimitive for SeqMem { + fn clone_boxed_rd(&self) -> Box { + Box::new(self.clone()) + } + fn as_primitive(&self) -> &dyn Primitive { self } @@ -832,11 +1034,13 @@ impl RaceDetectionPrimitive for SeqMem { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { 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)); @@ -855,7 +1059,12 @@ impl RaceDetectionPrimitive for SeqMem { ), clock_map, ) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| { + e.add_cell_info( + self.global_idx, + Some(addr.try_into().unwrap()), + ) + })?; } else if port_map[self.content_enable()] .as_bool() .unwrap_or_default() @@ -870,7 +1079,12 @@ impl RaceDetectionPrimitive for SeqMem { ), clock_map, ) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| { + e.add_cell_info( + self.global_idx, + Some(addr.try_into().unwrap()), + ) + })?; } } } diff --git a/interp/src/flatten/primitives/stateful/mod.rs b/cider/src/flatten/primitives/stateful/mod.rs similarity index 100% rename from interp/src/flatten/primitives/stateful/mod.rs rename to cider/src/flatten/primitives/stateful/mod.rs diff --git a/interp/src/flatten/primitives/utils.rs b/cider/src/flatten/primitives/utils.rs similarity index 99% rename from interp/src/flatten/primitives/utils.rs rename to cider/src/flatten/primitives/utils.rs index 6b91e17a3c..51ab75fd58 100644 --- a/interp/src/flatten/primitives/utils.rs +++ b/cider/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 { buffer: VecDeque>, } diff --git a/interp/src/flatten/setup.rs b/cider/src/flatten/setup.rs similarity index 83% rename from interp/src/flatten/setup.rs rename to cider/src/flatten/setup.rs index 72b99e0ee1..6a10994a5f 100644 --- a/interp/src/flatten/setup.rs +++ b/cider/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)> { +) -> CiderResult<(Context, CiderResult)> { // 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, lib_path: &Path, skip_verification: bool, -) -> InterpreterResult { +) -> CiderResult { let (ctx, _) = do_setup(file, lib_path, skip_verification, false)?; Ok(ctx) } @@ -70,7 +68,7 @@ pub fn setup_simulation_with_metadata( file: &Option, 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/cider/src/flatten/structures/context.rs similarity index 96% rename from interp/src/flatten/structures/context.rs rename to cider/src/flatten/structures/context.rs index 5ed19d5e48..eaa2dde318 100644 --- a/interp/src/flatten/structures/context.rs +++ b/cider/src/flatten/structures/context.rs @@ -1,10 +1,12 @@ use std::ops::Index; +use calyx_ir::Direction; + use crate::flatten::flat_ir::{ cell_prototype::CellPrototype, component::{ - AssignmentDefinitionLocation, AuxiliaryComponentInfo, ComponentCore, - ComponentMap, + AssignmentDefinitionLocation, AuxiliaryComponentInfo, ComponentMap, + PrimaryComponentInfo, }, identifier::IdMap, prelude::{ @@ -51,7 +53,7 @@ pub struct InterpretationContext { } impl Index for InterpretationContext { - type Output = ComponentCore; + type Output = PrimaryComponentInfo; fn index(&self, index: ComponentIdx) -> &Self::Output { &self.components[index] @@ -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, 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, 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/cider/src/flatten/structures/environment/assignments.rs b/cider/src/flatten/structures/environment/assignments.rs new file mode 100644 index 0000000000..2306404b1e --- /dev/null +++ b/cider/src/flatten/structures/environment/assignments.rs @@ -0,0 +1,103 @@ +use crate::flatten::{ + flat_ir::prelude::{GlobalCellIdx, LocalPortOffset}, + structures::thread::{ThreadIdx, ThreadMap}, +}; + +use super::env::AssignmentRange; + +#[derive(Debug)] +pub struct GroupInterfacePorts { + pub go: LocalPortOffset, + pub done: LocalPortOffset, +} + +/// An enum describing the source of a set of assignments +#[derive(Debug, Clone, Copy)] +pub enum AssignType { + /// Assignments come from a comb group + Combinational, + /// Assignments are continuous + Continuous, + /// Assignments come from a group or invoke + Control, +} + +impl AssignType { + /// Returns `true` if the assign source is [`Combinational`]. + /// + /// [`Combinational`]: AssignType::Combinational + #[must_use] + pub fn is_combinational(&self) -> bool { + matches!(self, Self::Combinational) + } + + /// Returns `true` if the assign source is [`Continuous`]. + /// + /// [`Continuous`]: AssignType::Continuous + #[must_use] + pub fn is_continuous(&self) -> bool { + matches!(self, Self::Continuous) + } + + /// Returns `true` if the assign type is [`Control`]. + /// + /// [`Control`]: AssignType::Control + #[must_use] + pub fn is_control(&self) -> bool { + matches!(self, Self::Control) + } +} + +/// A group of assignments that is scheduled to be evaluated +#[derive(Debug)] +pub struct ScheduledAssignments { + pub active_cell: GlobalCellIdx, + pub assignments: AssignmentRange, + pub interface_ports: Option, + pub thread: Option, + pub assign_type: AssignType, +} + +impl ScheduledAssignments { + pub fn new_control( + active_cell: GlobalCellIdx, + assignments: AssignmentRange, + interface_ports: Option, + thread: Option, + ) -> Self { + Self { + active_cell, + assignments, + interface_ports, + thread, + assign_type: AssignType::Control, + } + } + + pub fn new_combinational( + active_cell: GlobalCellIdx, + assignments: AssignmentRange, + ) -> Self { + Self { + active_cell, + assignments, + interface_ports: None, + thread: None, + assign_type: AssignType::Combinational, + } + } + + pub fn new_continuous( + active_cell: GlobalCellIdx, + assignments: AssignmentRange, + ) -> Self { + Self { + active_cell, + assignments, + interface_ports: None, + // all continuous assignments are executed under a single control thread + thread: Some(ThreadMap::continuous_thread()), + assign_type: AssignType::Continuous, + } + } +} diff --git a/interp/src/flatten/structures/environment/clock.rs b/cider/src/flatten/structures/environment/clock.rs similarity index 78% rename from interp/src/flatten/structures/environment/clock.rs rename to cider/src/flatten/structures/environment/clock.rs index 9d30eabc24..1ba27735da 100644 --- a/interp/src/flatten/structures/environment/clock.rs +++ b/cider/src/flatten/structures/environment/clock.rs @@ -3,18 +3,18 @@ use std::{ collections::HashMap, hash::Hash, num::NonZeroU32, + ops::{Index, IndexMut}, }; use crate::flatten::{ flat_ir::base::GlobalCellIdx, structures::{ - index_trait::{impl_index_nonzero, IndexRef}, - indexed_map::IndexedMap, + index_trait::impl_index_nonzero, indexed_map::IndexedMap, thread::ThreadIdx, }, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ClockIdx(NonZeroU32); impl_index_nonzero!(ClockIdx); @@ -22,18 +22,74 @@ use baa::BitVecValue; use itertools::Itertools; use thiserror::Error; -pub type ClockMap = IndexedMap>; pub type ThreadClockPair = (ThreadIdx, ClockIdx); +#[derive(Debug, Clone)] +pub struct ClockPairInfo { + /// The cell that this clock pair was generated for + pub attached_cell: GlobalCellIdx, + /// An optional entry number within the given cell. This is used for + /// memories but not for registers + pub entry_number: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct ClockMap { + clocks: IndexedMap>, + reverse_map: HashMap, +} + impl ClockMap { + pub fn new() -> Self { + Self::default() + } + /// pushes a new clock into the map and returns its index pub fn new_clock(&mut self) -> ClockIdx { - self.push(VectorClock::new()) + self.clocks.push(VectorClock::new()) + } + + pub fn new_clock_pair(&mut self) -> ClockPair { + let read = self.new_clock(); + let write = self.new_clock(); + ClockPair::new(read, write) + } + + pub fn insert_reverse_entry( + &mut self, + pair: ClockPair, + cell: GlobalCellIdx, + entry_number: Option, + ) { + self.reverse_map.insert( + pair, + ClockPairInfo { + attached_cell: cell, + entry_number, + }, + ); + } + + pub fn lookup_cell(&self, pair: ClockPair) -> Option<&ClockPairInfo> { + self.reverse_map.get(&pair) } /// Returns a new clock that is the clone of the given clock pub fn fork_clock(&mut self, parent: ClockIdx) -> ClockIdx { - self.push(self[parent].clone()) + self.clocks.push(self.clocks[parent].clone()) + } +} + +impl Index for ClockMap { + type Output = VectorClock; + fn index(&self, index: ClockIdx) -> &Self::Output { + &self.clocks[index] + } +} + +impl IndexMut for ClockMap { + fn index_mut(&mut self, index: ClockIdx) -> &mut Self::Output { + &mut self.clocks[index] } } @@ -86,11 +142,18 @@ impl Counter for u128 { /// If the clock map is provided, use it to create a new clock. Otherwise, /// return the 0th clock idx. -pub fn new_clock(clock_map: &mut Option<&mut ClockMap>) -> ClockIdx { - clock_map - .as_mut() - .map(|c| c.new_clock()) - .unwrap_or(ClockIdx::new(0)) +pub fn new_clock_pair( + clock_map: &mut Option<&mut ClockMap>, + cell: GlobalCellIdx, + entry_number: Option, +) -> ClockPair { + if let Some(map) = clock_map { + let pair = map.new_clock_pair(); + map.insert_reverse_entry(pair, cell, entry_number); + pair + } else { + ClockPair::zero() + } } /// A simple vector clock implementation. @@ -294,38 +357,40 @@ pub struct ValueWithClock { } impl ValueWithClock { - pub fn zero( - width: u32, - reading_clock: ClockIdx, - writing_clock: ClockIdx, - ) -> Self { + pub fn zero(width: u32, clocks: ClockPair) -> Self { Self { value: BitVecValue::zero(width), - clocks: ClockPair::new(reading_clock, writing_clock), + clocks, } } - pub fn new( - value: BitVecValue, - write_clock: ClockIdx, - read_clock: ClockIdx, - ) -> Self { + pub fn new(value: BitVecValue, clock_pair: ClockPair) -> Self { Self { value, - clocks: ClockPair::new(read_clock, write_clock), + clocks: clock_pair, } } } /// A struct containing the read and write clocks for a value. This is small /// enough to be copied around easily -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ClockPair { pub read_clock: ClockIdx, pub write_clock: ClockIdx, } impl ClockPair { + /// Returns a new clock pair where both indices point to the zero clock. + /// This should only be used as a placeholder entry for when clocks are not + /// actually being tracked. + pub fn zero() -> Self { + Self { + read_clock: ClockIdx::from(0), + write_clock: ClockIdx::from(0), + } + } + pub fn new(read_clock: ClockIdx, write_clock: ClockIdx) -> Self { Self { read_clock, @@ -356,6 +421,18 @@ impl ClockPair { } } + /// A wrapper method which checks the read and adds cell info on an error + pub fn check_read_w_cell( + &self, + (thread, reading_clock): ThreadClockPair, + clock_map: &mut ClockMap, + cell: GlobalCellIdx, + entry_number: Option, + ) -> Result<(), ClockError> { + self.check_read((thread, reading_clock), clock_map) + .map_err(|e| e.add_cell_info(cell, entry_number)) + } + pub fn check_write( &self, writing_clock: ClockIdx, @@ -371,6 +448,7 @@ impl ClockPair { .partial_cmp(&clock_map[self.read_clock]) .is_none() { + // dbg!(&clock_map[writing_clock], &clock_map[self.read_clock]); Err(ClockError::ReadWriteUnhelpful) } else if clock_map[writing_clock] .partial_cmp(&clock_map[self.write_clock]) @@ -394,16 +472,24 @@ pub enum ClockError { #[error("Concurrent writes to the same register/memory")] WriteWriteUnhelpful, #[error("Concurrent read & write to the same register/memory {0:?}")] - ReadWrite(GlobalCellIdx), + ReadWrite(GlobalCellIdx, Option), #[error("Concurrent writes to the same register/memory {0:?}")] - WriteWrite(GlobalCellIdx), + WriteWrite(GlobalCellIdx, Option), } impl ClockError { - pub fn add_cell_info(self, cell: GlobalCellIdx) -> Self { + pub fn add_cell_info( + self, + cell: GlobalCellIdx, + entry_number: Option, + ) -> Self { match self { - ClockError::ReadWriteUnhelpful => ClockError::ReadWrite(cell), - ClockError::WriteWriteUnhelpful => ClockError::WriteWrite(cell), + ClockError::ReadWriteUnhelpful => { + ClockError::ReadWrite(cell, entry_number) + } + ClockError::WriteWriteUnhelpful => { + ClockError::WriteWrite(cell, entry_number) + } _ => self, } } diff --git a/interp/src/flatten/structures/environment/env.rs b/cider/src/flatten/structures/environment/env.rs similarity index 62% rename from interp/src/flatten/structures/environment/env.rs rename to cider/src/flatten/structures/environment/env.rs index a7b9f54af6..d655dadb11 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/cider/src/flatten/structures/environment/env.rs @@ -3,13 +3,17 @@ use super::{ context::Context, index_trait::IndexRange, indexed_map::IndexedMap, }, assignments::{GroupInterfacePorts, ScheduledAssignments}, - clock::{ClockMap, VectorClock}, - program_counter::{ControlTuple, PcMaps, ProgramCounter, WithEntry}, + clock::ClockMap, + program_counter::{ + ControlTuple, ParEntry, PcMaps, ProgramCounter, WithEntry, + }, traverser::{Path, TraversalError}, }; -use crate::flatten::structures::environment::wave::WaveWriter; use crate::{ - errors::{BoxedInterpreterError, InterpreterError, InterpreterResult}, + configuration::{LoggingConfig, RuntimeConfig}, + errors::{ + BoxedCiderError, BoxedRuntimeError, CiderResult, ConflictingAssignments, + }, flatten::{ flat_ir::{ base::{ @@ -37,6 +41,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,21 +52,18 @@ use baa::{BitVecOps, BitVecValue}; use itertools::Itertools; use owo_colors::OwoColorize; -use slog::warn; -use std::fmt::Debug; +use slog::{info, warn, Logger}; use std::fmt::Write; +use std::{convert::Into, fmt::Debug}; pub type PortMap = IndexedMap; 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(()) } @@ -73,7 +78,11 @@ impl PortMap { val: PortValue, ) -> UpdateStatus { if self[target].is_undef() && val.is_undef() - || self[target].as_option() == val.as_option() + || self[target] + .as_option() + .zip(val.as_option()) + .map(|(a, b)| a.eq_no_transitive_clocks(b)) + .unwrap_or_default() { UpdateStatus::Unchanged } else { @@ -93,10 +102,12 @@ impl PortMap { &mut self, target: GlobalPortIdx, val: AssignedValue, - ) -> InterpreterResult { + ) -> Result> { match self[target].as_option() { // unchanged - Some(t) if *t == val => Ok(UpdateStatus::Unchanged), + Some(t) if t.eq_no_transitive_clocks(&val) => { + Ok(UpdateStatus::Unchanged) + } // conflict // TODO: Fix to make the error more helpful Some(t) @@ -105,14 +116,12 @@ 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, + } + .into()) } // changed Some(_) | None => { @@ -122,19 +131,32 @@ 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 { + self.insert_val(target, val) + .map_err(|e| RuntimeError::ConflictingAssignments(e).into()) + } + pub fn set_done( &mut self, target: GlobalPortIdx, done_bool: bool, - ) -> InterpreterResult { + ) -> RuntimeResult { self.insert_val( target, AssignedValue::cell_value(if done_bool { - BitVecValue::tru() + BitVecValue::new_true() } else { - BitVecValue::fals() + BitVecValue::new_false() }), ) + .map_err(|e| RuntimeError::ConflictingAssignments(e).into()) } } @@ -145,6 +167,7 @@ pub(crate) type RefPortMap = IndexedMap>; pub(crate) type AssignmentRange = IndexRange; +#[derive(Clone)] pub(crate) struct ComponentLedger { pub(crate) index_bases: BaseIndices, pub(crate) comp_id: ComponentIdx, @@ -181,6 +204,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 for CellLedger { fn from(v: ComponentLedger) -> Self { Self::Component(v) @@ -291,7 +332,7 @@ impl PinnedPorts { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Environment + Clone> { /// A map from global port IDs to their current values. ports: PortMap, @@ -317,9 +358,7 @@ pub struct Environment + Clone> { pub(super) ctx: C, memory_header: Option>, - - /// Whether to perform data race checking - check_data_race: bool, + logger: Logger, } impl + Clone> Environment { @@ -329,13 +368,50 @@ impl + Clone> Environment { pub fn ctx(&self) -> &Context { self.ctx.as_ref() } + + pub fn pc_iter(&self) -> impl Iterator { + self.pc.iter().map(|(_, x)| x) + } + + /// Method that returns an iterator over all component instances in the debugger + /// Used for Cider-DAP extension + pub fn iter_compts( + &self, + ) -> impl Iterator + '_ { + self.cells.iter().filter_map(|(idx, ledge)| match ledge { + CellLedger::Primitive { .. } => None, + CellLedger::Component(component_ledger) => { + Some((idx, self.ctx().lookup_name(component_ledger.comp_id))) + } + CellLedger::RaceDetectionPrimitive { .. } => None, //what this + }) + } + /// Method that returns an iterator over all cells in component cpt + /// Used for Cider-DAP extension + pub fn iter_cmpt_cells( + &self, + cpt: GlobalCellIdx, + ) -> impl Iterator)> + '_ { + // take globalcellid, look up in env to get compt ledger and get base indices + // w cmpt id, go to context look at ctx.secondary[cmptidx] to get aux info, want cell offset map just keys + // add local and globel offset, lookup full name and port info + let ledger = self.cells[cpt].as_comp().unwrap(); + let cells_keys = self.ctx().secondary.comp_aux_info[ledger.comp_id] + .cell_offset_map + .keys(); + cells_keys.map(|x| { + let idx = &ledger.index_bases + x; + (idx.get_full_name(self), self.ports_helper(idx)) + }) + } + /// Returns the full name and port list of each cell in the context pub fn iter_cells( &self, - ) -> impl Iterator)> + '_ { + ) -> impl Iterator)> + '_ { let env = self; let cell_names = self.cells.iter().map(|(idx, _ledger)| { - (idx.get_full_name(env), self.get_ports_for_cell(idx)) + (idx.get_full_name(env), self.ports_helper(idx)) }); cell_names @@ -344,45 +420,32 @@ impl + Clone> Environment { } //not sure if beneficial to change this to be impl iterator as well - fn get_ports_for_cell(&self, cell: GlobalCellIdx) -> Vec { + fn ports_helper(&self, cell: GlobalCellIdx) -> Vec<(String, PortValue)> { let parent = self.get_parent_cell_from_cell(cell); match parent { None => { - let comp_ledger = self.cells[cell].as_comp().unwrap(); - let comp_info = - self.ctx().secondary.comp_aux_info.get(comp_ledger.comp_id); - let port_ids = comp_info.signature().into_iter().map(|x| { - &self.ctx().secondary.local_port_defs - [comp_info.port_offset_map[x]] - .name - }); - let port_names = port_ids - .map(|x| String::from(x.lookup_name(self.ctx()))) + let ports = self.get_ports_from_cell(cell); + let info = ports + .map(|(name, id)| { + ( + (name.lookup_name(self.ctx())).clone(), + self.ports[id].clone(), + ) + }) .collect_vec(); - port_names + info } Some(parent_cell) => { - let parent_comp_ledger = self.cells[parent_cell].unwrap_comp(); - let comp_info = self - .ctx() - .secondary - .comp_aux_info - .get(parent_comp_ledger.comp_id); - let local_offset = cell - &parent_comp_ledger.index_bases; - - let port_ids = self.ctx().secondary.local_cell_defs - [comp_info.cell_offset_map[local_offset]] - .ports - .into_iter() - .map(|x| { - &self.ctx().secondary.local_port_defs - [comp_info.port_offset_map[x]] - .name - }); - let names = port_ids - .map(|x| String::from(x.lookup_name(self.ctx()))) + let ports = self.get_ports_from_cell(parent_cell); + let info = ports + .map(|(name, id)| { + ( + (name.lookup_name(self.ctx())).clone(), + self.ports[id].clone(), + ) + }) .collect_vec(); - names + info } } } @@ -390,13 +453,15 @@ impl + Clone> Environment { pub fn new( ctx: C, data_map: Option, - 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]; - let mut clocks = IndexedMap::new(); - let root_clock = clocks.push(VectorClock::new()); + let mut clocks = ClockMap::new(); + let root_clock = clocks.new_clock(); + let continuous_clock = clocks.new_clock(); let mut env = Self { ports: PortMap::with_capacity(aux.port_offset_map.count()), @@ -409,39 +474,47 @@ impl + Clone> Environment { ), pc: ProgramCounter::new_empty(), clocks, - thread_map: ThreadMap::new(root_clock), + thread_map: ThreadMap::new(root_clock, continuous_clock), ctx, 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); + env.clocks[continuous_clock].increment(&ThreadMap::continuous_thread()); // Initialize program counter // TODO griffin: Maybe refactor into a separate function for (idx, ledger) in env.cells.iter() { if let CellLedger::Component(comp) = ledger { - if let Some(ctrl) = - &env.ctx.as_ref().primary[comp.comp_id].control - { - env.pc.vec_mut().push(( - if comp.comp_id == root { - Some(root_thread) - } else { - None - }, - ControlPoint { - comp: idx, - control_node_idx: *ctrl, - }, - )) + let comp_info = &env.ctx.as_ref().primary[comp.comp_id]; + if !comp_info.is_comb() { + if let Some(ctrl) = comp_info.as_standard().unwrap().control + { + env.pc.vec_mut().push(( + if comp.comp_id == root { + Some(root_thread) + } else { + None + }, + ControlPoint { + comp: idx, + control_node_idx: ctrl, + }, + )) + } } } } @@ -466,6 +539,7 @@ impl + Clone> Environment { comp: GlobalCellIdx, data_map: &Option, memories_initialized: &mut HashSet, + check_race: bool, ) { // for mutability reasons, see note in `[Environment::new]` let ctx = self.ctx.clone(); @@ -481,7 +555,7 @@ impl + Clone> Environment { // Insert the component's continuous assignments into the program counter, if non-empty let cont_assigns = - self.ctx.as_ref().primary[*comp_id].continuous_assignments; + self.ctx.as_ref().primary[*comp_id].continuous_assignments(); if !cont_assigns.is_empty() { self.pc.push_continuous_assigns(comp, cont_assigns); } @@ -491,7 +565,10 @@ impl + Clone> Environment { 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()); } @@ -529,6 +606,24 @@ impl + Clone> Environment { self.control_ports.insert(done, 1); } + // ref cells and ports are initialized to None + for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { + let info = &self.ctx.as_ref().secondary[*def_idx]; + + for port_idx in info.ports.iter() { + let port_actual = self.ref_ports.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + port_idx, + port_actual + ); + } + let cell_actual = self.ref_cells.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, + cell_actual + ) + } + for (cell_off, def_idx) in comp_aux.cell_offset_map.iter() { let info = &self.ctx.as_ref().secondary[*def_idx]; if !info.prototype.is_component() { @@ -540,10 +635,12 @@ impl + Clone> Environment { 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 +650,7 @@ impl + Clone> Environment { 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 +669,12 @@ impl + Clone> Environment { ); // 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,48 +682,46 @@ impl + Clone> Environment { 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); } } } - - // ref cells and ports are initialized to None - for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { - let info = &self.ctx.as_ref().secondary[*def_idx]; - for port_idx in info.ports.iter() { - let port_actual = self.ref_ports.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + port_idx, - port_actual - ) - } - let cell_actual = self.ref_cells.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, - cell_actual - ) - } } - pub fn get_comp_go(&self, comp: GlobalCellIdx) -> GlobalPortIdx { + pub fn get_comp_go(&self, comp: GlobalCellIdx) -> Option { let ledger = self.cells[comp] .as_comp() .expect("Called get_comp_go with a non-component cell."); - &ledger.index_bases + self.ctx.as_ref().primary[ledger.comp_id].go + let go_port = self.ctx.as_ref().primary[ledger.comp_id] + .as_standard() + .map(|x| x.go); + + go_port.map(|go| &ledger.index_bases + go) + } + + pub fn unwrap_comp_go(&self, comp: GlobalCellIdx) -> GlobalPortIdx { + self.get_comp_go(comp).unwrap() } - pub fn get_comp_done(&self, comp: GlobalCellIdx) -> GlobalPortIdx { + pub fn get_comp_done(&self, comp: GlobalCellIdx) -> Option { let ledger = self.cells[comp] .as_comp() .expect("Called get_comp_done with a non-component cell."); - &ledger.index_bases + self.ctx.as_ref().primary[ledger.comp_id].done + let done_port = self.ctx.as_ref().primary[ledger.comp_id] + .as_standard() + .map(|x| x.done); + done_port.map(|done| &ledger.index_bases + done) + } + + pub fn unwrap_comp_done(&self, comp: GlobalCellIdx) -> GlobalPortIdx { + self.get_comp_done(comp).unwrap() } #[inline] pub fn get_root_done(&self) -> GlobalPortIdx { - self.get_comp_done(Self::get_root()) + self.get_comp_done(Self::get_root()).unwrap() } #[inline] @@ -640,7 +740,7 @@ impl + Clone> Environment { let node = &self.ctx.as_ref().primary[point.control_node_idx]; match node { ControlNode::Enable(x) => { - let comp_go = self.get_comp_go(point.comp); + let comp_go = self.get_comp_go(point.comp).unwrap(); if self.ports[comp_go].as_bool().unwrap_or_default() { Some(x.group()) } else { @@ -652,6 +752,15 @@ impl + Clone> Environment { }) } + /// 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 { + self.cells[cell].as_comp().map(|x| x.comp_id) + } + // ===================== Environment print implementations ===================== pub fn _print_env(&self) { @@ -754,7 +863,7 @@ impl + Clone> Environment { let node = &self.ctx.as_ref().primary[point.control_node_idx]; match node { ControlNode::Enable(_) | ControlNode::Invoke(_) => { - let comp_go = self.get_comp_go(point.comp); + let comp_go = self.unwrap_comp_go(point.comp); self.ports[comp_go].as_bool().unwrap_or_default() } @@ -768,10 +877,17 @@ impl + Clone> Environment { let node = &ctx.primary[point.control_node_idx]; match node { ControlNode::Enable(x) => { + let go = &self.cells[point.comp].unwrap_comp().index_bases + + self.ctx().primary[x.group()].go; println!( - "{}::{}", + "{}::{}{}", self.get_full_name(point.comp), - ctx.lookup_name(x.group()).underline() + ctx.lookup_name(x.group()).underline(), + if self.ports[go].as_bool().unwrap_or_default() { + "" + } else { + " [done]" + } ); } ControlNode::Invoke(x) => { @@ -803,6 +919,17 @@ impl + Clone> Environment { } } + 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, @@ -819,7 +946,7 @@ impl + Clone> Environment { /// Attempt to find the parent cell for a port. If no such cell exists (i.e. /// it is a hole port, then it returns None) - fn get_parent_cell_from_port( + fn _get_parent_cell_from_port( &self, port: PortRef, comp: GlobalCellIdx, @@ -1193,7 +1320,7 @@ impl + Clone> Environment { pub fn pin_value>(&mut self, port: S, val: BitVecValue) { let port = self.get_root_input_port(port); - let go = self.get_comp_go(Self::get_root()); + let go = self.unwrap_comp_go(Self::get_root()); assert!(port != go, "Cannot pin the go port"); self.pinned_ports.insert(port, val); @@ -1250,22 +1377,55 @@ impl + Clone> Environment { } } +enum ControlNodeEval { + Reprocess, + Stop { retain_node: bool }, +} + +impl ControlNodeEval { + fn stop(retain_node: bool) -> Self { + ControlNodeEval::Stop { retain_node } + } +} + +/// The core functionality of a simulator. Clonable. +#[derive(Clone)] +pub struct BaseSimulator + Clone> { + env: Environment, + 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 + Clone> { - env: Environment, + base: BaseSimulator, wave: Option, } impl + Clone> Simulator { - pub fn new( - env: Environment, + pub fn build_simulator( + ctx: C, + data_file: &Option, wave_file: &Option, - ) -> Self { - // open the wave form file and declare all signals + runtime_config: RuntimeConfig, + ) -> Result { + 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 +1433,124 @@ impl + Clone> Simulator { 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 + '_ { + 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 { + 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 { + self.base.traverse_name_vec(name) + } + + pub fn get_full_name(&self, nameable: N) -> String + where + N: GetFullName, + { + self.base.get_full_name(nameable) + } + + pub(crate) fn env(&self) -> &Environment { + 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 + Clone> BaseSimulator { + pub fn new(env: Environment, conf: RuntimeConfig) -> Self { + Self { env, conf } } pub(crate) fn env(&self) -> &Environment { @@ -1295,27 +1570,6 @@ impl + Clone> Simulator { self.env } - pub fn build_simulator( - ctx: C, - data_file: &Option, - wave_file: &Option, - check_races: bool, - ) -> Result { - 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 +1604,10 @@ impl + Clone> Simulator { 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 +1633,7 @@ impl + Clone> Simulator { } // =========================== simulation functions =========================== -impl + Clone> Simulator { +impl + Clone> BaseSimulator { #[inline] fn lookup_global_port_id(&self, port: GlobalPortRef) -> GlobalPortIdx { match port { @@ -1426,8 +1684,10 @@ impl + Clone> Simulator { fn set_root_go_high(&mut self) { let ledger = self.get_root_component(); let go = &ledger.index_bases - + self.env.ctx.as_ref().primary[ledger.comp_id].go; - self.env.ports[go] = PortValue::new_implicit(BitVecValue::tru()); + + self.env.ctx.as_ref().primary[ledger.comp_id] + .unwrap_standard() + .go; + self.env.ports[go] = PortValue::new_implicit(BitVecValue::new_true()); } // may want to make this iterate directly if it turns out that the vec @@ -1443,7 +1703,7 @@ impl + Clone> Simulator { ControlNode::Enable(e) => { let group = &self.ctx().primary[e.group()]; - Some(ScheduledAssignments::new( + Some(ScheduledAssignments::new_control( node.comp, group.assignments, Some(GroupInterfacePorts { @@ -1451,17 +1711,17 @@ impl + Clone> Simulator { done: group.done, }), *thread, - false, )) } - ControlNode::Invoke(i) => Some(ScheduledAssignments::new( - node.comp, - i.assignments, - None, - *thread, - false, - )), + ControlNode::Invoke(i) => { + Some(ScheduledAssignments::new_control( + node.comp, + i.assignments, + None, + *thread, + )) + } ControlNode::Empty(_) => None, // non-leaf nodes @@ -1473,25 +1733,20 @@ impl + Clone> Simulator { } }) .chain(self.env.pc.continuous_assigns().iter().map(|x| { - ScheduledAssignments::new(x.comp, x.assigns, None, None, true) + ScheduledAssignments::new_continuous(x.comp, x.assigns) })) .chain(self.env.pc.with_map().iter().map( |(ctrl_pt, with_entry)| { - let assigns = - self.ctx().primary[with_entry.group].assignments; - ScheduledAssignments::new( + ScheduledAssignments::new_combinational( ctrl_pt.comp, - assigns, - None, - with_entry.thread, - false, + self.ctx().primary[with_entry.group].assignments, ) }, )) .collect() } - /// A helper function which inserts indicies for the ref cells and ports + /// A helper function which inserts indices for the ref cells and ports /// used in the invoke statement fn initialize_ref_cells( &mut self, @@ -1602,7 +1857,7 @@ impl + Clone> Simulator { } // - 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 @@ -1611,26 +1866,35 @@ impl + Clone> Simulator { } 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 { + let done_port = self.env.unwrap_comp_done(*comp); + let v = PortValue::new_implicit(BitVecValue::new_true()); + self.env.ports[done_port] = if self.conf.check_data_race { v.with_thread(id.expect("finished comps should have a thread")) } else { v } } - let (vecs, par_map, mut with_map, repeat_map) = + let (mut vecs, par_map, mut with_map, repeat_map) = self.env.pc.take_fields(); + // For thread propagation during race detection we need to iterate in + // containment order. This probably isn't necessary for normal execution + // and could be guarded by the `check_data_race` flag. Will leave it for + // the moment though and see if we need to change it down the line. In + // expectation, the program counter should almost always be already + // sorted as the only thing which causes nodes to be added or removed + // are par nodes + vecs.sort_by_key(|x| x.1.comp); + // for mutability reasons, this should be a cheap clone, either an RC in // the owned case or a simple reference clone let ctx = self.env.ctx.clone(); let ctx_ref = ctx.as_ref(); for (thread, node) in vecs.iter() { - let comp_done = self.env.get_comp_done(node.comp); - let comp_go = self.env.get_comp_go(node.comp); + let comp_done = self.env.unwrap_comp_done(node.comp); + let comp_go = self.env.unwrap_comp_go(node.comp); let thread = thread.or_else(|| { self.env.ports[comp_go].as_option().and_then(|t| t.thread()) }); @@ -1638,7 +1902,7 @@ impl + Clone> Simulator { // if the done is not high & defined, we need to set it to low if !self.env.ports[comp_done].as_bool().unwrap_or_default() { self.env.ports[comp_done] = - PortValue::new_implicit(BitVecValue::fals()); + PortValue::new_implicit(BitVecValue::new_false()); } match &ctx_ref.primary[node.control_node_idx] { @@ -1653,7 +1917,7 @@ impl + Clone> Simulator { // set go high let go_idx = index_bases + go_local; self.env.ports[go_idx] = - PortValue::new_implicit(BitVecValue::tru()); + PortValue::new_implicit(BitVecValue::new_true()); } ControlNode::Invoke(invoke) => { if invoke.comb_group.is_some() @@ -1661,16 +1925,16 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(invoke.comb_group.unwrap(), thread), + WithEntry::new(invoke.comb_group.unwrap()), ); } let go = self.get_global_port_idx(&invoke.go, node.comp); self.env.ports[go] = - PortValue::new_implicit(BitVecValue::tru()) + PortValue::new_implicit(BitVecValue::new_true()) .with_thread_optional( - if self.env.check_data_race { - assert!(thread.is_some()); + if self.conf.check_data_race { + assert!(thread.is_some(), "Invoke is running but has no thread. This shouldn't happen. In {}", node.comp.get_full_name(&self.env)); thread } else { None @@ -1687,7 +1951,7 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(i.cond_group().unwrap(), thread), + WithEntry::new(i.cond_group().unwrap()), ); } } @@ -1696,7 +1960,7 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(w.cond_group().unwrap(), thread), + WithEntry::new(w.cond_group().unwrap()), ); } } @@ -1715,40 +1979,77 @@ impl + Clone> Simulator { 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 mut result = Ok(()); - for cell in self.env.cells.values_mut() { - match cell { - CellLedger::Primitive { cell_dyn } => { - let res = cell_dyn.exec_cycle(&mut self.env.ports); - if res.is_err() { - result = Err(res.unwrap_err()); - break; + if self.conf.check_data_race { + let mut clock_map = std::mem::take(&mut self.env.clocks); + for cell in self.env.cells.values() { + if !matches!(&cell, CellLedger::Component(_)) { + let dyn_prim = match cell { + CellLedger::Primitive { cell_dyn } => &**cell_dyn, + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + cell_dyn.as_primitive() } - } + CellLedger::Component(_) => { + unreachable!() + } + }; - CellLedger::RaceDetectionPrimitive { cell_dyn } => { - let res = cell_dyn.exec_cycle_checked( - &mut self.env.ports, - &mut self.env.clocks, - &self.env.thread_map, - ); - if res.is_err() { - result = Err(res.unwrap_err()); - break; + if !dyn_prim.is_combinational() { + let sig = dyn_prim.get_ports(); + for port in sig.iter_first() { + if let Some(val) = self.env.ports[port].as_option() + { + if val.propagate_clocks() + && (val.transitive_clocks().is_some()) + { + // For non-combinational cells with + // transitive reads, we will check them at + // the cycle boundary and attribute the read + // to the continuous thread + self.check_read( + ThreadMap::continuous_thread(), + port, + &mut clock_map, + ) + .map_err(|e| { + e.prettify_message(&self.env) + })? + } + } } } - CellLedger::Component(_) => {} } } - result - }; + + self.env.clocks = clock_map; + } + + let out: Result<(), BoxedRuntimeError> = self + .env + .cells + .values_mut() + .filter_map(|cell| match cell { + CellLedger::Primitive { cell_dyn } => { + Some(cell_dyn.exec_cycle(&mut self.env.ports)) + } + + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.exec_cycle_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + )) + } + CellLedger::Component(_) => None, + }) + .collect(); + + out.map_err(|e| e.prettify_message(&self.env))?; self.env.pc.clear_finished_comps(); @@ -1758,14 +2059,27 @@ impl + Clone> Simulator { 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), - )?; - if !keep_node { - removed.push(i); + let mut i = 0; + + while i < vecs.len() { + let node = &mut vecs[i]; + 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))?; + match keep_node { + ControlNodeEval::Reprocess => { + continue; + } + ControlNodeEval::Stop { retain_node } => { + if !retain_node { + removed.push(i); + } + i += 1; + } } } @@ -1780,7 +2094,7 @@ impl + Clone> Simulator { // 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)) + Ok(()) } fn evaluate_control_node( @@ -1788,11 +2102,11 @@ impl + Clone> Simulator { node: &mut ControlTuple, new_nodes: &mut Vec, maps: PcMaps, - ) -> InterpreterResult { + ) -> RuntimeResult { let (node_thread, node) = node; let (par_map, with_map, repeat_map) = maps; - let comp_go = self.env.get_comp_go(node.comp); - let comp_done = self.env.get_comp_done(node.comp); + let comp_go = self.env.unwrap_comp_go(node.comp); + let comp_done = self.env.unwrap_comp_done(node.comp); let thread = node_thread.or_else(|| { self.env.ports[comp_go].as_option().and_then(|x| x.thread()) @@ -1807,178 +2121,46 @@ impl + Clone> Simulator { { // if the go port is low or the done port is high, we skip the // node without doing anything - return Ok(true); + return Ok(ControlNodeEval::stop(true)); } // just considering a single node case for the moment let retain_bool = match &ctx.primary[node.control_node_idx] { - ControlNode::Seq(seq) => { - if !seq.is_empty() { - let next = seq.stms()[0]; - *node = node.new_retain_comp(next); - true - } else { - node.mutate_into_next(self.env.ctx.as_ref()) - } - } - ControlNode::Par(par) => { - if par_map.contains_key(node) { - let count = par_map.get_mut(node).unwrap(); - *count -= 1; - - if *count == 0 { - par_map.remove(node); - if self.env.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - - let child_clock_idx = - self.env.thread_map.unwrap_clock_id(thread); - let parent = - self.env.thread_map[thread].parent().unwrap(); - let parent_clock = - self.env.thread_map.unwrap_clock_id(parent); - let child_clock = std::mem::take( - &mut self.env.clocks[child_clock_idx], - ); - self.env.clocks[parent_clock].sync(&child_clock); - self.env.clocks[child_clock_idx] = child_clock; - assert!(self.env.thread_map[thread] - .parent() - .is_some()); - *node_thread = Some(parent); - self.env.clocks[parent_clock].increment(&parent); - } - node.mutate_into_next(self.env.ctx.as_ref()) - } else { - false - } - } else { - par_map.insert( - node.clone(), - par.stms().len().try_into().expect( - "More than (2^16 - 1 threads) in a par block. Are you sure this is a good idea?", - ), - ); - new_nodes.extend(par.stms().iter().map(|x| { - let thread = if self.env.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - - let new_thread_idx: ThreadIdx = *(self - .env - .pc - .lookup_thread(node.comp, thread, *x) - .or_insert_with(|| { - let new_clock_idx = - self.env.clocks.new_clock(); - - self.env - .thread_map - .spawn(thread, new_clock_idx) - })); - - let new_clock_idx = self - .env - .thread_map - .unwrap_clock_id(new_thread_idx); - - self.env.clocks[new_clock_idx] = self.env.clocks - [self.env.thread_map.unwrap_clock_id(thread)] - .clone(); - - self.env.clocks[new_clock_idx] - .increment(&new_thread_idx); - - Some(new_thread_idx) - } else { - None - }; - - (thread, node.new_retain_comp(*x)) - })); - - if self.env.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - let clock = self.env.thread_map.unwrap_clock_id(thread); - self.env.clocks[clock].increment(&thread); - } - - false - } - } + ControlNode::Seq(seq) => self.handle_seq(seq, node), + ControlNode::Par(par) => self.handle_par( + par_map, + node, + thread, + node_thread, + par, + new_nodes, + ), ControlNode::If(i) => self.handle_if(with_map, node, thread, i)?, ControlNode::While(w) => { self.handle_while(w, with_map, node, thread)? } ControlNode::Repeat(rep) => { - if let Some(count) = repeat_map.get_mut(node) { - *count -= 1; - if *count == 0 { - repeat_map.remove(node); - node.mutate_into_next(self.env.ctx.as_ref()) - } else { - *node = node.new_retain_comp(rep.body); - true - } - } else { - repeat_map.insert(node.clone(), rep.num_repeats); - *node = node.new_retain_comp(rep.body); - true - } + self.handle_repeat(repeat_map, node, rep) } // ===== leaf nodes ===== ControlNode::Empty(_) => { node.mutate_into_next(self.env.ctx.as_ref()) } - ControlNode::Enable(e) => { - let done_local = self.env.ctx.as_ref().primary[e.group()].done; - let done_idx = - &self.env.cells[node.comp].as_comp().unwrap().index_bases - + done_local; - if !self.env.ports[done_idx].as_bool().unwrap_or_default() { - true - } else { - // This group has finished running and may be removed - // this is somewhat dubious at the moment since it - // relies on the fact that the group done port will - // still be high since convergence hasn't propagated the - // low done signal yet. - node.mutate_into_next(self.env.ctx.as_ref()) - } - } - ControlNode::Invoke(i) => { - let done = self.get_global_port_idx(&i.done, node.comp); - - if i.comb_group.is_some() && !with_map.contains_key(node) { - with_map.insert( - node.clone(), - WithEntry::new(i.comb_group.unwrap(), thread), - ); - } - - if !self.env.ports[done].as_bool().unwrap_or_default() { - true - } else { - self.cleanup_ref_cells(node.comp, i); - - if i.comb_group.is_some() { - with_map.remove(node); - } - - node.mutate_into_next(self.env.ctx.as_ref()) - } - } + ControlNode::Enable(e) => self.handle_enable(e, node), + ControlNode::Invoke(i) => self.handle_invoke(i, node, with_map)?, }; + if retain_bool && node.should_reprocess(ctx) { + return Ok(ControlNodeEval::Reprocess); + } + if !retain_bool && ControlPoint::get_next(node, self.env.ctx.as_ref()).is_none() && // 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" @@ -1989,12 +2171,189 @@ impl + Clone> Simulator { let comp_ledger = self.env.cells[node.comp].unwrap_comp(); *node = node.new_retain_comp( self.env.ctx.as_ref().primary[comp_ledger.comp_id] + .unwrap_standard() .control .unwrap(), ); - Ok(true) + Ok(ControlNodeEval::stop(true)) + } else { + Ok(ControlNodeEval::stop(retain_bool)) + } + } + + fn handle_seq(&mut self, seq: &Seq, node: &mut ControlPoint) -> bool { + if !seq.is_empty() { + let next = seq.stms()[0]; + *node = node.new_retain_comp(next); + true + } else { + node.mutate_into_next(self.env.ctx.as_ref()) + } + } + + fn handle_repeat( + &mut self, + repeat_map: &mut HashMap, + node: &mut ControlPoint, + rep: &Repeat, + ) -> bool { + if let Some(count) = repeat_map.get_mut(node) { + *count -= 1; + if *count == 0 { + repeat_map.remove(node); + node.mutate_into_next(self.env.ctx.as_ref()) + } else { + *node = node.new_retain_comp(rep.body); + true + } } else { - Ok(retain_bool) + repeat_map.insert(node.clone(), rep.num_repeats); + *node = node.new_retain_comp(rep.body); + true + } + } + + fn handle_enable(&mut self, e: &Enable, node: &mut ControlPoint) -> bool { + let done_local = self.env.ctx.as_ref().primary[e.group()].done; + let done_idx = + &self.env.cells[node.comp].as_comp().unwrap().index_bases + + done_local; + + if !self.env.ports[done_idx].as_bool().unwrap_or_default() { + true + } else { + // This group has finished running and may be removed + // this is somewhat dubious at the moment since it + // relies on the fact that the group done port will + // still be high since convergence hasn't propagated the + // low done signal yet. + node.mutate_into_next(self.env.ctx.as_ref()) + } + } + + fn handle_invoke( + &mut self, + i: &Invoke, + node: &mut ControlPoint, + with_map: &mut HashMap, + ) -> Result { + let done = self.get_global_port_idx(&i.done, node.comp); + if i.comb_group.is_some() && !with_map.contains_key(node) { + with_map + .insert(node.clone(), WithEntry::new(i.comb_group.unwrap())); + } + Ok(if !self.env.ports[done].as_bool().unwrap_or_default() { + true + } else { + self.cleanup_ref_cells(node.comp, i); + + if i.comb_group.is_some() { + with_map.remove(node); + } + + node.mutate_into_next(self.env.ctx.as_ref()) + }) + } + + fn handle_par( + &mut self, + par_map: &mut HashMap, + node: &mut ControlPoint, + thread: Option, + node_thread: &mut Option, + par: &Par, + new_nodes: &mut Vec<(Option, ControlPoint)>, + ) -> bool { + if par_map.contains_key(node) { + let par_entry = par_map.get_mut(node).unwrap(); + *par_entry.child_count_mut() -= 1; + + if self.conf.check_data_race { + par_entry.add_finished_thread( + thread.expect("par nodes should have a thread"), + ); + } + + if par_entry.child_count() == 0 { + let par_entry = par_map.remove(node).unwrap(); + if self.conf.check_data_race { + assert!(par_entry + .iter_finished_threads() + .map(|thread| { + self.env.thread_map[thread].parent().unwrap() + }) + .all_equal()); + let parent = + self.env.thread_map[thread.unwrap()].parent().unwrap(); + let parent_clock = + self.env.thread_map.unwrap_clock_id(parent); + + for child_thread in par_entry.iter_finished_threads() { + let child_clock_idx = + self.env.thread_map.unwrap_clock_id(child_thread); + + let child_clock = std::mem::take( + &mut self.env.clocks[child_clock_idx], + ); + + self.env.clocks[parent_clock].sync(&child_clock); + + self.env.clocks[child_clock_idx] = child_clock; + } + + *node_thread = Some(parent); + self.env.clocks[parent_clock].increment(&parent); + } + node.mutate_into_next(self.env.ctx.as_ref()) + } else { + false + } + } else { + par_map.insert( + node.clone(), + par.stms().len().try_into().expect( + "More than (2^16 - 1 threads) in a par block. Are you sure this is a good idea?", + ), + ); + new_nodes.extend(par.stms().iter().map(|x| { + let thread = if self.conf.check_data_race { + let thread = + thread.expect("par nodes should have a thread"); + + let new_thread_idx: ThreadIdx = *(self + .env + .pc + .lookup_thread(node.comp, thread, *x) + .or_insert_with(|| { + let new_clock_idx = self.env.clocks.new_clock(); + + self.env.thread_map.spawn(thread, new_clock_idx) + })); + + let new_clock_idx = + self.env.thread_map.unwrap_clock_id(new_thread_idx); + + self.env.clocks[new_clock_idx] = self.env.clocks + [self.env.thread_map.unwrap_clock_id(thread)] + .clone(); + + self.env.clocks[new_clock_idx].increment(&new_thread_idx); + + Some(new_thread_idx) + } else { + None + }; + + (thread, node.new_retain_comp(*x)) + })); + + 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); + self.env.clocks[clock].increment(&thread); + } + + false } } @@ -2004,7 +2363,7 @@ impl + Clone> Simulator { with_map: &mut HashMap, node: &mut ControlPoint, thread: Option, - ) -> InterpreterResult { + ) -> RuntimeResult { let target = GlobalPortRef::from_local( w.cond_port(), &self.env.cells[node.comp].unwrap_comp().index_bases, @@ -2015,27 +2374,20 @@ impl + Clone> Simulator { GlobalPortRef::Ref(r) => self.env.ref_ports[r] .expect("While condition (ref) is undefined"), }; - if self.env.check_data_race { - if let Some(clocks) = self.env.ports[idx].clocks() { - let read_clock = - self.env.thread_map.unwrap_clock_id(thread.unwrap()); - clocks - .check_read( - (thread.unwrap(), read_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - e.add_cell_info( - self.env - .get_parent_cell_from_port( - w.cond_port(), - node.comp, - ) - .unwrap(), - ) - })?; - } + + if self.conf.check_data_race { + let mut clock_map = std::mem::take(&mut self.env.clocks); + + self.check_read_relative( + thread.unwrap(), + w.cond_port(), + node.comp, + &mut clock_map, + )?; + + self.env.clocks = clock_map; } + let result = self.env.ports[idx] .as_bool() .expect("While condition is undefined"); @@ -2059,7 +2411,7 @@ impl + Clone> Simulator { node: &mut ControlPoint, thread: Option, i: &If, - ) -> InterpreterResult { + ) -> RuntimeResult { 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,26 +2430,12 @@ impl + Clone> Simulator { .expect("If condition (ref) is undefined"), }; - if self.env.check_data_race { - if let Some(clocks) = self.env.ports[idx].clocks() { - let read_clock = - self.env.thread_map.unwrap_clock_id(thread.unwrap()); - clocks - .check_read( - (thread.unwrap(), read_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - e.add_cell_info( - self.env - .get_parent_cell_from_port( - i.cond_port(), - node.comp, - ) - .unwrap(), - ) - })?; - } + if self.conf.check_data_race { + let mut clock_map = std::mem::take(&mut self.env.clocks); + + self.check_read(thread.unwrap(), idx, &mut clock_map)?; + + self.env.clocks = clock_map; } let result = self.env.ports[idx] @@ -2116,18 +2454,20 @@ impl + Clone> Simulator { .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 +2530,14 @@ impl + Clone> Simulator { 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; @@ -2203,7 +2547,7 @@ impl + Clone> Simulator { assignments, interface_ports, thread, - is_cont, + assign_type, } in assigns_bundle.iter() { let ledger = self.env.cells[*active_cell].as_comp().unwrap(); @@ -2224,17 +2568,17 @@ impl + Clone> Simulator { // signal of the component must also be high .map(|g| { self.env.ports[*g].as_bool().unwrap_or_default() - && self.env.ports[comp_go] + && self.env.ports[comp_go.unwrap()] .as_bool() .unwrap_or_default() }) .unwrap_or_else(|| { // if there is no go signal, then we want to run the // continuous assignments but not comb group assignments - if *is_cont { + if assign_type.is_continuous() { true } else { - self.env.ports[comp_go] + self.env.ports[comp_go.unwrap()] .as_bool() .unwrap_or_default() } @@ -2254,38 +2598,13 @@ impl + Clone> Simulator { .get_global_port_idx(&assign.src, *active_cell); let val = &self.env.ports[port]; - if self.env.check_data_race { - if let Some(clocks) = val.clocks() { - // skip checking clocks for continuous assignments - if !is_cont { - if let Some(thread) = thread { - let thread_clock = self - .env - .thread_map - .unwrap_clock_id(thread); - - clocks - .check_read( - (thread, thread_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - // TODO griffin: find a less hacky way to - // do this - e.add_cell_info( - self.env - .get_parent_cell_from_port( - assign.src, - *active_cell, - ) - .unwrap(), - ) - })?; - } else { - panic!("cannot determine thread for non-continuous assignment that touches a checked port"); - } - } - } + if self.conf.debug_logging { + self.log_assignment( + active_cell, + ledger, + assign_idx, + val, + ); } let dest = self @@ -2304,14 +2623,63 @@ impl + Clone> Simulator { } if let Some(v) = val.as_option() { - let changed = self.env.ports.insert_val( + let mut assigned_value = AssignedValue::new( + v.val().clone(), + (assign_idx, *active_cell), + ); + + // if this assignment is in a combinational + // context we want to propagate any clocks which + // are present. Since clocks aren't present + // when not running with `check_data_race`, this + // won't happen when the flag is not set + if (val.clocks().is_some() + || val.transitive_clocks().is_some()) + && (assign_type.is_combinational() + || assign_type.is_continuous()) + { + assigned_value = assigned_value + .with_transitive_clocks_opt( + val.transitive_clocks().cloned(), + ) + .with_propagate_clocks(); + // direct clock becomes a transitive clock + // on assignment + if let Some(c) = val.clocks() { + assigned_value.add_transitive_clock(c); + } + } + + let result = self.env.ports.insert_val( dest, - AssignedValue::new( - v.val().clone(), - assign_idx, - ) - .with_thread_optional(thread), - )?; + assigned_value.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(); } @@ -2326,79 +2694,15 @@ impl + Clone> Simulator { todo!("Raise an error here since this assignment is undefining things: {}. Port currently has value: {}", self.env.ctx.as_ref().printer().print_assignment(ledger.comp_id, assign_idx), &self.env.ports[dest]) } } - - if self.env.check_data_race { - if let Some(read_ports) = self - .env - .ctx - .as_ref() - .primary - .guard_read_map - .get(assign.guard) - { - for port in read_ports { - let port_idx = self.get_global_port_idx( - port, - *active_cell, - ); - if let Some(clocks) = - self.env.ports[port_idx].clocks() - { - let thread = thread - .expect("cannot determine thread"); - let thread_clock = self - .env - .thread_map - .unwrap_clock_id(thread); - clocks - .check_read( - (thread, thread_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - // TODO griffin: find a less hacky way to - // do this - e.add_cell_info( - self.env - .get_parent_cell_from_port( - *port, - *active_cell, - ) - .unwrap(), - ) - })?; - } - } - } - } } } } - // Run all the primitives - let changed: bool = self - .env - .cells - .range() - .iter() - .filter_map(|x| match &mut self.env.cells[x] { - CellLedger::Primitive { cell_dyn } => { - Some(cell_dyn.exec_comb(&mut self.env.ports)) - } - CellLedger::RaceDetectionPrimitive { cell_dyn } => { - Some(cell_dyn.exec_comb_checked( - &mut self.env.ports, - &mut self.env.clocks, - &self.env.thread_map, - )) - } + if self.conf.check_data_race { + self.propagate_comb_reads(assigns_bundle)?; + } - CellLedger::Component(_) => None, - }) - .fold_ok(UpdateStatus::Unchanged, |has_changed, update| { - has_changed | update - })? - .as_bool(); + let changed = self.run_primitive_comb_path()?; has_changed |= changed; @@ -2413,14 +2717,409 @@ impl + Clone> Simulator { 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) + ); + } + } + } + } + } + + // check reads needs to happen before zeroing the go ports. If this is + // not observed then some read checks will be accidentally skipped + if self.conf.check_data_race { + self.handle_reads(assigns_bundle)?; + } + + if self.conf.undef_guard_check { + self.check_undefined_guards(assigns_bundle)?; + } + + // This should be the last update that occurs during convergence + self.zero_done_groups_go(assigns_bundle); + + if self.conf.debug_logging { + info!(self.env.logger, "Finished combinational convergence"); + } + + Ok(()) + } + + /// For all groups in the given assignments, set the go port to zero if the + /// done port is high + fn zero_done_groups_go(&mut self, assigns_bundle: &[ScheduledAssignments]) { + for ScheduledAssignments { + active_cell, + interface_ports, + .. + } in assigns_bundle.iter() + { + if let Some(interface_ports) = interface_ports { + let ledger = self.env.cells[*active_cell].as_comp().unwrap(); + let go = &ledger.index_bases + interface_ports.go; + let done = &ledger.index_bases + interface_ports.done; + if self.env.ports[done].as_bool().unwrap_or_default() { + self.env.ports[go] = + PortValue::new_implicit(BitVecValue::zero(1)); + } + } + } + } + + /// A final pass meant to be run after convergence which does the following: + /// + /// 1. For successful assignments, check reads from the source port if applicable + /// 2. For non-continuous/combinational contexts, check all reads performed + /// by the guard regardless of whether the assignment fired or not + /// 3. For continuous/combinational contexts, update the transitive reads of + /// the value in the destination with the reads done by the guard, + /// regardless of success + fn handle_reads( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + // needed for mutability reasons + let mut clock_map = std::mem::take(&mut self.env.clocks); + + for ScheduledAssignments { + active_cell, + assignments, + interface_ports, + thread, + assign_type, + } in assigns_bundle.iter() + { + let ledger = self.env.cells[*active_cell].as_comp().unwrap(); + let go = + interface_ports.as_ref().map(|x| &ledger.index_bases + x.go); + + let comp_go = self.env.get_comp_go(*active_cell); + + let thread = self.compute_thread(comp_go, thread, go); + + // check for direct reads + if assign_type.is_control() + && go + .as_ref() + .map(|g| { + self.env.ports[*g].as_bool().unwrap_or_default() + && self.env.ports[comp_go.unwrap()] + .as_bool() + .unwrap_or_default() + }) + .unwrap_or_default() + { + for assign_idx in assignments.iter() { + let assign = &self.env.ctx.as_ref().primary[assign_idx]; + + // read source + if self + .evaluate_guard(assign.guard, *active_cell) + .unwrap_or_default() + { + self.check_read_relative( + thread.unwrap(), + assign.src, + *active_cell, + &mut clock_map, + )?; + } + + // guard reads, assignment firing does not matter + if let Some(read_ports) = self + .env + .ctx + .as_ref() + .primary + .guard_read_map + .get(assign.guard) + { + for port in read_ports { + self.check_read_relative( + thread.unwrap(), + *port, + *active_cell, + &mut clock_map, + )?; + } + } + } + } + } + self.env.clocks = clock_map; + + Ok(()) + } + + /// For continuous/combinational contexts, update the transitive reads of + /// the value in the destination with the reads done by the guard, + /// regardless of success + fn propagate_comb_reads( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + for ScheduledAssignments { + active_cell, + assignments, + assign_type, + .. + } in assigns_bundle.iter() + { + let comp_go = self.env.get_comp_go(*active_cell); + + if (assign_type.is_combinational() + && comp_go + .and_then(|comp_go| self.env.ports[comp_go].as_bool()) + .unwrap_or_default()) + || assign_type.is_continuous() + { + for assign_idx in assignments.iter() { + let assign = &self.env.ctx.as_ref().primary[assign_idx]; + let dest = + self.get_global_port_idx(&assign.dst, *active_cell); + + if let Some(read_ports) = self + .env + .ctx + .as_ref() + .primary + .guard_read_map + .get(assign.guard) + { + if self.env.ports[dest].is_def() { + let mut set_extension = HashSet::new(); + + for port in read_ports { + let port = self + .get_global_port_idx(port, *active_cell); + if let Some(clock) = + self.env.ports[port].clocks() + { + set_extension.insert(clock); + } + if let Some(clocks) = + self.env.ports[port].transitive_clocks() + { + set_extension + .extend(clocks.iter().copied()); + } + } + + self.env.ports[dest] + .as_option_mut() + .unwrap() + .add_transitive_clocks(set_extension); + + // this is necessary for ports which were implicitly + // assigned zero and is redundant for other ports + // which will already have propagate_clocks set + self.env.ports[dest] + .as_option_mut() + .unwrap() + .set_propagate_clocks(true); + } + } + } + } + } + + Ok(()) + } + + /// Check for undefined guards and raise an error if any are found + fn check_undefined_guards( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + 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() { + Err(RuntimeError::UndefinedGuardError(error_v).into()) + } else { + Ok(()) + } + } + + fn run_primitive_comb_path(&mut self) -> Result { + let changed: bool = self + .env + .cells + .values_mut() + .filter_map(|x| match x { + CellLedger::Primitive { cell_dyn } => { + let result = cell_dyn.exec_comb(&mut self.env.ports); + + if result.is_ok() + && self.conf.check_data_race + && cell_dyn.is_combinational() + { + let mut working_set = vec![]; + let signature = cell_dyn.get_ports(); + + for port in signature.iter_first() { + let val = &self.env.ports[port]; + if let Some(val) = val.as_option() { + if val.propagate_clocks() + && (val.clocks().is_some() + || val.transitive_clocks().is_some()) + { + if let Some(clocks) = val.clocks() { + working_set.push(*clocks); + } + working_set + .extend(val.iter_transitive_clocks()); + } + } + } + + if signature.iter_second().len() == 1 { + let port = signature.iter_second().next().unwrap(); + let val = &mut self.env.ports[port]; + if let Some(val) = val.as_option_mut() { + val.add_transitive_clocks(working_set); + } + } else { + todo!("comb primitive with multiple outputs") + } } + Some(result) + } + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.exec_comb_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + )) } + + CellLedger::Component(_) => None, + }) + .fold_ok(UpdateStatus::Unchanged, |has_changed, update| { + has_changed | update + })? + .as_bool(); + Ok(changed) + } + + /// A wrapper function for [check_read] which takes in a [PortRef] and the + /// active component cell and calls [check_read] with the appropriate [GlobalPortIdx] + fn check_read_relative( + &self, + thread: ThreadIdx, + port: PortRef, + active_cell: GlobalCellIdx, + clock_map: &mut ClockMap, + ) -> Result<(), BoxedRuntimeError> { + let global_port = self.get_global_port_idx(&port, active_cell); + self.check_read(thread, global_port, clock_map) + } + + fn check_read( + &self, + thread: ThreadIdx, + global_port: GlobalPortIdx, + clock_map: &mut ClockMap, + ) -> Result<(), BoxedRuntimeError> { + let val = &self.env.ports[global_port]; + let thread_clock = self.env.thread_map.unwrap_clock_id(thread); + + if val.clocks().is_some() && val.transitive_clocks().is_some() { + // TODO griffin: Sort this out + panic!("Value has both direct clock and transitive clock. This shouldn't happen?") + } else if let Some(clocks) = val.clocks() { + let info = clock_map.lookup_cell(clocks).expect("Clock pair without cell. This should never happen, please report this bug"); + clocks.check_read_w_cell( + (thread, thread_clock), + clock_map, + info.attached_cell, + info.entry_number, + )? + } else if let Some(transitive_clocks) = val.transitive_clocks() { + for clock_pair in transitive_clocks { + let info = clock_map.lookup_cell(*clock_pair).expect("Clock pair without cell. This should never happen, please report this bug"); + + clock_pair.check_read_w_cell( + (thread, thread_clock), + clock_map, + info.attached_cell, + info.entry_number, + )? } } Ok(()) } + fn log_assignment( + &self, + active_cell: &GlobalCellIdx, + ledger: &ComponentLedger, + assign_idx: AssignmentIdx, + val: &PortValue, + ) { + 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() + ); + } + /// Attempts to compute the thread id for the given group/component. /// /// If the given thread is `None`, then the thread id is computed from the @@ -2429,7 +3128,7 @@ impl + Clone> Simulator { /// none of these succeed then `None` is returned. fn compute_thread( &self, - comp_go: GlobalPortIdx, + comp_go: Option, thread: &Option, go: Option, ) -> Option { @@ -2441,7 +3140,9 @@ impl + Clone> Simulator { return Some(go_thread); } } - self.env.ports[comp_go].as_option().and_then(|x| x.thread()) + comp_go.and_then(|comp_go| { + self.env.ports[comp_go].as_option().and_then(|x| x.thread()) + }) }) } @@ -2626,26 +3327,30 @@ impl + Clone> GetFullName for GlobalCellIdx { impl + Clone> GetFullName for GlobalPortIdx { fn get_full_name(&self, env: &Environment) -> 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 + "".to_string() + } } } diff --git a/interp/src/flatten/structures/environment/mod.rs b/cider/src/flatten/structures/environment/mod.rs similarity index 73% rename from interp/src/flatten/structures/environment/mod.rs rename to cider/src/flatten/structures/environment/mod.rs index f5133e74af..40808bfbea 100644 --- a/interp/src/flatten/structures/environment/mod.rs +++ b/cider/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/cider/src/flatten/structures/environment/program_counter.rs similarity index 81% rename from interp/src/flatten/structures/environment/program_counter.rs rename to cider/src/flatten/structures/environment/program_counter.rs index 3ce817a844..3c312ed483 100644 --- a/interp/src/flatten/structures/environment/program_counter.rs +++ b/cider/src/flatten/structures/environment/program_counter.rs @@ -1,6 +1,7 @@ use std::{collections::hash_map::Entry, num::NonZeroU32}; use ahash::{HashMap, HashMapExt}; +use smallvec::SmallVec; use super::super::context::Context; use crate::flatten::{ @@ -59,6 +60,80 @@ impl ControlPoint { false } } + + pub(super) fn should_reprocess(&self, ctx: &Context) -> bool { + match &ctx.primary.control[self.control_node_idx] { + ControlNode::Repeat(_) + | ControlNode::Empty(_) + | ControlNode::Seq(_) + | ControlNode::Par(_) => true, + ControlNode::Enable(_) + | ControlNode::If(_) + | ControlNode::While(_) + | ControlNode::Invoke(_) => 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 = 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)] @@ -327,7 +402,7 @@ impl SearchPath { .components .iter() .fold_while(ControlIdx::new(0), |current_root, (_, comp_info)| { - if let Some(index) = comp_info.control { + if let Some(index) = comp_info.control() { if index >= current_root && index < target { FoldWhile::Continue(index) } else { @@ -356,14 +431,12 @@ pub struct WithEntry { pub group: CombGroupIdx, /// Whether or not a body has been executed. Only used by if statements pub entered: bool, - pub thread: Option, } impl WithEntry { - pub fn new(group: CombGroupIdx, thread: Option) -> Self { + pub fn new(group: CombGroupIdx) -> Self { Self { group, - thread, entered: false, } } @@ -373,12 +446,48 @@ impl WithEntry { } } +#[derive(Debug, Clone)] +pub struct ParEntry { + child_count: ChildCount, + finished_threads: SmallVec<[ThreadIdx; 4]>, +} + +impl ParEntry { + pub fn child_count_mut(&mut self) -> &mut ChildCount { + &mut self.child_count + } + + pub fn child_count(&self) -> u16 { + self.child_count + } + pub fn add_finished_thread(&mut self, thread: ThreadIdx) { + self.finished_threads.push(thread); + } + + pub fn iter_finished_threads( + &self, + ) -> impl Iterator + '_ { + self.finished_threads.iter().copied() + } +} + +impl TryFrom for ParEntry { + type Error = std::num::TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(ParEntry { + child_count: value.try_into()?, + finished_threads: SmallVec::new(), + }) + } +} + /// 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, - par_map: HashMap, + par_map: HashMap, continuous_assigns: Vec, with_map: HashMap, repeat_map: HashMap, @@ -391,13 +500,13 @@ pub type ControlTuple = (Option, ControlPoint); pub type PcFields = ( Vec, - HashMap, + HashMap, HashMap, HashMap, ); pub type PcMaps<'a> = ( - &'a mut HashMap, + &'a mut HashMap, &'a mut HashMap, &'a mut HashMap, ); @@ -427,11 +536,11 @@ impl ProgramCounter { &mut self.vec } - pub fn _par_map_mut(&mut self) -> &mut HashMap { + pub fn _par_map_mut(&mut self) -> &mut HashMap { &mut self.par_map } - pub fn _par_map(&self) -> &HashMap { + pub fn _par_map(&self) -> &HashMap { &self.par_map } diff --git a/interp/src/flatten/structures/environment/traverser.rs b/cider/src/flatten/structures/environment/traverser.rs similarity index 100% rename from interp/src/flatten/structures/environment/traverser.rs rename to cider/src/flatten/structures/environment/traverser.rs diff --git a/interp/src/flatten/structures/environment/wave.rs b/cider/src/flatten/structures/environment/wave.rs similarity index 99% rename from interp/src/flatten/structures/environment/wave.rs rename to cider/src/flatten/structures/environment/wave.rs index 1019433a09..9f7096dc24 100644 --- a/interp/src/flatten/structures/environment/wave.rs +++ b/cider/src/flatten/structures/environment/wave.rs @@ -18,7 +18,7 @@ pub enum WaveError { pub type Result = std::result::Result; -impl From for crate::errors::InterpreterError { +impl From for crate::errors::CiderError { fn from(value: WaveError) -> Self { Self::GenericError(value.to_string()) } diff --git a/interp/src/flatten/structures/index_trait.rs b/cider/src/flatten/structures/index_trait.rs similarity index 84% rename from interp/src/flatten/structures/index_trait.rs rename to cider/src/flatten/structures/index_trait.rs index 09f2588538..816f4c9dc0 100644 --- a/interp/src/flatten/structures/index_trait.rs +++ b/cider/src/flatten/structures/index_trait.rs @@ -164,6 +164,46 @@ where } } +/// A continuous range of indices that is split into two parts. +/// +/// Represents the ranges +/// `[start, split)` and `[split, end)` +#[derive(Debug, Clone)] +pub struct SplitIndexRange { + start: I, + split: I, + end: I, +} + +impl SplitIndexRange { + /// Create a new split index range. + /// + /// The `start` must be less than or equal to + /// the `split`, and the `split` must be less than or equal to the `end`. It will + /// panic if these conditions are not met. + pub fn new(start: I, split: I, end: I) -> Self { + assert!(start <= split); + assert!(split <= end); + + Self { start, split, end } + } + + /// Returns an iterator over the first segment of the range, i.e `[start, split)`. + pub fn iter_first(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.start, self.split)) + } + + /// Returns an iterator over the second segment of the range, i.e `[split, end)`. + pub fn iter_second(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.split, self.end)) + } + + /// Returns an iterator over the entire range. + pub fn iter_all(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.start, self.end)) + } +} + impl IntoIterator for IndexRange where I: IndexRef + PartialOrd, @@ -199,7 +239,7 @@ where current: I, } -impl<'a, I> ExactSizeIterator for IndexRangeIterator<'a, I> where +impl ExactSizeIterator for IndexRangeIterator<'_, I> where I: IndexRef + PartialOrd { } @@ -216,7 +256,7 @@ where } } -impl<'a, I> Iterator for IndexRangeIterator<'a, I> +impl Iterator for IndexRangeIterator<'_, I> where I: IndexRef + PartialOrd, { @@ -242,10 +282,10 @@ where (size, Some(size)) } } -/// An iterator over a range of indices but without +/// An iterator over a range of indices that owns the range, rather than borrowing it. /// /// Because I really played myself by making the [IndexRangeIterator] have a -/// lifetime attached to it. This one doesn't do that. As with it's sibling, the +/// lifetime attached to it. This one doesn't do that. As with its sibling, the /// range is half open, meaning that the start is inclusive, but the end is /// exclusive. pub struct OwnedIndexRangeIterator @@ -253,7 +293,6 @@ where I: IndexRef + PartialOrd, { range: IndexRange, - current: I, } impl OwnedIndexRangeIterator @@ -261,10 +300,7 @@ where I: IndexRef + PartialOrd, { pub fn new(range: IndexRange) -> Self { - Self { - range, - current: range.start, - } + Self { range } } } @@ -275,18 +311,18 @@ where type Item = I; fn next(&mut self) -> Option { - if self.current < self.range.end { - let current = self.current; - self.current = I::new(self.current.index() + 1); - Some(current) + if self.range.start < self.range.end { + let out = self.range.start; + self.range.start = I::new(self.range.start.index() + 1); + Some(out) } else { None } } fn size_hint(&self) -> (usize, Option) { - let size = if self.range.end.index() > self.current.index() { - self.range.end.index() - self.current.index() + let size = if self.range.end > self.range.start { + self.range.end.index() - self.range.start.index() } else { 0 }; diff --git a/interp/src/flatten/structures/indexed_map.rs b/cider/src/flatten/structures/indexed_map.rs similarity index 95% rename from interp/src/flatten/structures/indexed_map.rs rename to cider/src/flatten/structures/indexed_map.rs index 137788b09b..58b1e7a135 100644 --- a/interp/src/flatten/structures/indexed_map.rs +++ b/cider/src/flatten/structures/indexed_map.rs @@ -4,7 +4,7 @@ use std::{ ops::{self, Index}, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct IndexedMap where K: IndexRef, @@ -121,6 +121,10 @@ where self.data.iter_mut() } + pub fn values(&self) -> impl Iterator { + self.data.iter() + } + pub fn keys(&self) -> impl Iterator + '_ { // TODO (griffin): Make this an actual struct instead self.data.iter().enumerate().map(|(i, _)| K::new(i)) @@ -143,6 +147,7 @@ where Self::new() } } + #[allow(dead_code)] pub struct IndexedMapRangeIterator<'range, 'data, K, D> where @@ -152,15 +157,12 @@ where data: &'data IndexedMap, } -impl<'range, 'data, K, D> ExactSizeIterator - for IndexedMapRangeIterator<'range, 'data, K, D> -where - K: IndexRef + PartialOrd, +impl ExactSizeIterator for IndexedMapRangeIterator<'_, '_, K, D> where + K: IndexRef + PartialOrd { } -impl<'range, 'data, K, D> Iterator - for IndexedMapRangeIterator<'range, 'data, K, D> +impl<'data, K, D> Iterator for IndexedMapRangeIterator<'_, 'data, K, D> where K: IndexRef + PartialOrd, { diff --git a/interp/src/flatten/structures/mod.rs b/cider/src/flatten/structures/mod.rs similarity index 100% rename from interp/src/flatten/structures/mod.rs rename to cider/src/flatten/structures/mod.rs diff --git a/interp/src/flatten/structures/printer.rs b/cider/src/flatten/structures/printer.rs similarity index 98% rename from interp/src/flatten/structures/printer.rs rename to cider/src/flatten/structures/printer.rs index 7d42a2bb71..952c65ee3d 100644 --- a/interp/src/flatten/structures/printer.rs +++ b/cider/src/flatten/structures/printer.rs @@ -76,9 +76,10 @@ impl<'a> Printer<'a> { for x in self.ctx.secondary[idx].definitions.comb_groups() { self.print_comb_group(x, idx) } - if !self.ctx.primary[idx].continuous_assignments.is_empty() { + if !self.ctx.primary[idx].continuous_assignments().is_empty() { println!("{}", text_utils::indent("Continuous Assignments:", 1)); - for assign in self.ctx.primary[idx].continuous_assignments.iter() { + for assign in self.ctx.primary[idx].continuous_assignments().iter() + { println!( "{}", text_utils::indent(self.print_assignment(idx, assign), 2) @@ -87,7 +88,7 @@ impl<'a> Printer<'a> { } println!(); println!("{}", text_utils::indent("Control:", 1)); - if let Some(ctrl) = self.ctx.primary[idx].control { + if let Some(ctrl) = self.ctx.primary[idx].control() { println!("{}", self.format_control(idx, ctrl, 2)); } } diff --git a/interp/src/flatten/structures/sparse_map.rs b/cider/src/flatten/structures/sparse_map.rs similarity index 98% rename from interp/src/flatten/structures/sparse_map.rs rename to cider/src/flatten/structures/sparse_map.rs index fc4fcfab60..0481dfa73a 100644 --- a/interp/src/flatten/structures/sparse_map.rs +++ b/cider/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/cider/src/flatten/structures/thread.rs similarity index 85% rename from interp/src/flatten/structures/thread.rs rename to cider/src/flatten/structures/thread.rs index 938990dbae..85a70fb5fe 100644 --- a/interp/src/flatten/structures/thread.rs +++ b/cider/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, clock_id: ClockIdx, @@ -25,24 +25,33 @@ impl ThreadInfo { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ThreadMap { map: IndexedMap, } impl ThreadMap { - pub fn new(root_clock: ClockIdx) -> Self { + pub fn new(root_clock: ClockIdx, continuous_clock: ClockIdx) -> Self { let mut map = IndexedMap::new(); map.push(ThreadInfo { parent: None, clock_id: root_clock, }); + map.push(ThreadInfo { + parent: None, + clock_id: continuous_clock, + }); Self { map } } pub fn root_thread() -> ThreadIdx { ThreadIdx::from(0) } + + pub fn continuous_thread() -> ThreadIdx { + ThreadIdx::from(1) + } + /// Lookup the clock associated with the given thread id. Returns `None` if /// the thread id is invalid. pub fn get_clock_id(&self, thread_id: &ThreadIdx) -> Option { diff --git a/interp/src/flatten/text_utils.rs b/cider/src/flatten/text_utils.rs similarity index 100% rename from interp/src/flatten/text_utils.rs rename to cider/src/flatten/text_utils.rs diff --git a/interp/src/lib.rs b/cider/src/lib.rs similarity index 100% rename from interp/src/lib.rs rename to cider/src/lib.rs diff --git a/cider/src/logging.rs b/cider/src/logging.rs new file mode 100644 index 0000000000..387f5ffaf2 --- /dev/null +++ b/cider/src/logging.rs @@ -0,0 +1,32 @@ +// re-export for convenience +pub use slog::Logger; +#[allow(unused_imports)] +pub(crate) use slog::{debug, error, info, o, trace, warn}; + +use slog::{Drain, Level}; + +use crate::configuration::LoggingConfig; + +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 conf.quiet && !conf.debug_logging { + Level::Error + } else { + Level::Trace + }; + let drain = drain.filter_level(filter_level).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!()); + + if conf.quiet && conf.debug_logging { + warn!( + logger, + "Quiet mode ignored because debug logging is enabled" + ) + } + logger +} diff --git a/interp/src/macros.rs b/cider/src/macros.rs similarity index 96% rename from interp/src/macros.rs rename to cider/src/macros.rs index 2eade11a04..046ef2b273 100644 --- a/interp/src/macros.rs +++ b/cider/src/macros.rs @@ -29,7 +29,7 @@ macro_rules! lit_or_id { #[macro_export] /// Helper macro to generate port bindings. /// ``` -/// # use interp::port_bindings; +/// # use cider::port_bindings; /// port_bindings![ binds; /// r#in -> (16, 32), /// write_en -> (1, 1) @@ -46,7 +46,7 @@ macro_rules! port_bindings { /// Helper macro to generate validation checks for the input passed to primitives /// ``` -/// # use interp::validate; +/// # use cider::validate; /// # use baa::BitVecValue; /// # let input = [("left", [4,4,4,4])]; /// # let inputs = &input; @@ -72,7 +72,7 @@ macro_rules! validate { /// Helper macro to generate validation checks for the input passed to /// primitives, does not error on unknown ports /// ``` -/// # use interp::validate_friendly; +/// # use cider::validate_friendly; /// # use baa::BitVecValue; /// # let input = [("left", [4,4,4,4])]; /// # let inputs = &input; diff --git a/interp/src/main.rs b/cider/src/main.rs similarity index 83% rename from interp/src/main.rs rename to cider/src/main.rs index bcb7858839..c86289a380 100644 --- a/interp/src/main.rs +++ b/cider/src/main.rs @@ -3,14 +3,13 @@ use argh::FromArgs; use calyx_utils::OutputFile; -use interp::{ +use cider::{ 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, @@ -98,28 +102,25 @@ 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( + let i_ctx = cider::flatten::setup_simulation( &opts.file, &opts.lib_path, opts.skip_verification, @@ -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/cider/src/serialization/data_dump.rs similarity index 94% rename from interp/src/serialization/data_dump.rs rename to cider/src/serialization/data_dump.rs index 5a95ffedfd..d676814c50 100644 --- a/interp/src/serialization/data_dump.rs +++ b/cider/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, SerializationError> { + let mut header_str = Vec::new(); + ciborium::ser::into_writer(&self, &mut header_str)?; + Ok(header_str) + } + + pub fn deserialize(data: &[u8]) -> Result { + 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( &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( + mut reader: R, ) -> Result { 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 = Vec::with_capacity(header.data_size()); diff --git a/interp/src/serialization/formatting.rs b/cider/src/serialization/formatting.rs similarity index 96% rename from interp/src/serialization/formatting.rs rename to cider/src/serialization/formatting.rs index ed41367c2a..55166958fc 100644 --- a/interp/src/serialization/formatting.rs +++ b/cider/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 for Shape { fn from(u: usize) -> Self { diff --git a/interp/src/serialization/mod.rs b/cider/src/serialization/mod.rs similarity index 100% rename from interp/src/serialization/mod.rs rename to cider/src/serialization/mod.rs diff --git a/interp/src/tests/mod.rs b/cider/src/tests/mod.rs similarity index 100% rename from interp/src/tests/mod.rs rename to cider/src/tests/mod.rs diff --git a/interp/src/tests/values.rs b/cider/src/tests/values.rs similarity index 100% rename from interp/src/tests/values.rs rename to cider/src/tests/values.rs diff --git a/interp/tests/benchmarks/ntt-32.expect b/cider/tests/benchmarks/ntt-32.expect similarity index 100% rename from interp/tests/benchmarks/ntt-32.expect rename to cider/tests/benchmarks/ntt-32.expect diff --git a/interp/tests/benchmarks/ntt-32.futil b/cider/tests/benchmarks/ntt-32.futil similarity index 100% rename from interp/tests/benchmarks/ntt-32.futil rename to cider/tests/benchmarks/ntt-32.futil diff --git a/interp/tests/benchmarks/ntt-32.futil.data b/cider/tests/benchmarks/ntt-32.futil.data similarity index 100% rename from interp/tests/benchmarks/ntt-32.futil.data rename to cider/tests/benchmarks/ntt-32.futil.data diff --git a/interp/tests/benchmarks/ntt-64.expect b/cider/tests/benchmarks/ntt-64.expect similarity index 100% rename from interp/tests/benchmarks/ntt-64.expect rename to cider/tests/benchmarks/ntt-64.expect diff --git a/interp/tests/benchmarks/ntt-64.futil b/cider/tests/benchmarks/ntt-64.futil similarity index 100% rename from interp/tests/benchmarks/ntt-64.futil rename to cider/tests/benchmarks/ntt-64.futil diff --git a/interp/tests/benchmarks/ntt-64.futil.data b/cider/tests/benchmarks/ntt-64.futil.data similarity index 100% rename from interp/tests/benchmarks/ntt-64.futil.data rename to cider/tests/benchmarks/ntt-64.futil.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-2mm.expect b/cider/tests/benchmarks/polybench/linear-algebra-2mm.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-2mm.expect rename to cider/tests/benchmarks/polybench/linear-algebra-2mm.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-2mm.fuse b/cider/tests/benchmarks/polybench/linear-algebra-2mm.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-2mm.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-2mm.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-2mm.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-2mm.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-2mm.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-2mm.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-3mm.expect b/cider/tests/benchmarks/polybench/linear-algebra-3mm.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-3mm.expect rename to cider/tests/benchmarks/polybench/linear-algebra-3mm.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-3mm.fuse b/cider/tests/benchmarks/polybench/linear-algebra-3mm.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-3mm.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-3mm.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-3mm.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-3mm.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-3mm.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-3mm.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-atax.expect b/cider/tests/benchmarks/polybench/linear-algebra-atax.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-atax.expect rename to cider/tests/benchmarks/polybench/linear-algebra-atax.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-atax.fuse b/cider/tests/benchmarks/polybench/linear-algebra-atax.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-atax.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-atax.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-atax.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-atax.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-atax.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-atax.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-bicg.expect b/cider/tests/benchmarks/polybench/linear-algebra-bicg.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-bicg.expect rename to cider/tests/benchmarks/polybench/linear-algebra-bicg.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-bicg.fuse b/cider/tests/benchmarks/polybench/linear-algebra-bicg.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-bicg.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-bicg.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-bicg.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-bicg.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-bicg.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-bicg.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-cholesky.expect b/cider/tests/benchmarks/polybench/linear-algebra-cholesky.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-cholesky.expect rename to cider/tests/benchmarks/polybench/linear-algebra-cholesky.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-cholesky.fuse b/cider/tests/benchmarks/polybench/linear-algebra-cholesky.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-cholesky.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-cholesky.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-cholesky.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-cholesky.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-cholesky.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-cholesky.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-doitgen.expect b/cider/tests/benchmarks/polybench/linear-algebra-doitgen.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-doitgen.expect rename to cider/tests/benchmarks/polybench/linear-algebra-doitgen.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-doitgen.fuse b/cider/tests/benchmarks/polybench/linear-algebra-doitgen.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-doitgen.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-doitgen.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-doitgen.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-doitgen.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-doitgen.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-doitgen.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-durbin.expect b/cider/tests/benchmarks/polybench/linear-algebra-durbin.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-durbin.expect rename to cider/tests/benchmarks/polybench/linear-algebra-durbin.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-durbin.fuse b/cider/tests/benchmarks/polybench/linear-algebra-durbin.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-durbin.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-durbin.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-durbin.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-durbin.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-durbin.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-durbin.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemm.expect b/cider/tests/benchmarks/polybench/linear-algebra-gemm.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemm.expect rename to cider/tests/benchmarks/polybench/linear-algebra-gemm.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemm.fuse b/cider/tests/benchmarks/polybench/linear-algebra-gemm.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemm.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-gemm.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemm.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-gemm.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemm.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-gemm.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemver.expect b/cider/tests/benchmarks/polybench/linear-algebra-gemver.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemver.expect rename to cider/tests/benchmarks/polybench/linear-algebra-gemver.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemver.fuse b/cider/tests/benchmarks/polybench/linear-algebra-gemver.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemver.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-gemver.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gemver.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-gemver.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gemver.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-gemver.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gesummv.expect b/cider/tests/benchmarks/polybench/linear-algebra-gesummv.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gesummv.expect rename to cider/tests/benchmarks/polybench/linear-algebra-gesummv.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gesummv.fuse b/cider/tests/benchmarks/polybench/linear-algebra-gesummv.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gesummv.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-gesummv.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gesummv.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-gesummv.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gesummv.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-gesummv.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.expect b/cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.expect rename to cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse b/cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-gramschmidt.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-lu.expect b/cider/tests/benchmarks/polybench/linear-algebra-lu.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-lu.expect rename to cider/tests/benchmarks/polybench/linear-algebra-lu.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-lu.fuse b/cider/tests/benchmarks/polybench/linear-algebra-lu.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-lu.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-lu.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-lu.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-lu.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-lu.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-lu.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-ludcmp.expect b/cider/tests/benchmarks/polybench/linear-algebra-ludcmp.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-ludcmp.expect rename to cider/tests/benchmarks/polybench/linear-algebra-ludcmp.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse b/cider/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-ludcmp.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-mvt.expect b/cider/tests/benchmarks/polybench/linear-algebra-mvt.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-mvt.expect rename to cider/tests/benchmarks/polybench/linear-algebra-mvt.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-mvt.fuse b/cider/tests/benchmarks/polybench/linear-algebra-mvt.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-mvt.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-mvt.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-mvt.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-mvt.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-mvt.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-mvt.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-symm.expect b/cider/tests/benchmarks/polybench/linear-algebra-symm.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-symm.expect rename to cider/tests/benchmarks/polybench/linear-algebra-symm.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-symm.fuse b/cider/tests/benchmarks/polybench/linear-algebra-symm.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-symm.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-symm.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-symm.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-symm.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-symm.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-symm.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syr2k.expect b/cider/tests/benchmarks/polybench/linear-algebra-syr2k.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syr2k.expect rename to cider/tests/benchmarks/polybench/linear-algebra-syr2k.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syr2k.fuse b/cider/tests/benchmarks/polybench/linear-algebra-syr2k.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syr2k.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-syr2k.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syr2k.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-syr2k.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syr2k.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-syr2k.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syrk.expect b/cider/tests/benchmarks/polybench/linear-algebra-syrk.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syrk.expect rename to cider/tests/benchmarks/polybench/linear-algebra-syrk.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syrk.fuse b/cider/tests/benchmarks/polybench/linear-algebra-syrk.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syrk.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-syrk.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-syrk.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-syrk.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-syrk.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-syrk.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trisolv.expect b/cider/tests/benchmarks/polybench/linear-algebra-trisolv.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trisolv.expect rename to cider/tests/benchmarks/polybench/linear-algebra-trisolv.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trisolv.fuse b/cider/tests/benchmarks/polybench/linear-algebra-trisolv.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trisolv.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-trisolv.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trisolv.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-trisolv.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trisolv.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-trisolv.fuse.data diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trmm.expect b/cider/tests/benchmarks/polybench/linear-algebra-trmm.expect similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trmm.expect rename to cider/tests/benchmarks/polybench/linear-algebra-trmm.expect diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trmm.fuse b/cider/tests/benchmarks/polybench/linear-algebra-trmm.fuse similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trmm.fuse rename to cider/tests/benchmarks/polybench/linear-algebra-trmm.fuse diff --git a/interp/tests/benchmarks/polybench/linear-algebra-trmm.fuse.data b/cider/tests/benchmarks/polybench/linear-algebra-trmm.fuse.data similarity index 100% rename from interp/tests/benchmarks/polybench/linear-algebra-trmm.fuse.data rename to cider/tests/benchmarks/polybench/linear-algebra-trmm.fuse.data diff --git a/interp/tests/benchmarks/tcam-32.expect b/cider/tests/benchmarks/tcam-32.expect similarity index 100% rename from interp/tests/benchmarks/tcam-32.expect rename to cider/tests/benchmarks/tcam-32.expect diff --git a/interp/tests/benchmarks/tcam-32.futil b/cider/tests/benchmarks/tcam-32.futil similarity index 100% rename from interp/tests/benchmarks/tcam-32.futil rename to cider/tests/benchmarks/tcam-32.futil diff --git a/interp/tests/benchmarks/tcam-32.futil.data b/cider/tests/benchmarks/tcam-32.futil.data similarity index 100% rename from interp/tests/benchmarks/tcam-32.futil.data rename to cider/tests/benchmarks/tcam-32.futil.data diff --git a/interp/tests/benchmarks/tcam-64.expect b/cider/tests/benchmarks/tcam-64.expect similarity index 100% rename from interp/tests/benchmarks/tcam-64.expect rename to cider/tests/benchmarks/tcam-64.expect diff --git a/interp/tests/benchmarks/tcam-64.futil b/cider/tests/benchmarks/tcam-64.futil similarity index 100% rename from interp/tests/benchmarks/tcam-64.futil rename to cider/tests/benchmarks/tcam-64.futil diff --git a/interp/tests/benchmarks/tcam-64.futil.data b/cider/tests/benchmarks/tcam-64.futil.data similarity index 100% rename from interp/tests/benchmarks/tcam-64.futil.data rename to cider/tests/benchmarks/tcam-64.futil.data diff --git a/interp/tests/complex/unsigned-div.expect b/cider/tests/complex/unsigned-div.expect similarity index 100% rename from interp/tests/complex/unsigned-div.expect rename to cider/tests/complex/unsigned-div.expect diff --git a/interp/tests/complex/unsigned-div.futil b/cider/tests/complex/unsigned-div.futil similarity index 100% rename from interp/tests/complex/unsigned-div.futil rename to cider/tests/complex/unsigned-div.futil diff --git a/interp/tests/complex/unsigned-dot-product.expect b/cider/tests/complex/unsigned-dot-product.expect similarity index 100% rename from interp/tests/complex/unsigned-dot-product.expect rename to cider/tests/complex/unsigned-dot-product.expect diff --git a/interp/tests/complex/unsigned-dot-product.futil b/cider/tests/complex/unsigned-dot-product.futil similarity index 95% rename from interp/tests/complex/unsigned-dot-product.futil rename to cider/tests/complex/unsigned-dot-product.futil index 7f7117b7c9..f30343e2ee 100644 --- a/interp/tests/complex/unsigned-dot-product.futil +++ b/cider/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/complex/unsigned-dot-product.futil.data b/cider/tests/complex/unsigned-dot-product.futil.data similarity index 100% rename from interp/tests/complex/unsigned-dot-product.futil.data rename to cider/tests/complex/unsigned-dot-product.futil.data diff --git a/interp/tests/control/if.expect b/cider/tests/control/if.expect similarity index 100% rename from interp/tests/control/if.expect rename to cider/tests/control/if.expect diff --git a/interp/tests/control/if.futil b/cider/tests/control/if.futil similarity index 100% rename from interp/tests/control/if.futil rename to cider/tests/control/if.futil diff --git a/interp/tests/control/if.futil.data b/cider/tests/control/if.futil.data similarity index 100% rename from interp/tests/control/if.futil.data rename to cider/tests/control/if.futil.data diff --git a/interp/tests/control/if_reg.expect b/cider/tests/control/if_reg.expect similarity index 100% rename from interp/tests/control/if_reg.expect rename to cider/tests/control/if_reg.expect diff --git a/interp/tests/control/if_reg.futil b/cider/tests/control/if_reg.futil similarity index 100% rename from interp/tests/control/if_reg.futil rename to cider/tests/control/if_reg.futil diff --git a/interp/tests/control/invoke/invoke.expect b/cider/tests/control/invoke/invoke.expect similarity index 100% rename from interp/tests/control/invoke/invoke.expect rename to cider/tests/control/invoke/invoke.expect diff --git a/interp/tests/control/invoke/invoke.futil b/cider/tests/control/invoke/invoke.futil similarity index 100% rename from interp/tests/control/invoke/invoke.futil rename to cider/tests/control/invoke/invoke.futil diff --git a/interp/tests/control/invoke/invoke.futil.data b/cider/tests/control/invoke/invoke.futil.data similarity index 100% rename from interp/tests/control/invoke/invoke.futil.data rename to cider/tests/control/invoke/invoke.futil.data diff --git a/interp/tests/control/invoke/k3.expect b/cider/tests/control/invoke/k3.expect similarity index 100% rename from interp/tests/control/invoke/k3.expect rename to cider/tests/control/invoke/k3.expect diff --git a/interp/tests/control/invoke/k3.futil b/cider/tests/control/invoke/k3.futil similarity index 100% rename from interp/tests/control/invoke/k3.futil rename to cider/tests/control/invoke/k3.futil diff --git a/interp/tests/control/invoke/k3.futil.data b/cider/tests/control/invoke/k3.futil.data similarity index 100% rename from interp/tests/control/invoke/k3.futil.data rename to cider/tests/control/invoke/k3.futil.data diff --git a/interp/tests/control/iter_mult.expect b/cider/tests/control/iter_mult.expect similarity index 100% rename from interp/tests/control/iter_mult.expect rename to cider/tests/control/iter_mult.expect diff --git a/interp/tests/control/iter_mult.futil b/cider/tests/control/iter_mult.futil similarity index 100% rename from interp/tests/control/iter_mult.futil rename to cider/tests/control/iter_mult.futil diff --git a/interp/tests/control/par_mem.expect b/cider/tests/control/par_mem.expect similarity index 100% rename from interp/tests/control/par_mem.expect rename to cider/tests/control/par_mem.expect diff --git a/interp/tests/control/par_mem.futil b/cider/tests/control/par_mem.futil similarity index 100% rename from interp/tests/control/par_mem.futil rename to cider/tests/control/par_mem.futil diff --git a/interp/tests/control/par_reg.expect b/cider/tests/control/par_reg.expect similarity index 100% rename from interp/tests/control/par_reg.expect rename to cider/tests/control/par_reg.expect diff --git a/interp/tests/control/par_reg.futil b/cider/tests/control/par_reg.futil similarity index 100% rename from interp/tests/control/par_reg.futil rename to cider/tests/control/par_reg.futil diff --git a/interp/tests/control/reg_seq.expect b/cider/tests/control/reg_seq.expect similarity index 100% rename from interp/tests/control/reg_seq.expect rename to cider/tests/control/reg_seq.expect diff --git a/interp/tests/control/reg_seq.futil b/cider/tests/control/reg_seq.futil similarity index 100% rename from interp/tests/control/reg_seq.futil rename to cider/tests/control/reg_seq.futil diff --git a/interp/tests/control/static/while_static.expect b/cider/tests/control/static/while_static.expect similarity index 100% rename from interp/tests/control/static/while_static.expect rename to cider/tests/control/static/while_static.expect diff --git a/interp/tests/control/static/while_static.futil b/cider/tests/control/static/while_static.futil similarity index 100% rename from interp/tests/control/static/while_static.futil rename to cider/tests/control/static/while_static.futil diff --git a/interp/tests/control/while.expect b/cider/tests/control/while.expect similarity index 100% rename from interp/tests/control/while.expect rename to cider/tests/control/while.expect diff --git a/interp/tests/control/while.futil b/cider/tests/control/while.futil similarity index 96% rename from interp/tests/control/while.futil rename to cider/tests/control/while.futil index 92c072e641..9e10b15a6a 100644 --- a/interp/tests/control/while.futil +++ b/cider/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/cider/tests/data-race/conflict.expect b/cider/tests/data-race/conflict.expect new file mode 100644 index 0000000000..fdf4dca079 --- /dev/null +++ b/cider/tests/data-race/conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.count diff --git a/cider/tests/data-race/conflict.futil b/cider/tests/data-race/conflict.futil new file mode 100644 index 0000000000..335ac9bc35 --- /dev/null +++ b/cider/tests/data-race/conflict.futil @@ -0,0 +1,66 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/binary_operators.futil"; + +component main() -> (out: 32) { + cells { + pow = std_reg(32); + count = std_reg(4); + mul = std_mult_pipe(32); + lt = std_lt(4); + incr = std_add(4); + const0 = std_const(4, 3); + base = std_reg(32); + exp = std_reg(4); + my_wire = std_wire(1); + useless_wire = std_wire(1); + } + wires { + group init<"static"=1> { + pow.in = 32'd1; + pow.write_en = 1'd1; + count.in = 4'd0; + count.write_en = 1'd1; + + base.in = 32'd10; + base.write_en = 1'd1; + + exp.in = 4'd3; + exp.write_en = 1'd1; + + + init[done] = pow.done & count.done ? 1'd1; + } + group do_mul { + mul.left = base.out; + mul.right = pow.out; + mul.go = !mul.done ? 1'd1; + pow.in = mul.out; + pow.write_en = mul.done; + useless_wire.in = my_wire.out; + do_mul[done] = pow.done; + } + group incr_count<"static"=1> { + incr.left = 4'd1; + incr.right = count.out; + count.in = incr.out; + count.write_en = 1'd1; + incr_count[done] = count.done; + } + comb group cond { + lt.right = exp.out; + lt.left = count.out; + my_wire.in = !lt.out & 1'd0 ? 1'd1; + } + + out = pow.out; + } + control { + seq { + init; + while lt.out with cond { + par { do_mul; incr_count; } + } + } + } +} diff --git a/cider/tests/data-race/continuous-race.expect b/cider/tests/data-race/continuous-race.expect new file mode 100644 index 0000000000..d6eeceb5f8 --- /dev/null +++ b/cider/tests/data-race/continuous-race.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.val diff --git a/cider/tests/data-race/continuous-race.futil b/cider/tests/data-race/continuous-race.futil new file mode 100644 index 0000000000..03fcc56bf4 --- /dev/null +++ b/cider/tests/data-race/continuous-race.futil @@ -0,0 +1,28 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +component main() -> () { + cells { + @external mem = comb_mem_d1(32, 20, 32); + val = std_reg(32); + add = std_add(32); + } + wires { + add.right = 32'b1; + add.left = val.out; + val.write_en = 1'b1; + val.in = add.out; + + group store { + mem.addr0 = val.out; + mem.write_data = val.out; + mem.write_en = 1'b1; + store[done] = mem.done; + } + } + control { + repeat 19 { + store; + } + } +} diff --git a/cider/tests/data-race/guard-conflict.expect b/cider/tests/data-race/guard-conflict.expect new file mode 100644 index 0000000000..cc56d3b526 --- /dev/null +++ b/cider/tests/data-race/guard-conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.cond_reg diff --git a/interp/tests/data-race/guard-conflict.futil b/cider/tests/data-race/guard-conflict.futil similarity index 100% rename from interp/tests/data-race/guard-conflict.futil rename to cider/tests/data-race/guard-conflict.futil diff --git a/cider/tests/data-race/par-conflict-cmem.expect b/cider/tests/data-race/par-conflict-cmem.expect new file mode 100644 index 0000000000..ed1403c818 --- /dev/null +++ b/cider/tests/data-race/par-conflict-cmem.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same memory main.cond_mem in slot 0 diff --git a/interp/tests/data-race/par-conflict-cmem.futil b/cider/tests/data-race/par-conflict-cmem.futil similarity index 100% rename from interp/tests/data-race/par-conflict-cmem.futil rename to cider/tests/data-race/par-conflict-cmem.futil diff --git a/cider/tests/data-race/par-conflict.expect b/cider/tests/data-race/par-conflict.expect new file mode 100644 index 0000000000..cc56d3b526 --- /dev/null +++ b/cider/tests/data-race/par-conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.cond_reg diff --git a/interp/tests/data-race/par-conflict.futil b/cider/tests/data-race/par-conflict.futil similarity index 100% rename from interp/tests/data-race/par-conflict.futil rename to cider/tests/data-race/par-conflict.futil diff --git a/cider/tests/data-race/strange_mult.expect b/cider/tests/data-race/strange_mult.expect new file mode 100644 index 0000000000..f96745bfa8 --- /dev/null +++ b/cider/tests/data-race/strange_mult.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.first_reg diff --git a/cider/tests/data-race/strange_mult.futil b/cider/tests/data-race/strange_mult.futil new file mode 100644 index 0000000000..8d3c43f4dd --- /dev/null +++ b/cider/tests/data-race/strange_mult.futil @@ -0,0 +1,50 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/binary_operators.futil"; + +component main() -> (out: 32) { + cells { + first_reg = std_reg(32); + mult = std_mult_pipe(32); + second_reg = std_reg(32); + adder = std_add(32); + adder2 = std_add(32); + } + wires { + group init { + first_reg.in = 32'd10; + first_reg.write_en = 1'd1; + second_reg.in = 32'd20; + second_reg.write_en = 1'd1; + init[done] = first_reg.done; + } + + group incr_first_reg { + adder.left = first_reg.out; + adder.right = 32'd1; + first_reg.in = adder.out; + first_reg.write_en = 1'd1; + incr_first_reg[done] = first_reg.done; + } + + group incr_second_reg { + adder2.left = second_reg.out; + adder2.right = 32'd1; + second_reg.in = adder2.out; + second_reg.write_en = 1'd1; + incr_second_reg[done] = second_reg.done; + } + + mult.left = first_reg.out; + mult.right = second_reg.out; + } + control { + seq { + init; + par { + incr_first_reg; + incr_second_reg; + } + } + } +} diff --git a/interp/tests/errors/multiple_drivers_comb.expect b/cider/tests/errors/multiple_drivers_comb.expect similarity index 100% rename from interp/tests/errors/multiple_drivers_comb.expect rename to cider/tests/errors/multiple_drivers_comb.expect diff --git a/interp/tests/errors/multiple_drivers_comb.futil b/cider/tests/errors/multiple_drivers_comb.futil similarity index 100% rename from interp/tests/errors/multiple_drivers_comb.futil rename to cider/tests/errors/multiple_drivers_comb.futil diff --git a/interp/tests/errors/multiple_drivers_reg.expect b/cider/tests/errors/multiple_drivers_reg.expect similarity index 100% rename from interp/tests/errors/multiple_drivers_reg.expect rename to cider/tests/errors/multiple_drivers_reg.expect diff --git a/interp/tests/errors/multiple_drivers_reg.futil b/cider/tests/errors/multiple_drivers_reg.futil similarity index 100% rename from interp/tests/errors/multiple_drivers_reg.futil rename to cider/tests/errors/multiple_drivers_reg.futil diff --git a/interp/tests/lowered/if.expect b/cider/tests/lowered/if.expect similarity index 100% rename from interp/tests/lowered/if.expect rename to cider/tests/lowered/if.expect diff --git a/interp/tests/lowered/if_reg.expect b/cider/tests/lowered/if_reg.expect similarity index 100% rename from interp/tests/lowered/if_reg.expect rename to cider/tests/lowered/if_reg.expect diff --git a/interp/tests/lowered/par_mem.expect b/cider/tests/lowered/par_mem.expect similarity index 100% rename from interp/tests/lowered/par_mem.expect rename to cider/tests/lowered/par_mem.expect diff --git a/interp/tests/lowered/par_reg.expect b/cider/tests/lowered/par_reg.expect similarity index 100% rename from interp/tests/lowered/par_reg.expect rename to cider/tests/lowered/par_reg.expect diff --git a/interp/tests/lowered/reg_seq.expect b/cider/tests/lowered/reg_seq.expect similarity index 100% rename from interp/tests/lowered/reg_seq.expect rename to cider/tests/lowered/reg_seq.expect diff --git a/interp/tests/multi-comp/my_add.expect b/cider/tests/multi-comp/my_add.expect similarity index 100% rename from interp/tests/multi-comp/my_add.expect rename to cider/tests/multi-comp/my_add.expect diff --git a/interp/tests/multi-comp/my_add.futil b/cider/tests/multi-comp/my_add.futil similarity index 100% rename from interp/tests/multi-comp/my_add.futil rename to cider/tests/multi-comp/my_add.futil diff --git a/interp/tests/multi-comp/my_add_invoke.expect b/cider/tests/multi-comp/my_add_invoke.expect similarity index 100% rename from interp/tests/multi-comp/my_add_invoke.expect rename to cider/tests/multi-comp/my_add_invoke.expect diff --git a/interp/tests/multi-comp/my_add_invoke.futil b/cider/tests/multi-comp/my_add_invoke.futil similarity index 100% rename from interp/tests/multi-comp/my_add_invoke.futil rename to cider/tests/multi-comp/my_add_invoke.futil diff --git a/interp/tests/multi-comp/my_add_invoke_ref.expect b/cider/tests/multi-comp/my_add_invoke_ref.expect similarity index 100% rename from interp/tests/multi-comp/my_add_invoke_ref.expect rename to cider/tests/multi-comp/my_add_invoke_ref.expect diff --git a/interp/tests/multi-comp/my_add_invoke_ref.futil b/cider/tests/multi-comp/my_add_invoke_ref.futil similarity index 100% rename from interp/tests/multi-comp/my_add_invoke_ref.futil rename to cider/tests/multi-comp/my_add_invoke_ref.futil diff --git a/interp/tests/ntt-results/ntt-16-reduced-4.expect b/cider/tests/ntt-results/ntt-16-reduced-4.expect similarity index 100% rename from interp/tests/ntt-results/ntt-16-reduced-4.expect rename to cider/tests/ntt-results/ntt-16-reduced-4.expect diff --git a/interp/tests/ntt-results/ntt-16.expect b/cider/tests/ntt-results/ntt-16.expect similarity index 100% rename from interp/tests/ntt-results/ntt-16.expect rename to cider/tests/ntt-results/ntt-16.expect diff --git a/interp/tests/ntt-results/ntt-8.expect b/cider/tests/ntt-results/ntt-8.expect similarity index 100% rename from interp/tests/ntt-results/ntt-8.expect rename to cider/tests/ntt-results/ntt-8.expect diff --git a/interp/tests/par-to-seq/par_mem.expect b/cider/tests/par-to-seq/par_mem.expect similarity index 100% rename from interp/tests/par-to-seq/par_mem.expect rename to cider/tests/par-to-seq/par_mem.expect diff --git a/interp/tests/par-to-seq/par_reg.expect b/cider/tests/par-to-seq/par_reg.expect similarity index 100% rename from interp/tests/par-to-seq/par_reg.expect rename to cider/tests/par-to-seq/par_reg.expect diff --git a/interp/tests/primitives/add-overflow.expect b/cider/tests/primitives/add-overflow.expect similarity index 100% rename from interp/tests/primitives/add-overflow.expect rename to cider/tests/primitives/add-overflow.expect diff --git a/interp/tests/primitives/add-overflow.futil b/cider/tests/primitives/add-overflow.futil similarity index 100% rename from interp/tests/primitives/add-overflow.futil rename to cider/tests/primitives/add-overflow.futil diff --git a/interp/tests/primitives/binary.expect b/cider/tests/primitives/binary.expect similarity index 100% rename from interp/tests/primitives/binary.expect rename to cider/tests/primitives/binary.expect diff --git a/interp/tests/primitives/binary.futil b/cider/tests/primitives/binary.futil similarity index 100% rename from interp/tests/primitives/binary.futil rename to cider/tests/primitives/binary.futil diff --git a/interp/tests/primitives/flickering_go.expect b/cider/tests/primitives/flickering_go.expect similarity index 100% rename from interp/tests/primitives/flickering_go.expect rename to cider/tests/primitives/flickering_go.expect diff --git a/interp/tests/primitives/flickering_go.futil b/cider/tests/primitives/flickering_go.futil similarity index 100% rename from interp/tests/primitives/flickering_go.futil rename to cider/tests/primitives/flickering_go.futil diff --git a/interp/tests/primitives/mem.expect b/cider/tests/primitives/mem.expect similarity index 100% rename from interp/tests/primitives/mem.expect rename to cider/tests/primitives/mem.expect diff --git a/interp/tests/primitives/mem.futil b/cider/tests/primitives/mem.futil similarity index 89% rename from interp/tests/primitives/mem.futil rename to cider/tests/primitives/mem.futil index 951a48503d..0800c6d230 100644 --- a/interp/tests/primitives/mem.futil +++ b/cider/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/primitives/mem2.expect b/cider/tests/primitives/mem2.expect similarity index 100% rename from interp/tests/primitives/mem2.expect rename to cider/tests/primitives/mem2.expect diff --git a/interp/tests/primitives/mem2.futil b/cider/tests/primitives/mem2.futil similarity index 100% rename from interp/tests/primitives/mem2.futil rename to cider/tests/primitives/mem2.futil diff --git a/interp/tests/primitives/reg.expect b/cider/tests/primitives/reg.expect similarity index 100% rename from interp/tests/primitives/reg.expect rename to cider/tests/primitives/reg.expect diff --git a/interp/tests/primitives/reg.futil b/cider/tests/primitives/reg.futil similarity index 100% rename from interp/tests/primitives/reg.futil rename to cider/tests/primitives/reg.futil diff --git a/interp/tests/primitives/slice.expect b/cider/tests/primitives/slice.expect similarity index 100% rename from interp/tests/primitives/slice.expect rename to cider/tests/primitives/slice.expect diff --git a/interp/tests/primitives/slice.futil b/cider/tests/primitives/slice.futil similarity index 100% rename from interp/tests/primitives/slice.futil rename to cider/tests/primitives/slice.futil diff --git a/interp/tests/primitives/sqrt.expect b/cider/tests/primitives/sqrt.expect similarity index 100% rename from interp/tests/primitives/sqrt.expect rename to cider/tests/primitives/sqrt.expect diff --git a/interp/tests/primitives/sqrt.futil b/cider/tests/primitives/sqrt.futil similarity index 100% rename from interp/tests/primitives/sqrt.futil rename to cider/tests/primitives/sqrt.futil diff --git a/interp/tests/runt.toml b/cider/tests/runt.toml similarity index 72% rename from interp/tests/runt.toml rename to cider/tests/runt.toml index 18c2967faf..f3f5ff1327 100644 --- a/interp/tests/runt.toml +++ b/cider/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 --check-data-race" \ + {} """ 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 --check-data-race" \ + {} """ 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" -s cider.flags="--check-data-race" """ timeout = 10 @@ -76,14 +91,17 @@ 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" \ + -s cider.flags="--check-data-race" """ [[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 +123,7 @@ fud2 --from calyx --to dat \ --through cider \ -s sim.data={}.data \ -s calyx.args="--log off" \ - {} | jq --sort-keys + {} """ [[tests]] @@ -115,19 +133,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 +166,8 @@ fud2 --from calyx --to dat \ --through cider \ -s sim.data={}.data \ -s calyx.args="--log off" \ - {} | jq --sort-keys + -s cider.flags="--check-data-race" \ + {} """ [[tests]] @@ -148,8 +178,9 @@ 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" \ + -s cider.flags="--check-data-race" \ + {} """ [[tests]] @@ -165,7 +196,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 +207,8 @@ fud2 --from calyx --to dat \ --through cider \ -s calyx.args="--log off" \ -s sim.data={}.data \ - {} | jq --sort-keys + -s cider.flags="--check-data-race" \ + {} """ [[tests]] @@ -187,7 +219,8 @@ fud2 --from calyx --to dat \ --through cider \ -s sim.data={}.data \ -s calyx.args="--log off" \ - {} | jq --sort-keys + -s cider.flags="--check-data-race" \ + {} """ [[tests]] @@ -198,7 +231,8 @@ fud2 --from dahlia --to dat \ --through cider \ -s sim.data={}.data \ -s calyx.args="--log off" \ - {} | jq --sort-keys + -s cider.flags="--check-data-race" \ + {} """ timeout = 180 @@ -223,7 +257,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 +272,7 @@ timeout = 180 # --through cider \ # -s sim.data={}.data \ # -s calyx.args="--log off" \ -# | jq --sort-keys +# {} # """ # [[tests]] @@ -250,6 +283,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/assign-order-does-not-matter.expect b/cider/tests/unit/assign-order-does-not-matter.expect similarity index 100% rename from interp/tests/unit/assign-order-does-not-matter.expect rename to cider/tests/unit/assign-order-does-not-matter.expect diff --git a/interp/tests/unit/assign-order-does-not-matter.futil b/cider/tests/unit/assign-order-does-not-matter.futil similarity index 100% rename from interp/tests/unit/assign-order-does-not-matter.futil rename to cider/tests/unit/assign-order-does-not-matter.futil diff --git a/cider/tests/unit/comb-comp-incr.expect b/cider/tests/unit/comb-comp-incr.expect new file mode 100644 index 0000000000..84a055204b --- /dev/null +++ b/cider/tests/unit/comb-comp-incr.expect @@ -0,0 +1,14 @@ +{ + "r": [ + 14 + ], + "r_t0": [ + 10 + ], + "r_t1": [ + 11 + ], + "r_t2": [ + 13 + ] +} diff --git a/cider/tests/unit/comb-comp-incr.futil b/cider/tests/unit/comb-comp-incr.futil new file mode 100644 index 0000000000..645ea6a34b --- /dev/null +++ b/cider/tests/unit/comb-comp-incr.futil @@ -0,0 +1,71 @@ +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; + + +comb component incr(in: 32) -> (out: 32) { + cells { + add = std_add(32); + } + wires { + add.left = in; + add.right = 32'd1; + out = add.out; + } +} + +component copy() -> () { + cells { + ref source = std_reg(32); + ref dest = std_reg(32); + } + wires { + group copy { + dest.in = source.out; + dest.write_en = 1'd1; + copy[done] = dest.done; + } + } + control { + seq { + copy; + } + } +} + +component main() -> () { + cells { + inc = incr(32); + r = std_reg(32); + r_t0 = std_reg(32); + r_t1 = std_reg(32); + r_t2 = std_reg(32); + copy = copy(); + } + + wires { + group init { + r.in = 32'd10; + r.write_en = 1'd1; + init[done] = r.done; + } + group incr { + inc.in = r.out; + r.in = inc.out; + r.write_en = 1'd1; + incr[done] = r.done; + } + } + + control { + seq { + init; + invoke copy[source=r, dest=r_t0]()(); + incr; + invoke copy[source=r, dest=r_t1]()(); + incr; + incr; + invoke copy[source=r, dest=r_t2]()(); + incr; + } + } +} diff --git a/interp/tests/unit/combinational-chain.expect b/cider/tests/unit/combinational-chain.expect similarity index 100% rename from interp/tests/unit/combinational-chain.expect rename to cider/tests/unit/combinational-chain.expect diff --git a/interp/tests/unit/combinational-chain.futil b/cider/tests/unit/combinational-chain.futil similarity index 100% rename from interp/tests/unit/combinational-chain.futil rename to cider/tests/unit/combinational-chain.futil diff --git a/interp/tests/unit/register-writes-visible-inside-group.expect b/cider/tests/unit/register-writes-visible-inside-group.expect similarity index 100% rename from interp/tests/unit/register-writes-visible-inside-group.expect rename to cider/tests/unit/register-writes-visible-inside-group.expect diff --git a/interp/tests/unit/register-writes-visible-inside-group.futil b/cider/tests/unit/register-writes-visible-inside-group.futil similarity index 100% rename from interp/tests/unit/register-writes-visible-inside-group.futil rename to cider/tests/unit/register-writes-visible-inside-group.futil diff --git a/interp/tests/unit/uninitialized-port-returns-zero.expect b/cider/tests/unit/uninitialized-port-returns-zero.expect similarity index 91% rename from interp/tests/unit/uninitialized-port-returns-zero.expect rename to cider/tests/unit/uninitialized-port-returns-zero.expect index 40f7d1ff70..7ed6839994 100644 --- a/interp/tests/unit/uninitialized-port-returns-zero.expect +++ b/cider/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/cider/tests/unit/uninitialized-port-returns-zero.futil similarity index 84% rename from interp/tests/unit/uninitialized-port-returns-zero.futil rename to cider/tests/unit/uninitialized-port-returns-zero.futil index 26626239f4..cfff26acfd 100644 --- a/interp/tests/unit/uninitialized-port-returns-zero.futil +++ b/cider/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/docs/SUMMARY.md b/docs/SUMMARY.md index 55a50136b9..508f69b253 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -26,6 +26,7 @@ - [Scripting](./running-calyx/fud2/scripts.md) - [Interfacing with Calyx RTL](./running-calyx/interfacing.md) - [The Calyx Interpreter](./running-calyx/interpreter.md) +- [The Calyx Profiler](./running-calyx/profiler.md) - [FIRRTL Backend](./running-calyx/firrtl.md) # Compiler Development Guide @@ -60,6 +61,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/compiler.md b/docs/compiler.md index 088646d417..64b35aaac8 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -33,6 +33,7 @@ For example, the alias `all` is an ordered sequence of default passes executed when the compiler is run from the command-line. The command-line provides two options to control the execution of passes: + - `-p, --pass`: Execute this pass or alias. Overrides default alias. - `-d, --disable-pass`: Disable this pass or alias. Takes priority over `-p`. @@ -43,6 +44,10 @@ the default execution alias `all`: cargo run -- examples/futil/simple.futil -p all -d static-timing ``` +If you want to work with passes interactively (for instance, you only care about +a pass far into the `all` sequence, and it is impractical to pass 20 `-p` +options), you can [visualize them](./dev/calyx-pass-explorer.md) with the `calyx-pass-explorer` tool. + ## Providing Pass Options Some passes take options to control their behavior. The `--list-passes` command prints out the options for each pass. For example, the `tdcc` pass has the following options: @@ -53,23 +58,25 @@ tdcc: ``` The option allows us to change the behavior of the pass. To provide a pass-specific option, we use the `-x` switch: + ``` cargo run -- examples/futil/simple.futil -p tdcc -x tdcc:dump-fsm ``` Note that we specify the option of `tdcc` by prefixing it with the pass name and a colon. - ## Specifying Primitives Library The compiler implementation uses a standard library of components to compile programs. The only standard library for the compiler is located in: + ``` /primitives ``` Specify the location of the library using the `-l` flag: + ``` cargo run -- -l ./primitives ``` 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/assets/calyx-missing.png b/docs/dev/assets/calyx-missing.png new file mode 100644 index 0000000000..06ab2f6730 Binary files /dev/null and b/docs/dev/assets/calyx-missing.png differ diff --git a/docs/dev/assets/horrific-interface.png b/docs/dev/assets/horrific-interface.png new file mode 100644 index 0000000000..e93a01a064 Binary files /dev/null and b/docs/dev/assets/horrific-interface.png differ diff --git a/docs/dev/assets/well-formed.png b/docs/dev/assets/well-formed.png new file mode 100644 index 0000000000..14af47f449 Binary files /dev/null and b/docs/dev/assets/well-formed.png differ diff --git a/docs/dev/calyx-pass-explorer.md b/docs/dev/calyx-pass-explorer.md new file mode 100644 index 0000000000..a71112858f --- /dev/null +++ b/docs/dev/calyx-pass-explorer.md @@ -0,0 +1,122 @@ +# 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. +It's been used to debug and develop new compiler passes as well as implement new features in the compiler, so I hope you can find it useful too! + +> ![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 ``` + 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` @@ -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` + +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` + +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` 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::(); ``` Once done, the pass is accessible from the command line: + ```bash cargo run -- -p new-pass ``` @@ -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/running-calyx/fud/axi-gen.md b/docs/running-calyx/fud/axi-gen.md index 8c5db9c6e9..1ff4f1ef63 100644 --- a/docs/running-calyx/fud/axi-gen.md +++ b/docs/running-calyx/fud/axi-gen.md @@ -75,7 +75,7 @@ allow this. [pynq]: https://github.com/Xilinx/PYNQ [xclbin]: https://xilinx.github.io/XRT/2021.2/html/formats.html#xclbin [xilinx_tools]: https://github.com/calyxir/calyx/blob/master/docs/fud/xilinx.md -[kernel_xml]: https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File +[kernel_xml]: https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/RTL-Kernel-XML-File [external]: https://docs.calyxir.org/lang/attributes.html?highlight=external#external [issues]: https://github.com/calyxir/calyx/issues [signals]: https://developer.arm.com/documentation/ihi0022/e/AMBA-AXI3-and-AXI4-Protocol-Specification/Signal-Descriptions?lang=en @@ -83,4 +83,4 @@ allow this. [access_protection]: https://developer.arm.com/documentation/ihi0022/e/AMBA-AXI3-and-AXI4-Protocol-Specification/Transaction-Attributes/Access-permissions?lang=en [toplevel]: https://docs.calyxir.org/lang/attributes.html?highlight=toplevel#toplevel [xilinx_how]: https://docs.calyxir.org/running-calyx/fud/xilinx.html?highlight=synthesis#how-it-works -[kernel_requirements]: https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/Kernel-Interface-Requirements +[kernel_requirements]: https://docs.amd.com/r/en-US/ug1701-vitis-accelerated-embedded/RTL-Kernel-Interface-Requirements diff --git a/docs/running-calyx/fud/xilinx.md b/docs/running-calyx/fud/xilinx.md index 0b89e7bb50..967400b5f4 100644 --- a/docs/running-calyx/fud/xilinx.md +++ b/docs/running-calyx/fud/xilinx.md @@ -154,7 +154,7 @@ Then, look in the resulting directory, which will be named `fud-out-*` for some In there, the Xilinx trace files you want are named `*.wdb` and `*.wcfg`. The VCD file is at `.run/*/hw_em/device0/binary_0/behav_waveform/xsim/dump.vcd` or similar. -[emconfig.json]: https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/emconfigutil-Utility +[emconfig.json]: https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/emconfigutil-Utility [xrt-debug]: https://xilinx.github.io/Vitis_Accel_Examples/2021.1/html/debug_profile.html [vcd]: https://en.wikipedia.org/wiki/Value_change_dump @@ -260,5 +260,5 @@ It sets up the necessary input files and constructs a command line that looks mu [ip-xact]: https://en.wikipedia.org/wiki/IP-XACT [duh]: https://github.com/sifive/duh [package_kernel]: https://github.com/Xilinx/Vitis-Tutorials/blob/2021.1/Hardware_Acceleration/Feature_Tutorials/01-rtl_kernel_workflow/reference-files/scripts/package_kernel.tcl -[package_xo]: https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/package_xo-Command +[package_xo]: https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/package_xo-Command [pynq]: https://github.com/Xilinx/PYNQ diff --git a/docs/running-calyx/fud2/index.md b/docs/running-calyx/fud2/index.md index ec32ad04db..0d63a1af44 100644 --- a/docs/running-calyx/fud2/index.md +++ b/docs/running-calyx/fud2/index.md @@ -11,7 +11,9 @@ Until then, fud remains your first choice for all your build-related needs. ## Set Up -fud2 is a Rust tool, so you can build it along with everything else in this monorepo with `cargo build`. +If you would like to use `fud2` as is, and already have Calyx installed, you can `cargo install --path fud2` from this repository's root to automatically add the binary to your path. + +Alternatively, if you would like to work on development of `fud2` and/or keep up with the latest changes when you `git pull`, you can build it along with everything else in this monorepo with `cargo build`. You might then want to do something like ``ln -s `pwd`/target/debug/fud2 ~/.local/bin`` for easy access to the `fud2` binary. fud2 depends on [Ninja][]. diff --git a/docs/running-calyx/interpreter.md b/docs/running-calyx/interpreter.md index 26c40885f7..32277903c9 100644 --- a/docs/running-calyx/interpreter.md +++ b/docs/running-calyx/interpreter.md @@ -13,11 +13,11 @@ can use [fud2][]. The latter is generally recommended. To run an example program, try: - cd interp && cargo run tests/control/if.futil + cd cider && cargo run -- tests/control/if.futil You should see something like: - Z{"top_level":"main","memories":[{"name":"mem","width":32,"size":1,"dimensions":{"D1":1}}]}% + ���T�itop_leveldmainhmemories��dnamecmemjdimensions�bD1fformat�fBitnum�fsigned�ewidth % This output contains some header information and the raw binary data of the @@ -26,7 +26,7 @@ memories in the program and as such is not human readable. A separate tool, vice versa. Once you've compiled it, either by running `cargo build` in `tools/cider-data-converter` or by running `cargo build --all`, you can run: - cargo run tests/control/if.futil | ../target/debug/cider-data-converter --to json + cargo run -- tests/control/if.futil | ../target/debug/cider-data-converter --to json which should produce ```json @@ -50,7 +50,7 @@ program before interpretation. You'll want to build the interpreter and compiler first: cargo build && \ - cd interp && cargo build && \ + cd cider && cargo build && \ cd ../tools/cider-data-converter && cargo build && cd ../../ or just run diff --git a/docs/running-calyx/profiler.md b/docs/running-calyx/profiler.md new file mode 100644 index 0000000000..bc0307b591 --- /dev/null +++ b/docs/running-calyx/profiler.md @@ -0,0 +1,37 @@ +# The Calyx Profiler + +*Note: The profiler is still in development. If you have any suggestions, thoughts, or feedback, please let us know!* + +Profilers can help you analyze performance information to find places you can optimize in your code. Currently, the Calyx profiler prouduces timing information in terms of cycle counts. + +## Setup + +In order to run the profiler, you need: +- [fud2](./fud2/index.html) +- The Python [`vcdvcd` library](https://github.com/cirosantilli/vcdvcd). Running `fud2 env init` should install this for you. +- A clone of Brendan Gregg's Flamegraph repository: [https://github.com/brendangregg/FlameGraph](https://github.com/brendangregg/FlameGraph) + +Then, you need to edit your fud2 configuration file to specify the location of `flamegraph.pl` within the `Flamegraph` repository: + +ex) +``` +[flamegraph] +script = "/home/ayaka/projects/FlameGraph/flamegraph.pl" +``` + +## Basic Use + +To obtain a flame graph, run fud2 with an SVG output file: + +ex) +``` +fud2 tests/correctness/pow.futil -o pow.svg -s sim.data=tests/correctness/pow.futil.data +``` + +The produced flame graph is "flattened", which means that each parallel arm gets its own "cycle". So, if arm A and arm B were executing on a single cycle, the flame graph would account for a cycle in arm A and a cycle in arm B. You can view and interact with the flame graph using your favorite web browser. + +If you retain the fud2 build directory with the `--keep` option or the `--dir` option, you will find additional profiler output files in `/profiler-out`: + + - `scaled-flame.svg`: A scaled flame graph, where a cycle is divided between the parallel arms in execution. So, if arm A and arm B were executing on a single cycle, the flame graph would account for 0.5 cycles in arm A and 0.5 cycles in arm B. + - `aggregate.dot.png`: A tree summary of the execution of the program. Nodes (groups and cells) are labeled with the number of times the node was a leaf, and edges are labeled with the number of cycles that edge was activated. + - `rank{i}.dot.png`: A tree representation of the `i`th most active stack picture. `rankings.csv` lists the specific cycles that each ranked tree was active for. 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 -``` - -# 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/frontends/queues/README.md b/frontends/queues/README.md index 139e974843..882359c905 100644 --- a/frontends/queues/README.md +++ b/frontends/queues/README.md @@ -1,12 +1,35 @@ # Queues Library -See the [docs](https://docs.calyxir.org/frontends/queues.html) for more details. +See the [docs][docs] for more details. ## Installation To use our queues: -1. Install [flit](https://flit.readthedocs.io/en/latest/#install) +1. Install [flit][flit] 2. Install the `queues` package: ``` $ cd frontends/queues/ $ flit install --symlink +``` + +## Converting Tests to Calyx + +To convert any of our [randomized tests][testing-harness] to a single Calyx file and their associated data and expect files: + +0. Follow the [installation instructions](#installation) +1. Choose a test by picking a `.py` file in [`tests/`][tests-dir] +2. Convert the test to Calyx: +``` +python3 _test.py 20000 --keepgoing > _test.futil ``` +3. Run the script [`gen_test_data.sh`][gen_test_data.sh] to generate data and expect files: +``` +./gen_test_data.sh +``` + +The files `_test.py`, `_test.data`, and `_test.expect` contain the Calyx program, input data, and expected outputs for the test. + +[docs]: https://docs.calyxir.org/frontends/queues.html +[flit]: https://flit.readthedocs.io/en/latest/#install +[testing-harness]: https://docs.calyxir.org/frontends/queues.html#shared-testing-harness +[tests-dir]: ./tests/ +[gen_test_data.sh]: ./test_data_gen/gen_test_data.sh \ No newline at end of file diff --git a/frontends/queues/test_data_gen/gen_test_data.sh b/frontends/queues/test_data_gen/gen_test_data.sh index 1448f826dd..c39b6877a9 100755 --- a/frontends/queues/test_data_gen/gen_test_data.sh +++ b/frontends/queues/test_data_gen/gen_test_data.sh @@ -1,9 +1,17 @@ #!/usr/bin/bash -num_cmds=20000 +if [[ $# -eq 2 ]]; then # Generate custom-length command list to the specified output directory + num_cmds=$1 + tests_dir=$2 + mkdir -p ${tests_dir}/binheap/round_robin ${tests_dir}/binheap/strict ${tests_dir}/round_robin ${tests_dir}/strict # NOTE: hacky and will break when other tests are created. + echo "Number of commands: ${num_cmds}; output directory: ${tests_dir}" +else + num_cmds=20000 + tests_dir="$(dirname "$0")/../tests" +fi + queue_size=16 -tests_dir="$(dirname "$0")/../tests" data_gen_dir="$(dirname "$0")" 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 { #[derive(FromArgs)] /// A generic compiler driver. -pub struct FakeArgs { +pub struct FudArgs { #[argh(subcommand)] pub sub: Option>, @@ -190,6 +190,10 @@ pub struct FakeArgs { #[argh(switch, short = 'v')] verbose: Option, + /// 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( driver: &Driver, - args: &FakeArgs, + args: &FudArgs, ) -> anyhow::Result> { get_states_with_errors( driver, @@ -243,7 +247,7 @@ fn from_states( fn to_state( driver: &Driver, - args: &FakeArgs, + args: &FudArgs, ) -> anyhow::Result> { get_states_with_errors( driver, @@ -257,7 +261,7 @@ fn to_state( fn get_request( driver: &Driver, - args: &FakeArgs, + args: &FudArgs, ) -> anyhow::Result { // 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 CliStart for T { fn config_from_cli_ext( name: &str, ) -> anyhow::Result { - let args: FakeArgs = argh::from_env(); + let args: FudArgs = argh::from_env(); let mut config = config::load_config(name); // Use `--set` arguments to override configuration values. @@ -428,7 +432,7 @@ fn cli_ext( driver: &Driver, config: &figment::Figment, ) -> anyhow::Result<()> { - let args: FakeArgs = argh::from_env(); + let args: FudArgs = argh::from_env(); // Configure logging. env_logger::Builder::new() .format_timestamp(None) @@ -479,8 +483,8 @@ fn cli_ext( 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, ) -> &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/axi.rhai b/fud2/scripts/axi.rhai index 2eab6522d8..b0705610d2 100644 --- a/fud2/scripts/axi.rhai +++ b/fud2/scripts/axi.rhai @@ -24,9 +24,9 @@ fn wrapper_setup(e) { let dynamic = e.config_constrained_or("dynamic", ["true", "false"], "false"); let generator_path = if dynamic == "true" { - "$calyx-base/yxi/axi-calyx/dynamic-axi-generator.py" + "$calyx-base/yxi/axi-calyx/dynamic_axi_generator.py" } else { - "$calyx-base/yxi/axi-calyx/axi-generator.py" + "$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"); @@ -36,6 +36,8 @@ fn wrapper_setup(e) { // Define a simple `combine` rule that just concatenates any numer of files. e.rule("combine", "cat $in > $out"); + // Removes imports and `external` primitive blocks added by passes by removing + // everything up until the first line containing `component main` e.rule( "remove-imports", "sed '1,/component main/{/component main/!d; }' $in > $out", 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..726991f019 --- /dev/null +++ b/fud2/scripts/profiler.rhai @@ -0,0 +1,81 @@ +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"); + + // series of passes after instrumentation? + e.config_var_or("passes", "profiler.compilation-passes", "-p all"); // set passes="-p 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 produce folded representation of flame graph + e.var_("parse-vcd-script", "$calyx-base/tools/profiler/profiler-process.py"); + e.rule("parse-vcd", "python3 $parse-vcd-script $in $cells profiler-out $out"); + + e.config_var("flamegraph-script", "flamegraph.script"); + e.var_("create-visuals-script", "$calyx-base/tools/profiler/create-visuals.sh"); + e.rule("create-visuals", "bash $create-visuals-script $flamegraph-script profiler-out $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 compile-invoke -p profiler-instrumentation $passes"); + + 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([flamegraph_folded], "parse-vcd", [instrumented_vcd], ["$cells"]); + e.build_cmd([output], "create-visuals", [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/cli_pyenv.rs b/fud2/src/cli_pyenv.rs index f5d01e9821..e01f15c659 100644 --- a/fud2/src/cli_pyenv.rs +++ b/fud2/src/cli_pyenv.rs @@ -46,6 +46,13 @@ impl PyenvCommand { .stdout(std::io::stdout()) .output()?; + // install vcdvcd (for Profiler) + Command::new(pyenv.join("bin").join("pip")) + .arg("install") + .arg("vcdvcd") + .stdout(std::io::stdout()) + .output()?; + // install flit Command::new(pyenv.join("bin").join("pip")) .arg("install") 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 --from verilog-noverify --to cocotb-axi --set sim.data=` - 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/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..19cb16f35c 100644 --- a/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap +++ b/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap @@ -8,20 +8,21 @@ 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 command = $yxi -l $calyx-base $in > $out -axi-generator = $calyx-base/yxi/axi-calyx/axi-generator.py +axi-generator = $calyx-base/yxi/axi-calyx/axi_generator.py python = python3 rule gen-axi command = $python $axi-generator $in > $out 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..7f340555e7 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@plan_profiler.snap @@ -0,0 +1,66 @@ +--- +source: fud2/tests/tests.rs +description: "emit plan: profiler" +snapshot_kind: text +--- +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 +passes = -p 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/profiler-process.py +rule parse-vcd + command = python3 $parse-vcd-script $in $cells profiler-out $out + +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 compile-invoke -p profiler-instrumentation $passes +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 flamegraph.folded: parse-vcd instrumented.vcd | $cells +build /output.ext: create-visuals 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/src/configuration.rs b/interp/src/configuration.rs deleted file mode 100644 index 548418b75d..0000000000 --- a/interp/src/configuration.rs +++ /dev/null @@ -1,101 +0,0 @@ -// this can be a copy type because it's just a bunch of bools -#[derive(Debug, Default, Clone, Copy)] -/// 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, - error_on_overflow: Option, - allow_par_conflicts: Option, - quiet: Option, - dump_registers: Option, - dump_all_memories: Option, -} - -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(), - } - } -} 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>, - 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/structures/environment/assignments.rs b/interp/src/flatten/structures/environment/assignments.rs deleted file mode 100644 index 1fdb672111..0000000000 --- a/interp/src/flatten/structures/environment/assignments.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::flatten::{ - flat_ir::prelude::{GlobalCellIdx, LocalPortOffset}, - structures::thread::ThreadIdx, -}; - -use super::env::AssignmentRange; - -#[derive(Debug)] -pub struct GroupInterfacePorts { - pub go: LocalPortOffset, - pub done: LocalPortOffset, -} - -/// A group of assignments that is scheduled to be evaluated -#[derive(Debug)] -pub struct ScheduledAssignments { - pub active_cell: GlobalCellIdx, - pub assignments: AssignmentRange, - pub interface_ports: Option, - pub thread: Option, - pub is_cont: bool, -} - -impl ScheduledAssignments { - pub fn new( - active_cell: GlobalCellIdx, - assignments: AssignmentRange, - interface_ports: Option, - thread: Option, - is_comb: bool, - ) -> Self { - Self { - active_cell, - assignments, - interface_ports, - thread, - is_cont: is_comb, - } - } -} diff --git a/interp/src/logging.rs b/interp/src/logging.rs deleted file mode 100644 index 111f892674..0000000000 --- a/interp/src/logging.rs +++ /dev/null @@ -1,44 +0,0 @@ -use once_cell::sync::OnceCell; - -// re-export for convenience -pub use slog::Logger; -#[allow(unused_imports)] -pub(crate) use slog::{debug, error, info, o, trace, warn}; - -use slog::{Drain, Level}; - -static ROOT_LOGGER: OnceCell = OnceCell::new(); - -pub fn initialize_default_logger() { - initialize_logger(true); -} - -pub fn initialize_logger(quiet: bool) { - 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 drain = drain.filter_level(filter_level).fuse(); - - let drain = slog_async::Async::new(drain).build().fuse(); - - let logger = slog::Logger::root(drain, o!()); - - #[allow(unused_must_use)] - { - ROOT_LOGGER.set(logger); - } -} - -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>(source_name: S) -> Logger { - root().new(o!("source" => String::from(source_name.as_ref()))) -} diff --git a/interp/tests/data-race/guard-conflict.expect b/interp/tests/data-race/guard-conflict.expect deleted file mode 100644 index d3f49ee6dd..0000000000 --- a/interp/tests/data-race/guard-conflict.expect +++ /dev/null @@ -1,4 +0,0 @@ ----CODE--- -1 ----STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_reg diff --git a/interp/tests/data-race/par-conflict-cmem.expect b/interp/tests/data-race/par-conflict-cmem.expect deleted file mode 100644 index 59aad0e0bd..0000000000 --- a/interp/tests/data-race/par-conflict-cmem.expect +++ /dev/null @@ -1,4 +0,0 @@ ----CODE--- -1 ----STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_mem diff --git a/interp/tests/data-race/par-conflict.expect b/interp/tests/data-race/par-conflict.expect deleted file mode 100644 index d3f49ee6dd..0000000000 --- a/interp/tests/data-race/par-conflict.expect +++ /dev/null @@ -1,4 +0,0 @@ ----CODE--- -1 ----STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_reg 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/primitives/memories/comb.futil b/primitives/memories/comb.futil index cbe4754ae5..556e345828 100644 --- a/primitives/memories/comb.futil +++ b/primitives/memories/comb.futil @@ -65,7 +65,8 @@ extern "comb.sv" { @read_together(1) @write_together(2) addr3: D3_IDX_SIZE, @write_together(1) @data write_data: WIDTH, @write_together(1) @interval(1) @go write_en: 1, - @clk clk: 1 + @clk clk: 1, + @reset reset: 1 ) -> ( @read_together(1) read_data: WIDTH, @done done: 1 diff --git a/runt.toml b/runt.toml index 6c1ac6e1fa..4b439deb60 100644 --- a/runt.toml +++ b/runt.toml @@ -68,7 +68,7 @@ cmd = """ [[tests]] name = "fud2 yxi invocation" -paths = ["yxi/tests/ref-mems-vec-add.futil"] +paths = ["yxi/tests/yxi-tool/ref-mems-vec-add.futil"] cmd = """ fud2 {} --from calyx --to yxi """ @@ -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]] @@ -613,14 +604,14 @@ fud e {} -s verilog.cycle_limit 500 \ name = "calyx-py read-write-compute AXI wrapper generation" paths = ["yxi/tests/axi/read-compute-write/*.yxi"] cmd = """ -python3 yxi/axi-calyx/axi-generator.py {} +python3 yxi/axi-calyx/axi_generator.py {} """ [[tests]] name = "calyx-py dynamic AXI wrapper generation" paths = ["yxi/tests/axi/dynamic/*.yxi"] cmd = """ -python3 yxi/axi-calyx/dynamic-axi-generator.py {} +python3 yxi/axi-calyx/dynamic_axi_generator.py {} """ #Ignore fud2 stderr for now due to ninja nondeterminism @@ -691,11 +682,3 @@ make --silent -B TEST_PATH={} COCOTB_LOG_LEVEL=CRITICAL |\ rm -f results.xml """ - -### Profiler tests -[[tests]] -name = "profiler" -paths = ["tests/profiler/*.futil"] -cmd = """ -bash tools/profiler/get-profile-counts-info.sh {} {}.data STDOUT -d -""" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..22efd9b707 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +# This file is used by github actions for the CI checks. + +[toolchain] +channel = "1.82.0" diff --git a/src/cmdline.rs b/src/cmdline.rs index f03eb0b365..5568a4e148 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -200,7 +200,7 @@ impl Opts { { return Err(Error::misc(format!( "--compile-mode=file is only valid with -b calyx. `-b {}` requires --compile-mode=project", - opts.backend.to_string() + opts.backend ))); } 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/profiler/par.futil.data b/tests/correctness/skid-buffers/bypass-reg.futil.data similarity index 57% rename from tests/profiler/par.futil.data rename to tests/correctness/skid-buffers/bypass-reg.futil.data index 4862df80f9..01e3629795 100644 --- a/tests/profiler/par.futil.data +++ b/tests/correctness/skid-buffers/bypass-reg.futil.data @@ -1,17 +1,16 @@ { - "a": { + "in": { "data": [ - 0 - ], - "format": { - "numeric_type": "bitnum", - "is_signed": true, - "width": 32 - } - }, - "b": { - "data": [ - 0 + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 ], "format": { "numeric_type": "bitnum", @@ -19,8 +18,17 @@ "width": 32 } }, - "c": { + "out": { "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 0 ], "format": { 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..7a7573e88e 100644 --- a/tests/import/a.expect +++ b/tests/import/a.expect @@ -8,7 +8,7 @@ extern "/calyx/primitives/memories/comb.sv" { primitive comb_mem_d1[WIDTH, SIZE, IDX_SIZE](@read_together addr0: IDX_SIZE, @write_together @data write_data: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@read_together read_data: WIDTH, @done done: 1); primitive comb_mem_d2[WIDTH, D0_SIZE, D1_SIZE, D0_IDX_SIZE, D1_IDX_SIZE](@read_together @write_together(2) addr0: D0_IDX_SIZE, @read_together @write_together(2) addr1: D1_IDX_SIZE, @write_together @data write_data: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@read_together read_data: WIDTH, @done done: 1); primitive comb_mem_d3[WIDTH, D0_SIZE, D1_SIZE, D2_SIZE, D0_IDX_SIZE, D1_IDX_SIZE, D2_IDX_SIZE](@read_together @write_together(2) addr0: D0_IDX_SIZE, @read_together @write_together(2) addr1: D1_IDX_SIZE, @read_together @write_together(2) addr2: D2_IDX_SIZE, @write_together @data write_data: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@read_together read_data: WIDTH, @done done: 1); - primitive comb_mem_d4[WIDTH, D0_SIZE, D1_SIZE, D2_SIZE, D3_SIZE, D0_IDX_SIZE, D1_IDX_SIZE, D2_IDX_SIZE, D3_IDX_SIZE](@read_together @write_together(2) addr0: D0_IDX_SIZE, @read_together @write_together(2) addr1: D1_IDX_SIZE, @read_together @write_together(2) addr2: D2_IDX_SIZE, @read_together @write_together(2) addr3: D3_IDX_SIZE, @write_together @data write_data: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1) -> (@read_together read_data: WIDTH, @done done: 1); + primitive comb_mem_d4[WIDTH, D0_SIZE, D1_SIZE, D2_SIZE, D3_SIZE, D0_IDX_SIZE, D1_IDX_SIZE, D2_IDX_SIZE, D3_IDX_SIZE](@read_together @write_together(2) addr0: D0_IDX_SIZE, @read_together @write_together(2) addr1: D1_IDX_SIZE, @read_together @write_together(2) addr2: D2_IDX_SIZE, @read_together @write_together(2) addr3: D3_IDX_SIZE, @write_together @data write_data: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@read_together read_data: WIDTH, @done done: 1); } extern "/calyx/primitives/core.sv" { comb primitive std_slice<"share"=1>[IN_WIDTH, OUT_WIDTH](@data in: IN_WIDTH) -> (out: OUT_WIDTH); @@ -28,6 +28,8 @@ extern "/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/tests/passes/profiler_instrumentation/cond.expect b/tests/passes/profiler_instrumentation/cond.expect new file mode 100644 index 0000000000..186dcc56f9 --- /dev/null +++ b/tests/passes/profiler_instrumentation/cond.expect @@ -0,0 +1,35 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + @external a = comb_mem_d1(32, 1, 1); + lt = std_lt(32); + @control @generated @protected wr_a___main_group_probe = std_wire(1); + @control @generated @protected a___wr_a___main_primitive_probe = std_wire(1); + @control @generated @protected wr_b___main_group_probe = std_wire(1); + @control @generated @protected wr_a___wr_b___main_se_probe = std_wire(1); + } + wires { + group wr_a { + a.addr0 = 1'd0; + a.write_en = 1'd1; + a.write_data = 32'd1; + wr_a[done] = a.done; + wr_a___main_group_probe.in = 1'd1; + a___wr_a___main_primitive_probe.in = 1'd1; + } + group wr_b { + wr_a[go] = lt.out ? 1'd1; + wr_b[done] = wr_a[done]; + wr_b___main_group_probe.in = 1'd1; + wr_a___wr_b___main_se_probe.in = lt.out ? 1'd1; + } + comb group cond { + lt.left = 32'd5; + lt.right = 32'd9; + } + } + control { + wr_b; + } +} diff --git a/tests/profiler/simple-par.futil b/tests/passes/profiler_instrumentation/cond.futil similarity index 51% rename from tests/profiler/simple-par.futil rename to tests/passes/profiler_instrumentation/cond.futil index a66b7ab5e7..a0d5d50943 100644 --- a/tests/profiler/simple-par.futil +++ b/tests/passes/profiler_instrumentation/cond.futil @@ -1,23 +1,34 @@ +// -p profiler-instrumentation + import "primitives/core.futil"; import "primitives/memories/comb.futil"; component main() -> () { cells { @external(1) a = comb_mem_d1(32, 1, 1); + lt = std_lt(32); } wires { - group write { + comb group cond { + lt.left = 32'd5; + lt.right = 32'd9; + } + + group wr_a { a.addr0 = 1'b0; a.write_en = 1'b1; a.write_data = 32'd1; - write[done] = a.done; + wr_a[done] = a.done; + } + + group wr_b { + wr_a[go] = lt.out ? 1'b1; + wr_b[done] = wr_a[done]; } } control { - par { - write; - } + wr_b; } } diff --git a/tests/profiler/simple-par.futil.data b/tests/passes/profiler_instrumentation/cond.futil.data similarity index 100% rename from tests/profiler/simple-par.futil.data rename to tests/passes/profiler_instrumentation/cond.futil.data diff --git a/tests/passes/profiler_instrumentation/profiler_instrumentation.expect b/tests/passes/profiler_instrumentation/profiler_instrumentation.expect new file mode 100644 index 0000000000..62eb8c93ed --- /dev/null +++ b/tests/passes/profiler_instrumentation/profiler_instrumentation.expect @@ -0,0 +1,50 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + @external i = comb_mem_d1(32, 1, 1); + lt = std_lt(32); + lt_reg = std_reg(1); + add = std_add(32); + @control @generated @protected cond___main_group_probe = std_wire(1); + @control @generated @protected lt_reg___cond___main_primitive_probe = std_wire(1); + @control @generated @protected incr___main_group_probe = std_wire(1); + @control @generated @protected i___incr___main_primitive_probe = std_wire(1); + } + wires { + group cond { + i.addr0 = 1'd0; + lt.left = i.read_data; + lt.right = 32'd8; + lt_reg.in = lt.out; + lt_reg.write_en = 1'd1; + cond[done] = lt_reg.done; + cond___main_group_probe.in = 1'd1; + lt_reg___cond___main_primitive_probe.in = 1'd1; + } + group incr<"static"=1> { + add.right = i.read_data; + add.left = 32'd1; + i.write_data = add.out; + i.addr0 = 1'd0; + i.write_en = 1'd1; + incr[done] = i.done; + incr___main_group_probe.in = 1'd1; + i___incr___main_primitive_probe.in = 1'd1; + } + } + control { + seq { + cond; + while lt_reg.out { + seq { + incr; + incr; + cond; + } + } + } + } +} +---STDERR--- +[WARN calyx_frontend::attribute] The attribute @static is deprecated and will be ignored by the compiler. diff --git a/tests/passes/profiler_instrumentation.futil b/tests/passes/profiler_instrumentation/profiler_instrumentation.futil similarity index 96% rename from tests/passes/profiler_instrumentation.futil rename to tests/passes/profiler_instrumentation/profiler_instrumentation.futil index 5ed5727dad..98578f073f 100644 --- a/tests/passes/profiler_instrumentation.futil +++ b/tests/passes/profiler_instrumentation/profiler_instrumentation.futil @@ -1,4 +1,4 @@ -//-p profiler-instrumentation +// -p profiler-instrumentation import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/profiler_instrumentation/triple-multi-component.expect b/tests/passes/profiler_instrumentation/triple-multi-component.expect new file mode 100644 index 0000000000..8bf55e4a35 --- /dev/null +++ b/tests/passes/profiler_instrumentation/triple-multi-component.expect @@ -0,0 +1,65 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +component identity(in: 32, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 32, @done done: 1) { + cells { + r = std_reg(32); + @control @generated @protected save___identity_group_probe = std_wire(1); + @control @generated @protected r___save___identity_primitive_probe = std_wire(1); + } + wires { + group save { + r.in = in; + r.write_en = 1'd1; + save[done] = r.done; + save___identity_group_probe.in = 1'd1; + r___save___identity_primitive_probe.in = 1'd1; + } + out = r.out; + } + control { + save; + } +} +component middle(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + id = identity(); + current_value = std_reg(32); + @control @generated @protected run_id___middle_group_probe = std_wire(1); + @control @generated @protected id___run_id___middle_cell_probe = std_wire(1); + @control @generated @protected use_id___middle_group_probe = std_wire(1); + @control @generated @protected current_value___use_id___middle_primitive_probe = std_wire(1); + } + wires { + group run_id { + id.in = 32'd10; + id.go = 1'd1; + run_id[done] = id.done; + run_id___middle_group_probe.in = 1'd1; + id___run_id___middle_cell_probe.in = 1'd1; + } + group use_id { + current_value.in = id.out; + current_value.write_en = 1'd1; + use_id[done] = current_value.done; + use_id___middle_group_probe.in = 1'd1; + current_value___use_id___middle_primitive_probe.in = 1'd1; + } + } + control { + seq { + run_id; + use_id; + } + } +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + mid = middle(); + } + wires {} + control { + seq { + invoke mid()(); + } + } +} diff --git a/tests/passes/profiler_instrumentation/triple-multi-component.futil b/tests/passes/profiler_instrumentation/triple-multi-component.futil new file mode 100644 index 0000000000..91dd7ca06a --- /dev/null +++ b/tests/passes/profiler_instrumentation/triple-multi-component.futil @@ -0,0 +1,72 @@ +// -p profiler-instrumentation + +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +//// ANCHOR: component +component identity(in: 32) -> (out: 32) { + cells { + r = std_reg(32); + } + wires { + group save { + r.in = in; + r.write_en = 1'd1; + save[done] = r.done; + } + + //// ANCHOR: wires + // This component always outputs the current value in r + out = r.out; + //// ANCHOR_END: wires + } + control { + save; + } +} +//// ANCHOR_END: component + +//// ANCHOR: main +component middle() -> () { + cells { + // Instantiate the identity element + id = identity(); + current_value = std_reg(32); + } + wires { + group run_id { + // We want to "save" the value 10 inside the identity group. + id.in = 32'd10; + // All components have a magic "go" and "done" port added to them. + // Execute the component. + id.go = 1'd1; + run_id[done] = id.done; + } + group use_id { + // We want to "observe" the current value saved in id. + // The out port on the `id` component always shows the last saved + // element. We don't need to set the `go` because we're not executing + // and control. + current_value.in = id.out; + current_value.write_en = 1'd1; + use_id[done] = current_value.done; + } + } + control { + seq { run_id; use_id; } + } +} + +component main() -> () { + cells { + mid = middle(); + } + wires { + + } + control { + seq { invoke mid()(); } + } +} + +//// ANCHOR_END: main diff --git a/tests/profiler/par.expect b/tests/profiler/par.expect deleted file mode 100644 index bf4f603070..0000000000 --- a/tests/profiler/par.expect +++ /dev/null @@ -1,69 +0,0 @@ -[get-profile-counts-info.sh] Obtaining FSM info from TDCC -[get-profile-counts-info.sh] Obtaining cell information from component-cells backend -[get-profile-counts-info.sh] Obtaining VCD file via simulation -[get-profile-counts-info.sh] Using FSM info and VCD file to obtain cycle level counts -Total clock cycles: 3 -=====SUMMARY===== - -[FSM] Group TOP.toplevel.main.par0 Summary: - Total cycles: 3 - # of times active: 1 - Avg runtime: 3.0 - -[GT] Group TOP.toplevel.main.par0 Summary: - Total cycles: 2 - # of times active: 1 - Avg runtime: 2.0 - -[GT] Group TOP.toplevel.main.wr_a Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -[GT] Group TOP.toplevel.main.wr_b Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -[GT] Group TOP.toplevel.main.wr_c Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -=====DUMP===== - -[FSM] Group TOP.toplevel.main.par0: - FSM name: TOP.toplevel.main.fsm - FSM state ids: [0] - Total cycles: 3 - # of times active: 1 - Segments: [0, 3) - -[GT] Group TOP.toplevel.main.par0: - Total cycles: 2 - # of times active: 1 - Segments: [0, 2) - -[GT] Group TOP.toplevel.main.wr_a: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -[GT] Group TOP.toplevel.main.wr_b: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -[GT] Group TOP.toplevel.main.wr_c: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -name,total-cycles,times-active,avg -TOP.toplevel.main.par0[FSM],3,1,3.0 -TOP.toplevel.main.par0,2,1,2.0 -TOP.toplevel.main.wr_a,1,1,1.0 -TOP.toplevel.main.wr_b,1,1,1.0 -TOP.toplevel.main.wr_c,1,1,1.0 -TOTAL,3,-,- -[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/par.futil b/tests/profiler/par.futil deleted file mode 100644 index 1f71a5e152..0000000000 --- a/tests/profiler/par.futil +++ /dev/null @@ -1,41 +0,0 @@ -import "primitives/core.futil"; -import "primitives/memories/comb.futil"; - -component main() -> () { - cells { - @external(1) a = comb_mem_d1(32, 1, 1); - @external(1) b = comb_mem_d1(32, 1, 1); - @external(1) c = comb_mem_d1(32, 1, 1); - } - - wires { - group wr_a { - a.addr0 = 1'b0; - a.write_en = 1'b1; - a.write_data = 32'd1; - wr_a[done] = a.done; - } - - group wr_b { - b.addr0 = 1'b0; - b.write_en = 1'b1; - b.write_data = 32'd1; - wr_b[done] = b.done; - } - - group wr_c { - c.addr0 = 1'b0; - c.write_en = 1'b1; - c.write_data = 32'd1; - wr_c[done] = c.done; - } - } - - control { - par { - wr_a; - wr_b; - wr_c; - } - } -} diff --git a/tests/profiler/simple-par.expect b/tests/profiler/simple-par.expect deleted file mode 100644 index 213fd30020..0000000000 --- a/tests/profiler/simple-par.expect +++ /dev/null @@ -1,47 +0,0 @@ -[get-profile-counts-info.sh] Obtaining FSM info from TDCC -[get-profile-counts-info.sh] Obtaining cell information from component-cells backend -[get-profile-counts-info.sh] Obtaining VCD file via simulation -[get-profile-counts-info.sh] Using FSM info and VCD file to obtain cycle level counts -Total clock cycles: 3 -=====SUMMARY===== - -[FSM] Group TOP.toplevel.main.par0 Summary: - Total cycles: 3 - # of times active: 1 - Avg runtime: 3.0 - -[GT] Group TOP.toplevel.main.par0 Summary: - Total cycles: 2 - # of times active: 1 - Avg runtime: 2.0 - -[GT] Group TOP.toplevel.main.write Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -=====DUMP===== - -[FSM] Group TOP.toplevel.main.par0: - FSM name: TOP.toplevel.main.fsm - FSM state ids: [0] - Total cycles: 3 - # of times active: 1 - Segments: [0, 3) - -[GT] Group TOP.toplevel.main.par0: - Total cycles: 2 - # of times active: 1 - Segments: [0, 2) - -[GT] Group TOP.toplevel.main.write: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -name,total-cycles,times-active,avg -TOP.toplevel.main.par0[FSM],3,1,3.0 -TOP.toplevel.main.par0,2,1,2.0 -TOP.toplevel.main.write,1,1,1.0 -TOTAL,3,-,- -[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/simple-seq.expect b/tests/profiler/simple-seq.expect deleted file mode 100644 index 35bff072bf..0000000000 --- a/tests/profiler/simple-seq.expect +++ /dev/null @@ -1,36 +0,0 @@ -[get-profile-counts-info.sh] Obtaining FSM info from TDCC -[get-profile-counts-info.sh] Obtaining cell information from component-cells backend -[get-profile-counts-info.sh] Obtaining VCD file via simulation -[get-profile-counts-info.sh] Using FSM info and VCD file to obtain cycle level counts -Total clock cycles: 2 -=====SUMMARY===== - -[FSM] Group TOP.toplevel.main.write Summary: - Total cycles: 2 - # of times active: 1 - Avg runtime: 2.0 - -[GT] Group TOP.toplevel.main.write Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -=====DUMP===== - -[FSM] Group TOP.toplevel.main.write: - FSM name: TOP.toplevel.main.fsm - FSM state ids: [0] - Total cycles: 2 - # of times active: 1 - Segments: [0, 2) - -[GT] Group TOP.toplevel.main.write: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -name,total-cycles,times-active,avg -TOP.toplevel.main.write[FSM],2,1,2.0 -TOP.toplevel.main.write,1,1,1.0 -TOTAL,2,-,- -[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/simple-seq.futil b/tests/profiler/simple-seq.futil deleted file mode 100644 index 9bf79820a1..0000000000 --- a/tests/profiler/simple-seq.futil +++ /dev/null @@ -1,23 +0,0 @@ -import "primitives/core.futil"; -import "primitives/memories/comb.futil"; - -component main() -> () { - cells { - @external(1) a = comb_mem_d1(32, 1, 1); - } - - wires { - group write { - a.addr0 = 1'b0; - a.write_en = 1'b1; - a.write_data = 32'd1; - write[done] = a.done; - } - } - - control { - seq { - write; - } - } -} diff --git a/tests/profiler/simple-seq.futil.data b/tests/profiler/simple-seq.futil.data deleted file mode 100644 index cad4ef0e4a..0000000000 --- a/tests/profiler/simple-seq.futil.data +++ /dev/null @@ -1,12 +0,0 @@ -{ - "a": { - "data": [ - 0 - ], - "format": { - "numeric_type": "bitnum", - "is_signed": true, - "width": 32 - } - } -} diff --git a/tests/profiler/while-never-true.expect b/tests/profiler/while-never-true.expect deleted file mode 100644 index 04bf0a1c9f..0000000000 --- a/tests/profiler/while-never-true.expect +++ /dev/null @@ -1,60 +0,0 @@ -[get-profile-counts-info.sh] Obtaining FSM info from TDCC -[get-profile-counts-info.sh] Obtaining cell information from component-cells backend -[get-profile-counts-info.sh] Obtaining VCD file via simulation -[get-profile-counts-info.sh] Using FSM info and VCD file to obtain cycle level counts -Total clock cycles: 2 -=====SUMMARY===== - -[FSM] Group TOP.toplevel.main.cond Summary: - Total cycles: 2 - # of times active: 1 - Avg runtime: 2.0 - -[GT] Group TOP.toplevel.main.cond Summary: - Total cycles: 1 - # of times active: 1 - Avg runtime: 1.0 - -[FSM] Group TOP.toplevel.main.incr Summary: - Total cycles: 0 - # of times active: 0 - Avg runtime: 0 - -[GT] Group TOP.toplevel.main.incr Summary: - Total cycles: 0 - # of times active: 0 - Avg runtime: 0 - -=====DUMP===== - -[FSM] Group TOP.toplevel.main.cond: - FSM name: TOP.toplevel.main.fsm - FSM state ids: [0, 3] - Total cycles: 2 - # of times active: 1 - Segments: [0, 2) - -[GT] Group TOP.toplevel.main.cond: - Total cycles: 1 - # of times active: 1 - Segments: [0, 1) - -[FSM] Group TOP.toplevel.main.incr: - FSM name: TOP.toplevel.main.fsm - FSM state ids: [2, 1] - Total cycles: 0 - # of times active: 0 - Segments: - -[GT] Group TOP.toplevel.main.incr: - Total cycles: 0 - # of times active: 0 - Segments: - -name,total-cycles,times-active,avg -TOP.toplevel.main.cond[FSM],2,1,2.0 -TOP.toplevel.main.cond,1,1,1.0 -TOP.toplevel.main.incr[FSM],0,0,0 -TOP.toplevel.main.incr,0,0,0 -TOTAL,2,-,- -[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/while-never-true.futil b/tests/profiler/while-never-true.futil deleted file mode 100644 index 97805bd8f6..0000000000 --- a/tests/profiler/while-never-true.futil +++ /dev/null @@ -1,46 +0,0 @@ -import "primitives/core.futil"; -import "primitives/memories/comb.futil"; - -component main() -> () { - cells { - @external(1) i = comb_mem_d1(32, 1, 1); - lt = std_lt(32); - lt_reg = std_reg(1); - add = std_add(32); - } - - wires { - group cond { - i.addr0 = 1'd0; - lt.left = i.read_data; - lt.right = 32'd0; - lt_reg.in = lt.out; - lt_reg.write_en = 1'b1; - cond[done] = lt_reg.done; - } - - group incr { - add.right = i.read_data; - add.left = 32'd1; - - i.write_data = add.out; - i.addr0 = 1'd0; - i.write_en = 1'b1; - - incr[done] = i.done; - } - } - - control { - seq { - cond; - while lt_reg.out { - seq { - incr; - incr; - cond; - } - } - } - } -} diff --git a/tests/profiler/while-never-true.futil.data b/tests/profiler/while-never-true.futil.data deleted file mode 100644 index a2b95ef52d..0000000000 --- a/tests/profiler/while-never-true.futil.data +++ /dev/null @@ -1,10 +0,0 @@ -{ - "i": { - "data": [0], - "format": { - "numeric_type": "bitnum", - "is_signed": false, - "width": 32 - } - } -} 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 ", "Sanjit Basker ","Omkar Bhalerao "] -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, -} - -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 for BitVector { - fn from(i: usize) -> Self { - let bitvec = BitVec::from_element(i); - BitVector { bits: bitvec } - } -} - -impl From> for BitVector { - fn from(v: Vec) -> 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::>() - .chunks_mut(4) - .rev() - .map(|chunk| { - chunk.reverse(); - chunk.iter().collect::() - }) - .collect::>() - .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 = 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, - - /// 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, -} 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) -> PositionalInterpError { - // // TODO: Support PositionalInterpError in the future - // } -} - -pub type InterpResult = Result; 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, - args: HashMap, - output: HashMap, -} - -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 { - &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::>(); - 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 - // TODO: Add support for - #[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, - 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::>(); - - 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::>(); - - 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 - let mut parser = Btor2Parser::new(); - let btor2_lines = - parser.read_lines(&btor2_file).unwrap().collect::>(); - - // 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::>(); - - // 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; - -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>>, -} - -// 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 - // 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::>()); - // Ok(()) - // } - // } - // } - - pub fn run( - &mut self, - inputs: HashMap, - ) -> Result, &str> { - let btor2_lines: &Vec> = &self - .parser - .read_lines(self.path) - .as_ref() - .unwrap() - .collect::>(); - 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::>(); - - 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, // RI: integers are little-endian - offsets: Vec, // 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) -> Self { - let offsets = once(&0usize) - .chain(once(&0usize)) - .chain(node_sorts.iter()) - .scan(0usize, |state, &x| { - *state += x; - Some(*state) - }) - .collect::>(); - 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) { - 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) { - 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..21351be5c3 100644 --- a/tools/cider-data-converter/Cargo.toml +++ b/tools/cider-data-converter/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "cider-data-converter" authors.workspace = true -rust-version = "1.73" +rust-version.workspace = true edition.workspace = true version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -interp = { path = "../../interp" } +cider = { path = "../../cider" } serde = { workspace = true } serde_json = { workspace = true, features = ["arbitrary_precision"] } itertools = { workspace = true } @@ -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..993caccef8 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 cider::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 @@ -163,68 +162,77 @@ fn float_to_rational(float: f64) -> BigRational { fn unroll_float( val: f64, - format: &interp::serialization::FormatInfo, + format: &cider::serialization::FormatInfo, round_float: bool, -) -> impl Iterator { - 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 { + match *format { + cider::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::>() + } + cider::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"), } } @@ -264,13 +272,13 @@ fn format_data(declaration: &MemoryDeclaration, data: &[u8]) -> ParseVec { let chunk_stream = data.chunks_exact(width.div_ceil(8) as usize).map(|chunk| { match declaration.format { - interp::serialization::FormatInfo::Bitnum { + cider::serialization::FormatInfo::Bitnum { signed, .. } => { let int = parse_bytes(chunk, width, signed); Number::from_str(&int.to_str_radix(10)).unwrap() } - interp::serialization::FormatInfo::Fixed { + cider::serialization::FormatInfo::Fixed { signed, int_width, frac_width, @@ -281,6 +289,24 @@ fn format_data(declaration: &MemoryDeclaration, data: &[u8]) -> ParseVec { Number::from_f64(float).unwrap() } + cider::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 @@ -385,14 +411,13 @@ mod tests { let int_width = 16; let frac_width = 16; - let format = interp::serialization::FormatInfo::Fixed { + let format = cider::serialization::FormatInfo::Fixed { signed, int_width, frac_width, }; 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::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>> { + let (_, res) = alt((hex_line, comment, empty_line))(input)?; + Ok(res) +} + +#[derive(Debug, PartialEq)] +pub enum LineOrComment { + Line(Vec), + 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) 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> { + 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..488ff16ab5 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 cider::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 { if self.int_width.is_some() { self.int_width @@ -66,9 +72,9 @@ impl FormatInfo { } } - pub fn as_data_dump_format(&self) -> interp::serialization::FormatInfo { + pub fn as_data_dump_format(&self) -> cider::serialization::FormatInfo { match &self.numeric_type { - NumericType::Bitnum => interp::serialization::FormatInfo::Bitnum { + NumericType::Bitnum => cider::serialization::FormatInfo::Bitnum { signed: self.is_signed, width: self.width.unwrap(), }, @@ -93,12 +99,18 @@ impl FormatInfo { ) }; - interp::serialization::FormatInfo::Fixed { + cider::serialization::FormatInfo::Fixed { signed: self.is_signed, int_width, frac_width, } } + NumericType::IEEE754Float => { + cider::serialization::FormatInfo::IEEFloat { + signed: self.is_signed, + width: self.width.unwrap(), + } + } } } } @@ -227,7 +239,7 @@ impl ParseVec { } pub fn parse(&self, format: &FormatInfo) -> Result { - 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); #[serde(untagged)] /// A structure meant to mimic the old style of data dump printing. pub enum JsonPrintDump { + #[serde(serialize_with = "ordered_map")] Normal(HashMap), + #[serde(serialize_with = "ordered_map")] Quoted(HashMap), } +/// 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( + value: &HashMap, + serializer: S, +) -> Result +where + S: Serializer, +{ + let ordered: BTreeMap<_, _> = value.iter().collect(); + ordered.serialize(serializer) +} + impl JsonPrintDump { #[must_use] pub fn as_normal(&self) -> Option<&HashMap> { 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..606ecc58eb 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::serialization::{self, DataDump, SerializationError}; +use cider_data_converter::{ + converter, dat_parser::unwrap_line_or_comment, json_data::JsonData, +}; +use core::str; 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 { 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: Option, /// 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 = opts - .input_path - .as_ref() - .map(|path| File::open(path).map(|x| Box::new(x) as Box)) - .unwrap_or(Ok(Box::new(io::stdin())))?; - - let mut output: Box = opts - .output_path - .as_ref() - .map(|path| File::create(path).map(|x| Box::new(x) as Box)) - .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 = 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, Box), CiderDataConverterError> { + let input = get_read_handle(opts)?; + let output = get_output_handle(opts)?; + Ok((input, output)) +} + +fn get_output_handle( + opts: &Opts, +) -> Result, CiderDataConverterError> { + let output: Box = opts + .output_path + .as_ref() + .map(|path| File::create(path).map(|x| Box::new(x) as Box)) + .unwrap_or(Ok(Box::new(io::stdout())))?; + Ok(output) +} + +fn get_read_handle( + opts: &Opts, +) -> Result, CiderDataConverterError> { + let input: Box = opts + .input_path + .as_ref() + .map(|path| File::open(path).map(|x| Box::new(x) as Box)) + .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 { Ok(Path::new(path).into()) } -#[derive(Default)] -pub struct ComponentCellsBackend; - fn main() -> CalyxResult<()> { let p: Args = argh::from_env(); diff --git a/tools/data-conversion/src/float_special_values b/tools/data-conversion/src/float_special_values new file mode 100644 index 0000000000..96aadf84ef --- /dev/null +++ b/tools/data-conversion/src/float_special_values @@ -0,0 +1,32 @@ +use num_bigint::BigUint; +use std::str::FromStr; + +pub struct IntermediateRepresentation { + pub sign: bool, + pub mantissa: BigUint, + pub exponent: i64, // Arbitrary precision exponent +} + +impl IntermediateRepresentation { + // Function to check if the value is NaN + pub fn is_nan(&self, bit_width: usize) -> bool { + let max_exponent_value = (1 << (bit_width - 1)) - 1; // Max exponent for NaN + self.exponent == max_exponent_value as i64 && !self.mantissa.is_zero() + } + + // Function to check if the value is infinity + pub fn is_infinity(&self, bit_width: usize) -> bool { + let max_exponent_value = (1 << (bit_width - 1)) - 1; // Max exponent for infinity + self.exponent == max_exponent_value as i64 && self.mantissa.is_zero() + } + + // Function to check if the value is denormalized + pub fn is_denormalized(&self) -> bool { + self.exponent == 0 && !self.mantissa.is_zero() + } + + // Function to check if the value is zero + pub fn is_zero(&self) -> bool { + self.exponent == 0 && self.mantissa.is_zero() + } +} diff --git a/tools/data-conversion/src/main.rs b/tools/data-conversion/src/main.rs index f41315a257..8f4663723c 100644 --- a/tools/data-conversion/src/main.rs +++ b/tools/data-conversion/src/main.rs @@ -1,12 +1,12 @@ //use std::env; use argh::FromArgs; -use std::error::Error; use std::fmt; use std::fs::read_to_string; use std::fs::File; use std::io::stdout; use std::io::{self, Write}; use std::str::FromStr; +use std::{error::Error, fmt::Display}; //cargo run -- --from $PATH1 --to $PATH2 --ftype "from" --totype "to" @@ -29,14 +29,15 @@ enum NumType { Fixed, } -impl ToString for NumType { - fn to_string(&self) -> String { +impl Display for NumType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - NumType::Binary => "binary".to_string(), - NumType::Float => "float".to_string(), - NumType::Hex => "hex".to_string(), - NumType::Fixed => "fixed".to_string(), + NumType::Binary => "binary", + NumType::Float => "float", + NumType::Hex => "hex", + NumType::Fixed => "fixed", } + .fmt(f) } } @@ -171,22 +172,18 @@ fn convert( } _ => panic!( "Conversion from {} to {} is not supported", - convert_from.to_string(), - convert_to.to_string() + convert_from, convert_to ), } if let Some(filepath) = filepath_send { eprintln!( "Successfully converted from {} to {} in {}", - convert_from.to_string(), - convert_to.to_string(), - filepath + convert_from, convert_to, filepath ); } else { eprintln!( "Successfully converted from {} to {}", - convert_from.to_string(), - convert_to.to_string(), + convert_from, convert_to, ); } } diff --git a/tools/profiler/adjust-scaled-flame-svg.py b/tools/profiler/adjust-scaled-flame-svg.py new file mode 100644 index 0000000000..f3845a2cbe --- /dev/null +++ b/tools/profiler/adjust-scaled-flame-svg.py @@ -0,0 +1,31 @@ +import os +import sys + +# Takes in a flame graph svg that is scaled by 1000 and prints a version with fixed cycles. +def main(svg_in): + oin = open(svg_in, "r") + + for line in oin: + if line.startswith(""): + line_split = line.strip().split(" ") + target_idx = 0 + for i in range(len(line_split)): + if line_split[i] == "cycles,": + target_idx = i-1 + new_number = int(line_split[target_idx].split("(")[1].replace(",", "")) / 1000 + print(" ".join(line_split[0:target_idx]) + " (" + "{:,}".format(new_number) + " " + " ".join(line_split[target_idx+1:])) + else: + print(line.strip()) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + svg_filename = sys.argv[1] + main(svg_filename) + else: + args_desc = [ + "INPUT_SVG" + ] + print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}") + print("CELLS_JSON: Run the `component_cells` tool") + sys.exit(-1) \ No newline at end of file diff --git a/tools/profiler/create-visuals.py b/tools/profiler/create-visuals.py deleted file mode 100644 index 50061d4daa..0000000000 --- a/tools/profiler/create-visuals.py +++ /dev/null @@ -1,399 +0,0 @@ -""" -Takes in a dump file created by parse-vcd.py and creates files for visualization: -- *.folded files for producing flame graphs -- *.json files displaying the timeline (to be read by Perfetto UI) -""" -import json -import sys - -class CallStackElement: - """ - A component on the stack that is active at a given snapshot in time. Contains cell name and any active groups. - (The element may not have any active groups when control is taking extra cycles for FSMs, etc.) - starting_group is the first group that shows up. When starting_group is None, the stack simply contains the cell. - """ - def __init__(self, component, starting_group): - self.component = component - if starting_group is None: - self.active_groups = [] - else: - self.active_groups = [starting_group] - self.cell_fullname = ".".join(starting_group.split(".")[:-1]) # remove group name - self.cell_id = None - - def __repr__(self): - return f"([{self.component}] Active groups: {self.active_groups})" - - """ - Registers a new group active on the stack. - """ - def add_group(self, group_name): - if group_name not in self.active_groups: - self.active_groups.append(group_name) - - """ - Returns the active group if the component is sequential (has only one active group), or None if the cell has no active groups. - Throws exception if there are multiple groups active at the same time, since we are assuming sequential programs right now. - """ - def get_active_group(self): - if len(self.active_groups) == 0: - return None - 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}') - - """ - 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. - """ - def get_fullname(self): - active_group = self.get_active_group() - if active_group is None: - return self.cell_fullname - else: - return active_group - - """ - Returns the representation of the current stack for *.folded flame graph files. - """ - def flame_stack_string(self, main_component): - main_shortname = main_component.split("TOP.toplevel.")[1] - if self.component == main_shortname: - prefix = main_component - else: - prefix = self.cell_id - - active_group = self.get_active_group() - if active_group is not None: - return prefix + ";" + active_group.split(".")[-1] - else: - return prefix - - """ - Returns the name of the stack's component. - """ - def component_flame_stack_string(self, main_component): - main_shortname = main_component.split("TOP.toplevel.")[1] - if self.component == main_shortname: - return main_component - else: - return self.component - - # other utility functions - - def get_stack_depth(self): - return self.get_fullname().count(".") - - def add_flame_cell_name(self, parent_component, cell_name): - self.cell_id = f"{self.component}[{parent_component}.{cell_name}]" - self.parent_component = parent_component - self.cell_name = cell_name - - def get_active_groups(self): - return self.active_groups - - def add_cell_fullname(self, cell_fullname): - self.cell_fullname = cell_fullname - -""" -Computes which groups were tracked by FSMs. -""" -def get_fsm_groups(profiled_info): - fsm_groups = set() - all_groups = set() - for group_info in profiled_info: - if group_info["name"] == "TOTAL" or group_info["component"] is None: - continue - all_groups.add(group_info["name"]) - if group_info["fsm_name"] is not None: - fsm_groups.add(group_info["name"]) - return fsm_groups, all_groups - -""" -Adds a new CallStackElement object for `component` to `timeline_map` -""" -def add_elem_to_callstack(timeline_map, component, name, is_cell): - if component not in timeline_map: - if is_cell: - timeline_map[component] = CallStackElement(component, None) - timeline_map[component].add_cell_fullname(name) - else: - timeline_map[component] = CallStackElement(component, name) - else: - timeline_map[component].add_group(name) - -""" -Returns the stack element that is deepest in the simultaneously running sets of components -""" -def get_deepest_stack_element(stack_elems): - elems_sorted = sorted(stack_elems, key=lambda k : stack_elems[k].get_stack_depth(), reverse=True) - # NOTE: assuming sequential for now, which means (with n as the "deepest stack size"): - # (1) There is only one element with stack depth n (this should be a group) - # (2) There are two elements with stack depth n (a group and a cell). We want to return the cell in this case. - if len(elems_sorted) == 1 or stack_elems[elems_sorted[0]].get_stack_depth() > stack_elems[elems_sorted[1]].get_stack_depth(): - return elems_sorted[0] - elif stack_elems[elems_sorted[0]].get_active_groups() is None: # 0th element is a cell - return elems_sorted[0] - else: # 1th element has to be a cell, assuming sequential programs. - return elems_sorted[1] - -""" -Helper method to put trace stack elements in calling order -""" -def order_trace(main_component, cells_map, timeline): - main_shortname = main_component.split("TOP.toplevel.")[1] - # timeline_map has an *unordered* - processed_trace = {} - for i in timeline: - if main_shortname not in timeline[i]: - continue - stack = [timeline[i][main_shortname]] - # get the element that is deepest within the stack, then reconstruct from there - component = get_deepest_stack_element(timeline[i]) - if component != main_shortname: - cell_full_name = timeline[i][component].cell_fullname - after_main = cell_full_name.split(f"{main_component}.")[1] - after_main_split = after_main.split(".") - prev_component = main_shortname - for cell_name in after_main_split: - cell_component = cells_map[prev_component][cell_name] - timeline[i][cell_component].add_flame_cell_name(prev_component, cell_name) - stack.append(timeline[i][cell_component]) - prev_component = cell_component - processed_trace[i] = stack - return processed_trace - -""" -Constructs traces: for every cycle, what was the stack of active cells/groups? -""" -def create_trace(profiled_info, main_component, cells_map, fsm_groups, all_groups): - summary = list(filter(lambda x : x["name"] == "TOTAL", profiled_info))[0] - total_cycles = summary["total_cycles"] - only_gt_groups = all_groups.difference(fsm_groups) - timeline_map = {i : {} for i in range(total_cycles)} - fsm_timeline_map = {i : {} for i in range(total_cycles)} - group_to_gt_segments = {} # we need segment info for frequency checking - # first iterate through all of the cells - for cell_info in filter(lambda x : "is_cell" in x and x["is_cell"], profiled_info): - for segment in cell_info["closed_segments"]: - for i in range(segment["start"], segment["end"]): - add_elem_to_callstack(fsm_timeline_map[i], cell_info["component"], cell_info["name"], True) - add_elem_to_callstack(timeline_map[i], cell_info["component"], cell_info["name"], True) - # next iterate through everything else - for group_info in profiled_info: - group_name = group_info["name"] - if group_name == "TOTAL" or group_info["is_cell"]: # only care about actual groups - continue - group_component = group_info["component"] - for segment in group_info["closed_segments"]: - if group_info["fsm_name"] is None: - if group_name not in group_to_gt_segments: - group_to_gt_segments[group_name] = {} # segment start cycle to segment end cycle - group_to_gt_segments[group_name][segment["start"]] = segment["end"] - for i in range(segment["start"], segment["end"]): # really janky, I wonder if there's a better way to do this? - if group_info["fsm_name"] is not None: # FSM version - add_elem_to_callstack(fsm_timeline_map[i], group_component, group_name, False) - elif group_name in only_gt_groups: # A group that isn't managed by an FSM. In which case it has to be in both FSM and GT - add_elem_to_callstack(fsm_timeline_map[i], group_component, group_name, False) - add_elem_to_callstack(timeline_map[i], group_component, group_name, False) - else: # The ground truth info about a group managed by an FSM. - add_elem_to_callstack(timeline_map[i], group_component, group_name, False) - - trace = order_trace(main_component, cells_map, timeline_map) - fsm_trace = order_trace(main_component, cells_map, fsm_timeline_map) - - return trace, fsm_trace, len(timeline_map) - -""" -Writes a flame graph counting the number of times a group was active to `frequency_flame_out`. -""" -def create_frequency_flame_graph(main_component, trace, total_cycles, frequency_flame_out): - frequency_stacks = {} - stack_last_cycle = "" - for i in range(total_cycles): - current_stack = "" - if i in trace and len(trace[i]) != 0: - current_stack = ";".join(map(lambda x : x.flame_stack_string(main_component), trace[i])) - if stack_last_cycle != current_stack and stack_last_cycle.count(";") <= current_stack.count(";"): # We activated a different group, or invoked a different component! - if current_stack not in frequency_stacks: - frequency_stacks[current_stack] = 0 - frequency_stacks[current_stack] += 1 - stack_last_cycle = current_stack - - write_flame_graph(frequency_flame_out, frequency_stacks) - -""" -Returns a representation of how many cycles each stack combination was active for. -""" -def compute_flame_stacks(trace, main_component, total_cycles): - stacks = {} - component_stacks = {} - for i in trace: - stack = ";".join(map(lambda x : x.flame_stack_string(main_component), trace[i])) - # FIXME: really should separate out component stack - component_stack = ";".join(map(lambda x : x.component_flame_stack_string(main_component), trace[i])) - if stack not in stacks: - stacks[stack] = 0 - if component_stack not in component_stacks: - component_stacks[component_stack] = 0 - stacks[stack] += 1 - component_stacks[component_stack] += 1 - component_stacks[main_component] = total_cycles - len(trace) - return stacks, component_stacks - -""" -Constructs and writes flame graphs. -""" -def create_flame_graph(main_component, trace, fsm_trace, num_cycles, flame_out, fsm_flame_out, component_out, fsm_component_out): - stacks, component_stacks = compute_flame_stacks(trace, main_component, num_cycles) - write_flame_graph(flame_out, stacks) - write_flame_graph(component_out, component_stacks) - fsm_stacks, fsm_component_stacks = compute_flame_stacks(fsm_trace, main_component, num_cycles) - write_flame_graph(fsm_flame_out, fsm_stacks) - write_flame_graph(fsm_component_out, fsm_component_stacks) - -""" -Creates the JSON timeline representation. -""" -def create_timeline_stacks(trace, main_component): - events = [] - currently_active = {} # group name to beginning traceEvent entry (so end event can copy) - ts_multiplier = 100 # some arbitrary number to multiply by so that it's easier to see in the viewer - cell_to_stackframe_info = {main_component : (2, 1)} # (stack_number, parent_stack_number) - stack_number_acc = 3 # To guarantee that we get unique stack numbers when we need a new one - - cell_to_stackframe_info["MAIN"] = (1, None) - 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" - for elem in trace[i]: - # we want an entry for the cell and the group, if the stack entry has a group in it. - # cell - cell_name = elem.cell_fullname - active_this_cycle.add(cell_name) - if cell_name not in currently_active: # first cycle of the cell. - # get the stackframe - if cell_name not in cell_to_stackframe_info: - (parent_stackframe, _) = cell_to_stackframe_info[parent] # the parent better have been registered by now - cell_to_stackframe_info[cell_name] = (stack_number_acc, parent_stackframe) - stack_number_acc += 1 - (cell_stackframe, _) = cell_to_stackframe_info[cell_name] - # add start event of this cell - start_event = {"name": cell_name.split(".")[-1], "cat": "cell", "ph": "B", "pid" : 1, "tid": 1, "ts": i * ts_multiplier, "sf" : cell_stackframe} - events.append(start_event) - currently_active[cell_name] = start_event - # add a group if one is active - group_name = elem.get_active_group() - if group_name is not None: - active_this_cycle.add(group_name) - if group_name not in currently_active: - # get the stackframe - if group_name not in cell_to_stackframe_info: - cell_to_stackframe_info[group_name] = (stack_number_acc, cell_stackframe) # parent of the group is the cell - stack_number_acc += 1 - (group_stackframe, _) = cell_to_stackframe_info[group_name] - # add start event of this group - start_event = {"name": group_name.split(".")[-1], "cat": "group", "ph": "B", "pid" : 1, "tid": 1, "ts": i * ts_multiplier, "sf" : group_stackframe} - events.append(start_event) - currently_active[group_name] = start_event - parent = group_name # the next cell's parent will be this group. - # Any element that was previously active but not active this cycle need to end - for non_active_group in set(currently_active.keys()).difference(active_this_cycle): - end_event = currently_active[non_active_group].copy() - del currently_active[non_active_group] - end_event["ts"] = (i) * ts_multiplier - 1 - end_event["ph"] = "E" - events.append(end_event) - # postprocess - add end events for all events still active by the end - for event in currently_active: - end_event = currently_active[event].copy() - end_event["ts"] = (len(trace)) * ts_multiplier - 1 # only difference w the above - end_event["ph"] = "E" - events.append(end_event) - - # "stackFrames" field of the Trace Format JSON - stacks = {} - stack_category = "C" - for cell in cell_to_stackframe_info: - stack_id, parent_stack_id = cell_to_stackframe_info[cell] - if parent_stack_id is None: - stacks[stack_id] = {"name" : "MAIN", "category": stack_category} - else: - stacks[stack_id] = {"name" : cell, "parent": parent_stack_id, "category" : stack_category} - - return { "traceEvents": events, "stackFrames": stacks } - -""" -Wrapper function for constructing and writing a timeline representation -""" -def create_timeline_json(trace, fsm_trace, main_component, timeline_out, fsm_timeline_out): - timeline_json_data = create_timeline_stacks(trace, main_component) - with open(timeline_out, "w", encoding="utf-8") as timeline_file: - timeline_file.write(json.dumps(timeline_json_data, indent=4)) - fsm_timeline_json_data = create_timeline_stacks(fsm_trace, main_component) - with open(fsm_timeline_out, "w", encoding="utf-8") as fsm_timeline_file: - fsm_timeline_file.write(json.dumps(fsm_timeline_json_data, indent=4)) - -""" -Helper function to output the *.folded file for flame graphs. -""" -def write_flame_graph(flame_out, stacks): - with open(flame_out, "w") as f: - for stack in sorted(stacks, key=lambda k : len(k)): # main needs to come first for flame graph script to not make two boxes for main? - f.write(f"{stack} {stacks[stack]}\n") - -""" -Helper function to process the cells_json input. -""" -def build_cells_map(json_file): - cell_json = json.load(open(json_file)) - cells_map = {} - for component_entry in cell_json: - inner_cells_map = {} - for cell_entry in component_entry["cell_info"]: - inner_cells_map[cell_entry["cell_name"]] = cell_entry["component_name"] - cells_map[component_entry["component"]] = inner_cells_map - return cells_map - -def main(profiler_dump_file, cells_json, timeline_out, fsm_timeline_out, flame_out, fsm_flame_out, frequency_flame_out, component_out, fsm_component_out): - profiled_info = json.load(open(profiler_dump_file, "r")) - fsm_groups, all_groups = get_fsm_groups(profiled_info) - # This cells_map is different from the one in parse-vcd.py - cells_map = build_cells_map(cells_json) - summary = list(filter(lambda x : x["name"] == "TOTAL", profiled_info))[0] - main_component = summary["main_full_path"] - trace, fsm_trace, num_cycles = create_trace(profiled_info, main_component, cells_map, fsm_groups, all_groups) - create_flame_graph(main_component, trace, fsm_trace, num_cycles, flame_out, fsm_flame_out, component_out, fsm_component_out) - create_timeline_json(trace, fsm_trace, main_component, timeline_out, fsm_timeline_out) - create_frequency_flame_graph(main_component, trace, num_cycles, frequency_flame_out) - -if __name__ == "__main__": - if len(sys.argv) > 9: - profiler_dump_json = sys.argv[1] - cells_json = sys.argv[2] - timeline_out = sys.argv[3] - fsm_timeline_out = sys.argv[4] - flame_out = sys.argv[5] - fsm_flame_out = sys.argv[6] - frequency_flame_out = sys.argv[7] - component_flame_out = sys.argv[8] - fsm_component_flame_out = sys.argv[9] - main(profiler_dump_json, cells_json, timeline_out, fsm_timeline_out, flame_out, fsm_flame_out, frequency_flame_out, component_flame_out, fsm_component_flame_out) - else: - args_desc = [ - "PROFILER_JSON", - "CELLS_JSON", - "TIMELINE_VIEW_JSON", - "FSM_TIMELINE_VIEW_JSON", - "FLAME_GRAPH_FOLDED", - "FSM_FLAME_GRAPH_FOLDED", - "FREQUENCY_FLAME_GRAPH_FOLDED", - "COMPONENT_FOLDED", - "FSM_COMPONENT_FOLDED" - ] - print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}") - sys.exit(-1) diff --git a/tools/profiler/create-visuals.sh b/tools/profiler/create-visuals.sh new file mode 100644 index 0000000000..61e36559d6 --- /dev/null +++ b/tools/profiler/create-visuals.sh @@ -0,0 +1,30 @@ +# Utility script for fud2 to produce flame graphs from produced .folded files + +if [ $# -lt 4 ]; then + echo "USAGE: bash $0 FLAME_GRAPH_SCRIPT DATA_DIR REPR_IN REPR_OUT" # the last two arguments are mainly for fud2's need for a defined input and output. + exit +fi + +SCRIPT_DIR=$( cd $( dirname $0 ) && pwd ) + +FLAME_GRAPH_SCRIPT=$1 +DATA_DIR=$2 +REPR_IN=$3 +REPR_OUT=$4 + +TREES_PDF_DIR=${DATA_DIR}-png +for f in $( ls ${DATA_DIR} | grep dot$ ); do + dot -Tpng ${DATA_DIR}/${f} > ${DATA_DIR}/${f}.png +done + +for folded in $( ls ${DATA_DIR}/*.folded ); do + base_name=$( echo "${folded}" | rev | cut -d. -f2- | rev ) + if [[ "${base_name}" == *"scaled"* ]]; then + ${FLAME_GRAPH_SCRIPT} --countname="cycles" ${folded} > ${base_name}-original.svg + python3 ${SCRIPT_DIR}/adjust-scaled-flame-svg.py ${base_name}-original.svg > ${base_name}.svg + else + ${FLAME_GRAPH_SCRIPT} --countname="cycles" ${folded} > ${base_name}.svg + fi +done + +${FLAME_GRAPH_SCRIPT} ${REPR_IN} > ${REPR_OUT} 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 deleted file mode 100644 index ce2dc236d0..0000000000 --- a/tools/profiler/parse-vcd.py +++ /dev/null @@ -1,403 +0,0 @@ -import csv -import sys -import json -import vcdvcd - -def remove_size_from_name(name: str) -> str: - """ changes e.g. "state[2:0]" to "state" """ - return name.split('[')[0] - -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.total_cycles = 0 - self.closed_segments = [] # Segments will be (start_time, end_time) - self.current_segment = None - self.tdcc_group = tdcc_group_name - self.component = component - self.is_cell = is_cell - - def __repr__ (self): - segments_str = "" - for segment in self.closed_segments: - if (segments_str != ""): - segments_str += ", " - segments_str += f"[{segment['start']}, {segment['end']})" - if self.fsm_name is not None: - header = (f"[FSM] Group {self.name}:\n" + - f"\tFSM name: {self.fsm_name}\n" + - f"\tFSM state ids: {self.fsm_values}\n" - ) - elif self.component is None: - header = f"[CMP] Group {self.name}:\n" - else: - header = f"[GT] Group {self.name}:\n" - - return (header + - f"\tTotal cycles: {self.total_cycles}\n" + - f"\t# of times active: {len(self.closed_segments)}\n" + - f"\tSegments: {segments_str}\n" - ) - - def is_active(self): - return self.current_segment is not None - - def start_clock_cycle(self): - if self.current_segment is None: - return -1 - else: - return self.current_segment["start"] - - def compute_average_cycles(self): - if len(self.closed_segments) == 0: - return 0 - else: - return round(self.total_cycles / len(self.closed_segments), 2) - - def emit_csv_data(self): - name = self.name - if self.fsm_name is not None: - name += "[FSM]" - if self.component is None: - name += "[CMP]" - return {"name": name, - "total-cycles" : self.total_cycles, - "times-active" : len(self.closed_segments), - "avg" : self.compute_average_cycles()} - - def summary(self): - if self.fsm_name is None: - header = "[GT] " - else: - header = "[FSM]" - return (f"{header} Group {self.name} Summary:\n" + - f"\tTotal cycles: {self.total_cycles}\n" + - f"\t# of times active: {len(self.closed_segments)}\n" + - f"\tAvg runtime: {self.compute_average_cycles()}\n" - ) - - def start_new_segment(self, curr_clock_cycle): - if self.current_segment is None: - self.current_segment = {"start": curr_clock_cycle, "end": -1} - else: - print(f"Error! The group {self.name} is starting a new segment while the current segment is not closed.") - print(f"Current segment: {self.current_segment}") - sys.exit(1) - - def end_current_segment(self, curr_clock_cycle): - if self.current_segment is not None and self.current_segment["end"] == -1: # ignore cases where done is high forever - self.current_segment["end"] = curr_clock_cycle - self.closed_segments.append(self.current_segment) - self.total_cycles += curr_clock_cycle - self.current_segment["start"] - self.current_segment = None # Reset current segment - -class VCDConverter(vcdvcd.StreamParserCallbacks): - - def __init__(self, fsms, single_enable_names, 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() - # 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) - # Map from timestamps [ns] to value change events that happened on that timestamp - self.timestamps_to_events = {} - - def enddefinitions(self, vcd, signals, cur_sig_vals): - # convert references to list and sort by name - refs = [(k, v) for k, v in vcd.references_to_ids.items()] - refs = sorted(refs, key=lambda e: e[0]) - 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...") - sys.exit(1) - signal_id_dict[vcd.references_to_ids[clock_name]] = [clock_name] - - # get go and done for cells (the signals are exactly {cell}.go and {cell}.done) - for cell in self.cells: - cell_go = cell + ".go" - cell_done = cell + ".done" - if cell_go not in vcd.references_to_ids: - print(f"Not accounting for cell {cell} (probably combinational)") - continue - signal_id_dict[vcd.references_to_ids[cell_go]].append(cell_go) - signal_id_dict[vcd.references_to_ids[cell_done]].append(cell_done) - - for name, sid in refs: - # FIXME: We may want to optimize these nested for loops - for tdcc_group in self.tdcc_group_to_dep_fsms: - if name.startswith(f"{tdcc_group}_go.out["): - signal_id_dict[sid].append(name) - 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) - - # 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} - - # Stream processes the events recorded in the VCD and stores them in self.timestamps_to_events - # NOTE: Stream processing doesn't work because value changes that happen in the same timestamp - # are not processed at the same time. - # NOTE: when we reimplement this script, we probably want to separate this part from the - # clock cycle processing - def value( - self, - vcd, - time, - value, - identifier_code, - cur_sig_vals, - ): - # ignore all signals we don't care about - if identifier_code not in self.signal_id_to_names: - return - - signal_names = self.signal_id_to_names[identifier_code] - int_value = int(value, 2) - - if time not in self.timestamps_to_events: - self.timestamps_to_events[time] = [] - - for signal_name in signal_names: - event = {"signal": signal_name, "value": int_value} - self.timestamps_to_events[time].append(event) - - # Postprocess data mapping timestamps to events (signal changes) - # We have to postprocess instead of processing signals in a stream because - # signal changes that happen at the same time as a clock tick might be recorded - # *before* or *after* the clock change on the VCD file (hence why we can't process - # everything within a stream if we wanted to be precise) - def postprocess(self): - clock_name = f"{self.main_component}.clk" - clock_cycles = -1 - fsm_to_active_group = {fsm : None for fsm in self.fsms} - # current values of FSM registers. This is different from fsm_to_active_group since the TDCC group for the FSM - # may not be active (which means that no group managed by the FSM is active) - fsm_to_curr_value = {fsm: -1 for fsm in self.fsms} - started = False - for ts in self.timestamps_to_events: - events = self.timestamps_to_events[ts] - started = started or [x for x in events if x["signal"] == f"{self.main_component}.go" and x["value"] == 1] - if not started: - # Update fsm_to_curr_value for any FSM signals that got updated. We will start the events corresponding - # to those values once the TDCC group for the FSM starts. - # Realistically this will most likely only happen on the 0th cycle just to set the FSM value to 0, - # but trying to be extra safe here. - for event in filter(lambda e : "fsm" in e["signal"], events): - fsm = ".".join(event["signal"].split(".")[0:-1]) - if event["value"] in self.fsms[fsm]: - fsm_to_curr_value[fsm] = event["value"] - continue - # checking whether the timestamp has a rising edge (hacky) - if {"signal": clock_name, "value": 1} in events: - clock_cycles += 1 - # TDCC groups need to be recorded (before FSMs) for tracking FSM values - # (ex. if the FSM has value 0 but the TDCC group isn't active, then the group represented by the - # FSM's 0 value should not be considered as active) - for tdcc_event in filter(lambda e : "tdcc" in e["signal"] and "go" in e["signal"], events): - tdcc_group = "_".join(tdcc_event["signal"].split("_")[0:-1]) - if self.tdcc_group_active_cycle[tdcc_group] == -1 and tdcc_event["value"] == 1: # value changed to 1 - self.tdcc_group_active_cycle[tdcc_group] = clock_cycles - for fsm in self.tdcc_group_to_dep_fsms[tdcc_group]: - value = fsm_to_curr_value[fsm] - if value != -1: - if value not in self.fsms[fsm]: - continue - next_group = f"{self.fsms[fsm][value]}FSM" - fsm_to_active_group[fsm] = next_group - self.profiling_info[next_group].start_new_segment(clock_cycles) - elif self.tdcc_group_active_cycle[tdcc_group] > -1 and tdcc_event["value"] == 0: # tdcc group that was active's signal turned to 0 - self.tdcc_group_active_cycle[tdcc_group] = -1 - for event in events: - signal_name = event["signal"] - value = event["value"] - if "tdcc" in signal_name and "go" in signal_name: # skip all tdcc events since we've already processed them - continue - if signal_name.endswith(".go") and value == 1: # cells have .go and .done - cell = signal_name.split(".go")[0] - self.profiling_info[cell].start_new_segment(clock_cycles) - 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]) - self.profiling_info[group].start_new_segment(clock_cycles) - elif "_done" in signal_name and value == 1: - group = "_".join(signal_name.split("_")[0:-1]) - self.profiling_info[group].end_current_segment(clock_cycles) - elif "fsm" in signal_name: - fsm = ".".join(signal_name.split(".")[0:-1]) - fsm_to_curr_value[fsm] = value - # Workarounds because the value 0 may not correspond to a group - if fsm_to_active_group[fsm] is not None: - prev_group = fsm_to_active_group[fsm] # getting the "FSM" variant of the group - self.profiling_info[prev_group].end_current_segment(clock_cycles) - if value in self.fsms[fsm]: - next_group = f"{self.fsms[fsm][value]}FSM" # getting the "FSM" variant of the group - tdcc_group_active_cycle = self.tdcc_group_active_cycle[self.profiling_info[next_group].tdcc_group] - if tdcc_group_active_cycle == -1: # If the TDCC group is not active, then no segments should start - continue - fsm_to_active_group[fsm] = next_group - self.profiling_info[next_group].start_new_segment(clock_cycles) - - self.clock_cycles = clock_cycles - -# Generates a list of all of the components to potential cell names -# `prefix` is the cell's "path" (ex. for a cell "my_cell" defined in "main", the prefix would be "TOP.toplevel.main") -# The initial value of curr_component should be the top level/main component -def build_components_to_cells(prefix, curr_component, cells_to_components, components_to_cells): - for (cell, cell_component) in cells_to_components[curr_component].items(): - if cell_component not in components_to_cells: - components_to_cells[cell_component] = [f"{prefix}.{cell}"] - else: - components_to_cells[cell_component].append(f"{prefix}.{cell}") - build_components_to_cells(prefix + f".{cell}", cell_component, cells_to_components, components_to_cells) - -# Reads json generated by component-cells backend to produce a mapping from all components -# to cell names they could have. -def read_component_cell_names_json(json_file): - cell_json = json.load(open(json_file)) - # For each component, contains a map from each cell name to its corresponding component - # component name --> { cell name --> component name} - cells_to_components = {} - main_component = "" - for curr_component_entry in cell_json: - cell_map = {} # mapping cell names to component names for all cells in the current component - if curr_component_entry["is_main_component"]: - main_component = curr_component_entry["component"] - for cell_info in curr_component_entry["cell_info"]: - cell_map[cell_info["cell_name"]] = cell_info["component_name"] - cells_to_components[curr_component_entry["component"]] = cell_map - full_main_component = f"TOP.toplevel.{main_component}" - components_to_cells = {main_component : [full_main_component]} # come up with a better name for this - build_components_to_cells(full_main_component, main_component, cells_to_components, components_to_cells) - 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) - 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) - fsms = {} # Remapping of JSON data for easy access - for profiling_info in profiling_infos: - if "Fsm" in profiling_info: - fsm = profiling_info["Fsm"] - # create entries for all possible cells of component - for cell in components_to_cells[fsm["component"]]: - fsm_name = cell + "." + fsm["fsm"] - fsms[fsm_name] = {} - for state in fsm["states"]: - group_name = cell + "." + state["group"] - fsms[fsm_name][state["id"]] = group_name - tdcc_group = cell + "." + fsm["group"] - if group_name not in fsm_group_maps: - fsm_group_maps[group_name] = {"fsm": fsm_name, "tdcc-group-name": tdcc_group, "ids": [state["id"]], "component": fsm["component"]} - 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 - -def output_result(out_csv, dump_out_json, converter): - print(f"Total clock cycles: {converter.clock_cycles}") - print("=====SUMMARY=====") - print() - groups_to_emit = list(filter(lambda group : not group.name.startswith("tdcc") and not group.name.endswith("END"), converter.profiling_info.values())) - groups_to_emit.sort(key=lambda x : x.name) # to preserve stability - groups_to_emit.sort(key=lambda x : x.total_cycles, reverse=True) - csv_acc = [] - dump_json_acc = [] - for group_info in groups_to_emit: - csv_acc.append(group_info.emit_csv_data()) - dump_json_acc.append(group_info.__dict__) - print(group_info.summary()) - print("=====DUMP=====") - print() - for group_info in groups_to_emit: - print(group_info) - # Add total cycles for visualizer script (probably want to do this in a neater fashion in the future) - dump_json_acc.append({"name": "TOTAL", "total_cycles": converter.clock_cycles, "main_full_path": converter.main_component}) - # emit a json for visualizer script - print(f"Writing dump JSON to {dump_out_json}") - with open(dump_out_json, "w", encoding="utf-8") as dump_file: - dump_file.write(json.dumps(dump_json_acc, indent=4)) - # emitting a CSV file for easier eyeballing - print(f"Writing summary to {out_csv}") - csv_keys = ["name", "total-cycles", "times-active", "avg"] - csv_acc.append({ "name": "TOTAL", "total-cycles": converter.clock_cycles, "times-active": "-", "avg": "-"}) - if (out_csv == "STDOUT"): - writer = csv.DictWriter(sys.stdout, csv_keys, lineterminator="\n") - else: - writer = csv.DictWriter(open(out_csv, "w"), csv_keys, lineterminator="\n") - writer.writeheader() - writer.writerows(csv_acc) - -def main(vcd_filename, groups_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) - vcdvcd.VCDVCD(vcd_filename, callbacks=converter, store_tvs=False) - converter.postprocess() - output_result(out_csv, dump_out_json, converter) - -if __name__ == "__main__": - if len(sys.argv) > 5: - vcd_filename = sys.argv[1] - fsm_json = sys.argv[2] - cells_json = sys.argv[3] - out_csv = sys.argv[4] - dump_out_json = sys.argv[5] - main(vcd_filename, fsm_json, cells_json, out_csv, dump_out_json) - else: - args_desc = [ - "VCD_FILE", - "TDCC_JSON", - "CELLS_JSON", - "SUMMARY_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("If SUMMARY_OUT_CSV is STDOUT, then summary CSV will be printed to stdout") - sys.exit(-1) diff --git a/tools/profiler/profiler-process.py b/tools/profiler/profiler-process.py new file mode 100644 index 0000000000..e865a59bd8 --- /dev/null +++ b/tools/profiler/profiler-process.py @@ -0,0 +1,673 @@ +from datetime import datetime +import csv +import json +import os +import sys +import vcdvcd + +DELIMITER = "___" +INVISIBLE = "gray" +ACTIVE_CELL_COLOR="pink" +ACTIVE_GROUP_COLOR="mediumspringgreen" +ACTIVE_PRIMITIVE_COLOR="orange" +TREE_PICTURE_LIMIT=300 +SCALED_FLAME_MULTIPLIER=1000 # multiplier so scaled flame graph will not round up. +ts_multiplier = 1 #ms on perfetto UI that resembles a single cycle + +def remove_size_from_name(name: str) -> str: + """ changes e.g. "state[2:0]" to "state" """ + return name.split('[')[0] + +def create_cycle_trace(info_this_cycle, cells_to_components, main_component, include_primitives): + stacks_this_cycle = [] + parents = set() # keeping track of entities that are parents of other entities + i_mapping = {} # each unique group inv mapping to its stack. the "group" should be the last item on each stack + i_mapping[main_component] = [main_component.split(".")[-1]] + cell_worklist = [main_component] + while len(cell_worklist) > 0: + current_cell = cell_worklist.pop() + covered_units_in_component = set() # collect all of the units we've covered. + # catch all active units that are groups in this component. + units_to_cover = info_this_cycle["group-active"][current_cell] if current_cell in info_this_cycle["group-active"] else set() + structural_enables = info_this_cycle["structural-enable"][current_cell] if current_cell in info_this_cycle["structural-enable"] else set() + primitive_enables = info_this_cycle["primitive-enable"][current_cell] if current_cell in info_this_cycle["primitive-enable"] else set() + cell_invokes = info_this_cycle["cell-invoke"][current_cell] if current_cell in info_this_cycle["cell-invoke"] else set() + # find all enables from control. these are all units that either (1) don't have any maps in call_stack_probes_info, or (2) have no active parent calls in call_stack_probes_info + for active_unit in units_to_cover: + shortname = active_unit.split(".")[-1] + if active_unit not in structural_enables: + i_mapping[active_unit] = i_mapping[current_cell] + [shortname] + parents.add(current_cell) + covered_units_in_component.add(active_unit) + # get all of the other active units + while len(covered_units_in_component) < len(units_to_cover): + # loop through all other elements to figure out parent child info + for active_unit in units_to_cover: + shortname = active_unit.split(".")[-1] + if active_unit in i_mapping: + continue + for parent_group in structural_enables[active_unit]: + parent = f"{current_cell}.{parent_group}" + if parent in i_mapping: + i_mapping[active_unit] = i_mapping[parent] + [shortname] + covered_units_in_component.add(active_unit) + parents.add(parent) + # get primitives if requested. + if include_primitives: + for primitive_parent_group in primitive_enables: + for primitive_name in primitive_enables[primitive_parent_group]: + primitive_parent = f"{current_cell}.{primitive_parent_group}" + primitive_shortname = primitive_name.split(".")[-1] + i_mapping[primitive_name] = i_mapping[primitive_parent] + [f"{primitive_shortname} (primitive)"] + parents.add(primitive_parent) + # by this point, we should have covered all groups in the same component... + # now we need to construct stacks for any cells that are called from a group in the current component. + for cell_invoker_group in cell_invokes: + for invoked_cell in cell_invokes[cell_invoker_group]: + if invoked_cell in info_this_cycle["cell-active"]: + cell_shortname = invoked_cell.split(".")[-1] + cell_worklist.append(invoked_cell) + cell_component = cells_to_components[invoked_cell] + parent = f"{current_cell}.{cell_invoker_group}" + i_mapping[invoked_cell] = i_mapping[parent] + [f"{cell_shortname} [{cell_component}]"] + parents.add(parent) + # Only retain paths that lead to leaf nodes. + for elem in i_mapping: + if elem not in parents: + stacks_this_cycle.append(i_mapping[elem]) + + return stacks_this_cycle + +class VCDConverter(vcdvcd.StreamParserCallbacks): + def __init__(self, main_component, cells_to_components): + super().__init__() + self.main_component = main_component + self.cells_to_components = cells_to_components + # Documenting other fields for reference + # signal_id_to_names + self.timestamps_to_events = {} + + def enddefinitions(self, vcd, signals, cur_sig_vals): + # convert references to list and sort by name + refs = [(k, v) for k, v in vcd.references_to_ids.items()] + refs = sorted(refs, key=lambda e: e[0]) + 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 + + clock_name = f"{self.main_component}.clk" + if clock_name not in names: + print("Can't find the clock? Exiting...") + sys.exit(1) + signal_id_dict[vcd.references_to_ids[clock_name]] = [clock_name] + + # get go and done for cells (the signals are exactly {cell}.go and {cell}.done) + for cell in self.cells_to_components.keys(): + cell_go = cell + ".go" + cell_done = cell + ".done" + if cell_go not in vcd.references_to_ids: + print(f"Not accounting for cell {cell} (probably combinational)") + continue + signal_id_dict[vcd.references_to_ids[cell_go]].append(cell_go) + signal_id_dict[vcd.references_to_ids[cell_done]].append(cell_done) + + for name, sid in refs: + if "probe_out" in name: + 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} + + def value(self, vcd, time, value, identifier_code, cur_sig_vals): + # ignore all signals we don't care about + if identifier_code not in self.signal_id_to_names: + return + + signal_names = self.signal_id_to_names[identifier_code] + int_value = int(value, 2) + + for signal_name in signal_names: + if signal_name == f"{self.main_component}.clk" and int_value == 0: # ignore falling edges + continue + event = {"signal": signal_name, "value": int_value} + if time not in self.timestamps_to_events: + self.timestamps_to_events[time] = [event] + else: + self.timestamps_to_events[time].append(event) + + # Postprocess data mapping timestamps to events (signal changes) + # We have to postprocess instead of processing signals in a stream because + # signal changes that happen at the same time as a clock tick might be recorded + # *before* or *after* the clock change on the VCD file (hence why we can't process + # everything within a stream if we wanted to be precise) + def postprocess(self): + clock_name = f"{self.main_component}.clk" + clock_cycles = -1 # will be 0 on the 0th cycle + started = False + cell_active = set() + group_active = set() + structural_enable_active = set() + cell_enable_active = set() + primitive_enable = set() + self.cell_to_active_cycles = {} # cell --> [{"start": X, "end": Y, "length": Y - X}]. + + probe_labels_to_sets = {"group_probe_out": group_active, "se_probe_out": structural_enable_active, "cell_probe_out": cell_enable_active, "primitive_probe_out" : primitive_enable} + + self.trace = {} # cycle number --> set of stacks + + main_done = False # Prevent creating a trace entry for the cycle where main.done is set high. + for ts in self.timestamps_to_events: + events = self.timestamps_to_events[ts] + started = started or [x for x in events if x["signal"] == f"{self.main_component}.go" and x["value"] == 1] + if not started: # only start counting when main component is on. + continue + # checking whether the timestamp has a rising edge + if {"signal": clock_name, "value": 1} in events: + clock_cycles += 1 + # Recording the data organization for every kind of probe so I don't forget. () is a set. + # groups-active: cell --> (active groups) + # cell-active: (cells) + # structural-enable: cell --> { child --> (parents) } + # cell-invoke: parent_cell --> { parent --> (cells) } + # primitive-enable: cell --> { parent --> (primitives) } + info_this_cycle = {"group-active" : {}, "cell-active": set(), "structural-enable": {}, "cell-invoke": {}, "primitive-enable": {}} + for event in events: + # check probe and cell signals to update currently active entities. + signal_name = event["signal"] + value = event["value"] + if signal_name.endswith(".go") and value == 1: # cells have .go and .done + cell = signal_name.split(".go")[0] + cell_active.add(cell) + if cell not in self.cell_to_active_cycles: + self.cell_to_active_cycles[cell] = [{"start": clock_cycles}] + else: + self.cell_to_active_cycles[cell].append({"start": clock_cycles}) + if signal_name.endswith(".done") and value == 1: + cell = signal_name.split(".done")[0] + if cell == self.main_component: # if main is done, we shouldn't compute a "trace" for this cycle. set flag to True. + main_done = True + cell_active.remove(cell) + current_segment = self.cell_to_active_cycles[cell][-1] + current_segment["end"] = clock_cycles + current_segment["length"] = clock_cycles - current_segment["start"] + # process all probes. + for probe_label in probe_labels_to_sets: + cutoff = f"_{probe_label}" + if cutoff in signal_name: + # record cell name instead of component name. + split = signal_name.split(cutoff)[0].split(DELIMITER)[:-1] + cell_name = ".".join(signal_name.split(cutoff)[0].split(".")[:-1]) + split.append(cell_name) + probe_info = tuple(split) + if value == 1: + probe_labels_to_sets[probe_label].add(probe_info) + elif value == 0: + probe_labels_to_sets[probe_label].remove(probe_info) + if not main_done: + # add all probe information + info_this_cycle["cell-active"] = cell_active.copy() + for (group, cell_name) in group_active: + if cell_name in info_this_cycle["group-active"]: + info_this_cycle["group-active"][cell_name].add(group) + else: + info_this_cycle["group-active"][cell_name] = {group} + for (child_group, parent_group, cell_name) in structural_enable_active: + if cell_name not in info_this_cycle["structural-enable"]: + info_this_cycle["structural-enable"][cell_name] = {child_group: {parent_group}} + elif child_group not in info_this_cycle["structural-enable"][cell_name]: + info_this_cycle["structural-enable"][cell_name][child_group] = {parent_group} + else: + info_this_cycle["structural-enable"][cell_name][child_group].add(parent_group) + for (cell_name, parent_group, parent_cell_name) in cell_enable_active: + if parent_cell_name not in info_this_cycle["cell-invoke"]: + info_this_cycle["cell-invoke"][parent_cell_name] = {parent_group : {cell_name}} + elif parent_group not in info_this_cycle["cell-invoke"][parent_cell_name]: + info_this_cycle["cell-invoke"][parent_cell_name][parent_group] = {cell_name} + else: + info_this_cycle["cell-invoke"][parent_cell_name][parent_group].add(cell_name) + for (primitive_name, parent_group, cell_name) in primitive_enable: + if cell_name not in info_this_cycle["primitive-enable"]: + info_this_cycle["primitive-enable"][cell_name] = {parent_group: {primitive_name}} + elif parent_group not in info_this_cycle["primitive-enable"][cell_name]: + info_this_cycle["primitive-enable"][cell_name][parent_group] = {primitive_name} + else: + info_this_cycle["primitive-enable"][cell_name][parent_group].add(primitive_name) + self.trace[clock_cycles] = create_cycle_trace(info_this_cycle, self.cells_to_components, self.main_component, True) # True to track primitives + + self.clock_cycles = clock_cycles # last rising edge does not count as a full cycle (probably) + +# Generates a list of all of the components to potential cell names +# `prefix` is the cell's "path" (ex. for a cell "my_cell" defined in "main", the prefix would be "TOP.toplevel.main") +# The initial value of curr_component should be the top level/main component +def build_components_to_cells(prefix, curr_component, cells_to_components, components_to_cells): + for (cell, cell_component) in cells_to_components[curr_component].items(): + if cell_component not in components_to_cells: + components_to_cells[cell_component] = [f"{prefix}.{cell}"] + else: + components_to_cells[cell_component].append(f"{prefix}.{cell}") + build_components_to_cells(prefix + f".{cell}", cell_component, cells_to_components, components_to_cells) + +# Reads json generated by component-cells backend to produce a mapping from all components +# to cell names they could have. +def read_component_cell_names_json(json_file): + cell_json = json.load(open(json_file)) + # For each component, contains a map from each cell name to its corresponding component + # component name --> { cell name --> component name } + cells_to_components = {} + main_component = "" + for curr_component_entry in cell_json: + cell_map = {} # mapping cell names to component names for all cells in the current component + if curr_component_entry["is_main_component"]: + main_component = curr_component_entry["component"] + for cell_info in curr_component_entry["cell_info"]: + cell_map[cell_info["cell_name"]] = cell_info["component_name"] + cells_to_components[curr_component_entry["component"]] = cell_map + full_main_component = f"TOP.toplevel.{main_component}" + components_to_cells = {main_component : [full_main_component]} # come up with a better name for this + build_components_to_cells(full_main_component, main_component, cells_to_components, components_to_cells) + full_cell_names_to_components = {} + for component in components_to_cells: + for cell in components_to_cells[component]: + full_cell_names_to_components[cell] = component + + return full_main_component, full_cell_names_to_components + +""" +Creates a tree that encapsulates all stacks that occur within the program. +""" +def create_tree(timeline_map): + node_id_acc = 0 + tree_dict = {} # node id --> node name + path_dict = {} # stack list string --> list of node ids + path_prefixes_dict = {} # stack list string --> list of node ids + stack_list = [] + # collect all of the stacks from the list. (i.e. "flatten" the timeline map values.) + for sl in timeline_map.values(): + for s in sl: + if s not in stack_list: + stack_list.append(s) + stack_list.sort(key=len) + for stack in stack_list: + stack_len = len(stack) + id_path_list = [] + prefix = "" + # obtain the longest prefix of the current stack. Everything after the prefix is a new stack element. + for i in range(1, stack_len+1): + attempted_prefix = ";".join(stack[0:stack_len-i]) + if attempted_prefix in path_prefixes_dict: + prefix = attempted_prefix + id_path_list = list(path_prefixes_dict[prefix]) + break + # create nodes + if prefix != "": + new_nodes = stack[stack_len - i:] + new_prefix = prefix + else: + new_nodes = stack + new_prefix = "" + for elem in new_nodes: + if new_prefix == "": + new_prefix = elem + else: + new_prefix += f";{elem}" + tree_dict[node_id_acc] = elem + id_path_list.append(node_id_acc) + path_prefixes_dict[new_prefix] = list(id_path_list) + node_id_acc += 1 + path_dict[new_prefix] = id_path_list + + return tree_dict, path_dict + +def create_tree_rankings(trace, tree_dict, path_dict, path_to_edges, all_edges, dot_out_dir): + stack_list_str_to_used_nodes = {} + stack_list_str_to_used_edges = {} + stack_list_str_to_cycles = {} + all_nodes = set(tree_dict.keys()) + + # accumulating counts + for i in trace: + stack_list_str = str(trace[i]) + if stack_list_str in stack_list_str_to_cycles: + stack_list_str_to_cycles[stack_list_str].append(i) + continue + stack_list_str_to_cycles[stack_list_str] = [i] + used_nodes = set() + used_edges = set() + + for stack in trace[i]: + stack_id = ";".join(stack) + for node_id in path_dict[stack_id]: + used_nodes.add(node_id) + for edge in path_to_edges[stack_id]: + used_edges.add(edge) + stack_list_str_to_used_nodes[stack_list_str] = used_nodes + stack_list_str_to_used_edges[stack_list_str] = used_edges + + sorted_stack_list_items = sorted(stack_list_str_to_cycles.items(), key=(lambda item : len(item[1])), reverse=True) + acc = 0 + rankings_out = open(os.path.join(dot_out_dir, "rankings.csv"), "w") + rankings_out.write("Rank,#Cycles,Cycles-list\n") + for (stack_list_str, cycles) in sorted_stack_list_items: + if acc == 5: + break + acc += 1 + # draw the tree + fpath = os.path.join(dot_out_dir, f"rank{acc}.dot") + with open(fpath, "w") as f: + f.write("digraph rank" + str(acc) + " {\n") + # declare nodes. + for node in all_nodes: + if node in stack_list_str_to_used_nodes[stack_list_str]: + f.write(f'\t{node} [label="{tree_dict[node]}"];\n') + else: + f.write(f'\t{node} [label="{tree_dict[node]}",color="{INVISIBLE}",fontcolor="{INVISIBLE}"];\n') + # write all edges. + for edge in all_edges: + if edge in stack_list_str_to_used_edges[stack_list_str]: + f.write(f'\t{edge} ; \n') + else: + f.write(f'\t{edge} [color="{INVISIBLE}"]; \n') + f.write("}") + + # should write to a txt file what + rankings_out.write(f"{acc},{len(cycles)},{';'.join(str(c) for c in cycles)}\n") + + +# one tree to summarize the entire execution. +def create_aggregate_tree(timeline_map, out_dir, tree_dict, path_dict): + path_to_edges, all_edges = create_edge_dict(path_dict) + + leaf_nodes_dict = {node_id: 0 for node_id in tree_dict} # how many times was this node a leaf? + edges_dict = {} # how many times was this edge active? + + for stack_list in timeline_map.values(): + edges_this_cycle = set() + leaves_this_cycle = set() + stacks_this_cycle = set(map(lambda stack : ";".join(stack), stack_list)) + for stack in stack_list: + stack_id = ";".join(stack) + for edge in path_to_edges[stack_id]: + if edge not in edges_this_cycle: + if edge not in edges_dict: + edges_dict[edge] = 1 + else: + edges_dict[edge] += 1 + edges_this_cycle.add(edge) + # record the leaf node. ignore all primitives as I think we care more about the group that called the primitive (up to debate) + leaf_node = path_dict[stack_id][-1] + if "primitive" in tree_dict[leaf_node]: + leaf_node = path_dict[stack_id][-2] + leaf_id = ";".join(stack[:-1]) + # if the current stack (minus primitive) is a prefix of another stack, then we shouldn't count it in as a leaf node. + contained = False + for other_stack in stacks_this_cycle: + if other_stack != stack_id and leaf_id in other_stack: + contained = True + break + if contained: # this is not actually a leaf node, so we should move onto the next leaf node. + continue + if leaf_node not in leaves_this_cycle: + leaf_nodes_dict[leaf_node] += 1 + leaves_this_cycle.add(leaf_node) + + # write the tree + if not os.path.exists(out_dir): + os.mkdir(out_dir) + with open(os.path.join(out_dir, "aggregate.dot"), "w") as f: + f.write("digraph aggregate {\n") + # declare nodes + for node in leaf_nodes_dict: + if "primitive" in tree_dict[node]: + f.write(f'\t{node} [label="{tree_dict[node]}", style=filled, color="{ACTIVE_PRIMITIVE_COLOR}"];\n') + elif "[" in tree_dict[node] or "main" == tree_dict[node]: + f.write(f'\t{node} [label="{tree_dict[node]} ({leaf_nodes_dict[node]})", style=filled, color="{ACTIVE_CELL_COLOR}"];\n') + else: + f.write(f'\t{node} [label="{tree_dict[node]} ({leaf_nodes_dict[node]})", style=filled, color="{ACTIVE_GROUP_COLOR}"];\n') + # write edges with labels + for edge in edges_dict: + f.write(f'\t{edge} [label="{edges_dict[edge]}"]; \n') + f.write("}") + +def create_path_dot_str_dict(path_dict): + path_to_dot_str = {} # stack list string --> stack path representation on dot file. + + for path_id in path_dict: + path = path_dict[path_id] + path_acc = "" + for node_id in path[0:-1]: + path_acc += f'{node_id} -> ' + path_acc += f'{path[-1]}' + path_to_dot_str[path_id] = path_acc + + return path_to_dot_str + +def create_edge_dict(path_dict): + path_to_edges = {} # stack list string --> [edge string representation] + all_edges = set() + + for path_id in path_dict: + path = path_dict[path_id] + edge_set = [] + for i in range(len(path)-1): + edge = f"{path[i]} -> {path[i+1]}" + edge_set.append(edge) + all_edges.add(edge) + path_to_edges[path_id] = edge_set + + return path_to_edges, list(sorted(all_edges)) + +# create a tree where we divide cycles via par arms +def compute_scaled_flame(trace): + stacks = {} + for i in trace: + num_stacks = len(trace[i]) + cycle_slice = round(1 / num_stacks, 3) + last_cycle_slice = 1 - (cycle_slice * (num_stacks - 1)) + acc = 0 + for stack_list in trace[i]: + stack_id = ";".join(stack_list) + slice_to_add = cycle_slice if acc < num_stacks - 1 else last_cycle_slice + if stack_id not in stacks: + stacks[stack_id] = slice_to_add * SCALED_FLAME_MULTIPLIER + else: + stacks[stack_id] += slice_to_add * SCALED_FLAME_MULTIPLIER + acc += 1 + + return stacks + +def create_flame_groups(trace, flame_out_file, flames_out_dir): + if not os.path.exists(flames_out_dir): + os.mkdir(flames_out_dir) + + # make flame graph folded file + stacks = {} # stack to number of cycles + for i in trace: + for stack_list in trace[i]: + stack_id = ";".join(stack_list) + if stack_id not in stacks: + stacks[stack_id] = 1 + else: + stacks[stack_id] += 1 + + with open(flame_out_file, "w") as flame_out: + for stack in stacks: + flame_out.write(f"{stack} {stacks[stack]}\n") + + scaled_stacks = compute_scaled_flame(trace) + with open(os.path.join(flames_out_dir, "scaled-flame.folded"), "w") as div_flame_out: + for stack in scaled_stacks: + div_flame_out.write(f"{stack} {scaled_stacks[stack]}\n") + +def create_slideshow_dot(timeline_map, dot_out_dir, flame_out_file, flames_out_dir): + + if not os.path.exists(dot_out_dir): + os.mkdir(dot_out_dir) + + # probably wise to not have a billion dot files. + if len(timeline_map) > TREE_PICTURE_LIMIT: + print(f"Simulation exceeds {TREE_PICTURE_LIMIT} cycles, skipping trees...") + return + tree_dict, path_dict = create_tree(timeline_map) + path_to_edges, all_edges = create_edge_dict(path_dict) + + for i in timeline_map: + used_edges = {} + used_paths = set() + used_nodes = set() + all_nodes = set(tree_dict.keys()) + # figure out what nodes are used and what nodes aren't used + for stack in timeline_map[i]: + stack_id = ";".join(stack) + used_paths.add(stack_id) + for node_id in path_dict[stack_id]: + used_nodes.add(node_id) + for edge in path_to_edges[stack_id]: + if edge not in used_edges: + used_edges[edge] = 1 + else: + used_edges[edge] += 1 + + fpath = os.path.join(dot_out_dir, f"cycle{i}.dot") + with open(fpath, "w") as f: + f.write("digraph cycle" + str(i) + " {\n") + # declare nodes. + for node in all_nodes: + if node in used_nodes: + f.write(f'\t{node} [label="{tree_dict[node]}"];\n') + else: + f.write(f'\t{node} [label="{tree_dict[node]}",color="{INVISIBLE}",fontcolor="{INVISIBLE}"];\n') + # write all edges. + for edge in all_edges: + if edge in used_edges.keys(): + f.write(f'\t{edge} ; \n') + else: + f.write(f'\t{edge} [color="{INVISIBLE}"]; \n') + f.write("}") + +def dump_trace(trace, out_dir): + with open(os.path.join(out_dir, "trace.json"), "w") as json_out: + json.dump(trace, json_out, indent = 2) + +def compute_timeline(trace, cells_for_timeline, cells_to_components, main_component, out_dir): + # cells_for_timeline should be a txt file with each line being a cell to display timeline info for. + cells_to_curr_active = {} + cells_to_closed_segments = {} # cell --> [{start: X, end: Y}]. Think [X, Y) + if cells_for_timeline != "": + with open(cells_for_timeline, "r") as ct_file: + for line in ct_file: + cell_to_track = line.strip() + cells_to_curr_active[cell_to_track] = -1 + cells_to_closed_segments[cell_to_track] = [] + else: # get all cells lol + for cell in sorted(cells_to_components.keys(), key=(lambda x : x.count("."))): + if cell != main_component: + cells_to_curr_active[cell] = -1 + cells_to_closed_segments[cell] = [] + # do the most naive thing for now. improve later? + currently_active = set() + for i in trace: + active_this_cycle = set() + for stack in trace[i]: + stack_acc = main_component + for stack_elem in stack: + if " [" in stack_elem: # cell + stack_acc += "." + stack_elem.split(" [")[0] + if stack_acc in cells_to_curr_active: # this is a cell we care about! + active_this_cycle.add(stack_acc) + for nonactive in currently_active.difference(active_this_cycle): # cell that was previously active but no longer is + start_cycle = cells_to_curr_active[nonactive] + cells_to_closed_segments[nonactive].append({"start": start_cycle, "end": i}) + cells_to_curr_active[nonactive] = -1 + for newly_active in active_this_cycle.difference(currently_active): + cells_to_curr_active[newly_active] = i + currently_active = active_this_cycle # retain the current one for next cycle. + for cell in currently_active: # need to close + start_cycle = cells_to_curr_active[cell] + cells_to_closed_segments[cell].append({"start": start_cycle, "end": len(trace)}) + events = [] + # add main on process + thread 1 so we get the full picture. + events.append({"name": main_component, "cat": "main", "ph": "B", "pid": 1, "tid": 1, "ts": 0}) + events.append({"name": main_component, "cat": "main", "ph": "E", "pid": 1, "tid": 1, "ts": len(trace) * ts_multiplier}) + pt_id = 2 + for cell in cells_to_closed_segments: + for closed_segment in cells_to_closed_segments[cell]: + start_event = {"name": cell, "cat": "cell", "ph": "B", "pid" : 1, "tid": pt_id, "ts": closed_segment["start"] * ts_multiplier} # , "sf" : cell_stackframe + events.append(start_event) + end_event = start_event.copy() + end_event["ph"] = "E" + end_event["ts"] = closed_segment["end"] * ts_multiplier + events.append(end_event) + pt_id += 1 + + # write to file + out_path = os.path.join(out_dir, "timeline-dump.json") + with open(out_path, "w", encoding="utf-8") as out_file: + out_file.write(json.dumps({"traceEvents" : events}, indent=4)) + +def write_cell_stats(cell_to_active_cycles, out_dir): + # cell-name,total-cycles,times-active,avg + stats = [] + for cell in cell_to_active_cycles: + total_cycles = 0 + times_active = len(cell_to_active_cycles[cell]) + for elem in cell_to_active_cycles[cell]: + total_cycles += elem["length"] + avg_cycles = round(total_cycles / times_active, 2) + stats.append({"cell-name" : cell, "total-cycles": total_cycles, "times-active": times_active, "avg" : avg_cycles}) + stats.sort(key=lambda e : e["total-cycles"], reverse=True) + fieldnames = ["cell-name", "total-cycles", "times-active", "avg"] + with open(os.path.join(out_dir, "cell-stats.csv"), "w") as csvFile: + writer = csv.DictWriter(csvFile, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(stats) + +def main(vcd_filename, cells_json_file, out_dir, flame_out, cells_for_timeline): + print(f"Start time: {datetime.now()}") + main_component, cells_to_components = read_component_cell_names_json(cells_json_file) + print(f"Start reading VCD: {datetime.now()}") + converter = VCDConverter(main_component, cells_to_components) + vcdvcd.VCDVCD(vcd_filename, callbacks=converter) + print(f"Start Postprocessing VCD: {datetime.now()}") + converter.postprocess() + print(f"End Postprocessing VCD: {datetime.now()}") + print(f"End reading VCD: {datetime.now()}") + + if len(converter.trace) < 100: + for i in converter.trace: + print(i) + for stack in converter.trace[i]: + print(f"\t{stack}") + + tree_dict, path_dict = create_tree(converter.trace) + path_to_edges, all_edges = create_edge_dict(path_dict) + + create_aggregate_tree(converter.trace, out_dir, tree_dict, path_dict) + create_tree_rankings(converter.trace, tree_dict, path_dict, path_to_edges, all_edges, out_dir) + create_flame_groups(converter.trace, flame_out, out_dir) + print(f"Cells for timeline file (will produce a timeline for all cells if empty): {cells_for_timeline}") + compute_timeline(converter.trace, cells_for_timeline, cells_to_components, main_component, out_dir) + print(f"End time: {datetime.now()}") + write_cell_stats(converter.cell_to_active_cycles, out_dir) + +if __name__ == "__main__": + if len(sys.argv) > 4: + vcd_filename = sys.argv[1] + cells_json = sys.argv[2] + out_dir = sys.argv[3] + flame_out = sys.argv[4] + if len(sys.argv) > 5: + cells_for_timeline = sys.argv[5] + else: + cells_for_timeline = "" + main(vcd_filename, cells_json, out_dir, flame_out, cells_for_timeline) + else: + args_desc = [ + "VCD_FILE", + "CELLS_JSON", + "OUT_DIR", + "FLATTENED_FLAME_OUT", + "[CELLS_FOR_TIMELINE]" + ] + print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}") + print("CELLS_JSON: Run the `component_cells` tool") + print("CELLS_FOR_TIMELINE is an optional ") + sys.exit(-1) diff --git a/tools/profiler/run-up-to-tdcc.sh b/tools/profiler/run-up-to-tdcc.sh index 2ae2f7055b..44373c1661 100644 --- a/tools/profiler/run-up-to-tdcc.sh +++ b/tools/profiler/run-up-to-tdcc.sh @@ -9,16 +9,14 @@ fi SCRIPT_DIR=$( cd $( dirname $0 ) && pwd ) CALYX_DIR=$( dirname $( dirname ${SCRIPT_DIR} ) ) -if [ "$2" == "-o" ]; then -( +if [ "$2" == "-no" ]; then + ( cd ${CALYX_DIR} - cargo run $1 -p compile-repeat -p well-formed -p papercut -p canonicalize -p infer-data-path -p collapse-control -p compile-sync-without-sync-reg -p group2seq -p dead-assign-removal -p group2invoke -p infer-share -p inline -p comb-prop -p dead-cell-removal -p cell-share -p simplify-with-control -p compile-invoke -p static-inference -p static-promotion -p compile-repeat -p dead-group-removal -p collapse-control -p static-inline -p merge-assigns -p dead-group-removal -p simplify-static-guards -p add-guard -p static-fsm-opts -p compile-static -p dead-group-removal -p tdcc -) + cargo run $1 -p compile-repeat -p well-formed -p papercut -p canonicalize -p compile-sync -p simplify-with-control -p compile-invoke -p static-inline -p merge-assigns -p dead-group-removal -p simplify-static-guards -p add-guard -p static-fsm-opts -p compile-static -p dead-group-removal -p tdcc + ) else - ( cd ${CALYX_DIR} - cargo run $1 -p compile-repeat -p well-formed -p papercut -p canonicalize -p compile-sync -p simplify-with-control -p compile-invoke -p static-inline -p merge-assigns -p dead-group-removal -p simplify-static-guards -p add-guard -p static-fsm-opts -p compile-static -p dead-group-removal -p tdcc + cargo run $1 -p compile-repeat -p well-formed -p papercut -p canonicalize -p infer-data-path -p collapse-control -p compile-sync-without-sync-reg -p group2seq -p dead-assign-removal -p group2invoke -p infer-share -p inline -p comb-prop -p dead-cell-removal -p cell-share -p simplify-with-control -p compile-invoke -p static-inference -p static-promotion -p compile-repeat -p dead-group-removal -p collapse-control -p static-inline -p merge-assigns -p dead-group-removal -p simplify-static-guards -p add-guard -p static-fsm-opts -p compile-static -p dead-group-removal # -p tdcc ) - fi diff --git a/yxi/axi-calyx/axi-combined-calyx.futil b/yxi/axi-calyx/axi-combined-calyx.futil index d116a9b248..b85c60732e 100644 --- a/yxi/axi-calyx/axi-combined-calyx.futil +++ b/yxi/axi-calyx/axi-combined-calyx.futil @@ -34,7 +34,7 @@ component m_arread_channel( // 2^ARSIZE is bytes used in transfer. For memory-mapped AXI (which is what we // are doing I believe), should match width of data bus (to shell?, so 32 wide? This // is 3'b010) - // see https://docs.xilinx.com/r/en-US/ug1393-vitis-application-acceleration/Kernel-Interface-Requirements + // see https://docs.amd.com/r/en-US/ug1701-vitis-accelerated-embedded/RTL-Kernel-Interface-Requirements // for restrictions ARSIZE: 3, // in AXI4 this is 8 bits, 1-256 transfers in requested transaction. diff --git a/yxi/axi-calyx/axi-generator.py b/yxi/axi-calyx/axi_generator.py similarity index 99% rename from yxi/axi-calyx/axi-generator.py rename to yxi/axi-calyx/axi_generator.py index f17845c18d..1c712542b8 100644 --- a/yxi/axi-calyx/axi-generator.py +++ b/yxi/axi-calyx/axi_generator.py @@ -1,3 +1,7 @@ +# The *original* axi generator which implement a read-compute-write sequence +# to get data in and out of the computational kernel. +# A `dynamic_axi_generator` also exists + from calyx.builder import ( Builder, add_comp_ports, diff --git a/yxi/axi-calyx/dynamic-axi-generator.py b/yxi/axi-calyx/dynamic_axi_generator.py similarity index 86% rename from yxi/axi-calyx/dynamic-axi-generator.py rename to yxi/axi-calyx/dynamic_axi_generator.py index bc7b7c37b0..b84547521e 100644 --- a/yxi/axi-calyx/dynamic-axi-generator.py +++ b/yxi/axi-calyx/dynamic_axi_generator.py @@ -1,11 +1,8 @@ -from calyx.builder import ( - Builder, - add_comp_ports, - invoke, - par, - while_, - if_ -) +# Implements an AXI controller which dynamically reads and writes data +# in and out of the computational kernel as needed. Compare with the +# read-compute-write implementation in the original `axi_generator`. + +from calyx.builder import Builder, add_comp_ports, invoke, par, while_, if_ from typing import Literal from math import log2, ceil import json @@ -17,11 +14,12 @@ width_key = "data_width" size_key = "total_size" name_key = "name" -#This returns an array based on dimensions of memory +# This returns an array based on dimensions of memory address_width_key = "idx_sizes" type_key = "memory_type" -#TODO (nathanielnrn): Should we make these comb groups? + +# TODO (nathanielnrn): Should we make these comb groups? def add_address_translator(prog, mem): address_width = mem[address_width_key][0] data_width = mem[width_key] @@ -32,13 +30,14 @@ def add_address_translator(prog, mem): translator_output = [("axi_address", 64)] add_comp_ports(address_translator, translator_inputs, translator_output) - #Cells - #XRT expects 64 bit address. - address_mult = address_translator.const_mult(64, width_in_bytes(data_width), f"mul_{name}") + # Cells + # XRT expects 64 bit address. + address_mult = address_translator.const_mult( + 64, width_in_bytes(data_width), f"mul_{name}" + ) pad_input_addr = address_translator.pad(address_width, 64, f"pad_input_addr") - - #Assignment + # Assignment with address_translator.continuous: pad_input_addr.in_ = address_translator.this()["calyx_mem_addr"] address_mult.in_ = pad_input_addr.out @@ -109,12 +108,14 @@ def _add_m_to_s_address_channel(prog, mem, prefix: Literal["AW", "AR"]): xhandshake_occurred.write_en = (~xhandshake_occurred.out) @ 1 # Drive output signals for transfer - m_to_s_address_channel.this()[f"{x}ADDR"] = m_to_s_address_channel.this()["axi_address"] + m_to_s_address_channel.this()[f"{x}ADDR"] = m_to_s_address_channel.this()[ + "axi_address" + ] # This is taken from mem size, we assume the databus width is the size # of our memory cell and that width is a power of 2 # TODO(nathanielnrn): convert to binary instead of decimal m_to_s_address_channel.this()[f"{x}SIZE"] = width_xsize(mem[width_key]) - #Dynamic accesses only need asingle transfer per transcation + # Dynamic accesses only need asingle transfer per transcation m_to_s_address_channel.this()[f"{x}LEN"] = 0 m_to_s_address_channel.this()[f"{x}BURST"] = 1 # Must be INCR for XRT # Required by spec, we hardcode to privileged, non-secure, data access @@ -126,12 +127,10 @@ def _add_m_to_s_address_channel(prog, mem, prefix: Literal["AW", "AR"]): bt_reg.write_en = 1 do_x_transfer.done = bt_reg.out - # ARLEN must be between 0-255, make sure to subtract 1 from yxi # size when assigning to ARLEN # assert mem[size_key] < 256, "Memory size must be less than 256" - m_to_s_address_channel.control += [ par( invoke(bt_reg, in_in=0), @@ -223,7 +222,7 @@ def add_read_channel(prog, mem): # Control invoke_n_RLAST = invoke(n_RLAST, in_in=1) # invoke_bt_reg = invoke(bt_reg, in_in=0) - + # Could arguably get rid of this while loop for the dynamic verison, but this # matches nicely with non dynamic version and conforms to spec, # and will be easier to exten to variable length dynamic transfers in the future @@ -241,11 +240,7 @@ def add_write_channel(prog, mem): name = mem[name_key] # Inputs/Outputs write_channel = prog.component(f"m_write_channel_{name}") - channel_inputs = [ - ("ARESETn", 1), - ("WREADY", 1), - ("write_data", data_width) - ] + channel_inputs = [("ARESETn", 1), ("WREADY", 1), ("write_data", data_width)] # TODO(nathanielnrn): We currently assume WDATA is the same width as the # memory. This limits throughput many AXI data busses are much wider # i.e., 512 bits. @@ -291,7 +286,7 @@ def add_write_channel(prog, mem): write_channel.this()["WLAST"] = 1 # done after handshake - #TODO(nathanielnrn): Perhaps we can combine between handshake_occurred and bt_reg + # TODO(nathanielnrn): Perhaps we can combine between handshake_occurred and bt_reg bt_reg.in_ = (wvalid.out & WREADY) @ 1 bt_reg.in_ = ~(wvalid.out & WREADY) @ 0 bt_reg.write_en = 1 @@ -347,6 +342,7 @@ def add_bresp_channel(prog, mem): # Control bresp_channel.control += [invoke(bt_reg, in_in=0), block_transfer] + def add_read_controller(prog, mem): data_width = mem[width_key] name = mem[name_key] @@ -371,18 +367,21 @@ def add_read_controller(prog, mem): (f"ARBURST", 2), (f"ARPROT", 3), (f"RREADY", 1), - #sent out to axi_dyn_mem + # sent out to axi_dyn_mem (f"read_data", data_width), ] add_comp_ports(read_controller, read_controller_inputs, read_controller_outputs) - #Cells - simple_ar_channel = read_controller.cell(f"ar_channel_{name}", prog.get_component(f"m_ar_channel_{name}")) - simple_read_channel = read_controller.cell(f"read_channel_{name}", prog.get_component(f"m_read_channel_{name}")) + # Cells + simple_ar_channel = read_controller.cell( + f"ar_channel_{name}", prog.get_component(f"m_ar_channel_{name}") + ) + simple_read_channel = read_controller.cell( + f"read_channel_{name}", prog.get_component(f"m_read_channel_{name}") + ) # No groups necesarry - # Control # Invokes @@ -416,6 +415,7 @@ def add_read_controller(prog, mem): simple_read_invoke, ] + def add_write_controller(prog, mem): data_width = mem[width_key] name = mem[name_key] @@ -446,13 +446,18 @@ def add_write_controller(prog, mem): add_comp_ports(write_controller, write_controller_inputs, write_controller_outputs) - #Cells - simple_aw_channel = write_controller.cell(f"aw_channel_{name}", prog.get_component(f"m_aw_channel_{name}")) - simple_write_channel = write_controller.cell(f"write_channel_{name}", prog.get_component(f"m_write_channel_{name}")) - simple_bresp_channel = write_controller.cell(f"bresp_channel_{name}", prog.get_component(f"m_bresp_channel_{name}")) + # Cells + simple_aw_channel = write_controller.cell( + f"aw_channel_{name}", prog.get_component(f"m_aw_channel_{name}") + ) + simple_write_channel = write_controller.cell( + f"write_channel_{name}", prog.get_component(f"m_write_channel_{name}") + ) + simple_bresp_channel = write_controller.cell( + f"bresp_channel_{name}", prog.get_component(f"m_bresp_channel_{name}") + ) # No groups necesarry - # Control # Invokes simple_aw_invoke = invoke( @@ -489,6 +494,7 @@ def add_write_controller(prog, mem): simple_bresp_invoke, ] + def add_axi_dyn_mem(prog, mem): address_width = mem[address_width_key][0] data_width = mem[width_key] @@ -497,7 +503,7 @@ def add_axi_dyn_mem(prog, mem): prog.import_("primitives/memories/dyn.futil") axi_dyn_mem = prog.component(f"axi_dyn_mem_{name}") # Inputs/Outputs - dyn_mem_inputs =[ + dyn_mem_inputs = [ ("addr0", address_width, [("write_together", 1), "data"]), ("content_en", 1, [("write_together", 1), ("go", 1)]), ("write_en", 1, [("write_together", 2)]), @@ -537,9 +543,15 @@ def add_axi_dyn_mem(prog, mem): add_comp_ports(axi_dyn_mem, dyn_mem_inputs, dyn_mem_outputs) # Cells - address_translator = axi_dyn_mem.cell(f"address_translator_{name}", prog.get_component(f"address_translator_{name}")) - read_controller = axi_dyn_mem.cell(f"read_controller_{name}", prog.get_component(f"read_controller_{name}")) - write_controller = axi_dyn_mem.cell(f"write_controller_{name}", prog.get_component(f"write_controller_{name}")) + address_translator = axi_dyn_mem.cell( + f"address_translator_{name}", prog.get_component(f"address_translator_{name}") + ) + read_controller = axi_dyn_mem.cell( + f"read_controller_{name}", prog.get_component(f"read_controller_{name}") + ) + write_controller = axi_dyn_mem.cell( + f"write_controller_{name}", prog.get_component(f"write_controller_{name}") + ) # Wires this_component = axi_dyn_mem.this() @@ -548,51 +560,54 @@ def add_axi_dyn_mem(prog, mem): address_translator.calyx_mem_addr = this_component["addr0"] axi_dyn_mem.this()["read_data"] = read_controller.read_data - #Control + # Control read_controller_invoke = invoke( - axi_dyn_mem.get_cell(f"read_controller_{name}"), - in_axi_address=address_translator.axi_address, - in_ARESETn=this_component[f"ARESETn"], - in_ARREADY=this_component[f"ARREADY"], - in_RVALID=this_component[f"RVALID"], - in_RLAST=this_component[f"RLAST"], - in_RDATA=this_component[f"RDATA"], - in_RRESP=this_component[f"RRESP"], - out_ARVALID=this_component[f"ARVALID"], - out_ARADDR=this_component[f"ARADDR"], - out_ARSIZE=this_component[f"ARSIZE"], - out_ARLEN=this_component[f"ARLEN"], - out_ARBURST=this_component[f"ARBURST"], - out_ARPROT=this_component[f"ARPROT"], - out_RREADY=this_component[f"RREADY"], - out_read_data=this_component[f"read_data"], - ) + axi_dyn_mem.get_cell(f"read_controller_{name}"), + in_axi_address=address_translator.axi_address, + in_ARESETn=this_component[f"ARESETn"], + in_ARREADY=this_component[f"ARREADY"], + in_RVALID=this_component[f"RVALID"], + in_RLAST=this_component[f"RLAST"], + in_RDATA=this_component[f"RDATA"], + in_RRESP=this_component[f"RRESP"], + out_ARVALID=this_component[f"ARVALID"], + out_ARADDR=this_component[f"ARADDR"], + out_ARSIZE=this_component[f"ARSIZE"], + out_ARLEN=this_component[f"ARLEN"], + out_ARBURST=this_component[f"ARBURST"], + out_ARPROT=this_component[f"ARPROT"], + out_RREADY=this_component[f"RREADY"], + out_read_data=this_component[f"read_data"], + ) write_controller_invoke = invoke( - axi_dyn_mem.get_cell(f"write_controller_{name}"), - in_axi_address=address_translator.axi_address, - in_write_data=this_component["write_data"], - in_ARESETn=this_component["ARESETn"], - in_AWREADY=this_component["AWREADY"], - in_WREADY=this_component["WREADY"], - in_BVALID=this_component["BVALID"], - out_AWVALID=this_component["AWVALID"], - out_AWADDR=this_component["AWADDR"], - out_AWSIZE=this_component["AWSIZE"], - out_AWLEN=this_component["AWLEN"], - out_AWBURST=this_component["AWBURST"], - out_AWPROT=this_component[f"AWPROT"], - out_WVALID=this_component["WVALID"], - out_WLAST=this_component["WLAST"], - out_WDATA=this_component["WDATA"], - out_BREADY=this_component["BREADY"], + axi_dyn_mem.get_cell(f"write_controller_{name}"), + in_axi_address=address_translator.axi_address, + in_write_data=this_component["write_data"], + in_ARESETn=this_component["ARESETn"], + in_AWREADY=this_component["AWREADY"], + in_WREADY=this_component["WREADY"], + in_BVALID=this_component["BVALID"], + out_AWVALID=this_component["AWVALID"], + out_AWADDR=this_component["AWADDR"], + out_AWSIZE=this_component["AWSIZE"], + out_AWLEN=this_component["AWLEN"], + out_AWBURST=this_component["AWBURST"], + out_AWPROT=this_component[f"AWPROT"], + out_WVALID=this_component["WVALID"], + out_WLAST=this_component["WLAST"], + out_WDATA=this_component["WDATA"], + out_BREADY=this_component["BREADY"], ) - + axi_dyn_mem.control += [ - if_(axi_dyn_mem.this()["write_en"], write_controller_invoke, read_controller_invoke) + if_( + axi_dyn_mem.this()["write_en"], + write_controller_invoke, + read_controller_invoke, + ) ] - # NOTE: Unlike the channel functions, this can expect multiple mems def add_main_comp(prog, mems): @@ -661,7 +676,9 @@ def add_main_comp(prog, mems): # TODO: Don't think these need to be marked external, but we # we need to raise them at some point form original calyx program - axi_mem = wrapper_comp.cell(f"axi_dyn_mem_{mem_name}", prog.get_component(f"axi_dyn_mem_{mem_name}")) + axi_mem = wrapper_comp.cell( + f"axi_dyn_mem_{mem_name}", prog.get_component(f"axi_dyn_mem_{mem_name}") + ) # Wires @@ -675,7 +692,9 @@ def add_main_comp(prog, mems): # Connect wrapper ports with axi_dyn_mem ports # Read controller portion inputs - axi_mem["ARESETn"] = wrapper_comp.this()[f"{mem_name}_ARESETn"] #note that both styles work + axi_mem["ARESETn"] = wrapper_comp.this()[ + f"{mem_name}_ARESETn" + ] # note that both styles work # wrapper_comp.this()[f"{mem_name}_ARESETn"] = axi_mem["ARESETn"] #note that both styles work axi_mem.ARREADY = wrapper_comp.this()[f"{mem_name}_ARREADY"] axi_mem.RVALID = wrapper_comp.this()[f"{mem_name}_RVALID"] @@ -705,7 +724,6 @@ def add_main_comp(prog, mems): wrapper_comp.this()[f"{mem_name}_WDATA"] = axi_mem.WDATA wrapper_comp.this()[f"{mem_name}_BREADY"] = axi_mem.BREADY - # Creates `<mem_name> = internal_mem_<mem_name>` as refs in invocation of `main_compute` ref_mem_kwargs[f"ref_{mem_name}"] = axi_mem @@ -756,7 +774,7 @@ def build(): add_address_translator(prog, mem) add_read_controller(prog, mem) add_write_controller(prog, mem) - add_axi_dyn_mem(prog, mem) #TODO: need one for each mem + add_axi_dyn_mem(prog, mem) # TODO: need one for each mem add_main_comp(prog, mems) return prog.program @@ -771,7 +789,9 @@ def check_mems_wellformed(mems): mem[width_key] ).is_integer(), "Width must be a power of 2 to be correctly described by xSIZE" assert mem[size_key] > 0, "Memory size must be greater than 0" - assert mem[type_key] == "Dynamic", "Only dynamic memories are currently supported for dynamic axi" + assert ( + mem[type_key] == "Dynamic" + ), "Only dynamic memories are currently supported for dynamic axi" if __name__ == "__main__": @@ -789,4 +809,4 @@ def check_mems_wellformed(mems): yxifile = open(yxi_filename) yxi = json.load(yxifile) mems = yxi["memories"] - build().emit() \ No newline at end of file + build().emit() 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 ) ( diff --git a/yxi/xml/xml_generator.py b/yxi/xml/xml_generator.py index b38557831b..3a9068940a 100644 --- a/yxi/xml/xml_generator.py +++ b/yxi/xml/xml_generator.py @@ -7,7 +7,7 @@ """ This file takes in a `.yxi` description and outputs a xml suitable for a `kernel.xml` file can be used to package an xclbin for the Xilinx XRT runtime. -See https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File +See https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/RTL-Kernel-XML-File for the spec this is based on. """ size_key = "total_size" @@ -26,7 +26,7 @@ def gen_xml(yxi): "name": yxi["toplevel"], "language": "ip_c", # TODO: Make sure this matches component.xml, Namely the `Toplevel` part. - # See https://docs.amd.com/r/en-US/ug1393-vitis-application-acceleration/RTL-Kernel-XML-File + # See https://docs.amd.com/r/en-US/ug1702-vitis-accelerated-reference/RTL-Kernel-XML-File "vlnv": "capra.cs.cornell.edu:kernel:Toplevel:1.0", "attributes": "", "preferredWorkGroupSizeMultiple": "0",