forked from libre-chip/fayalite
Compare commits
304 commits
square-bra
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ddb4780fa | |||
| b0e7873a17 | |||
| 1b16118ce5 | |||
| e2ca80af97 | |||
| 30ffd009f6 | |||
| 4bd6db3de8 | |||
| 98e7e91fc9 | |||
| ffca1a279d | |||
| d4d9706798 | |||
| 5d68885eaf | |||
| 31353862ce | |||
| b1116c4a1a | |||
| 6902aea3a6 | |||
| 1880ed682f | |||
| cf3e6cfc6b | |||
| ea183eac87 | |||
| 26224abe1c | |||
| 2266315944 | |||
| 7e9d7739fb | |||
| 7516ec3c24 | |||
| 8e4eeef723 | |||
| 402f457c68 | |||
| 8cff3687f7 | |||
| 80b92c7dd3 | |||
| 2aa41137d4 | |||
| a0b2dc085c | |||
| a8a541b357 | |||
| 52c41bb5db | |||
| a93e66d8ab | |||
| eb3ca59053 | |||
| dbed947408 | |||
| cb4e1f42c0 | |||
| 8c270b0e35 | |||
| c632e5d570 | |||
| 1bc835803b | |||
| 9db3240644 | |||
| caa097db0b | |||
| a96efa9696 | |||
| 4ac1bcbc0a | |||
| 39810043ea | |||
| 26b0dc3fd8 | |||
| 11281a9842 | |||
| e366793204 | |||
| a398f8f185 | |||
| 4fd4371054 | |||
| c97b44d9d6 | |||
| fbe4585578 | |||
| e4210a672f | |||
| e54558d848 | |||
| 46f3519c76 | |||
| 9e803223d0 | |||
| 2a65aa2bd5 | |||
| 2817cd3d58 | |||
| 053c1b2b10 | |||
| 17b58e8edb | |||
| df020e9c9b | |||
| 45fea70c18 | |||
| fbc8ffa5ae | |||
| 26a7090178 | |||
| 0b77d1bea0 | |||
| 840c5e1895 | |||
| c11a1743f9 | |||
| 0be9f9ce23 | |||
| 0b82178740 | |||
| 4b24a88641 | |||
| 094c77e26e | |||
| d2c8b023bf | |||
| c043ee54d0 | |||
| edcc5927a5 | |||
| 7dc4417874 | |||
| 838bd469ce | |||
| b6e4cd0614 | |||
| 3e5b2f126a | |||
| 040cefea21 | |||
| 3267cb38c4 | |||
| b3cc28e2b6 | |||
| 26840daf13 | |||
| 4d9e8d3b47 | |||
| c6feea6d51 | |||
| 409992961c | |||
| 2bdc8a7c72 | |||
| 477a1f2d29 | |||
| 4d54f903be | |||
| 3f5dd61e46 | |||
| def406ab52 | |||
| a565be1b09 | |||
| 676c1e3b7d | |||
| 169be960f8 | |||
| 2b52799f5c | |||
| 35f98f3229 | |||
| 8a63ea89d0 | |||
| 84c5978eaf | |||
| 42e3179a60 | |||
| 53ae3ff670 | |||
| 7af9abfb6f | |||
| aacd05378f | |||
| 908ccef674 | |||
| 057670c12a | |||
| f8ac78abd6 | |||
| 64ec6c0dcc | |||
| c06ef56482 | |||
| db9b1c202c | |||
| d3dd66cbf0 | |||
| b5b1ee866c | |||
| f0e3aef061 | |||
| 6d36698adf | |||
| e7e831cf00 | |||
| 4008c311bf | |||
| ef85d11327 | |||
| ae7c4be9dc | |||
| 65f9ab32f4 | |||
| 67e66ac3bd | |||
| 668e714dc9 | |||
| 88323a8c16 | |||
| 91e1b619e8 | |||
| e2d2d4110b | |||
| b1f9706e4e | |||
| 4eda4366c8 | |||
| 122c08d3cf | |||
| b08a747e20 | |||
| e0c9939147 | |||
| 07725ab489 | |||
| 36f1b9bbb6 | |||
| 9a1b047d2f | |||
| 5967e812a2 | |||
| 001fd31451 | |||
| 57aae7b7fb | |||
| 6929352be7 | |||
| 62058dc141 | |||
| c4b6a0fee6 | |||
| 9092e45447 | |||
| a40eaaa2da | |||
| 5028401a5a | |||
| e0f978fbb6 | |||
| ec3a61513b | |||
| fdc73b5f3b | |||
| a115585d5a | |||
| ab9ff4f2db | |||
| d1bd176b28 | |||
| 920d8d875f | |||
| d453755bb2 | |||
| 450e1004b6 | |||
| c0c5b550bc | |||
| 2fa0ea6192 | |||
| bd75fdfefd | |||
| 50c86e18dc | |||
| 60734cc9d1 | |||
| 3458c21f44 | |||
| 43797db36e | |||
| cdd84953d0 | |||
| 86a1bb46be | |||
| 209d5b5fe1 | |||
| d4ea826051 | |||
| 404a2ee043 | |||
| e3a2ccd41c | |||
| 3771cea78e | |||
| dcf865caec | |||
| 31d01046a8 | |||
| c16726cee6 | |||
| b63676d0ca | |||
| 7005fa3330 | |||
| 2ab8428062 | |||
| 9b06019bf5 | |||
| 36bad52978 | |||
| 21c73051ec | |||
| 304d8da0e8 | |||
| 2af38de900 | |||
| c756aeec70 | |||
| 903ca1bf30 | |||
| 8d030ac65d | |||
| 562c479b62 | |||
| 393f78a14d | |||
| 8616ee4737 | |||
| 5087f16099 | |||
| 6b31e6d515 | |||
| 564ccb30bc | |||
| ca759168ff | |||
| e4cf66adf8 | |||
| cd0dd7b7ee | |||
|
|
2e7d685dc7 | ||
| 9654167ca3 | |||
| 3ed7827485 | |||
| e504cfebfe | |||
| 9f42cab471 | |||
| 259bee39c2 | |||
| 643816d5b5 | |||
| 42afd2da0e | |||
| 15bc304bb6 | |||
| 4422157db8 | |||
| d3f52292a1 | |||
| fd45465d35 | |||
| 5e0548db26 | |||
| 12b3ba57f1 | |||
| 965fe53077 | |||
| 3abba7f9eb | |||
| 6446b71afd | |||
| d36cf92d7f | |||
| d744d85c66 | |||
| 358cdd10c8 | |||
| 9128a84284 | |||
| 546010739a | |||
| 9b5f1218fd | |||
| 89d84551f8 | |||
| c45624e3c2 | |||
| 7851bf545c | |||
| 3e3da53bd2 | |||
| fa50930ff8 | |||
| 9516fe03a1 | |||
| 52ab134673 | |||
| 698b8adc23 | |||
| 59be3bd645 | |||
| 913baa37e9 | |||
| 11ddbc43c7 | |||
| c4b5d00419 | |||
| 09aa9fbc78 | |||
| 288a6b71b9 | |||
| 0095570f19 | |||
| f54e55a143 | |||
| a6e40839ac | |||
| 3106a6fff6 | |||
| f338f37d3e | |||
| 277d3e0d4d | |||
| b288d6f8f2 | |||
| 479d59b287 | |||
| 6f904148c4 | |||
| 3ea0d98924 | |||
|
|
c1f1a8b749 | ||
| 3d5d8c54b6 | |||
| ee15fd2b94 | |||
| 20cf0abbcc | |||
| 5bd0de48b7 | |||
| 0c9c48a066 | |||
| cb17913004 | |||
| 42effd1132 | |||
| 3d0f95cfe5 | |||
| 3939ce2360 | |||
| d0229fbcfb | |||
| 4909724995 | |||
| d0694cbd52 | |||
| 1a2149b040 | |||
| 59cef3f398 | |||
| bf907c3872 | |||
| 99180eb3b4 | |||
| 017c14a2f1 | |||
| ed1aea41f3 | |||
| f12322aa2a | |||
| 44ca1a607a | |||
| 30b9a5e48d | |||
| eed0afc6ab | |||
| aec383c0af | |||
| f403eed7c0 | |||
| 2e8b73d2fc | |||
| e05c368688 | |||
| ec77559e2b | |||
| b7f1101164 | |||
| 487af07154 | |||
| c0d4de56a9 | |||
| 9f154e6b96 | |||
| 0d54b9a2a9 | |||
| 343805f80b | |||
| 15a28aa7a7 | |||
| 4084a70485 | |||
| 3e2fb9b94f | |||
| bc26fe32fd | |||
| eb65bec26e | |||
| 4497f09ea0 | |||
| 1c63a441a9 | |||
| 0cf01600b3 | |||
| f3d6528f5b | |||
| f35d88d2bb | |||
| e8c393f3bb | |||
| d0b406d288 | |||
| 2a25dd9d7b | |||
| 6e0b6c000d | |||
| d089095667 | |||
| 9d66fcc548 | |||
| 186488a82e | |||
| edcea1adc3 | |||
| 30a38bc8da | |||
| 1e2831da47 | |||
| d2ba313f0f | |||
| 04752c5037 | |||
| e661aeab11 | |||
| 5fc7dbd6e9 | |||
| 45dbb554d0 | |||
| bb860d54cc | |||
| efc3a539ed | |||
| f32c0a7863 | |||
| 4ff01690a7 | |||
| 28aad19bf5 | |||
| 716c65edcd | |||
| f6146048d1 | |||
| a701f99fd6 | |||
| 78edfc97b2 | |||
| 9ad4ec0f39 | |||
| 8449854cac | |||
| 790bb15408 | |||
| bdbc6d89bd | |||
| 10ae95fac1 | |||
| 053391b010 | |||
| 51ce7b079e | |||
| ff269e5def | |||
| df55a514e4 | |||
| ff94dda922 |
231 changed files with 198215 additions and 4288 deletions
|
|
@ -1,19 +1,28 @@
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
# See Notices.txt for copyright information
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: debian-12
|
runs-on: debian-12
|
||||||
|
container:
|
||||||
|
image: git.libre-chip.org/libre-chip/fayalite-deps:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: https://code.forgejo.org/actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: |
|
- run: |
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1
|
scripts/check-copyright.sh
|
||||||
source "$HOME/.cargo/env"
|
- uses: https://git.libre-chip.org/mirrors/rust-cache@v2
|
||||||
echo "$PATH" >> "$GITHUB_PATH"
|
|
||||||
- uses: https://github.com/Swatinem/rust-cache@v2
|
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/master' }}
|
save-if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
|
- run: rustup override set 1.93.0
|
||||||
|
- run: rustup component add rust-src
|
||||||
|
- run: make -C rocq-demo
|
||||||
- run: cargo test
|
- run: cargo test
|
||||||
- run: cargo test --features=unstable-doc
|
- run: cargo build --tests --features=unstable-doc
|
||||||
|
- run: cargo test --doc --features=unstable-doc
|
||||||
- run: cargo doc --features=unstable-doc
|
- run: cargo doc --features=unstable-doc
|
||||||
|
- run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher
|
||||||
|
- run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out
|
||||||
|
- run: cargo run --example tx_only_uart yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/tx_only_uart-out
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,4 @@
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
# See Notices.txt for copyright information
|
||||||
/target
|
/target
|
||||||
.vscode
|
.vscode
|
||||||
382
Cargo.lock
generated
382
Cargo.lock
generated
|
|
@ -1,18 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.8.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
|
|
@ -37,9 +25,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.7"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
|
|
@ -56,7 +44,7 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -66,9 +54,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -81,6 +81,12 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "basic-toml"
|
name = "basic-toml"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
|
@ -109,6 +115,20 @@ dependencies = [
|
||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
|
@ -118,6 +138,15 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.1.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
@ -126,9 +155,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.9"
|
version = "4.5.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
|
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -136,9 +165,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.9"
|
version = "4.5.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
|
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -147,10 +176,19 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_complete"
|
||||||
version = "4.5.8"
|
version = "4.5.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
|
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -160,9 +198,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.1"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
|
|
@ -170,6 +208,12 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
|
|
@ -189,6 +233,27 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_destructure2"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -218,7 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -239,32 +304,43 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite"
|
name = "fayalite"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
"ctor",
|
||||||
"eyre",
|
"eyre",
|
||||||
"fayalite-proc-macros",
|
"fayalite-proc-macros",
|
||||||
"fayalite-visit-gen",
|
"fayalite-visit-gen",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"jobslot",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"ordered-float",
|
||||||
|
"petgraph",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"tempfile",
|
||||||
"trybuild",
|
"trybuild",
|
||||||
|
"vec_map",
|
||||||
"which",
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-proc-macros"
|
name = "fayalite-proc-macros"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fayalite-proc-macros-impl",
|
"fayalite-proc-macros-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-proc-macros-impl"
|
name = "fayalite-proc-macros-impl"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
|
|
@ -278,7 +354,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fayalite-visit-gen"
|
name = "fayalite-visit-gen"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
|
|
@ -290,6 +366,18 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
@ -306,6 +394,18 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
@ -314,12 +414,13 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.3"
|
version = "0.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -334,7 +435,7 @@ version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -345,9 +446,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.6"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
|
@ -367,10 +468,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "jobslot"
|
||||||
version = "0.2.153"
|
version = "0.2.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"derive_destructure2",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"scopeguard",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.176"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
|
|
@ -380,11 +495,10 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.4"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
@ -409,9 +523,32 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.19.0"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "5.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
|
|
@ -425,9 +562,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.83"
|
version = "1.0.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
|
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -441,12 +578,37 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radium"
|
name = "radium"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.31"
|
version = "0.38.31"
|
||||||
|
|
@ -457,7 +619,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -466,6 +628,12 @@ version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.202"
|
version = "1.0.202"
|
||||||
|
|
@ -509,6 +677,12 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
@ -517,9 +691,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.66"
|
version = "2.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -541,7 +715,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -606,12 +780,36 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vec_map"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.14.7+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||||
|
dependencies = [
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "6.0.1"
|
version = "6.0.1"
|
||||||
|
|
@ -655,6 +853,12 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
@ -665,14 +869,24 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-sys"
|
||||||
version = "0.52.4"
|
version = "0.61.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm",
|
||||||
|
|
@ -681,45 +895,51 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winsafe"
|
name = "winsafe"
|
||||||
|
|
@ -727,6 +947,12 @@ version = "0.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -735,23 +961,3 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.7.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.7.32"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
32
Cargo.toml
32
Cargo.toml
|
|
@ -5,31 +5,43 @@ resolver = "2"
|
||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
license = "LGPL-3.0-or-later"
|
license = "LGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.libre-chip.org/libre-chip/fayalite"
|
repository = "https://git.libre-chip.org/libre-chip/fayalite"
|
||||||
keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"]
|
keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"]
|
||||||
categories = ["simulation", "development-tools", "compilers"]
|
categories = ["simulation", "development-tools", "compilers"]
|
||||||
rust-version = "1.80.1"
|
rust-version = "1.93.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
fayalite-proc-macros = { version = "=0.2.0", path = "crates/fayalite-proc-macros" }
|
fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros" }
|
||||||
fayalite-proc-macros-impl = { version = "=0.2.0", path = "crates/fayalite-proc-macros-impl" }
|
fayalite-proc-macros-impl = { version = "=0.3.0", path = "crates/fayalite-proc-macros-impl" }
|
||||||
fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" }
|
fayalite-visit-gen = { version = "=0.3.0", path = "crates/fayalite-visit-gen" }
|
||||||
base16ct = "0.2.0"
|
base16ct = "0.2.0"
|
||||||
|
base64 = "0.22.1"
|
||||||
bitvec = { version = "1.0.1", features = ["serde"] }
|
bitvec = { version = "1.0.1", features = ["serde"] }
|
||||||
hashbrown = "0.14.3"
|
blake3 = { version = "1.5.4", features = ["serde"] }
|
||||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
clap = { version = "4.5.9", features = ["derive", "env", "string"] }
|
||||||
num-bigint = "0.4.4"
|
clap_complete = "4.5.58"
|
||||||
|
ctor = "0.2.8"
|
||||||
|
eyre = "0.6.12"
|
||||||
|
hashbrown = "0.15.2"
|
||||||
|
indexmap = { version = "2.5.0", features = ["serde"] }
|
||||||
|
jobslot = "0.2.23"
|
||||||
|
num-bigint = "0.4.6"
|
||||||
num-traits = "0.2.16"
|
num-traits = "0.2.16"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
ordered-float = { version = "5.1.0", features = ["serde"] }
|
||||||
|
petgraph = "0.8.1"
|
||||||
prettyplease = "0.2.20"
|
prettyplease = "0.2.20"
|
||||||
proc-macro2 = "1.0.83"
|
proc-macro2 = "1.0.83"
|
||||||
quote = "1.0.36"
|
quote = "1.0.36"
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"] }
|
syn = { version = "2.0.93", features = ["full", "fold", "visit", "extra-traits"] }
|
||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
trybuild = "1.0"
|
trybuild = "1.0"
|
||||||
|
vec_map = "0.8.2"
|
||||||
|
which = "6.0.1"
|
||||||
|
|
|
||||||
79
README.md
79
README.md
|
|
@ -1,5 +1,84 @@
|
||||||
|
<!--
|
||||||
|
SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
See Notices.txt for copyright information
|
||||||
|
-->
|
||||||
# Fayalite
|
# Fayalite
|
||||||
|
|
||||||
Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/).
|
Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/).
|
||||||
|
|
||||||
[FIRRTL]: https://github.com/chipsalliance/firrtl-spec
|
[FIRRTL]: https://github.com/chipsalliance/firrtl-spec
|
||||||
|
|
||||||
|
# Building the [Blinky example] for the Arty A7 100T on Linux
|
||||||
|
|
||||||
|
[Blinky example]: crates/fayalite/examples/blinky.rs
|
||||||
|
|
||||||
|
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in <https://git.libre-chip.org/libre-chip/fayalite-deps>
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
|
||||||
|
Install podman (or docker).
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out
|
||||||
|
```
|
||||||
|
|
||||||
|
To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS:
|
||||||
|
|
||||||
|
[openFPGALoader]: https://github.com/trabucayre/openFPGALoader
|
||||||
|
|
||||||
|
On Debian 12:
|
||||||
|
```bash
|
||||||
|
sudo apt update && sudo apt install openfpgaloader
|
||||||
|
```
|
||||||
|
|
||||||
|
Then program the FPGA:
|
||||||
|
```bash
|
||||||
|
sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit
|
||||||
|
```
|
||||||
|
|
||||||
|
This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled.
|
||||||
|
|
||||||
|
To program the Flash also, so it stays programmed when power-cycling the board:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building the [Transmit-only UART example] for the Arty A7 100T on Linux
|
||||||
|
|
||||||
|
[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs
|
||||||
|
|
||||||
|
Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`.
|
||||||
|
|
||||||
|
View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`.
|
||||||
|
|
||||||
|
Find the correct USB device:
|
||||||
|
```bash
|
||||||
|
sudo tio --list
|
||||||
|
```
|
||||||
|
|
||||||
|
You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port):
|
||||||
|
`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0`
|
||||||
|
|
||||||
|
Connect to the serial port:
|
||||||
|
```bash
|
||||||
|
sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see (repeating endlessly):
|
||||||
|
```text
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
Hello World from Fayalite!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
Press Ctrl+T then `q` to exit tio.
|
||||||
|
|
||||||
|
# Funding
|
||||||
|
|
||||||
|
## NLnet Grants
|
||||||
|
|
||||||
|
* [Libre-Chip CPU with proof of No Spectre bugs](https://nlnet.nl/project/Libre-Chip-proof/) 2024-12-324 [(progress)](https://git.libre-chip.org/libre-chip/grant-tracking/src/branch/master/nlnet-2024-12-324/progress.md)
|
||||||
|
|
||||||
|
This project was funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) programme, under the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement № [101135429](https://cordis.europa.eu/project/id/101135429). Additional funding is made available by the [Swiss State Secretariat for Education, Research and Innovation](https://www.sbfi.admin.ch/sbfi/en/home.html) (SERI).
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base16ct = { workspace = true }
|
base16ct.workspace = true
|
||||||
num-bigint = { workspace = true }
|
num-bigint.workspace = true
|
||||||
prettyplease = { workspace = true }
|
prettyplease.workspace = true
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2.workspace = true
|
||||||
quote = { workspace = true }
|
quote.workspace = true
|
||||||
sha2 = { workspace = true }
|
sha2.workspace = true
|
||||||
syn = { workspace = true }
|
syn.workspace = true
|
||||||
tempfile = { workspace = true }
|
tempfile.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,7 @@ forward_fold!(syn::ExprArray => fold_expr_array);
|
||||||
forward_fold!(syn::ExprCall => fold_expr_call);
|
forward_fold!(syn::ExprCall => fold_expr_call);
|
||||||
forward_fold!(syn::ExprIf => fold_expr_if);
|
forward_fold!(syn::ExprIf => fold_expr_if);
|
||||||
forward_fold!(syn::ExprMatch => fold_expr_match);
|
forward_fold!(syn::ExprMatch => fold_expr_match);
|
||||||
|
forward_fold!(syn::ExprMethodCall => fold_expr_method_call);
|
||||||
forward_fold!(syn::ExprPath => fold_expr_path);
|
forward_fold!(syn::ExprPath => fold_expr_path);
|
||||||
forward_fold!(syn::ExprRepeat => fold_expr_repeat);
|
forward_fold!(syn::ExprRepeat => fold_expr_repeat);
|
||||||
forward_fold!(syn::ExprStruct => fold_expr_struct);
|
forward_fold!(syn::ExprStruct => fold_expr_struct);
|
||||||
|
|
@ -256,5 +257,6 @@ no_op_fold!(syn::Token![let]);
|
||||||
no_op_fold!(syn::Token![mut]);
|
no_op_fold!(syn::Token![mut]);
|
||||||
no_op_fold!(syn::Token![static]);
|
no_op_fold!(syn::Token![static]);
|
||||||
no_op_fold!(syn::Token![struct]);
|
no_op_fold!(syn::Token![struct]);
|
||||||
|
no_op_fold!(syn::Token![type]);
|
||||||
no_op_fold!(syn::Token![where]);
|
no_op_fold!(syn::Token![where]);
|
||||||
no_op_fold!(usize);
|
no_op_fold!(usize);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
519
crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs
Normal file
519
crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs
Normal file
|
|
@ -0,0 +1,519 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
use crate::{
|
||||||
|
Errors, HdlAttr,
|
||||||
|
hdl_type_common::{
|
||||||
|
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType,
|
||||||
|
PhantomConstGetBound, TypesParser, WrappedInConst, common_derives, get_target, known_items,
|
||||||
|
},
|
||||||
|
kw,
|
||||||
|
};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, format_ident, quote_spanned};
|
||||||
|
use syn::{
|
||||||
|
Attribute, Expr, Fields, GenericParam, Generics, Ident, ItemStruct, ItemType, Token, Type,
|
||||||
|
TypeGroup, TypeParam, TypeParen, Visibility, parse_quote_spanned, punctuated::Pair,
|
||||||
|
token::Paren,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct PhantomConstAccessorTypeParam {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
ident: Ident,
|
||||||
|
colon_token: Token![:],
|
||||||
|
phantom_const_get_bound: PhantomConstGetBound,
|
||||||
|
plus_token: Option<Token![+]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorTypeParam> for TypeParam {
|
||||||
|
fn from(value: PhantomConstAccessorTypeParam) -> Self {
|
||||||
|
let PhantomConstAccessorTypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
phantom_const_get_bound,
|
||||||
|
plus_token,
|
||||||
|
} = value;
|
||||||
|
TypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token: Some(colon_token),
|
||||||
|
bounds: FromIterator::from_iter([Pair::new(
|
||||||
|
phantom_const_get_bound.into(),
|
||||||
|
plus_token,
|
||||||
|
)]),
|
||||||
|
eq_token: None,
|
||||||
|
default: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorTypeParam> for GenericParam {
|
||||||
|
fn from(value: PhantomConstAccessorTypeParam) -> Self {
|
||||||
|
TypeParam::from(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstAccessorTypeParam {
|
||||||
|
fn parse_opt(generic_param: GenericParam) -> Option<Self> {
|
||||||
|
let GenericParam::Type(TypeParam {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
bounds,
|
||||||
|
eq_token: None,
|
||||||
|
default: None,
|
||||||
|
}) = generic_param
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let colon_token = colon_token.unwrap_or(Token));
|
||||||
|
let mut bounds = bounds.into_pairs();
|
||||||
|
let (bound, plus_token) = bounds.next()?.into_tuple();
|
||||||
|
let phantom_const_get_bound = PhantomConstGetBound::parse_type_param_bound(bound)
|
||||||
|
.ok()?
|
||||||
|
.ok()?;
|
||||||
|
let None = bounds.next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
attrs,
|
||||||
|
ident,
|
||||||
|
colon_token,
|
||||||
|
phantom_const_get_bound,
|
||||||
|
plus_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct PhantomConstAccessorGenerics {
|
||||||
|
lt_token: Token![<],
|
||||||
|
type_param: PhantomConstAccessorTypeParam,
|
||||||
|
comma_token: Option<Token![,]>,
|
||||||
|
gt_token: Token![>],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PhantomConstAccessorGenerics> for Generics {
|
||||||
|
fn from(value: PhantomConstAccessorGenerics) -> Self {
|
||||||
|
let PhantomConstAccessorGenerics {
|
||||||
|
lt_token,
|
||||||
|
type_param,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
} = value;
|
||||||
|
Generics {
|
||||||
|
lt_token: Some(lt_token),
|
||||||
|
params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]),
|
||||||
|
gt_token: Some(gt_token),
|
||||||
|
where_clause: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics {
|
||||||
|
fn from(value: &'a PhantomConstAccessorGenerics) -> Self {
|
||||||
|
value.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstAccessorGenerics {
|
||||||
|
fn parse_opt(generics: Generics) -> Option<Self> {
|
||||||
|
let Generics {
|
||||||
|
lt_token,
|
||||||
|
params,
|
||||||
|
gt_token,
|
||||||
|
where_clause: None,
|
||||||
|
} = generics
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let mut params = params.into_pairs();
|
||||||
|
let (generic_param, comma_token) = params.next()?.into_tuple();
|
||||||
|
let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?;
|
||||||
|
let span = type_param.ident.span();
|
||||||
|
let lt_token = lt_token.unwrap_or(Token);
|
||||||
|
let gt_token = gt_token.unwrap_or(Token);
|
||||||
|
let None = params.next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
lt_token,
|
||||||
|
type_param,
|
||||||
|
comma_token,
|
||||||
|
gt_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) enum ParsedTypeAlias {
|
||||||
|
TypeAlias {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
vis: Visibility,
|
||||||
|
type_token: Token![type],
|
||||||
|
ident: Ident,
|
||||||
|
generics: MaybeParsed<ParsedGenerics, Generics>,
|
||||||
|
eq_token: Token![=],
|
||||||
|
ty: MaybeParsed<ParsedType, Type>,
|
||||||
|
semi_token: Token![;],
|
||||||
|
},
|
||||||
|
PhantomConstAccessor {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
get: (kw::get, Paren, Expr),
|
||||||
|
vis: Visibility,
|
||||||
|
type_token: Token![type],
|
||||||
|
ident: Ident,
|
||||||
|
generics: PhantomConstAccessorGenerics,
|
||||||
|
eq_token: Token![=],
|
||||||
|
ty: Type,
|
||||||
|
ty_is_dyn_size: Option<known_items::DynSize>,
|
||||||
|
semi_token: Token![;],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParsedTypeAlias {
|
||||||
|
fn ty_is_dyn_size(ty: &Type) -> Option<known_items::DynSize> {
|
||||||
|
match ty {
|
||||||
|
Type::Group(TypeGroup {
|
||||||
|
group_token: _,
|
||||||
|
elem,
|
||||||
|
}) => Self::ty_is_dyn_size(elem),
|
||||||
|
Type::Paren(TypeParen {
|
||||||
|
paren_token: _,
|
||||||
|
elem,
|
||||||
|
}) => Self::ty_is_dyn_size(elem),
|
||||||
|
Type::Path(syn::TypePath { qself: None, path }) => {
|
||||||
|
known_items::DynSize::parse_path(path.clone()).ok()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn parse_phantom_const_accessor(
|
||||||
|
item: ItemType,
|
||||||
|
mut errors: Errors,
|
||||||
|
options: HdlAttr<ItemOptions, kw::hdl>,
|
||||||
|
get: (kw::get, Paren, Expr),
|
||||||
|
) -> syn::Result<Self> {
|
||||||
|
let ItemType {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
} = item;
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
ref target,
|
||||||
|
custom_bounds,
|
||||||
|
no_static,
|
||||||
|
no_runtime_generics,
|
||||||
|
cmp_eq,
|
||||||
|
get: _,
|
||||||
|
ref custom_debug,
|
||||||
|
custom_sim_display,
|
||||||
|
} = options.body;
|
||||||
|
if let Some((no_static,)) = no_static {
|
||||||
|
errors.error(no_static, "no_static is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((target, ..)) = target {
|
||||||
|
errors.error(
|
||||||
|
target,
|
||||||
|
"target is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((no_runtime_generics,)) = no_runtime_generics {
|
||||||
|
errors.error(
|
||||||
|
no_runtime_generics,
|
||||||
|
"no_runtime_generics is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((cmp_eq,)) = cmp_eq {
|
||||||
|
errors.error(cmp_eq, "cmp_eq is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((custom_debug, _, _)) = custom_debug {
|
||||||
|
errors.error(custom_debug, "custom_debug is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((custom_sim_display,)) = custom_sim_display {
|
||||||
|
errors.error(
|
||||||
|
custom_sim_display,
|
||||||
|
"custom_sim_display is not valid on type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((custom_bounds,)) = custom_bounds {
|
||||||
|
errors.error(
|
||||||
|
custom_bounds,
|
||||||
|
"custom_bounds is not implemented on PhantomConstGet type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else {
|
||||||
|
errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter<P: PhantomConstGet<MyType>> = RetType;");
|
||||||
|
errors.finish()?;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
errors.finish()?;
|
||||||
|
let ty_is_dyn_size = Self::ty_is_dyn_size(&ty);
|
||||||
|
Ok(Self::PhantomConstAccessor {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
get,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty: *ty,
|
||||||
|
ty_is_dyn_size,
|
||||||
|
semi_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn parse(item: ItemType) -> syn::Result<Self> {
|
||||||
|
let ItemType {
|
||||||
|
mut attrs,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
mut generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
} = item;
|
||||||
|
let mut errors = Errors::new();
|
||||||
|
let mut options = errors
|
||||||
|
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
|
||||||
|
&mut attrs,
|
||||||
|
))
|
||||||
|
.unwrap_or_default();
|
||||||
|
errors.ok(options.body.validate());
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
target: _,
|
||||||
|
custom_bounds,
|
||||||
|
no_static,
|
||||||
|
no_runtime_generics: _,
|
||||||
|
cmp_eq,
|
||||||
|
ref mut get,
|
||||||
|
ref custom_debug,
|
||||||
|
custom_sim_display,
|
||||||
|
} = options.body;
|
||||||
|
if let Some(get) = get.take() {
|
||||||
|
return Self::parse_phantom_const_accessor(
|
||||||
|
ItemType {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
},
|
||||||
|
errors,
|
||||||
|
options,
|
||||||
|
get,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some((no_static,)) = no_static {
|
||||||
|
errors.error(no_static, "no_static is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((cmp_eq,)) = cmp_eq {
|
||||||
|
errors.error(cmp_eq, "cmp_eq is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((custom_debug, _, _)) = custom_debug {
|
||||||
|
errors.error(custom_debug, "custom_debug is not valid on type aliases");
|
||||||
|
}
|
||||||
|
if let Some((custom_sim_display,)) = custom_sim_display {
|
||||||
|
errors.error(
|
||||||
|
custom_sim_display,
|
||||||
|
"custom_sim_display is not valid on type aliases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let generics = if custom_bounds.is_some() {
|
||||||
|
MaybeParsed::Unrecognized(generics)
|
||||||
|
} else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) {
|
||||||
|
MaybeParsed::Parsed(generics)
|
||||||
|
} else {
|
||||||
|
MaybeParsed::Unrecognized(generics)
|
||||||
|
};
|
||||||
|
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
|
||||||
|
errors.finish()?;
|
||||||
|
Ok(Self::TypeAlias {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ParsedTypeAlias {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
Self::TypeAlias {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
semi_token,
|
||||||
|
} => {
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
target,
|
||||||
|
custom_bounds: _,
|
||||||
|
no_static: _,
|
||||||
|
no_runtime_generics,
|
||||||
|
cmp_eq: _,
|
||||||
|
get: _,
|
||||||
|
custom_debug: _,
|
||||||
|
custom_sim_display: _,
|
||||||
|
} = &options.body;
|
||||||
|
let target = get_target(target, ident);
|
||||||
|
let mut type_attrs = attrs.clone();
|
||||||
|
type_attrs.push(parse_quote_spanned! {ident.span()=>
|
||||||
|
#[allow(type_alias_bounds)]
|
||||||
|
});
|
||||||
|
ItemType {
|
||||||
|
attrs: type_attrs,
|
||||||
|
vis: vis.clone(),
|
||||||
|
type_token: *type_token,
|
||||||
|
ident: ident.clone(),
|
||||||
|
generics: generics.into(),
|
||||||
|
eq_token: *eq_token,
|
||||||
|
ty: Box::new(ty.clone().into()),
|
||||||
|
semi_token: *semi_token,
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) =
|
||||||
|
(generics, ty, no_runtime_generics)
|
||||||
|
{
|
||||||
|
generics.make_runtime_generics(tokens, vis, ident, &target, |context| {
|
||||||
|
ty.make_hdl_type_expr(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::PhantomConstAccessor {
|
||||||
|
attrs,
|
||||||
|
options,
|
||||||
|
get: (_get_kw, _get_paren, get_expr),
|
||||||
|
vis,
|
||||||
|
type_token,
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
eq_token,
|
||||||
|
ty,
|
||||||
|
ty_is_dyn_size,
|
||||||
|
semi_token,
|
||||||
|
} => {
|
||||||
|
let ItemOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
target: _,
|
||||||
|
custom_bounds: _,
|
||||||
|
no_static: _,
|
||||||
|
no_runtime_generics: _,
|
||||||
|
cmp_eq: _,
|
||||||
|
get: _,
|
||||||
|
custom_debug: _,
|
||||||
|
custom_sim_display: _,
|
||||||
|
} = &options.body;
|
||||||
|
let span = ident.span();
|
||||||
|
let mut type_attrs = attrs.clone();
|
||||||
|
type_attrs.push(parse_quote_spanned! {span=>
|
||||||
|
#[allow(type_alias_bounds)]
|
||||||
|
});
|
||||||
|
let type_param_ident = &generics.type_param.ident;
|
||||||
|
let syn_generics = Generics::from(generics);
|
||||||
|
ItemType {
|
||||||
|
attrs: type_attrs,
|
||||||
|
vis: vis.clone(),
|
||||||
|
type_token: *type_token,
|
||||||
|
ident: ident.clone(),
|
||||||
|
generics: syn_generics.clone(),
|
||||||
|
eq_token: *eq_token,
|
||||||
|
ty: parse_quote_spanned! {span=>
|
||||||
|
<#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type
|
||||||
|
},
|
||||||
|
semi_token: *semi_token,
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
let generics_accumulation_ident =
|
||||||
|
format_ident!("__{}__GenericsAccumulation", ident);
|
||||||
|
ItemStruct {
|
||||||
|
attrs: vec![
|
||||||
|
common_derives(span, true),
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vis: vis.clone(),
|
||||||
|
struct_token: Token,
|
||||||
|
ident: generics_accumulation_ident.clone(),
|
||||||
|
generics: Generics::default(),
|
||||||
|
fields: Fields::Unnamed(parse_quote_spanned! {span=>
|
||||||
|
(())
|
||||||
|
}),
|
||||||
|
semi_token: Some(Token),
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(non_upper_case_globals, dead_code)]
|
||||||
|
#vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(());
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
|
||||||
|
let tokens = wrapped_in_const.inner();
|
||||||
|
let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl();
|
||||||
|
let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty;
|
||||||
|
let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size {
|
||||||
|
known_items::usize(ty_is_dyn_size.span).to_token_stream()
|
||||||
|
} else {
|
||||||
|
ty.to_token_stream()
|
||||||
|
};
|
||||||
|
quote_spanned! {span=>
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
#[automatically_derived]
|
||||||
|
impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident>
|
||||||
|
for #generics_accumulation_ident
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
type Output = #index_output;
|
||||||
|
|
||||||
|
fn index(&self, __param: #type_param_ident) -> &Self::Output {
|
||||||
|
::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>(
|
||||||
|
__param,
|
||||||
|
#get_expr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
|
||||||
|
let item = ParsedTypeAlias::parse(item)?;
|
||||||
|
let outline_generated = match &item {
|
||||||
|
ParsedTypeAlias::TypeAlias { options, .. }
|
||||||
|
| ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated,
|
||||||
|
};
|
||||||
|
let mut contents = item.to_token_stream();
|
||||||
|
if outline_generated.is_some() {
|
||||||
|
contents = crate::outline_generated(contents, "hdl-type-alias-");
|
||||||
|
}
|
||||||
|
Ok(contents)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,26 +2,47 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
#![cfg_attr(test, recursion_limit = "512")]
|
#![cfg_attr(test, recursion_limit = "512")]
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{ToTokens, quote};
|
||||||
use std::io::{ErrorKind, Write};
|
use std::{
|
||||||
|
collections::{HashMap, hash_map::Entry},
|
||||||
|
io::{ErrorKind, Write},
|
||||||
|
};
|
||||||
use syn::{
|
use syn::{
|
||||||
bracketed, parenthesized,
|
AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, bracketed,
|
||||||
|
ext::IdentExt,
|
||||||
|
parenthesized,
|
||||||
parse::{Parse, ParseStream, Parser},
|
parse::{Parse, ParseStream, Parser},
|
||||||
parse_quote,
|
parse_quote,
|
||||||
punctuated::Pair,
|
punctuated::{Pair, Punctuated},
|
||||||
AttrStyle, Attribute, Error, Item, Token,
|
spanned::Spanned,
|
||||||
|
token::{Bracket, Paren},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod fold;
|
mod fold;
|
||||||
mod hdl_bundle;
|
mod hdl_bundle;
|
||||||
mod hdl_enum;
|
mod hdl_enum;
|
||||||
|
mod hdl_type_alias;
|
||||||
mod hdl_type_common;
|
mod hdl_type_common;
|
||||||
mod module;
|
mod module;
|
||||||
//mod value_derive_common;
|
mod process_cfg;
|
||||||
//mod value_derive_struct;
|
|
||||||
|
pub(crate) trait CustomToken:
|
||||||
|
Copy
|
||||||
|
+ Spanned
|
||||||
|
+ ToTokens
|
||||||
|
+ std::fmt::Debug
|
||||||
|
+ Eq
|
||||||
|
+ std::hash::Hash
|
||||||
|
+ Default
|
||||||
|
+ quote::IdentFragment
|
||||||
|
+ Parse
|
||||||
|
{
|
||||||
|
const IDENT_STR: &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
mod kw {
|
mod kw {
|
||||||
pub(crate) use syn::token::Extern as extern_;
|
pub(crate) use syn::token::Extern as extern_;
|
||||||
|
pub(crate) use syn::token::Type as type_;
|
||||||
|
|
||||||
macro_rules! custom_keyword {
|
macro_rules! custom_keyword {
|
||||||
($kw:ident) => {
|
($kw:ident) => {
|
||||||
|
|
@ -38,28 +59,47 @@ mod kw {
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::fold::no_op_fold!($kw);
|
crate::fold::no_op_fold!($kw);
|
||||||
|
|
||||||
|
impl crate::CustomToken for $kw {
|
||||||
|
const IDENT_STR: &'static str = stringify!($kw);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom_keyword!(__evaluated_cfgs);
|
||||||
|
custom_keyword!(add_platform_io);
|
||||||
|
custom_keyword!(all);
|
||||||
|
custom_keyword!(any);
|
||||||
|
custom_keyword!(cfg);
|
||||||
|
custom_keyword!(cfg_attr);
|
||||||
custom_keyword!(clock_domain);
|
custom_keyword!(clock_domain);
|
||||||
|
custom_keyword!(cmp_eq);
|
||||||
custom_keyword!(connect_inexact);
|
custom_keyword!(connect_inexact);
|
||||||
custom_keyword!(custom_bounds);
|
custom_keyword!(custom_bounds);
|
||||||
|
custom_keyword!(custom_debug);
|
||||||
|
custom_keyword!(custom_sim_display);
|
||||||
custom_keyword!(flip);
|
custom_keyword!(flip);
|
||||||
|
custom_keyword!(get);
|
||||||
custom_keyword!(hdl);
|
custom_keyword!(hdl);
|
||||||
|
custom_keyword!(hdl_module);
|
||||||
|
custom_keyword!(incomplete_wire);
|
||||||
custom_keyword!(input);
|
custom_keyword!(input);
|
||||||
custom_keyword!(instance);
|
custom_keyword!(instance);
|
||||||
custom_keyword!(m);
|
custom_keyword!(m);
|
||||||
|
custom_keyword!(mask_sim);
|
||||||
|
custom_keyword!(mask_type);
|
||||||
custom_keyword!(memory);
|
custom_keyword!(memory);
|
||||||
custom_keyword!(memory_array);
|
custom_keyword!(memory_array);
|
||||||
custom_keyword!(memory_with_init);
|
custom_keyword!(memory_with_init);
|
||||||
custom_keyword!(no_reset);
|
custom_keyword!(no_reset);
|
||||||
custom_keyword!(no_runtime_generics);
|
custom_keyword!(no_runtime_generics);
|
||||||
custom_keyword!(no_static);
|
custom_keyword!(no_static);
|
||||||
|
custom_keyword!(not);
|
||||||
custom_keyword!(outline_generated);
|
custom_keyword!(outline_generated);
|
||||||
custom_keyword!(output);
|
custom_keyword!(output);
|
||||||
custom_keyword!(reg_builder);
|
custom_keyword!(reg_builder);
|
||||||
custom_keyword!(reset);
|
custom_keyword!(reset);
|
||||||
custom_keyword!(reset_default);
|
custom_keyword!(sim);
|
||||||
custom_keyword!(skip);
|
custom_keyword!(skip);
|
||||||
custom_keyword!(target);
|
custom_keyword!(target);
|
||||||
custom_keyword!(wire);
|
custom_keyword!(wire);
|
||||||
|
|
@ -68,34 +108,34 @@ mod kw {
|
||||||
type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676
|
type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct HdlAttr<T> {
|
pub(crate) struct HdlAttr<T, KW> {
|
||||||
pub(crate) pound_token: Pound,
|
pub(crate) pound_token: Pound,
|
||||||
pub(crate) style: AttrStyle,
|
pub(crate) style: AttrStyle,
|
||||||
pub(crate) bracket_token: syn::token::Bracket,
|
pub(crate) bracket_token: syn::token::Bracket,
|
||||||
pub(crate) hdl: kw::hdl,
|
pub(crate) kw: KW,
|
||||||
pub(crate) paren_token: Option<syn::token::Paren>,
|
pub(crate) paren_token: Option<syn::token::Paren>,
|
||||||
pub(crate) body: T,
|
pub(crate) body: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::fold::impl_fold! {
|
crate::fold::impl_fold! {
|
||||||
struct HdlAttr<T,> {
|
struct HdlAttr<T, KW,> {
|
||||||
pound_token: Pound,
|
pound_token: Pound,
|
||||||
style: AttrStyle,
|
style: AttrStyle,
|
||||||
bracket_token: syn::token::Bracket,
|
bracket_token: syn::token::Bracket,
|
||||||
hdl: kw::hdl,
|
kw: KW,
|
||||||
paren_token: Option<syn::token::Paren>,
|
paren_token: Option<syn::token::Paren>,
|
||||||
body: T,
|
body: T,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl<T> HdlAttr<T> {
|
impl<T, KW> HdlAttr<T, KW> {
|
||||||
pub(crate) fn split_body(self) -> (HdlAttr<()>, T) {
|
pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) {
|
||||||
let Self {
|
let Self {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
@ -104,19 +144,19 @@ impl<T> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body: (),
|
body: (),
|
||||||
},
|
},
|
||||||
body,
|
body,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2> {
|
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2, KW> {
|
||||||
let Self {
|
let Self {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body: _,
|
body: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
@ -124,17 +164,20 @@ impl<T> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn as_ref(&self) -> HdlAttr<&T> {
|
pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW>
|
||||||
|
where
|
||||||
|
KW: Clone,
|
||||||
|
{
|
||||||
let Self {
|
let Self {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
ref kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
ref body,
|
ref body,
|
||||||
} = *self;
|
} = *self;
|
||||||
|
|
@ -142,17 +185,20 @@ impl<T> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw: kw.clone(),
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(self, f: F) -> Result<HdlAttr<R>, E> {
|
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> Result<HdlAttr<R, KW>, E> {
|
||||||
let Self {
|
let Self {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
@ -160,17 +206,17 @@ impl<T> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body: f(body)?,
|
body: f(body)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R> {
|
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R, KW> {
|
||||||
let Self {
|
let Self {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
@ -178,7 +224,7 @@ impl<T> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body: f(body),
|
body: f(body),
|
||||||
}
|
}
|
||||||
|
|
@ -186,31 +232,32 @@ impl<T> HdlAttr<T> {
|
||||||
fn to_attr(&self) -> Attribute
|
fn to_attr(&self) -> Attribute
|
||||||
where
|
where
|
||||||
T: ToTokens,
|
T: ToTokens,
|
||||||
|
KW: ToTokens,
|
||||||
{
|
{
|
||||||
parse_quote! { #self }
|
parse_quote! { #self }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Default> Default for HdlAttr<T> {
|
impl<T: Default, KW: Default> Default for HdlAttr<T, KW> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
T::default().into()
|
T::default().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for HdlAttr<T> {
|
impl<T, KW: Default> From<T> for HdlAttr<T, KW> {
|
||||||
fn from(body: T) -> Self {
|
fn from(body: T) -> Self {
|
||||||
HdlAttr {
|
HdlAttr {
|
||||||
pound_token: Default::default(),
|
pound_token: Default::default(),
|
||||||
style: AttrStyle::Outer,
|
style: AttrStyle::Outer,
|
||||||
bracket_token: Default::default(),
|
bracket_token: Default::default(),
|
||||||
hdl: Default::default(),
|
kw: Default::default(),
|
||||||
paren_token: Default::default(),
|
paren_token: Default::default(),
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToTokens> ToTokens for HdlAttr<T> {
|
impl<T: ToTokens, KW: ToTokens + Spanned> ToTokens for HdlAttr<T, KW> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
self.pound_token.to_tokens(tokens);
|
self.pound_token.to_tokens(tokens);
|
||||||
match self.style {
|
match self.style {
|
||||||
|
|
@ -218,7 +265,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
|
||||||
AttrStyle::Outer => {}
|
AttrStyle::Outer => {}
|
||||||
};
|
};
|
||||||
self.bracket_token.surround(tokens, |tokens| {
|
self.bracket_token.surround(tokens, |tokens| {
|
||||||
self.hdl.to_tokens(tokens);
|
self.kw.to_tokens(tokens);
|
||||||
match self.paren_token {
|
match self.paren_token {
|
||||||
Some(paren_token) => {
|
Some(paren_token) => {
|
||||||
paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens))
|
paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens))
|
||||||
|
|
@ -226,7 +273,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
|
||||||
None => {
|
None => {
|
||||||
let body = self.body.to_token_stream();
|
let body = self.body.to_token_stream();
|
||||||
if !body.is_empty() {
|
if !body.is_empty() {
|
||||||
syn::token::Paren(self.hdl.span)
|
syn::token::Paren(self.kw.span())
|
||||||
.surround(tokens, |tokens| tokens.extend([body]));
|
.surround(tokens, |tokens| tokens.extend([body]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,18 +282,24 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_hdl_attr(attr: &Attribute) -> bool {
|
fn is_hdl_attr<KW: CustomToken>(attr: &Attribute) -> bool {
|
||||||
attr.path().is_ident("hdl")
|
attr.path().is_ident(KW::IDENT_STR)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Parse> HdlAttr<T> {
|
impl<T: Parse, KW: Parse> HdlAttr<T, KW> {
|
||||||
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>> {
|
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>>
|
||||||
|
where
|
||||||
|
KW: ToTokens,
|
||||||
|
{
|
||||||
let mut retval = None;
|
let mut retval = None;
|
||||||
let mut errors = Errors::new();
|
let mut errors = Errors::new();
|
||||||
attrs.retain(|attr| {
|
attrs.retain(|attr| {
|
||||||
if is_hdl_attr(attr) {
|
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
|
||||||
if retval.is_some() {
|
if retval.is_some() {
|
||||||
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
|
errors.push(Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
|
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
|
||||||
false
|
false
|
||||||
|
|
@ -257,13 +310,19 @@ impl<T: Parse> HdlAttr<T> {
|
||||||
errors.finish()?;
|
errors.finish()?;
|
||||||
Ok(retval)
|
Ok(retval)
|
||||||
}
|
}
|
||||||
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>> {
|
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>>
|
||||||
|
where
|
||||||
|
KW: ToTokens,
|
||||||
|
{
|
||||||
let mut retval = None;
|
let mut retval = None;
|
||||||
let mut errors = Errors::new();
|
let mut errors = Errors::new();
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
if is_hdl_attr(attr) {
|
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
|
||||||
if retval.is_some() {
|
if retval.is_some() {
|
||||||
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
|
errors.push(Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
|
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +343,7 @@ impl<T: Parse> HdlAttr<T> {
|
||||||
) -> syn::Result<Self> {
|
) -> syn::Result<Self> {
|
||||||
let bracket_content;
|
let bracket_content;
|
||||||
let bracket_token = bracketed!(bracket_content in input);
|
let bracket_token = bracketed!(bracket_content in input);
|
||||||
let hdl = bracket_content.parse()?;
|
let kw = bracket_content.parse()?;
|
||||||
let paren_content;
|
let paren_content;
|
||||||
let body;
|
let body;
|
||||||
let paren_token;
|
let paren_token;
|
||||||
|
|
@ -305,7 +364,7 @@ impl<T: Parse> HdlAttr<T> {
|
||||||
pound_token,
|
pound_token,
|
||||||
style,
|
style,
|
||||||
bracket_token,
|
bracket_token,
|
||||||
hdl,
|
kw,
|
||||||
paren_token,
|
paren_token,
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
|
|
@ -813,6 +872,7 @@ macro_rules! options {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::hdl_type_alias::hdl_type_alias_impl;
|
||||||
pub(crate) use options;
|
pub(crate) use options;
|
||||||
|
|
||||||
pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream {
|
pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream {
|
||||||
|
|
@ -832,7 +892,13 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _print_on_panic = PrintOnPanic(&contents);
|
let _print_on_panic = PrintOnPanic(&contents);
|
||||||
let contents = prettyplease::unparse(&parse_quote! { #contents });
|
let mut parse_err = None;
|
||||||
|
let (Ok(contents) | Err(contents)) = syn::parse2(contents.clone())
|
||||||
|
.map(|file| prettyplease::unparse(&file))
|
||||||
|
.map_err(|e| {
|
||||||
|
parse_err = Some(e);
|
||||||
|
contents.to_string()
|
||||||
|
});
|
||||||
let hash = <sha2::Sha256 as sha2::Digest>::digest(&contents);
|
let hash = <sha2::Sha256 as sha2::Digest>::digest(&contents);
|
||||||
let hash = base16ct::HexDisplay(&hash[..5]);
|
let hash = base16ct::HexDisplay(&hash[..5]);
|
||||||
file.write_all(contents.as_bytes()).unwrap();
|
file.write_all(contents.as_bytes()).unwrap();
|
||||||
|
|
@ -844,33 +910,397 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
|
||||||
e.unwrap();
|
e.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("generated {}", dest_file.display());
|
let log_msg = if let Some(parse_err) = parse_err {
|
||||||
|
format!(
|
||||||
|
"fayalite-proc-macros-impl internal error:\nfailed to parse generated output: {parse_err}\nunformatted output is in: {}\n",
|
||||||
|
dest_file.display()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("generated {}\n", dest_file.display())
|
||||||
|
};
|
||||||
|
// write message atomically if possible
|
||||||
|
let mut stderr = std::io::stderr().lock();
|
||||||
|
let write_result = stderr.write_all(log_msg.as_bytes());
|
||||||
|
let flush_result = stderr.flush();
|
||||||
|
drop(stderr); // unlock before we try to panic
|
||||||
|
write_result.unwrap();
|
||||||
|
flush_result.unwrap();
|
||||||
|
std::io::stderr()
|
||||||
|
.lock()
|
||||||
|
.write_all(log_msg.as_bytes())
|
||||||
|
.unwrap();
|
||||||
let dest_file = dest_file.to_str().unwrap();
|
let dest_file = dest_file.to_str().unwrap();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
include!(#dest_file);
|
include!(#dest_file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
|
fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
|
||||||
let options = syn::parse2::<module::ConfigOptions>(attr)?;
|
let func = module::ModuleFn::parse_from_fn(item)?;
|
||||||
let options = HdlAttr::from(options);
|
let options = func.config_options();
|
||||||
let func = syn::parse2::<module::ModuleFn>(quote! { #options #item })?;
|
|
||||||
let mut contents = func.generate();
|
let mut contents = func.generate();
|
||||||
if options.body.outline_generated.is_some() {
|
if options.outline_generated.is_some() {
|
||||||
contents = outline_generated(contents, "module-");
|
contents = outline_generated(contents, "module-");
|
||||||
}
|
}
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
let item = syn::parse2::<Item>(quote! { #[hdl(#attr)] #item })?;
|
pub(crate) enum CfgExpr {
|
||||||
|
Option {
|
||||||
|
ident: Ident,
|
||||||
|
value: Option<(Token![=], LitStr)>,
|
||||||
|
},
|
||||||
|
All {
|
||||||
|
all: kw::all,
|
||||||
|
paren: Paren,
|
||||||
|
exprs: Punctuated<CfgExpr, Token![,]>,
|
||||||
|
},
|
||||||
|
Any {
|
||||||
|
any: kw::any,
|
||||||
|
paren: Paren,
|
||||||
|
exprs: Punctuated<CfgExpr, Token![,]>,
|
||||||
|
},
|
||||||
|
Not {
|
||||||
|
not: kw::not,
|
||||||
|
paren: Paren,
|
||||||
|
expr: Box<CfgExpr>,
|
||||||
|
trailing_comma: Option<Token![,]>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for CfgExpr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
match input.cursor().ident() {
|
||||||
|
Some((_, cursor)) if cursor.eof() => {
|
||||||
|
return Ok(CfgExpr::Option {
|
||||||
|
ident: input.call(Ident::parse_any)?,
|
||||||
|
value: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if input.peek(Ident::peek_any) && input.peek2(Token![=]) {
|
||||||
|
return Ok(CfgExpr::Option {
|
||||||
|
ident: input.call(Ident::parse_any)?,
|
||||||
|
value: Some((input.parse()?, input.parse()?)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let contents;
|
||||||
|
if input.peek(kw::all) {
|
||||||
|
Ok(CfgExpr::All {
|
||||||
|
all: input.parse()?,
|
||||||
|
paren: parenthesized!(contents in input),
|
||||||
|
exprs: contents.call(Punctuated::parse_terminated)?,
|
||||||
|
})
|
||||||
|
} else if input.peek(kw::any) {
|
||||||
|
Ok(CfgExpr::Any {
|
||||||
|
any: input.parse()?,
|
||||||
|
paren: parenthesized!(contents in input),
|
||||||
|
exprs: contents.call(Punctuated::parse_terminated)?,
|
||||||
|
})
|
||||||
|
} else if input.peek(kw::not) {
|
||||||
|
Ok(CfgExpr::Not {
|
||||||
|
not: input.parse()?,
|
||||||
|
paren: parenthesized!(contents in input),
|
||||||
|
expr: contents.parse()?,
|
||||||
|
trailing_comma: contents.parse()?,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(input.error("expected cfg-pattern"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for CfgExpr {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
CfgExpr::Option { ident, value } => {
|
||||||
|
ident.to_tokens(tokens);
|
||||||
|
if let Some((eq, value)) = value {
|
||||||
|
eq.to_tokens(tokens);
|
||||||
|
value.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CfgExpr::All { all, paren, exprs } => {
|
||||||
|
all.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
|
||||||
|
}
|
||||||
|
CfgExpr::Any { any, paren, exprs } => {
|
||||||
|
any.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
|
||||||
|
}
|
||||||
|
CfgExpr::Not {
|
||||||
|
not,
|
||||||
|
paren,
|
||||||
|
expr,
|
||||||
|
trailing_comma,
|
||||||
|
} => {
|
||||||
|
not.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |tokens| {
|
||||||
|
expr.to_tokens(tokens);
|
||||||
|
trailing_comma.to_tokens(tokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub(crate) struct Cfg {
|
||||||
|
cfg: kw::cfg,
|
||||||
|
paren: Paren,
|
||||||
|
expr: CfgExpr,
|
||||||
|
trailing_comma: Option<Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cfg {
|
||||||
|
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
|
||||||
|
syn::parse2(meta.to_token_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Cfg {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self {
|
||||||
|
cfg,
|
||||||
|
paren,
|
||||||
|
expr,
|
||||||
|
trailing_comma,
|
||||||
|
} = self;
|
||||||
|
cfg.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |tokens| {
|
||||||
|
expr.to_tokens(tokens);
|
||||||
|
trailing_comma.to_tokens(tokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Cfg {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let contents;
|
||||||
|
Ok(Self {
|
||||||
|
cfg: input.parse()?,
|
||||||
|
paren: parenthesized!(contents in input),
|
||||||
|
expr: contents.parse()?,
|
||||||
|
trailing_comma: contents.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub(crate) struct CfgAttr {
|
||||||
|
cfg_attr: kw::cfg_attr,
|
||||||
|
paren: Paren,
|
||||||
|
expr: CfgExpr,
|
||||||
|
comma: Token![,],
|
||||||
|
attrs: Punctuated<Meta, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CfgAttr {
|
||||||
|
pub(crate) fn to_cfg(&self) -> Cfg {
|
||||||
|
Cfg {
|
||||||
|
cfg: kw::cfg(self.cfg_attr.span),
|
||||||
|
paren: self.paren,
|
||||||
|
expr: self.expr.clone(),
|
||||||
|
trailing_comma: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
|
||||||
|
syn::parse2(meta.to_token_stream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for CfgAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let contents;
|
||||||
|
Ok(Self {
|
||||||
|
cfg_attr: input.parse()?,
|
||||||
|
paren: parenthesized!(contents in input),
|
||||||
|
expr: contents.parse()?,
|
||||||
|
comma: contents.parse()?,
|
||||||
|
attrs: contents.call(Punctuated::parse_terminated)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct CfgAndValue {
|
||||||
|
cfg: Cfg,
|
||||||
|
eq_token: Token![=],
|
||||||
|
value: LitBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for CfgAndValue {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
cfg: input.parse()?,
|
||||||
|
eq_token: input.parse()?,
|
||||||
|
value: input.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Cfgs<T> {
|
||||||
|
pub(crate) bracket: Bracket,
|
||||||
|
pub(crate) cfgs_map: HashMap<Cfg, T>,
|
||||||
|
pub(crate) cfgs_list: Vec<Cfg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Cfgs<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
bracket: Default::default(),
|
||||||
|
cfgs_map: Default::default(),
|
||||||
|
cfgs_list: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Cfgs<T> {
|
||||||
|
fn insert_cfg(&mut self, cfg: Cfg, value: T) {
|
||||||
|
match self.cfgs_map.entry(cfg) {
|
||||||
|
Entry::Occupied(_) => {}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
self.cfgs_list.push(entry.key().clone());
|
||||||
|
entry.insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Cfgs<bool> {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let contents;
|
||||||
|
let bracket = bracketed!(contents in input);
|
||||||
|
let mut cfgs_map = HashMap::new();
|
||||||
|
let mut cfgs_list = Vec::new();
|
||||||
|
for CfgAndValue {
|
||||||
|
cfg,
|
||||||
|
eq_token,
|
||||||
|
value,
|
||||||
|
} in contents.call(Punctuated::<CfgAndValue, Token![,]>::parse_terminated)?
|
||||||
|
{
|
||||||
|
let _ = eq_token;
|
||||||
|
match cfgs_map.entry(cfg) {
|
||||||
|
Entry::Occupied(_) => {}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
cfgs_list.push(entry.key().clone());
|
||||||
|
entry.insert(value.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
bracket,
|
||||||
|
cfgs_map,
|
||||||
|
cfgs_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Cfgs<()> {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let contents;
|
||||||
|
let bracket = bracketed!(contents in input);
|
||||||
|
let mut cfgs_map = HashMap::new();
|
||||||
|
let mut cfgs_list = Vec::new();
|
||||||
|
for cfg in contents.call(Punctuated::<Cfg, Token![,]>::parse_terminated)? {
|
||||||
|
match cfgs_map.entry(cfg) {
|
||||||
|
Entry::Occupied(_) => {}
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
cfgs_list.push(entry.key().clone());
|
||||||
|
entry.insert(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
bracket,
|
||||||
|
cfgs_map,
|
||||||
|
cfgs_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Cfgs<()> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self {
|
||||||
|
bracket,
|
||||||
|
cfgs_map: _,
|
||||||
|
cfgs_list,
|
||||||
|
} = self;
|
||||||
|
bracket.surround(tokens, |tokens| {
|
||||||
|
for cfg in cfgs_list {
|
||||||
|
cfg.to_tokens(tokens);
|
||||||
|
<Token![,]>::default().to_tokens(tokens);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hdl_main(
|
||||||
|
kw: impl CustomToken,
|
||||||
|
attr: TokenStream,
|
||||||
|
item: TokenStream,
|
||||||
|
) -> syn::Result<TokenStream> {
|
||||||
|
fn parse_evaluated_cfgs_attr<R>(
|
||||||
|
input: ParseStream,
|
||||||
|
parse_inner: impl FnOnce(ParseStream) -> syn::Result<R>,
|
||||||
|
) -> syn::Result<R> {
|
||||||
|
let _: Token![#] = input.parse()?;
|
||||||
|
let bracket_content;
|
||||||
|
bracketed!(bracket_content in input);
|
||||||
|
let _: kw::__evaluated_cfgs = bracket_content.parse()?;
|
||||||
|
let paren_content;
|
||||||
|
parenthesized!(paren_content in bracket_content);
|
||||||
|
parse_inner(&paren_content)
|
||||||
|
}
|
||||||
|
let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2(
|
||||||
|
|input: ParseStream| {
|
||||||
|
let peek = input.fork();
|
||||||
|
if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() {
|
||||||
|
let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::<bool>::parse)?;
|
||||||
|
Ok((Some(evaluated_cfgs), input.parse()?))
|
||||||
|
} else {
|
||||||
|
Ok((None, input.parse()?))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
item,
|
||||||
|
)?;
|
||||||
|
let cfgs = if let Some(cfgs) = evaluated_cfgs {
|
||||||
|
cfgs
|
||||||
|
} else {
|
||||||
|
let cfgs = process_cfg::collect_cfgs(syn::parse2(item.clone())?)?;
|
||||||
|
if cfgs.cfgs_list.is_empty() {
|
||||||
|
Cfgs::default()
|
||||||
|
} else {
|
||||||
|
return Ok(quote! {
|
||||||
|
::fayalite::__cfg_expansion_helper! {
|
||||||
|
[]
|
||||||
|
#cfgs
|
||||||
|
{#[::fayalite::#kw(#attr)]} { #item }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let item = syn::parse2(quote! { #[#kw(#attr)] #item })?;
|
||||||
|
let Some(item) = process_cfg::process_cfgs(item, cfgs)? else {
|
||||||
|
return Ok(TokenStream::new());
|
||||||
|
};
|
||||||
match item {
|
match item {
|
||||||
Item::Enum(item) => hdl_enum::hdl_enum(item),
|
Item::Enum(item) => hdl_enum::hdl_enum(item),
|
||||||
Item::Struct(item) => hdl_bundle::hdl_bundle(item),
|
Item::Struct(item) => hdl_bundle::hdl_bundle(item),
|
||||||
|
Item::Fn(item) => hdl_module_impl(item),
|
||||||
|
Item::Type(item) => hdl_type_alias_impl(item),
|
||||||
_ => Err(syn::Error::new(
|
_ => Err(syn::Error::new(
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
"top-level #[hdl] can only be used on structs or enums",
|
"top-level #[hdl] can only be used on structs, enums, type aliases, or functions",
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
hdl_main(kw::hdl_module::default(), attr, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
hdl_main(kw::hdl::default(), attr, item)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Errors, HdlAttr, PairsIterExt,
|
||||||
hdl_type_common::{ParsedGenerics, SplitForImpl},
|
hdl_type_common::{ParsedGenerics, SplitForImpl},
|
||||||
module::transform_body::{HdlLet, HdlLetKindIO},
|
kw,
|
||||||
options, Errors, HdlAttr, PairsIterExt,
|
module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO},
|
||||||
|
options,
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
parse_quote,
|
|
||||||
visit::{visit_pat, Visit},
|
|
||||||
Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
|
Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
|
||||||
LifetimeParam, ReturnType, Signature, TypeParam, Visibility, WhereClause, WherePredicate,
|
LifetimeParam, ReturnType, Signature, TypeParam, Visibility, WhereClause, WherePredicate,
|
||||||
|
parse_quote,
|
||||||
|
visit::{Visit, visit_pat},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod transform_body;
|
mod transform_body;
|
||||||
|
|
@ -38,7 +39,7 @@ pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Res
|
||||||
if name == "m" {
|
if name == "m" {
|
||||||
Err(Error::new_spanned(
|
Err(Error::new_spanned(
|
||||||
name,
|
name,
|
||||||
"name conflicts with implicit `m: &mut ModuleBuilder<_>`",
|
"name conflicts with implicit `m: &ModuleBuilder`",
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -59,17 +60,37 @@ impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> {
|
||||||
|
|
||||||
pub(crate) type ModuleIO = HdlLet<HdlLetKindIO>;
|
pub(crate) type ModuleIO = HdlLet<HdlLetKindIO>;
|
||||||
|
|
||||||
pub(crate) struct ModuleFn {
|
struct ModuleFnModule {
|
||||||
attrs: Vec<Attribute>,
|
attrs: Vec<Attribute>,
|
||||||
config_options: HdlAttr<ConfigOptions>,
|
config_options: HdlAttr<ConfigOptions, kw::hdl_module>,
|
||||||
module_kind: ModuleKind,
|
module_kind: ModuleKind,
|
||||||
vis: Visibility,
|
vis: Visibility,
|
||||||
sig: Signature,
|
sig: Signature,
|
||||||
block: Box<Block>,
|
block: Box<Block>,
|
||||||
struct_generics: ParsedGenerics,
|
struct_generics: Option<ParsedGenerics>,
|
||||||
the_struct: TokenStream,
|
the_struct: TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ModuleFnImpl {
|
||||||
|
Module(ModuleFnModule),
|
||||||
|
Fn {
|
||||||
|
attrs: Vec<Attribute>,
|
||||||
|
config_options: HdlAttr<ConfigOptions, kw::hdl>,
|
||||||
|
vis: Visibility,
|
||||||
|
sig: Signature,
|
||||||
|
block: Box<Block>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
pub(crate) enum HdlOrHdlModule {
|
||||||
|
Hdl(hdl),
|
||||||
|
HdlModule(hdl_module),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ModuleFn(ModuleFnImpl);
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
pub(crate) enum ModuleKind {
|
pub(crate) enum ModuleKind {
|
||||||
Extern,
|
Extern,
|
||||||
|
|
@ -89,14 +110,25 @@ impl Visit<'_> for ContainsSkippedIdent<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for ModuleFn {
|
impl ModuleFn {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
pub(crate) fn config_options(&self) -> ConfigOptions {
|
||||||
|
let (ModuleFnImpl::Module(ModuleFnModule {
|
||||||
|
config_options: HdlAttr { body, .. },
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| ModuleFnImpl::Fn {
|
||||||
|
config_options: HdlAttr { body, .. },
|
||||||
|
..
|
||||||
|
}) = &self.0;
|
||||||
|
body.clone()
|
||||||
|
}
|
||||||
|
pub(crate) fn parse_from_fn(item: ItemFn) -> syn::Result<Self> {
|
||||||
let ItemFn {
|
let ItemFn {
|
||||||
mut attrs,
|
mut attrs,
|
||||||
vis,
|
vis,
|
||||||
mut sig,
|
mut sig,
|
||||||
block,
|
block,
|
||||||
} = input.parse()?;
|
} = item;
|
||||||
let Signature {
|
let Signature {
|
||||||
ref constness,
|
ref constness,
|
||||||
ref asyncness,
|
ref asyncness,
|
||||||
|
|
@ -111,17 +143,33 @@ impl Parse for ModuleFn {
|
||||||
ref output,
|
ref output,
|
||||||
} = sig;
|
} = sig;
|
||||||
let mut errors = Errors::new();
|
let mut errors = Errors::new();
|
||||||
let config_options = errors
|
let Some(mut config_options) =
|
||||||
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
|
errors.unwrap_or_default(
|
||||||
.unwrap_or_default();
|
HdlAttr::<ConfigOptions, HdlOrHdlModule>::parse_and_take_attr(&mut attrs),
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
errors.error(sig.ident, "missing #[hdl] or #[hdl_module] attribute");
|
||||||
|
errors.finish()?;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
let ConfigOptions {
|
let ConfigOptions {
|
||||||
outline_generated: _,
|
outline_generated: _,
|
||||||
extern_,
|
extern_,
|
||||||
} = config_options.body;
|
} = config_options.body;
|
||||||
let module_kind = match extern_ {
|
let module_kind = match (config_options.kw, extern_) {
|
||||||
Some(_) => ModuleKind::Extern,
|
(HdlOrHdlModule::Hdl(_), None) => None,
|
||||||
None => ModuleKind::Normal,
|
(HdlOrHdlModule::Hdl(_), Some(extern2)) => {
|
||||||
|
config_options.body.extern_ = None;
|
||||||
|
errors.error(
|
||||||
|
extern2.0,
|
||||||
|
"extern can only be used as #[hdl_module(extern)]",
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
(HdlOrHdlModule::HdlModule(_), None) => Some(ModuleKind::Normal),
|
||||||
|
(HdlOrHdlModule::HdlModule(_), Some(_)) => Some(ModuleKind::Extern),
|
||||||
};
|
};
|
||||||
|
if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
|
||||||
for fn_arg in inputs {
|
for fn_arg in inputs {
|
||||||
match fn_arg {
|
match fn_arg {
|
||||||
FnArg::Receiver(_) => {
|
FnArg::Receiver(_) => {
|
||||||
|
|
@ -149,20 +197,24 @@ impl Parse for ModuleFn {
|
||||||
if let Some(abi) = abi {
|
if let Some(abi) = abi {
|
||||||
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
|
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let mut skipped_idents = HashSet::new();
|
let mut skipped_idents = HashSet::new();
|
||||||
let struct_generic_params = generics
|
let struct_generic_params = generics
|
||||||
.params
|
.params
|
||||||
.pairs_mut()
|
.pairs_mut()
|
||||||
.filter_map_pair_value_mut(|v| match v {
|
.filter_map_pair_value_mut(|v| match v {
|
||||||
GenericParam::Lifetime(LifetimeParam { attrs, .. }) => {
|
GenericParam::Lifetime(LifetimeParam { attrs, .. }) => {
|
||||||
errors
|
errors.unwrap_or_default(
|
||||||
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs));
|
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
|
||||||
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
GenericParam::Type(TypeParam { attrs, ident, .. })
|
GenericParam::Type(TypeParam { attrs, ident, .. })
|
||||||
| GenericParam::Const(ConstParam { attrs, ident, .. }) => {
|
| GenericParam::Const(ConstParam { attrs, ident, .. }) => {
|
||||||
if errors
|
if errors
|
||||||
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs))
|
.unwrap_or_default(
|
||||||
|
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
|
||||||
|
)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
skipped_idents.insert(ident.clone());
|
skipped_idents.insert(ident.clone());
|
||||||
|
|
@ -176,6 +228,7 @@ impl Parse for ModuleFn {
|
||||||
let struct_where_clause = generics
|
let struct_where_clause = generics
|
||||||
.where_clause
|
.where_clause
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
.filter(|_| matches!(config_options.kw, HdlOrHdlModule::HdlModule(_)))
|
||||||
.map(|where_clause| WhereClause {
|
.map(|where_clause| WhereClause {
|
||||||
where_token: where_clause.where_token,
|
where_token: where_clause.where_token,
|
||||||
predicates: where_clause
|
predicates: where_clause
|
||||||
|
|
@ -198,7 +251,8 @@ impl Parse for ModuleFn {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
});
|
});
|
||||||
let struct_generics = Generics {
|
let struct_generics = if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
|
||||||
|
let mut struct_generics = Generics {
|
||||||
lt_token: generics.lt_token,
|
lt_token: generics.lt_token,
|
||||||
params: struct_generic_params,
|
params: struct_generic_params,
|
||||||
gt_token: generics.gt_token,
|
gt_token: generics.gt_token,
|
||||||
|
|
@ -213,7 +267,10 @@ impl Parse for ModuleFn {
|
||||||
"return type not allowed here",
|
"return type not allowed here",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let struct_generics = errors.ok(ParsedGenerics::parse(&mut { struct_generics }));
|
errors.ok(ParsedGenerics::parse(&mut struct_generics))
|
||||||
|
} else {
|
||||||
|
Some(ParsedGenerics::default())
|
||||||
|
};
|
||||||
let body_results = struct_generics.as_ref().and_then(|struct_generics| {
|
let body_results = struct_generics.as_ref().and_then(|struct_generics| {
|
||||||
errors.ok(transform_body::transform_body(
|
errors.ok(transform_body::transform_body(
|
||||||
module_kind,
|
module_kind,
|
||||||
|
|
@ -224,6 +281,62 @@ impl Parse for ModuleFn {
|
||||||
errors.finish()?;
|
errors.finish()?;
|
||||||
let struct_generics = struct_generics.unwrap();
|
let struct_generics = struct_generics.unwrap();
|
||||||
let (block, io) = body_results.unwrap();
|
let (block, io) = body_results.unwrap();
|
||||||
|
let config_options = match config_options {
|
||||||
|
HdlAttr {
|
||||||
|
pound_token,
|
||||||
|
style,
|
||||||
|
bracket_token,
|
||||||
|
kw: HdlOrHdlModule::Hdl((kw,)),
|
||||||
|
paren_token,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
|
debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty()));
|
||||||
|
return Ok(Self(ModuleFnImpl::Fn {
|
||||||
|
attrs,
|
||||||
|
config_options: HdlAttr {
|
||||||
|
pound_token,
|
||||||
|
style,
|
||||||
|
bracket_token,
|
||||||
|
kw,
|
||||||
|
paren_token,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
block,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
HdlAttr {
|
||||||
|
pound_token,
|
||||||
|
style,
|
||||||
|
bracket_token,
|
||||||
|
kw: HdlOrHdlModule::HdlModule((kw,)),
|
||||||
|
paren_token,
|
||||||
|
body,
|
||||||
|
} => HdlAttr {
|
||||||
|
pound_token,
|
||||||
|
style,
|
||||||
|
bracket_token,
|
||||||
|
kw,
|
||||||
|
paren_token,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let io = match io {
|
||||||
|
ModuleIOOrAddPlatformIO::ModuleIO(io) => io,
|
||||||
|
ModuleIOOrAddPlatformIO::AddPlatformIO => {
|
||||||
|
return Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
|
||||||
|
attrs,
|
||||||
|
config_options,
|
||||||
|
module_kind: module_kind.unwrap(),
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
block,
|
||||||
|
struct_generics: None,
|
||||||
|
the_struct: TokenStream::new(),
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
};
|
||||||
let (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
|
let (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
|
||||||
struct_generics.split_for_impl();
|
struct_generics.split_for_impl();
|
||||||
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
|
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
|
||||||
|
|
@ -259,31 +372,52 @@ impl Parse for ModuleFn {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?;
|
let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?;
|
||||||
Ok(Self {
|
Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
|
||||||
attrs,
|
attrs,
|
||||||
config_options,
|
config_options,
|
||||||
module_kind,
|
module_kind: module_kind.unwrap(),
|
||||||
vis,
|
vis,
|
||||||
sig,
|
sig,
|
||||||
block,
|
block,
|
||||||
struct_generics,
|
struct_generics: Some(struct_generics),
|
||||||
the_struct,
|
the_struct,
|
||||||
})
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleFn {
|
impl ModuleFn {
|
||||||
pub(crate) fn generate(self) -> TokenStream {
|
pub(crate) fn generate(self) -> TokenStream {
|
||||||
let Self {
|
let ModuleFnModule {
|
||||||
attrs,
|
attrs,
|
||||||
config_options,
|
config_options,
|
||||||
module_kind,
|
module_kind,
|
||||||
vis,
|
vis,
|
||||||
sig,
|
sig,
|
||||||
block,
|
mut block,
|
||||||
struct_generics,
|
struct_generics,
|
||||||
the_struct,
|
the_struct,
|
||||||
} = self;
|
} = match self.0 {
|
||||||
|
ModuleFnImpl::Module(v) => v,
|
||||||
|
ModuleFnImpl::Fn {
|
||||||
|
attrs,
|
||||||
|
config_options,
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
block,
|
||||||
|
} => {
|
||||||
|
let ConfigOptions {
|
||||||
|
outline_generated: _,
|
||||||
|
extern_: _,
|
||||||
|
} = config_options.body;
|
||||||
|
return ItemFn {
|
||||||
|
attrs,
|
||||||
|
vis,
|
||||||
|
sig,
|
||||||
|
block,
|
||||||
|
}
|
||||||
|
.into_token_stream();
|
||||||
|
}
|
||||||
|
};
|
||||||
let ConfigOptions {
|
let ConfigOptions {
|
||||||
outline_generated: _,
|
outline_generated: _,
|
||||||
extern_: _,
|
extern_: _,
|
||||||
|
|
@ -314,13 +448,24 @@ impl ModuleFn {
|
||||||
ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal },
|
ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal },
|
||||||
};
|
};
|
||||||
let fn_name = &outer_sig.ident;
|
let fn_name = &outer_sig.ident;
|
||||||
|
let struct_ty = match struct_generics {
|
||||||
|
Some(struct_generics) => {
|
||||||
let (_struct_impl_generics, struct_type_generics, _struct_where_clause) =
|
let (_struct_impl_generics, struct_type_generics, _struct_where_clause) =
|
||||||
struct_generics.split_for_impl();
|
struct_generics.split_for_impl();
|
||||||
let struct_ty = quote! {#fn_name #struct_type_generics};
|
quote! {#fn_name #struct_type_generics}
|
||||||
|
}
|
||||||
|
None => quote! {::fayalite::bundle::Bundle},
|
||||||
|
};
|
||||||
body_sig.ident = parse_quote! {__body};
|
body_sig.ident = parse_quote! {__body};
|
||||||
body_sig
|
body_sig
|
||||||
.inputs
|
.inputs
|
||||||
.insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder });
|
.insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder });
|
||||||
|
block.stmts.insert(
|
||||||
|
0,
|
||||||
|
parse_quote! {
|
||||||
|
let _ = m;
|
||||||
|
},
|
||||||
|
);
|
||||||
let body_fn = ItemFn {
|
let body_fn = ItemFn {
|
||||||
attrs: vec![],
|
attrs: vec![],
|
||||||
vis: Visibility::Inherited,
|
vis: Visibility::Inherited,
|
||||||
|
|
@ -332,12 +477,21 @@ impl ModuleFn {
|
||||||
let fn_name_str = fn_name.to_string();
|
let fn_name_str = fn_name.to_string();
|
||||||
let (_, body_type_generics, _) = body_fn.sig.generics.split_for_impl();
|
let (_, body_type_generics, _) = body_fn.sig.generics.split_for_impl();
|
||||||
let body_turbofish_type_generics = body_type_generics.as_turbofish();
|
let body_turbofish_type_generics = body_type_generics.as_turbofish();
|
||||||
|
let body_lambda = if param_names.is_empty() {
|
||||||
|
quote! {
|
||||||
|
__body #body_turbofish_type_generics
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*)
|
||||||
|
}
|
||||||
|
};
|
||||||
let block = parse_quote! {{
|
let block = parse_quote! {{
|
||||||
#body_fn
|
#body_fn
|
||||||
::fayalite::module::ModuleBuilder::run(
|
::fayalite::module::ModuleBuilder::run(
|
||||||
#fn_name_str,
|
#fn_name_str,
|
||||||
#module_kind_value,
|
#module_kind_value,
|
||||||
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*),
|
#body_lambda,
|
||||||
)
|
)
|
||||||
}};
|
}};
|
||||||
let outer_fn = ItemFn {
|
let outer_fn = ItemFn {
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,49 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
fold::{impl_fold, DoFold},
|
Errors, HdlAttr,
|
||||||
|
fold::{DoFold, impl_fold},
|
||||||
hdl_type_common::{
|
hdl_type_common::{
|
||||||
known_items, ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser,
|
ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser, known_items,
|
||||||
},
|
},
|
||||||
is_hdl_attr, kw,
|
is_hdl_attr, kw,
|
||||||
module::{check_name_conflicts_with_module_builder, ModuleIO, ModuleIOKind, ModuleKind},
|
module::{ModuleIO, ModuleIOKind, ModuleKind, check_name_conflicts_with_module_builder},
|
||||||
options, Errors, HdlAttr,
|
options,
|
||||||
};
|
};
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{ToTokens, quote, quote_spanned};
|
||||||
use std::{borrow::Borrow, convert::Infallible};
|
use std::{borrow::Borrow, convert::Infallible};
|
||||||
use syn::{
|
use syn::{
|
||||||
fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold},
|
Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary,
|
||||||
|
GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp,
|
||||||
|
fold::{Fold, fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt},
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Nothing, Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
parse_quote, parse_quote_spanned,
|
parse_quote, parse_quote_spanned,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::Paren,
|
token::Paren,
|
||||||
Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary,
|
|
||||||
GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod expand_aggregate_literals;
|
mod expand_aggregate_literals;
|
||||||
mod expand_match;
|
mod expand_match;
|
||||||
|
|
||||||
|
options! {
|
||||||
|
#[options = ExprOptions]
|
||||||
|
pub(crate) enum ExprOption {
|
||||||
|
Sim(sim),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options! {
|
options! {
|
||||||
pub(crate) enum LetFnKind {
|
pub(crate) enum LetFnKind {
|
||||||
Input(input),
|
Input(input),
|
||||||
Output(output),
|
Output(output),
|
||||||
|
AddPlatformIO(add_platform_io),
|
||||||
Instance(instance),
|
Instance(instance),
|
||||||
RegBuilder(reg_builder),
|
RegBuilder(reg_builder),
|
||||||
Wire(wire),
|
Wire(wire),
|
||||||
|
IncompleteWire(incomplete_wire),
|
||||||
Memory(memory),
|
Memory(memory),
|
||||||
MemoryArray(memory_array),
|
MemoryArray(memory_array),
|
||||||
MemoryWithInit(memory_with_init),
|
MemoryWithInit(memory_with_init),
|
||||||
|
|
@ -207,6 +217,49 @@ impl HdlLetKindToTokens for HdlLetKindInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct HdlLetKindAddPlatformIO {
|
||||||
|
pub(crate) m: kw::m,
|
||||||
|
pub(crate) dot_token: Token![.],
|
||||||
|
pub(crate) add_platform_io: kw::add_platform_io,
|
||||||
|
pub(crate) paren: Paren,
|
||||||
|
pub(crate) platform_io_builder: Box<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseTypes<Self> for HdlLetKindAddPlatformIO {
|
||||||
|
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
|
||||||
|
Ok(input.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_fold! {
|
||||||
|
struct HdlLetKindAddPlatformIO<> {
|
||||||
|
m: kw::m,
|
||||||
|
dot_token: Token![.],
|
||||||
|
add_platform_io: kw::add_platform_io,
|
||||||
|
paren: Paren,
|
||||||
|
platform_io_builder: Box<Expr>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HdlLetKindToTokens for HdlLetKindAddPlatformIO {
|
||||||
|
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
|
||||||
|
|
||||||
|
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self {
|
||||||
|
m,
|
||||||
|
dot_token,
|
||||||
|
add_platform_io,
|
||||||
|
paren,
|
||||||
|
platform_io_builder,
|
||||||
|
} = self;
|
||||||
|
m.to_tokens(tokens);
|
||||||
|
dot_token.to_tokens(tokens);
|
||||||
|
add_platform_io.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct RegBuilderClockDomain {
|
pub(crate) struct RegBuilderClockDomain {
|
||||||
pub(crate) dot_token: Token![.],
|
pub(crate) dot_token: Token![.],
|
||||||
|
|
@ -264,11 +317,6 @@ pub(crate) enum RegBuilderReset {
|
||||||
paren: Paren,
|
paren: Paren,
|
||||||
init_expr: Box<Expr>,
|
init_expr: Box<Expr>,
|
||||||
},
|
},
|
||||||
ResetDefault {
|
|
||||||
dot_token: Token![.],
|
|
||||||
reset_default: kw::reset_default,
|
|
||||||
paren: Paren,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_fold! {
|
impl_fold! {
|
||||||
|
|
@ -285,11 +333,6 @@ impl_fold! {
|
||||||
paren: Paren,
|
paren: Paren,
|
||||||
init_expr: Box<Expr>,
|
init_expr: Box<Expr>,
|
||||||
},
|
},
|
||||||
ResetDefault {
|
|
||||||
dot_token: Token![.],
|
|
||||||
reset_default: kw::reset_default,
|
|
||||||
paren: Paren,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,11 +354,6 @@ impl Parse for RegBuilderReset {
|
||||||
paren: parenthesized!(paren_contents in input),
|
paren: parenthesized!(paren_contents in input),
|
||||||
init_expr: paren_contents.call(parse_single_fn_arg)?,
|
init_expr: paren_contents.call(parse_single_fn_arg)?,
|
||||||
}),
|
}),
|
||||||
RegBuilderMethod::ResetDefault(reset_default) => Ok(Self::ResetDefault {
|
|
||||||
dot_token,
|
|
||||||
reset_default,
|
|
||||||
paren: parenthesized!(paren_contents in input),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,15 +381,6 @@ impl ToTokens for RegBuilderReset {
|
||||||
reset.to_tokens(tokens);
|
reset.to_tokens(tokens);
|
||||||
paren.surround(tokens, |tokens| init_expr.to_tokens(tokens));
|
paren.surround(tokens, |tokens| init_expr.to_tokens(tokens));
|
||||||
}
|
}
|
||||||
RegBuilderReset::ResetDefault {
|
|
||||||
dot_token,
|
|
||||||
reset_default,
|
|
||||||
paren,
|
|
||||||
} => {
|
|
||||||
dot_token.to_tokens(tokens);
|
|
||||||
reset_default.to_tokens(tokens);
|
|
||||||
paren.surround(tokens, |_| {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -400,8 +429,6 @@ make_builder_method_enum! {
|
||||||
NoReset(no_reset),
|
NoReset(no_reset),
|
||||||
#[cond = need_reset]
|
#[cond = need_reset]
|
||||||
Reset(reset),
|
Reset(reset),
|
||||||
#[cond = need_reset]
|
|
||||||
ResetDefault(reset_default),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,17 +471,13 @@ impl HdlLetKindRegBuilder {
|
||||||
let mut clock_domain = None;
|
let mut clock_domain = None;
|
||||||
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 {
|
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 {
|
||||||
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
|
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
|
||||||
RegBuilderMethod::NoReset(_)
|
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => {}
|
||||||
| RegBuilderMethod::Reset(_)
|
|
||||||
| RegBuilderMethod::ResetDefault(_) => {}
|
|
||||||
}
|
}
|
||||||
let reset = input.parse()?;
|
let reset = input.parse()?;
|
||||||
if clock_domain.is_none() {
|
if clock_domain.is_none() {
|
||||||
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 {
|
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 {
|
||||||
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
|
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
|
||||||
RegBuilderMethod::NoReset(_)
|
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => unreachable!(),
|
||||||
| RegBuilderMethod::Reset(_)
|
|
||||||
| RegBuilderMethod::ResetDefault(_) => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
@ -533,6 +556,41 @@ impl HdlLetKindToTokens for HdlLetKindWire {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options! {
|
||||||
|
pub(crate) enum LetFnKindIncomplete {
|
||||||
|
IncompleteWire(incomplete_wire),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct HdlLetKindIncomplete {
|
||||||
|
pub(crate) kind: LetFnKindIncomplete,
|
||||||
|
pub(crate) paren: Paren,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseTypes<Self> for HdlLetKindIncomplete {
|
||||||
|
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
|
||||||
|
Ok(input.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_fold! {
|
||||||
|
struct HdlLetKindIncomplete<> {
|
||||||
|
kind: LetFnKindIncomplete,
|
||||||
|
paren: Paren,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HdlLetKindToTokens for HdlLetKindIncomplete {
|
||||||
|
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
|
||||||
|
|
||||||
|
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { kind, paren } = self;
|
||||||
|
kind.to_tokens(tokens);
|
||||||
|
paren.surround(tokens, |_| {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options! {
|
options! {
|
||||||
pub(crate) enum MemoryFnName {
|
pub(crate) enum MemoryFnName {
|
||||||
Memory(memory),
|
Memory(memory),
|
||||||
|
|
@ -697,6 +755,8 @@ impl HdlLetKindMemory {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum HdlLetKind<IOType = ParsedType> {
|
pub(crate) enum HdlLetKind<IOType = ParsedType> {
|
||||||
IO(HdlLetKindIO<ModuleIOKind, IOType>),
|
IO(HdlLetKindIO<ModuleIOKind, IOType>),
|
||||||
|
AddPlatformIO(HdlLetKindAddPlatformIO),
|
||||||
|
Incomplete(HdlLetKindIncomplete),
|
||||||
Instance(HdlLetKindInstance),
|
Instance(HdlLetKindInstance),
|
||||||
RegBuilder(HdlLetKindRegBuilder),
|
RegBuilder(HdlLetKindRegBuilder),
|
||||||
Wire(HdlLetKindWire),
|
Wire(HdlLetKindWire),
|
||||||
|
|
@ -706,6 +766,8 @@ pub(crate) enum HdlLetKind<IOType = ParsedType> {
|
||||||
impl_fold! {
|
impl_fold! {
|
||||||
enum HdlLetKind<IOType,> {
|
enum HdlLetKind<IOType,> {
|
||||||
IO(HdlLetKindIO<ModuleIOKind, IOType>),
|
IO(HdlLetKindIO<ModuleIOKind, IOType>),
|
||||||
|
AddPlatformIO(HdlLetKindAddPlatformIO),
|
||||||
|
Incomplete(HdlLetKindIncomplete),
|
||||||
Instance(HdlLetKindInstance),
|
Instance(HdlLetKindInstance),
|
||||||
RegBuilder(HdlLetKindRegBuilder),
|
RegBuilder(HdlLetKindRegBuilder),
|
||||||
Wire(HdlLetKindWire),
|
Wire(HdlLetKindWire),
|
||||||
|
|
@ -720,6 +782,12 @@ impl<T: ParseTypes<I>, I> ParseTypes<HdlLetKind<I>> for HdlLetKind<T> {
|
||||||
) -> Result<Self, ParseFailed> {
|
) -> Result<Self, ParseFailed> {
|
||||||
match input {
|
match input {
|
||||||
HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO),
|
HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO),
|
||||||
|
HdlLetKind::AddPlatformIO(input) => {
|
||||||
|
ParseTypes::parse_types(input, parser).map(HdlLetKind::AddPlatformIO)
|
||||||
|
}
|
||||||
|
HdlLetKind::Incomplete(input) => {
|
||||||
|
ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete)
|
||||||
|
}
|
||||||
HdlLetKind::Instance(input) => {
|
HdlLetKind::Instance(input) => {
|
||||||
ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance)
|
ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance)
|
||||||
}
|
}
|
||||||
|
|
@ -842,6 +910,23 @@ impl HdlLetKindParse for HdlLetKind<Type> {
|
||||||
ModuleIOKind::Output(output),
|
ModuleIOKind::Output(output),
|
||||||
)
|
)
|
||||||
.map(Self::IO),
|
.map(Self::IO),
|
||||||
|
LetFnKind::AddPlatformIO((add_platform_io,)) => {
|
||||||
|
if let Some(parsed_ty) = parsed_ty {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
parsed_ty.1,
|
||||||
|
"type annotation not allowed for instance",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let (m, dot_token) = unwrap_m_dot(m_dot, kind)?;
|
||||||
|
let paren_contents;
|
||||||
|
Ok(Self::AddPlatformIO(HdlLetKindAddPlatformIO {
|
||||||
|
m,
|
||||||
|
dot_token,
|
||||||
|
add_platform_io,
|
||||||
|
paren: parenthesized!(paren_contents in input),
|
||||||
|
platform_io_builder: paren_contents.call(parse_single_fn_arg)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
LetFnKind::Instance((instance,)) => {
|
LetFnKind::Instance((instance,)) => {
|
||||||
if let Some(parsed_ty) = parsed_ty {
|
if let Some(parsed_ty) = parsed_ty {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
|
|
@ -871,6 +956,20 @@ impl HdlLetKindParse for HdlLetKind<Type> {
|
||||||
ty_expr: paren_contents.call(parse_optional_fn_arg)?,
|
ty_expr: paren_contents.call(parse_optional_fn_arg)?,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
LetFnKind::IncompleteWire(incomplete_wire) => {
|
||||||
|
if let Some(parsed_ty) = parsed_ty {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
parsed_ty.1,
|
||||||
|
"type annotation not allowed for incomplete_wire",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
check_empty_m_dot(m_dot, kind)?;
|
||||||
|
let _paren_contents;
|
||||||
|
Ok(Self::Incomplete(HdlLetKindIncomplete {
|
||||||
|
kind: LetFnKindIncomplete::IncompleteWire(incomplete_wire),
|
||||||
|
paren: parenthesized!(_paren_contents in input),
|
||||||
|
}))
|
||||||
|
}
|
||||||
LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse(
|
LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse(
|
||||||
input,
|
input,
|
||||||
parsed_ty,
|
parsed_ty,
|
||||||
|
|
@ -903,6 +1002,8 @@ impl HdlLetKindToTokens for HdlLetKind {
|
||||||
fn ty_to_tokens(&self, tokens: &mut TokenStream) {
|
fn ty_to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
match self {
|
match self {
|
||||||
HdlLetKind::IO(v) => v.ty_to_tokens(tokens),
|
HdlLetKind::IO(v) => v.ty_to_tokens(tokens),
|
||||||
|
HdlLetKind::AddPlatformIO(v) => v.ty_to_tokens(tokens),
|
||||||
|
HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens),
|
||||||
HdlLetKind::Instance(v) => v.ty_to_tokens(tokens),
|
HdlLetKind::Instance(v) => v.ty_to_tokens(tokens),
|
||||||
HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens),
|
HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens),
|
||||||
HdlLetKind::Wire(v) => v.ty_to_tokens(tokens),
|
HdlLetKind::Wire(v) => v.ty_to_tokens(tokens),
|
||||||
|
|
@ -913,6 +1014,8 @@ impl HdlLetKindToTokens for HdlLetKind {
|
||||||
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
|
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
match self {
|
match self {
|
||||||
HdlLetKind::IO(v) => v.expr_to_tokens(tokens),
|
HdlLetKind::IO(v) => v.expr_to_tokens(tokens),
|
||||||
|
HdlLetKind::AddPlatformIO(v) => v.expr_to_tokens(tokens),
|
||||||
|
HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens),
|
||||||
HdlLetKind::Instance(v) => v.expr_to_tokens(tokens),
|
HdlLetKind::Instance(v) => v.expr_to_tokens(tokens),
|
||||||
HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens),
|
HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens),
|
||||||
HdlLetKind::Wire(v) => v.expr_to_tokens(tokens),
|
HdlLetKind::Wire(v) => v.expr_to_tokens(tokens),
|
||||||
|
|
@ -925,7 +1028,7 @@ with_debug_clone_and_fold! {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) struct HdlLet<Kind = HdlLetKind> {
|
pub(crate) struct HdlLet<Kind = HdlLetKind> {
|
||||||
pub(crate) attrs: Vec<Attribute>,
|
pub(crate) attrs: Vec<Attribute>,
|
||||||
pub(crate) hdl_attr: HdlAttr<Nothing>,
|
pub(crate) hdl_attr: HdlAttr<syn::parse::Nothing, kw::hdl>,
|
||||||
pub(crate) let_token: Token![let],
|
pub(crate) let_token: Token![let],
|
||||||
pub(crate) mut_token: Option<Token![mut]>,
|
pub(crate) mut_token: Option<Token![mut]>,
|
||||||
pub(crate) name: Ident,
|
pub(crate) name: Ident,
|
||||||
|
|
@ -1082,7 +1185,7 @@ fn parse_quote_let_pat<T, R: ToTokens, C: Borrow<Token![:]>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
|
pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
|
||||||
parse_quote_spanned! {ty.span()=>
|
parse_quote_spanned! {ty.span()=>
|
||||||
::fayalite::expr::Expr<#ty>
|
::fayalite::expr::Expr<#ty>
|
||||||
}
|
}
|
||||||
|
|
@ -1112,30 +1215,41 @@ impl<T: ToString> ToTokens for ImplicitName<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Visitor<'a> {
|
struct Visitor<'a> {
|
||||||
module_kind: ModuleKind,
|
module_kind: Option<ModuleKind>,
|
||||||
errors: Errors,
|
errors: Errors,
|
||||||
io: Vec<ModuleIO>,
|
io: ModuleIOOrAddPlatformIO,
|
||||||
block_depth: usize,
|
block_depth: usize,
|
||||||
parsed_generics: &'a ParsedGenerics,
|
parsed_generics: &'a ParsedGenerics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visitor<'_> {
|
impl Visitor<'_> {
|
||||||
fn take_hdl_attr<T: Parse>(&mut self, attrs: &mut Vec<Attribute>) -> Option<HdlAttr<T>> {
|
fn take_hdl_attr<T: Parse>(
|
||||||
|
&mut self,
|
||||||
|
attrs: &mut Vec<Attribute>,
|
||||||
|
) -> Option<HdlAttr<T, kw::hdl>> {
|
||||||
self.errors.unwrap_or(
|
self.errors.unwrap_or(
|
||||||
HdlAttr::parse_and_take_attr(attrs),
|
HdlAttr::parse_and_take_attr(attrs),
|
||||||
Some(syn::parse2::<T>(quote! {}).unwrap().into()),
|
Some(syn::parse2::<T>(quote! {}).unwrap().into()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn require_normal_module(&mut self, spanned: impl ToTokens) {
|
fn require_normal_module_or_fn(&mut self, spanned: impl ToTokens) {
|
||||||
match self.module_kind {
|
match self.module_kind {
|
||||||
ModuleKind::Extern => {
|
Some(ModuleKind::Extern) => {
|
||||||
self.errors
|
self.errors
|
||||||
.error(spanned, "not allowed in #[hdl_module(extern)]");
|
.error(spanned, "not allowed in #[hdl_module(extern)]");
|
||||||
}
|
}
|
||||||
ModuleKind::Normal => {}
|
Some(ModuleKind::Normal) | None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<Nothing>, expr_if: ExprIf) -> Expr {
|
fn require_module(&mut self, spanned: impl ToTokens) {
|
||||||
|
match self.module_kind {
|
||||||
|
None => {
|
||||||
|
self.errors.error(spanned, "not allowed in #[hdl] fn");
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<ExprOptions, kw::hdl>, expr_if: ExprIf) -> Expr {
|
||||||
let ExprIf {
|
let ExprIf {
|
||||||
attrs,
|
attrs,
|
||||||
if_token,
|
if_token,
|
||||||
|
|
@ -1143,10 +1257,10 @@ impl Visitor<'_> {
|
||||||
then_branch,
|
then_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
} = expr_if;
|
} = expr_if;
|
||||||
self.require_normal_module(if_token);
|
let (else_token, else_expr) = else_branch.unzip();
|
||||||
let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr {
|
let else_expr = else_expr.map(|else_expr| match *else_expr {
|
||||||
Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if),
|
Expr::If(expr_if) => Box::new(self.process_hdl_if(hdl_attr.clone(), expr_if)),
|
||||||
expr => expr,
|
_ => else_expr,
|
||||||
});
|
});
|
||||||
if let Expr::Let(ExprLet {
|
if let Expr::Let(ExprLet {
|
||||||
attrs: let_attrs,
|
attrs: let_attrs,
|
||||||
|
|
@ -1168,7 +1282,19 @@ impl Visitor<'_> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(else_expr) = else_expr {
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
if sim.is_some() {
|
||||||
|
ExprIf {
|
||||||
|
attrs,
|
||||||
|
if_token,
|
||||||
|
cond: parse_quote_spanned! {if_token.span=>
|
||||||
|
*::fayalite::sim::value::SimValue::<::fayalite::int::Bool>::value(&::fayalite::sim::value::ToSimValue::into_sim_value(#cond))
|
||||||
|
},
|
||||||
|
then_branch,
|
||||||
|
else_branch: else_token.zip(else_expr),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else if let Some(else_expr) = else_expr {
|
||||||
parse_quote_spanned! {if_token.span=>
|
parse_quote_spanned! {if_token.span=>
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
{
|
{
|
||||||
|
|
@ -1208,11 +1334,12 @@ impl Visitor<'_> {
|
||||||
.to_tokens(expr);
|
.to_tokens(expr);
|
||||||
});
|
});
|
||||||
let mut attrs = hdl_let.attrs.clone();
|
let mut attrs = hdl_let.attrs.clone();
|
||||||
|
self.require_module(kind);
|
||||||
match self.module_kind {
|
match self.module_kind {
|
||||||
ModuleKind::Extern => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
|
Some(ModuleKind::Extern) => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
}),
|
}),
|
||||||
ModuleKind::Normal => {}
|
Some(ModuleKind::Normal) | None => {}
|
||||||
}
|
}
|
||||||
let let_stmt = Local {
|
let let_stmt = Local {
|
||||||
attrs,
|
attrs,
|
||||||
|
|
@ -1230,7 +1357,81 @@ impl Visitor<'_> {
|
||||||
}),
|
}),
|
||||||
semi_token: hdl_let.semi_token,
|
semi_token: hdl_let.semi_token,
|
||||||
};
|
};
|
||||||
self.io.push(hdl_let);
|
match &mut self.io {
|
||||||
|
ModuleIOOrAddPlatformIO::ModuleIO(io) => io.push(hdl_let),
|
||||||
|
ModuleIOOrAddPlatformIO::AddPlatformIO => {
|
||||||
|
self.errors.error(
|
||||||
|
kind,
|
||||||
|
"can't have other inputs/outputs in a module using m.add_platform_io()",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let_stmt
|
||||||
|
}
|
||||||
|
fn process_hdl_let_add_platform_io(
|
||||||
|
&mut self,
|
||||||
|
hdl_let: HdlLet<HdlLetKindAddPlatformIO>,
|
||||||
|
) -> Local {
|
||||||
|
let HdlLet {
|
||||||
|
mut attrs,
|
||||||
|
hdl_attr: _,
|
||||||
|
let_token,
|
||||||
|
mut_token,
|
||||||
|
ref name,
|
||||||
|
eq_token,
|
||||||
|
kind:
|
||||||
|
HdlLetKindAddPlatformIO {
|
||||||
|
m,
|
||||||
|
dot_token,
|
||||||
|
add_platform_io,
|
||||||
|
paren,
|
||||||
|
platform_io_builder,
|
||||||
|
},
|
||||||
|
semi_token,
|
||||||
|
} = hdl_let;
|
||||||
|
let mut expr = quote! {#m #dot_token #add_platform_io};
|
||||||
|
paren.surround(&mut expr, |expr| {
|
||||||
|
let name_str = ImplicitName {
|
||||||
|
name,
|
||||||
|
span: name.span(),
|
||||||
|
};
|
||||||
|
quote_spanned! {name.span()=>
|
||||||
|
#name_str, #platform_io_builder
|
||||||
|
}
|
||||||
|
.to_tokens(expr);
|
||||||
|
});
|
||||||
|
self.require_module(add_platform_io);
|
||||||
|
attrs.push(parse_quote_spanned! {let_token.span=>
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
});
|
||||||
|
let let_stmt = Local {
|
||||||
|
attrs,
|
||||||
|
let_token,
|
||||||
|
pat: parse_quote! { #mut_token #name },
|
||||||
|
init: Some(LocalInit {
|
||||||
|
eq_token,
|
||||||
|
expr: parse_quote! { #expr },
|
||||||
|
diverge: None,
|
||||||
|
}),
|
||||||
|
semi_token,
|
||||||
|
};
|
||||||
|
match &mut self.io {
|
||||||
|
ModuleIOOrAddPlatformIO::ModuleIO(io) => {
|
||||||
|
for io in io {
|
||||||
|
self.errors.error(
|
||||||
|
io.kind.kind,
|
||||||
|
"can't have other inputs/outputs in a module using m.add_platform_io()",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModuleIOOrAddPlatformIO::AddPlatformIO => {
|
||||||
|
self.errors.error(
|
||||||
|
add_platform_io,
|
||||||
|
"can't use m.add_platform_io() more than once in a single module",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.io = ModuleIOOrAddPlatformIO::AddPlatformIO;
|
||||||
let_stmt
|
let_stmt
|
||||||
}
|
}
|
||||||
fn process_hdl_let_instance(&mut self, hdl_let: HdlLet<HdlLetKindInstance>) -> Local {
|
fn process_hdl_let_instance(&mut self, hdl_let: HdlLet<HdlLetKindInstance>) -> Local {
|
||||||
|
|
@ -1249,7 +1450,7 @@ impl Visitor<'_> {
|
||||||
},
|
},
|
||||||
semi_token,
|
semi_token,
|
||||||
} = hdl_let;
|
} = hdl_let;
|
||||||
self.require_normal_module(instance);
|
self.require_normal_module_or_fn(instance);
|
||||||
let mut expr = instance.to_token_stream();
|
let mut expr = instance.to_token_stream();
|
||||||
paren.surround(&mut expr, |expr| {
|
paren.surround(&mut expr, |expr| {
|
||||||
let name_str = ImplicitName {
|
let name_str = ImplicitName {
|
||||||
|
|
@ -1276,7 +1477,7 @@ impl Visitor<'_> {
|
||||||
fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet<HdlLetKindRegBuilder>) -> Local {
|
fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet<HdlLetKindRegBuilder>) -> Local {
|
||||||
let name = &hdl_let.name;
|
let name = &hdl_let.name;
|
||||||
let reg_builder = hdl_let.kind.reg_builder;
|
let reg_builder = hdl_let.kind.reg_builder;
|
||||||
self.require_normal_module(reg_builder);
|
self.require_normal_module_or_fn(reg_builder);
|
||||||
let mut expr = reg_builder.to_token_stream();
|
let mut expr = reg_builder.to_token_stream();
|
||||||
hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| {
|
hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| {
|
||||||
let name_str = ImplicitName {
|
let name_str = ImplicitName {
|
||||||
|
|
@ -1301,7 +1502,7 @@ impl Visitor<'_> {
|
||||||
no_reset.to_tokens(&mut expr);
|
no_reset.to_tokens(&mut expr);
|
||||||
paren.surround(&mut expr, |expr| ty_expr.to_tokens(expr));
|
paren.surround(&mut expr, |expr| ty_expr.to_tokens(expr));
|
||||||
}
|
}
|
||||||
RegBuilderReset::Reset { .. } | RegBuilderReset::ResetDefault { .. } => {
|
RegBuilderReset::Reset { .. } => {
|
||||||
hdl_let.kind.reset.to_tokens(&mut expr);
|
hdl_let.kind.reset.to_tokens(&mut expr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1327,7 +1528,7 @@ impl Visitor<'_> {
|
||||||
fn process_hdl_let_wire(&mut self, hdl_let: HdlLet<HdlLetKindWire>) -> Local {
|
fn process_hdl_let_wire(&mut self, hdl_let: HdlLet<HdlLetKindWire>) -> Local {
|
||||||
let name = &hdl_let.name;
|
let name = &hdl_let.name;
|
||||||
let wire = hdl_let.kind.wire;
|
let wire = hdl_let.kind.wire;
|
||||||
self.require_normal_module(wire);
|
self.require_normal_module_or_fn(wire);
|
||||||
let ty_expr = unwrap_or_static_type(hdl_let.kind.ty_expr.as_ref(), wire.span());
|
let ty_expr = unwrap_or_static_type(hdl_let.kind.ty_expr.as_ref(), wire.span());
|
||||||
let mut expr = wire.to_token_stream();
|
let mut expr = wire.to_token_stream();
|
||||||
hdl_let.kind.paren.surround(&mut expr, |expr| {
|
hdl_let.kind.paren.surround(&mut expr, |expr| {
|
||||||
|
|
@ -1357,11 +1558,36 @@ impl Visitor<'_> {
|
||||||
semi_token: hdl_let.semi_token,
|
semi_token: hdl_let.semi_token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn process_hdl_let_incomplete(&mut self, hdl_let: HdlLet<HdlLetKindIncomplete>) -> Local {
|
||||||
|
let name = &hdl_let.name;
|
||||||
|
let kind = hdl_let.kind.kind;
|
||||||
|
self.require_normal_module_or_fn(kind);
|
||||||
|
let mut expr = kind.to_token_stream();
|
||||||
|
hdl_let.kind.paren.surround(&mut expr, |expr| {
|
||||||
|
ImplicitName {
|
||||||
|
name,
|
||||||
|
span: name.span(),
|
||||||
|
}
|
||||||
|
.to_tokens(expr);
|
||||||
|
});
|
||||||
|
let mut_token = &hdl_let.mut_token;
|
||||||
|
Local {
|
||||||
|
attrs: hdl_let.attrs.clone(),
|
||||||
|
let_token: hdl_let.let_token,
|
||||||
|
pat: parse_quote! { #mut_token #name },
|
||||||
|
init: Some(LocalInit {
|
||||||
|
eq_token: hdl_let.eq_token,
|
||||||
|
expr: parse_quote! { #expr },
|
||||||
|
diverge: None,
|
||||||
|
}),
|
||||||
|
semi_token: hdl_let.semi_token,
|
||||||
|
}
|
||||||
|
}
|
||||||
fn process_hdl_let_memory(&mut self, hdl_let: HdlLet<HdlLetKindMemory>) -> Local {
|
fn process_hdl_let_memory(&mut self, hdl_let: HdlLet<HdlLetKindMemory>) -> Local {
|
||||||
let name = &hdl_let.name;
|
let name = &hdl_let.name;
|
||||||
let memory_fn = hdl_let.kind.memory_fn;
|
let memory_fn = hdl_let.kind.memory_fn;
|
||||||
let memory_fn_name = memory_fn.name();
|
let memory_fn_name = memory_fn.name();
|
||||||
self.require_normal_module(memory_fn_name);
|
self.require_normal_module_or_fn(memory_fn_name);
|
||||||
let mut expr = memory_fn_name.to_token_stream();
|
let mut expr = memory_fn_name.to_token_stream();
|
||||||
let (paren, arg) = match memory_fn {
|
let (paren, arg) = match memory_fn {
|
||||||
MemoryFn::Memory {
|
MemoryFn::Memory {
|
||||||
|
|
@ -1426,6 +1652,8 @@ impl Visitor<'_> {
|
||||||
}
|
}
|
||||||
the_match! {
|
the_match! {
|
||||||
IO => process_hdl_let_io,
|
IO => process_hdl_let_io,
|
||||||
|
AddPlatformIO => process_hdl_let_add_platform_io,
|
||||||
|
Incomplete => process_hdl_let_incomplete,
|
||||||
Instance => process_hdl_let_instance,
|
Instance => process_hdl_let_instance,
|
||||||
RegBuilder => process_hdl_let_reg_builder,
|
RegBuilder => process_hdl_let_reg_builder,
|
||||||
Wire => process_hdl_let_wire,
|
Wire => process_hdl_let_wire,
|
||||||
|
|
@ -1521,7 +1749,7 @@ impl Visitor<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_let() -> Local {
|
pub(crate) fn empty_let() -> Local {
|
||||||
Local {
|
Local {
|
||||||
attrs: vec![],
|
attrs: vec![],
|
||||||
let_token: Default::default(),
|
let_token: Default::default(),
|
||||||
|
|
@ -1543,7 +1771,7 @@ impl Fold for Visitor<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
|
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
|
||||||
if is_hdl_attr(&attr) {
|
if is_hdl_attr::<kw::hdl>(&attr) {
|
||||||
self.errors
|
self.errors
|
||||||
.error(&attr, "#[hdl] attribute not supported here");
|
.error(&attr, "#[hdl] attribute not supported here");
|
||||||
}
|
}
|
||||||
|
|
@ -1603,19 +1831,42 @@ impl Fold for Visitor<'_> {
|
||||||
Repeat => process_hdl_repeat,
|
Repeat => process_hdl_repeat,
|
||||||
Struct => process_hdl_struct,
|
Struct => process_hdl_struct,
|
||||||
Tuple => process_hdl_tuple,
|
Tuple => process_hdl_tuple,
|
||||||
|
MethodCall => process_hdl_method_call,
|
||||||
|
Call => process_hdl_call,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fold_local(&mut self, let_stmt: Local) -> Local {
|
fn fold_local(&mut self, mut let_stmt: Local) -> Local {
|
||||||
match self
|
match self
|
||||||
.errors
|
.errors
|
||||||
.ok(HdlAttr::<Nothing>::parse_and_leave_attr(&let_stmt.attrs))
|
.ok(HdlAttr::<ExprOptions, kw::hdl>::parse_and_leave_attr(
|
||||||
{
|
&let_stmt.attrs,
|
||||||
|
)) {
|
||||||
None => return empty_let(),
|
None => return empty_let(),
|
||||||
Some(None) => return fold_local(self, let_stmt),
|
Some(None) => return fold_local(self, let_stmt),
|
||||||
Some(Some(HdlAttr { .. })) => {}
|
Some(Some(HdlAttr { .. })) => {}
|
||||||
};
|
};
|
||||||
|
let mut pat = &let_stmt.pat;
|
||||||
|
if let Pat::Type(pat_type) = pat {
|
||||||
|
pat = &pat_type.pat;
|
||||||
|
}
|
||||||
|
let Pat::Ident(syn::PatIdent {
|
||||||
|
attrs: _,
|
||||||
|
by_ref: None,
|
||||||
|
mutability: _,
|
||||||
|
ident: _,
|
||||||
|
subpat: None,
|
||||||
|
}) = pat
|
||||||
|
else {
|
||||||
|
let hdl_attr =
|
||||||
|
HdlAttr::<ExprOptions, kw::hdl>::parse_and_take_attr(&mut let_stmt.attrs)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.expect("already checked above");
|
||||||
|
let let_stmt = fold_local(self, let_stmt);
|
||||||
|
return self.process_hdl_let_pat(hdl_attr, let_stmt);
|
||||||
|
};
|
||||||
let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream());
|
let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream());
|
||||||
let Some(hdl_let) = self.errors.ok(hdl_let) else {
|
let Some(hdl_let) = self.errors.ok(hdl_let) else {
|
||||||
return empty_let();
|
return empty_let();
|
||||||
|
|
@ -1645,15 +1896,20 @@ impl Fold for Visitor<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ModuleIOOrAddPlatformIO {
|
||||||
|
ModuleIO(Vec<ModuleIO>),
|
||||||
|
AddPlatformIO,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn transform_body(
|
pub(crate) fn transform_body(
|
||||||
module_kind: ModuleKind,
|
module_kind: Option<ModuleKind>,
|
||||||
mut body: Box<Block>,
|
mut body: Box<Block>,
|
||||||
parsed_generics: &ParsedGenerics,
|
parsed_generics: &ParsedGenerics,
|
||||||
) -> syn::Result<(Box<Block>, Vec<ModuleIO>)> {
|
) -> syn::Result<(Box<Block>, ModuleIOOrAddPlatformIO)> {
|
||||||
let mut visitor = Visitor {
|
let mut visitor = Visitor {
|
||||||
module_kind,
|
module_kind,
|
||||||
errors: Errors::new(),
|
errors: Errors::new(),
|
||||||
io: vec![],
|
io: ModuleIOOrAddPlatformIO::ModuleIO(vec![]),
|
||||||
block_depth: 0,
|
block_depth: 0,
|
||||||
parsed_generics,
|
parsed_generics,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,105 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{module::transform_body::Visitor, HdlAttr};
|
|
||||||
|
use crate::{
|
||||||
|
HdlAttr, kw,
|
||||||
|
module::transform_body::{
|
||||||
|
ExprOptions, Visitor,
|
||||||
|
expand_match::{EnumPath, parse_enum_path},
|
||||||
|
},
|
||||||
|
};
|
||||||
use quote::{format_ident, quote_spanned};
|
use quote::{format_ident, quote_spanned};
|
||||||
|
use std::mem;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath,
|
Expr, ExprArray, ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat,
|
||||||
ExprRepeat, ExprStruct, ExprTuple, FieldValue, TypePath,
|
ExprStruct, ExprTuple, FieldValue, Token, TypePath, parse_quote_spanned,
|
||||||
|
punctuated::Punctuated, spanned::Spanned, token::Paren,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Visitor<'_> {
|
impl Visitor<'_> {
|
||||||
pub(crate) fn process_hdl_array(
|
pub(crate) fn process_hdl_array(
|
||||||
&mut self,
|
&mut self,
|
||||||
hdl_attr: HdlAttr<Nothing>,
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
mut expr_array: ExprArray,
|
mut expr_array: ExprArray,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
self.require_normal_module(hdl_attr);
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
let span = hdl_attr.kw.span;
|
||||||
|
if sim.is_some() {
|
||||||
|
for elem in &mut expr_array.elems {
|
||||||
|
*elem = parse_quote_spanned! {elem.span()=>
|
||||||
|
::fayalite::sim::value::ToSimValue::to_sim_value(&(#elem))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_array)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for elem in &mut expr_array.elems {
|
for elem in &mut expr_array.elems {
|
||||||
*elem = parse_quote_spanned! {elem.span()=>
|
*elem = parse_quote_spanned! {elem.span()=>
|
||||||
::fayalite::expr::ToExpr::to_expr(&(#elem))
|
::fayalite::expr::ToExpr::to_expr(&(#elem))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_array)}
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::expr::ToExpr::to_expr(&#expr_array)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn process_hdl_repeat(
|
pub(crate) fn process_hdl_repeat(
|
||||||
&mut self,
|
&mut self,
|
||||||
hdl_attr: HdlAttr<Nothing>,
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
mut expr_repeat: ExprRepeat,
|
mut expr_repeat: ExprRepeat,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
self.require_normal_module(hdl_attr);
|
|
||||||
let repeated_value = &expr_repeat.expr;
|
let repeated_value = &expr_repeat.expr;
|
||||||
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
let span = hdl_attr.kw.span;
|
||||||
|
if sim.is_some() {
|
||||||
|
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
|
||||||
|
::fayalite::sim::value::ToSimValue::to_sim_value(&(#repeated_value))
|
||||||
|
};
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_repeat)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
|
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
|
||||||
::fayalite::expr::ToExpr::to_expr(&(#repeated_value))
|
::fayalite::expr::ToExpr::to_expr(&(#repeated_value))
|
||||||
};
|
};
|
||||||
parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_repeat)}
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::expr::ToExpr::to_expr(&#expr_repeat)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub(crate) fn process_hdl_struct(
|
pub(crate) fn process_hdl_struct(
|
||||||
&mut self,
|
&mut self,
|
||||||
hdl_attr: HdlAttr<Nothing>,
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
expr_struct: ExprStruct,
|
mut expr_struct: ExprStruct,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
self.require_normal_module(&hdl_attr);
|
|
||||||
let name_span = expr_struct.path.segments.last().unwrap().ident.span();
|
let name_span = expr_struct.path.segments.last().unwrap().ident.span();
|
||||||
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
if sim.is_some() {
|
||||||
|
let ty_path = TypePath {
|
||||||
|
qself: expr_struct.qself.take(),
|
||||||
|
path: expr_struct.path,
|
||||||
|
};
|
||||||
|
expr_struct.path = parse_quote_spanned! {name_span=>
|
||||||
|
__SimValue::<#ty_path>
|
||||||
|
};
|
||||||
|
for field in &mut expr_struct.fields {
|
||||||
|
let expr = &field.expr;
|
||||||
|
field.expr = parse_quote_spanned! {field.member.span()=>
|
||||||
|
::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr))
|
||||||
|
};
|
||||||
|
field
|
||||||
|
.colon_token
|
||||||
|
.get_or_insert(Token));
|
||||||
|
}
|
||||||
|
return parse_quote_spanned! {name_span=>
|
||||||
|
{
|
||||||
|
type __SimValue<T> = <T as ::fayalite::ty::Type>::SimValue;
|
||||||
|
let value: ::fayalite::sim::value::SimValue<#ty_path> = ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_struct);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
let builder_ident = format_ident!("__builder", span = name_span);
|
let builder_ident = format_ident!("__builder", span = name_span);
|
||||||
let empty_builder = if expr_struct.qself.is_some()
|
let empty_builder = if expr_struct.qself.is_some()
|
||||||
|| expr_struct
|
|| expr_struct
|
||||||
|
|
@ -91,12 +151,126 @@ impl Visitor<'_> {
|
||||||
}
|
}
|
||||||
pub(crate) fn process_hdl_tuple(
|
pub(crate) fn process_hdl_tuple(
|
||||||
&mut self,
|
&mut self,
|
||||||
hdl_attr: HdlAttr<Nothing>,
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
expr_tuple: ExprTuple,
|
mut expr_tuple: ExprTuple,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
self.require_normal_module(hdl_attr);
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
if sim.is_some() {
|
||||||
|
for element in &mut expr_tuple.elems {
|
||||||
|
*element = parse_quote_spanned! {element.span()=>
|
||||||
|
&(#element)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
parse_quote_spanned! {expr_tuple.span()=>
|
||||||
|
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_tuple)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
parse_quote_spanned! {expr_tuple.span()=>
|
parse_quote_spanned! {expr_tuple.span()=>
|
||||||
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
|
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn process_hdl_call(
|
||||||
|
&mut self,
|
||||||
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
|
mut expr_call: ExprCall,
|
||||||
|
) -> Expr {
|
||||||
|
let span = hdl_attr.kw.span;
|
||||||
|
let mut func = &mut *expr_call.func;
|
||||||
|
let EnumPath {
|
||||||
|
variant_path: _,
|
||||||
|
enum_path,
|
||||||
|
variant_name,
|
||||||
|
} = loop {
|
||||||
|
match func {
|
||||||
|
Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
|
||||||
|
func = &mut **expr;
|
||||||
|
}
|
||||||
|
Expr::Path(_) => {
|
||||||
|
let Expr::Path(ExprPath { attrs, qself, path }) =
|
||||||
|
mem::replace(func, Expr::PLACEHOLDER)
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
match parse_enum_path(TypePath { qself, path }) {
|
||||||
|
Ok(path) => break path,
|
||||||
|
Err(path) => {
|
||||||
|
self.errors.error(&path, "unsupported enum variant path");
|
||||||
|
let TypePath { qself, path } = path;
|
||||||
|
*func = ExprPath { attrs, qself, path }.into();
|
||||||
|
return expr_call.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.errors.error(
|
||||||
|
&expr_call.func,
|
||||||
|
"#[hdl] function call -- function must be a possibly-parenthesized path",
|
||||||
|
);
|
||||||
|
return expr_call.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.process_hdl_method_call(
|
||||||
|
hdl_attr,
|
||||||
|
ExprMethodCall {
|
||||||
|
attrs: expr_call.attrs,
|
||||||
|
receiver: parse_quote_spanned! {span=>
|
||||||
|
<#enum_path as ::fayalite::ty::StaticType>::TYPE
|
||||||
|
},
|
||||||
|
dot_token: Token,
|
||||||
|
method: variant_name,
|
||||||
|
turbofish: None,
|
||||||
|
paren_token: expr_call.paren_token,
|
||||||
|
args: expr_call.args,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub(crate) fn process_hdl_method_call(
|
||||||
|
&mut self,
|
||||||
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
|
mut expr_method_call: ExprMethodCall,
|
||||||
|
) -> Expr {
|
||||||
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
let span = hdl_attr.kw.span;
|
||||||
|
// remove any number of groups and up to one paren
|
||||||
|
let mut receiver = &mut *expr_method_call.receiver;
|
||||||
|
let mut has_group = false;
|
||||||
|
let receiver = loop {
|
||||||
|
match receiver {
|
||||||
|
Expr::Group(ExprGroup { expr, .. }) => {
|
||||||
|
has_group = true;
|
||||||
|
receiver = expr;
|
||||||
|
}
|
||||||
|
Expr::Paren(ExprParen { expr, .. }) => break &mut **expr,
|
||||||
|
receiver @ Expr::Path(_) => break receiver,
|
||||||
|
_ => {
|
||||||
|
if !has_group {
|
||||||
|
self.errors.error(
|
||||||
|
&expr_method_call.receiver,
|
||||||
|
"#[hdl] on a method call needs parenthesized receiver",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break &mut *expr_method_call.receiver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let func = if sim.is_some() {
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::enum_::enum_type_to_sim_builder
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
::fayalite::enum_::assert_is_enum_type
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*expr_method_call.receiver = ExprCall {
|
||||||
|
attrs: vec![],
|
||||||
|
func,
|
||||||
|
paren_token: Paren(span),
|
||||||
|
args: Punctuated::from_iter([mem::replace(receiver, Expr::PLACEHOLDER)]),
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
expr_method_call.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,132 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
fold::{impl_fold, DoFold},
|
|
||||||
module::transform_body::{with_debug_clone_and_fold, Visitor},
|
|
||||||
Errors, HdlAttr, PairsIterExt,
|
Errors, HdlAttr, PairsIterExt,
|
||||||
|
fold::{DoFold, impl_fold},
|
||||||
|
kw,
|
||||||
|
module::transform_body::{
|
||||||
|
ExprOptions, Visitor, empty_let, with_debug_clone_and_fold, wrap_ty_with_expr,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
|
use quote::{ToTokens, TokenStreamExt, format_ident, quote_spanned};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use syn::{
|
use syn::{
|
||||||
fold::{fold_arm, fold_expr_match, fold_pat, Fold},
|
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr,
|
||||||
parse::Nothing,
|
PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment,
|
||||||
|
Token, TypePath,
|
||||||
|
fold::{Fold, fold_arm, fold_expr_match, fold_local, fold_pat},
|
||||||
parse_quote_spanned,
|
parse_quote_spanned,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
token::{Brace, Paren},
|
token::{Brace, Paren},
|
||||||
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen,
|
|
||||||
PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
macro_rules! visit_trait {
|
||||||
|
(
|
||||||
|
$($vis:vis fn $fn:ident($state:ident: _, $value:ident: &mut $Value:ty) $block:block)*
|
||||||
|
) => {
|
||||||
|
trait VisitMatchPat<'a> {
|
||||||
|
$(fn $fn(&mut self, $value: &'a mut $Value) {
|
||||||
|
$fn(self, $value);
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
|
||||||
|
$($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a mut $Value) $block)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_trait! {
|
||||||
|
fn visit_match_pat_binding(_state: _, v: &mut MatchPatBinding) {
|
||||||
|
let MatchPatBinding { mutability: _, ident: _ } = v;
|
||||||
|
}
|
||||||
|
fn visit_match_pat_wild(_state: _, v: &mut MatchPatWild) {
|
||||||
|
let MatchPatWild { underscore_token: _ } = v;
|
||||||
|
}
|
||||||
|
fn visit_match_pat_rest(_state: _, v: &mut MatchPatRest) {
|
||||||
|
let MatchPatRest { dot2_token: _ } = v;
|
||||||
|
}
|
||||||
|
fn visit_match_pat_paren(state: _, v: &mut MatchPatParen<MatchPat>) {
|
||||||
|
let MatchPatParen { paren_token: _, pat } = v;
|
||||||
|
state.visit_match_pat(pat);
|
||||||
|
}
|
||||||
|
fn visit_match_pat_paren_simple(state: _, v: &mut MatchPatParen<MatchPatSimple>) {
|
||||||
|
let MatchPatParen { paren_token: _, pat } = v;
|
||||||
|
state.visit_match_pat_simple(pat);
|
||||||
|
}
|
||||||
|
fn visit_match_pat_or(state: _, v: &mut MatchPatOr<MatchPat>) {
|
||||||
|
let MatchPatOr { leading_vert: _, cases } = v;
|
||||||
|
for v in cases {
|
||||||
|
state.visit_match_pat(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat_or_simple(state: _, v: &mut MatchPatOr<MatchPatSimple>) {
|
||||||
|
let MatchPatOr { leading_vert: _, cases } = v;
|
||||||
|
for v in cases {
|
||||||
|
state.visit_match_pat_simple(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat_struct_field(state: _, v: &mut MatchPatStructField) {
|
||||||
|
let MatchPatStructField { field_name: _, colon_token: _, pat } = v;
|
||||||
|
state.visit_match_pat_simple(pat);
|
||||||
|
}
|
||||||
|
fn visit_match_pat_struct(state: _, v: &mut MatchPatStruct) {
|
||||||
|
let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v;
|
||||||
|
for v in fields {
|
||||||
|
state.visit_match_pat_struct_field(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat_tuple(state: _, v: &mut MatchPatTuple) {
|
||||||
|
let MatchPatTuple { paren_token: _, fields } = v;
|
||||||
|
for v in fields {
|
||||||
|
state.visit_match_pat_simple(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat_enum_variant(state: _, v: &mut MatchPatEnumVariant) {
|
||||||
|
let MatchPatEnumVariant {
|
||||||
|
match_span:_,
|
||||||
|
sim:_,
|
||||||
|
variant_path: _,
|
||||||
|
enum_path: _,
|
||||||
|
variant_name: _,
|
||||||
|
field,
|
||||||
|
} = v;
|
||||||
|
if let Some((_, v)) = field {
|
||||||
|
state.visit_match_pat_simple(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat_simple(state: _, v: &mut MatchPatSimple) {
|
||||||
|
match v {
|
||||||
|
MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v),
|
||||||
|
MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v),
|
||||||
|
MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v),
|
||||||
|
MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v),
|
||||||
|
MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn visit_match_pat(state: _, v: &mut MatchPat) {
|
||||||
|
match v {
|
||||||
|
MatchPat::Simple(v) => state.visit_match_pat_simple(v),
|
||||||
|
MatchPat::Or(v) => state.visit_match_pat_or(v),
|
||||||
|
MatchPat::Paren(v) => state.visit_match_pat_paren(v),
|
||||||
|
MatchPat::Struct(v) => state.visit_match_pat_struct(v),
|
||||||
|
MatchPat::Tuple(v) => state.visit_match_pat_tuple(v),
|
||||||
|
MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with_debug_clone_and_fold! {
|
with_debug_clone_and_fold! {
|
||||||
struct MatchPatBinding<> {
|
struct MatchPatBinding<> {
|
||||||
|
mutability: Option<Token![mut]>,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for MatchPatBinding {
|
impl ToTokens for MatchPatBinding {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let Self { ident } = self;
|
let Self { mutability, ident } = self;
|
||||||
|
mutability.to_tokens(tokens);
|
||||||
ident.to_tokens(tokens);
|
ident.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +152,15 @@ with_debug_clone_and_fold! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P> MatchPatOr<P> {
|
||||||
|
/// returns the first `|` between two patterns
|
||||||
|
fn first_inner_vert(&self) -> Option<Token![|]> {
|
||||||
|
let mut pairs = self.cases.pairs();
|
||||||
|
pairs.next_back();
|
||||||
|
pairs.next().and_then(|v| v.into_tuple().1.copied())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: ToTokens> ToTokens for MatchPatOr<P> {
|
impl<P: ToTokens> ToTokens for MatchPatOr<P> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let Self {
|
let Self {
|
||||||
|
|
@ -76,6 +185,19 @@ impl ToTokens for MatchPatWild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with_debug_clone_and_fold! {
|
||||||
|
struct MatchPatRest<> {
|
||||||
|
dot2_token: Token![..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MatchPatRest {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self { dot2_token } = self;
|
||||||
|
dot2_token.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with_debug_clone_and_fold! {
|
with_debug_clone_and_fold! {
|
||||||
struct MatchPatStructField<> {
|
struct MatchPatStructField<> {
|
||||||
field_name: Ident,
|
field_name: Ident,
|
||||||
|
|
@ -91,12 +213,20 @@ impl ToTokens for MatchPatStructField {
|
||||||
colon_token,
|
colon_token,
|
||||||
pat,
|
pat,
|
||||||
} = self;
|
} = self;
|
||||||
field_name.to_tokens(tokens);
|
if let (
|
||||||
if let (None, MatchPatSimple::Binding(MatchPatBinding { ident })) = (colon_token, pat) {
|
None,
|
||||||
|
MatchPatSimple::Binding(MatchPatBinding {
|
||||||
|
mutability: _,
|
||||||
|
ident,
|
||||||
|
}),
|
||||||
|
) = (colon_token, pat)
|
||||||
|
{
|
||||||
if field_name == ident {
|
if field_name == ident {
|
||||||
|
pat.to_tokens(tokens);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
field_name.to_tokens(tokens);
|
||||||
colon_token
|
colon_token
|
||||||
.unwrap_or_else(|| Token))
|
.unwrap_or_else(|| Token))
|
||||||
.to_tokens(tokens);
|
.to_tokens(tokens);
|
||||||
|
|
@ -158,9 +288,29 @@ impl ToTokens for MatchPatStruct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with_debug_clone_and_fold! {
|
||||||
|
struct MatchPatTuple<> {
|
||||||
|
paren_token: Paren,
|
||||||
|
fields: Punctuated<MatchPatSimple, Token![,]>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MatchPatTuple {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let Self {
|
||||||
|
paren_token,
|
||||||
|
fields,
|
||||||
|
} = self;
|
||||||
|
paren_token.surround(tokens, |tokens| {
|
||||||
|
fields.to_tokens(tokens);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with_debug_clone_and_fold! {
|
with_debug_clone_and_fold! {
|
||||||
struct MatchPatEnumVariant<> {
|
struct MatchPatEnumVariant<> {
|
||||||
match_span: Span,
|
match_span: Span,
|
||||||
|
sim: Option<(kw::sim,)>,
|
||||||
variant_path: Path,
|
variant_path: Path,
|
||||||
enum_path: Path,
|
enum_path: Path,
|
||||||
variant_name: Ident,
|
variant_name: Ident,
|
||||||
|
|
@ -172,6 +322,7 @@ impl ToTokens for MatchPatEnumVariant {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let Self {
|
let Self {
|
||||||
match_span,
|
match_span,
|
||||||
|
sim,
|
||||||
variant_path: _,
|
variant_path: _,
|
||||||
enum_path,
|
enum_path,
|
||||||
variant_name,
|
variant_name,
|
||||||
|
|
@ -181,7 +332,28 @@ impl ToTokens for MatchPatEnumVariant {
|
||||||
__MatchTy::<#enum_path>::#variant_name
|
__MatchTy::<#enum_path>::#variant_name
|
||||||
}
|
}
|
||||||
.to_tokens(tokens);
|
.to_tokens(tokens);
|
||||||
|
if sim.is_some() {
|
||||||
if let Some((paren_token, field)) = field {
|
if let Some((paren_token, field)) = field {
|
||||||
|
paren_token.surround(tokens, |tokens| {
|
||||||
|
field.to_tokens(tokens);
|
||||||
|
match field {
|
||||||
|
MatchPatSimple::Paren(_)
|
||||||
|
| MatchPatSimple::Or(_)
|
||||||
|
| MatchPatSimple::Binding(_)
|
||||||
|
| MatchPatSimple::Wild(_) => quote_spanned! {*match_span=>
|
||||||
|
, _
|
||||||
|
}
|
||||||
|
.to_tokens(tokens),
|
||||||
|
MatchPatSimple::Rest(_) => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
quote_spanned! {*match_span=>
|
||||||
|
(_)
|
||||||
|
}
|
||||||
|
.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
} else if let Some((paren_token, field)) = field {
|
||||||
paren_token.surround(tokens, |tokens| field.to_tokens(tokens));
|
paren_token.surround(tokens, |tokens| field.to_tokens(tokens));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,6 +365,7 @@ enum MatchPatSimple {
|
||||||
Or(MatchPatOr<MatchPatSimple>),
|
Or(MatchPatOr<MatchPatSimple>),
|
||||||
Binding(MatchPatBinding),
|
Binding(MatchPatBinding),
|
||||||
Wild(MatchPatWild),
|
Wild(MatchPatWild),
|
||||||
|
Rest(MatchPatRest),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_fold! {
|
impl_fold! {
|
||||||
|
|
@ -201,6 +374,7 @@ impl_fold! {
|
||||||
Or(MatchPatOr<MatchPatSimple>),
|
Or(MatchPatOr<MatchPatSimple>),
|
||||||
Binding(MatchPatBinding),
|
Binding(MatchPatBinding),
|
||||||
Wild(MatchPatWild),
|
Wild(MatchPatWild),
|
||||||
|
Rest(MatchPatRest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,17 +385,18 @@ impl ToTokens for MatchPatSimple {
|
||||||
Self::Paren(v) => v.to_tokens(tokens),
|
Self::Paren(v) => v.to_tokens(tokens),
|
||||||
Self::Binding(v) => v.to_tokens(tokens),
|
Self::Binding(v) => v.to_tokens(tokens),
|
||||||
Self::Wild(v) => v.to_tokens(tokens),
|
Self::Wild(v) => v.to_tokens(tokens),
|
||||||
|
Self::Rest(v) => v.to_tokens(tokens),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EnumPath {
|
pub(crate) struct EnumPath {
|
||||||
variant_path: Path,
|
pub(crate) variant_path: Path,
|
||||||
enum_path: Path,
|
pub(crate) enum_path: Path,
|
||||||
variant_name: Ident,
|
pub(crate) variant_name: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
|
pub(crate) fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
|
||||||
let TypePath {
|
let TypePath {
|
||||||
qself: None,
|
qself: None,
|
||||||
path: variant_path,
|
path: variant_path,
|
||||||
|
|
@ -277,6 +452,7 @@ trait ParseMatchPat: Sized {
|
||||||
fn or(v: MatchPatOr<Self>) -> Self;
|
fn or(v: MatchPatOr<Self>) -> Self;
|
||||||
fn paren(v: MatchPatParen<Self>) -> Self;
|
fn paren(v: MatchPatParen<Self>) -> Self;
|
||||||
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
|
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
|
||||||
|
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()>;
|
||||||
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
|
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
|
||||||
-> Result<Self, ()>;
|
-> Result<Self, ()>;
|
||||||
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
|
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
|
||||||
|
|
@ -284,7 +460,7 @@ trait ParseMatchPat: Sized {
|
||||||
Pat::Ident(PatIdent {
|
Pat::Ident(PatIdent {
|
||||||
attrs: _,
|
attrs: _,
|
||||||
by_ref,
|
by_ref,
|
||||||
mutability,
|
mut mutability,
|
||||||
ident,
|
ident,
|
||||||
subpat,
|
subpat,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -293,10 +469,13 @@ trait ParseMatchPat: Sized {
|
||||||
.errors
|
.errors
|
||||||
.error(by_ref, "ref not allowed in #[hdl] patterns");
|
.error(by_ref, "ref not allowed in #[hdl] patterns");
|
||||||
}
|
}
|
||||||
if let Some(mutability) = mutability {
|
if let Some(mut_token) = mutability {
|
||||||
|
if state.sim.is_none() {
|
||||||
state
|
state
|
||||||
.errors
|
.errors
|
||||||
.error(mutability, "mut not allowed in #[hdl] patterns");
|
.error(mut_token, "mut not allowed in #[hdl] patterns");
|
||||||
|
mutability = None; // avoid duplicate errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some((at_token, _)) = subpat {
|
if let Some((at_token, _)) = subpat {
|
||||||
state
|
state
|
||||||
|
|
@ -308,17 +487,26 @@ trait ParseMatchPat: Sized {
|
||||||
variant_path,
|
variant_path,
|
||||||
enum_path,
|
enum_path,
|
||||||
variant_name,
|
variant_name,
|
||||||
}) => Self::enum_variant(
|
}) => {
|
||||||
|
if let Some(mut_token) = mutability {
|
||||||
|
state
|
||||||
|
.errors
|
||||||
|
.error(mut_token, "mut not allowed on unit variants");
|
||||||
|
}
|
||||||
|
Self::enum_variant(
|
||||||
state,
|
state,
|
||||||
MatchPatEnumVariant {
|
MatchPatEnumVariant {
|
||||||
match_span: state.match_span,
|
match_span: state.match_span,
|
||||||
|
sim: state.sim,
|
||||||
variant_path,
|
variant_path,
|
||||||
enum_path,
|
enum_path,
|
||||||
variant_name,
|
variant_name,
|
||||||
field: None,
|
field: None,
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
|
}
|
||||||
Err(ident) => Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
|
Err(ident) => Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
|
||||||
|
mutability,
|
||||||
ident,
|
ident,
|
||||||
}))),
|
}))),
|
||||||
}
|
}
|
||||||
|
|
@ -358,6 +546,7 @@ trait ParseMatchPat: Sized {
|
||||||
state,
|
state,
|
||||||
MatchPatEnumVariant {
|
MatchPatEnumVariant {
|
||||||
match_span: state.match_span,
|
match_span: state.match_span,
|
||||||
|
sim: state.sim,
|
||||||
variant_path,
|
variant_path,
|
||||||
enum_path,
|
enum_path,
|
||||||
variant_name,
|
variant_name,
|
||||||
|
|
@ -442,6 +631,7 @@ trait ParseMatchPat: Sized {
|
||||||
state,
|
state,
|
||||||
MatchPatEnumVariant {
|
MatchPatEnumVariant {
|
||||||
match_span: state.match_span,
|
match_span: state.match_span,
|
||||||
|
sim: state.sim,
|
||||||
variant_path,
|
variant_path,
|
||||||
enum_path,
|
enum_path,
|
||||||
variant_name,
|
variant_name,
|
||||||
|
|
@ -461,7 +651,34 @@ trait ParseMatchPat: Sized {
|
||||||
}) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild {
|
}) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild {
|
||||||
underscore_token,
|
underscore_token,
|
||||||
}))),
|
}))),
|
||||||
Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => {
|
Pat::Tuple(PatTuple {
|
||||||
|
attrs: _,
|
||||||
|
paren_token,
|
||||||
|
elems,
|
||||||
|
}) => {
|
||||||
|
let fields = elems
|
||||||
|
.into_pairs()
|
||||||
|
.filter_map_pair_value(|field_pat| {
|
||||||
|
if let Pat::Rest(PatRest {
|
||||||
|
attrs: _,
|
||||||
|
dot2_token,
|
||||||
|
}) = field_pat
|
||||||
|
{
|
||||||
|
Some(MatchPatSimple::Rest(MatchPatRest { dot2_token }))
|
||||||
|
} else {
|
||||||
|
MatchPatSimple::parse(state, field_pat).ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self::tuple(
|
||||||
|
state,
|
||||||
|
MatchPatTuple {
|
||||||
|
paren_token,
|
||||||
|
fields,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => {
|
||||||
state
|
state
|
||||||
.errors
|
.errors
|
||||||
.error(pat, "not yet implemented in #[hdl] patterns");
|
.error(pat, "not yet implemented in #[hdl] patterns");
|
||||||
|
|
@ -496,6 +713,14 @@ impl ParseMatchPat for MatchPatSimple {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
|
||||||
|
state.errors.push(syn::Error::new(
|
||||||
|
v.paren_token.span.open(),
|
||||||
|
"matching tuples is not yet implemented inside structs/enums in #[hdl] patterns",
|
||||||
|
));
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
fn enum_variant(
|
fn enum_variant(
|
||||||
state: &mut HdlMatchParseState<'_>,
|
state: &mut HdlMatchParseState<'_>,
|
||||||
v: MatchPatEnumVariant,
|
v: MatchPatEnumVariant,
|
||||||
|
|
@ -514,6 +739,7 @@ enum MatchPat {
|
||||||
Or(MatchPatOr<MatchPat>),
|
Or(MatchPatOr<MatchPat>),
|
||||||
Paren(MatchPatParen<MatchPat>),
|
Paren(MatchPatParen<MatchPat>),
|
||||||
Struct(MatchPatStruct),
|
Struct(MatchPatStruct),
|
||||||
|
Tuple(MatchPatTuple),
|
||||||
EnumVariant(MatchPatEnumVariant),
|
EnumVariant(MatchPatEnumVariant),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,6 +749,7 @@ impl_fold! {
|
||||||
Or(MatchPatOr<MatchPat>),
|
Or(MatchPatOr<MatchPat>),
|
||||||
Paren(MatchPatParen<MatchPat>),
|
Paren(MatchPatParen<MatchPat>),
|
||||||
Struct(MatchPatStruct),
|
Struct(MatchPatStruct),
|
||||||
|
Tuple(MatchPatTuple),
|
||||||
EnumVariant(MatchPatEnumVariant),
|
EnumVariant(MatchPatEnumVariant),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,6 +771,10 @@ impl ParseMatchPat for MatchPat {
|
||||||
Ok(Self::Struct(v))
|
Ok(Self::Struct(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
|
||||||
|
Ok(Self::Tuple(v))
|
||||||
|
}
|
||||||
|
|
||||||
fn enum_variant(
|
fn enum_variant(
|
||||||
_state: &mut HdlMatchParseState<'_>,
|
_state: &mut HdlMatchParseState<'_>,
|
||||||
v: MatchPatEnumVariant,
|
v: MatchPatEnumVariant,
|
||||||
|
|
@ -559,6 +790,7 @@ impl ToTokens for MatchPat {
|
||||||
Self::Or(v) => v.to_tokens(tokens),
|
Self::Or(v) => v.to_tokens(tokens),
|
||||||
Self::Paren(v) => v.to_tokens(tokens),
|
Self::Paren(v) => v.to_tokens(tokens),
|
||||||
Self::Struct(v) => v.to_tokens(tokens),
|
Self::Struct(v) => v.to_tokens(tokens),
|
||||||
|
Self::Tuple(v) => v.to_tokens(tokens),
|
||||||
Self::EnumVariant(v) => v.to_tokens(tokens),
|
Self::EnumVariant(v) => v.to_tokens(tokens),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -621,10 +853,6 @@ struct RewriteAsCheckMatch {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for RewriteAsCheckMatch {
|
impl Fold for RewriteAsCheckMatch {
|
||||||
fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat {
|
|
||||||
i.colon_token = Some(Token));
|
|
||||||
i
|
|
||||||
}
|
|
||||||
fn fold_pat(&mut self, pat: Pat) -> Pat {
|
fn fold_pat(&mut self, pat: Pat) -> Pat {
|
||||||
match pat {
|
match pat {
|
||||||
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
|
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
|
||||||
|
|
@ -739,17 +967,183 @@ impl Fold for RewriteAsCheckMatch {
|
||||||
// don't recurse into expressions
|
// don't recurse into expressions
|
||||||
i
|
i
|
||||||
}
|
}
|
||||||
|
fn fold_local(&mut self, mut let_stmt: Local) -> Local {
|
||||||
|
if let Some(syn::LocalInit {
|
||||||
|
eq_token,
|
||||||
|
expr: _,
|
||||||
|
diverge,
|
||||||
|
}) = let_stmt.init.take()
|
||||||
|
{
|
||||||
|
let_stmt.init = Some(syn::LocalInit {
|
||||||
|
eq_token,
|
||||||
|
expr: parse_quote_spanned! {self.span=>
|
||||||
|
__match_value
|
||||||
|
},
|
||||||
|
diverge: diverge.map(|(else_, _expr)| {
|
||||||
|
(
|
||||||
|
else_,
|
||||||
|
parse_quote_spanned! {self.span=>
|
||||||
|
match __infallible {}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fold_local(self, let_stmt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HdlMatchParseState<'a> {
|
struct HdlMatchParseState<'a> {
|
||||||
|
sim: Option<(kw::sim,)>,
|
||||||
match_span: Span,
|
match_span: Span,
|
||||||
errors: &'a mut Errors,
|
errors: &'a mut Errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HdlLetPatVisitState<'a> {
|
||||||
|
errors: &'a mut Errors,
|
||||||
|
bindings: BTreeMap<Ident, MatchPatBinding>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
|
||||||
|
fn visit_match_pat_binding(&mut self, v: &'a mut MatchPatBinding) {
|
||||||
|
self.bindings.insert(v.ident.clone(), v.clone());
|
||||||
|
v.mutability = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_match_pat_or(&mut self, v: &'a mut MatchPatOr<MatchPat>) {
|
||||||
|
if let Some(first_inner_vert) = v.first_inner_vert() {
|
||||||
|
self.errors.error(
|
||||||
|
first_inner_vert,
|
||||||
|
"or-patterns are not supported in let statements",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
visit_match_pat_or(self, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_match_pat_or_simple(&mut self, v: &'a mut MatchPatOr<MatchPatSimple>) {
|
||||||
|
if let Some(first_inner_vert) = v.first_inner_vert() {
|
||||||
|
self.errors.error(
|
||||||
|
first_inner_vert,
|
||||||
|
"or-patterns are not supported in let statements",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
visit_match_pat_or_simple(self, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_match_pat_enum_variant(&mut self, v: &'a mut MatchPatEnumVariant) {
|
||||||
|
self.errors.error(v, "refutable pattern in let statement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Visitor<'_> {
|
impl Visitor<'_> {
|
||||||
|
pub(crate) fn process_hdl_let_pat(
|
||||||
|
&mut self,
|
||||||
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
|
mut let_stmt: Local,
|
||||||
|
) -> Local {
|
||||||
|
let span = let_stmt.let_token.span();
|
||||||
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
|
if let Pat::Type(pat) = &mut let_stmt.pat {
|
||||||
|
*pat.ty = wrap_ty_with_expr((*pat.ty).clone());
|
||||||
|
}
|
||||||
|
let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone());
|
||||||
|
let Local {
|
||||||
|
attrs: _,
|
||||||
|
let_token,
|
||||||
|
pat,
|
||||||
|
init,
|
||||||
|
semi_token,
|
||||||
|
} = let_stmt;
|
||||||
|
let Some(syn::LocalInit {
|
||||||
|
eq_token,
|
||||||
|
expr,
|
||||||
|
diverge,
|
||||||
|
}) = init
|
||||||
|
else {
|
||||||
|
self.errors
|
||||||
|
.error(let_token, "#[hdl] let must be assigned a value");
|
||||||
|
return empty_let();
|
||||||
|
};
|
||||||
|
if let Some((else_, _)) = diverge {
|
||||||
|
// TODO: implement let-else
|
||||||
|
self.errors
|
||||||
|
.error(else_, "#[hdl] let ... else { ... } is not implemented");
|
||||||
|
return empty_let();
|
||||||
|
}
|
||||||
|
let Ok(mut pat) = MatchPat::parse(
|
||||||
|
&mut HdlMatchParseState {
|
||||||
|
sim,
|
||||||
|
match_span: span,
|
||||||
|
errors: &mut self.errors,
|
||||||
|
},
|
||||||
|
pat,
|
||||||
|
) else {
|
||||||
|
return empty_let();
|
||||||
|
};
|
||||||
|
let mut state = HdlLetPatVisitState {
|
||||||
|
errors: &mut self.errors,
|
||||||
|
bindings: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
state.visit_match_pat(&mut pat);
|
||||||
|
let HdlLetPatVisitState {
|
||||||
|
errors: _,
|
||||||
|
bindings,
|
||||||
|
} = state;
|
||||||
|
let bindings_idents = bindings.keys();
|
||||||
|
let bindings = bindings.values();
|
||||||
|
let retval = if sim.is_some() {
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
let (#(#bindings,)*) = {
|
||||||
|
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
|
||||||
|
let __match_value = #expr;
|
||||||
|
// use method syntax to deduce what type to convert to
|
||||||
|
let __match_value = ::fayalite::sim::value::match_sim_value::MatchSimValueHelper::new(__match_value)
|
||||||
|
.__fayalite_match_sim_value();
|
||||||
|
#let_token #pat #eq_token __match_value #semi_token
|
||||||
|
(#(#bindings_idents,)*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_quote_spanned! {span=>
|
||||||
|
let (#(#bindings,)* __scope,) = {
|
||||||
|
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
|
||||||
|
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
|
||||||
|
::fayalite::expr::check_match_expr(
|
||||||
|
__match_expr,
|
||||||
|
|__match_value, __infallible| {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#check_let_stmt
|
||||||
|
match __infallible {}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let mut __match_iter = ::fayalite::module::match_(__match_expr);
|
||||||
|
let ::fayalite::__std::option::Option::Some(__match_variant) =
|
||||||
|
::fayalite::__std::iter::Iterator::next(&mut __match_iter)
|
||||||
|
else {
|
||||||
|
::fayalite::__std::unreachable!("#[hdl] let with uninhabited type");
|
||||||
|
};
|
||||||
|
let ::fayalite::__std::option::Option::None =
|
||||||
|
::fayalite::__std::iter::Iterator::next(&mut __match_iter)
|
||||||
|
else {
|
||||||
|
::fayalite::__std::unreachable!("#[hdl] let with refutable pattern");
|
||||||
|
};
|
||||||
|
let (__match_variant, __scope) =
|
||||||
|
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
|
||||||
|
__match_variant,
|
||||||
|
);
|
||||||
|
#let_token #pat #eq_token __match_variant #semi_token
|
||||||
|
(#(#bindings_idents,)* __scope,)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match retval {
|
||||||
|
syn::Stmt::Local(retval) => retval,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub(crate) fn process_hdl_match(
|
pub(crate) fn process_hdl_match(
|
||||||
&mut self,
|
&mut self,
|
||||||
_hdl_attr: HdlAttr<Nothing>,
|
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
|
||||||
expr_match: ExprMatch,
|
expr_match: ExprMatch,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
let span = expr_match.match_token.span();
|
let span = expr_match.match_token.span();
|
||||||
|
|
@ -761,8 +1155,9 @@ impl Visitor<'_> {
|
||||||
brace_token: _,
|
brace_token: _,
|
||||||
arms,
|
arms,
|
||||||
} = expr_match;
|
} = expr_match;
|
||||||
self.require_normal_module(match_token);
|
let ExprOptions { sim } = hdl_attr.body;
|
||||||
let mut state = HdlMatchParseState {
|
let mut state = HdlMatchParseState {
|
||||||
|
sim,
|
||||||
match_span: span,
|
match_span: span,
|
||||||
errors: &mut self.errors,
|
errors: &mut self.errors,
|
||||||
};
|
};
|
||||||
|
|
@ -770,7 +1165,21 @@ impl Visitor<'_> {
|
||||||
arms.into_iter()
|
arms.into_iter()
|
||||||
.filter_map(|arm| MatchArm::parse(&mut state, arm).ok()),
|
.filter_map(|arm| MatchArm::parse(&mut state, arm).ok()),
|
||||||
);
|
);
|
||||||
let expr = quote_spanned! {span=>
|
let expr = if sim.is_some() {
|
||||||
|
quote_spanned! {span=>
|
||||||
|
{
|
||||||
|
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
|
||||||
|
let __match_value = #expr;
|
||||||
|
// use method syntax to deduce what type to convert to
|
||||||
|
let __match_value = ::fayalite::sim::value::match_sim_value::MatchSimValueHelper::new(__match_value)
|
||||||
|
.__fayalite_match_sim_value();
|
||||||
|
#match_token __match_value {
|
||||||
|
#(#arms)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {span=>
|
||||||
{
|
{
|
||||||
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
|
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
|
||||||
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
|
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
|
||||||
|
|
@ -788,6 +1197,7 @@ impl Visitor<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
syn::parse2(expr).unwrap()
|
syn::parse2(expr).unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2527
crates/fayalite-proc-macros-impl/src/process_cfg.rs
Normal file
2527
crates/fayalite-proc-macros-impl/src/process_cfg.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -16,4 +16,4 @@ version.workspace = true
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fayalite-proc-macros-impl = { workspace = true }
|
fayalite-proc-macros-impl.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
indexmap = { workspace = true }
|
indexmap.workspace = true
|
||||||
prettyplease = { workspace = true }
|
prettyplease.workspace = true
|
||||||
proc-macro2 = { workspace = true }
|
proc-macro2.workspace = true
|
||||||
quote = { workspace = true }
|
quote.workspace = true
|
||||||
serde = { workspace = true }
|
serde.workspace = true
|
||||||
serde_json = { workspace = true }
|
serde_json.workspace = true
|
||||||
syn = { workspace = true }
|
syn.workspace = true
|
||||||
thiserror = { workspace = true }
|
thiserror.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{ToTokens, format_ident, quote};
|
||||||
use std::{collections::BTreeMap, fs};
|
use std::{collections::BTreeMap, fs};
|
||||||
use syn::{fold::Fold, parse_quote};
|
use syn::{fold::Fold, parse_quote};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,25 +14,38 @@ rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitvec = { workspace = true }
|
base64.workspace = true
|
||||||
hashbrown = { workspace = true }
|
bitvec.workspace = true
|
||||||
num-bigint = { workspace = true }
|
blake3.workspace = true
|
||||||
num-traits = { workspace = true }
|
clap.workspace = true
|
||||||
fayalite-proc-macros = { workspace = true }
|
clap_complete.workspace = true
|
||||||
serde = { workspace = true }
|
ctor.workspace = true
|
||||||
serde_json = { workspace = true }
|
eyre.workspace = true
|
||||||
clap = { version = "4.5.9", features = ["derive", "env"] }
|
fayalite-proc-macros.workspace = true
|
||||||
eyre = "0.6.12"
|
hashbrown.workspace = true
|
||||||
which = "6.0.1"
|
jobslot.workspace = true
|
||||||
|
num-bigint.workspace = true
|
||||||
|
num-traits.workspace = true
|
||||||
|
once_cell.workspace = true
|
||||||
|
ordered-float.workspace = true
|
||||||
|
petgraph.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
sha2.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
vec_map.workspace = true
|
||||||
|
which.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
trybuild = { workspace = true }
|
trybuild.workspace = true
|
||||||
|
serde = { workspace = true, features = ["rc"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
fayalite-visit-gen = { workspace = true }
|
fayalite-visit-gen.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
unstable-doc = []
|
unstable-doc = []
|
||||||
|
unstable-test-hasher = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["unstable-doc"]
|
features = ["unstable-doc"]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ use std::{env, fs, path::Path};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo::rustc-check-cfg=cfg(todo)");
|
println!("cargo::rustc-check-cfg=cfg(todo)");
|
||||||
|
println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)");
|
||||||
|
println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)");
|
||||||
|
println!("cargo::rustc-cfg=cfg_true_for_tests");
|
||||||
let path = "visit_types.json";
|
let path = "visit_types.json";
|
||||||
println!("cargo::rerun-if-changed={path}");
|
println!("cargo::rerun-if-changed={path}");
|
||||||
println!("cargo::rerun-if-changed=build.rs");
|
println!("cargo::rerun-if-changed=build.rs");
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,64 @@
|
||||||
use clap::Parser;
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
use fayalite::{cli, prelude::*};
|
// See Notices.txt for copyright information
|
||||||
|
use fayalite::prelude::*;
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
fn blinky(clock_frequency: u64) {
|
fn blinky(platform_io_builder: PlatformIOBuilder<'_>) {
|
||||||
#[hdl]
|
let clk_input =
|
||||||
let clk: Clock = m.input();
|
platform_io_builder.peripherals_with_type::<peripherals::ClockInput>()[0].use_peripheral();
|
||||||
#[hdl]
|
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
|
||||||
let rst: SyncReset = m.input();
|
|
||||||
let cd = #[hdl]
|
let cd = #[hdl]
|
||||||
ClockDomain {
|
ClockDomain {
|
||||||
clk,
|
clk: clk_input.clk,
|
||||||
rst: rst.to_reset(),
|
rst,
|
||||||
};
|
};
|
||||||
let max_value = clock_frequency / 2 - 1;
|
let max_value = (clk_input.ty().frequency() / 2.0).round_ties_even() as u64 - 1;
|
||||||
let int_ty = UInt::range_inclusive(0..=max_value);
|
let int_ty = UInt::range_inclusive(0..=max_value);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let counter: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
|
let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
|
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
if counter.cmp_eq(max_value) {
|
let rgb_output_reg = reg_builder().clock_domain(cd).reset(
|
||||||
connect_any(counter, 0u8);
|
#[hdl]
|
||||||
|
peripherals::RgbLed {
|
||||||
|
r: false,
|
||||||
|
g: false,
|
||||||
|
b: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
#[hdl]
|
||||||
|
if counter_reg.cmp_eq(max_value) {
|
||||||
|
connect_any(counter_reg, 0u8);
|
||||||
connect(output_reg, !output_reg);
|
connect(output_reg, !output_reg);
|
||||||
|
connect(rgb_output_reg.r, !rgb_output_reg.r);
|
||||||
|
#[hdl]
|
||||||
|
if rgb_output_reg.r {
|
||||||
|
connect(rgb_output_reg.g, !rgb_output_reg.g);
|
||||||
|
#[hdl]
|
||||||
|
if rgb_output_reg.g {
|
||||||
|
connect(rgb_output_reg.b, !rgb_output_reg.b);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
connect_any(counter, counter + 1_hdl_u1);
|
connect_any(counter_reg, counter_reg + 1_hdl_u1);
|
||||||
|
}
|
||||||
|
for led in platform_io_builder.peripherals_with_type::<peripherals::Led>() {
|
||||||
|
if let Ok(led) = led.try_use_peripheral() {
|
||||||
|
connect(led.on, output_reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for rgb_led in platform_io_builder.peripherals_with_type::<peripherals::RgbLed>() {
|
||||||
|
if let Ok(rgb_led) = rgb_led.try_use_peripheral() {
|
||||||
|
connect(rgb_led, rgb_output_reg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let led: Bool = m.output();
|
let io = m.add_platform_io(platform_io_builder);
|
||||||
connect(led, output_reg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
fn main() {
|
||||||
struct Cli {
|
<BuildCli>::main("blinky", |_, platform, _| {
|
||||||
/// clock frequency in hertz
|
Ok(JobParams::new(platform.wrap_main_module(blinky)))
|
||||||
#[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))]
|
});
|
||||||
clock_frequency: u64,
|
|
||||||
#[command(subcommand)]
|
|
||||||
cli: cli::Cli,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> cli::Result {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
cli.cli.run(blinky(cli.clock_frequency))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
188
crates/fayalite/examples/tx_only_uart.rs
Normal file
188
crates/fayalite/examples/tx_only_uart.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
use clap::builder::TypedValueParser;
|
||||||
|
use fayalite::{
|
||||||
|
build::{ToArgs, WriteArgs},
|
||||||
|
platform::PeripheralRef,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
|
fn pick_clock<'a>(
|
||||||
|
platform_io_builder: &PlatformIOBuilder<'a>,
|
||||||
|
) -> PeripheralRef<'a, peripherals::ClockInput> {
|
||||||
|
let mut clks = platform_io_builder.peripherals_with_type::<peripherals::ClockInput>();
|
||||||
|
clks.sort_by_key(|clk| {
|
||||||
|
// sort clocks by preference, smaller return values means higher preference
|
||||||
|
let mut frequency = clk.ty().frequency();
|
||||||
|
let priority;
|
||||||
|
if frequency < 10e6 {
|
||||||
|
frequency = -frequency; // prefer bigger frequencies
|
||||||
|
priority = 1;
|
||||||
|
} else if frequency > 50e6 {
|
||||||
|
// prefer smaller frequencies
|
||||||
|
priority = 2; // least preferred
|
||||||
|
} else {
|
||||||
|
priority = 0; // most preferred
|
||||||
|
frequency = (frequency - 25e6).abs(); // prefer closer to 25MHz
|
||||||
|
}
|
||||||
|
(priority, NotNan::new(frequency).expect("should be valid"))
|
||||||
|
});
|
||||||
|
clks[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl_module]
|
||||||
|
fn tx_only_uart(
|
||||||
|
platform_io_builder: PlatformIOBuilder<'_>,
|
||||||
|
divisor: f64,
|
||||||
|
message: impl AsRef<[u8]>,
|
||||||
|
) {
|
||||||
|
let message = message.as_ref();
|
||||||
|
let clk_input = pick_clock(&platform_io_builder).use_peripheral();
|
||||||
|
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
|
||||||
|
let cd = #[hdl]
|
||||||
|
ClockDomain {
|
||||||
|
clk: clk_input.clk,
|
||||||
|
rst,
|
||||||
|
};
|
||||||
|
let numerator = 1u128 << 16;
|
||||||
|
let denominator = (divisor * numerator as f64).round() as u128;
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let remainder_reg: UInt<128> = reg_builder().clock_domain(cd).reset(0u128);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let sum: UInt<128> = wire();
|
||||||
|
connect_any(sum, remainder_reg + numerator);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let tick_reg = reg_builder().clock_domain(cd).reset(false);
|
||||||
|
connect(tick_reg, false);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let next_remainder: UInt<128> = wire();
|
||||||
|
connect(remainder_reg, next_remainder);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
if sum.cmp_ge(denominator) {
|
||||||
|
connect_any(next_remainder, sum - denominator);
|
||||||
|
connect(tick_reg, true);
|
||||||
|
} else {
|
||||||
|
connect(next_remainder, sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let uart_state_reg = reg_builder().clock_domain(cd).reset(0_hdl_u4);
|
||||||
|
#[hdl]
|
||||||
|
let next_uart_state: UInt<4> = wire();
|
||||||
|
|
||||||
|
connect_any(next_uart_state, uart_state_reg + 1u8);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let message_mem: Array<UInt<8>> = wire(Array[UInt::new_static()][message.len()]);
|
||||||
|
for (message, message_mem) in message.iter().zip(message_mem) {
|
||||||
|
connect(message_mem, *message);
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32);
|
||||||
|
#[hdl]
|
||||||
|
let next_addr: UInt<32> = wire();
|
||||||
|
connect(next_addr, addr_reg);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let tx = reg_builder().clock_domain(cd).reset(true);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let tx_bits: Array<Bool, 10> = wire();
|
||||||
|
|
||||||
|
connect(tx_bits[0], false); // start bit
|
||||||
|
connect(tx_bits[9], true); // stop bit
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
connect(tx_bits[i + 1], message_mem[addr_reg][i]); // data bits
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(tx, tx_bits[uart_state_reg]);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
if uart_state_reg.cmp_eq(tx_bits.ty().len() - 1) {
|
||||||
|
connect(next_uart_state, 0_hdl_u4);
|
||||||
|
let next_addr_val = addr_reg + 1u8;
|
||||||
|
#[hdl]
|
||||||
|
if next_addr_val.cmp_lt(message.len()) {
|
||||||
|
connect_any(next_addr, next_addr_val);
|
||||||
|
} else {
|
||||||
|
connect(next_addr, 0u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
if tick_reg {
|
||||||
|
connect(uart_state_reg, next_uart_state);
|
||||||
|
connect(addr_reg, next_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
for uart in platform_io_builder.peripherals_with_type::<peripherals::Uart>() {
|
||||||
|
connect(uart.use_peripheral().tx, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
let io = m.add_platform_io(platform_io_builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_baud_rate(
|
||||||
|
v: impl AsRef<str>,
|
||||||
|
) -> Result<NotNan<f64>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let retval: NotNan<f64> = v
|
||||||
|
.as_ref()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| "invalid baud rate, must be a finite positive floating-point value")?;
|
||||||
|
if *retval > 0.0 && retval.is_finite() {
|
||||||
|
Ok(retval)
|
||||||
|
} else {
|
||||||
|
Err("baud rate must be finite and positive".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||||
|
pub struct ExtraArgs {
|
||||||
|
#[arg(long, value_parser = clap::builder::StringValueParser::new().try_map(parse_baud_rate), default_value = "115200")]
|
||||||
|
pub baud_rate: NotNan<f64>,
|
||||||
|
#[arg(long, default_value = "Hello World from Fayalite!!!\r\n", value_parser = clap::builder::NonEmptyStringValueParser::new())]
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToArgs for ExtraArgs {
|
||||||
|
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self { baud_rate, message } = self;
|
||||||
|
args.write_display_arg(format_args!("--baud-rate={baud_rate}"));
|
||||||
|
args.write_long_option_eq("message", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
type Cli = BuildCli<ExtraArgs>;
|
||||||
|
Cli::main(
|
||||||
|
"tx_only_uart",
|
||||||
|
|_, platform, ExtraArgs { baud_rate, message }| {
|
||||||
|
Ok(JobParams::new(platform.try_wrap_main_module(|io| {
|
||||||
|
let clk = pick_clock(&io).ty();
|
||||||
|
let divisor = clk.frequency() / *baud_rate;
|
||||||
|
let baud_rate_error = |msg| {
|
||||||
|
<Cli as clap::CommandFactory>::command()
|
||||||
|
.error(clap::error::ErrorKind::ValueValidation, msg)
|
||||||
|
};
|
||||||
|
const HUGE_DIVISOR: f64 = u64::MAX as f64;
|
||||||
|
match divisor {
|
||||||
|
divisor if !divisor.is_finite() => {
|
||||||
|
return Err(baud_rate_error("bad baud rate"));
|
||||||
|
}
|
||||||
|
HUGE_DIVISOR.. => return Err(baud_rate_error("baud rate is too small")),
|
||||||
|
4.0.. => {}
|
||||||
|
_ => return Err(baud_rate_error("baud rate is too large")),
|
||||||
|
}
|
||||||
|
Ok(tx_only_uart(io, divisor, message))
|
||||||
|
})?))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Fayalite Modules
|
//! # Fayalite Modules
|
||||||
//!
|
//!
|
||||||
//! The [`#[hdl_module]`][`crate::hdl_module`] attribute is applied to a Rust
|
//! The [`#[hdl_module]`][`crate::hdl_module`] attribute is applied to a Rust
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! These are for when you want to use modules written in
|
//! These are for when you want to use modules written in
|
||||||
//! some other language, such as Verilog.
|
//! some other language, such as Verilog.
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Module Function Bodies
|
//! # Module Function Bodies
|
||||||
//!
|
//!
|
||||||
//! The `#[hdl_module]` attribute lets you have statements/expressions with `#[hdl]` annotations
|
//! The `#[hdl_module]` attribute lets you have statements/expressions with `#[hdl]` annotations
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # `#[hdl]` Array Expressions
|
//! # `#[hdl]` Array Expressions
|
||||||
//!
|
//!
|
||||||
//! `#[hdl]` can be used on Array Expressions to construct an [`Array<[T; N]>`][type@Array] expression:
|
//! `#[hdl]` can be used on Array Expressions to construct an [`Array<[T; N]>`][type@Array] expression:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # `#[hdl] if` Statements
|
//! # `#[hdl] if` Statements
|
||||||
//!
|
//!
|
||||||
//! `#[hdl] if` statements behave similarly to Rust `if` statements, except they end up as muxes
|
//! `#[hdl] if` statements behave similarly to Rust `if` statements, except they end up as muxes
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! ## `#[hdl] let` statements
|
//! ## `#[hdl] let` statements
|
||||||
|
|
||||||
|
pub mod destructuring;
|
||||||
pub mod inputs_outputs;
|
pub mod inputs_outputs;
|
||||||
pub mod instances;
|
pub mod instances;
|
||||||
pub mod memories;
|
pub mod memories;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
//! ### Destructuring Let
|
||||||
|
//!
|
||||||
|
//! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns.
|
||||||
|
//!
|
||||||
|
//! `#[hdl] let` statements can only match one level of struct/tuple pattern for now,
|
||||||
|
//! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use fayalite::prelude::*;
|
||||||
|
//! #[hdl]
|
||||||
|
//! struct MyStruct {
|
||||||
|
//! a: UInt<8>,
|
||||||
|
//! b: Bool,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl_module]
|
||||||
|
//! fn my_module() {
|
||||||
|
//! #[hdl]
|
||||||
|
//! let my_input: MyStruct = m.input();
|
||||||
|
//! #[hdl]
|
||||||
|
//! let my_output: UInt<8> = m.input();
|
||||||
|
//! #[hdl]
|
||||||
|
//! let MyStruct { a, b } = my_input;
|
||||||
|
//! #[hdl]
|
||||||
|
//! if b {
|
||||||
|
//! connect(my_output, a);
|
||||||
|
//! } else {
|
||||||
|
//! connect(my_output, 0_hdl_u8);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! You can also use `#[hdl(sim)] let` to destructure [`SimValue`]s (or anything that implements [`ToSimValue`]).
|
||||||
|
//!
|
||||||
|
//! [`SimValue`]: crate::sim::value::SimValue
|
||||||
|
//! [`ToSimValue`]: crate::sim::value::ToSimValue
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use fayalite::prelude::*;
|
||||||
|
//! #[hdl]
|
||||||
|
//! struct MyStruct<T> {
|
||||||
|
//! a: UInt<8>,
|
||||||
|
//! b: Bool,
|
||||||
|
//! c: T,
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure<T: Type>(v: SimValue<MyStruct<T>>) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! mut b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: SimValue<UInt<8>> = a;
|
||||||
|
//! let _: SimValue<Bool> = b;
|
||||||
|
//! let _: SimValue<T> = c;
|
||||||
|
//! *b = false; // can modify b since mut was used
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure_ref<'a, T: Type>(v: &'a SimValue<MyStruct<T>>) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: &'a SimValue<UInt<8>> = a;
|
||||||
|
//! let _: &'a SimValue<Bool> = b;
|
||||||
|
//! let _: &'a SimValue<T> = c;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure_mut<'a, T: Type>(v: &'a mut SimValue<MyStruct<T>>) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! **b = true; // you can modify v by modifying b which borrows from it
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: &'a mut SimValue<UInt<8>> = a;
|
||||||
|
//! let _: &'a mut SimValue<Bool> = b;
|
||||||
|
//! let _: &'a mut SimValue<T> = c;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure_inner<T: Type>(v: <MyStruct<T> as Type>::SimValue) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! mut b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: SimValue<UInt<8>> = a;
|
||||||
|
//! let _: SimValue<Bool> = b;
|
||||||
|
//! let _: SimValue<T> = c;
|
||||||
|
//! *b = false; // can modify b since mut was used
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure_inner_ref<'a, T: Type>(v: &'a <MyStruct<T> as Type>::SimValue) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: &'a SimValue<UInt<8>> = a;
|
||||||
|
//! let _: &'a SimValue<Bool> = b;
|
||||||
|
//! let _: &'a SimValue<T> = c;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn destructure_inner_mut<'a, T: Type>(v: &'a mut <MyStruct<T> as Type>::SimValue) {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! let MyStruct::<T> {
|
||||||
|
//! a,
|
||||||
|
//! b,
|
||||||
|
//! c,
|
||||||
|
//! } = v;
|
||||||
|
//!
|
||||||
|
//! **b = true; // you can modify v by modifying b which borrows from it
|
||||||
|
//!
|
||||||
|
//! // that gives these types:
|
||||||
|
//! let _: &'a mut SimValue<UInt<8>> = a;
|
||||||
|
//! let _: &'a mut SimValue<Bool> = b;
|
||||||
|
//! let _: &'a mut SimValue<T> = c;
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! ### Inputs/Outputs
|
//! ### Inputs/Outputs
|
||||||
//!
|
//!
|
||||||
//! Inputs/Outputs create a Rust variable with type [`Expr<T>`] where `T` is the type of the input/output.
|
//! Inputs/Outputs create a Rust variable with type [`Expr<T>`] where `T` is the type of the input/output.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! ### Module Instances
|
//! ### Module Instances
|
||||||
//!
|
//!
|
||||||
//! module instances are kinda like the hardware equivalent of calling a function,
|
//! module instances are kinda like the hardware equivalent of calling a function,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Memories
|
//! # Memories
|
||||||
//!
|
//!
|
||||||
//! Memories are optimized for storing large amounts of data.
|
//! Memories are optimized for storing large amounts of data.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! ### Registers
|
//! ### Registers
|
||||||
//!
|
//!
|
||||||
//! Registers are memory devices that will change their state only on a clock
|
//! Registers are memory devices that will change their state only on a clock
|
||||||
|
|
@ -7,6 +9,9 @@
|
||||||
//!
|
//!
|
||||||
//! Registers follow [connection semantics], which are unlike assignments in software, so you should read it.
|
//! Registers follow [connection semantics], which are unlike assignments in software, so you should read it.
|
||||||
//!
|
//!
|
||||||
|
//! By convention, register names end in `_reg` -- this helps you tell which values are written
|
||||||
|
//! immediately or on the next clock edge when connecting to them.
|
||||||
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! # use fayalite::prelude::*;
|
//! # use fayalite::prelude::*;
|
||||||
//! # #[hdl_module]
|
//! # #[hdl_module]
|
||||||
|
|
@ -16,11 +21,11 @@
|
||||||
//! #[hdl]
|
//! #[hdl]
|
||||||
//! let cd: ClockDomain = m.input();
|
//! let cd: ClockDomain = m.input();
|
||||||
//! #[hdl]
|
//! #[hdl]
|
||||||
//! let my_register: UInt<8> = reg_builder().clock_domain(cd).reset(8_hdl_u8);
|
//! let my_reg: UInt<8> = reg_builder().clock_domain(cd).reset(8_hdl_u8);
|
||||||
//! #[hdl]
|
//! #[hdl]
|
||||||
//! if v {
|
//! if v {
|
||||||
//! // my_register is only changed when both `v` is set and `cd`'s clock edge occurs.
|
//! // my_reg is only changed when both `v` is set and `cd`'s clock edge occurs.
|
||||||
//! connect(my_register, 0x45_hdl_u8);
|
//! connect(my_reg, 0x45_hdl_u8);
|
||||||
//! }
|
//! }
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! ### Wires
|
//! ### Wires
|
||||||
//!
|
//!
|
||||||
//! Wires are kinda like variables, but unlike registers,
|
//! Wires are kinda like variables, but unlike registers,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # `_hdl`-suffixed literals
|
//! # `_hdl`-suffixed literals
|
||||||
//!
|
//!
|
||||||
//! You can have integer literals with an arbitrary number of bits like so:
|
//! You can have integer literals with an arbitrary number of bits like so:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # `#[hdl] match` Statements
|
//! # `#[hdl] match` Statements
|
||||||
//!
|
//!
|
||||||
//! `#[hdl] match` statements behave similarly to Rust `match` statements, except they end up as muxes
|
//! `#[hdl] match` statements behave similarly to Rust `match` statements, except they end up as muxes
|
||||||
|
|
@ -5,5 +7,112 @@
|
||||||
//!
|
//!
|
||||||
//! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
|
//! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
|
||||||
//!
|
//!
|
||||||
//! `#[hdl] match` statements can only match one level of struct/enum pattern for now,
|
//! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now,
|
||||||
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.
|
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.
|
||||||
|
//!
|
||||||
|
//! You can also use `#[hdl(sim)] match` to match [`SimValue`]s (or anything that implements [`ToSimValue`]).
|
||||||
|
//!
|
||||||
|
//! `#[hdl(sim)] match` statements' bodies may have any type, unlike `#[hdl] match`.
|
||||||
|
//!
|
||||||
|
//! [`SimValue`]: crate::sim::value::SimValue
|
||||||
|
//! [`ToSimValue`]: crate::sim::value::ToSimValue
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use fayalite::prelude::*;
|
||||||
|
//! #[hdl]
|
||||||
|
//! enum MyEnum<T> {
|
||||||
|
//! A,
|
||||||
|
//! B(Bool),
|
||||||
|
//! C(T),
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_move<T: Type>(v: SimValue<MyEnum<T>>) -> String {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => String::from("got A"),
|
||||||
|
//! MyEnum::<T>::B(mut b) => {
|
||||||
|
//! let _: SimValue<Bool> = b; // b has this type
|
||||||
|
//! let text = format!("got B({b})");
|
||||||
|
//! *b = true; // can modify b since mut was used
|
||||||
|
//! text
|
||||||
|
//! }
|
||||||
|
//! _ => String::from("something else"),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_ref<'a, T: Type>(v: &'a SimValue<MyEnum<T>>) -> u32 {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => 1,
|
||||||
|
//! MyEnum::<T>::B(b) => {
|
||||||
|
//! let _: &'a SimValue<Bool> = b; // b has this type
|
||||||
|
//! println!("got B({b})");
|
||||||
|
//! 5
|
||||||
|
//! }
|
||||||
|
//! _ => 42,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_mut<'a, T: Type>(v: &'a mut SimValue<MyEnum<T>>) -> Option<&'a mut SimValue<T>> {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => None,
|
||||||
|
//! MyEnum::<T>::B(b) => {
|
||||||
|
//! println!("got B({b})");
|
||||||
|
//! **b = true; // you can modify v by modifying b which borrows from it
|
||||||
|
//! let _: &'a mut SimValue<Bool> = b; // b has this type
|
||||||
|
//! None
|
||||||
|
//! }
|
||||||
|
//! MyEnum::<T>::C(v) => Some(v), // you can return matched values
|
||||||
|
//! _ => None, // HDL enums can have invalid discriminants, so we need this extra match arm
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_inner_move<T: Type>(v: <MyEnum<T> as Type>::SimValue) -> String {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => String::from("got A"),
|
||||||
|
//! MyEnum::<T>::B(mut b) => {
|
||||||
|
//! let _: SimValue<Bool> = b; // b has this type
|
||||||
|
//! let text = format!("got B({b})");
|
||||||
|
//! *b = true; // can modify b since mut was used
|
||||||
|
//! text
|
||||||
|
//! }
|
||||||
|
//! _ => String::from("something else"),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_inner_ref<'a, T: Type>(v: &'a <MyEnum<T> as Type>::SimValue) -> u32 {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => 1,
|
||||||
|
//! MyEnum::<T>::B(b) => {
|
||||||
|
//! let _: &'a SimValue<Bool> = b; // b has this type
|
||||||
|
//! println!("got B({b})");
|
||||||
|
//! 5
|
||||||
|
//! }
|
||||||
|
//! _ => 42,
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[hdl]
|
||||||
|
//! fn match_inner_mut<'a, T: Type>(v: &'a mut <MyEnum<T> as Type>::SimValue) -> Option<&'a mut SimValue<T>> {
|
||||||
|
//! #[hdl(sim)]
|
||||||
|
//! match v {
|
||||||
|
//! MyEnum::<T>::A => None,
|
||||||
|
//! MyEnum::<T>::B(b) => {
|
||||||
|
//! println!("got B({b})");
|
||||||
|
//! **b = true; // you can modify v by modifying b which borrows from it
|
||||||
|
//! let _: &'a mut SimValue<Bool> = b; // b has this type
|
||||||
|
//! None
|
||||||
|
//! }
|
||||||
|
//! MyEnum::<T>::C(v) => Some(v), // you can return matched values
|
||||||
|
//! _ => None, // HDL enums can have invalid discriminants, so we need this extra match arm
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # `#[hdl]` Struct/Variant Expressions
|
//! # `#[hdl]` Struct/Variant Expressions
|
||||||
//!
|
//!
|
||||||
//! Note: Structs are also known as [Bundles] when used in Fayalite, the Bundle name comes from [FIRRTL].
|
//! Note: Structs are also known as [Bundles] when used in Fayalite, the Bundle name comes from [FIRRTL].
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Normal Modules
|
//! # Normal Modules
|
||||||
//!
|
//!
|
||||||
//! See also: [Extern Modules][`super::extern_module`]
|
//! See also: [Extern Modules][`super::extern_module`]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Fayalite Semantics
|
//! # Fayalite Semantics
|
||||||
//!
|
//!
|
||||||
//! Fayalite's semantics are based on [FIRRTL]. Due to their significance, some of the semantics are also documented here.
|
//! Fayalite's semantics are based on [FIRRTL]. Due to their significance, some of the semantics are also documented here.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
//! # Connection Semantics
|
//! # Connection Semantics
|
||||||
//!
|
//!
|
||||||
//! Fayalite's connection semantics are unlike assignments in software, so be careful!
|
//! Fayalite's connection semantics are unlike assignments in software, so be careful!
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
iter::FusedIterator,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
struct CustomFirrtlAnnotationFieldsImpl {
|
struct CustomFirrtlAnnotationFieldsImpl {
|
||||||
value: serde_json::Map<String, serde_json::Value>,
|
value: serde_json::Map<String, serde_json::Value>,
|
||||||
serialized: Interned<str>,
|
serialized: Interned<str>,
|
||||||
|
|
@ -118,11 +119,109 @@ pub struct CustomFirrtlAnnotation {
|
||||||
pub additional_fields: CustomFirrtlAnnotationFields,
|
pub additional_fields: CustomFirrtlAnnotationFields,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||||
#[non_exhaustive]
|
pub struct DontTouchAnnotation;
|
||||||
pub enum Annotation {
|
|
||||||
DontTouch,
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct SVAttributeAnnotation {
|
||||||
|
pub text: Interned<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct BlackBoxInlineAnnotation {
|
||||||
|
pub path: Interned<str>,
|
||||||
|
pub text: Interned<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct BlackBoxPathAnnotation {
|
||||||
|
pub path: Interned<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct DocStringAnnotation {
|
||||||
|
pub text: Interned<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_annotation_enum {
|
||||||
|
(
|
||||||
|
#[$non_exhaustive:ident]
|
||||||
|
$(#[$meta:meta])*
|
||||||
|
$vis:vis enum $AnnotationEnum:ident {
|
||||||
|
$($Variant:ident($T:ty),)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
crate::annotations::make_annotation_enum!(@require_non_exhaustive $non_exhaustive);
|
||||||
|
|
||||||
|
#[$non_exhaustive]
|
||||||
|
$(#[$meta])*
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
$vis enum $AnnotationEnum {
|
||||||
|
$($Variant($T),)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for $AnnotationEnum {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
$(Self::$Variant(v) => v.fmt(f),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(impl From<$T> for crate::annotations::Annotation {
|
||||||
|
fn from(v: $T) -> Self {
|
||||||
|
$AnnotationEnum::$Variant(v).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::annotations::IntoAnnotations for $T {
|
||||||
|
type IntoAnnotations = [crate::annotations::Annotation; 1];
|
||||||
|
|
||||||
|
fn into_annotations(self) -> Self::IntoAnnotations {
|
||||||
|
[self.into()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::annotations::IntoAnnotations for &'_ $T {
|
||||||
|
type IntoAnnotations = [crate::annotations::Annotation; 1];
|
||||||
|
|
||||||
|
fn into_annotations(self) -> Self::IntoAnnotations {
|
||||||
|
[crate::annotations::Annotation::from(self.clone())]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::annotations::IntoAnnotations for &'_ mut $T {
|
||||||
|
type IntoAnnotations = [crate::annotations::Annotation; 1];
|
||||||
|
|
||||||
|
fn into_annotations(self) -> Self::IntoAnnotations {
|
||||||
|
[crate::annotations::Annotation::from(self.clone())]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::annotations::IntoAnnotations for Box<$T> {
|
||||||
|
type IntoAnnotations = [crate::annotations::Annotation; 1];
|
||||||
|
|
||||||
|
fn into_annotations(self) -> Self::IntoAnnotations {
|
||||||
|
[crate::annotations::Annotation::from(*self)]
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
};
|
||||||
|
(@require_non_exhaustive non_exhaustive) => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use make_annotation_enum;
|
||||||
|
|
||||||
|
make_annotation_enum! {
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Annotation {
|
||||||
|
DontTouch(DontTouchAnnotation),
|
||||||
|
SVAttribute(SVAttributeAnnotation),
|
||||||
|
BlackBoxInline(BlackBoxInlineAnnotation),
|
||||||
|
BlackBoxPath(BlackBoxPathAnnotation),
|
||||||
|
DocString(DocStringAnnotation),
|
||||||
CustomFirrtl(CustomFirrtlAnnotation),
|
CustomFirrtl(CustomFirrtlAnnotation),
|
||||||
|
Xilinx(crate::vendor::xilinx::XilinxAnnotation),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
|
@ -139,7 +238,10 @@ impl TargetedAnnotation {
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn assert_valid_target(target: Interned<Target>) {
|
pub fn assert_valid_target(target: Interned<Target>) {
|
||||||
assert!(target.is_static(), "can't annotate non-static targets");
|
assert!(
|
||||||
|
target.is_valid_annotation_target(),
|
||||||
|
"not a valid annotation target: {target:?}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
pub fn target(&self) -> Interned<Target> {
|
pub fn target(&self) -> Interned<Target> {
|
||||||
self.target
|
self.target
|
||||||
|
|
@ -187,10 +289,68 @@ impl IntoAnnotations for &'_ mut Annotation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: IntoIterator<Item = Annotation>> IntoAnnotations for T {
|
pub struct IterIntoAnnotations<T: Iterator<Item: IntoAnnotations>> {
|
||||||
type IntoAnnotations = Self;
|
outer: T,
|
||||||
|
inner: Option<<<T::Item as IntoAnnotations>::IntoAnnotations as IntoIterator>::IntoIter>,
|
||||||
|
}
|
||||||
|
|
||||||
fn into_annotations(self) -> Self::IntoAnnotations {
|
impl<T: Iterator<Item: IntoAnnotations>> Iterator for IterIntoAnnotations<T> {
|
||||||
self
|
type Item = Annotation;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
if let Some(inner) = &mut self.inner {
|
||||||
|
let Some(retval) = inner.next() else {
|
||||||
|
self.inner = None;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
return Some(retval);
|
||||||
|
} else {
|
||||||
|
self.inner = Some(self.outer.next()?.into_annotations().into_iter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
if let (0, Some(0)) = self.outer.size_hint() {
|
||||||
|
self.inner
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.size_hint())
|
||||||
|
.unwrap_or((0, Some(0)))
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
self.inner.as_ref().map(|v| v.size_hint().0).unwrap_or(0),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold<B, F>(self, init: B, f: F) -> B
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.inner
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.outer.map(|v| v.into_annotations().into_iter()))
|
||||||
|
.flatten()
|
||||||
|
.fold(init, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
T: FusedIterator<Item: IntoAnnotations<IntoAnnotations: IntoIterator<IntoIter: FusedIterator>>>,
|
||||||
|
> FusedIterator for IterIntoAnnotations<T>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoIterator<Item: IntoAnnotations>> IntoAnnotations for T {
|
||||||
|
type IntoAnnotations = IterIntoAnnotations<T::IntoIter>;
|
||||||
|
|
||||||
|
fn into_annotations(self) -> Self::IntoAnnotations {
|
||||||
|
IterIntoAnnotations {
|
||||||
|
outer: self.into_iter(),
|
||||||
|
inner: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,24 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expr::{ops::ArrayIndex, Expr, ToExpr},
|
expr::{
|
||||||
int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE},
|
CastToBits, Expr, HdlPartialEq, HdlPartialEqImpl, ReduceBits, ToExpr, ValueType, Valueless,
|
||||||
|
ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, StructuralEq},
|
||||||
|
},
|
||||||
|
int::{Bool, DYN_SIZE, DynSize, KnownSize, Size, SizeType},
|
||||||
intern::{Intern, Interned, LazyInterned},
|
intern::{Intern, Interned, LazyInterned},
|
||||||
module::transform::visit::{Fold, Folder, Visit, Visitor},
|
module::transform::visit::{Fold, Folder, Visit, Visitor},
|
||||||
|
sim::value::SimValue,
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{
|
ty::{
|
||||||
CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref,
|
CanonicalType, MatchVariantWithoutScope, OpaqueSimValueSlice, OpaqueSimValueWriter,
|
||||||
|
OpaqueSimValueWritten, SimValueDebug, StaticType, Type, TypeProperties, TypeWithDeref,
|
||||||
|
serde_impls::SerdeCanonicalType,
|
||||||
},
|
},
|
||||||
util::ConstUsize,
|
util::ConstUsize,
|
||||||
};
|
};
|
||||||
use std::ops::Index;
|
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
|
||||||
|
use std::{borrow::Cow, fmt, iter::FusedIterator, ops::Index};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
|
pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
|
||||||
|
|
@ -21,8 +28,8 @@ pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
|
||||||
type_properties: TypeProperties,
|
type_properties: TypeProperties,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type, Len: Size> std::fmt::Debug for ArrayType<T, Len> {
|
impl<T: Type, Len: Size> fmt::Debug for ArrayType<T, Len> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Array<{:?}, {}>", self.element, self.len())
|
write!(f, "Array<{:?}, {}>", self.element, self.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,15 +48,20 @@ impl<T: Type, Len: Size> ArrayType<T, Len> {
|
||||||
is_storable,
|
is_storable,
|
||||||
is_castable_from_bits,
|
is_castable_from_bits,
|
||||||
bit_width,
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
} = element;
|
} = element;
|
||||||
let Some(bit_width) = bit_width.checked_mul(len) else {
|
let Some(bit_width) = bit_width.checked_mul(len) else {
|
||||||
panic!("array too big");
|
panic!("array too big");
|
||||||
};
|
};
|
||||||
|
let Some(sim_only_values_len) = sim_only_values_len.checked_mul(len) else {
|
||||||
|
panic!("array too big");
|
||||||
|
};
|
||||||
TypeProperties {
|
TypeProperties {
|
||||||
is_passive,
|
is_passive,
|
||||||
is_storable,
|
is_storable,
|
||||||
is_castable_from_bits,
|
is_castable_from_bits,
|
||||||
bit_width,
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new(element: T, len: Len::SizeType) -> Self {
|
pub fn new(element: T, len: Len::SizeType) -> Self {
|
||||||
|
|
@ -85,20 +97,54 @@ impl<T: Type, Len: Size> ArrayType<T, Len> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type, Len: KnownSize> ArrayType<T, Len> {
|
impl<T: Type, Len: KnownSize + Size<SizeType = Len>> ArrayType<T, Len> {
|
||||||
pub fn new_static(element: T) -> Self {
|
pub fn new_static(element: T) -> Self {
|
||||||
Self::new(element, Len::SizeType::default())
|
Self::new(element, Len::SizeType::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: StaticType, Len: KnownSize> Default for ArrayType<T, Len> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MakeType<T: StaticType>(Interned<T>);
|
||||||
|
|
||||||
|
impl<T: StaticType> From<MakeType<T>> for Interned<T> {
|
||||||
|
fn from(value: MakeType<T>) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: StaticType> Default for MakeType<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(T::TYPE.intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MakeMaskType<T: StaticType>(Interned<T::MaskType>);
|
||||||
|
|
||||||
|
impl<T: StaticType> From<MakeMaskType<T>> for Interned<T::MaskType> {
|
||||||
|
fn from(value: MakeMaskType<T>) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: StaticType> Default for MakeMaskType<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(T::MASK_TYPE.intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: StaticType, Len: KnownSize> StaticType for ArrayType<T, Len> {
|
impl<T: StaticType, Len: KnownSize> StaticType for ArrayType<T, Len> {
|
||||||
const TYPE: Self = Self {
|
const TYPE: Self = Self {
|
||||||
element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()),
|
element: LazyInterned::new_const::<MakeType<T>>(),
|
||||||
len: Len::SIZE,
|
len: Len::SIZE,
|
||||||
type_properties: Self::TYPE_PROPERTIES,
|
type_properties: Self::TYPE_PROPERTIES,
|
||||||
};
|
};
|
||||||
const MASK_TYPE: Self::MaskType = ArrayType::<T::MaskType, Len> {
|
const MASK_TYPE: Self::MaskType = ArrayType::<T::MaskType, Len> {
|
||||||
element: LazyInterned::new_lazy(&|| T::MASK_TYPE.intern_sized()),
|
element: LazyInterned::new_const::<MakeMaskType<T>>(),
|
||||||
len: Len::SIZE,
|
len: Len::SIZE,
|
||||||
type_properties: Self::MASK_TYPE_PROPERTIES,
|
type_properties: Self::MASK_TYPE_PROPERTIES,
|
||||||
};
|
};
|
||||||
|
|
@ -136,9 +182,19 @@ impl<T: Type + Visit<State>, Len: Size, State: Visitor + ?Sized> Visit<State>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> SimValueDebug for ArrayType<T, Len> {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
|
impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
|
||||||
type BaseType = Array;
|
type BaseType = Array;
|
||||||
type MaskType = ArrayType<T::MaskType, Len>;
|
type MaskType = ArrayType<T::MaskType, Len>;
|
||||||
|
type SimValue = Len::ArraySimValue<T>;
|
||||||
type MatchVariant = Len::ArrayMatch<T>;
|
type MatchVariant = Len::ArrayMatch<T>;
|
||||||
type MatchActiveScope = ();
|
type MatchActiveScope = ();
|
||||||
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Len::ArrayMatch<T>>;
|
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Len::ArrayMatch<T>>;
|
||||||
|
|
@ -148,10 +204,8 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
|
||||||
this: Expr<Self>,
|
this: Expr<Self>,
|
||||||
source_location: SourceLocation,
|
source_location: SourceLocation,
|
||||||
) -> Self::MatchVariantsIter {
|
) -> Self::MatchVariantsIter {
|
||||||
let base = Expr::as_dyn_array(this);
|
|
||||||
let base_ty = Expr::ty(base);
|
|
||||||
let _ = source_location;
|
let _ = source_location;
|
||||||
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
|
let retval = Vec::from_iter(this);
|
||||||
std::iter::once(MatchVariantWithoutScope(
|
std::iter::once(MatchVariantWithoutScope(
|
||||||
Len::ArrayMatch::<T>::try_from(retval)
|
Len::ArrayMatch::<T>::try_from(retval)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -177,17 +231,107 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
|
||||||
Len::from_usize(array.len()),
|
Len::from_usize(array.len()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source_location() -> SourceLocation {
|
fn source_location() -> SourceLocation {
|
||||||
SourceLocation::builtin()
|
SourceLocation::builtin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, mut opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
let element_ty = self.element();
|
||||||
|
let element_size = element_ty.canonical().size();
|
||||||
|
let mut value = Vec::with_capacity(self.len());
|
||||||
|
for _ in 0..self.len() {
|
||||||
|
let (element_opaque, rest) = opaque.split_at(element_size);
|
||||||
|
value.push(SimValue::from_opaque(element_ty, element_opaque.to_owned()));
|
||||||
|
opaque = rest;
|
||||||
|
}
|
||||||
|
value.try_into().ok().expect("used correct length")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
mut opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
let element_ty = self.element();
|
||||||
|
let element_size = element_ty.canonical().size();
|
||||||
|
let value = AsMut::<[SimValue<T>]>::as_mut(value);
|
||||||
|
assert_eq!(self.len(), value.len());
|
||||||
|
for element_value in value {
|
||||||
|
assert_eq!(element_value.ty(), element_ty);
|
||||||
|
let (element_opaque, rest) = opaque.split_at(element_size);
|
||||||
|
SimValue::opaque_mut(element_value).clone_from_slice(element_opaque);
|
||||||
|
opaque = rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
mut writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
let element_ty = self.element();
|
||||||
|
let element_size = element_ty.canonical().size();
|
||||||
|
let value = AsRef::<[SimValue<T>]>::as_ref(value);
|
||||||
|
assert_eq!(self.len(), value.len());
|
||||||
|
for element_value in value {
|
||||||
|
assert_eq!(element_value.ty(), element_ty);
|
||||||
|
writer.fill_prefix_with(element_size, |writer| {
|
||||||
|
writer.fill_cloned_from_slice(SimValue::opaque(element_value).as_slice())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type + Serialize, Len: Size> Serialize for ArrayType<T, Len> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
SerdeCanonicalType::<T>::Array {
|
||||||
|
element: self.element(),
|
||||||
|
len: self.len(),
|
||||||
|
}
|
||||||
|
.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T: Type + Deserialize<'de>, Len: Size> Deserialize<'de> for ArrayType<T, Len> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let name = |len| -> String {
|
||||||
|
if let Some(len) = len {
|
||||||
|
format!("an Array<_, {len}>")
|
||||||
|
} else {
|
||||||
|
"an Array<_>".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match SerdeCanonicalType::<T>::deserialize(deserializer)? {
|
||||||
|
SerdeCanonicalType::Array { element, len } => {
|
||||||
|
if let Some(len) = Len::try_from_usize(len) {
|
||||||
|
Ok(Self::new(element, len))
|
||||||
|
} else {
|
||||||
|
Err(Error::invalid_value(
|
||||||
|
serde::de::Unexpected::Other(&name(Some(len))),
|
||||||
|
&&*name(Len::KNOWN_VALUE),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ty => Err(Error::invalid_value(
|
||||||
|
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
|
||||||
|
&&*name(Len::KNOWN_VALUE),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
|
impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
|
||||||
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
|
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
|
||||||
let base = Expr::as_dyn_array(*this);
|
let retval = Vec::from_iter(*this);
|
||||||
let base_ty = Expr::ty(base);
|
Interned::into_inner(Intern::intern_sized(
|
||||||
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
|
|
||||||
Interned::<_>::into_inner(Intern::intern_sized(
|
|
||||||
Len::ArrayMatch::<T>::try_from(retval)
|
Len::ArrayMatch::<T>::try_from(retval)
|
||||||
.ok()
|
.ok()
|
||||||
.expect("unreachable"),
|
.expect("unreachable"),
|
||||||
|
|
@ -202,7 +346,7 @@ impl<T: Type> Index<T> for ArrayWithoutGenerics {
|
||||||
type Output = ArrayWithoutLen<T>;
|
type Output = ArrayWithoutLen<T>;
|
||||||
|
|
||||||
fn index(&self, element: T) -> &Self::Output {
|
fn index(&self, element: T) -> &Self::Output {
|
||||||
Interned::<_>::into_inner(Intern::intern_sized(ArrayWithoutLen { element }))
|
Interned::into_inner(Intern::intern_sized(ArrayWithoutLen { element }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,6 +359,173 @@ impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
|
||||||
type Output = ArrayType<T, L::Size>;
|
type Output = ArrayType<T, L::Size>;
|
||||||
|
|
||||||
fn index(&self, len: L) -> &Self::Output {
|
fn index(&self, len: L) -> &Self::Output {
|
||||||
Interned::<_>::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
|
Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Lhs: Type, Rhs: Type, Len: Size> HdlPartialEqImpl<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
|
||||||
|
where
|
||||||
|
Lhs: HdlPartialEqImpl<Rhs>,
|
||||||
|
{
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = <Lhs as HdlPartialEqImpl<Rhs>>::TRY_STRUCTURAL_EQ;
|
||||||
|
|
||||||
|
fn cmp_value_eq(
|
||||||
|
lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: ArrayType<Rhs, Len>,
|
||||||
|
rhs_value: Cow<'_, <ArrayType<Rhs, Len> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs.len(), rhs.len());
|
||||||
|
let lhs = lhs.element();
|
||||||
|
let rhs = rhs.element();
|
||||||
|
let lhs_value: &[_] = (*lhs_value).as_ref();
|
||||||
|
let rhs_value: &[_] = (*rhs_value).as_ref();
|
||||||
|
for (lhs_value, rhs_value) in lhs_value.iter().zip(rhs_value) {
|
||||||
|
if !Lhs::cmp_value_eq(lhs, Cow::Borrowed(lhs_value), rhs, Cow::Borrowed(rhs_value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty().len(), rhs.ty().len());
|
||||||
|
if Self::TRY_STRUCTURAL_EQ {
|
||||||
|
if let Ok(retval) = StructuralEq::try_new(Expr::canonical(lhs), Expr::canonical(rhs)) {
|
||||||
|
return retval.to_expr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lhs.into_iter()
|
||||||
|
.zip(rhs)
|
||||||
|
.map(|(l, r)| l.cmp_eq(r))
|
||||||
|
.collect::<Expr<Array<Bool>>>()
|
||||||
|
.cast_to_bits()
|
||||||
|
.all_one_bits()
|
||||||
|
}
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty().len(), rhs.ty().len());
|
||||||
|
if Self::TRY_STRUCTURAL_EQ {
|
||||||
|
if let Ok(retval) = StructuralEq::try_new(Expr::canonical(lhs), Expr::canonical(rhs)) {
|
||||||
|
return !retval.to_expr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lhs.into_iter()
|
||||||
|
.zip(rhs)
|
||||||
|
.map(|(l, r)| l.cmp_ne(r))
|
||||||
|
.collect::<Expr<Array<Bool>>>()
|
||||||
|
.cast_to_bits()
|
||||||
|
.any_one_bits()
|
||||||
|
}
|
||||||
|
fn cmp_valueless_eq(
|
||||||
|
lhs: Valueless<Self>,
|
||||||
|
rhs: Valueless<ArrayType<Rhs, Len>>,
|
||||||
|
) -> Valueless<Bool> {
|
||||||
|
assert_eq!(lhs.ty().len(), rhs.ty().len());
|
||||||
|
Valueless::new(Bool)
|
||||||
|
}
|
||||||
|
fn cmp_valueless_ne(
|
||||||
|
lhs: Valueless<Self>,
|
||||||
|
rhs: Valueless<ArrayType<Rhs, Len>>,
|
||||||
|
) -> Valueless<Bool> {
|
||||||
|
assert_eq!(lhs.ty().len(), rhs.ty().len());
|
||||||
|
Valueless::new(Bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> ExprIntoIterator for ArrayType<T, Len> {
|
||||||
|
type Item = T;
|
||||||
|
type ExprIntoIter = ExprArrayIter<T, Len>;
|
||||||
|
|
||||||
|
fn expr_into_iter(e: Expr<Self>) -> Self::ExprIntoIter {
|
||||||
|
ExprArrayIter {
|
||||||
|
base: e,
|
||||||
|
indexes: 0..e.ty().len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ExprArrayIter<T: Type, Len: Size> {
|
||||||
|
base: Expr<ArrayType<T, Len>>,
|
||||||
|
indexes: std::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> ExprArrayIter<T, Len> {
|
||||||
|
pub fn base(&self) -> Expr<ArrayType<T, Len>> {
|
||||||
|
self.base
|
||||||
|
}
|
||||||
|
pub fn indexes(&self) -> std::ops::Range<usize> {
|
||||||
|
self.indexes.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> Iterator for ExprArrayIter<T, Len> {
|
||||||
|
type Item = Expr<T>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.indexes.next().map(|i| self.base[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.indexes.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(self) -> usize {
|
||||||
|
self.indexes.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(mut self) -> Option<Self::Item> {
|
||||||
|
self.next_back()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.indexes.nth(n).map(|i| self.base[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold<B, F>(self, init: B, mut f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.indexes.fold(init, |b, i| f(b, self.base[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> DoubleEndedIterator for ExprArrayIter<T, Len> {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
self.indexes.next_back().map(|i| self.base[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.indexes.nth_back(n).map(|i| self.base[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfold<B, F>(self, init: B, mut f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.indexes.rfold(init, |b, i| f(b, self.base[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> ExactSizeIterator for ExprArrayIter<T, Len> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.indexes.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, Len: Size> FusedIterator for ExprArrayIter<T, Len> {}
|
||||||
|
|
||||||
|
impl<A: StaticType> ExprFromIterator<Expr<A>> for Array<A> {
|
||||||
|
fn expr_from_iter<T: IntoIterator<Item = Expr<A>>>(iter: T) -> Expr<Self> {
|
||||||
|
ArrayLiteral::new(
|
||||||
|
A::TYPE,
|
||||||
|
iter.into_iter().map(|v| Expr::canonical(v)).collect(),
|
||||||
|
)
|
||||||
|
.to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: StaticType> ExprFromIterator<&'a Expr<A>> for Array<A> {
|
||||||
|
fn expr_from_iter<T: IntoIterator<Item = &'a Expr<A>>>(iter: T) -> Expr<Self> {
|
||||||
|
iter.into_iter().copied().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2803
crates/fayalite/src/build.rs
Normal file
2803
crates/fayalite/src/build.rs
Normal file
File diff suppressed because it is too large
Load diff
1177
crates/fayalite/src/build/external.rs
Normal file
1177
crates/fayalite/src/build/external.rs
Normal file
File diff suppressed because it is too large
Load diff
128
crates/fayalite/src/build/firrtl.rs
Normal file
128
crates/fayalite/src/build/firrtl.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{
|
||||||
|
BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies,
|
||||||
|
JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams,
|
||||||
|
ToArgs, WriteArgs,
|
||||||
|
},
|
||||||
|
firrtl::{ExportOptions, FileBackend},
|
||||||
|
intern::{Intern, InternSlice, Interned},
|
||||||
|
util::job_server::AcquiredJob,
|
||||||
|
};
|
||||||
|
use clap::Args;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
|
||||||
|
pub struct FirrtlJobKind;
|
||||||
|
|
||||||
|
#[derive(Args, Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
#[group(id = "Firrtl")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct FirrtlArgs {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub export_options: ExportOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToArgs for FirrtlArgs {
|
||||||
|
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self { export_options } = self;
|
||||||
|
export_options.to_args(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Firrtl {
|
||||||
|
base: BaseJob,
|
||||||
|
export_options: ExportOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Firrtl {
|
||||||
|
fn make_firrtl_file_backend(&self) -> FileBackend {
|
||||||
|
FileBackend {
|
||||||
|
dir_path: PathBuf::from(&*self.base.output_dir()),
|
||||||
|
top_fir_file_stem: Some(self.base.file_stem().into()),
|
||||||
|
circuit_name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn firrtl_file(&self) -> Interned<Path> {
|
||||||
|
self.base.file_with_ext("fir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKind for FirrtlJobKind {
|
||||||
|
type Args = FirrtlArgs;
|
||||||
|
type Job = Firrtl;
|
||||||
|
type Dependencies = JobKindAndDependencies<BaseJobKind>;
|
||||||
|
|
||||||
|
fn dependencies(self) -> Self::Dependencies {
|
||||||
|
JobKindAndDependencies::new(BaseJobKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_to_jobs(
|
||||||
|
args: JobArgsAndDependencies<Self>,
|
||||||
|
params: &JobParams,
|
||||||
|
global_params: &GlobalParams,
|
||||||
|
) -> eyre::Result<JobAndDependencies<Self>> {
|
||||||
|
args.args_to_jobs_simple(
|
||||||
|
params,
|
||||||
|
global_params,
|
||||||
|
|_kind, FirrtlArgs { export_options }, dependencies| {
|
||||||
|
Ok(Firrtl {
|
||||||
|
base: dependencies.get_job::<BaseJob, _>().clone(),
|
||||||
|
export_options,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::Path {
|
||||||
|
path: job.base.output_dir(),
|
||||||
|
}]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::Path {
|
||||||
|
path: job.firrtl_file(),
|
||||||
|
}]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(self) -> Interned<str> {
|
||||||
|
"firrtl".intern()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self,
|
||||||
|
job: &Self::Job,
|
||||||
|
inputs: &[JobItem],
|
||||||
|
params: &JobParams,
|
||||||
|
_global_params: &GlobalParams,
|
||||||
|
_acquired_job: &mut AcquiredJob,
|
||||||
|
) -> eyre::Result<Vec<JobItem>> {
|
||||||
|
let [JobItem::Path { path: input_path }] = *inputs else {
|
||||||
|
panic!("wrong inputs, expected a single `Path`");
|
||||||
|
};
|
||||||
|
assert_eq!(input_path, job.base.output_dir());
|
||||||
|
crate::firrtl::export(
|
||||||
|
job.make_firrtl_file_backend(),
|
||||||
|
params.main_module(),
|
||||||
|
job.export_options,
|
||||||
|
)?;
|
||||||
|
Ok(vec![JobItem::Path {
|
||||||
|
path: job.firrtl_file(),
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|
||||||
|
[DynJobKind::new(FirrtlJobKind)]
|
||||||
|
}
|
||||||
388
crates/fayalite/src/build/formal.rs
Normal file
388
crates/fayalite/src/build/formal.rs
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{
|
||||||
|
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
|
||||||
|
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
|
||||||
|
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
|
||||||
|
external::{
|
||||||
|
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
|
||||||
|
},
|
||||||
|
verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind},
|
||||||
|
},
|
||||||
|
intern::{Intern, InternSlice, Interned},
|
||||||
|
module::NameId,
|
||||||
|
testing::FormalMode,
|
||||||
|
util::job_server::AcquiredJob,
|
||||||
|
};
|
||||||
|
use clap::Args;
|
||||||
|
use eyre::Context;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fmt::{self, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct FormalArgs {
|
||||||
|
#[arg(long = "sby-extra-arg", value_name = "ARG")]
|
||||||
|
pub sby_extra_args: Vec<OsString>,
|
||||||
|
#[arg(long, default_value_t)]
|
||||||
|
pub formal_mode: FormalMode,
|
||||||
|
#[arg(long, default_value_t = Self::DEFAULT_DEPTH)]
|
||||||
|
pub formal_depth: u64,
|
||||||
|
#[arg(long, default_value = Self::DEFAULT_SOLVER)]
|
||||||
|
pub formal_solver: String,
|
||||||
|
#[arg(long = "smtbmc-extra-arg", value_name = "ARG")]
|
||||||
|
pub smtbmc_extra_args: Vec<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalArgs {
|
||||||
|
pub const DEFAULT_DEPTH: u64 = 20;
|
||||||
|
pub const DEFAULT_SOLVER: &'static str = "z3";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToArgs for FormalArgs {
|
||||||
|
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self {
|
||||||
|
sby_extra_args,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver,
|
||||||
|
smtbmc_extra_args,
|
||||||
|
} = self;
|
||||||
|
for arg in sby_extra_args {
|
||||||
|
args.write_long_option_eq("sby-extra-arg", arg);
|
||||||
|
}
|
||||||
|
args.write_display_args([
|
||||||
|
format_args!("--formal-mode={formal_mode}"),
|
||||||
|
format_args!("--formal-depth={formal_depth}"),
|
||||||
|
format_args!("--formal-solver={formal_solver}"),
|
||||||
|
]);
|
||||||
|
for arg in smtbmc_extra_args {
|
||||||
|
args.write_long_option_eq("smtbmc-extra-arg", arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct WriteSbyFileJobKind;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub struct WriteSbyFileJob {
|
||||||
|
sby_extra_args: Interned<[Interned<OsStr>]>,
|
||||||
|
formal_mode: FormalMode,
|
||||||
|
formal_depth: u64,
|
||||||
|
formal_solver: Interned<str>,
|
||||||
|
smtbmc_extra_args: Interned<[Interned<OsStr>]>,
|
||||||
|
sby_file: Interned<Path>,
|
||||||
|
output_dir: Interned<Path>,
|
||||||
|
main_verilog_file: Interned<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteSbyFileJob {
|
||||||
|
pub fn sby_extra_args(&self) -> Interned<[Interned<OsStr>]> {
|
||||||
|
self.sby_extra_args
|
||||||
|
}
|
||||||
|
pub fn formal_mode(&self) -> FormalMode {
|
||||||
|
self.formal_mode
|
||||||
|
}
|
||||||
|
pub fn formal_depth(&self) -> u64 {
|
||||||
|
self.formal_depth
|
||||||
|
}
|
||||||
|
pub fn formal_solver(&self) -> Interned<str> {
|
||||||
|
self.formal_solver
|
||||||
|
}
|
||||||
|
pub fn smtbmc_extra_args(&self) -> Interned<[Interned<OsStr>]> {
|
||||||
|
self.smtbmc_extra_args
|
||||||
|
}
|
||||||
|
pub fn sby_file(&self) -> Interned<Path> {
|
||||||
|
self.sby_file
|
||||||
|
}
|
||||||
|
pub fn output_dir(&self) -> Interned<Path> {
|
||||||
|
self.output_dir
|
||||||
|
}
|
||||||
|
pub fn main_verilog_file(&self) -> Interned<Path> {
|
||||||
|
self.main_verilog_file
|
||||||
|
}
|
||||||
|
fn write_sby(
|
||||||
|
&self,
|
||||||
|
output: &mut OsString,
|
||||||
|
additional_files: &[Interned<Path>],
|
||||||
|
main_module_name_id: NameId,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
let Self {
|
||||||
|
sby_extra_args: _,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver,
|
||||||
|
smtbmc_extra_args,
|
||||||
|
sby_file: _,
|
||||||
|
output_dir: _,
|
||||||
|
main_verilog_file,
|
||||||
|
} = self;
|
||||||
|
write!(
|
||||||
|
output,
|
||||||
|
"[options]\n\
|
||||||
|
mode {formal_mode}\n\
|
||||||
|
depth {formal_depth}\n\
|
||||||
|
wait on\n\
|
||||||
|
\n\
|
||||||
|
[engines]\n\
|
||||||
|
smtbmc {formal_solver} -- --"
|
||||||
|
)
|
||||||
|
.expect("writing to OsString can't fail");
|
||||||
|
for i in smtbmc_extra_args {
|
||||||
|
output.push(" ");
|
||||||
|
output.push(i);
|
||||||
|
}
|
||||||
|
output.push(
|
||||||
|
"\n\
|
||||||
|
\n\
|
||||||
|
[script]\n",
|
||||||
|
);
|
||||||
|
for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? {
|
||||||
|
output.push("read_verilog -sv -formal \"");
|
||||||
|
output.push(verilog_file);
|
||||||
|
output.push("\"\n");
|
||||||
|
}
|
||||||
|
let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id);
|
||||||
|
// workaround for wires disappearing -- set `keep` on all wires
|
||||||
|
writeln!(
|
||||||
|
output,
|
||||||
|
"hierarchy -top {circuit_name}\n\
|
||||||
|
proc\n\
|
||||||
|
setattr -set keep 1 w:\\*\n\
|
||||||
|
prep",
|
||||||
|
)
|
||||||
|
.expect("writing to OsString can't fail");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKind for WriteSbyFileJobKind {
|
||||||
|
type Args = FormalArgs;
|
||||||
|
type Job = WriteSbyFileJob;
|
||||||
|
type Dependencies = JobKindAndDependencies<VerilogJobKind>;
|
||||||
|
|
||||||
|
fn dependencies(self) -> Self::Dependencies {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_to_jobs(
|
||||||
|
mut args: JobArgsAndDependencies<Self>,
|
||||||
|
params: &JobParams,
|
||||||
|
global_params: &GlobalParams,
|
||||||
|
) -> eyre::Result<JobAndDependencies<Self>> {
|
||||||
|
args.dependencies
|
||||||
|
.dependencies
|
||||||
|
.args
|
||||||
|
.args
|
||||||
|
.additional_args
|
||||||
|
.verilog_dialect
|
||||||
|
.get_or_insert(VerilogDialect::Yosys);
|
||||||
|
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
|
||||||
|
let FormalArgs {
|
||||||
|
sby_extra_args,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver,
|
||||||
|
smtbmc_extra_args,
|
||||||
|
} = args;
|
||||||
|
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||||
|
Ok(WriteSbyFileJob {
|
||||||
|
sby_extra_args: sby_extra_args.into_iter().map(Interned::from).collect(),
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver: formal_solver.intern_deref(),
|
||||||
|
smtbmc_extra_args: smtbmc_extra_args.into_iter().map(Interned::from).collect(),
|
||||||
|
sby_file: base_job.file_with_ext("sby"),
|
||||||
|
output_dir: base_job.output_dir(),
|
||||||
|
main_verilog_file: dependencies.get_job::<VerilogJob, _>().main_verilog_file(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::DynamicPaths {
|
||||||
|
source_job_name: VerilogJobKind.name(),
|
||||||
|
}]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::Path { path: job.sby_file }].intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(self) -> Interned<str> {
|
||||||
|
"write-sby-file".intern()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self,
|
||||||
|
job: &Self::Job,
|
||||||
|
inputs: &[JobItem],
|
||||||
|
params: &JobParams,
|
||||||
|
_global_params: &GlobalParams,
|
||||||
|
_acquired_job: &mut AcquiredJob,
|
||||||
|
) -> eyre::Result<Vec<JobItem>> {
|
||||||
|
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
|
||||||
|
let [additional_files] = inputs else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let additional_files = VerilogJob::unwrap_additional_files(additional_files);
|
||||||
|
let mut contents = OsString::new();
|
||||||
|
job.write_sby(
|
||||||
|
&mut contents,
|
||||||
|
additional_files,
|
||||||
|
params.main_module().name_id(),
|
||||||
|
)?;
|
||||||
|
let path = job.sby_file;
|
||||||
|
std::fs::write(path, contents.as_encoded_bytes())
|
||||||
|
.wrap_err_with(|| format!("writing {path:?} failed"))?;
|
||||||
|
Ok(vec![JobItem::Path { path }])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand_hidden(self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Formal {
|
||||||
|
#[serde(flatten)]
|
||||||
|
write_sby_file: WriteSbyFileJob,
|
||||||
|
sby_file_name: Interned<OsStr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Formal {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self {
|
||||||
|
write_sby_file:
|
||||||
|
WriteSbyFileJob {
|
||||||
|
sby_extra_args,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver,
|
||||||
|
smtbmc_extra_args,
|
||||||
|
sby_file,
|
||||||
|
output_dir: _,
|
||||||
|
main_verilog_file,
|
||||||
|
},
|
||||||
|
sby_file_name,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("Formal")
|
||||||
|
.field("sby_extra_args", sby_extra_args)
|
||||||
|
.field("formal_mode", formal_mode)
|
||||||
|
.field("formal_depth", formal_depth)
|
||||||
|
.field("formal_solver", formal_solver)
|
||||||
|
.field("smtbmc_extra_args", smtbmc_extra_args)
|
||||||
|
.field("sby_file", sby_file)
|
||||||
|
.field("sby_file_name", sby_file_name)
|
||||||
|
.field("main_verilog_file", main_verilog_file)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||||
|
pub struct Symbiyosys;
|
||||||
|
|
||||||
|
impl ExternalProgramTrait for Symbiyosys {
|
||||||
|
fn default_program_name() -> Interned<str> {
|
||||||
|
"sby".intern()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, PartialEq, Eq, Debug, Args)]
|
||||||
|
pub struct FormalAdditionalArgs {}
|
||||||
|
|
||||||
|
impl ToArgs for FormalAdditionalArgs {
|
||||||
|
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self {} = self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalCommand for Formal {
|
||||||
|
type AdditionalArgs = FormalAdditionalArgs;
|
||||||
|
type AdditionalJobData = Formal;
|
||||||
|
type BaseJobPosition = GetJobPositionDependencies<
|
||||||
|
GetJobPositionDependencies<
|
||||||
|
GetJobPositionDependencies<<UnadjustedVerilog as ExternalCommand>::BaseJobPosition>,
|
||||||
|
>,
|
||||||
|
>;
|
||||||
|
type Dependencies = JobKindAndDependencies<WriteSbyFileJobKind>;
|
||||||
|
type ExternalProgram = Symbiyosys;
|
||||||
|
|
||||||
|
fn dependencies() -> Self::Dependencies {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_to_jobs(
|
||||||
|
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
|
||||||
|
params: &JobParams,
|
||||||
|
global_params: &GlobalParams,
|
||||||
|
) -> eyre::Result<(
|
||||||
|
Self::AdditionalJobData,
|
||||||
|
<Self::Dependencies as JobDependencies>::JobsAndKinds,
|
||||||
|
)> {
|
||||||
|
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
|
||||||
|
let FormalAdditionalArgs {} = args.additional_args;
|
||||||
|
let write_sby_file = dependencies.get_job::<WriteSbyFileJob, _>().clone();
|
||||||
|
Ok(Formal {
|
||||||
|
sby_file_name: write_sby_file
|
||||||
|
.sby_file()
|
||||||
|
.interned_file_name()
|
||||||
|
.expect("known to have file name"),
|
||||||
|
write_sby_file,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
|
||||||
|
[
|
||||||
|
JobItemName::Path {
|
||||||
|
path: job.additional_job_data().write_sby_file.sby_file(),
|
||||||
|
},
|
||||||
|
JobItemName::Path {
|
||||||
|
path: job.additional_job_data().write_sby_file.main_verilog_file(),
|
||||||
|
},
|
||||||
|
JobItemName::DynamicPaths {
|
||||||
|
source_job_name: VerilogJobKind.name(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_paths(_job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
|
||||||
|
Interned::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
||||||
|
// args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode
|
||||||
|
args.write_arg("-f");
|
||||||
|
args.write_interned_arg(job.additional_job_data().sby_file_name);
|
||||||
|
args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
|
||||||
|
Some(job.output_dir())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn job_kind_name() -> Interned<str> {
|
||||||
|
"formal".intern()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|
||||||
|
[
|
||||||
|
DynJobKind::new(WriteSbyFileJobKind),
|
||||||
|
DynJobKind::new(ExternalCommandJobKind::<Formal>::new()),
|
||||||
|
]
|
||||||
|
}
|
||||||
855
crates/fayalite/src/build/graph.rs
Normal file
855
crates/fayalite/src/build/graph.rs
Normal file
|
|
@ -0,0 +1,855 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{
|
||||||
|
DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs,
|
||||||
|
},
|
||||||
|
intern::Interned,
|
||||||
|
platform::DynPlatform,
|
||||||
|
util::{HashMap, HashSet, job_server::AcquiredJob},
|
||||||
|
};
|
||||||
|
use eyre::{ContextCompat, eyre};
|
||||||
|
use petgraph::{
|
||||||
|
algo::{DfsSpace, kosaraju_scc, toposort},
|
||||||
|
graph::{DiGraph, NodeIndex},
|
||||||
|
visit::{GraphBase, Visitable},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq};
|
||||||
|
use std::{
|
||||||
|
cell::OnceCell,
|
||||||
|
collections::{BTreeMap, BTreeSet, VecDeque},
|
||||||
|
convert::Infallible,
|
||||||
|
ffi::OsStr,
|
||||||
|
fmt::{self, Write},
|
||||||
|
panic,
|
||||||
|
rc::Rc,
|
||||||
|
str::Utf8Error,
|
||||||
|
sync::mpsc,
|
||||||
|
thread::{self, ScopedJoinHandle},
|
||||||
|
};
|
||||||
|
|
||||||
|
macro_rules! write_str {
|
||||||
|
($s:expr, $($rest:tt)*) => {
|
||||||
|
write!($s, $($rest)*).expect("String::write_fmt can't fail")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum JobGraphNode {
|
||||||
|
Job(DynJob),
|
||||||
|
Item {
|
||||||
|
#[allow(dead_code, reason = "name used for debugging")]
|
||||||
|
name: JobItemName,
|
||||||
|
source_job: Option<DynJob>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobGraphInner = DiGraph<JobGraphNode, ()>;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct JobGraph {
|
||||||
|
jobs: HashMap<DynJob, <JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
items: HashMap<JobItemName, <JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
graph: JobGraphInner,
|
||||||
|
topological_order: Vec<<JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
space: DfsSpace<<JobGraphInner as GraphBase>::NodeId, <JobGraphInner as Visitable>::Map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for JobGraph {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self {
|
||||||
|
jobs: _,
|
||||||
|
items: _,
|
||||||
|
graph,
|
||||||
|
topological_order,
|
||||||
|
space: _,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("JobGraph")
|
||||||
|
.field("graph", graph)
|
||||||
|
.field("topological_order", topological_order)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum JobGraphError {
|
||||||
|
CycleError {
|
||||||
|
job: DynJob,
|
||||||
|
output: JobItemName,
|
||||||
|
},
|
||||||
|
MultipleJobsCreateSameOutput {
|
||||||
|
output_item: JobItemName,
|
||||||
|
existing_job: DynJob,
|
||||||
|
new_job: DynJob,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for JobGraphError {}
|
||||||
|
|
||||||
|
impl fmt::Display for JobGraphError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::CycleError { job, output } => write!(
|
||||||
|
f,
|
||||||
|
"job can't be added to job graph because it would introduce a cyclic dependency through this job output:\n\
|
||||||
|
{output:?}\n\
|
||||||
|
job:\n{job:?}",
|
||||||
|
),
|
||||||
|
JobGraphError::MultipleJobsCreateSameOutput {
|
||||||
|
output_item,
|
||||||
|
existing_job,
|
||||||
|
new_job,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"job can't be added to job graph because the new job has an output that is also produced by an existing job.\n\
|
||||||
|
conflicting output:\n\
|
||||||
|
{output_item:?}\n\
|
||||||
|
existing job:\n\
|
||||||
|
{existing_job:?}\n\
|
||||||
|
new job:\n\
|
||||||
|
{new_job:?}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum EscapeForUnixShellState {
|
||||||
|
DollarSingleQuote,
|
||||||
|
SingleQuote,
|
||||||
|
Unquoted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EscapeForUnixShell<'a> {
|
||||||
|
state: EscapeForUnixShellState,
|
||||||
|
prefix: [u8; 3],
|
||||||
|
bytes: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Debug for EscapeForUnixShell<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for EscapeForUnixShell<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for c in self.clone() {
|
||||||
|
f.write_char(c)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EscapeForUnixShell<'a> {
|
||||||
|
pub fn new(s: &'a (impl ?Sized + AsRef<OsStr>)) -> Self {
|
||||||
|
Self::from_bytes(s.as_ref().as_encoded_bytes())
|
||||||
|
}
|
||||||
|
fn make_prefix(bytes: &[u8]) -> [u8; 3] {
|
||||||
|
let mut prefix = [0; 3];
|
||||||
|
prefix[..bytes.len()].copy_from_slice(bytes);
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
pub fn from_bytes(bytes: &'a [u8]) -> Self {
|
||||||
|
let mut needs_single_quote = bytes.is_empty();
|
||||||
|
for &b in bytes {
|
||||||
|
match b {
|
||||||
|
b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true,
|
||||||
|
0..0x20 | 0x7F.. => {
|
||||||
|
return Self {
|
||||||
|
state: EscapeForUnixShellState::DollarSingleQuote,
|
||||||
|
prefix: Self::make_prefix(b"$'"),
|
||||||
|
bytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needs_single_quote {
|
||||||
|
Self {
|
||||||
|
state: EscapeForUnixShellState::SingleQuote,
|
||||||
|
prefix: Self::make_prefix(b"'"),
|
||||||
|
bytes,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
state: EscapeForUnixShellState::Unquoted,
|
||||||
|
prefix: Self::make_prefix(b""),
|
||||||
|
bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for EscapeForUnixShell<'_> {
|
||||||
|
type Item = char;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match &mut self.prefix {
|
||||||
|
[0, 0, 0] => {}
|
||||||
|
[0, 0, v] | // find first
|
||||||
|
[0, v, _] | // non-zero byte
|
||||||
|
[v, _, _] => {
|
||||||
|
let retval = *v as char;
|
||||||
|
*v = 0;
|
||||||
|
return Some(retval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(&next_byte) = self.bytes.split_off_first() else {
|
||||||
|
return match self.state {
|
||||||
|
EscapeForUnixShellState::DollarSingleQuote
|
||||||
|
| EscapeForUnixShellState::SingleQuote => {
|
||||||
|
self.state = EscapeForUnixShellState::Unquoted;
|
||||||
|
Some('\'')
|
||||||
|
}
|
||||||
|
EscapeForUnixShellState::Unquoted => None,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
match self.state {
|
||||||
|
EscapeForUnixShellState::DollarSingleQuote => match next_byte {
|
||||||
|
b'\'' | b'\\' => {
|
||||||
|
self.prefix = Self::make_prefix(&[next_byte]);
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
b'\t' => {
|
||||||
|
self.prefix = Self::make_prefix(b"t");
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
b'\n' => {
|
||||||
|
self.prefix = Self::make_prefix(b"n");
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
b'\r' => {
|
||||||
|
self.prefix = Self::make_prefix(b"r");
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
0x20..=0x7E => Some(next_byte as char),
|
||||||
|
_ => {
|
||||||
|
self.prefix = [
|
||||||
|
b'x',
|
||||||
|
char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range")
|
||||||
|
as u8,
|
||||||
|
char::from_digit(next_byte as u32 & 0xF, 0x10)
|
||||||
|
.expect("known to be in range") as u8,
|
||||||
|
];
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EscapeForUnixShellState::SingleQuote => {
|
||||||
|
if next_byte == b'\'' {
|
||||||
|
self.prefix = Self::make_prefix(b"\\''");
|
||||||
|
Some('\'')
|
||||||
|
} else {
|
||||||
|
Some(next_byte as char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EscapeForUnixShellState::Unquoted => match next_byte {
|
||||||
|
b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b','
|
||||||
|
| b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|'
|
||||||
|
| b'}' | b'~' => {
|
||||||
|
self.prefix = Self::make_prefix(&[next_byte]);
|
||||||
|
Some('\\')
|
||||||
|
}
|
||||||
|
_ => Some(next_byte as char),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum UnixMakefileEscapeKind {
|
||||||
|
NonRecipe,
|
||||||
|
RecipeWithoutShellEscaping,
|
||||||
|
RecipeWithShellEscaping,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct EscapeForUnixMakefile<'a> {
|
||||||
|
s: &'a OsStr,
|
||||||
|
kind: UnixMakefileEscapeKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> fmt::Display for EscapeForUnixMakefile<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.do_write(
|
||||||
|
f,
|
||||||
|
fmt::Write::write_str,
|
||||||
|
fmt::Write::write_char,
|
||||||
|
|_, _| Ok(()),
|
||||||
|
|_| unreachable!("already checked that the input causes no UTF-8 errors"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EscapeForUnixMakefile<'a> {
|
||||||
|
fn do_write<S: ?Sized, E>(
|
||||||
|
&self,
|
||||||
|
state: &mut S,
|
||||||
|
write_str: impl Fn(&mut S, &str) -> Result<(), E>,
|
||||||
|
write_char: impl Fn(&mut S, char) -> Result<(), E>,
|
||||||
|
add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>,
|
||||||
|
utf8_error: impl Fn(Utf8Error) -> E,
|
||||||
|
) -> Result<(), E> {
|
||||||
|
let escape_recipe_char = |c| match c {
|
||||||
|
'$' => write_str(state, "$$"),
|
||||||
|
'\0'..='\x1F' | '\x7F' => {
|
||||||
|
panic!("can't escape a control character for Unix Makefile: {c:?}");
|
||||||
|
}
|
||||||
|
_ => write_char(state, c),
|
||||||
|
};
|
||||||
|
match self.kind {
|
||||||
|
UnixMakefileEscapeKind::NonRecipe => str::from_utf8(self.s.as_encoded_bytes())
|
||||||
|
.map_err(&utf8_error)?
|
||||||
|
.chars()
|
||||||
|
.try_for_each(|c| match c {
|
||||||
|
'=' => {
|
||||||
|
add_variable(state, "EQUALS = =")?;
|
||||||
|
write_str(state, "$(EQUALS)")
|
||||||
|
}
|
||||||
|
';' => panic!("can't escape a semicolon (;) for Unix Makefile"),
|
||||||
|
'$' => write_str(state, "$$"),
|
||||||
|
'\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => {
|
||||||
|
write_char(state, '\\')?;
|
||||||
|
write_char(state, c)
|
||||||
|
}
|
||||||
|
'\0'..='\x1F' | '\x7F' => {
|
||||||
|
panic!("can't escape a control character for Unix Makefile: {c:?}");
|
||||||
|
}
|
||||||
|
_ => write_char(state, c),
|
||||||
|
}),
|
||||||
|
UnixMakefileEscapeKind::RecipeWithoutShellEscaping => {
|
||||||
|
str::from_utf8(self.s.as_encoded_bytes())
|
||||||
|
.map_err(&utf8_error)?
|
||||||
|
.chars()
|
||||||
|
.try_for_each(escape_recipe_char)
|
||||||
|
}
|
||||||
|
UnixMakefileEscapeKind::RecipeWithShellEscaping => {
|
||||||
|
EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new(
|
||||||
|
s: &'a (impl ?Sized + AsRef<OsStr>),
|
||||||
|
kind: UnixMakefileEscapeKind,
|
||||||
|
needed_variables: &mut BTreeSet<&'static str>,
|
||||||
|
) -> Result<Self, Utf8Error> {
|
||||||
|
let s = s.as_ref();
|
||||||
|
let retval = Self { s, kind };
|
||||||
|
retval.do_write(
|
||||||
|
needed_variables,
|
||||||
|
|_, _| Ok(()),
|
||||||
|
|_, _| Ok(()),
|
||||||
|
|needed_variables, variable| {
|
||||||
|
needed_variables.insert(variable);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|e| e,
|
||||||
|
)?;
|
||||||
|
Ok(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobGraph {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
fn try_add_item_node(
|
||||||
|
&mut self,
|
||||||
|
name: JobItemName,
|
||||||
|
new_source_job: Option<DynJob>,
|
||||||
|
new_nodes: &mut HashSet<<JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
) -> Result<<JobGraphInner as GraphBase>::NodeId, JobGraphError> {
|
||||||
|
use hashbrown::hash_map::Entry;
|
||||||
|
match self.items.entry(name) {
|
||||||
|
Entry::Occupied(item_entry) => {
|
||||||
|
let node_id = *item_entry.get();
|
||||||
|
let JobGraphNode::Item {
|
||||||
|
name: _,
|
||||||
|
source_job,
|
||||||
|
} = &mut self.graph[node_id]
|
||||||
|
else {
|
||||||
|
unreachable!("known to be an item");
|
||||||
|
};
|
||||||
|
if let Some(new_source_job) = new_source_job {
|
||||||
|
if let Some(source_job) = source_job {
|
||||||
|
return Err(JobGraphError::MultipleJobsCreateSameOutput {
|
||||||
|
output_item: item_entry.key().clone(),
|
||||||
|
existing_job: source_job.clone(),
|
||||||
|
new_job: new_source_job,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
*source_job = Some(new_source_job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(node_id)
|
||||||
|
}
|
||||||
|
Entry::Vacant(item_entry) => {
|
||||||
|
let node_id = self.graph.add_node(JobGraphNode::Item {
|
||||||
|
name,
|
||||||
|
source_job: new_source_job,
|
||||||
|
});
|
||||||
|
new_nodes.insert(node_id);
|
||||||
|
item_entry.insert(node_id);
|
||||||
|
Ok(node_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_add_jobs<I: IntoIterator<Item = DynJob>>(
|
||||||
|
&mut self,
|
||||||
|
jobs: I,
|
||||||
|
) -> Result<(), JobGraphError> {
|
||||||
|
use hashbrown::hash_map::Entry;
|
||||||
|
let jobs = jobs.into_iter();
|
||||||
|
struct RemoveNewNodesOnError<'a> {
|
||||||
|
this: &'a mut JobGraph,
|
||||||
|
new_nodes: HashSet<<JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
}
|
||||||
|
impl Drop for RemoveNewNodesOnError<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for node in self.new_nodes.drain() {
|
||||||
|
self.this.graph.remove_node(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut remove_new_nodes_on_error = RemoveNewNodesOnError {
|
||||||
|
this: self,
|
||||||
|
new_nodes: HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default()),
|
||||||
|
};
|
||||||
|
let new_nodes = &mut remove_new_nodes_on_error.new_nodes;
|
||||||
|
let this = &mut *remove_new_nodes_on_error.this;
|
||||||
|
for job in jobs {
|
||||||
|
let Entry::Vacant(job_entry) = this.jobs.entry(job.clone()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let job_node_id = this
|
||||||
|
.graph
|
||||||
|
.add_node(JobGraphNode::Job(job_entry.key().clone()));
|
||||||
|
new_nodes.insert(job_node_id);
|
||||||
|
job_entry.insert(job_node_id);
|
||||||
|
for name in job.outputs() {
|
||||||
|
let item_node_id = this.try_add_item_node(name, Some(job.clone()), new_nodes)?;
|
||||||
|
this.graph.add_edge(job_node_id, item_node_id, ());
|
||||||
|
}
|
||||||
|
for name in job.inputs() {
|
||||||
|
let item_node_id = this.try_add_item_node(name, None, new_nodes)?;
|
||||||
|
this.graph.add_edge(item_node_id, job_node_id, ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match toposort(&this.graph, Some(&mut this.space)) {
|
||||||
|
Ok(v) => {
|
||||||
|
this.topological_order = v;
|
||||||
|
// no need to remove any of the new nodes on drop since we didn't encounter any errors
|
||||||
|
remove_new_nodes_on_error.new_nodes.clear();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// there's at least one cycle, find one!
|
||||||
|
let cycle = kosaraju_scc(&this.graph)
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|scc| {
|
||||||
|
if scc.len() <= 1 {
|
||||||
|
// can't be a cycle since our graph is bipartite --
|
||||||
|
// jobs only connect to items, never jobs to jobs or items to items
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(scc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("we know there's a cycle");
|
||||||
|
let cycle_set = HashSet::<NodeIndex>::from_iter(cycle.iter().copied());
|
||||||
|
let job = cycle
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|node_id| {
|
||||||
|
if let JobGraphNode::Job(job) = &this.graph[node_id] {
|
||||||
|
Some(job.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("a job must be part of the cycle");
|
||||||
|
let output = job
|
||||||
|
.outputs()
|
||||||
|
.into_iter()
|
||||||
|
.find(|output| cycle_set.contains(&this.items[output]))
|
||||||
|
.expect("an output must be part of the cycle");
|
||||||
|
Err(JobGraphError::CycleError { job, output })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn add_jobs<I: IntoIterator<Item = DynJob>>(&mut self, jobs: I) {
|
||||||
|
match self.try_add_jobs(jobs) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => panic!("error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_unix_makefile(
|
||||||
|
&self,
|
||||||
|
platform: Option<&DynPlatform>,
|
||||||
|
extra_args: &[Interned<OsStr>],
|
||||||
|
) -> Result<String, Utf8Error> {
|
||||||
|
self.to_unix_makefile_with_internal_program_prefix(
|
||||||
|
&[program_name_for_internal_jobs()],
|
||||||
|
platform,
|
||||||
|
extra_args,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn to_unix_makefile_with_internal_program_prefix(
|
||||||
|
&self,
|
||||||
|
internal_program_prefix: &[Interned<OsStr>],
|
||||||
|
platform: Option<&DynPlatform>,
|
||||||
|
extra_args: &[Interned<OsStr>],
|
||||||
|
) -> Result<String, Utf8Error> {
|
||||||
|
let mut retval = String::new();
|
||||||
|
let mut needed_variables = BTreeSet::new();
|
||||||
|
let mut phony_targets = BTreeSet::new();
|
||||||
|
for &node_id in &self.topological_order {
|
||||||
|
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let outputs = job.outputs();
|
||||||
|
if outputs.is_empty() {
|
||||||
|
retval.push_str(":");
|
||||||
|
} else {
|
||||||
|
for output in job.outputs() {
|
||||||
|
match output {
|
||||||
|
JobItemName::Path { path } => {
|
||||||
|
write_str!(
|
||||||
|
retval,
|
||||||
|
"{} ",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
&str::from_utf8(path.as_os_str().as_encoded_bytes())?,
|
||||||
|
UnixMakefileEscapeKind::NonRecipe,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
JobItemName::DynamicPaths { source_job_name } => {
|
||||||
|
write_str!(
|
||||||
|
retval,
|
||||||
|
"{} ",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
&source_job_name,
|
||||||
|
UnixMakefileEscapeKind::NonRecipe,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
phony_targets.insert(Interned::into_inner(source_job_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if outputs.len() == 1 {
|
||||||
|
retval.push_str(":");
|
||||||
|
} else {
|
||||||
|
retval.push_str("&:");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for input in job.inputs() {
|
||||||
|
match input {
|
||||||
|
JobItemName::Path { path } => {
|
||||||
|
write_str!(
|
||||||
|
retval,
|
||||||
|
" {}",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
&str::from_utf8(path.as_os_str().as_encoded_bytes())?,
|
||||||
|
UnixMakefileEscapeKind::NonRecipe,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
JobItemName::DynamicPaths { source_job_name } => {
|
||||||
|
write_str!(
|
||||||
|
retval,
|
||||||
|
" {}",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
&source_job_name,
|
||||||
|
UnixMakefileEscapeKind::NonRecipe,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
phony_targets.insert(Interned::into_inner(source_job_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval.push_str("\n\t");
|
||||||
|
job.command_params_with_internal_program_prefix(
|
||||||
|
internal_program_prefix,
|
||||||
|
platform,
|
||||||
|
extra_args,
|
||||||
|
)
|
||||||
|
.to_unix_shell_line(&mut retval, |arg, output| {
|
||||||
|
write_str!(
|
||||||
|
output,
|
||||||
|
"{}",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
arg,
|
||||||
|
UnixMakefileEscapeKind::RecipeWithShellEscaping,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
retval.push_str("\n\n");
|
||||||
|
}
|
||||||
|
if !phony_targets.is_empty() {
|
||||||
|
retval.push_str("\n.PHONY:");
|
||||||
|
for phony_target in phony_targets {
|
||||||
|
write_str!(
|
||||||
|
retval,
|
||||||
|
" {}",
|
||||||
|
EscapeForUnixMakefile::new(
|
||||||
|
phony_target,
|
||||||
|
UnixMakefileEscapeKind::NonRecipe,
|
||||||
|
&mut needed_variables
|
||||||
|
)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
retval.push_str("\n");
|
||||||
|
}
|
||||||
|
if !needed_variables.is_empty() {
|
||||||
|
retval.insert_str(
|
||||||
|
0,
|
||||||
|
&String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(retval)
|
||||||
|
}
|
||||||
|
pub fn to_unix_shell_script(
|
||||||
|
&self,
|
||||||
|
platform: Option<&DynPlatform>,
|
||||||
|
extra_args: &[Interned<OsStr>],
|
||||||
|
) -> String {
|
||||||
|
self.to_unix_shell_script_with_internal_program_prefix(
|
||||||
|
&[program_name_for_internal_jobs()],
|
||||||
|
platform,
|
||||||
|
extra_args,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn to_unix_shell_script_with_internal_program_prefix(
|
||||||
|
&self,
|
||||||
|
internal_program_prefix: &[Interned<OsStr>],
|
||||||
|
platform: Option<&DynPlatform>,
|
||||||
|
extra_args: &[Interned<OsStr>],
|
||||||
|
) -> String {
|
||||||
|
let mut retval = String::from(
|
||||||
|
"#!/bin/sh\n\
|
||||||
|
set -ex\n",
|
||||||
|
);
|
||||||
|
for &node_id in &self.topological_order {
|
||||||
|
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(()) = job
|
||||||
|
.command_params_with_internal_program_prefix(
|
||||||
|
internal_program_prefix,
|
||||||
|
platform,
|
||||||
|
extra_args,
|
||||||
|
)
|
||||||
|
.to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> {
|
||||||
|
write_str!(output, "{}", EscapeForUnixShell::new(&arg));
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
retval.push_str("\n");
|
||||||
|
}
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> {
|
||||||
|
// use scope to auto-join threads on errors
|
||||||
|
thread::scope(|scope| {
|
||||||
|
struct WaitingJobState {
|
||||||
|
job_node_id: <JobGraphInner as GraphBase>::NodeId,
|
||||||
|
job: DynJob,
|
||||||
|
inputs: BTreeMap<JobItemName, OnceCell<JobItem>>,
|
||||||
|
}
|
||||||
|
let mut ready_jobs = VecDeque::new();
|
||||||
|
let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default();
|
||||||
|
for &node_id in &self.topological_order {
|
||||||
|
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let waiting_job = WaitingJobState {
|
||||||
|
job_node_id: node_id,
|
||||||
|
job: job.clone(),
|
||||||
|
inputs: job
|
||||||
|
.inputs()
|
||||||
|
.iter()
|
||||||
|
.map(|&name| (name, OnceCell::new()))
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
if waiting_job.inputs.is_empty() {
|
||||||
|
ready_jobs.push_back(waiting_job);
|
||||||
|
} else {
|
||||||
|
let waiting_job = Rc::new(waiting_job);
|
||||||
|
for &input_item in waiting_job.inputs.keys() {
|
||||||
|
item_name_to_waiting_jobs_map
|
||||||
|
.entry(input_item)
|
||||||
|
.or_default()
|
||||||
|
.push(waiting_job.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct RunningJob<'scope> {
|
||||||
|
job: DynJob,
|
||||||
|
thread: ScopedJoinHandle<'scope, eyre::Result<Vec<JobItem>>>,
|
||||||
|
}
|
||||||
|
let mut running_jobs = HashMap::<NodeIndex, RunningJob>::default();
|
||||||
|
let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel();
|
||||||
|
let mut next_finished_job = None;
|
||||||
|
loop {
|
||||||
|
if let Some(finished_job) = next_finished_job
|
||||||
|
.take()
|
||||||
|
.or_else(|| finished_jobs_receiver.try_recv().ok())
|
||||||
|
{
|
||||||
|
let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job)
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let output_items = thread.join().map_err(panic::resume_unwind)??;
|
||||||
|
assert!(
|
||||||
|
output_items.iter().map(JobItem::name).eq(job.outputs()),
|
||||||
|
"job's run() method returned the wrong output items:\n\
|
||||||
|
output items:\n\
|
||||||
|
{output_items:?}\n\
|
||||||
|
expected outputs:\n\
|
||||||
|
{:?}\n\
|
||||||
|
job:\n\
|
||||||
|
{job:?}",
|
||||||
|
job.outputs(),
|
||||||
|
);
|
||||||
|
for output_item in output_items {
|
||||||
|
for waiting_job in item_name_to_waiting_jobs_map
|
||||||
|
.remove(&output_item.name())
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
let Ok(()) =
|
||||||
|
waiting_job.inputs[&output_item.name()].set(output_item.clone())
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
if let Some(waiting_job) = Rc::into_inner(waiting_job) {
|
||||||
|
ready_jobs.push_back(waiting_job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(WaitingJobState {
|
||||||
|
job_node_id,
|
||||||
|
job,
|
||||||
|
inputs,
|
||||||
|
}) = ready_jobs.pop_front()
|
||||||
|
{
|
||||||
|
struct RunningJobInThread<'a> {
|
||||||
|
job_node_id: <JobGraphInner as GraphBase>::NodeId,
|
||||||
|
job: DynJob,
|
||||||
|
inputs: Vec<JobItem>,
|
||||||
|
params: &'a JobParams,
|
||||||
|
global_params: &'a GlobalParams,
|
||||||
|
acquired_job: AcquiredJob,
|
||||||
|
finished_jobs_sender: mpsc::Sender<<JobGraphInner as GraphBase>::NodeId>,
|
||||||
|
}
|
||||||
|
impl RunningJobInThread<'_> {
|
||||||
|
fn run(mut self) -> eyre::Result<Vec<JobItem>> {
|
||||||
|
self.job.run(
|
||||||
|
&self.inputs,
|
||||||
|
self.params,
|
||||||
|
self.global_params,
|
||||||
|
&mut self.acquired_job,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for RunningJobInThread<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.finished_jobs_sender.send(self.job_node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let name = job.kind().name();
|
||||||
|
let running_job_in_thread = RunningJobInThread {
|
||||||
|
job_node_id,
|
||||||
|
job: job.clone(),
|
||||||
|
inputs: Result::from_iter(job.inputs().iter().map(|input_name| {
|
||||||
|
inputs.get(input_name).and_then(|v| v.get().cloned()).wrap_err_with(|| {
|
||||||
|
eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}")
|
||||||
|
})
|
||||||
|
}))?,
|
||||||
|
params,
|
||||||
|
global_params,
|
||||||
|
acquired_job: AcquiredJob::acquire()?,
|
||||||
|
finished_jobs_sender: finished_jobs_sender.clone(),
|
||||||
|
};
|
||||||
|
running_jobs.insert(
|
||||||
|
job_node_id,
|
||||||
|
RunningJob {
|
||||||
|
job,
|
||||||
|
thread: thread::Builder::new()
|
||||||
|
.name(format!("job:{name}"))
|
||||||
|
.spawn_scoped(scope, move || running_job_in_thread.run())
|
||||||
|
.expect("failed to spawn thread for job"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if running_jobs.is_empty() {
|
||||||
|
assert!(item_name_to_waiting_jobs_map.is_empty());
|
||||||
|
assert!(ready_jobs.is_empty());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// nothing to do yet, block to avoid busy waiting
|
||||||
|
next_finished_job = finished_jobs_receiver.recv().ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extend<DynJob> for JobGraph {
|
||||||
|
#[track_caller]
|
||||||
|
fn extend<T: IntoIterator<Item = DynJob>>(&mut self, iter: T) {
|
||||||
|
self.add_jobs(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<DynJob> for JobGraph {
|
||||||
|
#[track_caller]
|
||||||
|
fn from_iter<T: IntoIterator<Item = DynJob>>(iter: T) -> Self {
|
||||||
|
let mut retval = Self::new();
|
||||||
|
retval.add_jobs(iter);
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for JobGraph {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?;
|
||||||
|
for &node_id in &self.topological_order {
|
||||||
|
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
serializer.serialize_element(job)?;
|
||||||
|
}
|
||||||
|
serializer.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for JobGraph {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let jobs = Vec::<DynJob>::deserialize(deserializer)?;
|
||||||
|
let mut retval = JobGraph::new();
|
||||||
|
retval.try_add_jobs(jobs).map_err(D::Error::custom)?;
|
||||||
|
Ok(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
313
crates/fayalite/src/build/registry.rs
Normal file
313
crates/fayalite/src/build/registry.rs
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{DynJobKind, JobKind, built_in_job_kinds},
|
||||||
|
intern::Interned,
|
||||||
|
util::InternedStrCompareAsStr,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt,
|
||||||
|
sync::{Arc, OnceLock, RwLock, RwLockWriteGuard},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl DynJobKind {
|
||||||
|
pub fn registry() -> JobKindRegistrySnapshot {
|
||||||
|
JobKindRegistrySnapshot(JobKindRegistry::get())
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn register(self) {
|
||||||
|
JobKindRegistry::register(JobKindRegistry::lock(), self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct JobKindRegistry {
|
||||||
|
job_kinds: BTreeMap<InternedStrCompareAsStr, DynJobKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobKindRegisterError {
|
||||||
|
SameName {
|
||||||
|
name: InternedStrCompareAsStr,
|
||||||
|
old_job_kind: DynJobKind,
|
||||||
|
new_job_kind: DynJobKind,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for JobKindRegisterError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::SameName {
|
||||||
|
name,
|
||||||
|
old_job_kind,
|
||||||
|
new_job_kind,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"two different `JobKind` can't share the same name:\n\
|
||||||
|
{name:?}\n\
|
||||||
|
old job kind:\n\
|
||||||
|
{old_job_kind:?}\n\
|
||||||
|
new job kind:\n\
|
||||||
|
{new_job_kind:?}",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait JobKindRegistryRegisterLock {
|
||||||
|
type Locked;
|
||||||
|
fn lock(self) -> Self::Locked;
|
||||||
|
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKindRegistryRegisterLock for &'static RwLock<Arc<JobKindRegistry>> {
|
||||||
|
type Locked = RwLockWriteGuard<'static, Arc<JobKindRegistry>>;
|
||||||
|
fn lock(self) -> Self::Locked {
|
||||||
|
self.write().expect("shouldn't be poisoned")
|
||||||
|
}
|
||||||
|
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry {
|
||||||
|
Arc::make_mut(locked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry {
|
||||||
|
type Locked = Self;
|
||||||
|
|
||||||
|
fn lock(self) -> Self::Locked {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry {
|
||||||
|
locked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKindRegistry {
|
||||||
|
fn lock() -> &'static RwLock<Arc<Self>> {
|
||||||
|
static REGISTRY: OnceLock<RwLock<Arc<JobKindRegistry>>> = OnceLock::new();
|
||||||
|
REGISTRY.get_or_init(Default::default)
|
||||||
|
}
|
||||||
|
fn try_register<L: JobKindRegistryRegisterLock>(
|
||||||
|
lock: L,
|
||||||
|
job_kind: DynJobKind,
|
||||||
|
) -> Result<(), JobKindRegisterError> {
|
||||||
|
use std::collections::btree_map::Entry;
|
||||||
|
let name = InternedStrCompareAsStr(job_kind.name());
|
||||||
|
// run user code only outside of lock
|
||||||
|
let mut locked = lock.lock();
|
||||||
|
let this = L::make_mut(&mut locked);
|
||||||
|
let result = match this.job_kinds.entry(name) {
|
||||||
|
Entry::Occupied(entry) => Err(JobKindRegisterError::SameName {
|
||||||
|
name,
|
||||||
|
old_job_kind: entry.get().clone(),
|
||||||
|
new_job_kind: job_kind,
|
||||||
|
}),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
entry.insert(job_kind);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(locked);
|
||||||
|
// outside of lock now, so we can test if it's the same DynJobKind
|
||||||
|
match result {
|
||||||
|
Err(JobKindRegisterError::SameName {
|
||||||
|
name: _,
|
||||||
|
old_job_kind,
|
||||||
|
new_job_kind,
|
||||||
|
}) if old_job_kind == new_job_kind => Ok(()),
|
||||||
|
result => result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn register<L: JobKindRegistryRegisterLock>(lock: L, job_kind: DynJobKind) {
|
||||||
|
match Self::try_register(lock, job_kind) {
|
||||||
|
Err(e) => panic!("{e}"),
|
||||||
|
Ok(()) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get() -> Arc<Self> {
|
||||||
|
Self::lock().read().expect("shouldn't be poisoned").clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for JobKindRegistry {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut retval = Self {
|
||||||
|
job_kinds: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
for job_kind in built_in_job_kinds() {
|
||||||
|
Self::register(&mut retval, job_kind);
|
||||||
|
}
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct JobKindRegistrySnapshot(Arc<JobKindRegistry>);
|
||||||
|
|
||||||
|
impl JobKindRegistrySnapshot {
|
||||||
|
pub fn get() -> Self {
|
||||||
|
JobKindRegistrySnapshot(JobKindRegistry::get())
|
||||||
|
}
|
||||||
|
pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynJobKind> {
|
||||||
|
self.0.job_kinds.get(name)
|
||||||
|
}
|
||||||
|
pub fn iter_with_names(&self) -> JobKindRegistryIterWithNames<'_> {
|
||||||
|
JobKindRegistryIterWithNames(self.0.job_kinds.iter())
|
||||||
|
}
|
||||||
|
pub fn iter(&self) -> JobKindRegistryIter<'_> {
|
||||||
|
JobKindRegistryIter(self.0.job_kinds.values())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a JobKindRegistrySnapshot {
|
||||||
|
type Item = &'a DynJobKind;
|
||||||
|
type IntoIter = JobKindRegistryIter<'a>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a mut JobKindRegistrySnapshot {
|
||||||
|
type Item = &'a DynJobKind;
|
||||||
|
type IntoIter = JobKindRegistryIter<'a>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct JobKindRegistryIter<'a>(
|
||||||
|
std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynJobKind>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'a> Iterator for JobKindRegistryIter<'a> {
|
||||||
|
type Item = &'a DynJobKind;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(self) -> usize
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.0.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(self) -> Option<Self::Item> {
|
||||||
|
self.0.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.0.nth(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold<B, F>(self, init: B, f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.0.fold(init, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::FusedIterator for JobKindRegistryIter<'a> {}
|
||||||
|
|
||||||
|
impl<'a> ExactSizeIterator for JobKindRegistryIter<'a> {}
|
||||||
|
|
||||||
|
impl<'a> DoubleEndedIterator for JobKindRegistryIter<'a> {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.next_back()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.0.nth_back(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfold<B, F>(self, init: B, f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.0.rfold(init, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct JobKindRegistryIterWithNames<'a>(
|
||||||
|
std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynJobKind>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'a> Iterator for JobKindRegistryIterWithNames<'a> {
|
||||||
|
type Item = (Interned<str>, &'a DynJobKind);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.next().map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.0.size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count(self) -> usize
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.0.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(self) -> Option<Self::Item> {
|
||||||
|
self.0.last().map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.0.nth(n).map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold<B, F>(self, init: B, f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.0
|
||||||
|
.map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
.fold(init, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::FusedIterator for JobKindRegistryIterWithNames<'a> {}
|
||||||
|
|
||||||
|
impl<'a> ExactSizeIterator for JobKindRegistryIterWithNames<'a> {}
|
||||||
|
|
||||||
|
impl<'a> DoubleEndedIterator for JobKindRegistryIterWithNames<'a> {
|
||||||
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0
|
||||||
|
.next_back()
|
||||||
|
.map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||||
|
self.0
|
||||||
|
.nth_back(n)
|
||||||
|
.map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfold<B, F>(self, init: B, f: F) -> B
|
||||||
|
where
|
||||||
|
F: FnMut(B, Self::Item) -> B,
|
||||||
|
{
|
||||||
|
self.0
|
||||||
|
.map(|(name, job_kind)| (name.0, job_kind))
|
||||||
|
.rfold(init, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn register_job_kind<K: JobKind>(kind: K) {
|
||||||
|
DynJobKind::new(kind).register();
|
||||||
|
}
|
||||||
418
crates/fayalite/src/build/verilog.rs
Normal file
418
crates/fayalite/src/build/verilog.rs
Normal file
|
|
@ -0,0 +1,418 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{
|
||||||
|
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob,
|
||||||
|
GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem,
|
||||||
|
JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
|
||||||
|
external::{
|
||||||
|
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
|
||||||
|
},
|
||||||
|
firrtl::{Firrtl, FirrtlJobKind},
|
||||||
|
},
|
||||||
|
intern::{Intern, InternSlice, Interned},
|
||||||
|
util::job_server::AcquiredJob,
|
||||||
|
};
|
||||||
|
use clap::Args;
|
||||||
|
use eyre::{Context, bail};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fmt, mem,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// based on [LLVM Circt's recommended lowering options][lowering-options]
|
||||||
|
///
|
||||||
|
/// [lowering-options]: https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target
|
||||||
|
#[derive(clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum VerilogDialect {
|
||||||
|
Questa,
|
||||||
|
Spyglass,
|
||||||
|
Verilator,
|
||||||
|
Vivado,
|
||||||
|
Yosys,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VerilogDialect {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerilogDialect {
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
VerilogDialect::Questa => "questa",
|
||||||
|
VerilogDialect::Spyglass => "spyglass",
|
||||||
|
VerilogDialect::Verilator => "verilator",
|
||||||
|
VerilogDialect::Vivado => "vivado",
|
||||||
|
VerilogDialect::Yosys => "yosys",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn firtool_extra_args(self) -> &'static [&'static str] {
|
||||||
|
match self {
|
||||||
|
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
|
||||||
|
VerilogDialect::Spyglass => {
|
||||||
|
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
|
||||||
|
}
|
||||||
|
VerilogDialect::Verilator => &[
|
||||||
|
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
|
||||||
|
],
|
||||||
|
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
|
||||||
|
VerilogDialect::Yosys => {
|
||||||
|
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct UnadjustedVerilogArgs {
|
||||||
|
#[arg(long = "firtool-extra-arg", value_name = "ARG")]
|
||||||
|
pub firtool_extra_args: Vec<OsString>,
|
||||||
|
/// adapt the generated Verilog for a particular toolchain
|
||||||
|
#[arg(long)]
|
||||||
|
pub verilog_dialect: Option<VerilogDialect>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub verilog_debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToArgs for UnadjustedVerilogArgs {
|
||||||
|
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self {
|
||||||
|
ref firtool_extra_args,
|
||||||
|
verilog_dialect,
|
||||||
|
verilog_debug,
|
||||||
|
} = *self;
|
||||||
|
for arg in firtool_extra_args {
|
||||||
|
args.write_long_option_eq("firtool-extra-arg", arg);
|
||||||
|
}
|
||||||
|
if let Some(verilog_dialect) = verilog_dialect {
|
||||||
|
args.write_long_option_eq("verilog-dialect", verilog_dialect.as_str());
|
||||||
|
}
|
||||||
|
if verilog_debug {
|
||||||
|
args.write_arg("--verilog-debug");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||||
|
pub struct Firtool;
|
||||||
|
|
||||||
|
impl ExternalProgramTrait for Firtool {
|
||||||
|
fn default_program_name() -> Interned<str> {
|
||||||
|
"firtool".intern()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct UnadjustedVerilog {
|
||||||
|
firrtl_file: Interned<Path>,
|
||||||
|
firrtl_file_name: Interned<OsStr>,
|
||||||
|
unadjusted_verilog_file: Interned<Path>,
|
||||||
|
unadjusted_verilog_file_name: Interned<OsStr>,
|
||||||
|
firtool_extra_args: Interned<[Interned<OsStr>]>,
|
||||||
|
verilog_dialect: Option<VerilogDialect>,
|
||||||
|
verilog_debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnadjustedVerilog {
|
||||||
|
pub fn firrtl_file(&self) -> Interned<Path> {
|
||||||
|
self.firrtl_file
|
||||||
|
}
|
||||||
|
pub fn unadjusted_verilog_file(&self) -> Interned<Path> {
|
||||||
|
self.unadjusted_verilog_file
|
||||||
|
}
|
||||||
|
pub fn firtool_extra_args(&self) -> Interned<[Interned<OsStr>]> {
|
||||||
|
self.firtool_extra_args
|
||||||
|
}
|
||||||
|
pub fn verilog_dialect(&self) -> Option<VerilogDialect> {
|
||||||
|
self.verilog_dialect
|
||||||
|
}
|
||||||
|
pub fn verilog_debug(&self) -> bool {
|
||||||
|
self.verilog_debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalCommand for UnadjustedVerilog {
|
||||||
|
type AdditionalArgs = UnadjustedVerilogArgs;
|
||||||
|
type AdditionalJobData = UnadjustedVerilog;
|
||||||
|
type BaseJobPosition = GetJobPositionDependencies<GetJobPositionJob>;
|
||||||
|
type Dependencies = JobKindAndDependencies<FirrtlJobKind>;
|
||||||
|
type ExternalProgram = Firtool;
|
||||||
|
|
||||||
|
fn dependencies() -> Self::Dependencies {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_to_jobs(
|
||||||
|
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
|
||||||
|
params: &JobParams,
|
||||||
|
global_params: &GlobalParams,
|
||||||
|
) -> eyre::Result<(
|
||||||
|
Self::AdditionalJobData,
|
||||||
|
<Self::Dependencies as JobDependencies>::JobsAndKinds,
|
||||||
|
)> {
|
||||||
|
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
|
||||||
|
let UnadjustedVerilogArgs {
|
||||||
|
firtool_extra_args,
|
||||||
|
verilog_dialect,
|
||||||
|
verilog_debug,
|
||||||
|
} = args.additional_args;
|
||||||
|
let unadjusted_verilog_file = dependencies
|
||||||
|
.dependencies
|
||||||
|
.job
|
||||||
|
.job
|
||||||
|
.file_with_ext("unadjusted.v");
|
||||||
|
let firrtl_job = dependencies.get_job::<Firrtl, _>();
|
||||||
|
Ok(UnadjustedVerilog {
|
||||||
|
firrtl_file: firrtl_job.firrtl_file(),
|
||||||
|
firrtl_file_name: firrtl_job
|
||||||
|
.firrtl_file()
|
||||||
|
.interned_file_name()
|
||||||
|
.expect("known to have file name"),
|
||||||
|
unadjusted_verilog_file,
|
||||||
|
unadjusted_verilog_file_name: unadjusted_verilog_file
|
||||||
|
.interned_file_name()
|
||||||
|
.expect("known to have file name"),
|
||||||
|
firtool_extra_args: firtool_extra_args.into_iter().map(Interned::from).collect(),
|
||||||
|
verilog_dialect,
|
||||||
|
verilog_debug,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::Path {
|
||||||
|
path: job.additional_job_data().firrtl_file,
|
||||||
|
}]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
|
||||||
|
[job.additional_job_data().unadjusted_verilog_file].intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
||||||
|
let UnadjustedVerilog {
|
||||||
|
firrtl_file: _,
|
||||||
|
firrtl_file_name,
|
||||||
|
unadjusted_verilog_file: _,
|
||||||
|
unadjusted_verilog_file_name,
|
||||||
|
firtool_extra_args,
|
||||||
|
verilog_dialect,
|
||||||
|
verilog_debug,
|
||||||
|
} = *job.additional_job_data();
|
||||||
|
args.write_interned_arg(firrtl_file_name);
|
||||||
|
args.write_arg("-o");
|
||||||
|
args.write_interned_arg(unadjusted_verilog_file_name);
|
||||||
|
if verilog_debug {
|
||||||
|
args.write_args(["-g", "--preserve-values=all"]);
|
||||||
|
}
|
||||||
|
if let Some(dialect) = verilog_dialect {
|
||||||
|
args.write_args(dialect.firtool_extra_args().iter().copied());
|
||||||
|
}
|
||||||
|
args.write_interned_args(firtool_extra_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
|
||||||
|
Some(job.output_dir())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn job_kind_name() -> Interned<str> {
|
||||||
|
"unadjusted-verilog".intern()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand_hidden() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_even_if_cached_arg_name() -> Interned<str> {
|
||||||
|
"firtool-run-even-if-cached".intern()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct VerilogJobKind;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Args)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct VerilogJobArgs {}
|
||||||
|
|
||||||
|
impl ToArgs for VerilogJobArgs {
|
||||||
|
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
||||||
|
let Self {} = self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct VerilogJob {
|
||||||
|
output_dir: Interned<Path>,
|
||||||
|
unadjusted_verilog_file: Interned<Path>,
|
||||||
|
main_verilog_file: Interned<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerilogJob {
|
||||||
|
pub fn output_dir(&self) -> Interned<Path> {
|
||||||
|
self.output_dir
|
||||||
|
}
|
||||||
|
pub fn unadjusted_verilog_file(&self) -> Interned<Path> {
|
||||||
|
self.unadjusted_verilog_file
|
||||||
|
}
|
||||||
|
pub fn main_verilog_file(&self) -> Interned<Path> {
|
||||||
|
self.main_verilog_file
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned<Path>] {
|
||||||
|
match additional_files {
|
||||||
|
JobItem::DynamicPaths {
|
||||||
|
paths,
|
||||||
|
source_job_name,
|
||||||
|
} if *source_job_name == VerilogJobKind.name() => paths,
|
||||||
|
v => panic!("expected VerilogJob's additional files JobItem: {v:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn all_verilog_files(
|
||||||
|
main_verilog_file: Interned<Path>,
|
||||||
|
additional_files: &[Interned<Path>],
|
||||||
|
) -> eyre::Result<Interned<[Interned<Path>]>> {
|
||||||
|
let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1));
|
||||||
|
for verilog_file in [main_verilog_file].iter().chain(additional_files) {
|
||||||
|
if !["v", "sv"]
|
||||||
|
.iter()
|
||||||
|
.any(|extension| verilog_file.extension() == Some(extension.as_ref()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let verilog_file = std::path::absolute(verilog_file).wrap_err_with(|| {
|
||||||
|
format!("converting {verilog_file:?} to an absolute path failed")
|
||||||
|
})?;
|
||||||
|
if verilog_file
|
||||||
|
.as_os_str()
|
||||||
|
.as_encoded_bytes()
|
||||||
|
.iter()
|
||||||
|
.any(|&ch| (ch != b' ' && ch != b'\t' && ch.is_ascii_whitespace()) || ch == b'"')
|
||||||
|
{
|
||||||
|
bail!("verilog file path contains characters that aren't permitted");
|
||||||
|
}
|
||||||
|
retval.push(verilog_file.intern_deref());
|
||||||
|
}
|
||||||
|
Ok(retval.intern_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobKind for VerilogJobKind {
|
||||||
|
type Args = VerilogJobArgs;
|
||||||
|
type Job = VerilogJob;
|
||||||
|
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<UnadjustedVerilog>>;
|
||||||
|
|
||||||
|
fn dependencies(self) -> Self::Dependencies {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn args_to_jobs(
|
||||||
|
args: JobArgsAndDependencies<Self>,
|
||||||
|
params: &JobParams,
|
||||||
|
global_params: &GlobalParams,
|
||||||
|
) -> eyre::Result<JobAndDependencies<Self>> {
|
||||||
|
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
|
||||||
|
let VerilogJobArgs {} = args;
|
||||||
|
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||||
|
Ok(VerilogJob {
|
||||||
|
output_dir: base_job.output_dir(),
|
||||||
|
unadjusted_verilog_file: dependencies
|
||||||
|
.job
|
||||||
|
.job
|
||||||
|
.additional_job_data()
|
||||||
|
.unadjusted_verilog_file(),
|
||||||
|
main_verilog_file: base_job.file_with_ext("v"),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[JobItemName::Path {
|
||||||
|
path: job.unadjusted_verilog_file,
|
||||||
|
}]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
|
||||||
|
[
|
||||||
|
JobItemName::Path {
|
||||||
|
path: job.main_verilog_file,
|
||||||
|
},
|
||||||
|
JobItemName::DynamicPaths {
|
||||||
|
source_job_name: self.name(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(self) -> Interned<str> {
|
||||||
|
"verilog".intern()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self,
|
||||||
|
job: &Self::Job,
|
||||||
|
inputs: &[JobItem],
|
||||||
|
_params: &JobParams,
|
||||||
|
_global_params: &GlobalParams,
|
||||||
|
_acquired_job: &mut AcquiredJob,
|
||||||
|
) -> eyre::Result<Vec<JobItem>> {
|
||||||
|
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
|
||||||
|
let input = std::fs::read_to_string(job.unadjusted_verilog_file())?;
|
||||||
|
let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
|
||||||
|
let file_separator_suffix = "\" ----- 8< -----\n\n";
|
||||||
|
let mut input = &*input;
|
||||||
|
let main_verilog_file = job.main_verilog_file();
|
||||||
|
let mut file_name = Some(main_verilog_file);
|
||||||
|
let mut additional_outputs = Vec::new();
|
||||||
|
loop {
|
||||||
|
let (chunk, next_file_name) = if let Some((chunk, rest)) =
|
||||||
|
input.split_once(file_separator_prefix)
|
||||||
|
{
|
||||||
|
let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else {
|
||||||
|
bail!(
|
||||||
|
"parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
input = rest;
|
||||||
|
let next_file_name = job.output_dir.join(next_file_name).intern_deref();
|
||||||
|
additional_outputs.push(next_file_name);
|
||||||
|
(chunk, Some(next_file_name))
|
||||||
|
} else {
|
||||||
|
(mem::take(&mut input), None)
|
||||||
|
};
|
||||||
|
let Some(file_name) = mem::replace(&mut file_name, next_file_name) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
std::fs::write(&file_name, chunk)?;
|
||||||
|
}
|
||||||
|
Ok(vec![
|
||||||
|
JobItem::Path {
|
||||||
|
path: main_verilog_file,
|
||||||
|
},
|
||||||
|
JobItem::DynamicPaths {
|
||||||
|
paths: additional_outputs,
|
||||||
|
source_job_name: self.name(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|
||||||
|
[
|
||||||
|
DynJobKind::new(ExternalCommandJobKind::<UnadjustedVerilog>::new()),
|
||||||
|
DynJobKind::new(VerilogJobKind),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -2,18 +2,27 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expr::{ops::BundleLiteral, Expr, ToExpr},
|
expr::{
|
||||||
intern::{Intern, Interned},
|
CastToBits, Expr, HdlPartialEqImpl, ReduceBits, ToExpr, ToSimValueInner, ValueType,
|
||||||
|
Valueless,
|
||||||
|
ops::{ArrayLiteral, BundleLiteral, StructuralEq},
|
||||||
|
value_category::{ValueCategoryCommon, ValueCategoryExpr, ValueCategoryValue},
|
||||||
|
},
|
||||||
|
int::{Bool, DynSize},
|
||||||
|
intern::{Intern, InternSlice, Interned},
|
||||||
|
sim::value::{SimValue, SimValueEq, ToSimValue, ToSimValueWithType},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{
|
ty::{
|
||||||
impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, StaticType, Type,
|
CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, OpaqueSimValueSize,
|
||||||
TypeProperties, TypeWithDeref,
|
OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, SimValueDebug,
|
||||||
|
StaticType, Type, TypeProperties, TypeWithDeref, impl_match_variant_as_self,
|
||||||
},
|
},
|
||||||
|
util::HashMap,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fmt, marker::PhantomData};
|
use std::{borrow::Cow, fmt, marker::PhantomData};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct BundleField {
|
pub struct BundleField {
|
||||||
pub name: Interned<str>,
|
pub name: Interned<str>,
|
||||||
pub flipped: bool,
|
pub flipped: bool,
|
||||||
|
|
@ -62,7 +71,7 @@ impl fmt::Display for FmtDebugInStruct {
|
||||||
struct BundleImpl {
|
struct BundleImpl {
|
||||||
fields: Interned<[BundleField]>,
|
fields: Interned<[BundleField]>,
|
||||||
name_indexes: HashMap<Interned<str>, usize>,
|
name_indexes: HashMap<Interned<str>, usize>,
|
||||||
field_offsets: Interned<[usize]>,
|
field_offsets: Interned<[OpaqueSimValueSize]>,
|
||||||
type_properties: TypeProperties,
|
type_properties: TypeProperties,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,12 +91,9 @@ impl std::fmt::Debug for BundleImpl {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str("Bundle ")?;
|
f.write_str("Bundle ")?;
|
||||||
f.debug_set()
|
f.debug_set()
|
||||||
.entries(
|
.entries(self.fields.iter().enumerate().map(|(index, field)| {
|
||||||
self.fields
|
field.fmt_debug_in_struct(self.field_offsets[index].bit_width)
|
||||||
.iter()
|
}))
|
||||||
.enumerate()
|
|
||||||
.map(|(index, field)| field.fmt_debug_in_struct(self.field_offsets[index])),
|
|
||||||
)
|
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,6 +118,7 @@ impl BundleTypePropertiesBuilder {
|
||||||
is_storable: true,
|
is_storable: true,
|
||||||
is_castable_from_bits: true,
|
is_castable_from_bits: true,
|
||||||
bit_width: 0,
|
bit_width: 0,
|
||||||
|
sim_only_values_len: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub const fn clone(&self) -> Self {
|
pub const fn clone(&self) -> Self {
|
||||||
|
|
@ -119,8 +126,12 @@ impl BundleTypePropertiesBuilder {
|
||||||
}
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn field(self, flipped: bool, field_props: TypeProperties) -> Self {
|
pub const fn field(self, flipped: bool, field_props: TypeProperties) -> Self {
|
||||||
let Some(bit_width) = self.0.bit_width.checked_add(field_props.bit_width) else {
|
let Some(OpaqueSimValueSize {
|
||||||
panic!("bundle is too big: bit-width overflowed");
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
|
}) = self.0.size().checked_add(field_props.size())
|
||||||
|
else {
|
||||||
|
panic!("bundle is too big: size overflowed");
|
||||||
};
|
};
|
||||||
if flipped {
|
if flipped {
|
||||||
Self(TypeProperties {
|
Self(TypeProperties {
|
||||||
|
|
@ -128,6 +139,7 @@ impl BundleTypePropertiesBuilder {
|
||||||
is_storable: false,
|
is_storable: false,
|
||||||
is_castable_from_bits: false,
|
is_castable_from_bits: false,
|
||||||
bit_width,
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Self(TypeProperties {
|
Self(TypeProperties {
|
||||||
|
|
@ -136,6 +148,7 @@ impl BundleTypePropertiesBuilder {
|
||||||
is_castable_from_bits: self.0.is_castable_from_bits
|
is_castable_from_bits: self.0.is_castable_from_bits
|
||||||
& field_props.is_castable_from_bits,
|
& field_props.is_castable_from_bits,
|
||||||
bit_width,
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,17 +157,23 @@ impl BundleTypePropertiesBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for BundleTypePropertiesBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Bundle {
|
impl Bundle {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(fields: Interned<[BundleField]>) -> Self {
|
pub fn new(fields: Interned<[BundleField]>) -> Self {
|
||||||
let mut name_indexes = HashMap::with_capacity(fields.len());
|
let mut name_indexes = HashMap::with_capacity_and_hasher(fields.len(), Default::default());
|
||||||
let mut field_offsets = Vec::with_capacity(fields.len());
|
let mut field_offsets = Vec::with_capacity(fields.len());
|
||||||
let mut type_props_builder = BundleTypePropertiesBuilder::new();
|
let mut type_props_builder = BundleTypePropertiesBuilder::new();
|
||||||
for (index, &BundleField { name, flipped, ty }) in fields.iter().enumerate() {
|
for (index, &BundleField { name, flipped, ty }) in fields.iter().enumerate() {
|
||||||
if let Some(old_index) = name_indexes.insert(name, index) {
|
if let Some(old_index) = name_indexes.insert(name, index) {
|
||||||
panic!("duplicate field name {name:?}: at both index {old_index} and {index}");
|
panic!("duplicate field name {name:?}: at both index {old_index} and {index}");
|
||||||
}
|
}
|
||||||
field_offsets.push(type_props_builder.0.bit_width);
|
field_offsets.push(type_props_builder.0.size());
|
||||||
type_props_builder = type_props_builder.field(flipped, ty.type_properties());
|
type_props_builder = type_props_builder.field(flipped, ty.type_properties());
|
||||||
}
|
}
|
||||||
Self(Intern::intern_sized(BundleImpl {
|
Self(Intern::intern_sized(BundleImpl {
|
||||||
|
|
@ -170,7 +189,7 @@ impl Bundle {
|
||||||
pub fn field_by_name(&self, name: Interned<str>) -> Option<BundleField> {
|
pub fn field_by_name(&self, name: Interned<str>) -> Option<BundleField> {
|
||||||
Some(self.0.fields[*self.0.name_indexes.get(&name)?])
|
Some(self.0.fields[*self.0.name_indexes.get(&name)?])
|
||||||
}
|
}
|
||||||
pub fn field_offsets(self) -> Interned<[usize]> {
|
pub fn field_offsets(self) -> Interned<[OpaqueSimValueSize]> {
|
||||||
self.0.field_offsets
|
self.0.field_offsets
|
||||||
}
|
}
|
||||||
pub fn type_properties(self) -> TypeProperties {
|
pub fn type_properties(self) -> TypeProperties {
|
||||||
|
|
@ -204,6 +223,7 @@ impl Bundle {
|
||||||
impl Type for Bundle {
|
impl Type for Bundle {
|
||||||
type BaseType = Bundle;
|
type BaseType = Bundle;
|
||||||
type MaskType = Bundle;
|
type MaskType = Bundle;
|
||||||
|
type SimValue = OpaqueSimValue;
|
||||||
impl_match_variant_as_self!();
|
impl_match_variant_as_self!();
|
||||||
fn mask_type(&self) -> Self::MaskType {
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
Self::new(Interned::from_iter(self.0.fields.into_iter().map(
|
Self::new(Interned::from_iter(self.0.fields.into_iter().map(
|
||||||
|
|
@ -227,28 +247,145 @@ impl Type for Bundle {
|
||||||
fn source_location() -> SourceLocation {
|
fn source_location() -> SourceLocation {
|
||||||
SourceLocation::builtin()
|
SourceLocation::builtin()
|
||||||
}
|
}
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert_eq!(self.type_properties().size(), opaque.size());
|
||||||
|
opaque.to_owned()
|
||||||
|
}
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert_eq!(self.type_properties().size(), opaque.size());
|
||||||
|
assert_eq!(value.size(), opaque.size());
|
||||||
|
value.clone_from_slice(opaque);
|
||||||
|
}
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
assert_eq!(self.type_properties().size(), writer.size());
|
||||||
|
assert_eq!(value.size(), writer.size());
|
||||||
|
writer.fill_cloned_from_slice(value.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDebug for Bundle {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BundleType: Type<BaseType = Bundle> {
|
pub trait BundleType: Type<BaseType = Bundle> {
|
||||||
type Builder: Default;
|
type Builder: Default;
|
||||||
type FilledBuilder: ToExpr<Type = Self>;
|
|
||||||
fn fields(&self) -> Interned<[BundleField]>;
|
fn fields(&self) -> Interned<[BundleField]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BundleSimValueFromOpaque<'a> {
|
||||||
|
fields: std::slice::Iter<'static, BundleField>,
|
||||||
|
opaque: OpaqueSimValueSlice<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BundleSimValueFromOpaque<'a> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<T: BundleType>(bundle_ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self {
|
||||||
|
let fields = bundle_ty.fields();
|
||||||
|
assert_eq!(
|
||||||
|
opaque.size(),
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.map(|BundleField { ty, .. }| ty.size())
|
||||||
|
.sum::<OpaqueSimValueSize>()
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
fields: Interned::into_inner(fields).iter(),
|
||||||
|
opaque,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn field_ty_and_opaque<T: Type>(&mut self) -> (T, OpaqueSimValueSlice<'a>) {
|
||||||
|
let Some(&BundleField {
|
||||||
|
name: _,
|
||||||
|
flipped: _,
|
||||||
|
ty,
|
||||||
|
}) = self.fields.next()
|
||||||
|
else {
|
||||||
|
panic!("tried to read too many fields from BundleSimValueFromBits");
|
||||||
|
};
|
||||||
|
let (field_opaque, rest) = self.opaque.split_at(ty.size());
|
||||||
|
self.opaque = rest;
|
||||||
|
(T::from_canonical(ty), field_opaque)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn field_from_opaque<T: Type>(&mut self) -> SimValue<T> {
|
||||||
|
let (field_ty, field_opaque) = self.field_ty_and_opaque::<T>();
|
||||||
|
SimValue::from_opaque(field_ty, field_opaque.to_owned())
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn field_clone_from_opaque<T: Type>(&mut self, field_value: &mut SimValue<T>) {
|
||||||
|
let (field_ty, field_opaque) = self.field_ty_and_opaque::<T>();
|
||||||
|
assert_eq!(field_ty, field_value.ty());
|
||||||
|
SimValue::opaque_mut(field_value).clone_from_slice(field_opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BundleSimValueToOpaque<'a> {
|
||||||
|
fields: std::slice::Iter<'static, BundleField>,
|
||||||
|
writer: OpaqueSimValueWriter<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BundleSimValueToOpaque<'a> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<T: BundleType>(bundle_ty: T, writer: OpaqueSimValueWriter<'a>) -> Self {
|
||||||
|
let fields = bundle_ty.fields();
|
||||||
|
assert_eq!(
|
||||||
|
writer.size(),
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.map(|BundleField { ty, .. }| ty.size())
|
||||||
|
.sum::<OpaqueSimValueSize>()
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
fields: Interned::into_inner(fields).iter(),
|
||||||
|
writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn field<T: Type>(&mut self, field_value: &SimValue<T>) {
|
||||||
|
let Some(&BundleField {
|
||||||
|
name: _,
|
||||||
|
flipped: _,
|
||||||
|
ty,
|
||||||
|
}) = self.fields.next()
|
||||||
|
else {
|
||||||
|
panic!("tried to write too many fields with BundleSimValueToOpaque");
|
||||||
|
};
|
||||||
|
assert_eq!(T::from_canonical(ty), field_value.ty());
|
||||||
|
self.writer.fill_prefix_with(ty.size(), |writer| {
|
||||||
|
writer.fill_cloned_from_slice(SimValue::opaque(field_value).as_slice())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn finish(mut self) -> OpaqueSimValueWritten<'a> {
|
||||||
|
assert_eq!(
|
||||||
|
self.fields.next(),
|
||||||
|
None,
|
||||||
|
"wrote too few fields with BundleSimValueToOpaque"
|
||||||
|
);
|
||||||
|
self.writer
|
||||||
|
.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct NoBuilder;
|
pub struct NoBuilder;
|
||||||
|
|
||||||
pub struct Unfilled<T: Type>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T: Type> Default for Unfilled<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BundleType for Bundle {
|
impl BundleType for Bundle {
|
||||||
type Builder = NoBuilder;
|
type Builder = NoBuilder;
|
||||||
type FilledBuilder = Expr<Bundle>;
|
|
||||||
fn fields(&self) -> Interned<[BundleField]> {
|
fn fields(&self) -> Interned<[BundleField]> {
|
||||||
self.0.fields
|
self.0.fields
|
||||||
}
|
}
|
||||||
|
|
@ -284,15 +421,14 @@ macro_rules! impl_tuple_builder_fields {
|
||||||
) => {
|
) => {
|
||||||
impl<
|
impl<
|
||||||
$($head_type_var,)*
|
$($head_type_var,)*
|
||||||
$cur_type_var: Type,
|
|
||||||
$($tail_type_var,)*
|
$($tail_type_var,)*
|
||||||
> TupleBuilder<(
|
> TupleBuilder<(
|
||||||
$($head_type_var,)*
|
$($head_type_var,)*
|
||||||
Unfilled<$cur_type_var>,
|
(),
|
||||||
$($tail_type_var,)*
|
$($tail_type_var,)*
|
||||||
)>
|
)>
|
||||||
{
|
{
|
||||||
pub fn $cur_field(self, $cur_var: impl ToExpr<Type = $cur_type_var>) -> TupleBuilder<(
|
pub fn $cur_field<$cur_type_var: Type>(self, $cur_var: impl ToExpr<Type = $cur_type_var>) -> TupleBuilder<(
|
||||||
$($head_type_var,)*
|
$($head_type_var,)*
|
||||||
Expr<$cur_type_var>,
|
Expr<$cur_type_var>,
|
||||||
$($tail_type_var,)*
|
$($tail_type_var,)*
|
||||||
|
|
@ -316,8 +452,26 @@ macro_rules! impl_tuple_builder_fields {
|
||||||
($global:tt []) => {};
|
($global:tt []) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! get_unit_ty {
|
||||||
|
($($tt:tt)*) => {
|
||||||
|
()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_tuples {
|
macro_rules! impl_tuples {
|
||||||
([$({#[num = $num:literal, field = $field:ident] $var:ident: $T:ident})*] []) => {
|
(
|
||||||
|
[$({
|
||||||
|
#[
|
||||||
|
num = $num:tt,
|
||||||
|
field = $field:ident,
|
||||||
|
ty = $ty_var:ident: $Ty:ident,
|
||||||
|
lhs = $lhs_var:ident: $Lhs:ident,
|
||||||
|
rhs = $rhs_var:ident: $Rhs:ident
|
||||||
|
]
|
||||||
|
$var:ident: $T:ident
|
||||||
|
})*]
|
||||||
|
[]
|
||||||
|
) => {
|
||||||
impl_tuple_builder_fields! {
|
impl_tuple_builder_fields! {
|
||||||
{}
|
{}
|
||||||
[$({
|
[$({
|
||||||
|
|
@ -326,9 +480,18 @@ macro_rules! impl_tuples {
|
||||||
#[var($var)]
|
#[var($var)]
|
||||||
})*]
|
})*]
|
||||||
}
|
}
|
||||||
|
impl<$($T: Type,)*> SimValueDebug for ($($T,)*) {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<$($T: Type,)*> Type for ($($T,)*) {
|
impl<$($T: Type,)*> Type for ($($T,)*) {
|
||||||
type BaseType = Bundle;
|
type BaseType = Bundle;
|
||||||
type MaskType = ($($T::MaskType,)*);
|
type MaskType = ($($T::MaskType,)*);
|
||||||
|
type SimValue = ($(SimValue<$T>,)*);
|
||||||
type MatchVariant = ($(Expr<$T>,)*);
|
type MatchVariant = ($(Expr<$T>,)*);
|
||||||
type MatchActiveScope = ();
|
type MatchActiveScope = ();
|
||||||
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
|
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
|
||||||
|
|
@ -342,6 +505,7 @@ macro_rules! impl_tuples {
|
||||||
std::iter::once(MatchVariantWithoutScope(($(Expr::field(this, stringify!($num)),)*)))
|
std::iter::once(MatchVariantWithoutScope(($(Expr::field(this, stringify!($num)),)*)))
|
||||||
}
|
}
|
||||||
fn mask_type(&self) -> Self::MaskType {
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
|
#![allow(clippy::unused_unit)]
|
||||||
let ($($var,)*) = self;
|
let ($($var,)*) = self;
|
||||||
($($var.mask_type(),)*)
|
($($var.mask_type(),)*)
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +514,7 @@ macro_rules! impl_tuples {
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
||||||
|
#![allow(clippy::unused_unit)]
|
||||||
let CanonicalType::Bundle(bundle) = canonical_type else {
|
let CanonicalType::Bundle(bundle) = canonical_type else {
|
||||||
panic!("expected bundle");
|
panic!("expected bundle");
|
||||||
};
|
};
|
||||||
|
|
@ -358,26 +523,52 @@ macro_rules! impl_tuples {
|
||||||
};
|
};
|
||||||
$(let BundleField { name, flipped, ty } = $var;
|
$(let BundleField { name, flipped, ty } = $var;
|
||||||
assert_eq!(&*name, stringify!($num));
|
assert_eq!(&*name, stringify!($num));
|
||||||
assert_eq!(flipped, false);
|
assert!(!flipped);
|
||||||
let $var = $T::from_canonical(ty);)*
|
let $var = $T::from_canonical(ty);)*
|
||||||
($($var,)*)
|
($($var,)*)
|
||||||
}
|
}
|
||||||
fn source_location() -> SourceLocation {
|
fn source_location() -> SourceLocation {
|
||||||
SourceLocation::builtin()
|
SourceLocation::builtin()
|
||||||
}
|
}
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
#![allow(unused_mut, unused_variables)]
|
||||||
|
let mut v = BundleSimValueFromOpaque::new(*self, opaque);
|
||||||
|
$(let $var = v.field_from_opaque();)*
|
||||||
|
($($var,)*)
|
||||||
|
}
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
#![allow(unused_mut, unused_variables)]
|
||||||
|
let mut v = BundleSimValueFromOpaque::new(*self, opaque);
|
||||||
|
let ($($var,)*) = value;
|
||||||
|
$(v.field_clone_from_opaque($var);)*
|
||||||
|
}
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
#![allow(unused_mut, unused_variables)]
|
||||||
|
let mut v = BundleSimValueToOpaque::new(*self, writer);
|
||||||
|
let ($($var,)*) = value;
|
||||||
|
$(v.field($var);)*
|
||||||
|
v.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<$($T: Type,)*> BundleType for ($($T,)*) {
|
impl<$($T: Type,)*> BundleType for ($($T,)*) {
|
||||||
type Builder = TupleBuilder<($(Unfilled<$T>,)*)>;
|
type Builder = TupleBuilder<($(get_unit_ty!($T),)*)>;
|
||||||
type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>;
|
|
||||||
fn fields(&self) -> Interned<[BundleField]> {
|
fn fields(&self) -> Interned<[BundleField]> {
|
||||||
let ($($var,)*) = self;
|
let ($($var,)*) = self;
|
||||||
[$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*][..].intern()
|
[$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) {
|
impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) {
|
||||||
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
|
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
|
||||||
let _ = this;
|
let _ = this;
|
||||||
Interned::<_>::into_inner(($(Expr::field(*this, stringify!($num)),)*).intern_sized())
|
Interned::into_inner(($(Expr::field(*this, stringify!($num)),)*).intern_sized())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<$($T: StaticType,)*> StaticType for ($($T,)*) {
|
impl<$($T: StaticType,)*> StaticType for ($($T,)*) {
|
||||||
|
|
@ -394,27 +585,199 @@ macro_rules! impl_tuples {
|
||||||
builder.finish()
|
builder.finish()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
impl<$($T: ToExpr,)*> ToExpr for ($($T,)*) {
|
impl<'a, $($T: ToSimValue,)*> ToSimValueInner<'a> for ($($T,)*)
|
||||||
|
where
|
||||||
|
Self: ValueType<Type = ($($T::Type,)*)>,
|
||||||
|
{
|
||||||
|
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
|
||||||
|
let ($($var,)*) = this;
|
||||||
|
Cow::Owned(($($var.to_sim_value(),)*))
|
||||||
|
}
|
||||||
|
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
|
||||||
|
let ($($var,)*) = this;
|
||||||
|
Cow::Owned(($($var.into_sim_value(),)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: ValueType,)*> ValueType for ($($T,)*)
|
||||||
|
where
|
||||||
|
ValueCategoryValue: ValueCategoryCommon<($($T::ValueCategory,)*)>,
|
||||||
|
{
|
||||||
type Type = ($($T::Type,)*);
|
type Type = ($($T::Type,)*);
|
||||||
|
type ValueCategory = <ValueCategoryValue as ValueCategoryCommon<($($T::ValueCategory,)*)>>::Common;
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
($($var.ty(),)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: ToExpr,)*> ToExpr for ($($T,)*)
|
||||||
|
where
|
||||||
|
Self: ValueType<Type = ($($T::Type,)*)>,
|
||||||
|
{
|
||||||
fn to_expr(&self) -> Expr<Self::Type> {
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
let ($($var,)*) = self;
|
let ($($var,)*) = self;
|
||||||
$(let $var = $var.to_expr();)*
|
$(let $var = $var.to_expr();)*
|
||||||
let ty = ($(Expr::ty($var),)*);
|
let ty = ($($var.ty(),)*);
|
||||||
let field_values = [$(Expr::canonical($var)),*];
|
let field_values = [$(Expr::canonical($var)),*];
|
||||||
BundleLiteral::new(ty, field_values[..].intern()).to_expr()
|
BundleLiteral::new(ty, field_values.intern_slice()).to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: Type,)*> ValueType for TupleBuilder<($(Expr<$T>,)*)> {
|
||||||
|
type Type = ($($T,)*);
|
||||||
|
type ValueCategory = ValueCategoryExpr;
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
let ($($var,)*) = self.0;
|
||||||
|
($($var.ty(),)*)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> {
|
impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> {
|
||||||
type Type = ($($T,)*);
|
|
||||||
|
|
||||||
fn to_expr(&self) -> Expr<Self::Type> {
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
let ($($var,)*) = self.0;
|
let ($($var,)*) = self.0;
|
||||||
let ty = ($(Expr::ty($var),)*);
|
let ty = ($($var.ty(),)*);
|
||||||
let field_values = [$(Expr::canonical($var)),*];
|
let field_values = [$(Expr::canonical($var)),*];
|
||||||
BundleLiteral::new(ty, field_values[..].intern()).to_expr()
|
BundleLiteral::new(ty, field_values.intern_slice()).to_expr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<$($T: ToSimValueWithType<CanonicalType>,)*> ToSimValueWithType<CanonicalType> for ($($T,)*) {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue<CanonicalType> {
|
||||||
|
SimValue::into_canonical(ToSimValueWithType::<Bundle>::to_sim_value_with_type(self, Bundle::from_canonical(ty)))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue<CanonicalType>
|
||||||
|
{
|
||||||
|
SimValue::into_canonical(ToSimValueWithType::<Bundle>::into_sim_value_with_type(self, Bundle::from_canonical(ty)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: ToSimValueWithType<CanonicalType>,)*> ToSimValueWithType<Bundle> for ($($T,)*) {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue<Bundle> {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
let [$($ty_var,)*] = *ty.fields() else {
|
||||||
|
panic!("bundle has wrong number of fields");
|
||||||
|
};
|
||||||
|
$(let $var = $var.to_sim_value_with_type($ty_var.ty);)*
|
||||||
|
ToSimValueWithType::into_sim_value_with_type(($($var,)*), ty)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn into_sim_value_with_type(self, ty: Bundle) -> SimValue<Bundle> {
|
||||||
|
#![allow(unused_mut)]
|
||||||
|
#![allow(clippy::unused_unit)]
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
let [$($ty_var,)*] = *ty.fields() else {
|
||||||
|
panic!("bundle has wrong number of fields");
|
||||||
|
};
|
||||||
|
let mut opaque = OpaqueSimValue::empty();
|
||||||
|
$(let $var = $var.into_sim_value_with_type($ty_var.ty);
|
||||||
|
assert_eq!($var.ty(), $ty_var.ty);
|
||||||
|
opaque.extend_from_slice(SimValue::opaque(&$var).as_slice());
|
||||||
|
)*
|
||||||
|
SimValue::from_opaque(ty, opaque)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: ToSimValueWithType<$Ty>, $Ty: Type,)*> ToSimValueWithType<($($Ty,)*)> for ($($T,)*) {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
let ($($ty_var,)*) = ty;
|
||||||
|
$(let $var = $var.to_sim_value_with_type($ty_var);)*
|
||||||
|
SimValue::from_value(ty, ($($var,)*))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn into_sim_value_with_type(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
let ($($ty_var,)*) = ty;
|
||||||
|
$(let $var = $var.into_sim_value_with_type($ty_var);)*
|
||||||
|
SimValue::from_value(ty, ($($var,)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*)
|
||||||
|
where
|
||||||
|
Self: ValueType<Type = ($($T::Type,)*)>,
|
||||||
|
{
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value(&self) -> SimValue<Self::Type> {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
$(let $var = $var.to_sim_value();)*
|
||||||
|
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn into_sim_value(self) -> SimValue<Self::Type> {
|
||||||
|
let ($($var,)*) = self;
|
||||||
|
$(let $var = $var.to_sim_value();)*
|
||||||
|
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($Lhs: Type + HdlPartialEqImpl<$Rhs>, $Rhs: Type,)*> HdlPartialEqImpl<($($Rhs,)*)> for ($($Lhs,)*) {
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = true $(&& <$Lhs as HdlPartialEqImpl<$Rhs>>::TRY_STRUCTURAL_EQ)*;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_eq(
|
||||||
|
lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: ($($Rhs,)*),
|
||||||
|
rhs_value: Cow<'_, <($($Rhs,)*) as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
#![allow(unused_variables)]
|
||||||
|
let ($($lhs_var,)*) = &*lhs_value;
|
||||||
|
let ($($rhs_var,)*) = &*rhs_value;
|
||||||
|
let retval = true;
|
||||||
|
$(let retval = retval && $Lhs::cmp_value_eq(lhs.$num, Cow::Borrowed($lhs_var), rhs.$num, Cow::Borrowed($rhs_var));)*
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
|
||||||
|
if Self::TRY_STRUCTURAL_EQ {
|
||||||
|
if let Ok(retval) = StructuralEq::try_new(Expr::canonical(lhs), Expr::canonical(rhs)) {
|
||||||
|
return retval.to_expr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ($($lhs_var,)*) = *lhs;
|
||||||
|
let ($($rhs_var,)*) = *rhs;
|
||||||
|
ArrayLiteral::<Bool, DynSize>::new(
|
||||||
|
Bool,
|
||||||
|
FromIterator::from_iter([$(Expr::canonical($Lhs::cmp_expr_eq($lhs_var, $rhs_var)),)*]),
|
||||||
|
)
|
||||||
|
.cast_to_bits()
|
||||||
|
.all_one_bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
|
||||||
|
if Self::TRY_STRUCTURAL_EQ {
|
||||||
|
if let Ok(retval) = StructuralEq::try_new(Expr::canonical(lhs), Expr::canonical(rhs)) {
|
||||||
|
return !retval.to_expr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ($($lhs_var,)*) = *lhs;
|
||||||
|
let ($($rhs_var,)*) = *rhs;
|
||||||
|
ArrayLiteral::<Bool, DynSize>::new(
|
||||||
|
Bool,
|
||||||
|
FromIterator::from_iter([$(Expr::canonical($Lhs::cmp_expr_ne($lhs_var, $rhs_var)),)*]),
|
||||||
|
)
|
||||||
|
.cast_to_bits()
|
||||||
|
.any_one_bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_valueless_eq(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
|
||||||
|
let ($($lhs_var,)*) = lhs.ty();
|
||||||
|
let ($($rhs_var,)*) = rhs.ty();
|
||||||
|
// let them check that the types can be compared
|
||||||
|
$($Lhs::cmp_valueless_eq(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
|
||||||
|
Valueless::new(Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_valueless_ne(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
|
||||||
|
let ($($lhs_var,)*) = lhs.ty();
|
||||||
|
let ($($rhs_var,)*) = rhs.ty();
|
||||||
|
// let them check that the types can be compared
|
||||||
|
$($Lhs::cmp_valueless_ne(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
|
||||||
|
Valueless::new(Bool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<$($T: SimValueEq + HdlPartialEqImpl<$T>,)*> SimValueEq for ($($T,)*) {}
|
||||||
};
|
};
|
||||||
([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => {
|
([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => {
|
||||||
impl_tuples!([$($lhs)*] []);
|
impl_tuples!([$($lhs)*] []);
|
||||||
|
|
@ -424,17 +787,171 @@ macro_rules! impl_tuples {
|
||||||
|
|
||||||
impl_tuples! {
|
impl_tuples! {
|
||||||
[] [
|
[] [
|
||||||
{#[num = 0, field = field_0] v0: T0}
|
{#[num = 0, field = field_0, ty = ty0: Ty0, lhs = lhs0: Lhs0, rhs = rhs0: Rhs0] v0: T0}
|
||||||
{#[num = 1, field = field_1] v1: T1}
|
{#[num = 1, field = field_1, ty = ty1: Ty1, lhs = lhs1: Lhs1, rhs = rhs1: Rhs1] v1: T1}
|
||||||
{#[num = 2, field = field_2] v2: T2}
|
{#[num = 2, field = field_2, ty = ty2: Ty2, lhs = lhs2: Lhs2, rhs = rhs2: Rhs2] v2: T2}
|
||||||
{#[num = 3, field = field_3] v3: T3}
|
{#[num = 3, field = field_3, ty = ty3: Ty3, lhs = lhs3: Lhs3, rhs = rhs3: Rhs3] v3: T3}
|
||||||
{#[num = 4, field = field_4] v4: T4}
|
{#[num = 4, field = field_4, ty = ty4: Ty4, lhs = lhs4: Lhs4, rhs = rhs4: Rhs4] v4: T4}
|
||||||
{#[num = 5, field = field_5] v5: T5}
|
{#[num = 5, field = field_5, ty = ty5: Ty5, lhs = lhs5: Lhs5, rhs = rhs5: Rhs5] v5: T5}
|
||||||
{#[num = 6, field = field_6] v6: T6}
|
{#[num = 6, field = field_6, ty = ty6: Ty6, lhs = lhs6: Lhs6, rhs = rhs6: Rhs6] v6: T6}
|
||||||
{#[num = 7, field = field_7] v7: T7}
|
{#[num = 7, field = field_7, ty = ty7: Ty7, lhs = lhs7: Lhs7, rhs = rhs7: Rhs7] v7: T7}
|
||||||
{#[num = 8, field = field_8] v8: T8}
|
{#[num = 8, field = field_8, ty = ty8: Ty8, lhs = lhs8: Lhs8, rhs = rhs8: Rhs8] v8: T8}
|
||||||
{#[num = 9, field = field_9] v9: T9}
|
{#[num = 9, field = field_9, ty = ty9: Ty9, lhs = lhs9: Lhs9, rhs = rhs9: Rhs9] v9: T9}
|
||||||
{#[num = 10, field = field_10] v10: T10}
|
{#[num = 10, field = field_10, ty = ty10: Ty10, lhs = lhs10: Lhs10, rhs = rhs10: Rhs10] v10: T10}
|
||||||
{#[num = 11, field = field_11] v11: T11}
|
{#[num = 11, field = field_11, ty = ty11: Ty11, lhs = lhs11: Lhs11, rhs = rhs11: Rhs11] v11: T11}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> SimValueDebug for PhantomData<T> {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> Type for PhantomData<T> {
|
||||||
|
type BaseType = Bundle;
|
||||||
|
type MaskType = ();
|
||||||
|
type SimValue = PhantomData<T>;
|
||||||
|
type MatchVariant = PhantomData<T>;
|
||||||
|
type MatchActiveScope = ();
|
||||||
|
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
|
||||||
|
type MatchVariantsIter = std::iter::Once<Self::MatchVariantAndInactiveScope>;
|
||||||
|
fn match_variants(
|
||||||
|
this: Expr<Self>,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) -> Self::MatchVariantsIter {
|
||||||
|
let _ = this;
|
||||||
|
let _ = source_location;
|
||||||
|
std::iter::once(MatchVariantWithoutScope(PhantomData))
|
||||||
|
}
|
||||||
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
fn canonical(&self) -> CanonicalType {
|
||||||
|
Bundle::new(self.fields()).canonical()
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
||||||
|
let CanonicalType::Bundle(bundle) = canonical_type else {
|
||||||
|
panic!("expected bundle");
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
bundle.fields().is_empty(),
|
||||||
|
"bundle has wrong number of fields"
|
||||||
|
);
|
||||||
|
PhantomData
|
||||||
|
}
|
||||||
|
fn source_location() -> SourceLocation {
|
||||||
|
SourceLocation::builtin()
|
||||||
|
}
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert!(opaque.is_empty());
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
_value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert!(opaque.is_empty());
|
||||||
|
}
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
_value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PhantomDataBuilder<T: ?Sized + Send + Sync + 'static>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> Default for PhantomDataBuilder<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomDataBuilder<T> {
|
||||||
|
type Type = PhantomData<T>;
|
||||||
|
type ValueCategory = ValueCategoryValue;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomDataBuilder<T> {
|
||||||
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
|
PhantomData.to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> BundleType for PhantomData<T> {
|
||||||
|
type Builder = PhantomDataBuilder<T>;
|
||||||
|
fn fields(&self) -> Interned<[BundleField]> {
|
||||||
|
Interned::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> TypeWithDeref for PhantomData<T> {
|
||||||
|
fn expr_deref(_this: &Expr<Self>) -> &Self::MatchVariant {
|
||||||
|
&PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> StaticType for PhantomData<T> {
|
||||||
|
const TYPE: Self = PhantomData;
|
||||||
|
const MASK_TYPE: Self::MaskType = ();
|
||||||
|
const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
|
||||||
|
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomData<T> {
|
||||||
|
type Type = PhantomData<T>;
|
||||||
|
type ValueCategory = ValueCategoryValue;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomData<T> {
|
||||||
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
|
BundleLiteral::new(PhantomData, Interned::default()).to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ToSimValue for PhantomData<T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value(&self) -> SimValue<Self> {
|
||||||
|
SimValue::from_value(*self, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + Send + Sync + 'static> ToSimValueWithType<Self> for PhantomData<T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, ty: Self) -> SimValue<Self> {
|
||||||
|
SimValue::from_value(ty, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> ToSimValueWithType<Bundle> for PhantomData<T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue<Bundle> {
|
||||||
|
assert!(ty.fields().is_empty());
|
||||||
|
SimValue::from_opaque(ty, OpaqueSimValue::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> ToSimValueWithType<CanonicalType> for PhantomData<T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn to_sim_value_with_type(&self, canonical_ty: CanonicalType) -> SimValue<CanonicalType> {
|
||||||
|
let ty = Bundle::from_canonical(canonical_ty);
|
||||||
|
assert!(ty.fields().is_empty());
|
||||||
|
SimValue::from_opaque(canonical_ty, OpaqueSimValue::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
use crate::{
|
|
||||||
bundle::{Bundle, BundleType},
|
|
||||||
firrtl,
|
|
||||||
intern::Interned,
|
|
||||||
module::Module,
|
|
||||||
};
|
|
||||||
use clap::{
|
|
||||||
builder::{OsStringValueParser, TypedValueParser},
|
|
||||||
Args, Parser, Subcommand, ValueEnum, ValueHint,
|
|
||||||
};
|
|
||||||
use eyre::{eyre, Report};
|
|
||||||
use std::{error, ffi::OsString, fmt, io, path::PathBuf, process};
|
|
||||||
|
|
||||||
pub type Result<T = (), E = CliError> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
pub struct CliError(Report);
|
|
||||||
|
|
||||||
impl fmt::Debug for CliError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for CliError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for CliError {}
|
|
||||||
|
|
||||||
impl From<io::Error> for CliError {
|
|
||||||
fn from(value: io::Error) -> Self {
|
|
||||||
CliError(Report::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait RunPhase<Arg> {
|
|
||||||
type Output;
|
|
||||||
fn run(&self, arg: Arg) -> Result<Self::Output>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct BaseArgs {
|
|
||||||
/// the directory to put the generated main output file and associated files in
|
|
||||||
#[arg(short, long, value_hint = ValueHint::DirPath)]
|
|
||||||
pub output: PathBuf,
|
|
||||||
/// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
|
|
||||||
#[arg(long)]
|
|
||||||
pub file_stem: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BaseArgs {
|
|
||||||
pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend {
|
|
||||||
firrtl::FileBackend {
|
|
||||||
dir_path: self.output.clone(),
|
|
||||||
top_fir_file_stem: self.file_stem.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct FirrtlArgs {
|
|
||||||
#[command(flatten)]
|
|
||||||
pub base: BaseArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct FirrtlOutput {
|
|
||||||
pub file_stem: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FirrtlOutput {
|
|
||||||
pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf {
|
|
||||||
let mut retval = args.base.output.join(&self.file_stem);
|
|
||||||
retval.set_extension("fir");
|
|
||||||
retval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FirrtlArgs {
|
|
||||||
fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> {
|
|
||||||
let firrtl::FileBackend {
|
|
||||||
top_fir_file_stem, ..
|
|
||||||
} = firrtl::export(self.base.to_firrtl_file_backend(), &top_module)?;
|
|
||||||
Ok(FirrtlOutput {
|
|
||||||
file_stem: top_fir_file_stem.expect(
|
|
||||||
"export is known to set the file stem from the circuit name if not provided",
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BundleType> RunPhase<Module<T>> for FirrtlArgs {
|
|
||||||
type Output = FirrtlOutput;
|
|
||||||
fn run(&self, top_module: Module<T>) -> Result<Self::Output> {
|
|
||||||
self.run_impl(top_module.canonical())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BundleType> RunPhase<Interned<Module<T>>> for FirrtlArgs {
|
|
||||||
type Output = FirrtlOutput;
|
|
||||||
fn run(&self, top_module: Interned<Module<T>>) -> Result<Self::Output> {
|
|
||||||
self.run(*top_module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// based on [LLVM Circt's recommended lowering options
|
|
||||||
/// ](https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target)
|
|
||||||
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum VerilogDialect {
|
|
||||||
Questa,
|
|
||||||
Spyglass,
|
|
||||||
Verilator,
|
|
||||||
Vivado,
|
|
||||||
Yosys,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerilogDialect {
|
|
||||||
pub fn firtool_extra_args(self) -> &'static [&'static str] {
|
|
||||||
match self {
|
|
||||||
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
|
|
||||||
VerilogDialect::Spyglass => {
|
|
||||||
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
|
|
||||||
}
|
|
||||||
VerilogDialect::Verilator => &[
|
|
||||||
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
|
|
||||||
],
|
|
||||||
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
|
|
||||||
VerilogDialect::Yosys => {
|
|
||||||
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct VerilogArgs {
|
|
||||||
#[command(flatten)]
|
|
||||||
pub firrtl: FirrtlArgs,
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
default_value = "firtool",
|
|
||||||
env = "FIRTOOL",
|
|
||||||
value_hint = ValueHint::CommandName,
|
|
||||||
value_parser = OsStringValueParser::new().try_map(which::which)
|
|
||||||
)]
|
|
||||||
pub firtool: PathBuf,
|
|
||||||
#[arg(long)]
|
|
||||||
pub firtool_extra_args: Vec<OsString>,
|
|
||||||
/// adapt the generated Verilog for a particular toolchain
|
|
||||||
#[arg(long)]
|
|
||||||
pub verilog_dialect: Option<VerilogDialect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct VerilogOutput {
|
|
||||||
pub firrtl: FirrtlOutput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerilogOutput {
|
|
||||||
pub fn verilog_file(&self, args: &VerilogArgs) -> PathBuf {
|
|
||||||
let mut retval = args.firrtl.base.output.join(&self.firrtl.file_stem);
|
|
||||||
retval.set_extension("v");
|
|
||||||
retval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerilogArgs {
|
|
||||||
fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> {
|
|
||||||
let output = VerilogOutput {
|
|
||||||
firrtl: firrtl_output,
|
|
||||||
};
|
|
||||||
let mut cmd = process::Command::new(&self.firtool);
|
|
||||||
cmd.arg(output.firrtl.firrtl_file(&self.firrtl));
|
|
||||||
cmd.arg("-o");
|
|
||||||
cmd.arg(output.verilog_file(self));
|
|
||||||
if let Some(dialect) = self.verilog_dialect {
|
|
||||||
cmd.args(dialect.firtool_extra_args());
|
|
||||||
}
|
|
||||||
cmd.args(&self.firtool_extra_args);
|
|
||||||
cmd.current_dir(&self.firrtl.base.output);
|
|
||||||
let status = cmd.status()?;
|
|
||||||
if status.success() {
|
|
||||||
Ok(output)
|
|
||||||
} else {
|
|
||||||
Err(CliError(eyre!(
|
|
||||||
"running {} failed: {status}",
|
|
||||||
self.firtool.display()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Arg> RunPhase<Arg> for VerilogArgs
|
|
||||||
where
|
|
||||||
FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>,
|
|
||||||
{
|
|
||||||
type Output = VerilogOutput;
|
|
||||||
fn run(&self, arg: Arg) -> Result<Self::Output> {
|
|
||||||
let firrtl_output = self.firrtl.run(arg)?;
|
|
||||||
self.run_impl(firrtl_output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
|
||||||
enum CliCommand {
|
|
||||||
/// Generate FIRRTL
|
|
||||||
Firrtl(FirrtlArgs),
|
|
||||||
/// Generate Verilog
|
|
||||||
Verilog(VerilogArgs),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a simple CLI
|
|
||||||
///
|
|
||||||
/// Use like:
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # use fayalite::prelude::*;
|
|
||||||
/// # #[hdl_module]
|
|
||||||
/// # fn my_module() {}
|
|
||||||
/// use fayalite::cli;
|
|
||||||
///
|
|
||||||
/// fn main() -> cli::Result {
|
|
||||||
/// cli::Cli::parse().run(my_module())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can also use it with a larger [`clap`]-based CLI like so:
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # use fayalite::prelude::*;
|
|
||||||
/// # #[hdl_module]
|
|
||||||
/// # fn my_module() {}
|
|
||||||
/// use clap::{Subcommand, Parser};
|
|
||||||
/// use fayalite::cli;
|
|
||||||
///
|
|
||||||
/// #[derive(Subcommand)]
|
|
||||||
/// pub enum Cmd {
|
|
||||||
/// #[command(flatten)]
|
|
||||||
/// Fayalite(cli::Cli),
|
|
||||||
/// MySpecialCommand {
|
|
||||||
/// #[arg(long)]
|
|
||||||
/// foo: bool,
|
|
||||||
/// },
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// #[derive(Parser)]
|
|
||||||
/// pub struct Cli {
|
|
||||||
/// #[command(subcommand)]
|
|
||||||
/// cmd: Cmd, // or just use cli::Cli directly if you don't need more subcommands
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn main() -> cli::Result {
|
|
||||||
/// match Cli::parse().cmd {
|
|
||||||
/// Cmd::Fayalite(v) => v.run(my_module())?,
|
|
||||||
/// Cmd::MySpecialCommand { foo } => println!("special: foo={foo}"),
|
|
||||||
/// }
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
// clear things that would be crate-specific
|
|
||||||
#[command(name = "Fayalite Simple CLI", about = None, long_about = None)]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
subcommand: CliCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl clap::Subcommand for Cli {
|
|
||||||
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
|
|
||||||
CliCommand::augment_subcommands(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
|
|
||||||
CliCommand::augment_subcommands_for_update(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_subcommand(name: &str) -> bool {
|
|
||||||
CliCommand::has_subcommand(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> RunPhase<T> for Cli
|
|
||||||
where
|
|
||||||
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
|
|
||||||
{
|
|
||||||
type Output = ();
|
|
||||||
fn run(&self, arg: T) -> Result<Self::Output> {
|
|
||||||
match &self.subcommand {
|
|
||||||
CliCommand::Firrtl(c) => {
|
|
||||||
c.run(arg)?;
|
|
||||||
}
|
|
||||||
CliCommand::Verilog(c) => {
|
|
||||||
c.run(arg)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cli {
|
|
||||||
/// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`]
|
|
||||||
pub fn parse() -> Self {
|
|
||||||
clap::Parser::parse()
|
|
||||||
}
|
|
||||||
/// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`]
|
|
||||||
pub fn run<T>(&self, top_module: T) -> Result<()>
|
|
||||||
where
|
|
||||||
Self: RunPhase<T, Output = ()>,
|
|
||||||
{
|
|
||||||
RunPhase::run(self, top_module)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expr::{Expr, ToExpr},
|
expr::{Expr, ValueType},
|
||||||
hdl,
|
hdl,
|
||||||
int::Bool,
|
int::Bool,
|
||||||
reset::Reset,
|
reset::{Reset, ResetType},
|
||||||
|
sim::value::SimValue,
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
|
ty::{
|
||||||
|
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
|
||||||
|
OpaqueSimValueWritten, SimValueDebug, StaticType, Type, TypeProperties,
|
||||||
|
impl_match_variant_as_self,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use bitvec::{bits, order::Lsb0};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
|
||||||
pub struct Clock;
|
pub struct Clock;
|
||||||
|
|
@ -15,6 +23,7 @@ pub struct Clock;
|
||||||
impl Type for Clock {
|
impl Type for Clock {
|
||||||
type BaseType = Clock;
|
type BaseType = Clock;
|
||||||
type MaskType = Bool;
|
type MaskType = Bool;
|
||||||
|
type SimValue = bool;
|
||||||
|
|
||||||
impl_match_variant_as_self!();
|
impl_match_variant_as_self!();
|
||||||
|
|
||||||
|
|
@ -36,6 +45,40 @@ impl Type for Clock {
|
||||||
};
|
};
|
||||||
retval
|
retval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
opaque.bits()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
*value = opaque.bits()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
|
||||||
|
[bits![0], bits![1]][*value as usize],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDebug for Clock {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
|
|
@ -55,46 +98,70 @@ impl StaticType for Clock {
|
||||||
is_storable: false,
|
is_storable: false,
|
||||||
is_castable_from_bits: true,
|
is_castable_from_bits: true,
|
||||||
bit_width: 1,
|
bit_width: 1,
|
||||||
|
sim_only_values_len: 0,
|
||||||
};
|
};
|
||||||
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
|
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToClock {
|
pub trait ToClock {
|
||||||
fn to_clock(&self) -> Expr<Clock>;
|
type Output: ValueType<Type = Clock>;
|
||||||
|
fn to_clock(&self) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + ToClock> ToClock for &'_ T {
|
impl<T: ?Sized + ToClock> ToClock for &'_ T {
|
||||||
fn to_clock(&self) -> Expr<Clock> {
|
type Output = T::Output;
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
(**self).to_clock()
|
(**self).to_clock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + ToClock> ToClock for &'_ mut T {
|
impl<T: ?Sized + ToClock> ToClock for &'_ mut T {
|
||||||
fn to_clock(&self) -> Expr<Clock> {
|
type Output = T::Output;
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
(**self).to_clock()
|
(**self).to_clock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + ToClock> ToClock for Box<T> {
|
impl<T: ?Sized + ToClock> ToClock for Box<T> {
|
||||||
fn to_clock(&self) -> Expr<Clock> {
|
type Output = T::Output;
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
(**self).to_clock()
|
(**self).to_clock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToClock for Expr<Clock> {
|
impl ToClock for Expr<Clock> {
|
||||||
fn to_clock(&self) -> Expr<Clock> {
|
type Output = Expr<Clock>;
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
pub struct ClockDomain {
|
pub struct ClockDomain<R: ResetType = Reset> {
|
||||||
pub clk: Clock,
|
pub clk: Clock,
|
||||||
pub rst: Reset,
|
pub rst: R,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToClock for bool {
|
impl ToClock for bool {
|
||||||
fn to_clock(&self) -> Expr<Clock> {
|
type Output = SimValue<Clock>;
|
||||||
self.to_expr().to_clock()
|
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
|
SimValue::from_value(Clock, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToClock for SimValue<Bool> {
|
||||||
|
type Output = SimValue<Clock>;
|
||||||
|
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
|
SimValue::from_value(Clock, **self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToClock for SimValue<Clock> {
|
||||||
|
type Output = SimValue<Clock>;
|
||||||
|
|
||||||
|
fn to_clock(&self) -> Self::Output {
|
||||||
|
self.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,28 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expr::{ops::VariantAccess, Expr, ToExpr},
|
expr::{Expr, ToExpr, ValueType, ops::VariantAccess},
|
||||||
hdl,
|
hdl,
|
||||||
int::Bool,
|
int::{Bool, UIntValue},
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
module::{
|
module::{
|
||||||
enum_match_variants_helper, EnumMatchVariantAndInactiveScopeImpl,
|
EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, connect,
|
||||||
EnumMatchVariantsIterImpl, Scope,
|
enum_match_variants_helper, incomplete_wire, wire,
|
||||||
},
|
},
|
||||||
|
sim::value::{SimValue, ToSimValue, ToSimValueWithType},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties},
|
ty::{
|
||||||
|
CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, OpaqueSimValueSize,
|
||||||
|
OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, SimValueDebug,
|
||||||
|
StaticType, Type, TypeProperties,
|
||||||
|
},
|
||||||
|
util::HashMap,
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
use bitvec::{order::Lsb0, slice::BitSlice, view::BitView};
|
||||||
use std::{fmt, iter::FusedIterator};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||||
pub struct EnumVariant {
|
pub struct EnumVariant {
|
||||||
pub name: Interned<str>,
|
pub name: Interned<str>,
|
||||||
pub ty: Option<CanonicalType>,
|
pub ty: Option<CanonicalType>,
|
||||||
|
|
@ -111,6 +118,7 @@ impl EnumTypePropertiesBuilder {
|
||||||
is_storable: true,
|
is_storable: true,
|
||||||
is_castable_from_bits: true,
|
is_castable_from_bits: true,
|
||||||
bit_width: 0,
|
bit_width: 0,
|
||||||
|
sim_only_values_len: 0,
|
||||||
},
|
},
|
||||||
variant_count: 0,
|
variant_count: 0,
|
||||||
}
|
}
|
||||||
|
|
@ -129,9 +137,14 @@ impl EnumTypePropertiesBuilder {
|
||||||
is_storable,
|
is_storable,
|
||||||
is_castable_from_bits,
|
is_castable_from_bits,
|
||||||
bit_width,
|
bit_width,
|
||||||
|
sim_only_values_len,
|
||||||
}) = field_props
|
}) = field_props
|
||||||
{
|
{
|
||||||
assert!(is_passive, "variant type must be a passive type");
|
assert!(is_passive, "variant type must be a passive type");
|
||||||
|
assert!(
|
||||||
|
sim_only_values_len == 0,
|
||||||
|
"can't have `SimOnlyValue`s in an Enum"
|
||||||
|
);
|
||||||
type_properties = TypeProperties {
|
type_properties = TypeProperties {
|
||||||
is_passive: true,
|
is_passive: true,
|
||||||
is_storable: type_properties.is_storable & is_storable,
|
is_storable: type_properties.is_storable & is_storable,
|
||||||
|
|
@ -142,6 +155,7 @@ impl EnumTypePropertiesBuilder {
|
||||||
} else {
|
} else {
|
||||||
type_properties.bit_width
|
type_properties.bit_width
|
||||||
},
|
},
|
||||||
|
sim_only_values_len: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -149,6 +163,12 @@ impl EnumTypePropertiesBuilder {
|
||||||
variant_count: variant_count + 1,
|
variant_count: variant_count + 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn variants(self, variants: impl IntoIterator<Item = EnumVariant>) -> Self {
|
||||||
|
variants.into_iter().fold(self, |this, variant| {
|
||||||
|
this.variant(variant.ty.map(CanonicalType::type_properties))
|
||||||
|
})
|
||||||
|
}
|
||||||
pub const fn finish(self) -> TypeProperties {
|
pub const fn finish(self) -> TypeProperties {
|
||||||
assert!(
|
assert!(
|
||||||
self.variant_count != 0,
|
self.variant_count != 0,
|
||||||
|
|
@ -169,10 +189,17 @@ impl EnumTypePropertiesBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for EnumTypePropertiesBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Enum {
|
impl Enum {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(variants: Interned<[EnumVariant]>) -> Self {
|
pub fn new(variants: Interned<[EnumVariant]>) -> Self {
|
||||||
let mut name_indexes = HashMap::with_capacity(variants.len());
|
let mut name_indexes =
|
||||||
|
HashMap::with_capacity_and_hasher(variants.len(), Default::default());
|
||||||
let mut type_props_builder = EnumTypePropertiesBuilder::new();
|
let mut type_props_builder = EnumTypePropertiesBuilder::new();
|
||||||
for (index, EnumVariant { name, ty }) in variants.iter().enumerate() {
|
for (index, EnumVariant { name, ty }) in variants.iter().enumerate() {
|
||||||
if let Some(old_index) = name_indexes.insert(*name, index) {
|
if let Some(old_index) = name_indexes.insert(*name, index) {
|
||||||
|
|
@ -237,8 +264,9 @@ pub trait EnumType:
|
||||||
MatchActiveScope = Scope,
|
MatchActiveScope = Scope,
|
||||||
MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>,
|
MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>,
|
||||||
MatchVariantsIter = EnumMatchVariantsIter<Self>,
|
MatchVariantsIter = EnumMatchVariantsIter<Self>,
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
type SimBuilder: From<Self>;
|
||||||
fn variants(&self) -> Interned<[EnumVariant]>;
|
fn variants(&self) -> Interned<[EnumVariant]>;
|
||||||
fn match_activate_scope(
|
fn match_activate_scope(
|
||||||
v: Self::MatchVariantAndInactiveScope,
|
v: Self::MatchVariantAndInactiveScope,
|
||||||
|
|
@ -301,7 +329,18 @@ impl<T: EnumType> DoubleEndedIterator for EnumMatchVariantsIter<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct NoBuilder {
|
||||||
|
_ty: Enum,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Enum> for NoBuilder {
|
||||||
|
fn from(_ty: Enum) -> Self {
|
||||||
|
Self { _ty }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EnumType for Enum {
|
impl EnumType for Enum {
|
||||||
|
type SimBuilder = NoBuilder;
|
||||||
fn match_activate_scope(
|
fn match_activate_scope(
|
||||||
v: Self::MatchVariantAndInactiveScope,
|
v: Self::MatchVariantAndInactiveScope,
|
||||||
) -> (Self::MatchVariant, Self::MatchActiveScope) {
|
) -> (Self::MatchVariant, Self::MatchActiveScope) {
|
||||||
|
|
@ -316,6 +355,7 @@ impl EnumType for Enum {
|
||||||
impl Type for Enum {
|
impl Type for Enum {
|
||||||
type BaseType = Enum;
|
type BaseType = Enum;
|
||||||
type MaskType = Bool;
|
type MaskType = Bool;
|
||||||
|
type SimValue = OpaqueSimValue;
|
||||||
type MatchVariant = Option<Expr<CanonicalType>>;
|
type MatchVariant = Option<Expr<CanonicalType>>;
|
||||||
type MatchActiveScope = Scope;
|
type MatchActiveScope = Scope;
|
||||||
type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>;
|
type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>;
|
||||||
|
|
@ -346,9 +386,353 @@ impl Type for Enum {
|
||||||
fn source_location() -> SourceLocation {
|
fn source_location() -> SourceLocation {
|
||||||
SourceLocation::builtin()
|
SourceLocation::builtin()
|
||||||
}
|
}
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert_eq!(self.type_properties().size(), opaque.size());
|
||||||
|
opaque.to_owned()
|
||||||
|
}
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert_eq!(self.type_properties().size(), opaque.size());
|
||||||
|
assert_eq!(value.size(), opaque.size());
|
||||||
|
value.clone_from_slice(opaque);
|
||||||
|
}
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
assert_eq!(self.type_properties().size(), writer.size());
|
||||||
|
assert_eq!(value.size(), writer.size());
|
||||||
|
writer.fill_cloned_from_slice(value.as_slice())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl]
|
impl SimValueDebug for Enum {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
|
||||||
|
pub struct EnumPaddingSimValue {
|
||||||
|
bits: Option<UIntValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnumPaddingSimValue {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { bits: None }
|
||||||
|
}
|
||||||
|
pub fn bit_width(&self) -> Option<usize> {
|
||||||
|
self.bits.as_ref().map(UIntValue::width)
|
||||||
|
}
|
||||||
|
pub fn bits(&self) -> &Option<UIntValue> {
|
||||||
|
&self.bits
|
||||||
|
}
|
||||||
|
pub fn bits_mut(&mut self) -> &mut Option<UIntValue> {
|
||||||
|
&mut self.bits
|
||||||
|
}
|
||||||
|
pub fn into_bits(self) -> Option<UIntValue> {
|
||||||
|
self.bits
|
||||||
|
}
|
||||||
|
pub fn from_bits(bits: Option<UIntValue>) -> Self {
|
||||||
|
Self { bits }
|
||||||
|
}
|
||||||
|
pub fn from_bitslice(v: &BitSlice) -> Self {
|
||||||
|
Self {
|
||||||
|
bits: Some(UIntValue::new(Arc::new(v.to_bitvec()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub struct UnknownVariantSimValue {
|
||||||
|
discriminant: usize,
|
||||||
|
body_bits: UIntValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnknownVariantSimValue {
|
||||||
|
pub fn discriminant(&self) -> usize {
|
||||||
|
self.discriminant
|
||||||
|
}
|
||||||
|
pub fn body_bits(&self) -> &UIntValue {
|
||||||
|
&self.body_bits
|
||||||
|
}
|
||||||
|
pub fn body_bits_mut(&mut self) -> &mut UIntValue {
|
||||||
|
&mut self.body_bits
|
||||||
|
}
|
||||||
|
pub fn into_body_bits(self) -> UIntValue {
|
||||||
|
self.body_bits
|
||||||
|
}
|
||||||
|
pub fn into_parts(self) -> (usize, UIntValue) {
|
||||||
|
(self.discriminant, self.body_bits)
|
||||||
|
}
|
||||||
|
pub fn new(discriminant: usize, body_bits: UIntValue) -> Self {
|
||||||
|
Self {
|
||||||
|
discriminant,
|
||||||
|
body_bits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnumSimValueFromOpaque<'a> {
|
||||||
|
variants: Interned<[EnumVariant]>,
|
||||||
|
discriminant: usize,
|
||||||
|
body_bits: &'a BitSlice,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EnumSimValueFromOpaque<'a> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<T: EnumType>(ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self {
|
||||||
|
let variants = ty.variants();
|
||||||
|
let size = EnumTypePropertiesBuilder::new()
|
||||||
|
.variants(variants)
|
||||||
|
.finish()
|
||||||
|
.size();
|
||||||
|
assert!(size.only_bit_width().is_some());
|
||||||
|
assert_eq!(size, opaque.size());
|
||||||
|
let (discriminant_bits, body_bits) = opaque
|
||||||
|
.bits()
|
||||||
|
.split_at(discriminant_bit_width_impl(variants.len()));
|
||||||
|
let mut discriminant = 0usize;
|
||||||
|
discriminant.view_bits_mut::<Lsb0>()[..discriminant_bits.len()]
|
||||||
|
.copy_from_bitslice(discriminant_bits);
|
||||||
|
Self {
|
||||||
|
variants,
|
||||||
|
discriminant,
|
||||||
|
body_bits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn discriminant(&self) -> usize {
|
||||||
|
self.discriminant
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
#[cold]
|
||||||
|
fn usage_error(&self, clone: bool) -> ! {
|
||||||
|
let clone = if clone { "clone_" } else { "" };
|
||||||
|
match self.variants.get(self.discriminant) {
|
||||||
|
None => {
|
||||||
|
panic!("should have called EnumSimValueFromBits::unknown_variant_{clone}from_bits");
|
||||||
|
}
|
||||||
|
Some(EnumVariant { ty: None, .. }) => {
|
||||||
|
panic!(
|
||||||
|
"should have called EnumSimValueFromBits::variant_no_field_{clone}from_bits"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(EnumVariant { ty: Some(_), .. }) => {
|
||||||
|
panic!(
|
||||||
|
"should have called EnumSimValueFromBits::variant_with_field_{clone}from_bits"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn known_variant(&self, clone: bool) -> (Option<CanonicalType>, &'a BitSlice, &'a BitSlice) {
|
||||||
|
let Some(EnumVariant { ty, .. }) = self.variants.get(self.discriminant) else {
|
||||||
|
self.usage_error(clone);
|
||||||
|
};
|
||||||
|
let variant_bit_width = ty.map_or(0, CanonicalType::bit_width);
|
||||||
|
let (variant_bits, padding_bits) = self.body_bits.split_at(variant_bit_width);
|
||||||
|
(*ty, variant_bits, padding_bits)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unknown_variant_from_opaque(self) -> UnknownVariantSimValue {
|
||||||
|
let None = self.variants.get(self.discriminant) else {
|
||||||
|
self.usage_error(false);
|
||||||
|
};
|
||||||
|
UnknownVariantSimValue::new(
|
||||||
|
self.discriminant,
|
||||||
|
UIntValue::new(Arc::new(self.body_bits.to_bitvec())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unknown_variant_clone_from_opaque(self, value: &mut UnknownVariantSimValue) {
|
||||||
|
let None = self.variants.get(self.discriminant) else {
|
||||||
|
self.usage_error(true);
|
||||||
|
};
|
||||||
|
value.discriminant = self.discriminant;
|
||||||
|
assert_eq!(value.body_bits.width(), self.body_bits.len());
|
||||||
|
value
|
||||||
|
.body_bits
|
||||||
|
.bits_mut()
|
||||||
|
.copy_from_bitslice(self.body_bits);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_no_field_from_opaque(self) -> EnumPaddingSimValue {
|
||||||
|
let (None, _variant_bits, padding_bits) = self.known_variant(false) else {
|
||||||
|
self.usage_error(false);
|
||||||
|
};
|
||||||
|
EnumPaddingSimValue::from_bitslice(padding_bits)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_with_field_from_opaque<T: Type>(self) -> (SimValue<T>, EnumPaddingSimValue) {
|
||||||
|
let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(false) else {
|
||||||
|
self.usage_error(false);
|
||||||
|
};
|
||||||
|
(
|
||||||
|
SimValue::from_bitslice(T::from_canonical(variant_ty), variant_bits),
|
||||||
|
EnumPaddingSimValue::from_bitslice(padding_bits),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn clone_padding_from_bits(padding: &mut EnumPaddingSimValue, padding_bits: &BitSlice) {
|
||||||
|
match padding.bits_mut() {
|
||||||
|
None => *padding = EnumPaddingSimValue::from_bitslice(padding_bits),
|
||||||
|
Some(padding) => {
|
||||||
|
assert_eq!(padding.width(), padding_bits.len());
|
||||||
|
padding.bits_mut().copy_from_bitslice(padding_bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_no_field_clone_from_opaque(self, padding: &mut EnumPaddingSimValue) {
|
||||||
|
let (None, _variant_bits, padding_bits) = self.known_variant(true) else {
|
||||||
|
self.usage_error(true);
|
||||||
|
};
|
||||||
|
Self::clone_padding_from_bits(padding, padding_bits);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_with_field_clone_from_opaque<T: Type>(
|
||||||
|
self,
|
||||||
|
value: &mut SimValue<T>,
|
||||||
|
padding: &mut EnumPaddingSimValue,
|
||||||
|
) {
|
||||||
|
let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(true) else {
|
||||||
|
self.usage_error(true);
|
||||||
|
};
|
||||||
|
assert_eq!(value.ty(), T::from_canonical(variant_ty));
|
||||||
|
SimValue::bits_mut(value)
|
||||||
|
.bits_mut()
|
||||||
|
.copy_from_bitslice(variant_bits);
|
||||||
|
Self::clone_padding_from_bits(padding, padding_bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnumSimValueToOpaque<'a> {
|
||||||
|
variants: Interned<[EnumVariant]>,
|
||||||
|
bit_width: usize,
|
||||||
|
discriminant_bit_width: usize,
|
||||||
|
writer: OpaqueSimValueWriter<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EnumSimValueToOpaque<'a> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<T: EnumType>(ty: T, writer: OpaqueSimValueWriter<'a>) -> Self {
|
||||||
|
let variants = ty.variants();
|
||||||
|
let size = EnumTypePropertiesBuilder::new()
|
||||||
|
.variants(variants)
|
||||||
|
.finish()
|
||||||
|
.size();
|
||||||
|
assert_eq!(size, writer.size());
|
||||||
|
Self {
|
||||||
|
variants,
|
||||||
|
bit_width: size
|
||||||
|
.only_bit_width()
|
||||||
|
.expect("enums should only contain bits"),
|
||||||
|
discriminant_bit_width: discriminant_bit_width_impl(variants.len()),
|
||||||
|
writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn write_discriminant(&mut self, mut discriminant: usize) {
|
||||||
|
let orig_discriminant = discriminant;
|
||||||
|
let discriminant_bits =
|
||||||
|
&mut discriminant.view_bits_mut::<Lsb0>()[..self.discriminant_bit_width];
|
||||||
|
self.writer.fill_prefix_with(
|
||||||
|
OpaqueSimValueSize::from_bit_width(self.discriminant_bit_width),
|
||||||
|
|writer| {
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(discriminant_bits))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
discriminant_bits.fill(false);
|
||||||
|
assert!(
|
||||||
|
discriminant == 0,
|
||||||
|
"{orig_discriminant:#x} is too big to fit in enum discriminant bits",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unknown_variant_to_opaque(
|
||||||
|
mut self,
|
||||||
|
value: &UnknownVariantSimValue,
|
||||||
|
) -> OpaqueSimValueWritten<'a> {
|
||||||
|
self.write_discriminant(value.discriminant);
|
||||||
|
let None = self.variants.get(value.discriminant) else {
|
||||||
|
panic!("can't use UnknownVariantSimValue to set known discriminant");
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
self.bit_width - self.discriminant_bit_width,
|
||||||
|
value.body_bits.width()
|
||||||
|
);
|
||||||
|
self.writer
|
||||||
|
.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(value.body_bits.bits()))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn known_variant(
|
||||||
|
mut self,
|
||||||
|
discriminant: usize,
|
||||||
|
value: Option<&OpaqueSimValue>,
|
||||||
|
padding: &EnumPaddingSimValue,
|
||||||
|
) -> OpaqueSimValueWritten<'a> {
|
||||||
|
self.write_discriminant(discriminant);
|
||||||
|
let variant_ty = self.variants[discriminant].ty;
|
||||||
|
let variant_size = variant_ty.map_or(OpaqueSimValueSize::empty(), CanonicalType::size);
|
||||||
|
if let Some(value) = value {
|
||||||
|
if variant_ty.is_none() {
|
||||||
|
panic!("expected variant to have no field");
|
||||||
|
}
|
||||||
|
self.writer.fill_prefix_with(variant_size, |writer| {
|
||||||
|
writer.fill_cloned_from_slice(value.as_slice())
|
||||||
|
});
|
||||||
|
} else if variant_ty.is_some() {
|
||||||
|
panic!("expected variant to have a field");
|
||||||
|
}
|
||||||
|
if let Some(padding) = padding.bits() {
|
||||||
|
assert_eq!(padding.ty().type_properties().size(), self.writer.size());
|
||||||
|
self.writer
|
||||||
|
.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(padding.bits()))
|
||||||
|
} else {
|
||||||
|
self.writer.fill_with_zeros()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_no_field_to_opaque(
|
||||||
|
self,
|
||||||
|
discriminant: usize,
|
||||||
|
padding: &EnumPaddingSimValue,
|
||||||
|
) -> OpaqueSimValueWritten<'a> {
|
||||||
|
self.known_variant(discriminant, None, padding)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn variant_with_field_to_opaque<T: Type>(
|
||||||
|
self,
|
||||||
|
discriminant: usize,
|
||||||
|
value: &SimValue<T>,
|
||||||
|
padding: &EnumPaddingSimValue,
|
||||||
|
) -> OpaqueSimValueWritten<'a> {
|
||||||
|
let Some(variant_ty) = self.variants[discriminant].ty else {
|
||||||
|
panic!("expected variant to have no field");
|
||||||
|
};
|
||||||
|
assert_eq!(value.ty(), T::from_canonical(variant_ty));
|
||||||
|
self.known_variant(discriminant, Some(SimValue::opaque(value)), padding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn assert_is_enum_type<T: EnumType>(v: T) -> T {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn enum_type_to_sim_builder<T: EnumType>(v: T) -> T::SimBuilder {
|
||||||
|
v.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(cmp_eq)]
|
||||||
pub enum HdlOption<T: Type> {
|
pub enum HdlOption<T: Type> {
|
||||||
HdlNone,
|
HdlNone,
|
||||||
HdlSome(T),
|
HdlSome(T),
|
||||||
|
|
@ -362,5 +746,426 @@ pub fn HdlNone<T: StaticType>() -> Expr<HdlOption<T>> {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn HdlSome<T: Type>(value: impl ToExpr<Type = T>) -> Expr<HdlOption<T>> {
|
pub fn HdlSome<T: Type>(value: impl ToExpr<Type = T>) -> Expr<HdlOption<T>> {
|
||||||
let value = value.to_expr();
|
let value = value.to_expr();
|
||||||
HdlOption[Expr::ty(value)].HdlSome(value)
|
HdlOption[value.ty()].HdlSome(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type> From<SimValue<HdlOption<T>>> for Option<SimValue<T>> {
|
||||||
|
#[hdl]
|
||||||
|
fn from(value: SimValue<HdlOption<T>>) -> Self {
|
||||||
|
#[hdl(sim)]
|
||||||
|
match value {
|
||||||
|
HdlSome(v) => Some(v),
|
||||||
|
HdlNone => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Type> From<&'a SimValue<HdlOption<T>>> for Option<&'a SimValue<T>> {
|
||||||
|
#[hdl]
|
||||||
|
fn from(value: &'a SimValue<HdlOption<T>>) -> Self {
|
||||||
|
#[hdl(sim)]
|
||||||
|
match value {
|
||||||
|
HdlSome(v) => Some(v),
|
||||||
|
HdlNone => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Type> From<&'a mut SimValue<HdlOption<T>>> for Option<&'a mut SimValue<T>> {
|
||||||
|
#[hdl]
|
||||||
|
fn from(value: &'a mut SimValue<HdlOption<T>>) -> Self {
|
||||||
|
#[hdl(sim)]
|
||||||
|
match value {
|
||||||
|
HdlSome(v) => Some(v),
|
||||||
|
HdlNone => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueType<Type: StaticType<MaskType: StaticType>>> ValueType for Option<T> {
|
||||||
|
type Type = HdlOption<T::Type>;
|
||||||
|
type ValueCategory = T::ValueCategory;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
StaticType::TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, V: ToSimValueWithType<T>> ToSimValueWithType<HdlOption<T>> for Option<V> {
|
||||||
|
#[hdl]
|
||||||
|
fn to_sim_value_with_type(&self, ty: HdlOption<T>) -> SimValue<HdlOption<T>> {
|
||||||
|
match self {
|
||||||
|
Some(v) =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
ty.HdlSome(v)
|
||||||
|
}
|
||||||
|
None =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
ty.HdlNone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
fn into_sim_value_with_type(self, ty: HdlOption<T>) -> SimValue<HdlOption<T>> {
|
||||||
|
match self {
|
||||||
|
Some(v) =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
ty.HdlSome(v)
|
||||||
|
}
|
||||||
|
None =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
ty.HdlNone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToSimValue<Type: StaticType<MaskType: StaticType>>> ToSimValue for Option<T> {
|
||||||
|
#[hdl]
|
||||||
|
fn to_sim_value(&self) -> SimValue<Self::Type> {
|
||||||
|
match self {
|
||||||
|
Some(v) =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlSome(v)
|
||||||
|
}
|
||||||
|
None =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlNone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
fn into_sim_value(self) -> SimValue<Self::Type> {
|
||||||
|
match self {
|
||||||
|
Some(v) =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlSome(v)
|
||||||
|
}
|
||||||
|
None =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
HdlNone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ToExpr<Type: StaticType<MaskType: StaticType>>> ToExpr for Option<T> {
|
||||||
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
|
match self {
|
||||||
|
Some(v) => HdlSome(v),
|
||||||
|
None => HdlNone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type> HdlOption<T> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn try_map<R: Type, E>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Result<Expr<R>, E>,
|
||||||
|
) -> Result<Expr<HdlOption<R>>, E> {
|
||||||
|
Self::try_and_then(expr, |v| Ok(HdlSome(f(v)?)))
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn map<R: Type>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Expr<R>,
|
||||||
|
) -> Expr<HdlOption<R>> {
|
||||||
|
Self::and_then(expr, |v| HdlSome(f(v)))
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn try_and_then<R: Type, E>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Result<Expr<HdlOption<R>>, E>,
|
||||||
|
) -> Result<Expr<HdlOption<R>>, E> {
|
||||||
|
// manually run match steps so we can extract the return type to construct HdlNone
|
||||||
|
type Wrap<T> = T;
|
||||||
|
#[hdl]
|
||||||
|
let mut and_then_out = incomplete_wire();
|
||||||
|
let mut iter = Self::match_variants(expr, SourceLocation::caller());
|
||||||
|
let none = iter.next().unwrap();
|
||||||
|
let some = iter.next().unwrap();
|
||||||
|
assert!(iter.next().is_none());
|
||||||
|
let (Wrap::<<Self as Type>::MatchVariant>::HdlSome(value), some_scope) =
|
||||||
|
Self::match_activate_scope(some)
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let value = f(value).inspect_err(|_| {
|
||||||
|
and_then_out.complete(()); // avoid error
|
||||||
|
})?;
|
||||||
|
let and_then_out = and_then_out.complete(value.ty());
|
||||||
|
connect(and_then_out, value);
|
||||||
|
drop(some_scope);
|
||||||
|
let (Wrap::<<Self as Type>::MatchVariant>::HdlNone, none_scope) =
|
||||||
|
Self::match_activate_scope(none)
|
||||||
|
else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
connect(and_then_out, and_then_out.ty().HdlNone());
|
||||||
|
drop(none_scope);
|
||||||
|
Ok(and_then_out)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn and_then<R: Type>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Expr<HdlOption<R>>,
|
||||||
|
) -> Expr<HdlOption<R>> {
|
||||||
|
match Self::try_and_then(expr, |v| Ok::<_, Infallible>(f(v))) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => match e {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn and<U: Type>(expr: Expr<Self>, opt_b: Expr<HdlOption<U>>) -> Expr<HdlOption<U>> {
|
||||||
|
#[hdl]
|
||||||
|
let and_out = wire(opt_b.ty());
|
||||||
|
connect(and_out, opt_b.ty().HdlNone());
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(_) = expr {
|
||||||
|
connect(and_out, opt_b);
|
||||||
|
}
|
||||||
|
and_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn try_filter<E>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Result<Expr<Bool>, E>,
|
||||||
|
) -> Result<Expr<Self>, E> {
|
||||||
|
#[hdl]
|
||||||
|
let filtered = wire(expr.ty());
|
||||||
|
connect(filtered, expr.ty().HdlNone());
|
||||||
|
let mut f = Some(f);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
#[hdl]
|
||||||
|
if f.take().unwrap()(v)? {
|
||||||
|
connect(filtered, HdlSome(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(filtered)
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn filter(expr: Expr<Self>, f: impl FnOnce(Expr<T>) -> Expr<Bool>) -> Expr<Self> {
|
||||||
|
match Self::try_filter(expr, |v| Ok::<_, Infallible>(f(v))) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => match e {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn try_inspect<E>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Result<(), E>,
|
||||||
|
) -> Result<Expr<Self>, E> {
|
||||||
|
let mut f = Some(f);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
f.take().unwrap()(v)?;
|
||||||
|
}
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn inspect(expr: Expr<Self>, f: impl FnOnce(Expr<T>)) -> Expr<Self> {
|
||||||
|
let mut f = Some(f);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
f.take().unwrap()(v);
|
||||||
|
}
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn is_none(expr: Expr<Self>) -> Expr<Bool> {
|
||||||
|
#[hdl]
|
||||||
|
let is_none_out: Bool = wire();
|
||||||
|
connect(is_none_out, false);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlNone = expr {
|
||||||
|
connect(is_none_out, true);
|
||||||
|
}
|
||||||
|
is_none_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn is_some(expr: Expr<Self>) -> Expr<Bool> {
|
||||||
|
#[hdl]
|
||||||
|
let is_some_out: Bool = wire();
|
||||||
|
connect(is_some_out, false);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(_) = expr {
|
||||||
|
connect(is_some_out, true);
|
||||||
|
}
|
||||||
|
is_some_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn map_or<R: Type>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
default: Expr<R>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Expr<R>,
|
||||||
|
) -> Expr<R> {
|
||||||
|
#[hdl]
|
||||||
|
let mapped = wire(default.ty());
|
||||||
|
let mut f = Some(f);
|
||||||
|
#[hdl]
|
||||||
|
match expr {
|
||||||
|
HdlSome(v) => connect(mapped, f.take().unwrap()(v)),
|
||||||
|
HdlNone => connect(mapped, default),
|
||||||
|
}
|
||||||
|
mapped
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn map_or_else<R: Type>(
|
||||||
|
expr: Expr<Self>,
|
||||||
|
default: impl FnOnce() -> Expr<R>,
|
||||||
|
f: impl FnOnce(Expr<T>) -> Expr<R>,
|
||||||
|
) -> Expr<R> {
|
||||||
|
#[hdl]
|
||||||
|
let mut mapped = incomplete_wire();
|
||||||
|
let mut default = Some(default);
|
||||||
|
let mut f = Some(f);
|
||||||
|
let mut retval = None;
|
||||||
|
#[hdl]
|
||||||
|
match expr {
|
||||||
|
HdlSome(v) => {
|
||||||
|
let v = f.take().unwrap()(v);
|
||||||
|
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
|
||||||
|
connect(mapped, v);
|
||||||
|
}
|
||||||
|
HdlNone => {
|
||||||
|
let v = default.take().unwrap()();
|
||||||
|
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
|
||||||
|
connect(mapped, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval.unwrap()
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn or(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
|
||||||
|
#[hdl]
|
||||||
|
let or_out = wire(expr.ty());
|
||||||
|
connect(or_out, opt_b);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(_) = expr {
|
||||||
|
connect(or_out, expr);
|
||||||
|
}
|
||||||
|
or_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn or_else(expr: Expr<Self>, f: impl FnOnce() -> Expr<Self>) -> Expr<Self> {
|
||||||
|
#[hdl]
|
||||||
|
let or_else_out = wire(expr.ty());
|
||||||
|
connect(or_else_out, f());
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(_) = expr {
|
||||||
|
connect(or_else_out, expr);
|
||||||
|
}
|
||||||
|
or_else_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unwrap_or(expr: Expr<Self>, default: Expr<T>) -> Expr<T> {
|
||||||
|
#[hdl]
|
||||||
|
let unwrap_or_else_out = wire(default.ty());
|
||||||
|
connect(unwrap_or_else_out, default);
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
connect(unwrap_or_else_out, v);
|
||||||
|
}
|
||||||
|
unwrap_or_else_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unwrap_or_else(expr: Expr<Self>, f: impl FnOnce() -> Expr<T>) -> Expr<T> {
|
||||||
|
#[hdl]
|
||||||
|
let unwrap_or_else_out = wire(expr.ty().HdlSome);
|
||||||
|
connect(unwrap_or_else_out, f());
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
connect(unwrap_or_else_out, v);
|
||||||
|
}
|
||||||
|
unwrap_or_else_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn xor(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
|
||||||
|
#[hdl]
|
||||||
|
let xor_out = wire(expr.ty());
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(_) = expr {
|
||||||
|
#[hdl]
|
||||||
|
if let HdlNone = opt_b {
|
||||||
|
connect(xor_out, expr);
|
||||||
|
} else {
|
||||||
|
connect(xor_out, expr.ty().HdlNone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connect(xor_out, opt_b);
|
||||||
|
}
|
||||||
|
xor_out
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn zip<U: Type>(expr: Expr<Self>, other: Expr<HdlOption<U>>) -> Expr<HdlOption<(T, U)>> {
|
||||||
|
#[hdl]
|
||||||
|
let zip_out = wire(HdlOption[(expr.ty().HdlSome, other.ty().HdlSome)]);
|
||||||
|
connect(zip_out, zip_out.ty().HdlNone());
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(l) = expr {
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(r) = other {
|
||||||
|
connect(zip_out, HdlSome((l, r)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zip_out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type> HdlOption<HdlOption<T>> {
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn flatten(expr: Expr<Self>) -> Expr<HdlOption<T>> {
|
||||||
|
#[hdl]
|
||||||
|
let flattened = wire(expr.ty().HdlSome);
|
||||||
|
#[hdl]
|
||||||
|
match expr {
|
||||||
|
HdlSome(v) => connect(flattened, v),
|
||||||
|
HdlNone => connect(flattened, expr.ty().HdlSome.HdlNone()),
|
||||||
|
}
|
||||||
|
flattened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, U: Type> HdlOption<(T, U)> {
|
||||||
|
#[hdl]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn unzip(expr: Expr<Self>) -> Expr<(HdlOption<T>, HdlOption<U>)> {
|
||||||
|
let (t, u) = expr.ty().HdlSome;
|
||||||
|
#[hdl]
|
||||||
|
let unzipped = wire((HdlOption[t], HdlOption[u]));
|
||||||
|
connect(unzipped, (HdlOption[t].HdlNone(), HdlOption[u].HdlNone()));
|
||||||
|
#[hdl]
|
||||||
|
if let HdlSome(v) = expr {
|
||||||
|
connect(unzipped.0, HdlSome(v.0));
|
||||||
|
connect(unzipped.1, HdlSome(v.1));
|
||||||
|
}
|
||||||
|
unzipped
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
222
crates/fayalite/src/expr/ops/test_ops_impls.rs
Normal file
222
crates/fayalite/src/expr/ops/test_ops_impls.rs
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{expr::Valueless, expr::ops::make_impls, prelude::*};
|
||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
|
macro_rules! assert_neg_impls {
|
||||||
|
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
|
||||||
|
const _: () = {
|
||||||
|
fn _check_neg_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <$ty as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$ty as ValueType>::Type> as std::ops::Neg>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
std::ops::Neg::neg(v)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((sint_local<'a, Width>))]
|
||||||
|
assert_neg_impls! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_not_impls {
|
||||||
|
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
|
||||||
|
const _: () = {
|
||||||
|
fn _check_not_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <$ty as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$ty as ValueType>::Type> as std::ops::Not>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
std::ops::Not::not(v)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((int_local<'a, Width>), (bool<'a>))]
|
||||||
|
assert_not_impls! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_cast_to_bits_impls {
|
||||||
|
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
|
||||||
|
const _: () = {
|
||||||
|
fn _check_cast_to_bits_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <$ty as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$ty as ValueType>::Type> as CastToBits>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
<$ty as CastToBits>::cast_to_bits(&v)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((uint<'a, Width>), (sint<'a, Width>), (bool<'a>))]
|
||||||
|
assert_cast_to_bits_impls! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_simple_bin_op_impls {
|
||||||
|
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] ($($R:tt)*), !$Trait:ident::$f:ident) => {
|
||||||
|
#[expect(dead_code)]
|
||||||
|
const _: () = {
|
||||||
|
trait HasImpl {
|
||||||
|
fn check_impl(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait NoImpl {
|
||||||
|
fn check_impl(&self) -> &'static dyn NoImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> NoImpl for (L, R) {
|
||||||
|
fn check_impl(&self) -> &'static dyn NoImpl {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: std::ops::$Trait<R>, R> HasImpl for (L, R) {
|
||||||
|
fn check_impl(self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_simple_bin_op_no_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: $($R)*) {
|
||||||
|
let _: &'static dyn NoImpl = (l, r).check_impl();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] (usize), Shl::shl) => {
|
||||||
|
const _: () = {
|
||||||
|
#[expect(dead_code)]
|
||||||
|
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: usize)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <($($L)*, usize) as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::Shl<usize>>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
std::ops::Shl::shl(l, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] (usize), Shr::shr) => {
|
||||||
|
const _: () = {
|
||||||
|
#[expect(dead_code)]
|
||||||
|
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: usize)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <($($L)*, usize) as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::Shr<usize>>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
std::ops::Shr::shr(l, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] ($($R:tt)*), $Trait:ident::$f:ident) => {
|
||||||
|
const _: () = {
|
||||||
|
#[expect(dead_code)]
|
||||||
|
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: $($R)*)
|
||||||
|
-> impl ValueType<
|
||||||
|
ValueCategory = <($($L)*, $($R)*) as ValueType>::ValueCategory,
|
||||||
|
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::$Trait<Valueless<<$($R)* as ValueType>::Type>>>::Output as ValueType>::Type,
|
||||||
|
> {
|
||||||
|
std::ops::$Trait::$f(l, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
($LLifetimes:tt $LBounds:tt ($($L:tt)*), $RLifetimes:tt $RBounds:tt ($($R:tt)*), !$FirstTrait:ident::$first_f:ident, $($rest:tt)*) => {
|
||||||
|
assert_simple_bin_op_impls! {
|
||||||
|
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), !$FirstTrait::$first_f
|
||||||
|
}
|
||||||
|
assert_simple_bin_op_impls! {
|
||||||
|
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $($rest)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($LLifetimes:tt $LBounds:tt ($($L:tt)*), $RLifetimes:tt $RBounds:tt ($($R:tt)*), $FirstTrait:ident::$first_f:ident, $($rest:tt)*) => {
|
||||||
|
assert_simple_bin_op_impls! {
|
||||||
|
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $FirstTrait::$first_f
|
||||||
|
}
|
||||||
|
assert_simple_bin_op_impls! {
|
||||||
|
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $($rest)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((uint_local<'l, L>))]
|
||||||
|
#[kinds((uint<'r, R>))]
|
||||||
|
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((uint<'l, L>))]
|
||||||
|
#[kinds((uint_local<'r, R>))]
|
||||||
|
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor, Shl::shl, Shr::shr}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((sint_local<'l, L>))]
|
||||||
|
#[kinds((sint<'r, R>))]
|
||||||
|
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((sint<'l, L>))]
|
||||||
|
#[kinds((sint_local<'r, R>))]
|
||||||
|
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((sint<'l, L>))]
|
||||||
|
#[kinds((uint_local<'r, R>))]
|
||||||
|
assert_simple_bin_op_impls! {Shl::shl, Shr::shr}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((uint_local<'l, L>))]
|
||||||
|
assert_simple_bin_op_impls! {[][] (usize), Shl::shl, Shr::shr}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((sint_local<'l, L>))]
|
||||||
|
assert_simple_bin_op_impls! {[][] (usize), Shl::shl, Shr::shr}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((bool_local<'l>))]
|
||||||
|
#[kinds((bool_local<'r>))]
|
||||||
|
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((bool<'l>))]
|
||||||
|
#[kinds((Valueless<Bool>))]
|
||||||
|
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((Valueless<Bool>))]
|
||||||
|
#[kinds((bool<'r>))]
|
||||||
|
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((bool_at_most_sim_value<'l>))]
|
||||||
|
#[kinds((bool_at_most_sim_value<'r>))]
|
||||||
|
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert there are no impls of BitAnd/BitOr/BitXor between Expr<Bool> and Rust's bool,
|
||||||
|
// since that helps avoid using `==`/`!=` in hdl boolean expressions, which doesn't do
|
||||||
|
// what is usually wanted.
|
||||||
|
make_impls! {
|
||||||
|
#[kinds((Expr<Bool>))]
|
||||||
|
#[kinds(bool)]
|
||||||
|
assert_simple_bin_op_impls! {!BitAnd::bitand, !BitOr::bitor, !BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_impls! {
|
||||||
|
#[kinds(bool)]
|
||||||
|
#[kinds((Expr<Bool>))]
|
||||||
|
assert_simple_bin_op_impls! {!BitAnd::bitand, !BitOr::bitor, !BitXor::bitxor}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
array::Array,
|
array::Array,
|
||||||
bundle::{Bundle, BundleField},
|
bundle::{Bundle, BundleField},
|
||||||
expr::Flow,
|
expr::{Expr, Flow, ToExpr, ValueType, value_category::ValueCategoryExpr},
|
||||||
|
formal::FormalInput,
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
memory::{DynPortType, MemPort},
|
memory::{DynPortType, MemPort},
|
||||||
module::{Instance, ModuleIO, TargetName},
|
module::{Instance, ModuleIO, TargetName},
|
||||||
reg::Reg,
|
reg::Reg,
|
||||||
|
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{CanonicalType, Type},
|
ty::{CanonicalType, TraceAsString, Type},
|
||||||
wire::Wire,
|
wire::Wire,
|
||||||
};
|
};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct TargetPathBundleField {
|
pub struct TargetPathBundleField {
|
||||||
pub name: Interned<str>,
|
pub name: Interned<str>,
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +27,7 @@ impl fmt::Display for TargetPathBundleField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct TargetPathArrayElement {
|
pub struct TargetPathArrayElement {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +38,7 @@ impl fmt::Display for TargetPathArrayElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct TargetPathDynArrayElement {}
|
pub struct TargetPathDynArrayElement {}
|
||||||
|
|
||||||
impl fmt::Display for TargetPathDynArrayElement {
|
impl fmt::Display for TargetPathDynArrayElement {
|
||||||
|
|
@ -43,11 +47,33 @@ impl fmt::Display for TargetPathDynArrayElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TargetPathTraceAsStringInner {}
|
||||||
|
|
||||||
|
impl fmt::Display for TargetPathTraceAsStringInner {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, ".<inner>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TargetPathToTraceAsString {
|
||||||
|
pub ty: TraceAsString<CanonicalType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TargetPathToTraceAsString {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, ".to_trace_as_string()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum TargetPathElement {
|
pub enum TargetPathElement {
|
||||||
BundleField(TargetPathBundleField),
|
BundleField(TargetPathBundleField),
|
||||||
ArrayElement(TargetPathArrayElement),
|
ArrayElement(TargetPathArrayElement),
|
||||||
DynArrayElement(TargetPathDynArrayElement),
|
DynArrayElement(TargetPathDynArrayElement),
|
||||||
|
TraceAsStringInner(TargetPathTraceAsStringInner),
|
||||||
|
ToTraceAsString(TargetPathToTraceAsString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TargetPathBundleField> for TargetPathElement {
|
impl From<TargetPathBundleField> for TargetPathElement {
|
||||||
|
|
@ -68,12 +94,26 @@ impl From<TargetPathDynArrayElement> for TargetPathElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TargetPathTraceAsStringInner> for TargetPathElement {
|
||||||
|
fn from(value: TargetPathTraceAsStringInner) -> Self {
|
||||||
|
Self::TraceAsStringInner(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TargetPathToTraceAsString> for TargetPathElement {
|
||||||
|
fn from(value: TargetPathToTraceAsString) -> Self {
|
||||||
|
Self::ToTraceAsString(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for TargetPathElement {
|
impl fmt::Display for TargetPathElement {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::BundleField(v) => v.fmt(f),
|
Self::BundleField(v) => v.fmt(f),
|
||||||
Self::ArrayElement(v) => v.fmt(f),
|
Self::ArrayElement(v) => v.fmt(f),
|
||||||
Self::DynArrayElement(v) => v.fmt(f),
|
Self::DynArrayElement(v) => v.fmt(f),
|
||||||
|
Self::TraceAsStringInner(v) => v.fmt(f),
|
||||||
|
Self::ToTraceAsString(v) => v.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +137,15 @@ impl TargetPathElement {
|
||||||
let parent_ty = Array::<CanonicalType>::from_canonical(parent.canonical_ty());
|
let parent_ty = Array::<CanonicalType>::from_canonical(parent.canonical_ty());
|
||||||
parent_ty.element()
|
parent_ty.element()
|
||||||
}
|
}
|
||||||
|
Self::TraceAsStringInner(_) => {
|
||||||
|
let parent_ty =
|
||||||
|
TraceAsString::<CanonicalType>::from_canonical(parent.canonical_ty());
|
||||||
|
parent_ty.inner_ty()
|
||||||
|
}
|
||||||
|
&Self::ToTraceAsString(TargetPathToTraceAsString { ty }) => {
|
||||||
|
assert_eq!(parent.canonical_ty(), ty.inner_ty());
|
||||||
|
ty.canonical()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn flow(&self, parent: Interned<Target>) -> Flow {
|
pub fn flow(&self, parent: Interned<Target>) -> Flow {
|
||||||
|
|
@ -108,13 +157,18 @@ impl TargetPathElement {
|
||||||
.expect("field name is known to be a valid field of parent type");
|
.expect("field name is known to be a valid field of parent type");
|
||||||
parent.flow().flip_if(field.flipped)
|
parent.flow().flip_if(field.flipped)
|
||||||
}
|
}
|
||||||
Self::ArrayElement(_) => parent.flow(),
|
Self::ArrayElement(_)
|
||||||
Self::DynArrayElement(_) => parent.flow(),
|
| Self::DynArrayElement(_)
|
||||||
|
| Self::TraceAsStringInner(_)
|
||||||
|
| Self::ToTraceAsString(_) => parent.flow(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_static(&self) -> bool {
|
pub fn is_static(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::BundleField(_) | Self::ArrayElement(_) => true,
|
Self::BundleField(_)
|
||||||
|
| Self::ArrayElement(_)
|
||||||
|
| Self::TraceAsStringInner(_)
|
||||||
|
| Self::ToTraceAsString(_) => true,
|
||||||
Self::DynArrayElement(_) => false,
|
Self::DynArrayElement(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +179,7 @@ macro_rules! impl_target_base {
|
||||||
$(#[$enum_meta:meta])*
|
$(#[$enum_meta:meta])*
|
||||||
$enum_vis:vis enum $TargetBase:ident {
|
$enum_vis:vis enum $TargetBase:ident {
|
||||||
$(
|
$(
|
||||||
|
$(#[from = $from:ident])?
|
||||||
#[is = $is_fn:ident]
|
#[is = $is_fn:ident]
|
||||||
#[to = $to_fn:ident]
|
#[to = $to_fn:ident]
|
||||||
$(#[$variant_meta:meta])*
|
$(#[$variant_meta:meta])*
|
||||||
|
|
@ -148,19 +203,19 @@ macro_rules! impl_target_base {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(
|
$($(
|
||||||
impl From<$VariantTy> for $TargetBase {
|
impl From<$VariantTy> for $TargetBase {
|
||||||
fn from(value: $VariantTy) -> Self {
|
fn $from(value: $VariantTy) -> Self {
|
||||||
Self::$Variant(value)
|
Self::$Variant(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<$VariantTy> for Target {
|
impl From<$VariantTy> for Target {
|
||||||
fn from(value: $VariantTy) -> Self {
|
fn $from(value: $VariantTy) -> Self {
|
||||||
$TargetBase::$Variant(value).into()
|
$TargetBase::$Variant(value).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*)?
|
||||||
|
|
||||||
impl $TargetBase {
|
impl $TargetBase {
|
||||||
$(
|
$(
|
||||||
|
|
@ -191,27 +246,93 @@ macro_rules! impl_target_base {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValueType for $TargetBase {
|
||||||
|
type Type = CanonicalType;
|
||||||
|
type ValueCategory = ValueCategoryExpr;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
match self {
|
||||||
|
$(Self::$Variant(v) => v.ty().canonical(),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToExpr for $TargetBase {
|
||||||
|
fn to_expr(&self) -> Expr<Self::Type> {
|
||||||
|
match self {
|
||||||
|
$(Self::$Variant(v) => Expr::canonical(v.to_expr()),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_target_base! {
|
impl_target_base! {
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum TargetBase {
|
pub enum TargetBase {
|
||||||
|
#[from = from]
|
||||||
#[is = is_module_io]
|
#[is = is_module_io]
|
||||||
#[to = module_io]
|
#[to = module_io]
|
||||||
ModuleIO(ModuleIO<CanonicalType>),
|
ModuleIO(ModuleIO<CanonicalType>),
|
||||||
|
#[from = from]
|
||||||
#[is = is_mem_port]
|
#[is = is_mem_port]
|
||||||
#[to = mem_port]
|
#[to = mem_port]
|
||||||
MemPort(MemPort<DynPortType>),
|
MemPort(MemPort<DynPortType>),
|
||||||
#[is = is_reg]
|
#[is = is_reg]
|
||||||
#[to = reg]
|
#[to = reg]
|
||||||
Reg(Reg<CanonicalType>),
|
Reg(Reg<CanonicalType, Reset>),
|
||||||
|
#[is = is_reg_sync]
|
||||||
|
#[to = reg_sync]
|
||||||
|
RegSync(Reg<CanonicalType, SyncReset>),
|
||||||
|
#[is = is_reg_async]
|
||||||
|
#[to = reg_async]
|
||||||
|
RegAsync(Reg<CanonicalType, AsyncReset>),
|
||||||
|
#[from = from]
|
||||||
#[is = is_wire]
|
#[is = is_wire]
|
||||||
#[to = wire]
|
#[to = wire]
|
||||||
Wire(Wire<CanonicalType>),
|
Wire(Wire<CanonicalType>),
|
||||||
|
#[from = from]
|
||||||
#[is = is_instance]
|
#[is = is_instance]
|
||||||
#[to = instance]
|
#[to = instance]
|
||||||
Instance(Instance<Bundle>),
|
Instance(Instance<Bundle>),
|
||||||
|
#[from = from]
|
||||||
|
#[is = is_formal_input]
|
||||||
|
#[to = formal_input]
|
||||||
|
FormalInput(FormalInput),
|
||||||
|
#[from = from]
|
||||||
|
#[is = is_sim_io_for_global]
|
||||||
|
#[to = sim_io_for_global]
|
||||||
|
SimIoForGlobal(crate::expr::ops::SimIoForGlobal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ResetType> From<Reg<CanonicalType, R>> for TargetBase {
|
||||||
|
fn from(value: Reg<CanonicalType, R>) -> Self {
|
||||||
|
struct Dispatch;
|
||||||
|
impl ResetTypeDispatch for Dispatch {
|
||||||
|
type Input<T: ResetType> = Reg<CanonicalType, T>;
|
||||||
|
type Output<T: ResetType> = TargetBase;
|
||||||
|
|
||||||
|
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
|
||||||
|
TargetBase::Reg(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
|
||||||
|
TargetBase::RegSync(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
|
||||||
|
TargetBase::RegAsync(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R::dispatch(value, Dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: ResetType> From<Reg<CanonicalType, R>> for Target {
|
||||||
|
fn from(value: Reg<CanonicalType, R>) -> Self {
|
||||||
|
TargetBase::from(value).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,8 +348,12 @@ impl TargetBase {
|
||||||
TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None),
|
TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None),
|
||||||
TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())),
|
TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())),
|
||||||
TargetBase::Reg(v) => TargetName(v.scoped_name(), None),
|
TargetBase::Reg(v) => TargetName(v.scoped_name(), None),
|
||||||
|
TargetBase::RegSync(v) => TargetName(v.scoped_name(), None),
|
||||||
|
TargetBase::RegAsync(v) => TargetName(v.scoped_name(), None),
|
||||||
TargetBase::Wire(v) => TargetName(v.scoped_name(), None),
|
TargetBase::Wire(v) => TargetName(v.scoped_name(), None),
|
||||||
TargetBase::Instance(v) => TargetName(v.scoped_name(), None),
|
TargetBase::Instance(v) => TargetName(v.scoped_name(), None),
|
||||||
|
TargetBase::FormalInput(v) => TargetName(v.scoped_name(), None),
|
||||||
|
TargetBase::SimIoForGlobal(v) => TargetName(v.global().scoped_name(), None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn canonical_ty(&self) -> CanonicalType {
|
pub fn canonical_ty(&self) -> CanonicalType {
|
||||||
|
|
@ -236,8 +361,25 @@ impl TargetBase {
|
||||||
TargetBase::ModuleIO(v) => v.ty(),
|
TargetBase::ModuleIO(v) => v.ty(),
|
||||||
TargetBase::MemPort(v) => v.ty().canonical(),
|
TargetBase::MemPort(v) => v.ty().canonical(),
|
||||||
TargetBase::Reg(v) => v.ty(),
|
TargetBase::Reg(v) => v.ty(),
|
||||||
|
TargetBase::RegSync(v) => v.ty(),
|
||||||
|
TargetBase::RegAsync(v) => v.ty(),
|
||||||
TargetBase::Wire(v) => v.ty(),
|
TargetBase::Wire(v) => v.ty(),
|
||||||
TargetBase::Instance(v) => v.ty().canonical(),
|
TargetBase::Instance(v) => v.ty().canonical(),
|
||||||
|
TargetBase::FormalInput(v) => v.ty(),
|
||||||
|
TargetBase::SimIoForGlobal(v) => v.ty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_valid_annotation_target(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ModuleIO(_) => true,
|
||||||
|
Self::MemPort(_) => true,
|
||||||
|
Self::Reg(_) => true,
|
||||||
|
Self::RegSync(_) => true,
|
||||||
|
Self::RegAsync(_) => true,
|
||||||
|
Self::Wire(_) => true,
|
||||||
|
Self::Instance(_) => true,
|
||||||
|
Self::FormalInput(_) => false,
|
||||||
|
Self::SimIoForGlobal(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +390,7 @@ pub struct TargetChild {
|
||||||
path_element: Interned<TargetPathElement>,
|
path_element: Interned<TargetPathElement>,
|
||||||
canonical_ty: CanonicalType,
|
canonical_ty: CanonicalType,
|
||||||
flow: Flow,
|
flow: Flow,
|
||||||
|
canonicalized_if_different: Option<Interned<Target>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for TargetChild {
|
impl fmt::Debug for TargetChild {
|
||||||
|
|
@ -257,6 +400,7 @@ impl fmt::Debug for TargetChild {
|
||||||
path_element,
|
path_element,
|
||||||
canonical_ty: _,
|
canonical_ty: _,
|
||||||
flow: _,
|
flow: _,
|
||||||
|
canonicalized_if_different: _,
|
||||||
} = self;
|
} = self;
|
||||||
parent.fmt(f)?;
|
parent.fmt(f)?;
|
||||||
fmt::Display::fmt(path_element, f)
|
fmt::Display::fmt(path_element, f)
|
||||||
|
|
@ -270,6 +414,7 @@ impl fmt::Display for TargetChild {
|
||||||
path_element,
|
path_element,
|
||||||
canonical_ty: _,
|
canonical_ty: _,
|
||||||
flow: _,
|
flow: _,
|
||||||
|
canonicalized_if_different: _,
|
||||||
} = self;
|
} = self;
|
||||||
parent.fmt(f)?;
|
parent.fmt(f)?;
|
||||||
path_element.fmt(f)
|
path_element.fmt(f)
|
||||||
|
|
@ -277,14 +422,69 @@ impl fmt::Display for TargetChild {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetChild {
|
impl TargetChild {
|
||||||
pub fn new(parent: Interned<Target>, path_element: Interned<TargetPathElement>) -> Self {
|
fn new_helper(
|
||||||
|
parent: Interned<Target>,
|
||||||
|
path_element: Interned<TargetPathElement>,
|
||||||
|
canonicalized_if_different: Option<Interned<Target>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parent,
|
parent,
|
||||||
path_element,
|
path_element,
|
||||||
canonical_ty: path_element.canonical_ty(parent),
|
canonical_ty: path_element.canonical_ty(parent),
|
||||||
flow: path_element.flow(parent),
|
flow: path_element.flow(parent),
|
||||||
|
canonicalized_if_different,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn make_canonicalized_if_different(
|
||||||
|
parent: Interned<Target>,
|
||||||
|
path_element: Interned<TargetPathElement>,
|
||||||
|
) -> Option<Interned<Target>> {
|
||||||
|
use TargetPathElement::*;
|
||||||
|
match *path_element {
|
||||||
|
BundleField(_) => {}
|
||||||
|
ArrayElement(_) => {}
|
||||||
|
DynArrayElement(_) => {}
|
||||||
|
TraceAsStringInner(_) => {
|
||||||
|
if let Some(child) = parent.canonicalized().child() {
|
||||||
|
match *child.path_element() {
|
||||||
|
BundleField(_)
|
||||||
|
| ArrayElement(_)
|
||||||
|
| DynArrayElement(_)
|
||||||
|
| TraceAsStringInner(_) => {}
|
||||||
|
ToTraceAsString(_) => return Some(child.parent()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToTraceAsString(TargetPathToTraceAsString { ty }) => {
|
||||||
|
if let Some(child) = parent.canonicalized().child() {
|
||||||
|
match *child.path_element() {
|
||||||
|
BundleField(_) | ArrayElement(_) | DynArrayElement(_)
|
||||||
|
| ToTraceAsString(_) => {}
|
||||||
|
TraceAsStringInner(_) => {
|
||||||
|
if ty.canonical() == child.parent().canonical_ty() {
|
||||||
|
return Some(child.parent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
Target::Child(Self::new_helper(
|
||||||
|
parent.canonicalized_if_different()?,
|
||||||
|
path_element,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.intern_sized(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn new(parent: Interned<Target>, path_element: Interned<TargetPathElement>) -> Self {
|
||||||
|
Self::new_helper(
|
||||||
|
parent,
|
||||||
|
path_element,
|
||||||
|
Self::make_canonicalized_if_different(parent, path_element),
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn parent(self) -> Interned<Target> {
|
pub fn parent(self) -> Interned<Target> {
|
||||||
self.parent
|
self.parent
|
||||||
}
|
}
|
||||||
|
|
@ -297,6 +497,19 @@ impl TargetChild {
|
||||||
pub fn flow(self) -> Flow {
|
pub fn flow(self) -> Flow {
|
||||||
self.flow
|
self.flow
|
||||||
}
|
}
|
||||||
|
pub fn is_canonicalized(self) -> bool {
|
||||||
|
self.canonicalized_if_different.is_none()
|
||||||
|
}
|
||||||
|
pub fn canonicalized_if_different(self) -> Option<Interned<Target>> {
|
||||||
|
self.canonicalized_if_different
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonicalized(self) -> Target {
|
||||||
|
match self.canonicalized_if_different {
|
||||||
|
Some(v) => *v,
|
||||||
|
None => Target::Child(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn bundle_field(self) -> Option<BundleField> {
|
pub fn bundle_field(self) -> Option<BundleField> {
|
||||||
if let TargetPathElement::BundleField(TargetPathBundleField { name }) = *self.path_element {
|
if let TargetPathElement::BundleField(TargetPathBundleField { name }) = *self.path_element {
|
||||||
let parent_ty = Bundle::from_canonical(self.parent.canonical_ty());
|
let parent_ty = Bundle::from_canonical(self.parent.canonical_ty());
|
||||||
|
|
@ -311,7 +524,7 @@ impl TargetChild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Target {
|
pub enum Target {
|
||||||
Base(Interned<TargetBase>),
|
Base(Interned<TargetBase>),
|
||||||
Child(TargetChild),
|
Child(TargetChild),
|
||||||
|
|
@ -361,6 +574,16 @@ impl Target {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn is_valid_annotation_target(&self) -> bool {
|
||||||
|
let mut target = self;
|
||||||
|
loop {
|
||||||
|
match target {
|
||||||
|
Self::Base(target_base) => return target_base.is_valid_annotation_target(),
|
||||||
|
Self::Child(v) if !v.path_element().is_static() => return false,
|
||||||
|
Self::Child(v) => target = &v.parent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn join(&self, path_element: Interned<TargetPathElement>) -> Self {
|
pub fn join(&self, path_element: Interned<TargetPathElement>) -> Self {
|
||||||
TargetChild::new(self.intern(), path_element).into()
|
TargetChild::new(self.intern(), path_element).into()
|
||||||
|
|
@ -377,6 +600,82 @@ impl Target {
|
||||||
Target::Child(v) => v.canonical_ty(),
|
Target::Child(v) => v.canonical_ty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn is_canonicalized(self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Base(_) => true,
|
||||||
|
Self::Child(child) => child.is_canonicalized(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn canonicalized_if_different(self) -> Option<Interned<Self>> {
|
||||||
|
match self {
|
||||||
|
Self::Base(_) => None,
|
||||||
|
Self::Child(child) => child.canonicalized_if_different(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonicalized(self) -> Target {
|
||||||
|
match self.canonicalized_if_different() {
|
||||||
|
Some(v) => *v,
|
||||||
|
None => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn canonicalized_interned(this: Interned<Target>) -> Interned<Target> {
|
||||||
|
this.canonicalized_if_different().unwrap_or(this)
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn unwrap_transparent_types(mut self) -> Target {
|
||||||
|
loop {
|
||||||
|
self = self.canonicalized();
|
||||||
|
match self.canonical_ty() {
|
||||||
|
CanonicalType::UInt(_)
|
||||||
|
| CanonicalType::SInt(_)
|
||||||
|
| CanonicalType::Bool(_)
|
||||||
|
| CanonicalType::Array(_)
|
||||||
|
| CanonicalType::Enum(_)
|
||||||
|
| CanonicalType::Bundle(_)
|
||||||
|
| CanonicalType::AsyncReset(_)
|
||||||
|
| CanonicalType::SyncReset(_)
|
||||||
|
| CanonicalType::Reset(_)
|
||||||
|
| CanonicalType::Clock(_)
|
||||||
|
| CanonicalType::PhantomConst(_)
|
||||||
|
| CanonicalType::DynSimOnly(_) => return self,
|
||||||
|
CanonicalType::TraceAsString(_) => {
|
||||||
|
if let Self::Child(child) = self
|
||||||
|
&& let TargetPathElement::ToTraceAsString(_) = *child.path_element()
|
||||||
|
{
|
||||||
|
self = *child.parent();
|
||||||
|
} else {
|
||||||
|
self = self.join(TargetPathElement::intern_sized(
|
||||||
|
TargetPathTraceAsStringInner {}.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn unwrap_transparent_types_interned(this: Interned<Target>) -> Interned<Target> {
|
||||||
|
let retval = this.unwrap_transparent_types();
|
||||||
|
if retval != *this {
|
||||||
|
retval.intern_sized()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
pub fn without_trailing_transparent_path_elements(mut self) -> Target {
|
||||||
|
use TargetPathElement::*;
|
||||||
|
loop {
|
||||||
|
match self {
|
||||||
|
Self::Base(_) => return self,
|
||||||
|
Self::Child(child) => match *child.path_element() {
|
||||||
|
BundleField(_) | ArrayElement(_) | DynArrayElement(_) => return self,
|
||||||
|
TraceAsStringInner(_) | ToTraceAsString(_) => self = *child.parent(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Target {
|
impl fmt::Display for Target {
|
||||||
|
|
@ -401,6 +700,18 @@ pub trait GetTarget {
|
||||||
fn target(&self) -> Option<Interned<Target>>;
|
fn target(&self) -> Option<Interned<Target>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GetTarget for Target {
|
||||||
|
fn target(&self) -> Option<Interned<Target>> {
|
||||||
|
Some(self.intern())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTarget for TargetBase {
|
||||||
|
fn target(&self) -> Option<Interned<Target>> {
|
||||||
|
Some(Target::Base(self.intern()).intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GetTarget for bool {
|
impl GetTarget for bool {
|
||||||
fn target(&self) -> Option<Interned<Target>> {
|
fn target(&self) -> Option<Interned<Target>> {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
378
crates/fayalite/src/expr/value_category.rs
Normal file
378
crates/fayalite/src/expr/value_category.rs
Normal file
|
|
@ -0,0 +1,378 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{expr::ValueType, ty::Type};
|
||||||
|
|
||||||
|
trait ValueCategorySealed {}
|
||||||
|
|
||||||
|
#[expect(private_bounds)]
|
||||||
|
pub trait ValueCategory:
|
||||||
|
ValueCategorySealed
|
||||||
|
+ Copy
|
||||||
|
+ Ord
|
||||||
|
+ Default
|
||||||
|
+ std::fmt::Debug
|
||||||
|
+ std::hash::Hash
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
{
|
||||||
|
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>>: ValueType<Type = D::Type, ValueCategory: ValueCategoryIsValueSimValueExprOrValueless>;
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> Self::DispatchOutput<D>;
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> ValueCategoryDispatchOutputEnum<D>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
|
||||||
|
pub struct ValueCategoryValue;
|
||||||
|
|
||||||
|
impl ValueCategorySealed for ValueCategoryValue {}
|
||||||
|
|
||||||
|
impl ValueCategory for ValueCategoryValue {
|
||||||
|
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Value;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> Self::DispatchOutput<D> {
|
||||||
|
dispatch.dispatch_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> ValueCategoryDispatchOutputEnum<D> {
|
||||||
|
ValueCategoryDispatchOutputEnum::Value(dispatch.dispatch_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
|
||||||
|
pub struct ValueCategorySimValue;
|
||||||
|
|
||||||
|
impl ValueCategorySealed for ValueCategorySimValue {}
|
||||||
|
|
||||||
|
impl ValueCategory for ValueCategorySimValue {
|
||||||
|
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::SimValue;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> Self::DispatchOutput<D> {
|
||||||
|
dispatch.dispatch_sim_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> ValueCategoryDispatchOutputEnum<D> {
|
||||||
|
ValueCategoryDispatchOutputEnum::SimValue(dispatch.dispatch_sim_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
|
||||||
|
pub struct ValueCategoryExpr;
|
||||||
|
|
||||||
|
impl ValueCategorySealed for ValueCategoryExpr {}
|
||||||
|
|
||||||
|
impl ValueCategory for ValueCategoryExpr {
|
||||||
|
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Expr;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> Self::DispatchOutput<D> {
|
||||||
|
dispatch.dispatch_expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> ValueCategoryDispatchOutputEnum<D> {
|
||||||
|
ValueCategoryDispatchOutputEnum::Expr(dispatch.dispatch_expr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
|
||||||
|
pub struct ValueCategoryValueless;
|
||||||
|
|
||||||
|
impl ValueCategorySealed for ValueCategoryValueless {}
|
||||||
|
|
||||||
|
impl ValueCategory for ValueCategoryValueless {
|
||||||
|
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Valueless;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> Self::DispatchOutput<D> {
|
||||||
|
dispatch.dispatch_valueless()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
|
||||||
|
dispatch: D,
|
||||||
|
) -> ValueCategoryDispatchOutputEnum<D> {
|
||||||
|
ValueCategoryDispatchOutputEnum::Valueless(dispatch.dispatch_valueless())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsValue: ValueCategoryIsValueOrSimValue {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsValue for ValueCategoryValue {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsValueOrSimValue: ValueCategoryIsValueSimValueOrExpr {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsValueOrSimValue for ValueCategoryValue {}
|
||||||
|
impl ValueCategoryIsValueOrSimValue for ValueCategorySimValue {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsValueSimValueOrExpr: ValueCategoryIsValueSimValueExprOrValueless {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsValueSimValueOrExpr for ValueCategoryValue {}
|
||||||
|
impl ValueCategoryIsValueSimValueOrExpr for ValueCategorySimValue {}
|
||||||
|
impl ValueCategoryIsValueSimValueOrExpr for ValueCategoryExpr {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsValueSimValueExprOrValueless: ValueCategory {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryValue {}
|
||||||
|
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategorySimValue {}
|
||||||
|
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryExpr {}
|
||||||
|
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryValueless {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsValueless: ValueCategoryIsExprOrValueless {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsValueless for ValueCategoryValueless {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsExprOrValueless: ValueCategoryIsValueSimValueExprOrValueless {}
|
||||||
|
|
||||||
|
impl ValueCategoryIsExprOrValueless for ValueCategoryExpr {}
|
||||||
|
impl ValueCategoryIsExprOrValueless for ValueCategoryValueless {}
|
||||||
|
|
||||||
|
pub trait ValueCategoryIsSimValueExprOrValueless:
|
||||||
|
ValueCategoryIsValueSimValueExprOrValueless
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueCategoryIsSimValueExprOrValueless for ValueCategorySimValue {}
|
||||||
|
impl ValueCategoryIsSimValueExprOrValueless for ValueCategoryExpr {}
|
||||||
|
impl ValueCategoryIsSimValueExprOrValueless for ValueCategoryValueless {}
|
||||||
|
|
||||||
|
trait ValueCategoryCommonSealed {}
|
||||||
|
|
||||||
|
#[expect(private_bounds)]
|
||||||
|
pub trait ValueCategoryCommon<
|
||||||
|
T: ValueCategoryCommonSealed
|
||||||
|
+ Copy
|
||||||
|
+ Ord
|
||||||
|
+ Default
|
||||||
|
+ std::fmt::Debug
|
||||||
|
+ std::hash::Hash
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
>: ValueCategory
|
||||||
|
{
|
||||||
|
type Common: ValueCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_value_category_common {
|
||||||
|
($($T:ident),* $(,)?) => {
|
||||||
|
impl_value_category_common!([$($T,)*]);
|
||||||
|
};
|
||||||
|
([$($A:ident,)?]) => {};
|
||||||
|
([$FirstT:ident, $($T:ident,)+]) => {
|
||||||
|
impl_value_category_common!([$($T,)*]);
|
||||||
|
|
||||||
|
impl<$FirstT: ValueCategory, $($T: ValueCategory),*> ValueCategoryCommonSealed for ($FirstT, $($T),*) {}
|
||||||
|
|
||||||
|
impl<This, $FirstT, $($T),*> ValueCategoryCommon<($FirstT, $($T),*)> for This
|
||||||
|
where
|
||||||
|
$FirstT: ValueCategory,
|
||||||
|
$($T: ValueCategory,)*
|
||||||
|
This: ValueCategoryCommon<($FirstT,), Common: ValueCategoryCommon<($($T,)*)>>,
|
||||||
|
{
|
||||||
|
type Common = <<This as ValueCategoryCommon<($FirstT,)>>::Common as ValueCategoryCommon<($($T,)*)>>::Common;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_value_category_common!(T11, T10, T9, T8, T7, T6, T5, T4, T3, T2, T1, T0);
|
||||||
|
|
||||||
|
impl<T: ValueCategory> ValueCategoryCommonSealed for (T,) {}
|
||||||
|
|
||||||
|
impl ValueCategoryCommonSealed for () {}
|
||||||
|
|
||||||
|
impl<T: ValueCategory> ValueCategoryCommon<()> for T {
|
||||||
|
type Common = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValue> ValueCategoryCommon<(ValueCategoryValue,)> for T {
|
||||||
|
type Common = ValueCategoryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValueOrSimValue> ValueCategoryCommon<(ValueCategorySimValue,)> for T {
|
||||||
|
type Common = ValueCategorySimValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValue> ValueCategoryCommon<(T,)> for ValueCategorySimValue {
|
||||||
|
type Common = ValueCategorySimValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValueSimValueOrExpr> ValueCategoryCommon<(ValueCategoryExpr,)> for T {
|
||||||
|
type Common = ValueCategoryExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValueOrSimValue> ValueCategoryCommon<(T,)> for ValueCategoryExpr {
|
||||||
|
type Common = ValueCategoryExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValueSimValueExprOrValueless> ValueCategoryCommon<(ValueCategoryValueless,)>
|
||||||
|
for T
|
||||||
|
{
|
||||||
|
type Common = ValueCategoryValueless;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryIsValueSimValueOrExpr> ValueCategoryCommon<(T,)> for ValueCategoryValueless {
|
||||||
|
type Common = ValueCategoryValueless;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ValueCategoryDispatch: Sized {
|
||||||
|
type Type: Type;
|
||||||
|
type InputValueCategory: ValueCategory;
|
||||||
|
type Value: ValueType<ValueCategory: ValueCategoryIsValueSimValueExprOrValueless, Type = Self::Type>;
|
||||||
|
type SimValue: ValueType<ValueCategory: ValueCategoryIsSimValueExprOrValueless, Type = Self::Type>;
|
||||||
|
type Expr: ValueType<ValueCategory: ValueCategoryIsExprOrValueless, Type = Self::Type>;
|
||||||
|
type Valueless: ValueType<ValueCategory: ValueCategoryIsValueless, Type = Self::Type>;
|
||||||
|
fn dispatch_value(self) -> Self::Value
|
||||||
|
where
|
||||||
|
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryValue>;
|
||||||
|
fn dispatch_sim_value(self) -> Self::SimValue
|
||||||
|
where
|
||||||
|
Self: ValueCategoryDispatch<InputValueCategory = ValueCategorySimValue>;
|
||||||
|
fn dispatch_expr(self) -> Self::Expr
|
||||||
|
where
|
||||||
|
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryExpr>;
|
||||||
|
fn dispatch_valueless(self) -> Self::Valueless
|
||||||
|
where
|
||||||
|
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryValueless>;
|
||||||
|
fn dispatch(self) -> <Self::InputValueCategory as ValueCategory>::DispatchOutput<Self> {
|
||||||
|
Self::InputValueCategory::dispatch(self)
|
||||||
|
}
|
||||||
|
fn dispatch_enum(self) -> ValueCategoryDispatchOutputEnum<Self> {
|
||||||
|
Self::InputValueCategory::dispatch_enum(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ValueCategoryDispatchOutputEnum<T: ValueCategoryDispatch> {
|
||||||
|
Value(T::Value),
|
||||||
|
SimValue(T::SimValue),
|
||||||
|
Expr(T::Expr),
|
||||||
|
Valueless(T::Valueless),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ValueCategoryDispatch> ValueCategoryDispatchOutputEnum<T> {
|
||||||
|
pub fn try_into_value(self) -> Result<T::Value, Self> {
|
||||||
|
if let Self::Value(v) = self {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_into_sim_value(self) -> Result<T::SimValue, Self> {
|
||||||
|
if let Self::SimValue(v) = self {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_into_expr(self) -> Result<T::Expr, Self> {
|
||||||
|
if let Self::Expr(v) = self {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn try_into_valueless(self) -> Result<T::Valueless, Self> {
|
||||||
|
if let Self::Valueless(v) = self {
|
||||||
|
Ok(v)
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
#[cold]
|
||||||
|
fn unwrap_invalid(self, expected_kind: &'static str) -> ! {
|
||||||
|
match self {
|
||||||
|
Self::Value(_) => panic!("expected {expected_kind}, got Value"),
|
||||||
|
Self::SimValue(_) => panic!("expected {expected_kind}, got SimValue"),
|
||||||
|
Self::Expr(_) => panic!("expected {expected_kind}, got Expr"),
|
||||||
|
Self::Valueless(_) => panic!("expected {expected_kind}, got Valueless"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn into_value_unwrap(self) -> T::Value {
|
||||||
|
let Self::Value(v) = self else {
|
||||||
|
self.unwrap_invalid("Value");
|
||||||
|
};
|
||||||
|
v
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn into_sim_value_unwrap(self) -> T::SimValue {
|
||||||
|
let Self::SimValue(v) = self else {
|
||||||
|
self.unwrap_invalid("SimValue");
|
||||||
|
};
|
||||||
|
v
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn into_expr_unwrap(self) -> T::Expr {
|
||||||
|
let Self::Expr(v) = self else {
|
||||||
|
self.unwrap_invalid("Expr");
|
||||||
|
};
|
||||||
|
v
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn into_valueless_unwrap(self) -> T::Valueless {
|
||||||
|
let Self::Valueless(v) = self else {
|
||||||
|
self.unwrap_invalid("Valueless");
|
||||||
|
};
|
||||||
|
v
|
||||||
|
}
|
||||||
|
pub fn ty(&self) -> T::Type {
|
||||||
|
match self {
|
||||||
|
Self::Value(v) => v.ty(),
|
||||||
|
Self::SimValue(v) => v.ty(),
|
||||||
|
Self::Expr(v) => v.ty(),
|
||||||
|
Self::Valueless(v) => v.ty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
type Identity<T> = T;
|
||||||
|
|
||||||
|
macro_rules! check_categories {
|
||||||
|
($($Categories:ident,)+) => {
|
||||||
|
check_categories!([] [$($Categories,)+]);
|
||||||
|
};
|
||||||
|
([$($Categories:ident,)*] []) => {};
|
||||||
|
([$($WeakerCategories:ident,)*] [$Category:ident, $($StrongerCategories:ident,)*]) => {
|
||||||
|
$(const _: $Category = Identity::<<$WeakerCategories as ValueCategoryCommon<($Category,)>>::Common> {};
|
||||||
|
const _: $Category = Identity::<<$Category as ValueCategoryCommon<($WeakerCategories,)>>::Common> {};)*
|
||||||
|
const _: $Category = Identity::<<$Category as ValueCategoryCommon<($Category,)>>::Common> {};
|
||||||
|
check_categories!([$($WeakerCategories,)* $Category,] [$($StrongerCategories,)*]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
check_categories!(
|
||||||
|
ValueCategoryValue,
|
||||||
|
ValueCategorySimValue,
|
||||||
|
ValueCategoryExpr,
|
||||||
|
ValueCategoryValueless,
|
||||||
|
);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
391
crates/fayalite/src/formal.rs
Normal file
391
crates/fayalite/src/formal.rs
Normal file
|
|
@ -0,0 +1,391 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
use crate::{
|
||||||
|
expr::target::{GetTarget, Target},
|
||||||
|
int::BoolOrIntType,
|
||||||
|
intern::{Intern, Interned},
|
||||||
|
module::{NameId, NameIdOrGlobal, ScopedNameId},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use std::{fmt, sync::OnceLock};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum FormalInputKind {
|
||||||
|
FormalGlobalClock,
|
||||||
|
FormalReset,
|
||||||
|
AnyConst,
|
||||||
|
AnySeq,
|
||||||
|
AllConst,
|
||||||
|
AllSeq,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalInputKind {
|
||||||
|
pub fn fixed_ty(self) -> Option<CanonicalType> {
|
||||||
|
match self {
|
||||||
|
Self::FormalGlobalClock => Some(Clock.into()),
|
||||||
|
Self::FormalReset => Some(SyncReset.into()),
|
||||||
|
Self::AnyConst => None,
|
||||||
|
Self::AnySeq => None,
|
||||||
|
Self::AllConst => None,
|
||||||
|
Self::AllSeq => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fixed_id(self) -> Option<crate::module::Id> {
|
||||||
|
struct Cache {
|
||||||
|
formal_global_clock: crate::module::Id,
|
||||||
|
formal_reset: crate::module::Id,
|
||||||
|
}
|
||||||
|
static CACHE: OnceLock<Cache> = OnceLock::new();
|
||||||
|
let cache = || {
|
||||||
|
CACHE.get_or_init(
|
||||||
|
#[cold]
|
||||||
|
|| Cache {
|
||||||
|
formal_global_clock: crate::module::Id::new(),
|
||||||
|
formal_reset: crate::module::Id::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
Self::FormalGlobalClock => Some(cache().formal_global_clock),
|
||||||
|
Self::FormalReset => Some(cache().formal_reset),
|
||||||
|
Self::AnyConst => None,
|
||||||
|
Self::AnySeq => None,
|
||||||
|
Self::AllConst => None,
|
||||||
|
Self::AllSeq => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fixed_source_location(self) -> Option<SourceLocation> {
|
||||||
|
match self {
|
||||||
|
Self::FormalGlobalClock | Self::FormalReset => Some(SourceLocation::builtin()),
|
||||||
|
Self::AnyConst | Self::AnySeq | Self::AllConst | Self::AllSeq => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::FormalGlobalClock => "formal_global_clock",
|
||||||
|
Self::FormalReset => "formal_reset",
|
||||||
|
Self::AnyConst => "any_const",
|
||||||
|
Self::AnySeq => "any_seq",
|
||||||
|
Self::AllConst => "all_const",
|
||||||
|
Self::AllSeq => "all_seq",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn interned_name(self) -> Interned<str> {
|
||||||
|
macro_rules! impl_interned_name {
|
||||||
|
($($variant:ident,)*) => {
|
||||||
|
match self {
|
||||||
|
$(Self::$variant => {
|
||||||
|
static CACHE: OnceLock<Interned<str>> = OnceLock::new();
|
||||||
|
*CACHE.get_or_init(|| Self::$variant.name().intern())
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_interned_name! {
|
||||||
|
FormalGlobalClock,
|
||||||
|
FormalReset,
|
||||||
|
AnyConst,
|
||||||
|
AnySeq,
|
||||||
|
AllConst,
|
||||||
|
AllSeq,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct FormalInputData {
|
||||||
|
kind: FormalInputKind,
|
||||||
|
name_id: NameId,
|
||||||
|
ty: CanonicalType,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FormalInput(Interned<FormalInputData>);
|
||||||
|
|
||||||
|
impl fmt::Debug for FormalInput {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.kind().fixed_ty().is_some() {
|
||||||
|
f.write_str(&self.name())
|
||||||
|
} else {
|
||||||
|
f.debug_tuple(&self.name()).field(&self.0.ty).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalInput {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(
|
||||||
|
kind: FormalInputKind,
|
||||||
|
name_id: NameId,
|
||||||
|
ty: CanonicalType,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) -> Self {
|
||||||
|
let NameId(name, id) = name_id;
|
||||||
|
assert_eq!(kind.interned_name(), name);
|
||||||
|
if let Some(fixed_ty) = kind.fixed_ty() {
|
||||||
|
assert_eq!(ty, fixed_ty);
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
ty.is_castable_from_bits(),
|
||||||
|
"{name} type must be castable from bits. got:\n{ty:#?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(fixed_source_location) = kind.fixed_source_location() {
|
||||||
|
assert_eq!(source_location, fixed_source_location);
|
||||||
|
}
|
||||||
|
if let Some(fixed_id) = kind.fixed_id() {
|
||||||
|
assert_eq!(id, fixed_id);
|
||||||
|
}
|
||||||
|
Self(
|
||||||
|
FormalInputData {
|
||||||
|
kind,
|
||||||
|
name_id,
|
||||||
|
ty,
|
||||||
|
source_location,
|
||||||
|
}
|
||||||
|
.intern_sized(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn kind(self) -> FormalInputKind {
|
||||||
|
self.0.kind
|
||||||
|
}
|
||||||
|
pub fn name(self) -> Interned<str> {
|
||||||
|
self.0.name_id.0
|
||||||
|
}
|
||||||
|
pub fn name_id(self) -> NameId {
|
||||||
|
self.0.name_id
|
||||||
|
}
|
||||||
|
pub fn scoped_name(self) -> ScopedNameId {
|
||||||
|
ScopedNameId(NameIdOrGlobal::Global, self.name_id())
|
||||||
|
}
|
||||||
|
pub fn source_location(self) -> SourceLocation {
|
||||||
|
self.0.source_location
|
||||||
|
}
|
||||||
|
pub(crate) fn must_connect_to(self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub(crate) fn flow(self) -> crate::expr::Flow {
|
||||||
|
crate::expr::Flow::Source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueType for FormalInput {
|
||||||
|
type Type = CanonicalType;
|
||||||
|
type ValueCategory = crate::expr::value_category::ValueCategoryExpr;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
self.0.ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTarget for FormalInput {
|
||||||
|
fn target(&self) -> Option<Interned<Target>> {
|
||||||
|
Some(Target::from(*self).intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum FormalKind {
|
||||||
|
Assert,
|
||||||
|
Assume,
|
||||||
|
Cover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalKind {
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Assert => "assert",
|
||||||
|
Self::Assume => "assume",
|
||||||
|
Self::Cover => "cover",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn formal_stmt_with_enable_and_loc(
|
||||||
|
kind: FormalKind,
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
en: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) {
|
||||||
|
crate::module::add_stmt_formal(crate::module::StmtFormal {
|
||||||
|
kind,
|
||||||
|
clk,
|
||||||
|
pred,
|
||||||
|
en: en & !formal_reset().cast_to_static::<Bool>(),
|
||||||
|
text: text.intern(),
|
||||||
|
source_location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn formal_stmt_with_enable(
|
||||||
|
kind: FormalKind,
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
en: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
) {
|
||||||
|
formal_stmt_with_enable_and_loc(kind, clk, pred, en, text, SourceLocation::caller());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn formal_stmt_with_loc(
|
||||||
|
kind: FormalKind,
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) {
|
||||||
|
formal_stmt_with_enable_and_loc(kind, clk, pred, true.to_expr(), text, source_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn formal_stmt(kind: FormalKind, clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
|
||||||
|
formal_stmt_with_loc(kind, clk, pred, text, SourceLocation::caller());
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_formal {
|
||||||
|
($kind:ident, $formal_stmt_with_enable_and_loc:ident, $formal_stmt_with_enable:ident, $formal_stmt_with_loc:ident, $formal_stmt:ident) => {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn $formal_stmt_with_enable_and_loc(
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
en: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) {
|
||||||
|
formal_stmt_with_enable_and_loc(
|
||||||
|
FormalKind::$kind,
|
||||||
|
clk,
|
||||||
|
pred,
|
||||||
|
en,
|
||||||
|
text,
|
||||||
|
source_location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn $formal_stmt_with_enable(
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
en: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
) {
|
||||||
|
formal_stmt_with_enable(FormalKind::$kind, clk, pred, en, text);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn $formal_stmt_with_loc(
|
||||||
|
clk: Expr<Clock>,
|
||||||
|
pred: Expr<Bool>,
|
||||||
|
text: &str,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) {
|
||||||
|
formal_stmt_with_loc(FormalKind::$kind, clk, pred, text, source_location);
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn $formal_stmt(clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
|
||||||
|
formal_stmt(FormalKind::$kind, clk, pred, text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_formal!(
|
||||||
|
Assert,
|
||||||
|
hdl_assert_with_enable_and_loc,
|
||||||
|
hdl_assert_with_enable,
|
||||||
|
hdl_assert_with_loc,
|
||||||
|
hdl_assert
|
||||||
|
);
|
||||||
|
|
||||||
|
make_formal!(
|
||||||
|
Assume,
|
||||||
|
hdl_assume_with_enable_and_loc,
|
||||||
|
hdl_assume_with_enable,
|
||||||
|
hdl_assume_with_loc,
|
||||||
|
hdl_assume
|
||||||
|
);
|
||||||
|
|
||||||
|
make_formal!(
|
||||||
|
Cover,
|
||||||
|
hdl_cover_with_enable_and_loc,
|
||||||
|
hdl_cover_with_enable,
|
||||||
|
hdl_cover_with_loc,
|
||||||
|
hdl_cover
|
||||||
|
);
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
pub fn formal_global_clock() -> Expr<Clock> {
|
||||||
|
static CACHE: OnceLock<Expr<Clock>> = OnceLock::new();
|
||||||
|
*CACHE.get_or_init(|| {
|
||||||
|
let kind = FormalInputKind::FormalGlobalClock;
|
||||||
|
Expr::from_canonical(
|
||||||
|
FormalInput::new(
|
||||||
|
kind,
|
||||||
|
NameId(
|
||||||
|
kind.interned_name(),
|
||||||
|
kind.fixed_id().expect("known to have a fixed Id"),
|
||||||
|
),
|
||||||
|
Clock.into(),
|
||||||
|
kind.fixed_source_location()
|
||||||
|
.expect("known to have a fixed SourceLocation"),
|
||||||
|
)
|
||||||
|
.to_expr(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
pub fn formal_reset() -> Expr<SyncReset> {
|
||||||
|
static CACHE: OnceLock<Expr<SyncReset>> = OnceLock::new();
|
||||||
|
*CACHE.get_or_init(|| {
|
||||||
|
let kind = FormalInputKind::FormalReset;
|
||||||
|
Expr::from_canonical(
|
||||||
|
FormalInput::new(
|
||||||
|
kind,
|
||||||
|
NameId(
|
||||||
|
kind.interned_name(),
|
||||||
|
kind.fixed_id().expect("known to have a fixed Id"),
|
||||||
|
),
|
||||||
|
SyncReset.into(),
|
||||||
|
kind.fixed_source_location()
|
||||||
|
.expect("known to have a fixed SourceLocation"),
|
||||||
|
)
|
||||||
|
.to_expr(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! make_any_const_fn {
|
||||||
|
($ident:ident, $ident_with_loc:ident, $verilog_attribute:literal, $kind:ident) => {
|
||||||
|
#[track_caller]
|
||||||
|
#[hdl]
|
||||||
|
pub fn $ident<T: BoolOrIntType>(ty: T) -> Expr<T> {
|
||||||
|
$ident_with_loc(ty, SourceLocation::caller())
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
#[hdl]
|
||||||
|
pub fn $ident_with_loc<T: BoolOrIntType>(
|
||||||
|
ty: T,
|
||||||
|
source_location: SourceLocation,
|
||||||
|
) -> Expr<T> {
|
||||||
|
let kind = FormalInputKind::$kind;
|
||||||
|
Expr::from_canonical(
|
||||||
|
FormalInput::new(
|
||||||
|
kind,
|
||||||
|
NameId(kind.interned_name(), crate::module::Id::new()),
|
||||||
|
ty.canonical(),
|
||||||
|
source_location,
|
||||||
|
)
|
||||||
|
.to_expr(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
make_any_const_fn!(any_const, any_const_with_loc, "anyconst", AnyConst);
|
||||||
|
make_any_const_fn!(any_seq, any_seq_with_loc, "anyseq", AnySeq);
|
||||||
|
make_any_const_fn!(all_const, all_const_with_loc, "allconst", AllConst);
|
||||||
|
make_any_const_fn!(all_seq, all_seq_with_loc, "allseq", AllSeq);
|
||||||
File diff suppressed because it is too large
Load diff
833
crates/fayalite/src/int/uint_in_range.rs
Normal file
833
crates/fayalite/src/int/uint_in_range.rs
Normal file
|
|
@ -0,0 +1,833 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder},
|
||||||
|
expr::{
|
||||||
|
CastBitsTo, CastTo, CastToBits, CastToImpl, Expr, HdlPartialEq, HdlPartialEqImpl,
|
||||||
|
HdlPartialOrd, HdlPartialOrdImpl,
|
||||||
|
},
|
||||||
|
int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType, UIntValue},
|
||||||
|
intern::{Intern, InternSlice, Interned},
|
||||||
|
phantom_const::PhantomConst,
|
||||||
|
sim::value::{SimValue, ToSimValueWithType},
|
||||||
|
source_location::SourceLocation,
|
||||||
|
ty::{
|
||||||
|
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
|
||||||
|
SimValueDebug, StaticType, Type, TypeProperties, impl_match_variant_as_self,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use bitvec::{order::Lsb0, view::BitView};
|
||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::{Error, Visitor, value::UsizeDeserializer},
|
||||||
|
};
|
||||||
|
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Index};
|
||||||
|
|
||||||
|
const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"];
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
|
||||||
|
pub struct UIntInRangeMaskType {
|
||||||
|
value: Bool,
|
||||||
|
range: PhantomConstRangeMaskType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type for UIntInRangeMaskType {
|
||||||
|
type BaseType = Bundle;
|
||||||
|
type MaskType = Self;
|
||||||
|
type SimValue = bool;
|
||||||
|
impl_match_variant_as_self!();
|
||||||
|
|
||||||
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical(&self) -> CanonicalType {
|
||||||
|
CanonicalType::Bundle(Bundle::new(self.fields()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
||||||
|
let fields = Bundle::from_canonical(canonical_type).fields();
|
||||||
|
let [
|
||||||
|
BundleField {
|
||||||
|
name: value_name,
|
||||||
|
flipped: false,
|
||||||
|
ty: value,
|
||||||
|
},
|
||||||
|
BundleField {
|
||||||
|
name: range_name,
|
||||||
|
flipped: false,
|
||||||
|
ty: range,
|
||||||
|
},
|
||||||
|
] = *fields
|
||||||
|
else {
|
||||||
|
panic!("expected UIntInRangeMaskType");
|
||||||
|
};
|
||||||
|
assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES);
|
||||||
|
let value = Bool::from_canonical(value);
|
||||||
|
let range = PhantomConstRangeMaskType::from_canonical(range);
|
||||||
|
Self { value, range }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_location() -> SourceLocation {
|
||||||
|
SourceLocation::builtin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
Bool.sim_value_from_opaque(opaque)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
Bool.sim_value_clone_from_opaque(value, opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
Bool.sim_value_to_opaque(value, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDebug for UIntInRangeMaskType {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BundleType for UIntInRangeMaskType {
|
||||||
|
type Builder = NoBuilder;
|
||||||
|
|
||||||
|
fn fields(&self) -> Interned<[BundleField]> {
|
||||||
|
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
|
||||||
|
let Self { value, range } = self;
|
||||||
|
[
|
||||||
|
BundleField {
|
||||||
|
name: value_name.intern(),
|
||||||
|
flipped: false,
|
||||||
|
ty: value.canonical(),
|
||||||
|
},
|
||||||
|
BundleField {
|
||||||
|
name: range_name.intern(),
|
||||||
|
flipped: false,
|
||||||
|
ty: range.canonical(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticType for UIntInRangeMaskType {
|
||||||
|
const TYPE: Self = Self {
|
||||||
|
value: Bool,
|
||||||
|
range: PhantomConstRangeMaskType::TYPE,
|
||||||
|
};
|
||||||
|
const MASK_TYPE: Self::MaskType = Self::TYPE;
|
||||||
|
const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new()
|
||||||
|
.field(false, Bool::TYPE_PROPERTIES)
|
||||||
|
.field(false, PhantomConstRangeMaskType::TYPE_PROPERTIES)
|
||||||
|
.finish();
|
||||||
|
const MASK_TYPE_PROPERTIES: TypeProperties = Self::TYPE_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSimValueWithType<UIntInRangeMaskType> for bool {
|
||||||
|
fn to_sim_value_with_type(&self, ty: UIntInRangeMaskType) -> SimValue<UIntInRangeMaskType> {
|
||||||
|
SimValue::from_value(ty, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CastToImpl<Bool> for UIntInRangeMaskType {
|
||||||
|
type ValueOutput = bool;
|
||||||
|
|
||||||
|
fn cast_value_to(
|
||||||
|
_this: Self,
|
||||||
|
value: Cow<'_, Self::SimValue>,
|
||||||
|
_to_type: Bool,
|
||||||
|
) -> Self::ValueOutput {
|
||||||
|
*value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_expr_to(value: Expr<Self>, to_type: Bool) -> Expr<Bool> {
|
||||||
|
value.cast_to_bits().cast_to(to_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CastToImpl<UIntInRangeMaskType> for Bool {
|
||||||
|
type ValueOutput = SimValue<UIntInRangeMaskType>;
|
||||||
|
|
||||||
|
fn cast_value_to(
|
||||||
|
_this: Self,
|
||||||
|
value: Cow<'_, Self::SimValue>,
|
||||||
|
to_type: UIntInRangeMaskType,
|
||||||
|
) -> Self::ValueOutput {
|
||||||
|
SimValue::from_value(to_type, *value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_expr_to(value: Expr<Self>, to_type: UIntInRangeMaskType) -> Expr<UIntInRangeMaskType> {
|
||||||
|
value.cast_to_static::<UInt<1>>().cast_bits_to(to_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HdlPartialEqImpl<Self> for UIntInRangeMaskType {
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = true;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_eq(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: Self,
|
||||||
|
rhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
*lhs_value == *rhs_value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhantomConstRangeMaskType = <PhantomConst<SerdeRange<DynSize, DynSize>> as Type>::MaskType;
|
||||||
|
|
||||||
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
|
struct RangeParseError;
|
||||||
|
|
||||||
|
macro_rules! define_uint_in_range_type {
|
||||||
|
(
|
||||||
|
$UIntInRange:ident,
|
||||||
|
$UIntInRangeType:ident,
|
||||||
|
$UIntInRangeTypeWithoutGenerics:ident,
|
||||||
|
$UIntInRangeTypeWithStart:ident,
|
||||||
|
$SerdeRange:ident,
|
||||||
|
$range_operator_str:literal,
|
||||||
|
|$uint_range_usize_start:ident, $uint_range_usize_end:ident| $uint_range_usize:expr,
|
||||||
|
) => {
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
struct $SerdeRange<Start: Size, End: Size> {
|
||||||
|
start: Start::SizeType,
|
||||||
|
end: End::SizeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: KnownSize, End: KnownSize> Default for $SerdeRange<Start, End> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
start: Start::SIZE,
|
||||||
|
end: End::SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for $SerdeRange<DynSize, DynSize> {
|
||||||
|
type Err = RangeParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let Some((start, end)) = s.split_once($range_operator_str) else {
|
||||||
|
return Err(RangeParseError);
|
||||||
|
};
|
||||||
|
if start.is_empty()
|
||||||
|
|| start.bytes().any(|b| !b.is_ascii_digit())
|
||||||
|
|| end.is_empty()
|
||||||
|
|| end.bytes().any(|b| !b.is_ascii_digit())
|
||||||
|
{
|
||||||
|
return Err(RangeParseError);
|
||||||
|
}
|
||||||
|
let start = start.parse().map_err(|_| RangeParseError)?;
|
||||||
|
let end = end.parse().map_err(|_| RangeParseError)?;
|
||||||
|
let retval = Self { start, end };
|
||||||
|
if retval.is_empty() {
|
||||||
|
Err(RangeParseError)
|
||||||
|
} else {
|
||||||
|
Ok(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> fmt::Display for $SerdeRange<Start, End> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self { start, end } = *self;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}{}",
|
||||||
|
Start::as_usize(start),
|
||||||
|
$range_operator_str,
|
||||||
|
End::as_usize(end),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> Serialize for $SerdeRange<Start, End> {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.collect_str(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, Start: Size, End: Size> Deserialize<'de> for $SerdeRange<Start, End> {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
struct SerdeRangeVisitor<Start: Size, End: Size>(PhantomData<(Start, End)>);
|
||||||
|
impl<'de, Start: Size, End: Size> Visitor<'de> for SerdeRangeVisitor<Start, End> {
|
||||||
|
type Value = $SerdeRange<Start, End>;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("a string with format \"")?;
|
||||||
|
if let Some(start) = Start::KNOWN_VALUE {
|
||||||
|
write!(f, "{start}")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("<int>")?;
|
||||||
|
};
|
||||||
|
f.write_str($range_operator_str)?;
|
||||||
|
if let Some(end) = End::KNOWN_VALUE {
|
||||||
|
write!(f, "{end}")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("<int>")?;
|
||||||
|
};
|
||||||
|
f.write_str("\" that is a non-empty range")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
let $SerdeRange::<DynSize, DynSize> { start, end } =
|
||||||
|
v.parse().map_err(|_| {
|
||||||
|
Error::invalid_value(serde::de::Unexpected::Str(v), &self)
|
||||||
|
})?;
|
||||||
|
let start =
|
||||||
|
Start::SizeType::deserialize(UsizeDeserializer::<E>::new(start))?;
|
||||||
|
let end = End::SizeType::deserialize(UsizeDeserializer::<E>::new(end))?;
|
||||||
|
Ok($SerdeRange { start, end })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
|
||||||
|
match std::str::from_utf8(v) {
|
||||||
|
Ok(v) => self.visit_str(v),
|
||||||
|
Err(_) => {
|
||||||
|
Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deserializer.deserialize_str(SerdeRangeVisitor(PhantomData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct $UIntInRangeType<Start: Size, End: Size> {
|
||||||
|
value: UInt,
|
||||||
|
range: PhantomConst<$SerdeRange<Start, End>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> $UIntInRangeType<Start, End> {
|
||||||
|
fn from_phantom_const_range(range: PhantomConst<$SerdeRange<Start, End>>) -> Self {
|
||||||
|
let $SerdeRange { start, end } = *range.get();
|
||||||
|
let $uint_range_usize_start = Start::as_usize(start);
|
||||||
|
let $uint_range_usize_end = End::as_usize(end);
|
||||||
|
Self {
|
||||||
|
value: $uint_range_usize,
|
||||||
|
range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new(start: Start::SizeType, end: End::SizeType) -> Self {
|
||||||
|
Self::from_phantom_const_range(PhantomConst::new_sized($SerdeRange { start, end }))
|
||||||
|
}
|
||||||
|
pub fn bit_width(self) -> usize {
|
||||||
|
self.value.width()
|
||||||
|
}
|
||||||
|
pub fn start(self) -> Start::SizeType {
|
||||||
|
self.range.get().start
|
||||||
|
}
|
||||||
|
pub fn end(self) -> End::SizeType {
|
||||||
|
self.range.get().end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> SimValueDebug for $UIntInRangeType<Start, End> {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> fmt::Debug for $UIntInRangeType<Start, End> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self { value, range } = self;
|
||||||
|
let $SerdeRange { start, end } = *range.get();
|
||||||
|
f.debug_struct(&format!(
|
||||||
|
"{}<{}, {}>",
|
||||||
|
stringify!($UIntInRange),
|
||||||
|
Start::as_usize(start),
|
||||||
|
End::as_usize(end),
|
||||||
|
))
|
||||||
|
.field("value", value)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> Type for $UIntInRangeType<Start, End> {
|
||||||
|
type BaseType = Bundle;
|
||||||
|
type MaskType = UIntInRangeMaskType;
|
||||||
|
type SimValue = usize;
|
||||||
|
impl_match_variant_as_self!();
|
||||||
|
|
||||||
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
|
UIntInRangeMaskType::TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical(&self) -> CanonicalType {
|
||||||
|
CanonicalType::Bundle(Bundle::new(self.fields()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
||||||
|
let fields = Bundle::from_canonical(canonical_type).fields();
|
||||||
|
let [
|
||||||
|
BundleField {
|
||||||
|
name: value_name,
|
||||||
|
flipped: false,
|
||||||
|
ty: value,
|
||||||
|
},
|
||||||
|
BundleField {
|
||||||
|
name: range_name,
|
||||||
|
flipped: false,
|
||||||
|
ty: range,
|
||||||
|
},
|
||||||
|
] = *fields
|
||||||
|
else {
|
||||||
|
panic!("expected {}", stringify!($UIntInRange));
|
||||||
|
};
|
||||||
|
assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES);
|
||||||
|
let value = UInt::from_canonical(value);
|
||||||
|
let range = PhantomConst::<$SerdeRange<Start, End>>::from_canonical(range);
|
||||||
|
let retval = Self::from_phantom_const_range(range);
|
||||||
|
assert_eq!(retval, Self { value, range });
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_location() -> SourceLocation {
|
||||||
|
SourceLocation::builtin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert_eq!(opaque.size(), self.value.type_properties().size());
|
||||||
|
let mut retval = 0usize;
|
||||||
|
retval.view_bits_mut::<Lsb0>()[..opaque.bit_width()]
|
||||||
|
.clone_from_bitslice(opaque.bits());
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
*value = self.sim_value_from_opaque(opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
|
||||||
|
&value.view_bits::<Lsb0>()[..self.value.width()],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> BundleType for $UIntInRangeType<Start, End> {
|
||||||
|
type Builder = NoBuilder;
|
||||||
|
|
||||||
|
fn fields(&self) -> Interned<[BundleField]> {
|
||||||
|
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
|
||||||
|
let Self { value, range } = self;
|
||||||
|
[
|
||||||
|
BundleField {
|
||||||
|
name: value_name.intern(),
|
||||||
|
flipped: false,
|
||||||
|
ty: value.canonical(),
|
||||||
|
},
|
||||||
|
BundleField {
|
||||||
|
name: range_name.intern(),
|
||||||
|
flipped: false,
|
||||||
|
ty: range.canonical(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.intern_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: KnownSize, End: KnownSize> Default for $UIntInRangeType<Start, End> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: KnownSize, End: KnownSize> StaticType for $UIntInRangeType<Start, End> {
|
||||||
|
const TYPE: Self = {
|
||||||
|
let $uint_range_usize_start = Start::VALUE;
|
||||||
|
let $uint_range_usize_end = End::VALUE;
|
||||||
|
Self {
|
||||||
|
value: $uint_range_usize,
|
||||||
|
range: PhantomConst::<$SerdeRange<Start, End>>::TYPE,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const MASK_TYPE: Self::MaskType = UIntInRangeMaskType::TYPE;
|
||||||
|
const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new()
|
||||||
|
.field(false, Self::TYPE.value.type_properties_dyn())
|
||||||
|
.field(
|
||||||
|
false,
|
||||||
|
PhantomConst::<$SerdeRange<Start, End>>::TYPE_PROPERTIES,
|
||||||
|
)
|
||||||
|
.finish();
|
||||||
|
const MASK_TYPE_PROPERTIES: TypeProperties = UIntInRangeMaskType::TYPE_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size> ToSimValueWithType<$UIntInRangeType<Start, End>> for usize {
|
||||||
|
fn to_sim_value_with_type(
|
||||||
|
&self,
|
||||||
|
ty: $UIntInRangeType<Start, End>,
|
||||||
|
) -> SimValue<$UIntInRangeType<Start, End>> {
|
||||||
|
SimValue::from_value(ty, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
|
||||||
|
pub struct $UIntInRangeTypeWithoutGenerics;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const $UIntInRangeType: $UIntInRangeTypeWithoutGenerics =
|
||||||
|
$UIntInRangeTypeWithoutGenerics;
|
||||||
|
|
||||||
|
impl<StartSize: SizeType> Index<StartSize> for $UIntInRangeTypeWithoutGenerics {
|
||||||
|
type Output = $UIntInRangeTypeWithStart<StartSize::Size>;
|
||||||
|
|
||||||
|
fn index(&self, start: StartSize) -> &Self::Output {
|
||||||
|
Interned::into_inner($UIntInRangeTypeWithStart(start).intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub struct $UIntInRangeTypeWithStart<Start: Size>(Start::SizeType);
|
||||||
|
|
||||||
|
impl<Start: Size, EndSize: SizeType<Size = End>, End: Size<SizeType = EndSize>>
|
||||||
|
Index<EndSize> for $UIntInRangeTypeWithStart<Start>
|
||||||
|
{
|
||||||
|
type Output = $UIntInRangeType<Start, End>;
|
||||||
|
|
||||||
|
fn index(&self, end: EndSize) -> &Self::Output {
|
||||||
|
Interned::into_inner($UIntInRangeType::new(self.0, end).intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> CastToImpl<UIntType<Width>>
|
||||||
|
for $UIntInRangeType<Start, End>
|
||||||
|
{
|
||||||
|
type ValueOutput = UIntValue<Width>;
|
||||||
|
|
||||||
|
fn cast_value_to(
|
||||||
|
_this: Self,
|
||||||
|
value: Cow<'_, Self::SimValue>,
|
||||||
|
to_type: UIntType<Width>,
|
||||||
|
) -> Self::ValueOutput {
|
||||||
|
value.cast_to(to_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_expr_to(value: Expr<Self>, to_type: UIntType<Width>) -> Expr<UIntType<Width>> {
|
||||||
|
value.cast_to_bits().cast_to(to_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> CastToImpl<$UIntInRangeType<Start, End>>
|
||||||
|
for UIntType<Width>
|
||||||
|
{
|
||||||
|
type ValueOutput = SimValue<$UIntInRangeType<Start, End>>;
|
||||||
|
|
||||||
|
fn cast_value_to(
|
||||||
|
_this: Self,
|
||||||
|
value: Cow<'_, Self::SimValue>,
|
||||||
|
to_type: $UIntInRangeType<Start, End>,
|
||||||
|
) -> Self::ValueOutput {
|
||||||
|
value.cast_to(to_type.value).cast_bits_to(to_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_expr_to(
|
||||||
|
value: Expr<Self>,
|
||||||
|
to_type: $UIntInRangeType<Start, End>,
|
||||||
|
) -> Expr<$UIntInRangeType<Start, End>> {
|
||||||
|
value.cast_to(to_type.value).cast_bits_to(to_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
|
||||||
|
HdlPartialEqImpl<$UIntInRangeType<RhsStart, RhsEnd>>
|
||||||
|
for $UIntInRangeType<LhsStart, LhsEnd>
|
||||||
|
{
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = true;
|
||||||
|
|
||||||
|
fn cmp_value_eq(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
*lhs_value == *rhs_value
|
||||||
|
}
|
||||||
|
fn cmp_expr_eq(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_ne(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
|
||||||
|
HdlPartialOrdImpl<$UIntInRangeType<RhsStart, RhsEnd>>
|
||||||
|
for $UIntInRangeType<LhsStart, LhsEnd>
|
||||||
|
{
|
||||||
|
fn cmp_value_lt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
PartialOrd::lt(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_le(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
PartialOrd::le(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_gt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
PartialOrd::gt(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_ge(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
PartialOrd::ge(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_expr_lt(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_le(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_le(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_gt(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_ge(
|
||||||
|
lhs: Expr<Self>,
|
||||||
|
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
|
||||||
|
) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_ge(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<UIntType<Width>>
|
||||||
|
for $UIntInRangeType<Start, End>
|
||||||
|
{
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = false;
|
||||||
|
|
||||||
|
fn cmp_value_eq(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: UIntType<Width>,
|
||||||
|
rhs_value: Cow<'_, UIntValue<Width>>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialEq::cmp_eq(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_eq(rhs)
|
||||||
|
}
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_ne(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<$UIntInRangeType<Start, End>>
|
||||||
|
for UIntType<Width>
|
||||||
|
{
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = false;
|
||||||
|
|
||||||
|
fn cmp_value_eq(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<Start, End>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialEq::cmp_eq(&*lhs_value, *rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_eq(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_ne(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<UIntType<Width>>
|
||||||
|
for $UIntInRangeType<Start, End>
|
||||||
|
{
|
||||||
|
fn cmp_value_lt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: UIntType<Width>,
|
||||||
|
rhs_value: Cow<'_, UIntValue<Width>>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_lt(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_le(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: UIntType<Width>,
|
||||||
|
rhs_value: Cow<'_, UIntValue<Width>>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_le(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_gt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: UIntType<Width>,
|
||||||
|
rhs_value: Cow<'_, UIntValue<Width>>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_gt(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_ge(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: UIntType<Width>,
|
||||||
|
rhs_value: Cow<'_, UIntValue<Width>>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_ge(&*lhs_value, &*rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_lt(rhs)
|
||||||
|
}
|
||||||
|
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_le(rhs)
|
||||||
|
}
|
||||||
|
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_gt(rhs)
|
||||||
|
}
|
||||||
|
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
|
||||||
|
lhs.cast_to_bits().cmp_ge(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<$UIntInRangeType<Start, End>>
|
||||||
|
for UIntType<Width>
|
||||||
|
{
|
||||||
|
fn cmp_value_lt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<Start, End>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_lt(&*lhs_value, *rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_le(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<Start, End>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_le(&*lhs_value, *rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_gt(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<Start, End>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_gt(&*lhs_value, *rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_value_ge(
|
||||||
|
_lhs: Self,
|
||||||
|
lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
_rhs: $UIntInRangeType<Start, End>,
|
||||||
|
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
HdlPartialOrd::cmp_ge(&*lhs_value, *rhs_value)
|
||||||
|
}
|
||||||
|
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_lt(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_le(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_gt(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
|
||||||
|
lhs.cmp_ge(rhs.cast_to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_uint_in_range_type! {
|
||||||
|
UIntInRange,
|
||||||
|
UIntInRangeType,
|
||||||
|
UIntInRangeTypeWithoutGenerics,
|
||||||
|
UIntInRangeTypeWithStart,
|
||||||
|
SerdeRange,
|
||||||
|
"..",
|
||||||
|
|start, end| UInt::range_usize(start..end),
|
||||||
|
}
|
||||||
|
|
||||||
|
define_uint_in_range_type! {
|
||||||
|
UIntInRangeInclusive,
|
||||||
|
UIntInRangeInclusiveType,
|
||||||
|
UIntInRangeInclusiveTypeWithoutGenerics,
|
||||||
|
UIntInRangeInclusiveTypeWithStart,
|
||||||
|
SerdeRangeInclusive,
|
||||||
|
"..=",
|
||||||
|
|start, end| UInt::range_inclusive_usize(start..=end),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerdeRange<DynSize, DynSize> {
|
||||||
|
fn is_empty(self) -> bool {
|
||||||
|
self.start >= self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerdeRangeInclusive<DynSize, DynSize> {
|
||||||
|
fn is_empty(self) -> bool {
|
||||||
|
self.start > self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
117
crates/fayalite/src/intern/interner.rs
Normal file
117
crates/fayalite/src/intern/interner.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
intern::{Interned, type_map::TypeIdMap},
|
||||||
|
util::DefaultBuildHasher,
|
||||||
|
};
|
||||||
|
use bitvec::slice::BitSlice;
|
||||||
|
use hashbrown::HashTable;
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
hash::{BuildHasher, Hash},
|
||||||
|
sync::RwLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InternerShard<T: ?Sized + 'static + Send + Sync> {
|
||||||
|
table: HashTable<&'static T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOG2_SHARD_COUNT: u32 = 6;
|
||||||
|
|
||||||
|
fn shard_index_from_hash(hash: u64) -> usize {
|
||||||
|
// number of bits used for hashbrown's Tag
|
||||||
|
const HASH_BROWN_TAG_BITS: u32 = 7;
|
||||||
|
// try to extract bits of the hash that hashbrown isn't using,
|
||||||
|
// while accounting for some hash functions only returning `usize` bits.
|
||||||
|
const SHARD_INDEX_START: u32 = usize::BITS
|
||||||
|
.saturating_sub(HASH_BROWN_TAG_BITS)
|
||||||
|
.saturating_sub(LOG2_SHARD_COUNT);
|
||||||
|
let mut shard_index = hash >> SHARD_INDEX_START;
|
||||||
|
shard_index %= 1 << LOG2_SHARD_COUNT;
|
||||||
|
shard_index as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Interner<T: ?Sized + 'static + Send + Sync> {
|
||||||
|
shards: [RwLock<InternerShard<T>>; 1 << LOG2_SHARD_COUNT],
|
||||||
|
hasher: DefaultBuildHasher,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + 'static + Send + Sync> Interner<T> {
|
||||||
|
pub(crate) fn get() -> &'static Interner<T> {
|
||||||
|
static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new();
|
||||||
|
thread_local! {
|
||||||
|
static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() };
|
||||||
|
}
|
||||||
|
TYPE_ID_MAP_CACHE.with(|cache| {
|
||||||
|
cache.get_or_insert_with(|| {
|
||||||
|
TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + 'static + Send + Sync> Default for Interner<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
shards: [const {
|
||||||
|
RwLock::new(InternerShard {
|
||||||
|
table: HashTable::new(),
|
||||||
|
})
|
||||||
|
}; _],
|
||||||
|
hasher: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + 'static + Send + Sync + Hash + Eq + ToOwned> Interner<T> {
|
||||||
|
fn intern<F: FnOnce(Cow<'_, T>) -> &'static T>(
|
||||||
|
&self,
|
||||||
|
alloc: F,
|
||||||
|
value: Cow<'_, T>,
|
||||||
|
) -> Interned<T> {
|
||||||
|
let hash = self.hasher.hash_one(&*value);
|
||||||
|
let shard_index = shard_index_from_hash(hash);
|
||||||
|
let shard = &self.shards[shard_index];
|
||||||
|
let shard_read = shard.read().unwrap();
|
||||||
|
let Some(&inner) = shard_read.table.find(hash, |k| **k == *value) else {
|
||||||
|
drop(shard_read);
|
||||||
|
return self.intern_cold(alloc, value, hash, shard);
|
||||||
|
};
|
||||||
|
Interned { inner }
|
||||||
|
}
|
||||||
|
#[cold]
|
||||||
|
fn intern_cold<F: FnOnce(Cow<'_, T>) -> &'static T>(
|
||||||
|
&self,
|
||||||
|
alloc: F,
|
||||||
|
value: Cow<'_, T>,
|
||||||
|
hash: u64,
|
||||||
|
shard: &RwLock<InternerShard<T>>,
|
||||||
|
) -> Interned<T> {
|
||||||
|
let mut shard = shard.write().unwrap();
|
||||||
|
let inner = *shard
|
||||||
|
.table
|
||||||
|
.entry(hash, |k| **k == *value, |k| self.hasher.hash_one(&**k))
|
||||||
|
.or_insert_with(|| alloc(value))
|
||||||
|
.get();
|
||||||
|
Interned { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static + Send + Sync + Hash + Eq> Interner<T> {
|
||||||
|
pub(crate) fn intern_sized(&self, value: Cow<'_, T>) -> Interned<T> {
|
||||||
|
self.intern(|value| Box::leak(Box::new(value.into_owned())), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static + Send + Sync + Hash + Eq> Interner<[T]> {
|
||||||
|
pub(crate) fn intern_slice(&self, value: Cow<'_, [T]>) -> Interned<[T]> {
|
||||||
|
self.intern(|value| value.into_owned().leak(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interner<BitSlice> {
|
||||||
|
pub(crate) fn intern_bit_slice(&self, value: Cow<'_, BitSlice>) -> Interned<BitSlice> {
|
||||||
|
self.intern(|value| value.into_owned().leak(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use hashbrown::HashMap;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
hash::{BuildHasher, Hasher},
|
hash::{BuildHasher, Hasher},
|
||||||
ptr::NonNull,
|
|
||||||
sync::RwLock,
|
sync::RwLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypeIdHasher(u64);
|
pub(crate) struct TypeIdHasher(u64);
|
||||||
|
|
||||||
// assumes TypeId has at least 64 bits that is a good hash
|
// assumes TypeId has at least 64 bits that is a good hash
|
||||||
impl Hasher for TypeIdHasher {
|
impl Hasher for TypeIdHasher {
|
||||||
|
|
@ -65,7 +63,7 @@ impl Hasher for TypeIdHasher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TypeIdBuildHasher;
|
pub(crate) struct TypeIdBuildHasher;
|
||||||
|
|
||||||
impl BuildHasher for TypeIdBuildHasher {
|
impl BuildHasher for TypeIdBuildHasher {
|
||||||
type Hasher = TypeIdHasher;
|
type Hasher = TypeIdHasher;
|
||||||
|
|
@ -75,59 +73,39 @@ impl BuildHasher for TypeIdBuildHasher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Value(NonNull<dyn Any + Send + Sync>);
|
pub(crate) struct TypeIdMap(
|
||||||
|
RwLock<hashbrown::HashMap<TypeId, &'static (dyn Any + Send + Sync), TypeIdBuildHasher>>,
|
||||||
impl Value {
|
);
|
||||||
unsafe fn get_transmute_lifetime<'b>(&self) -> &'b (dyn Any + Send + Sync) {
|
|
||||||
unsafe { &*self.0.as_ptr() }
|
|
||||||
}
|
|
||||||
fn new(v: Box<dyn Any + Send + Sync>) -> Self {
|
|
||||||
unsafe { Self(NonNull::new_unchecked(Box::into_raw(v))) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Value {}
|
|
||||||
unsafe impl Sync for Value {}
|
|
||||||
|
|
||||||
impl Drop for Value {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { std::ptr::drop_in_place(self.0.as_ptr()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TypeIdMap(RwLock<HashMap<TypeId, Value, TypeIdBuildHasher>>);
|
|
||||||
|
|
||||||
impl TypeIdMap {
|
impl TypeIdMap {
|
||||||
pub const fn new() -> Self {
|
pub(crate) const fn new() -> Self {
|
||||||
Self(RwLock::new(HashMap::with_hasher(TypeIdBuildHasher)))
|
Self(RwLock::new(hashbrown::HashMap::with_hasher(
|
||||||
|
TypeIdBuildHasher,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
#[cold]
|
#[cold]
|
||||||
unsafe fn insert_slow(
|
fn insert_slow(
|
||||||
&self,
|
&self,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
make: fn() -> Box<dyn Any + Sync + Send>,
|
make: impl FnOnce() -> &'static (dyn Any + Sync + Send),
|
||||||
) -> &(dyn Any + Sync + Send) {
|
) -> &'static (dyn Any + Sync + Send) {
|
||||||
let value = Value::new(make());
|
let value = make();
|
||||||
let mut write_guard = self.0.write().unwrap();
|
let mut write_guard = self.0.write().unwrap();
|
||||||
unsafe {
|
*write_guard.entry(type_id).or_insert(value)
|
||||||
write_guard
|
|
||||||
.entry(type_id)
|
|
||||||
.or_insert(value)
|
|
||||||
.get_transmute_lifetime()
|
|
||||||
}
|
}
|
||||||
}
|
pub(crate) fn get_or_insert_with<T: Sized + Any + Send + Sync>(
|
||||||
pub fn get_or_insert_default<T: Sized + Any + Send + Sync + Default>(&self) -> &T {
|
&self,
|
||||||
|
make: impl FnOnce() -> &'static T,
|
||||||
|
) -> &'static T {
|
||||||
let type_id = TypeId::of::<T>();
|
let type_id = TypeId::of::<T>();
|
||||||
let read_guard = self.0.read().unwrap();
|
let read_guard = self.0.read().unwrap();
|
||||||
let retval = read_guard
|
let retval = read_guard.get(&type_id).map(|v| *v);
|
||||||
.get(&type_id)
|
|
||||||
.map(|v| unsafe { Value::get_transmute_lifetime(v) });
|
|
||||||
drop(read_guard);
|
drop(read_guard);
|
||||||
let retval = match retval {
|
let retval = match retval {
|
||||||
Some(retval) => retval,
|
Some(retval) => retval,
|
||||||
None => unsafe { self.insert_slow(type_id, move || Box::new(T::default())) },
|
None => self.insert_slow(type_id, move || make()),
|
||||||
};
|
};
|
||||||
unsafe { &*(retval as *const dyn Any as *const T) }
|
retval.downcast_ref().expect("known to have correct TypeId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
// TODO: enable:
|
// TODO: enable:
|
||||||
// #![warn(missing_docs)]
|
// #![warn(missing_docs)]
|
||||||
|
|
||||||
|
#![deny(
|
||||||
|
rustdoc::bare_urls,
|
||||||
|
rustdoc::broken_intra_doc_links,
|
||||||
|
rustdoc::invalid_codeblock_attributes,
|
||||||
|
rustdoc::invalid_html_tags,
|
||||||
|
rustdoc::invalid_rust_codeblocks,
|
||||||
|
rustdoc::private_doc_tests,
|
||||||
|
rustdoc::private_intra_doc_links,
|
||||||
|
rustdoc::redundant_explicit_links,
|
||||||
|
rustdoc::unescaped_backticks
|
||||||
|
)]
|
||||||
|
|
||||||
//! [Main Documentation][_docs]
|
//! [Main Documentation][_docs]
|
||||||
|
|
||||||
extern crate self as fayalite;
|
extern crate self as fayalite;
|
||||||
|
|
@ -11,6 +23,59 @@ extern crate self as fayalite;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use std as __std;
|
pub use std as __std;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! __cfg_expansion_helper {
|
||||||
|
(
|
||||||
|
[
|
||||||
|
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
|
||||||
|
]
|
||||||
|
[
|
||||||
|
$cfg:ident($($expr:tt)*),
|
||||||
|
$($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)*
|
||||||
|
]
|
||||||
|
// pass as tt so we get right span for attribute
|
||||||
|
$after_evaluation_attr:tt $after_evaluation_body:tt
|
||||||
|
) => {
|
||||||
|
#[$cfg($($expr)*)]
|
||||||
|
$crate::__cfg_expansion_helper! {
|
||||||
|
[
|
||||||
|
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
|
||||||
|
$cfg($($expr)*) = true,
|
||||||
|
]
|
||||||
|
[
|
||||||
|
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
|
||||||
|
]
|
||||||
|
$after_evaluation_attr $after_evaluation_body
|
||||||
|
}
|
||||||
|
#[$cfg(not($($expr)*))]
|
||||||
|
$crate::__cfg_expansion_helper! {
|
||||||
|
[
|
||||||
|
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
|
||||||
|
$cfg($($expr)*) = false,
|
||||||
|
]
|
||||||
|
[
|
||||||
|
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
|
||||||
|
]
|
||||||
|
$after_evaluation_attr $after_evaluation_body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
[
|
||||||
|
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
// don't use #[...] so we get right span for `#` and `[]` of attribute
|
||||||
|
{$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*}
|
||||||
|
) => {
|
||||||
|
$($after_evaluation_attr)*
|
||||||
|
#[__evaluated_cfgs([
|
||||||
|
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
|
||||||
|
])]
|
||||||
|
$($after_evaluation_body)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates
|
/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates
|
||||||
/// a [`Module`][`::fayalite::module::Module`] when called.
|
/// a [`Module`][`::fayalite::module::Module`] when called.
|
||||||
|
|
@ -21,8 +86,139 @@ pub use std as __std;
|
||||||
pub use fayalite_proc_macros::hdl_module;
|
pub use fayalite_proc_macros::hdl_module;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
/// The `#[hdl]` attribute is supported on several different kinds of [Rust Items](https://doc.rust-lang.org/reference/items.html):
|
||||||
|
///
|
||||||
|
/// # Functions and Methods
|
||||||
|
/// Enable's the stuff that you can use inside a [module's body](crate::_docs::modules::module_bodies),
|
||||||
|
/// but without being a module or changing the function's signature.
|
||||||
|
/// The only exception is that you can't use stuff that requires the automatically-provided `m` variable.
|
||||||
|
///
|
||||||
|
/// # Structs
|
||||||
|
// TODO: expand on struct docs
|
||||||
|
/// e.g.:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct OtherStruct {}
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub struct MyStruct {
|
||||||
|
/// #[hdl(flip)]
|
||||||
|
/// pub a: UInt<5>,
|
||||||
|
/// pub b: Bool,
|
||||||
|
/// #[hdl(flip)]
|
||||||
|
/// pub c: OtherStruct,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Enums
|
||||||
|
// TODO: expand on enum docs
|
||||||
|
/// e.g.:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct MyStruct {}
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub enum MyEnum {
|
||||||
|
/// A(UInt<3>),
|
||||||
|
/// B,
|
||||||
|
/// C(MyStruct),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Aliases
|
||||||
|
///
|
||||||
|
/// There's three different ways you can create a type alias:
|
||||||
|
///
|
||||||
|
/// # Normal Type Alias
|
||||||
|
///
|
||||||
|
/// This works exactly how you'd expect:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # pub struct MyStruct<T: Type> {
|
||||||
|
/// # v: T,
|
||||||
|
/// # }
|
||||||
|
/// #[hdl]
|
||||||
|
/// pub type MyType<T: Type> = MyStruct<T>;
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
///
|
||||||
|
/// let ty = MyType[UInt[3]];
|
||||||
|
/// assert_eq!(ty, MyStruct[UInt[3]]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Alias that gets a [`Type`] from a [`PhantomConst`]
|
||||||
|
///
|
||||||
|
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// pub struct Config {
|
||||||
|
/// pub foo: usize,
|
||||||
|
/// pub bar: Bundle,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // the expression inside `get` is called with `Interned<Config>` and returns `Array<Bundle>`
|
||||||
|
/// #[hdl(get(|config| Array[config.bar][config.foo]))]
|
||||||
|
/// pub type GetMyArray<P: PhantomConstGet<Config>> = Array<Bundle>;
|
||||||
|
///
|
||||||
|
/// // you can then use it in other types:
|
||||||
|
///
|
||||||
|
/// #[hdl(no_static)]
|
||||||
|
/// pub struct WrapMyArray<P: PhantomConstGet<Config>> {
|
||||||
|
/// pub my_array: GetMyArray<P>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
/// let bar = Bundle::new(Default::default());
|
||||||
|
/// let config = PhantomConst::new_sized(Config { foo: 12, bar });
|
||||||
|
/// let ty = WrapMyArray[config];
|
||||||
|
/// assert_eq!(ty.my_array, Array[bar][12]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Type Alias that gets a [`Size`] from a [`PhantomConst`]
|
||||||
|
///
|
||||||
|
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// # pub struct ConfigItem {}
|
||||||
|
/// # impl ConfigItem {
|
||||||
|
/// # pub fn new() -> Self {
|
||||||
|
/// # Self {}
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
/// pub struct Config {
|
||||||
|
/// pub items: Vec<ConfigItem>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // the expression inside `get` is called with `Interned<Config>` and returns `usize` (not DynSize)
|
||||||
|
/// #[hdl(get(|config| config.items.len()))]
|
||||||
|
/// pub type GetItemsLen<P: PhantomConstGet<Config>> = DynSize; // must be DynSize
|
||||||
|
///
|
||||||
|
/// // you can then use it in other types:
|
||||||
|
///
|
||||||
|
/// #[hdl(no_static)]
|
||||||
|
/// pub struct FlagPerItem<P: PhantomConstGet<Config>> {
|
||||||
|
/// pub flags: ArrayType<Bool, GetItemsLen<P>>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
|
||||||
|
/// let config = PhantomConst::new_sized(Config { items: vec![ConfigItem::new(); 5] });
|
||||||
|
/// let ty = FlagPerItem[config];
|
||||||
|
/// assert_eq!(ty.flags, Array[Bool][5]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`PhantomConst`]: crate::phantom_const::PhantomConst
|
||||||
|
/// [`Size`]: crate::int::Size
|
||||||
|
/// [`Type`]: crate::ty::Type
|
||||||
pub use fayalite_proc_macros::hdl;
|
pub use fayalite_proc_macros::hdl;
|
||||||
|
|
||||||
|
pub use bitvec;
|
||||||
|
|
||||||
/// struct used as a placeholder when applying defaults
|
/// struct used as a placeholder when applying defaults
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct __;
|
pub struct __;
|
||||||
|
|
@ -30,24 +226,28 @@ pub struct __;
|
||||||
#[cfg(feature = "unstable-doc")]
|
#[cfg(feature = "unstable-doc")]
|
||||||
pub mod _docs;
|
pub mod _docs;
|
||||||
|
|
||||||
// FIXME: finish
|
|
||||||
pub mod annotations;
|
pub mod annotations;
|
||||||
pub mod array;
|
pub mod array;
|
||||||
|
pub mod build;
|
||||||
pub mod bundle;
|
pub mod bundle;
|
||||||
pub mod cli;
|
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod enum_;
|
pub mod enum_;
|
||||||
pub mod expr;
|
pub mod expr;
|
||||||
pub mod firrtl;
|
pub mod firrtl;
|
||||||
|
pub mod formal;
|
||||||
pub mod int;
|
pub mod int;
|
||||||
pub mod intern;
|
pub mod intern;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
pub mod phantom_const;
|
||||||
|
pub mod platform;
|
||||||
|
pub mod prelude;
|
||||||
pub mod reg;
|
pub mod reg;
|
||||||
pub mod reset;
|
pub mod reset;
|
||||||
|
pub mod sim;
|
||||||
pub mod source_location;
|
pub mod source_location;
|
||||||
|
pub mod testing;
|
||||||
pub mod ty;
|
pub mod ty;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
//pub mod valueless;
|
pub mod vendor;
|
||||||
pub mod prelude;
|
|
||||||
pub mod wire;
|
pub mod wire;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,10 @@ use crate::{
|
||||||
array::{Array, ArrayType},
|
array::{Array, ArrayType},
|
||||||
bundle::{Bundle, BundleType},
|
bundle::{Bundle, BundleType},
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
expr::{Expr, Flow, ToExpr, ToLiteralBits},
|
expr::{
|
||||||
|
Expr, Flow, ToExpr, ToLiteralBits, ValueType, ops::BundleLiteral, repeat,
|
||||||
|
value_category::ValueCategoryExpr,
|
||||||
|
},
|
||||||
hdl,
|
hdl,
|
||||||
int::{Bool, DynSize, Size, UInt, UIntType},
|
int::{Bool, DynSize, Size, UInt, UIntType},
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
|
|
@ -22,7 +25,7 @@ use std::{
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
num::NonZeroU32,
|
num::NonZeroUsize,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -366,10 +369,16 @@ impl<T: PortType> fmt::Debug for MemPort<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PortType> MemPort<T> {
|
impl<T: PortType> ValueType for MemPort<T> {
|
||||||
pub fn ty(&self) -> T::Port {
|
type Type = T::Port;
|
||||||
|
type ValueCategory = ValueCategoryExpr;
|
||||||
|
|
||||||
|
fn ty(&self) -> T::Port {
|
||||||
T::port_ty(self)
|
T::port_ty(self)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PortType> MemPort<T> {
|
||||||
pub fn source_location(&self) -> SourceLocation {
|
pub fn source_location(&self) -> SourceLocation {
|
||||||
self.source_location
|
self.source_location
|
||||||
}
|
}
|
||||||
|
|
@ -470,7 +479,7 @@ pub enum ReadUnderWrite {
|
||||||
Undefined,
|
Undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
struct MemImpl<Element: Type, Len: Size, P> {
|
struct MemImpl<Element: Type, Len: Size, P> {
|
||||||
scoped_name: ScopedNameId,
|
scoped_name: ScopedNameId,
|
||||||
source_location: SourceLocation,
|
source_location: SourceLocation,
|
||||||
|
|
@ -478,7 +487,7 @@ struct MemImpl<Element: Type, Len: Size, P> {
|
||||||
initial_value: Option<Interned<BitSlice>>,
|
initial_value: Option<Interned<BitSlice>>,
|
||||||
ports: P,
|
ports: P,
|
||||||
read_latency: usize,
|
read_latency: usize,
|
||||||
write_latency: NonZeroU32,
|
write_latency: NonZeroUsize,
|
||||||
read_under_write: ReadUnderWrite,
|
read_under_write: ReadUnderWrite,
|
||||||
port_annotations: Interned<[TargetedAnnotation]>,
|
port_annotations: Interned<[TargetedAnnotation]>,
|
||||||
mem_annotations: Interned<[Annotation]>,
|
mem_annotations: Interned<[Annotation]>,
|
||||||
|
|
@ -519,7 +528,12 @@ impl<Element: Type, Len: Size> fmt::Debug for Mem<Element, Len> {
|
||||||
f.debug_struct("Mem")
|
f.debug_struct("Mem")
|
||||||
.field("name", scoped_name)
|
.field("name", scoped_name)
|
||||||
.field("array_type", array_type)
|
.field("array_type", array_type)
|
||||||
.field("initial_value", initial_value)
|
.field(
|
||||||
|
"initial_value",
|
||||||
|
&initial_value.as_ref().map(|initial_value| {
|
||||||
|
DebugMemoryData::from_bit_slice(*array_type, initial_value)
|
||||||
|
}),
|
||||||
|
)
|
||||||
.field("read_latency", read_latency)
|
.field("read_latency", read_latency)
|
||||||
.field("write_latency", write_latency)
|
.field("write_latency", write_latency)
|
||||||
.field("read_under_write", read_under_write)
|
.field("read_under_write", read_under_write)
|
||||||
|
|
@ -562,7 +576,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
|
||||||
initial_value: Option<Interned<BitSlice>>,
|
initial_value: Option<Interned<BitSlice>>,
|
||||||
ports: Interned<[MemPort<DynPortType>]>,
|
ports: Interned<[MemPort<DynPortType>]>,
|
||||||
read_latency: usize,
|
read_latency: usize,
|
||||||
write_latency: NonZeroU32,
|
write_latency: NonZeroUsize,
|
||||||
read_under_write: ReadUnderWrite,
|
read_under_write: ReadUnderWrite,
|
||||||
port_annotations: Interned<[TargetedAnnotation]>,
|
port_annotations: Interned<[TargetedAnnotation]>,
|
||||||
mem_annotations: Interned<[Annotation]>,
|
mem_annotations: Interned<[Annotation]>,
|
||||||
|
|
@ -634,7 +648,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
|
||||||
self.0.source_location
|
self.0.source_location
|
||||||
}
|
}
|
||||||
pub fn array_type(self) -> ArrayType<Element, Len> {
|
pub fn array_type(self) -> ArrayType<Element, Len> {
|
||||||
self.0.array_type.clone()
|
self.0.array_type
|
||||||
}
|
}
|
||||||
pub fn initial_value(self) -> Option<Interned<BitSlice>> {
|
pub fn initial_value(self) -> Option<Interned<BitSlice>> {
|
||||||
self.0.initial_value
|
self.0.initial_value
|
||||||
|
|
@ -645,7 +659,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
|
||||||
pub fn read_latency(self) -> usize {
|
pub fn read_latency(self) -> usize {
|
||||||
self.0.read_latency
|
self.0.read_latency
|
||||||
}
|
}
|
||||||
pub fn write_latency(self) -> NonZeroU32 {
|
pub fn write_latency(self) -> NonZeroUsize {
|
||||||
self.0.write_latency
|
self.0.write_latency
|
||||||
}
|
}
|
||||||
pub fn read_under_write(self) -> ReadUnderWrite {
|
pub fn read_under_write(self) -> ReadUnderWrite {
|
||||||
|
|
@ -707,7 +721,7 @@ pub(crate) struct MemBuilderTarget {
|
||||||
pub(crate) initial_value: Option<Interned<BitSlice>>,
|
pub(crate) initial_value: Option<Interned<BitSlice>>,
|
||||||
pub(crate) ports: Vec<MemPort<DynPortType>>,
|
pub(crate) ports: Vec<MemPort<DynPortType>>,
|
||||||
pub(crate) read_latency: usize,
|
pub(crate) read_latency: usize,
|
||||||
pub(crate) write_latency: NonZeroU32,
|
pub(crate) write_latency: NonZeroUsize,
|
||||||
pub(crate) read_under_write: ReadUnderWrite,
|
pub(crate) read_under_write: ReadUnderWrite,
|
||||||
pub(crate) port_annotations: Vec<TargetedAnnotation>,
|
pub(crate) port_annotations: Vec<TargetedAnnotation>,
|
||||||
pub(crate) mem_annotations: Vec<Annotation>,
|
pub(crate) mem_annotations: Vec<Annotation>,
|
||||||
|
|
@ -825,7 +839,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
depth: Option<usize>,
|
depth: Option<usize>,
|
||||||
initial_value: Expr<Array>,
|
initial_value: Expr<Array>,
|
||||||
) -> Interned<BitSlice> {
|
) -> Interned<BitSlice> {
|
||||||
let initial_value_ty = Expr::ty(initial_value);
|
let initial_value_ty = initial_value.ty();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*mem_element_type,
|
*mem_element_type,
|
||||||
Element::from_canonical(initial_value_ty.element()),
|
Element::from_canonical(initial_value_ty.element()),
|
||||||
|
|
@ -867,7 +881,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
initial_value: None,
|
initial_value: None,
|
||||||
ports: vec![],
|
ports: vec![],
|
||||||
read_latency: 0,
|
read_latency: 0,
|
||||||
write_latency: NonZeroU32::new(1).unwrap(),
|
write_latency: NonZeroUsize::new(1).unwrap(),
|
||||||
read_under_write: ReadUnderWrite::Old,
|
read_under_write: ReadUnderWrite::Old,
|
||||||
port_annotations: vec![],
|
port_annotations: vec![],
|
||||||
mem_annotations: vec![],
|
mem_annotations: vec![],
|
||||||
|
|
@ -987,7 +1001,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
#[allow(clippy::result_unit_err)]
|
#[allow(clippy::result_unit_err)]
|
||||||
pub fn get_array_type(&self) -> Result<ArrayType<Element, Len>, ()> {
|
pub fn get_array_type(&self) -> Result<ArrayType<Element, Len>, ()> {
|
||||||
Ok(ArrayType::new(
|
Ok(ArrayType::new(
|
||||||
self.mem_element_type.clone(),
|
self.mem_element_type,
|
||||||
Len::from_usize(self.get_depth()?),
|
Len::from_usize(self.get_depth()?),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
@ -1006,7 +1020,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
target.depth,
|
target.depth,
|
||||||
initial_value,
|
initial_value,
|
||||||
));
|
));
|
||||||
target.depth = Some(Expr::ty(initial_value).len());
|
target.depth = Some(initial_value.ty().len());
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn initial_value_bit_slice(&mut self, initial_value: Interned<BitSlice>) {
|
pub fn initial_value_bit_slice(&mut self, initial_value: Interned<BitSlice>) {
|
||||||
|
|
@ -1030,10 +1044,10 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
pub fn read_latency(&mut self, read_latency: usize) {
|
pub fn read_latency(&mut self, read_latency: usize) {
|
||||||
self.target.borrow_mut().read_latency = read_latency;
|
self.target.borrow_mut().read_latency = read_latency;
|
||||||
}
|
}
|
||||||
pub fn get_write_latency(&self) -> NonZeroU32 {
|
pub fn get_write_latency(&self) -> NonZeroUsize {
|
||||||
self.target.borrow().write_latency
|
self.target.borrow().write_latency
|
||||||
}
|
}
|
||||||
pub fn write_latency(&mut self, write_latency: NonZeroU32) {
|
pub fn write_latency(&mut self, write_latency: NonZeroUsize) {
|
||||||
self.target.borrow_mut().write_latency = write_latency;
|
self.target.borrow_mut().write_latency = write_latency;
|
||||||
}
|
}
|
||||||
pub fn get_read_under_write(&self) -> ReadUnderWrite {
|
pub fn get_read_under_write(&self) -> ReadUnderWrite {
|
||||||
|
|
@ -1050,3 +1064,93 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
|
||||||
.extend(annotations.into_annotations());
|
.extend(annotations.into_annotations());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn splat_mask<T: Type>(ty: T, value: Expr<Bool>) -> Expr<AsMask<T>> {
|
||||||
|
let canonical_ty = ty.canonical();
|
||||||
|
match canonical_ty {
|
||||||
|
CanonicalType::UInt(_)
|
||||||
|
| CanonicalType::SInt(_)
|
||||||
|
| CanonicalType::Bool(_)
|
||||||
|
| CanonicalType::AsyncReset(_)
|
||||||
|
| CanonicalType::SyncReset(_)
|
||||||
|
| CanonicalType::Reset(_)
|
||||||
|
| CanonicalType::Clock(_)
|
||||||
|
| CanonicalType::Enum(_)
|
||||||
|
| CanonicalType::DynSimOnly(_) => Expr::from_canonical(Expr::canonical(value)),
|
||||||
|
CanonicalType::Array(array) => Expr::from_canonical(Expr::canonical(repeat(
|
||||||
|
splat_mask(array.element(), value),
|
||||||
|
array.len(),
|
||||||
|
))),
|
||||||
|
CanonicalType::Bundle(bundle) => Expr::from_canonical(Expr::canonical(
|
||||||
|
BundleLiteral::new(
|
||||||
|
bundle.mask_type(),
|
||||||
|
bundle
|
||||||
|
.fields()
|
||||||
|
.iter()
|
||||||
|
.map(|field| splat_mask(field.ty, value))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.to_expr(),
|
||||||
|
)),
|
||||||
|
CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())),
|
||||||
|
CanonicalType::TraceAsString(ty) => Expr::from_canonical(splat_mask(ty.inner_ty(), value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DebugMemoryDataGetElement {
|
||||||
|
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, F: ?Sized + Fn(usize, Array) -> &'a BitSlice> DebugMemoryDataGetElement for &'a F {
|
||||||
|
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice {
|
||||||
|
self(element_index, array_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DebugMemoryData<GetElement: DebugMemoryDataGetElement> {
|
||||||
|
pub array_type: Array,
|
||||||
|
pub get_element: GetElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugMemoryDataGetElement for &'_ BitSlice {
|
||||||
|
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice {
|
||||||
|
assert!(element_index < array_type.len());
|
||||||
|
let stride = array_type.element().bit_width();
|
||||||
|
let start = element_index
|
||||||
|
.checked_mul(stride)
|
||||||
|
.expect("memory is too big");
|
||||||
|
let end = start.checked_add(stride).expect("memory is too big");
|
||||||
|
&self[start..end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DebugMemoryData<&'a BitSlice> {
|
||||||
|
pub fn from_bit_slice<T: Type, Depth: Size>(
|
||||||
|
array_type: ArrayType<T, Depth>,
|
||||||
|
bit_slice: &'a BitSlice,
|
||||||
|
) -> Self {
|
||||||
|
let array_type = array_type.as_dyn_array();
|
||||||
|
assert_eq!(bit_slice.len(), array_type.type_properties().bit_width);
|
||||||
|
Self {
|
||||||
|
array_type,
|
||||||
|
get_element: bit_slice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<GetElement: DebugMemoryDataGetElement> fmt::Debug for DebugMemoryData<GetElement> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.array_type.len() == 0 {
|
||||||
|
return f.write_str("[]");
|
||||||
|
}
|
||||||
|
writeln!(f, "[\n // len = {:#x}", self.array_type.len())?;
|
||||||
|
for element_index in 0..self.array_type.len() {
|
||||||
|
let element = crate::util::BitSliceWriteWithBase(
|
||||||
|
self.get_element.get_element(element_index, self.array_type),
|
||||||
|
);
|
||||||
|
writeln!(f, " [{element_index:#x}]: {element:#x},")?;
|
||||||
|
}
|
||||||
|
f.write_str("]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
pub mod deduce_resets;
|
||||||
|
pub mod deduce_structural_eq_flags;
|
||||||
pub mod simplify_enums;
|
pub mod simplify_enums;
|
||||||
pub mod simplify_memories;
|
pub mod simplify_memories;
|
||||||
pub mod visit;
|
pub mod visit;
|
||||||
|
|
|
||||||
2481
crates/fayalite/src/module/transform/deduce_resets.rs
Normal file
2481
crates/fayalite/src/module/transform/deduce_resets.rs
Normal file
File diff suppressed because it is too large
Load diff
1011
crates/fayalite/src/module/transform/deduce_structural_eq_flags.rs
Normal file
1011
crates/fayalite/src/module/transform/deduce_structural_eq_flags.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -4,21 +4,20 @@ use crate::{
|
||||||
annotations::TargetedAnnotation,
|
annotations::TargetedAnnotation,
|
||||||
array::Array,
|
array::Array,
|
||||||
bundle::{Bundle, BundleType},
|
bundle::{Bundle, BundleType},
|
||||||
expr::{CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr},
|
expr::{CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr, ValueType},
|
||||||
int::{Bool, SInt, Size, UInt},
|
int::{Bool, SInt, Size, UInt},
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
memory::{Mem, MemPort, PortType},
|
memory::{Mem, MemPort, PortType},
|
||||||
module::{
|
module::{
|
||||||
|
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtWire,
|
||||||
transform::visit::{Fold, Folder},
|
transform::visit::{Fold, Folder},
|
||||||
Block, Module, NameId, NameIdGen, ScopedNameId, Stmt, StmtConnect, StmtWire,
|
|
||||||
},
|
},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{CanonicalType, Type},
|
ty::{CanonicalType, Type},
|
||||||
util::MakeMutSlice,
|
util::{HashMap, MakeMutSlice},
|
||||||
wire::Wire,
|
wire::Wire,
|
||||||
};
|
};
|
||||||
use bitvec::{slice::BitSlice, vec::BitVec};
|
use bitvec::{slice::BitSlice, vec::BitVec};
|
||||||
use hashbrown::HashMap;
|
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
|
|
@ -62,6 +61,7 @@ enum MemSplit {
|
||||||
Bundle {
|
Bundle {
|
||||||
fields: Rc<[MemSplit]>,
|
fields: Rc<[MemSplit]>,
|
||||||
},
|
},
|
||||||
|
PhantomConst,
|
||||||
Single {
|
Single {
|
||||||
output_mem: Option<Mem>,
|
output_mem: Option<Mem>,
|
||||||
element_type: SingleType,
|
element_type: SingleType,
|
||||||
|
|
@ -76,6 +76,7 @@ impl MemSplit {
|
||||||
fn mark_changed_element_type(self) -> Self {
|
fn mark_changed_element_type(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
MemSplit::Bundle { fields: _ } => self,
|
MemSplit::Bundle { fields: _ } => self,
|
||||||
|
MemSplit::PhantomConst => self,
|
||||||
MemSplit::Single {
|
MemSplit::Single {
|
||||||
output_mem,
|
output_mem,
|
||||||
element_type,
|
element_type,
|
||||||
|
|
@ -89,7 +90,7 @@ impl MemSplit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn new(element_type: CanonicalType) -> Self {
|
fn new(element_type: CanonicalType) -> Self {
|
||||||
match element_type {
|
match element_type.unwrap_transparent_types() {
|
||||||
CanonicalType::Bundle(bundle_ty) => MemSplit::Bundle {
|
CanonicalType::Bundle(bundle_ty) => MemSplit::Bundle {
|
||||||
fields: bundle_ty
|
fields: bundle_ty
|
||||||
.fields()
|
.fields()
|
||||||
|
|
@ -97,6 +98,7 @@ impl MemSplit {
|
||||||
.map(|field| Self::new(field.ty).mark_changed_element_type())
|
.map(|field| Self::new(field.ty).mark_changed_element_type())
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
CanonicalType::PhantomConst(_) => MemSplit::PhantomConst,
|
||||||
CanonicalType::Array(ty) => {
|
CanonicalType::Array(ty) => {
|
||||||
let element = MemSplit::new(ty.element());
|
let element = MemSplit::new(ty.element());
|
||||||
if let Self::Single {
|
if let Self::Single {
|
||||||
|
|
@ -192,6 +194,8 @@ impl MemSplit {
|
||||||
| CanonicalType::AsyncReset(_)
|
| CanonicalType::AsyncReset(_)
|
||||||
| CanonicalType::SyncReset(_)
|
| CanonicalType::SyncReset(_)
|
||||||
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
|
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
|
||||||
|
CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"),
|
||||||
|
CanonicalType::TraceAsString(_) => unreachable!("handled by unwrap_transparent_types"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +307,9 @@ impl SplitMemState<'_, '_> {
|
||||||
let outer_mem_name_path_len = self.mem_name_path.len();
|
let outer_mem_name_path_len = self.mem_name_path.len();
|
||||||
match self.split {
|
match self.split {
|
||||||
MemSplit::Bundle { fields } => {
|
MemSplit::Bundle { fields } => {
|
||||||
let CanonicalType::Bundle(bundle_type) = self.element_type else {
|
let CanonicalType::Bundle(bundle_type) =
|
||||||
|
self.element_type.unwrap_transparent_types()
|
||||||
|
else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
for ((field, field_offset), split) in bundle_type
|
for ((field, field_offset), split) in bundle_type
|
||||||
|
|
@ -318,9 +324,15 @@ impl SplitMemState<'_, '_> {
|
||||||
let field_ty_bit_width = field.ty.bit_width();
|
let field_ty_bit_width = field.ty.bit_width();
|
||||||
self.split_state_stack.push_map(
|
self.split_state_stack.push_map(
|
||||||
|e: Expr<CanonicalType>| {
|
|e: Expr<CanonicalType>| {
|
||||||
Expr::field(Expr::<Bundle>::from_canonical(e), &field.name)
|
Expr::field(
|
||||||
|
Expr::<Bundle>::from_canonical(Expr::unwrap_transparent_types(e)),
|
||||||
|
&field.name,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|initial_value_element| {
|
|initial_value_element| {
|
||||||
|
let Some(field_offset) = field_offset.only_bit_width() else {
|
||||||
|
todo!("memory containing sim-only values");
|
||||||
|
};
|
||||||
&initial_value_element[field_offset..][..field_ty_bit_width]
|
&initial_value_element[field_offset..][..field_ty_bit_width]
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -339,6 +351,7 @@ impl SplitMemState<'_, '_> {
|
||||||
self.split_state_stack.pop();
|
self.split_state_stack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MemSplit::PhantomConst => {}
|
||||||
MemSplit::Single {
|
MemSplit::Single {
|
||||||
output_mem,
|
output_mem,
|
||||||
element_type: single_type,
|
element_type: single_type,
|
||||||
|
|
@ -370,8 +383,8 @@ impl SplitMemState<'_, '_> {
|
||||||
};
|
};
|
||||||
self.output_stmts.push(
|
self.output_stmts.push(
|
||||||
StmtConnect {
|
StmtConnect {
|
||||||
lhs: Expr::field(port_expr, name),
|
lhs: Expr::unwrap_transparent_types(Expr::field(port_expr, name)),
|
||||||
rhs: Expr::field(wire_expr, name),
|
rhs: Expr::unwrap_transparent_types(Expr::field(wire_expr, name)),
|
||||||
source_location: port.source_location(),
|
source_location: port.source_location(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -382,7 +395,8 @@ impl SplitMemState<'_, '_> {
|
||||||
self.output_mems.push(new_mem);
|
self.output_mems.push(new_mem);
|
||||||
}
|
}
|
||||||
MemSplit::Array { elements } => {
|
MemSplit::Array { elements } => {
|
||||||
let CanonicalType::Array(array_type) = self.element_type else {
|
let CanonicalType::Array(array_type) = self.element_type.unwrap_transparent_types()
|
||||||
|
else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
let element_type = array_type.element();
|
let element_type = array_type.element();
|
||||||
|
|
@ -391,7 +405,7 @@ impl SplitMemState<'_, '_> {
|
||||||
self.mem_name_path.truncate(outer_mem_name_path_len);
|
self.mem_name_path.truncate(outer_mem_name_path_len);
|
||||||
write!(self.mem_name_path, "_{index}").unwrap();
|
write!(self.mem_name_path, "_{index}").unwrap();
|
||||||
self.split_state_stack.push_map(
|
self.split_state_stack.push_map(
|
||||||
|e| Expr::<Array>::from_canonical(e)[index],
|
|e| Expr::<Array>::from_canonical(Expr::unwrap_transparent_types(e))[index],
|
||||||
|initial_value_element| {
|
|initial_value_element| {
|
||||||
&initial_value_element[index * element_bit_width..][..element_bit_width]
|
&initial_value_element[index * element_bit_width..][..element_bit_width]
|
||||||
},
|
},
|
||||||
|
|
@ -417,7 +431,6 @@ impl SplitMemState<'_, '_> {
|
||||||
|
|
||||||
struct ModuleState {
|
struct ModuleState {
|
||||||
output_module: Option<Interned<Module<Bundle>>>,
|
output_module: Option<Interned<Module<Bundle>>>,
|
||||||
name_id_gen: NameIdGen,
|
|
||||||
memories: HashMap<ScopedNameId, MemState>,
|
memories: HashMap<ScopedNameId, MemState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -458,7 +471,7 @@ impl ModuleState {
|
||||||
assert_eq!(memory_element_array_range_len % input_array_type.len(), 0);
|
assert_eq!(memory_element_array_range_len % input_array_type.len(), 0);
|
||||||
let chunk_size = memory_element_array_range_len / input_array_type.len();
|
let chunk_size = memory_element_array_range_len / input_array_type.len();
|
||||||
for index in 0..input_array_type.len() {
|
for index in 0..input_array_type.len() {
|
||||||
let map = |e| Expr::<Array>::from_canonical(e)[index];
|
let map = |e| Expr::<Array>::from_canonical(Expr::unwrap_transparent_types(e))[index];
|
||||||
let wire_rdata = wire_rdata.map(map);
|
let wire_rdata = wire_rdata.map(map);
|
||||||
let wire_wdata = wire_wdata.map(map);
|
let wire_wdata = wire_wdata.map(map);
|
||||||
let wire_wmask = wire_wmask.map(map);
|
let wire_wmask = wire_wmask.map(map);
|
||||||
|
|
@ -499,8 +512,8 @@ impl ModuleState {
|
||||||
port_read: Expr<CanonicalType>| {
|
port_read: Expr<CanonicalType>| {
|
||||||
output_stmts.push(
|
output_stmts.push(
|
||||||
StmtConnect {
|
StmtConnect {
|
||||||
lhs: wire_read,
|
lhs: Expr::unwrap_transparent_types(wire_read),
|
||||||
rhs: port_read,
|
rhs: Expr::unwrap_transparent_types(port_read),
|
||||||
source_location,
|
source_location,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -511,8 +524,8 @@ impl ModuleState {
|
||||||
port_write: Expr<CanonicalType>| {
|
port_write: Expr<CanonicalType>| {
|
||||||
output_stmts.push(
|
output_stmts.push(
|
||||||
StmtConnect {
|
StmtConnect {
|
||||||
lhs: port_write,
|
lhs: Expr::unwrap_transparent_types(port_write),
|
||||||
rhs: wire_write,
|
rhs: Expr::unwrap_transparent_types(wire_write),
|
||||||
source_location,
|
source_location,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -524,7 +537,8 @@ impl ModuleState {
|
||||||
connect_read(
|
connect_read(
|
||||||
output_stmts,
|
output_stmts,
|
||||||
wire_read,
|
wire_read,
|
||||||
Expr::<UInt>::from_canonical(port_read).cast_bits_to(Expr::ty(wire_read)),
|
Expr::<UInt>::from_canonical(Expr::unwrap_transparent_types(port_read))
|
||||||
|
.cast_bits_to(wire_read.ty()),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let connect_write_enum =
|
let connect_write_enum =
|
||||||
|
|
@ -538,8 +552,13 @@ impl ModuleState {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
match input_element_type {
|
match input_element_type.unwrap_transparent_types() {
|
||||||
CanonicalType::Bundle(_) => unreachable!("bundle types are always split"),
|
CanonicalType::Bundle(_) => {
|
||||||
|
unreachable!("bundle types are always split")
|
||||||
|
}
|
||||||
|
CanonicalType::PhantomConst(_) => {
|
||||||
|
unreachable!("PhantomConst are always removed")
|
||||||
|
}
|
||||||
CanonicalType::Enum(_)
|
CanonicalType::Enum(_)
|
||||||
if input_array_types
|
if input_array_types
|
||||||
.first()
|
.first()
|
||||||
|
|
@ -569,7 +588,7 @@ impl ModuleState {
|
||||||
port_wmask.map(Expr::from_canonical),
|
port_wmask.map(Expr::from_canonical),
|
||||||
connect_read_enum,
|
connect_read_enum,
|
||||||
connect_write_enum,
|
connect_write_enum,
|
||||||
connect_write_enum,
|
connect_write,
|
||||||
),
|
),
|
||||||
CanonicalType::Array(array_type) => {
|
CanonicalType::Array(array_type) => {
|
||||||
input_array_types.push(array_type);
|
input_array_types.push(array_type);
|
||||||
|
|
@ -613,6 +632,10 @@ impl ModuleState {
|
||||||
| CanonicalType::AsyncReset(_)
|
| CanonicalType::AsyncReset(_)
|
||||||
| CanonicalType::SyncReset(_)
|
| CanonicalType::SyncReset(_)
|
||||||
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
|
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
|
||||||
|
CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"),
|
||||||
|
CanonicalType::TraceAsString(_) => {
|
||||||
|
unreachable!("handled by unwrap_transparent_types")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -626,10 +649,10 @@ impl ModuleState {
|
||||||
mem_name_path: &str,
|
mem_name_path: &str,
|
||||||
split_state: &SplitState<'_>,
|
split_state: &SplitState<'_>,
|
||||||
) -> Mem {
|
) -> Mem {
|
||||||
let mem_name = self.name_id_gen.gen(Intern::intern_owned(format!(
|
let mem_name = NameId(
|
||||||
"{}{mem_name_path}",
|
Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1.0)),
|
||||||
input_mem.scoped_name().1 .0
|
Id::new(),
|
||||||
)));
|
);
|
||||||
let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name);
|
let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name);
|
||||||
let output_element_type = match single_type {
|
let output_element_type = match single_type {
|
||||||
SingleType::UInt(ty) => ty.canonical(),
|
SingleType::UInt(ty) => ty.canonical(),
|
||||||
|
|
@ -744,7 +767,8 @@ impl ModuleState {
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
| MemSplit::Bundle { .. }
|
| MemSplit::Bundle { .. }
|
||||||
| MemSplit::Array { .. } => {
|
| MemSplit::Array { .. }
|
||||||
|
| MemSplit::PhantomConst => {
|
||||||
let mut replacement_ports = Vec::with_capacity(input_mem.ports().len());
|
let mut replacement_ports = Vec::with_capacity(input_mem.ports().len());
|
||||||
let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len());
|
let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len());
|
||||||
let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len());
|
let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len());
|
||||||
|
|
@ -753,9 +777,10 @@ impl ModuleState {
|
||||||
let port_ty = port.ty();
|
let port_ty = port.ty();
|
||||||
let NameId(mem_name, _) = input_mem.scoped_name().1;
|
let NameId(mem_name, _) = input_mem.scoped_name().1;
|
||||||
let port_name = port.port_name();
|
let port_name = port.port_name();
|
||||||
let wire_name = self
|
let wire_name = NameId(
|
||||||
.name_id_gen
|
Intern::intern_owned(format!("{mem_name}_{port_name}")),
|
||||||
.gen(Intern::intern_owned(format!("{mem_name}_{port_name}")));
|
Id::new(),
|
||||||
|
);
|
||||||
let wire = Wire::new_unchecked(
|
let wire = Wire::new_unchecked(
|
||||||
ScopedNameId(input_mem.scoped_name().0, wire_name),
|
ScopedNameId(input_mem.scoped_name().0, wire_name),
|
||||||
port.source_location(),
|
port.source_location(),
|
||||||
|
|
@ -766,7 +791,7 @@ impl ModuleState {
|
||||||
output_stmts.push(
|
output_stmts.push(
|
||||||
StmtWire {
|
StmtWire {
|
||||||
annotations: Default::default(),
|
annotations: Default::default(),
|
||||||
wire: canonical_wire.clone(),
|
wire: canonical_wire,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
@ -887,8 +912,7 @@ impl Folder for State {
|
||||||
module,
|
module,
|
||||||
ModuleState {
|
ModuleState {
|
||||||
output_module: None,
|
output_module: None,
|
||||||
name_id_gen: NameIdGen::for_module(*module),
|
memories: HashMap::default(),
|
||||||
memories: HashMap::new(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let mut this = PushedState::push_module(self, module);
|
let mut this = PushedState::push_module(self, module);
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,41 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
#![allow(clippy::multiple_bound_locations)]
|
#![allow(clippy::multiple_bound_locations)]
|
||||||
use crate::{
|
use crate::{
|
||||||
annotations::{Annotation, CustomFirrtlAnnotation, TargetedAnnotation},
|
annotations::{
|
||||||
|
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
|
||||||
|
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
|
||||||
|
},
|
||||||
array::ArrayType,
|
array::ArrayType,
|
||||||
bundle::{Bundle, BundleField, BundleType},
|
bundle::{Bundle, BundleField, BundleType},
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
enum_::{Enum, EnumType, EnumVariant},
|
enum_::{Enum, EnumType, EnumVariant},
|
||||||
expr::{
|
expr::{
|
||||||
ops,
|
Expr, ExprEnum, ValueType, ops,
|
||||||
target::{
|
target::{
|
||||||
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
|
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
|
||||||
TargetPathDynArrayElement, TargetPathElement,
|
TargetPathDynArrayElement, TargetPathElement, TargetPathToTraceAsString,
|
||||||
|
TargetPathTraceAsStringInner,
|
||||||
},
|
},
|
||||||
Expr, ExprEnum,
|
|
||||||
},
|
},
|
||||||
|
formal::{FormalInput, FormalInputKind, FormalKind},
|
||||||
int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue},
|
int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue},
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
memory::{Mem, MemPort, PortKind, PortName, PortType, ReadUnderWrite},
|
memory::{Mem, MemPort, PortKind, PortName, PortType, ReadUnderWrite},
|
||||||
module::{
|
module::{
|
||||||
AnnotatedModuleIO, Block, BlockId, ExternModuleBody, ExternModuleParameter,
|
AnnotatedModuleIO, Block, BlockId, ExternModuleBody, ExternModuleParameter,
|
||||||
ExternModuleParameterValue, Instance, Module, ModuleBody, ModuleIO, NameId,
|
ExternModuleParameterValue, Instance, Module, ModuleBody, ModuleIO, NameId, NameIdOrGlobal,
|
||||||
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtIf, StmtInstance,
|
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf,
|
||||||
StmtMatch, StmtReg, StmtWire,
|
StmtInstance, StmtMatch, StmtReg, StmtWire,
|
||||||
},
|
},
|
||||||
|
phantom_const::PhantomConst,
|
||||||
reg::Reg,
|
reg::Reg,
|
||||||
reset::{AsyncReset, Reset, SyncReset},
|
reset::{AsyncReset, Reset, ResetType, SyncReset},
|
||||||
|
sim::{ExternModuleSimulation, value::DynSimOnly},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{CanonicalType, Type},
|
ty::{CanonicalType, TraceAsString, Type},
|
||||||
|
vendor::xilinx::{
|
||||||
|
XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation,
|
||||||
|
},
|
||||||
wire::Wire,
|
wire::Wire,
|
||||||
};
|
};
|
||||||
use num_bigint::{BigInt, BigUint};
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
|
@ -473,4 +482,30 @@ impl<T: ?Sized + Visit<State>, State: ?Sized + Visitor> Visit<State> for &'_ mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<State: ?Sized + Visitor> Visit<State> for NameIdOrGlobal {
|
||||||
|
fn visit(&self, state: &mut State) -> Result<(), <State>::Error> {
|
||||||
|
state.visit_name_id_or_global(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_visit(&self, state: &mut State) -> Result<(), <State>::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Global => Ok(()),
|
||||||
|
Self::NameId(name_id) => name_id.visit(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<State: ?Sized + Folder> Fold<State> for NameIdOrGlobal {
|
||||||
|
fn fold(self, state: &mut State) -> Result<Self, <State>::Error> {
|
||||||
|
state.fold_name_id_or_global(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_fold(self, state: &mut State) -> Result<Self, <State>::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Global => Ok(Self::Global),
|
||||||
|
Self::NameId(name_id) => Ok(Self::NameId(name_id.fold(state)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/visit.rs"));
|
include!(concat!(env!("OUT_DIR"), "/visit.rs"));
|
||||||
|
|
|
||||||
567
crates/fayalite/src/phantom_const.rs
Normal file
567
crates/fayalite/src/phantom_const.rs
Normal file
|
|
@ -0,0 +1,567 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
expr::{Expr, HdlPartialEqImpl, HdlPartialOrdImpl, ToExpr, ValueType},
|
||||||
|
int::Bool,
|
||||||
|
intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize},
|
||||||
|
sim::value::{SimValue, ToSimValue, ToSimValueWithType},
|
||||||
|
source_location::SourceLocation,
|
||||||
|
ty::{
|
||||||
|
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
|
||||||
|
SimValueDebug, StaticType, Type, TypeProperties, impl_match_variant_as_self,
|
||||||
|
serde_impls::{SerdeCanonicalType, SerdePhantomConst},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::{DeserializeOwned, Error},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
borrow::Cow,
|
||||||
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::Index,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PhantomConstCanonicalValue {
|
||||||
|
parsed: serde_json::Value,
|
||||||
|
serialized: Interned<str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhantomConstCanonicalValue {
|
||||||
|
pub fn from_json_value(parsed: serde_json::Value) -> Self {
|
||||||
|
let serialized = Intern::intern_owned(
|
||||||
|
serde_json::to_string(&parsed)
|
||||||
|
.expect("conversion from json value to text shouldn't fail"),
|
||||||
|
);
|
||||||
|
Self { parsed, serialized }
|
||||||
|
}
|
||||||
|
pub fn as_json_value(&self) -> &serde_json::Value {
|
||||||
|
&self.parsed
|
||||||
|
}
|
||||||
|
pub fn as_str(&self) -> Interned<str> {
|
||||||
|
self.serialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for PhantomConstCanonicalValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.serialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PhantomConstCanonicalValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.serialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for PhantomConstCanonicalValue {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.serialized == other.serialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for PhantomConstCanonicalValue {}
|
||||||
|
|
||||||
|
impl Hash for PhantomConstCanonicalValue {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.serialized.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for PhantomConstCanonicalValue {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
self.parsed.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for PhantomConstCanonicalValue {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(Self::from_json_value(serde_json::Value::deserialize(
|
||||||
|
deserializer,
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug {
|
||||||
|
fn deserialize_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PhantomConstValue for T
|
||||||
|
where
|
||||||
|
T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug,
|
||||||
|
Interned<T>: DeserializeOwned,
|
||||||
|
{
|
||||||
|
fn deserialize_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
<Interned<T> as Deserialize<'de>>::deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper type that allows any Rust value to be smuggled as a HDL [`Type`].
|
||||||
|
/// This only works for values that can be [serialized][Serialize] to and [deserialized][Deserialize] from [JSON][serde_json].
|
||||||
|
pub struct PhantomConst<T: ?Sized + PhantomConstValue = PhantomConstCanonicalValue> {
|
||||||
|
value: LazyInterned<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
|
||||||
|
pub struct PhantomConstWithoutGenerics;
|
||||||
|
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const PhantomConst: PhantomConstWithoutGenerics = PhantomConstWithoutGenerics;
|
||||||
|
|
||||||
|
impl<T: Type + PhantomConstValue> Index<T> for PhantomConstWithoutGenerics {
|
||||||
|
type Output = PhantomConst<T>;
|
||||||
|
|
||||||
|
fn index(&self, value: T) -> &Self::Output {
|
||||||
|
Interned::into_inner(PhantomConst::new(&value).intern_sized())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> fmt::Debug for PhantomConst<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("PhantomConst").field(&self.get()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Clone for PhantomConst<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Copy for PhantomConst<T> {}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> PartialEq for PhantomConst<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.get() == other.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Eq for PhantomConst<T> {}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Hash for PhantomConst<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.get().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PhantomConstCanonicalMemoize<T: ?Sized, const IS_FROM_CANONICAL: bool>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Copy
|
||||||
|
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Clone
|
||||||
|
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Eq
|
||||||
|
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> PartialEq
|
||||||
|
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
|
||||||
|
{
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Hash
|
||||||
|
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
|
||||||
|
{
|
||||||
|
fn hash<H: Hasher>(&self, _state: &mut H) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, false> {
|
||||||
|
type Input = Interned<T>;
|
||||||
|
type InputOwned = Interned<T>;
|
||||||
|
type Output = Interned<PhantomConstCanonicalValue>;
|
||||||
|
|
||||||
|
fn inner(self, input: &Self::Input) -> Self::Output {
|
||||||
|
Intern::intern_sized(PhantomConstCanonicalValue::from_json_value(
|
||||||
|
serde_json::to_value(input)
|
||||||
|
.expect("serialization failed when constructing a canonical PhantomConst"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, true> {
|
||||||
|
type Input = Interned<PhantomConstCanonicalValue>;
|
||||||
|
type InputOwned = Interned<PhantomConstCanonicalValue>;
|
||||||
|
type Output = Interned<T>;
|
||||||
|
|
||||||
|
fn inner(self, input: &Self::Input) -> Self::Output {
|
||||||
|
PhantomConstValue::deserialize_value(input.as_json_value())
|
||||||
|
.expect("deserialization failed ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
|
||||||
|
pub fn new_interned(value: Interned<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
value: LazyInterned::Interned(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new_sized(value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
Self::new_interned(value.intern_sized())
|
||||||
|
}
|
||||||
|
pub fn new(value: &T) -> Self {
|
||||||
|
Self::new_interned(value.intern())
|
||||||
|
}
|
||||||
|
pub fn new_deref<U: Intern + std::ops::Deref<Target = T>>(value: U) -> Self
|
||||||
|
where
|
||||||
|
T: ToOwned<Owned = U>,
|
||||||
|
{
|
||||||
|
Self::new_interned(value.intern_deref())
|
||||||
|
}
|
||||||
|
pub const fn new_const<V: Default + Into<Interned<T>>>() -> Self {
|
||||||
|
Self {
|
||||||
|
value: LazyInterned::new_const::<V>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn new_const_default() -> Self
|
||||||
|
where
|
||||||
|
Interned<T>: Default,
|
||||||
|
{
|
||||||
|
Self::new_const::<Interned<T>>()
|
||||||
|
}
|
||||||
|
pub fn get(self) -> Interned<T> {
|
||||||
|
self.value.interned()
|
||||||
|
}
|
||||||
|
pub fn type_properties(self) -> TypeProperties {
|
||||||
|
<()>::TYPE_PROPERTIES
|
||||||
|
}
|
||||||
|
pub fn can_connect(self, other: Self) -> bool {
|
||||||
|
self == other
|
||||||
|
}
|
||||||
|
pub fn canonical_phantom_const(self) -> PhantomConst {
|
||||||
|
if let Some(&retval) = <dyn Any>::downcast_ref::<PhantomConst>(&self) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
<PhantomConst>::new_interned(
|
||||||
|
PhantomConstCanonicalMemoize::<T, false>(PhantomData).get_owned(self.get()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self {
|
||||||
|
if let Some(&retval) = <dyn Any>::downcast_ref::<Self>(&canonical_type) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
Self::new_interned(
|
||||||
|
PhantomConstCanonicalMemoize::<T, true>(PhantomData).get_owned(canonical_type.get()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Type for PhantomConst<T> {
|
||||||
|
type BaseType = PhantomConst;
|
||||||
|
type MaskType = ();
|
||||||
|
type SimValue = PhantomConst<T>;
|
||||||
|
impl_match_variant_as_self!();
|
||||||
|
|
||||||
|
fn mask_type(&self) -> Self::MaskType {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical(&self) -> CanonicalType {
|
||||||
|
CanonicalType::PhantomConst(self.canonical_phantom_const())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_canonical(canonical_type: CanonicalType) -> Self {
|
||||||
|
let CanonicalType::PhantomConst(phantom_const) = canonical_type else {
|
||||||
|
panic!("expected PhantomConst");
|
||||||
|
};
|
||||||
|
Self::from_canonical_phantom_const(phantom_const)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source_location() -> SourceLocation {
|
||||||
|
SourceLocation::builtin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert!(opaque.is_empty());
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert!(opaque.is_empty());
|
||||||
|
assert_eq!(*value, *self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
assert_eq!(*value, *self);
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> SimValueDebug for PhantomConst<T> {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Default for PhantomConst<T>
|
||||||
|
where
|
||||||
|
Interned<T>: Default,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> StaticType for PhantomConst<T>
|
||||||
|
where
|
||||||
|
Interned<T>: Default,
|
||||||
|
{
|
||||||
|
const TYPE: Self = Self::new_const_default();
|
||||||
|
const MASK_TYPE: Self::MaskType = ();
|
||||||
|
const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
|
||||||
|
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SerdeType<T> = SerdeCanonicalType<CanonicalType, SerdePhantomConst<Interned<T>>>;
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Serialize for PhantomConst<T> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
SerdeType::<T>::PhantomConst(SerdePhantomConst(self.get())).serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst<T> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
match SerdeType::<T>::deserialize(deserializer)? {
|
||||||
|
SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => {
|
||||||
|
Ok(Self::new_interned(value))
|
||||||
|
}
|
||||||
|
ty => Err(Error::invalid_value(
|
||||||
|
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
|
||||||
|
&"a PhantomConst",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> HdlPartialEqImpl<Self> for PhantomConst<T> {
|
||||||
|
const TRY_STRUCTURAL_EQ: bool = true;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_eq(
|
||||||
|
lhs: Self,
|
||||||
|
_lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: Self,
|
||||||
|
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs, rhs);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
true.to_expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
false.to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> HdlPartialOrdImpl<Self> for PhantomConst<T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_lt(
|
||||||
|
lhs: Self,
|
||||||
|
_lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: Self,
|
||||||
|
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs, rhs);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_le(
|
||||||
|
lhs: Self,
|
||||||
|
_lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: Self,
|
||||||
|
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs, rhs);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_gt(
|
||||||
|
lhs: Self,
|
||||||
|
_lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: Self,
|
||||||
|
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs, rhs);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_value_ge(
|
||||||
|
lhs: Self,
|
||||||
|
_lhs_value: Cow<'_, Self::SimValue>,
|
||||||
|
rhs: Self,
|
||||||
|
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
|
||||||
|
) -> bool {
|
||||||
|
assert_eq!(lhs, rhs);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
false.to_expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
true.to_expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
false.to_expr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
|
||||||
|
assert_eq!(lhs.ty(), rhs.ty());
|
||||||
|
true.to_expr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> ToSimValue for PhantomConst<T> {
|
||||||
|
fn to_sim_value(&self) -> SimValue<Self::Type> {
|
||||||
|
SimValue::from_value(*self, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<PhantomConst<T>> for PhantomConst<T> {
|
||||||
|
fn to_sim_value_with_type(&self, ty: PhantomConst<T>) -> SimValue<PhantomConst<T>> {
|
||||||
|
SimValue::from_value(ty, *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<CanonicalType> for PhantomConst<T> {
|
||||||
|
fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue<CanonicalType> {
|
||||||
|
SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<T: ?Sized> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhantomConstGet<T: ?Sized + PhantomConstValue>: sealed::Sealed<T> {
|
||||||
|
fn get(&self) -> Interned<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
|
||||||
|
sealed::Sealed<T> for This
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
|
||||||
|
PhantomConstGet<T> for This
|
||||||
|
{
|
||||||
|
fn get(&self) -> Interned<T> {
|
||||||
|
This::Target::get(&**self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_phantom_const_get {
|
||||||
|
(
|
||||||
|
impl PhantomConstGet<$T:ident> for $ty:ty {
|
||||||
|
fn $get:ident(&$get_self:ident) -> _ $get_body:block
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {}
|
||||||
|
|
||||||
|
impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty {
|
||||||
|
fn $get(&$get_self) -> Interned<$T> $get_body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_phantom_const_get! {
|
||||||
|
impl PhantomConstGet<T> for PhantomConst<T> {
|
||||||
|
fn get(&self) -> _ {
|
||||||
|
PhantomConst::get(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_phantom_const_get! {
|
||||||
|
impl PhantomConstGet<T> for Expr<PhantomConst<T>> {
|
||||||
|
fn get(&self) -> _ {
|
||||||
|
PhantomConst::get(self.ty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait ReturnSelfUnchanged<T: ?Sized> {
|
||||||
|
type Type: ?Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<This: ?Sized, T: ?Sized> ReturnSelfUnchanged<T> for This {
|
||||||
|
type Type = This;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn type_alias_phantom_const_get_helper<T: ?Sized + PhantomConstValue, R: Intern + Clone>(
|
||||||
|
param: impl PhantomConstGet<T>,
|
||||||
|
get: impl FnOnce(Interned<T>) -> R,
|
||||||
|
) -> &'static R {
|
||||||
|
Interned::into_inner(get(param.get()).intern_sized())
|
||||||
|
}
|
||||||
1923
crates/fayalite/src/platform.rs
Normal file
1923
crates/fayalite/src/platform.rs
Normal file
File diff suppressed because it is too large
Load diff
59
crates/fayalite/src/platform/peripherals.rs
Normal file
59
crates/fayalite/src/platform/peripherals.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use ordered_float::NotNan;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct ClockInputProperties {
|
||||||
|
pub frequency: NotNan<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl(no_runtime_generics, no_static)]
|
||||||
|
pub struct ClockInput {
|
||||||
|
pub clk: Clock,
|
||||||
|
pub properties: PhantomConst<ClockInputProperties>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockInput {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(frequency: f64) -> Self {
|
||||||
|
assert!(
|
||||||
|
frequency > 0.0 && frequency.is_finite(),
|
||||||
|
"invalid clock frequency: {frequency}"
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
clk: Clock,
|
||||||
|
properties: PhantomConst::new_sized(ClockInputProperties {
|
||||||
|
frequency: NotNan::new(frequency).expect("just checked"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn frequency(self) -> f64 {
|
||||||
|
self.properties.get().frequency.into_inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
pub struct Led {
|
||||||
|
pub on: Bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
pub struct RgbLed {
|
||||||
|
pub r: Bool,
|
||||||
|
pub g: Bool,
|
||||||
|
pub b: Bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
/// UART, used as an output from the FPGA
|
||||||
|
pub struct Uart {
|
||||||
|
/// transmit from the FPGA's perspective
|
||||||
|
pub tx: Bool,
|
||||||
|
/// receive from the FPGA's perspective
|
||||||
|
#[hdl(flip)]
|
||||||
|
pub rx: Bool,
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,45 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
annotations::Annotation,
|
__,
|
||||||
|
annotations::{
|
||||||
|
BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
|
||||||
|
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
|
||||||
|
},
|
||||||
array::{Array, ArrayType},
|
array::{Array, ArrayType},
|
||||||
cli::Cli,
|
build::{BuildCli, JobParams, RunBuild},
|
||||||
|
bundle::Bundle,
|
||||||
clock::{Clock, ClockDomain, ToClock},
|
clock::{Clock, ClockDomain, ToClock},
|
||||||
enum_::{HdlNone, HdlOption, HdlSome},
|
enum_::{Enum, HdlNone, HdlOption, HdlSome},
|
||||||
expr::{CastBitsTo, CastTo, CastToBits, Expr, ReduceBits, ToExpr},
|
expr::{
|
||||||
|
CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr,
|
||||||
|
ReduceBits, ToExpr, ToTraceAsString, ValueType, repeat,
|
||||||
|
},
|
||||||
|
formal::{
|
||||||
|
all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset, hdl_assert,
|
||||||
|
hdl_assert_with_enable, hdl_assume, hdl_assume_with_enable, hdl_cover,
|
||||||
|
hdl_cover_with_enable,
|
||||||
|
},
|
||||||
hdl, hdl_module,
|
hdl, hdl_module,
|
||||||
int::{Bool, DynSize, IntCmp, KnownSize, SInt, SIntType, Size, UInt, UIntType},
|
int::{Bool, DynSize, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue},
|
||||||
memory::{Mem, MemBuilder, ReadUnderWrite},
|
memory::{Mem, MemBuilder, ReadUnderWrite},
|
||||||
module::{
|
module::{
|
||||||
annotate, connect, connect_any, instance, memory, memory_array, memory_with_init,
|
Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance,
|
||||||
reg_builder, wire, Instance, Module, ModuleBuilder,
|
memory, memory_array, memory_with_init, reg_builder, wire,
|
||||||
},
|
},
|
||||||
|
phantom_const::{PhantomConst, PhantomConstGet},
|
||||||
|
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
|
||||||
reg::Reg,
|
reg::Reg,
|
||||||
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
|
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
|
||||||
|
sim::{
|
||||||
|
ExternModuleSimulationState, Simulation,
|
||||||
|
time::{SimDuration, SimInstant},
|
||||||
|
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
|
||||||
|
},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{AsMask, CanonicalType, Type},
|
testing::{FormalMode, assert_formal, checked_vcd_output},
|
||||||
|
ty::{AsMask, CanonicalType, TraceAsString, Type},
|
||||||
util::{ConstUsize, GenericConstUsize},
|
util::{ConstUsize, GenericConstUsize},
|
||||||
wire::Wire,
|
wire::Wire,
|
||||||
__,
|
|
||||||
};
|
};
|
||||||
|
pub use bitvec::{slice::BitSlice, vec::BitVec};
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,34 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
clock::ClockDomain,
|
clock::ClockDomain,
|
||||||
expr::{Expr, Flow},
|
expr::{Expr, Flow, ValueType, value_category::ValueCategoryExpr},
|
||||||
intern::Interned,
|
intern::Interned,
|
||||||
module::{NameId, ScopedNameId},
|
module::{NameId, ScopedNameId},
|
||||||
|
reset::{Reset, ResetType},
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{CanonicalType, Type},
|
ty::{CanonicalType, Type},
|
||||||
};
|
};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Reg<T: Type> {
|
pub struct Reg<T: Type, R: ResetType = Reset> {
|
||||||
name: ScopedNameId,
|
name: ScopedNameId,
|
||||||
source_location: SourceLocation,
|
source_location: SourceLocation,
|
||||||
ty: T,
|
ty: T,
|
||||||
clock_domain: Expr<ClockDomain>,
|
clock_domain: Expr<ClockDomain<R>>,
|
||||||
init: Option<Expr<T>>,
|
init: Option<Expr<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
|
impl<T: Type, R: ResetType> ValueType for Reg<T, R> {
|
||||||
|
type Type = T;
|
||||||
|
type ValueCategory = ValueCategoryExpr;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
self.ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type, R: ResetType> fmt::Debug for Reg<T, R> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
|
|
@ -37,8 +47,8 @@ impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Type> Reg<T> {
|
impl<T: Type, R: ResetType> Reg<T, R> {
|
||||||
pub fn canonical(&self) -> Reg<CanonicalType> {
|
pub fn canonical(&self) -> Reg<CanonicalType, R> {
|
||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
source_location,
|
source_location,
|
||||||
|
|
@ -59,7 +69,7 @@ impl<T: Type> Reg<T> {
|
||||||
scoped_name: ScopedNameId,
|
scoped_name: ScopedNameId,
|
||||||
source_location: SourceLocation,
|
source_location: SourceLocation,
|
||||||
ty: T,
|
ty: T,
|
||||||
clock_domain: Expr<ClockDomain>,
|
clock_domain: Expr<ClockDomain<R>>,
|
||||||
init: Option<Expr<T>>,
|
init: Option<Expr<T>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -67,8 +77,9 @@ impl<T: Type> Reg<T> {
|
||||||
"register type must be a storable type"
|
"register type must be a storable type"
|
||||||
);
|
);
|
||||||
if let Some(init) = init {
|
if let Some(init) = init {
|
||||||
assert_eq!(ty, Expr::ty(init), "register's type must match init type");
|
assert_eq!(ty, init.ty(), "register's type must match init type");
|
||||||
}
|
}
|
||||||
|
scoped_name.0.assert_is_name_id();
|
||||||
Self {
|
Self {
|
||||||
name: scoped_name,
|
name: scoped_name,
|
||||||
source_location,
|
source_location,
|
||||||
|
|
@ -77,9 +88,6 @@ impl<T: Type> Reg<T> {
|
||||||
init,
|
init,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn ty(&self) -> T {
|
|
||||||
self.ty
|
|
||||||
}
|
|
||||||
pub fn source_location(&self) -> SourceLocation {
|
pub fn source_location(&self) -> SourceLocation {
|
||||||
self.source_location
|
self.source_location
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +95,7 @@ impl<T: Type> Reg<T> {
|
||||||
self.containing_module_name_id().0
|
self.containing_module_name_id().0
|
||||||
}
|
}
|
||||||
pub fn containing_module_name_id(&self) -> NameId {
|
pub fn containing_module_name_id(&self) -> NameId {
|
||||||
self.name.0
|
self.name.0.unwrap_name_id()
|
||||||
}
|
}
|
||||||
pub fn name(&self) -> Interned<str> {
|
pub fn name(&self) -> Interned<str> {
|
||||||
self.name_id().0
|
self.name_id().0
|
||||||
|
|
@ -98,7 +106,7 @@ impl<T: Type> Reg<T> {
|
||||||
pub fn scoped_name(&self) -> ScopedNameId {
|
pub fn scoped_name(&self) -> ScopedNameId {
|
||||||
self.name
|
self.name
|
||||||
}
|
}
|
||||||
pub fn clock_domain(&self) -> Expr<ClockDomain> {
|
pub fn clock_domain(&self) -> Expr<ClockDomain<R>> {
|
||||||
self.clock_domain
|
self.clock_domain
|
||||||
}
|
}
|
||||||
pub fn init(&self) -> Option<Expr<T>> {
|
pub fn init(&self) -> Option<Expr<T>> {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,60 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
expr::{Expr, ToExpr},
|
clock::Clock,
|
||||||
int::Bool,
|
expr::{CastToImpl, Expr, ValueType},
|
||||||
|
int::{Bool, SInt, SIntValue, UInt, UIntValue},
|
||||||
|
sim::value::SimValue,
|
||||||
source_location::SourceLocation,
|
source_location::SourceLocation,
|
||||||
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
|
ty::{
|
||||||
|
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
|
||||||
|
OpaqueSimValueWritten, SimValueDebug, StaticType, Type, TypeProperties,
|
||||||
|
impl_match_variant_as_self,
|
||||||
|
},
|
||||||
|
util::ConstUsize,
|
||||||
};
|
};
|
||||||
|
use bitvec::{bits, order::Lsb0};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
pub trait ResetTypeSealed {}
|
pub trait ResetTypeSealed {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ResetType: StaticType<MaskType = Bool> + sealed::ResetTypeSealed {}
|
pub trait ResetType:
|
||||||
|
StaticType<MaskType = Bool>
|
||||||
|
+ sealed::ResetTypeSealed
|
||||||
|
+ CastToImpl<Bool, ValueOutput = bool>
|
||||||
|
+ CastToImpl<Reset, ValueOutput = SimValue<Reset>>
|
||||||
|
+ CastToImpl<SyncReset, ValueOutput = SimValue<SyncReset>>
|
||||||
|
+ CastToImpl<AsyncReset, ValueOutput = SimValue<AsyncReset>>
|
||||||
|
+ CastToImpl<Clock, ValueOutput = SimValue<Clock>>
|
||||||
|
+ CastToImpl<UInt<1>, ValueOutput = UIntValue<ConstUsize<1>>>
|
||||||
|
+ CastToImpl<SInt<1>, ValueOutput = SIntValue<ConstUsize<1>>>
|
||||||
|
+ CastToImpl<UInt, ValueOutput = UIntValue>
|
||||||
|
+ CastToImpl<SInt, ValueOutput = SIntValue>
|
||||||
|
{
|
||||||
|
fn dispatch<D: ResetTypeDispatch>(input: D::Input<Self>, dispatch: D) -> D::Output<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ResetTypeDispatch: Sized {
|
||||||
|
type Input<T: ResetType>;
|
||||||
|
type Output<T: ResetType>;
|
||||||
|
|
||||||
|
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset>;
|
||||||
|
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset>;
|
||||||
|
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset>;
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! reset_type {
|
macro_rules! reset_type {
|
||||||
($name:ident, $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal) => {
|
($name:ident, $(#[$impl_trait:ident])? $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal, $dispatch_fn:ident) => {
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
|
||||||
pub struct $name;
|
pub struct $name;
|
||||||
|
|
||||||
impl Type for $name {
|
impl Type for $name {
|
||||||
type BaseType = $name;
|
type BaseType = $name;
|
||||||
type MaskType = Bool;
|
type MaskType = Bool;
|
||||||
|
type SimValue = bool;
|
||||||
|
|
||||||
impl_match_variant_as_self!();
|
impl_match_variant_as_self!();
|
||||||
|
|
||||||
|
|
@ -42,6 +76,40 @@ macro_rules! reset_type {
|
||||||
};
|
};
|
||||||
retval
|
retval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
|
||||||
|
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
opaque.bits()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_clone_from_opaque(
|
||||||
|
&self,
|
||||||
|
value: &mut Self::SimValue,
|
||||||
|
opaque: OpaqueSimValueSlice<'_>,
|
||||||
|
) {
|
||||||
|
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
*value = opaque.bits()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_value_to_opaque<'w>(
|
||||||
|
&self,
|
||||||
|
value: &Self::SimValue,
|
||||||
|
writer: OpaqueSimValueWriter<'w>,
|
||||||
|
) -> OpaqueSimValueWritten<'w> {
|
||||||
|
assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1));
|
||||||
|
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
|
||||||
|
[bits![0], bits![1]][*value as usize],
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimValueDebug for $name {
|
||||||
|
fn sim_value_debug(
|
||||||
|
value: &<Self as Type>::SimValue,
|
||||||
|
f: &mut fmt::Formatter<'_>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(value, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
|
|
@ -61,60 +129,76 @@ macro_rules! reset_type {
|
||||||
is_storable: false,
|
is_storable: false,
|
||||||
is_castable_from_bits: $is_castable_from_bits,
|
is_castable_from_bits: $is_castable_from_bits,
|
||||||
bit_width: 1,
|
bit_width: 1,
|
||||||
|
sim_only_values_len: 0,
|
||||||
};
|
};
|
||||||
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
|
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sealed::ResetTypeSealed for $name {}
|
impl sealed::ResetTypeSealed for $name {}
|
||||||
|
|
||||||
impl ResetType for $name {}
|
impl ResetType for $name {
|
||||||
|
fn dispatch<D: ResetTypeDispatch>(
|
||||||
|
input: D::Input<Self>,
|
||||||
|
dispatch: D,
|
||||||
|
) -> D::Output<Self> {
|
||||||
|
dispatch.$dispatch_fn(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait $Trait {
|
pub trait $Trait {
|
||||||
fn $trait_fn(&self) -> Expr<$name>;
|
type Output: ValueType<Type = $name>;
|
||||||
|
fn $trait_fn(&self) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + $Trait> $Trait for &'_ T {
|
impl<T: ?Sized + $Trait> $Trait for &'_ T {
|
||||||
fn $trait_fn(&self) -> Expr<$name> {
|
type Output = T::Output;
|
||||||
|
fn $trait_fn(&self) -> Self::Output {
|
||||||
(**self).$trait_fn()
|
(**self).$trait_fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + $Trait> $Trait for &'_ mut T {
|
impl<T: ?Sized + $Trait> $Trait for &'_ mut T {
|
||||||
fn $trait_fn(&self) -> Expr<$name> {
|
type Output = T::Output;
|
||||||
|
fn $trait_fn(&self) -> Self::Output {
|
||||||
(**self).$trait_fn()
|
(**self).$trait_fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + $Trait> $Trait for Box<T> {
|
impl<T: ?Sized + $Trait> $Trait for Box<T> {
|
||||||
fn $trait_fn(&self) -> Expr<$name> {
|
type Output = T::Output;
|
||||||
|
fn $trait_fn(&self) -> Self::Output {
|
||||||
(**self).$trait_fn()
|
(**self).$trait_fn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $Trait for Expr<$name> {
|
$($impl_trait $Trait for Expr<$name> {
|
||||||
fn $trait_fn(&self) -> Expr<$name> {
|
type Output = Expr<$name>;
|
||||||
|
fn $trait_fn(&self) -> Self::Output {
|
||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
})?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_type!(AsyncReset, ToAsyncReset::to_async_reset, true);
|
reset_type!(AsyncReset, #[impl] ToAsyncReset::to_async_reset, true, async_reset);
|
||||||
reset_type!(SyncReset, ToSyncReset::to_sync_reset, true);
|
reset_type!(SyncReset, #[impl] ToSyncReset::to_sync_reset, true, sync_reset);
|
||||||
reset_type!(
|
reset_type!(
|
||||||
Reset,
|
Reset,
|
||||||
ToReset::to_reset,
|
ToReset::to_reset,
|
||||||
false // Reset is not castable from bits because we don't know if it's async or sync
|
false, // Reset is not castable from bits because we don't know if it's async or sync
|
||||||
|
reset
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ToSyncReset for bool {
|
impl ToSyncReset for bool {
|
||||||
fn to_sync_reset(&self) -> Expr<SyncReset> {
|
type Output = SimValue<SyncReset>;
|
||||||
self.to_expr().to_sync_reset()
|
fn to_sync_reset(&self) -> Self::Output {
|
||||||
|
SimValue::from_value(SyncReset, *self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToAsyncReset for bool {
|
impl ToAsyncReset for bool {
|
||||||
fn to_async_reset(&self) -> Expr<AsyncReset> {
|
type Output = SimValue<AsyncReset>;
|
||||||
self.to_expr().to_async_reset()
|
fn to_async_reset(&self) -> Self::Output {
|
||||||
|
SimValue::from_value(AsyncReset, *self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4899
crates/fayalite/src/sim.rs
Normal file
4899
crates/fayalite/src/sim.rs
Normal file
File diff suppressed because it is too large
Load diff
5898
crates/fayalite/src/sim/compiler.rs
Normal file
5898
crates/fayalite/src/sim/compiler.rs
Normal file
File diff suppressed because it is too large
Load diff
2135
crates/fayalite/src/sim/interpreter.rs
Normal file
2135
crates/fayalite/src/sim/interpreter.rs
Normal file
File diff suppressed because it is too large
Load diff
1331
crates/fayalite/src/sim/interpreter/parts.rs
Normal file
1331
crates/fayalite/src/sim/interpreter/parts.rs
Normal file
File diff suppressed because it is too large
Load diff
397
crates/fayalite/src/sim/time.rs
Normal file
397
crates/fayalite/src/sim/time.rs
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
ops::{Add, AddAssign, Sub, SubAssign},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct SimInstant {
|
||||||
|
time_since_start: SimDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimInstant {
|
||||||
|
pub const fn checked_add(self, duration: SimDuration) -> Option<Self> {
|
||||||
|
let Some(time_since_start) = self.time_since_start.checked_add(duration) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(SimInstant { time_since_start })
|
||||||
|
}
|
||||||
|
pub const fn checked_duration_since(self, earlier: Self) -> Option<SimDuration> {
|
||||||
|
self.time_since_start.checked_sub(earlier.time_since_start)
|
||||||
|
}
|
||||||
|
pub const fn checked_sub(self, duration: SimDuration) -> Option<Self> {
|
||||||
|
let Some(time_since_start) = self.time_since_start.checked_sub(duration) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(SimInstant { time_since_start })
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub const fn duration_since(self, earlier: Self) -> SimDuration {
|
||||||
|
let Some(retval) = self.checked_duration_since(earlier) else {
|
||||||
|
panic!(
|
||||||
|
"tried to compute the duration since a later time -- durations can't be negative"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub const fn saturating_duration_since(self, earlier: Self) -> SimDuration {
|
||||||
|
let Some(retval) = self.checked_duration_since(earlier) else {
|
||||||
|
return SimDuration::ZERO;
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<SimDuration> for SimInstant {
|
||||||
|
type Output = SimInstant;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn add(mut self, rhs: SimDuration) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<SimDuration> for SimInstant {
|
||||||
|
#[track_caller]
|
||||||
|
fn add_assign(&mut self, rhs: SimDuration) {
|
||||||
|
self.time_since_start += rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<SimInstant> for SimDuration {
|
||||||
|
type Output = SimInstant;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn add(self, rhs: SimInstant) -> Self::Output {
|
||||||
|
rhs.add(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for SimInstant {
|
||||||
|
type Output = SimDuration;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn sub(self, rhs: SimInstant) -> Self::Output {
|
||||||
|
self.duration_since(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<SimDuration> for SimInstant {
|
||||||
|
type Output = SimInstant;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn sub(self, rhs: SimDuration) -> Self::Output {
|
||||||
|
let Some(retval) = self.checked_sub(rhs) else {
|
||||||
|
panic!("SimInstant underflow");
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign<SimDuration> for SimInstant {
|
||||||
|
#[track_caller]
|
||||||
|
fn sub_assign(&mut self, rhs: SimDuration) {
|
||||||
|
*self = *self - rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimInstant {
|
||||||
|
pub const START: SimInstant = SimInstant {
|
||||||
|
time_since_start: SimDuration::ZERO,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SimInstant {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.time_since_start.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
pub struct SimDuration {
|
||||||
|
attos: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for SimDuration {
|
||||||
|
#[track_caller]
|
||||||
|
fn add_assign(&mut self, rhs: SimDuration) {
|
||||||
|
*self = *self + rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for SimDuration {
|
||||||
|
type Output = SimDuration;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn add(self, rhs: SimDuration) -> Self::Output {
|
||||||
|
SimDuration {
|
||||||
|
attos: self
|
||||||
|
.attos
|
||||||
|
.checked_add(rhs.attos)
|
||||||
|
.expect("overflow adding durations"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for SimDuration {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
SimDuration {
|
||||||
|
attos: self
|
||||||
|
.attos
|
||||||
|
.checked_add(rhs.attos)
|
||||||
|
.expect("underflow subtracting durations -- durations can't be negative"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign for SimDuration {
|
||||||
|
#[track_caller]
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
*self = *self - rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||||
|
pub struct SimDurationParts {
|
||||||
|
pub attos: u16,
|
||||||
|
pub femtos: u16,
|
||||||
|
pub picos: u16,
|
||||||
|
pub nanos: u16,
|
||||||
|
pub micros: u16,
|
||||||
|
pub millis: u16,
|
||||||
|
pub secs: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_duration_units {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
#[unit_const = $UNIT:ident, from_units = $from_units:ident, as_units = $as_units:ident, units = $units:ident, suffix = $suffix:literal]
|
||||||
|
const $log10_units_per_sec:ident: u32 = $log10_units_per_sec_value:expr;
|
||||||
|
)*
|
||||||
|
) => {
|
||||||
|
impl SimDuration {
|
||||||
|
$(
|
||||||
|
const $log10_units_per_sec: u32 = $log10_units_per_sec_value;
|
||||||
|
pub const fn $from_units($units: u128) -> Self {
|
||||||
|
Self::from_units_helper::<{ Self::$log10_units_per_sec }>($units)
|
||||||
|
}
|
||||||
|
pub const fn $as_units(self) -> u128 {
|
||||||
|
self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
pub const fn to_parts(mut self) -> SimDurationParts {
|
||||||
|
$(
|
||||||
|
let $units = self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
|
||||||
|
self.attos %= const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
|
||||||
|
)*
|
||||||
|
SimDurationParts {
|
||||||
|
$($units: $units as _,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn from_parts_checked(parts: SimDurationParts) -> Option<Self> {
|
||||||
|
let attos = 0u128;
|
||||||
|
$(
|
||||||
|
let Some(product) = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }.checked_mul(parts.$units as u128) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Some(attos) = attos.checked_add(product) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
)*
|
||||||
|
Some(Self {
|
||||||
|
attos,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SimDuration {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let ilog10_attos = match self.attos.checked_ilog10() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => Self::LOG10_ATTOS_PER_SEC,
|
||||||
|
};
|
||||||
|
let (suffix, int, fraction, fraction_digits) =
|
||||||
|
match Self::LOG10_ATTOS_PER_SEC.saturating_sub(ilog10_attos) {
|
||||||
|
$(
|
||||||
|
..=Self::$log10_units_per_sec => {
|
||||||
|
let divisor = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
|
||||||
|
(
|
||||||
|
$suffix,
|
||||||
|
self.attos / divisor,
|
||||||
|
self.attos % divisor,
|
||||||
|
(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) as usize,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)*
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
write!(f, "{int}")?;
|
||||||
|
if fraction != 0 {
|
||||||
|
write!(f, ".{fraction:0fraction_digits$}")?;
|
||||||
|
}
|
||||||
|
write!(f, " {suffix}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_duration_debug() {
|
||||||
|
$(
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", SimDuration::$from_units(123)),
|
||||||
|
concat!("123 ", $suffix)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", SimDuration::$from_units(1)),
|
||||||
|
concat!("1 ", $suffix),
|
||||||
|
);
|
||||||
|
let mut v = SimDuration::$from_units(1);
|
||||||
|
if v.attos < 1 << 53 {
|
||||||
|
v.attos += 1;
|
||||||
|
assert_eq!(
|
||||||
|
format!("{v:?}"),
|
||||||
|
format!("{} {}", v.attos as f64 / 10.0f64.powf((SimDuration::LOG10_ATTOS_PER_SEC - SimDuration::$log10_units_per_sec) as f64), $suffix),
|
||||||
|
"1 {} + 1 as == {} as", $suffix, v.attos,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_duration_units! {
|
||||||
|
#[unit_const = SECOND, from_units = from_secs, as_units = as_secs, units = secs, suffix = "s"]
|
||||||
|
const LOG10_SECS_PER_SEC: u32 = 0;
|
||||||
|
#[unit_const = MILLISECOND, from_units = from_millis, as_units = as_millis, units = millis, suffix = "ms"]
|
||||||
|
const LOG10_MILLIS_PER_SEC: u32 = 3;
|
||||||
|
#[unit_const = MICROSECOND, from_units = from_micros, as_units = as_micros, units = micros, suffix = "μs"]
|
||||||
|
const LOG10_MICROS_PER_SEC: u32 = 6;
|
||||||
|
#[unit_const = NANOSECOND, from_units = from_nanos, as_units = as_nanos, units = nanos, suffix = "ns"]
|
||||||
|
const LOG10_NANOS_PER_SEC: u32 = 9;
|
||||||
|
#[unit_const = PICOSECOND, from_units = from_picos, as_units = as_picos, units = picos, suffix = "ps"]
|
||||||
|
const LOG10_PICOS_PER_SEC: u32 = 12;
|
||||||
|
#[unit_const = FEMTOSECOND, from_units = from_femtos, as_units = as_femtos, units = femtos, suffix = "fs"]
|
||||||
|
const LOG10_FEMTOS_PER_SEC: u32 = 15;
|
||||||
|
#[unit_const = ATTOSECOND, from_units = from_attos, as_units = as_attos, units = attos, suffix = "as"]
|
||||||
|
const LOG10_ATTOS_PER_SEC: u32 = 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimDuration {
|
||||||
|
const fn from_units_helper<const UNITS_PER_SEC: u32>(units: u128) -> Self {
|
||||||
|
let Some(attos) =
|
||||||
|
units.checked_mul(const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - UNITS_PER_SEC) })
|
||||||
|
else {
|
||||||
|
panic!("duration too big");
|
||||||
|
};
|
||||||
|
Self { attos }
|
||||||
|
}
|
||||||
|
pub const ZERO: SimDuration = SimDuration::from_secs(0);
|
||||||
|
pub const fn from_parts(parts: SimDurationParts) -> Self {
|
||||||
|
match Self::from_parts_checked(parts) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => panic!("duration too big"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn abs_diff(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
attos: self.attos.abs_diff(other.attos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
|
||||||
|
let Some(attos) = self.attos.checked_add(rhs.attos) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self { attos })
|
||||||
|
}
|
||||||
|
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||||
|
let Some(attos) = self.attos.checked_sub(rhs.attos) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self { attos })
|
||||||
|
}
|
||||||
|
pub const fn is_zero(self) -> bool {
|
||||||
|
self.attos == 0
|
||||||
|
}
|
||||||
|
pub const fn saturating_add(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
attos: self.attos.saturating_add(rhs.attos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn saturating_sub(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
attos: self.attos.saturating_sub(rhs.attos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn checked_ilog10(self) -> Option<i32> {
|
||||||
|
let Some(ilog10_attos) = self.attos.checked_ilog10() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(ilog10_attos as i32 - Self::LOG10_ATTOS_PER_SEC as i32)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub const fn ilog10(self) -> i32 {
|
||||||
|
let Some(retval) = self.checked_ilog10() else {
|
||||||
|
panic!("tried to take the ilog10 of 0");
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub const fn checked_pow10(log10: i32, underflow_is_zero: bool) -> Option<Self> {
|
||||||
|
let Some(log10) = Self::LOG10_ATTOS_PER_SEC.checked_add_signed(log10) else {
|
||||||
|
return if log10 < 0 && underflow_is_zero {
|
||||||
|
Some(Self::ZERO)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let Some(attos) = 10u128.checked_pow(log10) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Self { attos })
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub const fn pow10(log10: i32) -> Self {
|
||||||
|
let Some(retval) = Self::checked_pow10(log10, true) else {
|
||||||
|
panic!("pow10 overflowed");
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub const fn is_power_of_ten(self) -> bool {
|
||||||
|
const TEN: u128 = 10;
|
||||||
|
const NUMBER_OF_POWERS_OF_TEN: usize = {
|
||||||
|
let mut n = 0;
|
||||||
|
while let Some(_) = TEN.checked_pow(n as u32) {
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
n
|
||||||
|
};
|
||||||
|
const POWERS_OF_TEN: [u128; NUMBER_OF_POWERS_OF_TEN] = {
|
||||||
|
let mut retval = [0; NUMBER_OF_POWERS_OF_TEN];
|
||||||
|
let mut i = 0;
|
||||||
|
while i < NUMBER_OF_POWERS_OF_TEN {
|
||||||
|
retval[i] = TEN.pow(i as u32);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
retval
|
||||||
|
};
|
||||||
|
let mut i = 0;
|
||||||
|
while i < NUMBER_OF_POWERS_OF_TEN {
|
||||||
|
if self.attos == POWERS_OF_TEN[i] {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Duration> for SimDuration {
|
||||||
|
fn from(duration: Duration) -> Self {
|
||||||
|
Self::from_nanos(duration.as_nanos())
|
||||||
|
}
|
||||||
|
}
|
||||||
1589
crates/fayalite/src/sim/value.rs
Normal file
1589
crates/fayalite/src/sim/value.rs
Normal file
File diff suppressed because it is too large
Load diff
337
crates/fayalite/src/sim/value/sim_only_value_unsafe.rs
Normal file
337
crates/fayalite/src/sim/value/sim_only_value_unsafe.rs
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
//! `unsafe` parts of [`DynSimOnlyValue`]
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
expr::{ValueType, value_category::ValueCategoryValue},
|
||||||
|
util::serde_by_id::SerdeByIdProperties,
|
||||||
|
};
|
||||||
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
|
use std::{
|
||||||
|
any::{self, TypeId},
|
||||||
|
fmt,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
marker::PhantomData,
|
||||||
|
mem::ManuallyDrop,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait SimOnlyValueTrait:
|
||||||
|
'static + Eq + Hash + fmt::Debug + Serialize + DeserializeOwned + Clone + Default
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static + Eq + Hash + fmt::Debug + Serialize + DeserializeOwned + Clone + Default>
|
||||||
|
SimOnlyValueTrait for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: `type_id_dyn` must return `TypeId::of::<T>()` where `Self = SimOnly<T>`
|
||||||
|
unsafe trait DynSimOnlyTrait: 'static + Send + Sync {
|
||||||
|
fn type_id_dyn(&self) -> TypeId;
|
||||||
|
fn type_name(&self) -> &'static str;
|
||||||
|
fn default_value(&self) -> Rc<dyn DynSimOnlyValueTrait>;
|
||||||
|
fn deserialize_from_json_string(
|
||||||
|
&self,
|
||||||
|
json_str: &str,
|
||||||
|
) -> serde_json::Result<Rc<dyn DynSimOnlyValueTrait>>;
|
||||||
|
fn serde_by_id_properties_inner(&self) -> SerdeByIdProperties<DynSimOnly>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: `type_id_dyn` is implemented correctly
|
||||||
|
unsafe impl<T: SimOnlyValueTrait> DynSimOnlyTrait for SimOnly<T> {
|
||||||
|
fn type_id_dyn(&self) -> TypeId {
|
||||||
|
TypeId::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
any::type_name::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_value(&self) -> Rc<dyn DynSimOnlyValueTrait> {
|
||||||
|
Rc::new(T::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_from_json_string(
|
||||||
|
&self,
|
||||||
|
json_str: &str,
|
||||||
|
) -> serde_json::Result<Rc<dyn DynSimOnlyValueTrait>> {
|
||||||
|
Ok(Rc::<T>::new(serde_json::from_str(json_str)?))
|
||||||
|
}
|
||||||
|
fn serde_by_id_properties_inner(&self) -> SerdeByIdProperties<DynSimOnly> {
|
||||||
|
SerdeByIdProperties::of::<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety:
|
||||||
|
/// * `type_id_dyn()` must return `TypeId::of::<Self>()`.
|
||||||
|
/// * `ty().type_id()` must return `TypeId::of::<Self>()`.
|
||||||
|
unsafe trait DynSimOnlyValueTrait: 'static + fmt::Debug {
|
||||||
|
fn type_id_dyn(&self) -> TypeId;
|
||||||
|
fn ty(&self) -> DynSimOnly;
|
||||||
|
fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool;
|
||||||
|
fn serialize_to_json_string(&self) -> serde_json::Result<String>;
|
||||||
|
fn hash_dyn(&self, state: &mut dyn Hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl dyn DynSimOnlyValueTrait {
|
||||||
|
fn is<T: SimOnlyValueTrait>(&self) -> bool {
|
||||||
|
Self::type_id_dyn(self) == TypeId::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast_ref<T: SimOnlyValueTrait>(&self) -> Option<&T> {
|
||||||
|
if Self::is::<T>(self) {
|
||||||
|
// Safety: checked that `Self` is really `T`
|
||||||
|
Some(unsafe { &*(self as *const Self as *const T) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn downcast_rc<T: SimOnlyValueTrait>(self: Rc<Self>) -> Result<Rc<T>, Rc<Self>> {
|
||||||
|
if Self::is::<T>(&*self) {
|
||||||
|
// Safety: checked that `Self` is really `T`
|
||||||
|
Ok(unsafe { Rc::from_raw(Rc::into_raw(self) as *const T) })
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety:
|
||||||
|
/// * `type_id_dyn()` returns `TypeId::of::<Self>()`.
|
||||||
|
/// * `ty().type_id()` returns `TypeId::of::<Self>()`.
|
||||||
|
unsafe impl<T: SimOnlyValueTrait> DynSimOnlyValueTrait for T {
|
||||||
|
fn type_id_dyn(&self) -> TypeId {
|
||||||
|
TypeId::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ty(&self) -> DynSimOnly {
|
||||||
|
DynSimOnly::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool {
|
||||||
|
other.downcast_ref::<T>().is_some_and(|other| self == other)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize_to_json_string(&self) -> serde_json::Result<String> {
|
||||||
|
serde_json::to_string(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
|
||||||
|
self.hash(&mut state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct DynSimOnly {
|
||||||
|
ty: &'static dyn DynSimOnlyTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynSimOnly {
|
||||||
|
pub const fn of<T: SimOnlyValueTrait>() -> Self {
|
||||||
|
Self {
|
||||||
|
ty: &const { SimOnly::<T>::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn type_id(self) -> TypeId {
|
||||||
|
self.ty.type_id_dyn()
|
||||||
|
}
|
||||||
|
pub fn type_name(self) -> &'static str {
|
||||||
|
self.ty.type_name()
|
||||||
|
}
|
||||||
|
pub fn is<T: SimOnlyValueTrait>(self) -> bool {
|
||||||
|
self.type_id() == TypeId::of::<T>()
|
||||||
|
}
|
||||||
|
pub fn downcast<T: SimOnlyValueTrait>(self) -> Option<SimOnly<T>> {
|
||||||
|
self.is::<T>().then_some(SimOnly::default())
|
||||||
|
}
|
||||||
|
pub fn deserialize_from_json_string(
|
||||||
|
self,
|
||||||
|
json_str: &str,
|
||||||
|
) -> serde_json::Result<DynSimOnlyValue> {
|
||||||
|
self.ty
|
||||||
|
.deserialize_from_json_string(json_str)
|
||||||
|
.map(DynSimOnlyValue)
|
||||||
|
}
|
||||||
|
pub fn default_value(self) -> DynSimOnlyValue {
|
||||||
|
DynSimOnlyValue(self.ty.default_value())
|
||||||
|
}
|
||||||
|
pub(super) fn serde_by_id_properties_inner(self) -> SerdeByIdProperties<Self> {
|
||||||
|
self.ty.serde_by_id_properties_inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for DynSimOnly {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Self::type_id(*self) == Self::type_id(*other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for DynSimOnly {}
|
||||||
|
|
||||||
|
impl Hash for DynSimOnly {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
Self::type_id(*self).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for DynSimOnly {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "SimOnly<{}>", self.ty.type_name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> From<SimOnly<T>> for DynSimOnly {
|
||||||
|
fn from(value: SimOnly<T>) -> Self {
|
||||||
|
let SimOnly(PhantomData) = value;
|
||||||
|
Self::of::<T>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the [`Type`][Type] for a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL
|
||||||
|
///
|
||||||
|
/// [Type]: crate::ty::Type
|
||||||
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct SimOnly<T: SimOnlyValueTrait>(PhantomData<fn(T) -> T>);
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> fmt::Debug for SimOnly<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
DynSimOnly::of::<T>().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> SimOnly<T> {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self(PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> Copy for SimOnly<T> {}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> Default for SimOnly<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL
|
||||||
|
#[derive(Clone, Eq, Hash, Default, Ord)]
|
||||||
|
pub struct SimOnlyValue<T: SimOnlyValueTrait>(Rc<T>);
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait + PartialEq<U>, U: SimOnlyValueTrait> PartialEq<SimOnlyValue<U>>
|
||||||
|
for SimOnlyValue<T>
|
||||||
|
{
|
||||||
|
fn eq(&self, other: &SimOnlyValue<U>) -> bool {
|
||||||
|
<T as PartialEq<U>>::eq(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait + PartialOrd<U>, U: SimOnlyValueTrait> PartialOrd<SimOnlyValue<U>>
|
||||||
|
for SimOnlyValue<T>
|
||||||
|
{
|
||||||
|
fn partial_cmp(&self, other: &SimOnlyValue<U>) -> Option<std::cmp::Ordering> {
|
||||||
|
<T as PartialOrd<U>>::partial_cmp(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> SimOnlyValue<T> {
|
||||||
|
pub fn with_dyn_ref<F: FnOnce(&DynSimOnlyValue) -> R, R>(&self, f: F) -> R {
|
||||||
|
// Safety: creating a copied `Rc<T>` is safe as long as the copy isn't dropped and isn't changed
|
||||||
|
// to point somewhere else, `f` can't change `dyn_ref` because it's only given a shared reference.
|
||||||
|
let dyn_ref =
|
||||||
|
unsafe { ManuallyDrop::new(DynSimOnlyValue(Rc::<T>::from_raw(Rc::as_ptr(&self.0)))) };
|
||||||
|
f(&dyn_ref)
|
||||||
|
}
|
||||||
|
pub fn from_rc(v: Rc<T>) -> Self {
|
||||||
|
Self(v)
|
||||||
|
}
|
||||||
|
pub fn new(v: T) -> Self {
|
||||||
|
Self(Rc::new(v))
|
||||||
|
}
|
||||||
|
pub fn into_inner(this: Self) -> Rc<T> {
|
||||||
|
this.0
|
||||||
|
}
|
||||||
|
pub fn inner_mut(this: &mut Self) -> &mut Rc<T> {
|
||||||
|
&mut this.0
|
||||||
|
}
|
||||||
|
pub fn inner(this: &Self) -> &Rc<T> {
|
||||||
|
&this.0
|
||||||
|
}
|
||||||
|
pub fn into_dyn(this: Self) -> DynSimOnlyValue {
|
||||||
|
DynSimOnlyValue::from(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> std::ops::Deref for SimOnlyValue<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> std::ops::DerefMut for SimOnlyValue<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
Rc::make_mut(&mut self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DynSimOnlyValue(Rc<dyn DynSimOnlyValueTrait>);
|
||||||
|
|
||||||
|
impl fmt::Debug for DynSimOnlyValue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
<dyn DynSimOnlyValueTrait as fmt::Debug>::fmt(&*self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for DynSimOnlyValue {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
DynSimOnlyValueTrait::eq_dyn(&*self.0, &*other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for DynSimOnlyValue {}
|
||||||
|
|
||||||
|
impl Hash for DynSimOnlyValue {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
DynSimOnlyValueTrait::hash_dyn(&*self.0, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SimOnlyValueTrait> From<SimOnlyValue<T>> for DynSimOnlyValue {
|
||||||
|
fn from(value: SimOnlyValue<T>) -> Self {
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueType for DynSimOnlyValue {
|
||||||
|
type Type = DynSimOnly;
|
||||||
|
type ValueCategory = ValueCategoryValue;
|
||||||
|
|
||||||
|
fn ty(&self) -> Self::Type {
|
||||||
|
self.0.ty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynSimOnlyValue {
|
||||||
|
pub fn type_id(&self) -> TypeId {
|
||||||
|
self.0.type_id_dyn()
|
||||||
|
}
|
||||||
|
pub fn is<T: SimOnlyValueTrait>(&self) -> bool {
|
||||||
|
self.0.is::<T>()
|
||||||
|
}
|
||||||
|
pub fn downcast<T: SimOnlyValueTrait>(self) -> Result<SimOnlyValue<T>, DynSimOnlyValue> {
|
||||||
|
match <dyn DynSimOnlyValueTrait>::downcast_rc(self.0) {
|
||||||
|
Ok(v) => Ok(SimOnlyValue(v)),
|
||||||
|
Err(v) => Err(Self(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn downcast_ref<T: SimOnlyValueTrait>(&self) -> Option<&T> {
|
||||||
|
<dyn DynSimOnlyValueTrait>::downcast_ref(&*self.0)
|
||||||
|
}
|
||||||
|
pub fn serialize_to_json_string(&self) -> serde_json::Result<String> {
|
||||||
|
self.0.serialize_to_json_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
1564
crates/fayalite/src/sim/vcd.rs
Normal file
1564
crates/fayalite/src/sim/vcd.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,9 +2,8 @@
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
use crate::{
|
use crate::{
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
util::DebugAsDisplay,
|
util::{DebugAsDisplay, HashMap},
|
||||||
};
|
};
|
||||||
use hashbrown::HashMap;
|
|
||||||
use std::{cell::RefCell, fmt, num::NonZeroUsize, panic, path::Path};
|
use std::{cell::RefCell, fmt, num::NonZeroUsize, panic, path::Path};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
|
@ -97,7 +96,7 @@ impl NormalizeFilesForTestsState {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
test_position: panic::Location::caller(),
|
test_position: panic::Location::caller(),
|
||||||
file_pattern_matches: HashMap::new(),
|
file_pattern_matches: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +142,7 @@ impl From<&'_ panic::Location<'_>> for SourceLocation {
|
||||||
map.entry_ref(file)
|
map.entry_ref(file)
|
||||||
.or_insert_with(|| NormalizedFileForTestState {
|
.or_insert_with(|| NormalizedFileForTestState {
|
||||||
file_name_id: NonZeroUsize::new(len + 1).unwrap(),
|
file_name_id: NonZeroUsize::new(len + 1).unwrap(),
|
||||||
positions_map: HashMap::new(),
|
positions_map: HashMap::default(),
|
||||||
});
|
});
|
||||||
file_str = m.generate_file_name(file_state.file_name_id);
|
file_str = m.generate_file_name(file_state.file_name_id);
|
||||||
file = &file_str;
|
file = &file_str;
|
||||||
|
|
|
||||||
413
crates/fayalite/src/testing.rs
Normal file
413
crates/fayalite/src/testing.rs
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
use crate::{
|
||||||
|
build::{
|
||||||
|
BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams,
|
||||||
|
NoArgs, RunBuild,
|
||||||
|
external::{ExternalCommandArgs, ExternalCommandJobKind},
|
||||||
|
firrtl::{FirrtlArgs, FirrtlJobKind},
|
||||||
|
formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind},
|
||||||
|
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
|
||||||
|
},
|
||||||
|
bundle::BundleType,
|
||||||
|
firrtl::ExportOptions,
|
||||||
|
module::Module,
|
||||||
|
sim::{Simulation, vcd::VcdWriterDecls},
|
||||||
|
util::{HashMap, RcWriter},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Write},
|
||||||
|
panic::Location,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
sync::{Mutex, OnceLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize,
|
||||||
|
)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum FormalMode {
|
||||||
|
#[default]
|
||||||
|
BMC,
|
||||||
|
Prove,
|
||||||
|
Live,
|
||||||
|
Cover,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormalMode {
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
FormalMode::BMC => "bmc",
|
||||||
|
FormalMode::Prove => "prove",
|
||||||
|
FormalMode::Live => "live",
|
||||||
|
FormalMode::Cover => "cover",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FormalMode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CargoMetadata {
|
||||||
|
target_directory: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cargo_target_dir() -> &'static Path {
|
||||||
|
static RETVAL: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
RETVAL.get_or_init(|| {
|
||||||
|
let output = Command::new(
|
||||||
|
std::env::var_os("CARGO")
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("cargo".as_ref()),
|
||||||
|
)
|
||||||
|
.arg("metadata")
|
||||||
|
.output()
|
||||||
|
.expect("can't run `cargo metadata`");
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(
|
||||||
|
"can't run `cargo metadata`:\n{}\nexited with status: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr),
|
||||||
|
output.status
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let CargoMetadata { target_directory } =
|
||||||
|
serde_json::from_slice(&output.stdout).expect("can't parse output of `cargo metadata`");
|
||||||
|
PathBuf::from(target_directory)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf {
|
||||||
|
static DIRS: Mutex<Option<HashMap<String, u64>>> = Mutex::new(None);
|
||||||
|
let test_name = test_name.to_string();
|
||||||
|
// don't use line/column numbers since that constantly changes as you edit tests
|
||||||
|
let file = std::panic::Location::caller().file();
|
||||||
|
// simple reproducible hash
|
||||||
|
let simple_hash = file.bytes().chain(test_name.bytes()).fold(
|
||||||
|
((file.len() as u32) << 16).wrapping_add(test_name.len() as u32),
|
||||||
|
|mut h, b| {
|
||||||
|
h = h.wrapping_mul(0xaa0d184b);
|
||||||
|
h ^= h.rotate_right(5);
|
||||||
|
h ^= h.rotate_right(13);
|
||||||
|
h.wrapping_add(b as u32)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let mut dir = String::with_capacity(64);
|
||||||
|
for ch in Path::new(file)
|
||||||
|
.file_stem()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.chars()
|
||||||
|
.chain(['-'])
|
||||||
|
.chain(test_name.chars())
|
||||||
|
{
|
||||||
|
dir.push(match ch {
|
||||||
|
ch if ch.is_alphanumeric() => ch,
|
||||||
|
'_' | '-' | '+' | '.' | ',' | ' ' => ch,
|
||||||
|
_ => '_',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
write!(dir, "-{simple_hash:08x}").unwrap();
|
||||||
|
let index = *DIRS
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_or_insert_with(HashMap::default)
|
||||||
|
.entry_ref(&dir)
|
||||||
|
.and_modify(|v| *v += 1)
|
||||||
|
.or_insert(0);
|
||||||
|
write!(dir, "-{index}").unwrap();
|
||||||
|
get_cargo_target_dir()
|
||||||
|
.join("fayalite_assert_formal")
|
||||||
|
.join(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_assert_formal_args(
|
||||||
|
test_name: &dyn std::fmt::Display,
|
||||||
|
formal_mode: FormalMode,
|
||||||
|
formal_depth: u64,
|
||||||
|
solver: Option<&str>,
|
||||||
|
export_options: ExportOptions,
|
||||||
|
) -> eyre::Result<JobArgsAndDependencies<ExternalCommandJobKind<Formal>>> {
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: BaseJobKind,
|
||||||
|
args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None),
|
||||||
|
};
|
||||||
|
let dependencies = JobArgsAndDependencies {
|
||||||
|
args,
|
||||||
|
dependencies: (),
|
||||||
|
};
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: FirrtlJobKind,
|
||||||
|
args: FirrtlArgs { export_options },
|
||||||
|
};
|
||||||
|
let dependencies = JobArgsAndDependencies { args, dependencies };
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: ExternalCommandJobKind::new(),
|
||||||
|
args: ExternalCommandArgs::resolve_program_path(
|
||||||
|
None,
|
||||||
|
UnadjustedVerilogArgs {
|
||||||
|
firtool_extra_args: vec![],
|
||||||
|
verilog_dialect: None,
|
||||||
|
verilog_debug: true,
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
let dependencies = JobArgsAndDependencies { args, dependencies };
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: VerilogJobKind,
|
||||||
|
args: VerilogJobArgs {},
|
||||||
|
};
|
||||||
|
let dependencies = JobArgsAndDependencies { args, dependencies };
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: WriteSbyFileJobKind,
|
||||||
|
args: FormalArgs {
|
||||||
|
sby_extra_args: vec![],
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
formal_solver: solver.unwrap_or(FormalArgs::DEFAULT_SOLVER).into(),
|
||||||
|
smtbmc_extra_args: vec![],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let dependencies = JobArgsAndDependencies { args, dependencies };
|
||||||
|
let args = JobKindAndArgs {
|
||||||
|
kind: ExternalCommandJobKind::new(),
|
||||||
|
args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?,
|
||||||
|
};
|
||||||
|
Ok(JobArgsAndDependencies { args, dependencies })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_assert_formal<M: AsRef<Module<T>>, T: BundleType>(
|
||||||
|
test_name: impl std::fmt::Display,
|
||||||
|
module: M,
|
||||||
|
formal_mode: FormalMode,
|
||||||
|
formal_depth: u64,
|
||||||
|
solver: Option<&str>,
|
||||||
|
export_options: ExportOptions,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
const APP_NAME: &'static str = "fayalite::testing::assert_formal";
|
||||||
|
make_assert_formal_args(
|
||||||
|
&test_name,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
solver,
|
||||||
|
export_options,
|
||||||
|
)?
|
||||||
|
.run_without_platform(
|
||||||
|
|NoArgs {}| Ok(JobParams::new(module)),
|
||||||
|
&GlobalParams::new(None, APP_NAME),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn assert_formal<M: AsRef<Module<T>>, T: BundleType>(
|
||||||
|
test_name: impl std::fmt::Display,
|
||||||
|
module: M,
|
||||||
|
formal_mode: FormalMode,
|
||||||
|
formal_depth: u64,
|
||||||
|
solver: Option<&str>,
|
||||||
|
export_options: ExportOptions,
|
||||||
|
) {
|
||||||
|
try_assert_formal(
|
||||||
|
test_name,
|
||||||
|
module,
|
||||||
|
formal_mode,
|
||||||
|
formal_depth,
|
||||||
|
solver,
|
||||||
|
export_options,
|
||||||
|
)
|
||||||
|
.expect("testing::assert_formal() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CheckedVcdOutput {
|
||||||
|
writer: Option<RcWriter>,
|
||||||
|
expected_path: PathBuf,
|
||||||
|
expected_contents: Result<String, (Option<PathBuf>, std::io::Error)>,
|
||||||
|
location: &'static Location<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckedVcdOutput {
|
||||||
|
#[must_use]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new<T: BundleType>(sim: &mut Simulation<T>, expected_path: PathBuf) -> Self {
|
||||||
|
let writer = RcWriter::default();
|
||||||
|
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
||||||
|
Self {
|
||||||
|
writer: Some(writer),
|
||||||
|
expected_contents: std::fs::read_to_string(&expected_path).map_err(|e| {
|
||||||
|
eprintln!(
|
||||||
|
"error: failed to read expected VCD from: {}",
|
||||||
|
expected_path.display(),
|
||||||
|
);
|
||||||
|
(std::env::current_dir().ok(), e)
|
||||||
|
}),
|
||||||
|
expected_path,
|
||||||
|
location: Location::caller(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[must_use]
|
||||||
|
#[track_caller]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn __checked_vcd_output_macro_helper<T: BundleType>(
|
||||||
|
sim: &mut Simulation<T>,
|
||||||
|
cargo_manifest_dir: &'static str,
|
||||||
|
path: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(sim, Path::new(cargo_manifest_dir).join(path))
|
||||||
|
}
|
||||||
|
pub fn with_vcd_output<R>(&self, f: impl FnOnce(&str) -> R) -> R {
|
||||||
|
let Some(writer) = &self.writer else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
writer.clone().borrow(|output| {
|
||||||
|
let Ok(output) = str::from_utf8(output) else {
|
||||||
|
unreachable!("VcdWriter writes valid UTF-8");
|
||||||
|
};
|
||||||
|
f(output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn finish(mut self) {
|
||||||
|
let Ok(()) = self.finish_impl(|msg| panic!("{msg}"));
|
||||||
|
}
|
||||||
|
fn finish_impl<E>(
|
||||||
|
&mut self,
|
||||||
|
error: impl FnOnce(std::fmt::Arguments<'_>) -> E,
|
||||||
|
) -> Result<(), E> {
|
||||||
|
let Self {
|
||||||
|
writer: Some(writer),
|
||||||
|
expected_path,
|
||||||
|
expected_contents,
|
||||||
|
location,
|
||||||
|
} = self
|
||||||
|
else {
|
||||||
|
// already finished
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Ok(vcd) = String::from_utf8(writer.take()) else {
|
||||||
|
unreachable!("VcdWriter writes valid UTF-8");
|
||||||
|
};
|
||||||
|
let expected_path_d = expected_path.display();
|
||||||
|
if expected_contents
|
||||||
|
.as_ref()
|
||||||
|
.is_ok_and(|expected_contents| *expected_contents == vcd)
|
||||||
|
{
|
||||||
|
// avoid written output from being split from threads interleaving writes to stdout
|
||||||
|
let _stdout = std::io::stderr().lock();
|
||||||
|
// use println to get output captured by tests
|
||||||
|
println!("\n{location}: generated VCD matches the expected VCD in {expected_path_d}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// avoid written output from being split from threads interleaving writes to stderr
|
||||||
|
let _stderr = std::io::stderr().lock();
|
||||||
|
let error = |msg: std::fmt::Arguments<'_>| {
|
||||||
|
// print msg at both beginning and end so it's easier to find when the vcd is huge
|
||||||
|
Err(error(format_args!(
|
||||||
|
"\n{msg}####### VCD:\n{vcd}\n#######\n{msg}"
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
let error = |msg: std::fmt::Arguments<'_>| match &*expected_contents {
|
||||||
|
Ok(_) => error(format_args!(
|
||||||
|
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||||
|
{msg}",
|
||||||
|
)),
|
||||||
|
Err((Some(current_dir), e)) => error(format_args!(
|
||||||
|
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||||
|
error: failed to read: {e}\n\
|
||||||
|
current dir: {current_dir}\n\
|
||||||
|
{msg}",
|
||||||
|
current_dir = current_dir.display(),
|
||||||
|
)),
|
||||||
|
Err((None, e)) => error(format_args!(
|
||||||
|
"{location}: generated VCD doesn't match the expected VCD in {expected_path_d}\n\
|
||||||
|
error: failed to read: {e}\n\
|
||||||
|
{msg}",
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
const OVERWRITE_VAR_NAME: &str = "OVERWRITE_EXPECTED_VCD";
|
||||||
|
const OVERWRITE_VAR_VALUE: &str = "overwrite";
|
||||||
|
match std::env::var_os(OVERWRITE_VAR_NAME) {
|
||||||
|
Some(v) if v == OVERWRITE_VAR_VALUE => match std::fs::write(&expected_path, &vcd) {
|
||||||
|
Ok(()) => error(format_args!(
|
||||||
|
"warning: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- writing the generated VCD to {expected_path_d}\n"
|
||||||
|
)),
|
||||||
|
Err(e) => error(format_args!(
|
||||||
|
"error: since `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}` is set -- tried to write the generated VCD to {expected_path_d}\n\
|
||||||
|
error: failed to write: {e}"
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => error(format_args!(
|
||||||
|
"note: rerun the test with the environment variable `{OVERWRITE_VAR_NAME}={OVERWRITE_VAR_VALUE}`\n\
|
||||||
|
to update the expected output to match the generated output.\n"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CheckedVcdOutput {
|
||||||
|
#[track_caller]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.finish_impl(|msg| {
|
||||||
|
if std::thread::panicking() {
|
||||||
|
eprintln!("{msg}"); // use eprintln to get output captured by tests
|
||||||
|
} else {
|
||||||
|
panic!("{msg}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
/// Use in tests to check that [`Simulation`] generates the expected VCD traces, by comparing to a `.vcd` file containing the expected traces.
|
||||||
|
///
|
||||||
|
/// Use like so:
|
||||||
|
/// ```
|
||||||
|
/// # use fayalite::prelude::*;
|
||||||
|
/// #
|
||||||
|
/// # #[hdl_module]
|
||||||
|
/// # fn my_module() {
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # let a: UInt<8> = m.input();
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # let b: UInt<8> = m.output();
|
||||||
|
/// # connect(b, 0u8);
|
||||||
|
/// # #[hdl]
|
||||||
|
/// # if a.cmp_eq(100u8) {
|
||||||
|
/// # connect(b, 42u8);
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// // inside your #[test] fn my_test():
|
||||||
|
///
|
||||||
|
/// // get the module to simulate:
|
||||||
|
/// let m = my_module();
|
||||||
|
/// // create a simulation of the module:
|
||||||
|
/// let mut sim = Simulation::new(m);
|
||||||
|
/// // set up the expected VCD traces, the given .vcd path is relative to env!("CARGO_MANIFEST_DIR")
|
||||||
|
/// let _checked_vcd_output = checked_vcd_output!(
|
||||||
|
/// &mut sim,
|
||||||
|
/// "tests/expected/my_test.vcd",
|
||||||
|
/// );
|
||||||
|
/// // now run the simulation like normal:
|
||||||
|
/// sim.write(sim.io().a, 0u8);
|
||||||
|
/// assert_eq!(sim.read(sim.io().b).as_int(), 0);
|
||||||
|
/// sim.advance_time(SimDuration::from_micros(1));
|
||||||
|
/// sim.write(sim.io().a, 100u8);
|
||||||
|
/// assert_eq!(sim.read(sim.io().b).as_int(), 42);
|
||||||
|
/// ```
|
||||||
|
macro_rules! checked_vcd_output {
|
||||||
|
($sim:expr, $path_relative_to_manifest_dir:expr $(,)?) => {
|
||||||
|
$crate::testing::CheckedVcdOutput::__checked_vcd_output_macro_helper(
|
||||||
|
$sim,
|
||||||
|
$crate::__std::env!("CARGO_MANIFEST_DIR"),
|
||||||
|
$crate::__std::concat!($path_relative_to_manifest_dir),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use checked_vcd_output;
|
||||||
File diff suppressed because it is too large
Load diff
158
crates/fayalite/src/ty/serde_impls.rs
Normal file
158
crates/fayalite/src/ty/serde_impls.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
array::Array,
|
||||||
|
bundle::{Bundle, BundleType},
|
||||||
|
clock::Clock,
|
||||||
|
enum_::{Enum, EnumType},
|
||||||
|
int::{Bool, SInt, UInt},
|
||||||
|
intern::Interned,
|
||||||
|
phantom_const::{PhantomConstCanonicalValue, PhantomConstValue},
|
||||||
|
prelude::PhantomConst,
|
||||||
|
reset::{AsyncReset, Reset, SyncReset},
|
||||||
|
sim::value::DynSimOnly,
|
||||||
|
ty::{BaseType, CanonicalType, TraceAsString, TraceAsStringTrait},
|
||||||
|
util::serde_by_id::SerdeById,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub(crate) struct SerdePhantomConst<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: ?Sized + PhantomConstValue> Serialize for SerdePhantomConst<Interned<T>> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
self.0.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for SerdePhantomConst<Interned<T>> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
T::deserialize_value(deserializer).map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename = "CanonicalType")]
|
||||||
|
#[expect(private_interfaces)]
|
||||||
|
pub(crate) enum SerdeCanonicalType<
|
||||||
|
ArrayElement = CanonicalType,
|
||||||
|
ThePhantomConst = SerdePhantomConst<Interned<PhantomConstCanonicalValue>>,
|
||||||
|
> {
|
||||||
|
UInt {
|
||||||
|
width: usize,
|
||||||
|
},
|
||||||
|
SInt {
|
||||||
|
width: usize,
|
||||||
|
},
|
||||||
|
Bool,
|
||||||
|
Array {
|
||||||
|
element: ArrayElement,
|
||||||
|
len: usize,
|
||||||
|
},
|
||||||
|
Enum {
|
||||||
|
variants: Interned<[crate::enum_::EnumVariant]>,
|
||||||
|
},
|
||||||
|
Bundle {
|
||||||
|
fields: Interned<[crate::bundle::BundleField]>,
|
||||||
|
},
|
||||||
|
AsyncReset,
|
||||||
|
SyncReset,
|
||||||
|
Reset,
|
||||||
|
Clock,
|
||||||
|
PhantomConst(ThePhantomConst),
|
||||||
|
DynSimOnly(DynSimOnly),
|
||||||
|
TraceAsString {
|
||||||
|
inner_ty: Interned<CanonicalType>,
|
||||||
|
trace_as_string: SerdeById<Interned<dyn TraceAsStringTrait>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ArrayElement, PhantomConstInner> SerdeCanonicalType<ArrayElement, PhantomConstInner> {
|
||||||
|
pub(crate) fn as_serde_unexpected_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::UInt { .. } => "a UInt",
|
||||||
|
Self::SInt { .. } => "a SInt",
|
||||||
|
Self::Bool => "a Bool",
|
||||||
|
Self::Array { .. } => "an Array",
|
||||||
|
Self::Enum { .. } => "an Enum",
|
||||||
|
Self::Bundle { .. } => "a Bundle",
|
||||||
|
Self::AsyncReset => "an AsyncReset",
|
||||||
|
Self::SyncReset => "a SyncReset",
|
||||||
|
Self::Reset => "a Reset",
|
||||||
|
Self::Clock => "a Clock",
|
||||||
|
Self::PhantomConst(_) => "a PhantomConst",
|
||||||
|
Self::DynSimOnly(_) => "a SimOnlyValue",
|
||||||
|
Self::TraceAsString { .. } => "a TraceAsString",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BaseType> From<T> for SerdeCanonicalType {
|
||||||
|
fn from(ty: T) -> Self {
|
||||||
|
let ty: CanonicalType = ty.into();
|
||||||
|
match ty {
|
||||||
|
CanonicalType::UInt(ty) => Self::UInt { width: ty.width() },
|
||||||
|
CanonicalType::SInt(ty) => Self::SInt { width: ty.width() },
|
||||||
|
CanonicalType::Bool(Bool {}) => Self::Bool,
|
||||||
|
CanonicalType::Array(ty) => Self::Array {
|
||||||
|
element: ty.element(),
|
||||||
|
len: ty.len(),
|
||||||
|
},
|
||||||
|
CanonicalType::Enum(ty) => Self::Enum {
|
||||||
|
variants: ty.variants(),
|
||||||
|
},
|
||||||
|
CanonicalType::Bundle(ty) => Self::Bundle {
|
||||||
|
fields: ty.fields(),
|
||||||
|
},
|
||||||
|
CanonicalType::AsyncReset(AsyncReset {}) => Self::AsyncReset,
|
||||||
|
CanonicalType::SyncReset(SyncReset {}) => Self::SyncReset,
|
||||||
|
CanonicalType::Reset(Reset {}) => Self::Reset,
|
||||||
|
CanonicalType::Clock(Clock {}) => Self::Clock,
|
||||||
|
CanonicalType::PhantomConst(ty) => Self::PhantomConst(SerdePhantomConst(ty.get())),
|
||||||
|
CanonicalType::DynSimOnly(ty) => Self::DynSimOnly(ty),
|
||||||
|
CanonicalType::TraceAsString(TraceAsString {
|
||||||
|
inner_ty,
|
||||||
|
trace_as_string,
|
||||||
|
}) => Self::TraceAsString {
|
||||||
|
inner_ty: inner_ty.interned(),
|
||||||
|
trace_as_string: SerdeById {
|
||||||
|
inner: trace_as_string.interned(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SerdeCanonicalType> for CanonicalType {
|
||||||
|
fn from(ty: SerdeCanonicalType) -> Self {
|
||||||
|
match ty {
|
||||||
|
SerdeCanonicalType::UInt { width } => Self::UInt(UInt::new(width)),
|
||||||
|
SerdeCanonicalType::SInt { width } => Self::SInt(SInt::new(width)),
|
||||||
|
SerdeCanonicalType::Bool => Self::Bool(Bool),
|
||||||
|
SerdeCanonicalType::Array { element, len } => Self::Array(Array::new(element, len)),
|
||||||
|
SerdeCanonicalType::Enum { variants } => Self::Enum(Enum::new(variants)),
|
||||||
|
SerdeCanonicalType::Bundle { fields } => Self::Bundle(Bundle::new(fields)),
|
||||||
|
SerdeCanonicalType::AsyncReset => Self::AsyncReset(AsyncReset),
|
||||||
|
SerdeCanonicalType::SyncReset => Self::SyncReset(SyncReset),
|
||||||
|
SerdeCanonicalType::Reset => Self::Reset(Reset),
|
||||||
|
SerdeCanonicalType::Clock => Self::Clock(Clock),
|
||||||
|
SerdeCanonicalType::PhantomConst(value) => {
|
||||||
|
Self::PhantomConst(PhantomConst::new_interned(value.0))
|
||||||
|
}
|
||||||
|
SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value),
|
||||||
|
SerdeCanonicalType::TraceAsString {
|
||||||
|
inner_ty,
|
||||||
|
trace_as_string,
|
||||||
|
} => Self::TraceAsString(TraceAsString {
|
||||||
|
inner_ty: crate::intern::LazyInterned::Interned(inner_ty),
|
||||||
|
trace_as_string: crate::intern::LazyInterned::Interned(trace_as_string.inner),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
pub(crate) mod alternating_cell;
|
||||||
mod const_bool;
|
mod const_bool;
|
||||||
mod const_cmp;
|
mod const_cmp;
|
||||||
mod const_usize;
|
mod const_usize;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod scoped_ref;
|
mod scoped_ref;
|
||||||
|
pub(crate) mod streaming_read_utf8;
|
||||||
|
mod test_hasher;
|
||||||
|
|
||||||
|
// allow easily switching the hasher crate-wide for testing
|
||||||
|
#[cfg(feature = "unstable-test-hasher")]
|
||||||
|
pub type DefaultBuildHasher = test_hasher::DefaultBuildHasher;
|
||||||
|
#[cfg(not(feature = "unstable-test-hasher"))]
|
||||||
|
pub(crate) type DefaultBuildHasher = hashbrown::DefaultHashBuilder;
|
||||||
|
|
||||||
|
pub(crate) type HashMap<K, V, H = DefaultBuildHasher> = hashbrown::HashMap<K, V, H>;
|
||||||
|
pub(crate) type HashSet<T, H = DefaultBuildHasher> = hashbrown::HashSet<T, H>;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool};
|
pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool};
|
||||||
|
|
@ -23,5 +35,19 @@ pub use scoped_ref::ScopedRef;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use misc::{
|
pub use misc::{
|
||||||
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
|
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter,
|
||||||
|
SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest,
|
||||||
|
SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix,
|
||||||
|
os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty,
|
||||||
|
serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range,
|
||||||
};
|
};
|
||||||
|
pub(crate) use misc::{InternedStrCompareAsStr, chain, copy_le_bytes_to_bitslice};
|
||||||
|
|
||||||
|
pub mod bool_fixed_point_solver;
|
||||||
|
pub(crate) mod indented_print;
|
||||||
|
pub mod job_server;
|
||||||
|
pub mod map_trait;
|
||||||
|
pub mod prefix_sum;
|
||||||
|
pub mod ready_valid;
|
||||||
|
pub(crate) mod serde_by_id;
|
||||||
|
pub mod union_find_map;
|
||||||
|
|
|
||||||
122
crates/fayalite/src/util/alternating_cell.rs
Normal file
122
crates/fayalite/src/util/alternating_cell.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use crate::util::DebugAsDisplay;
|
||||||
|
use std::{
|
||||||
|
cell::{Cell, UnsafeCell},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) trait AlternatingCellMethods {
|
||||||
|
fn unique_to_shared(&mut self);
|
||||||
|
fn shared_to_unique(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum State {
|
||||||
|
Unique,
|
||||||
|
Shared,
|
||||||
|
Locked,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct AlternatingCell<T: ?Sized> {
|
||||||
|
state: Cell<State>,
|
||||||
|
value: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + fmt::Debug + AlternatingCellMethods> fmt::Debug for AlternatingCell<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("AlternatingCell")
|
||||||
|
.field(
|
||||||
|
self.try_share()
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| -> &dyn fmt::Debug { v })
|
||||||
|
.unwrap_or(&DebugAsDisplay("<...>")),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> AlternatingCell<T> {
|
||||||
|
pub(crate) const fn new_shared(value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Sized,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
state: Cell::new(State::Shared),
|
||||||
|
value: UnsafeCell::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) const fn new_unique(value: T) -> Self
|
||||||
|
where
|
||||||
|
T: Sized,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
state: Cell::new(State::Unique),
|
||||||
|
value: UnsafeCell::new(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn is_unique(&self) -> bool {
|
||||||
|
matches!(self.state.get(), State::Unique)
|
||||||
|
}
|
||||||
|
pub(crate) fn is_shared(&self) -> bool {
|
||||||
|
matches!(self.state.get(), State::Shared)
|
||||||
|
}
|
||||||
|
pub(crate) fn into_inner(self) -> T
|
||||||
|
where
|
||||||
|
T: Sized,
|
||||||
|
{
|
||||||
|
self.value.into_inner()
|
||||||
|
}
|
||||||
|
pub(crate) fn try_share(&self) -> Option<&T>
|
||||||
|
where
|
||||||
|
T: AlternatingCellMethods,
|
||||||
|
{
|
||||||
|
match self.state.get() {
|
||||||
|
State::Shared => {}
|
||||||
|
State::Unique => {
|
||||||
|
struct Locked<'a>(&'a Cell<State>);
|
||||||
|
impl Drop for Locked<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.set(State::Shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state.set(State::Locked);
|
||||||
|
let lock = Locked(&self.state);
|
||||||
|
// Safety: state is Locked, so nothing else will
|
||||||
|
// access value while calling unique_to_shared.
|
||||||
|
unsafe { &mut *self.value.get() }.unique_to_shared();
|
||||||
|
drop(lock);
|
||||||
|
}
|
||||||
|
State::Locked => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: state is Shared so nothing will create any mutable
|
||||||
|
// references until the returned reference's lifetime expires.
|
||||||
|
Some(unsafe { &*self.value.get() })
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn share(&self) -> &T
|
||||||
|
where
|
||||||
|
T: AlternatingCellMethods,
|
||||||
|
{
|
||||||
|
let Some(retval) = self.try_share() else {
|
||||||
|
panic!("`share` called recursively");
|
||||||
|
};
|
||||||
|
retval
|
||||||
|
}
|
||||||
|
pub(crate) fn unique(&mut self) -> &mut T
|
||||||
|
where
|
||||||
|
T: AlternatingCellMethods,
|
||||||
|
{
|
||||||
|
match self.state.get() {
|
||||||
|
State::Shared => {
|
||||||
|
self.state.set(State::Unique);
|
||||||
|
self.value.get_mut().shared_to_unique();
|
||||||
|
}
|
||||||
|
State::Unique => {}
|
||||||
|
State::Locked => unreachable!(),
|
||||||
|
}
|
||||||
|
self.value.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
711
crates/fayalite/src/util/bool_fixed_point_solver.rs
Normal file
711
crates/fayalite/src/util/bool_fixed_point_solver.rs
Normal file
|
|
@ -0,0 +1,711 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use petgraph::unionfind::UnionFind;
|
||||||
|
use std::{collections::BTreeSet, fmt};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Variable(usize);
|
||||||
|
|
||||||
|
impl Variable {
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Variable {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Variable {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "v{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub enum Constraint {
|
||||||
|
/// `variable` is constrained to be [`!solver.unconstrained_variables_value()`](BoolFixedPointSolver::unconstrained_variables_value())
|
||||||
|
MaximallyConstrained { variable: Variable },
|
||||||
|
/// the constraint is `dest == src`
|
||||||
|
Equal { dest: Variable, src: Variable },
|
||||||
|
/// the constraint is `dest == dest & src`
|
||||||
|
And { dest: Variable, src: Variable },
|
||||||
|
/// the constraint is `dest == dest | src`
|
||||||
|
Or { dest: Variable, src: Variable },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
/// the constraint is `dest == dest & src`
|
||||||
|
struct AndConstraint {
|
||||||
|
dest: Variable,
|
||||||
|
src: Variable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AndConstraint {
|
||||||
|
fn from_or_constraint(or_constraint_dest: Variable, or_constraint_src: Variable) -> Self {
|
||||||
|
// `a == a | b` is equivalent to `b == b & a`
|
||||||
|
Self {
|
||||||
|
dest: or_constraint_src,
|
||||||
|
src: or_constraint_dest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for AndConstraint {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self { dest, src } = *self;
|
||||||
|
write!(f, "{dest} == {dest} & {src}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BoolFixedPointSolver {
|
||||||
|
variables_union_find: UnionFind<usize>,
|
||||||
|
variables_value: Vec<bool>,
|
||||||
|
maximally_constrained: Vec<bool>,
|
||||||
|
unconstrained_variables_value: bool,
|
||||||
|
solved: bool,
|
||||||
|
and_constraints: BTreeSet<AndConstraint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for BoolFixedPointSolver {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let Self {
|
||||||
|
variables_union_find,
|
||||||
|
variables_value,
|
||||||
|
maximally_constrained,
|
||||||
|
unconstrained_variables_value,
|
||||||
|
solved,
|
||||||
|
and_constraints,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("BoolFixedPointSolver")
|
||||||
|
.field(
|
||||||
|
"variables_union_find",
|
||||||
|
&fmt::from_fn(|f| {
|
||||||
|
f.debug_map()
|
||||||
|
.entries(
|
||||||
|
(0..variables_union_find.len())
|
||||||
|
.map(|i| (Variable(i), Variable(variables_union_find.find(i)))),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.field(
|
||||||
|
"variables_value",
|
||||||
|
&fmt::from_fn(|f| {
|
||||||
|
let mut debug_map = f.debug_map();
|
||||||
|
for (i, v) in variables_value.iter().enumerate() {
|
||||||
|
if variables_union_find.find(i) == i {
|
||||||
|
debug_map.entry(&Variable(i), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_map.finish()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.field(
|
||||||
|
"maximally_constrained",
|
||||||
|
&fmt::from_fn(|f| {
|
||||||
|
let mut debug_map = f.debug_map();
|
||||||
|
for (i, v) in maximally_constrained.iter().enumerate() {
|
||||||
|
if variables_union_find.find(i) == i {
|
||||||
|
debug_map.entry(&Variable(i), v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_map.finish()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.field(
|
||||||
|
"unconstrained_variables_value",
|
||||||
|
unconstrained_variables_value,
|
||||||
|
)
|
||||||
|
.field("solved", solved)
|
||||||
|
.field("and_constraints", and_constraints)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoolFixedPointSolver {
|
||||||
|
pub const fn new(unconstrained_variables_value: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
variables_union_find: UnionFind::new_empty(),
|
||||||
|
variables_value: Vec::new(),
|
||||||
|
maximally_constrained: Vec::new(),
|
||||||
|
unconstrained_variables_value,
|
||||||
|
solved: false,
|
||||||
|
and_constraints: BTreeSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn unconstrained_variables_value(&self) -> bool {
|
||||||
|
self.unconstrained_variables_value
|
||||||
|
}
|
||||||
|
pub fn new_variable(&mut self) -> Variable {
|
||||||
|
let index = self.variables_union_find.new_set();
|
||||||
|
self.variables_value
|
||||||
|
.push(self.unconstrained_variables_value);
|
||||||
|
self.maximally_constrained.push(false);
|
||||||
|
self.solved = false;
|
||||||
|
Variable(index)
|
||||||
|
}
|
||||||
|
pub fn variable_count(&self) -> usize {
|
||||||
|
self.variables_union_find.len()
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_variable_in_range(&self, variable: Variable) {
|
||||||
|
if variable.0 >= self.variable_count() {
|
||||||
|
panic!("invalid variable {variable:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn add_constraint(&mut self, constraint: Constraint) {
|
||||||
|
self.solved = false;
|
||||||
|
match constraint {
|
||||||
|
Constraint::MaximallyConstrained { variable } => {
|
||||||
|
self.assert_variable_in_range(variable);
|
||||||
|
self.maximally_constrained[self.variables_union_find.find_mut(variable.0)] = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Constraint::Equal { dest, src } => {
|
||||||
|
self.assert_variable_in_range(dest);
|
||||||
|
self.assert_variable_in_range(src);
|
||||||
|
let maximally_constrained = self.maximally_constrained
|
||||||
|
[self.variables_union_find.find_mut(dest.0)]
|
||||||
|
| self.maximally_constrained[self.variables_union_find.find_mut(src.0)];
|
||||||
|
self.variables_union_find.union(dest.0, src.0);
|
||||||
|
let merged_index = self.variables_union_find.find_mut(dest.0);
|
||||||
|
self.maximally_constrained[merged_index] = maximally_constrained;
|
||||||
|
}
|
||||||
|
Constraint::And { dest, src } => {
|
||||||
|
self.assert_variable_in_range(src);
|
||||||
|
self.assert_variable_in_range(dest);
|
||||||
|
if src != dest {
|
||||||
|
self.and_constraints.insert(AndConstraint { dest, src });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Constraint::Or { dest, src } => {
|
||||||
|
self.assert_variable_in_range(src);
|
||||||
|
self.assert_variable_in_range(dest);
|
||||||
|
if src != dest {
|
||||||
|
self.and_constraints
|
||||||
|
.insert(AndConstraint::from_or_constraint(dest, src));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn solve(&mut self) {
|
||||||
|
for (value, maximally_constrained) in self
|
||||||
|
.variables_value
|
||||||
|
.iter_mut()
|
||||||
|
.zip(&self.maximally_constrained)
|
||||||
|
{
|
||||||
|
*value = self.unconstrained_variables_value ^ *maximally_constrained;
|
||||||
|
}
|
||||||
|
let mut variables_to_constraints_map: Vec<Vec<AndConstraint>> =
|
||||||
|
vec![Vec::new(); self.variable_count()];
|
||||||
|
for &AndConstraint { mut dest, mut src } in &self.and_constraints {
|
||||||
|
dest.0 = self.variables_union_find.find_mut(dest.0);
|
||||||
|
src.0 = self.variables_union_find.find_mut(src.0);
|
||||||
|
if dest == src {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let constraint = AndConstraint { dest, src };
|
||||||
|
variables_to_constraints_map[dest.0].push(constraint);
|
||||||
|
variables_to_constraints_map[src.0].push(constraint);
|
||||||
|
}
|
||||||
|
let mut worklist: Vec<Variable> = (0..self.variable_count())
|
||||||
|
.filter(|&index| self.variables_union_find.find_mut(index) == index)
|
||||||
|
.map(Variable)
|
||||||
|
.collect();
|
||||||
|
while let Some(variable) = worklist.pop() {
|
||||||
|
for &AndConstraint { dest, src } in &variables_to_constraints_map[variable.0] {
|
||||||
|
let dest_value = self.variables_value[dest.0];
|
||||||
|
let src_value = self.variables_value[src.0];
|
||||||
|
// equivalent to `dest_value != dest_value & src_value`:
|
||||||
|
let is_unsatisfied = dest_value && !src_value;
|
||||||
|
if is_unsatisfied {
|
||||||
|
if self.unconstrained_variables_value {
|
||||||
|
self.variables_value[dest.0] = false;
|
||||||
|
worklist.push(dest);
|
||||||
|
} else {
|
||||||
|
self.variables_value[src.0] = true;
|
||||||
|
worklist.push(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.solved = true;
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
pub fn value(&mut self, variable: Variable) -> bool {
|
||||||
|
#[cold]
|
||||||
|
fn solve_cold(this: &mut BoolFixedPointSolver) {
|
||||||
|
this.solve();
|
||||||
|
}
|
||||||
|
self.assert_variable_in_range(variable);
|
||||||
|
if !self.solved {
|
||||||
|
solve_cold(self);
|
||||||
|
}
|
||||||
|
self.variables_value[self.variables_union_find.find_mut(variable.0)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
|
struct TestCase<'a, C, Vars, Vals> {
|
||||||
|
variable_count: usize,
|
||||||
|
expected_values: Option<&'a [bool]>,
|
||||||
|
constraints: C,
|
||||||
|
variables: Vars,
|
||||||
|
values: Vals,
|
||||||
|
solver: BoolFixedPointSolver,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, C: FnOnce(&[Variable]) -> I, I: IntoIterator<Item = Constraint>> TestCase<'a, C, (), ()> {
|
||||||
|
fn new_expected(
|
||||||
|
unconstrained_variables_value: bool,
|
||||||
|
expected_values: &'a [bool],
|
||||||
|
constraints: C,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
variable_count: expected_values.len(),
|
||||||
|
expected_values: Some(expected_values),
|
||||||
|
constraints,
|
||||||
|
variables: (),
|
||||||
|
values: (),
|
||||||
|
solver: BoolFixedPointSolver::new(unconstrained_variables_value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn get_constraints_and_variables(
|
||||||
|
self,
|
||||||
|
) -> TestCase<'a, Vec<Constraint>, Vec<Variable>, [bool; 0]> {
|
||||||
|
let Self {
|
||||||
|
variable_count,
|
||||||
|
expected_values,
|
||||||
|
constraints,
|
||||||
|
variables: (),
|
||||||
|
values: (),
|
||||||
|
mut solver,
|
||||||
|
} = self;
|
||||||
|
assert_eq!(
|
||||||
|
expected_values.map_or(variable_count, |v| v.len()),
|
||||||
|
variable_count,
|
||||||
|
);
|
||||||
|
let variables = Vec::from_iter((0..variable_count).map(|_| solver.new_variable()));
|
||||||
|
let constraints = Vec::from_iter(constraints(&variables));
|
||||||
|
TestCase {
|
||||||
|
variable_count,
|
||||||
|
expected_values,
|
||||||
|
constraints,
|
||||||
|
variables,
|
||||||
|
values: [],
|
||||||
|
solver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestCase<'a, Vec<Constraint>, Vec<Variable>, [bool; 0]> {
|
||||||
|
#[track_caller]
|
||||||
|
fn add_and_check_constraints(&mut self) {
|
||||||
|
if let Some(expected_values) = self.expected_values {
|
||||||
|
self.check_constraints("expected values", expected_values);
|
||||||
|
}
|
||||||
|
for &constraint in &self.constraints {
|
||||||
|
self.solver.add_constraint(constraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn get_values(self) -> TestCase<'a, Vec<Constraint>, Vec<Variable>, Vec<bool>> {
|
||||||
|
let Self {
|
||||||
|
variable_count,
|
||||||
|
expected_values,
|
||||||
|
constraints,
|
||||||
|
variables,
|
||||||
|
values: [],
|
||||||
|
mut solver,
|
||||||
|
} = self;
|
||||||
|
let values = Vec::from_iter(variables.iter().map(|&v| solver.value(v)));
|
||||||
|
TestCase {
|
||||||
|
variable_count,
|
||||||
|
expected_values,
|
||||||
|
constraints,
|
||||||
|
variables,
|
||||||
|
values,
|
||||||
|
solver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TestCase<'a, Vec<Constraint>, Vec<Variable>, Vec<bool>> {
|
||||||
|
#[track_caller]
|
||||||
|
fn check_values(&self) {
|
||||||
|
let Self {
|
||||||
|
variable_count: _,
|
||||||
|
expected_values,
|
||||||
|
constraints: _,
|
||||||
|
variables,
|
||||||
|
values,
|
||||||
|
solver: _,
|
||||||
|
} = self;
|
||||||
|
if let Some(expected_values) = expected_values {
|
||||||
|
for ((&expected_value, &variable), &value) in
|
||||||
|
expected_values.iter().zip(variables).zip(values)
|
||||||
|
{
|
||||||
|
if expected_value != value {
|
||||||
|
self.error(format_args!(
|
||||||
|
"solver output for {variable} of {value:?} doesn't \
|
||||||
|
match expected value of {expected_value:?}",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.check_constraints("solved values", values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Vals: AsRef<[bool]>> TestCase<'a, Vec<Constraint>, Vec<Variable>, Vals> {
|
||||||
|
#[track_caller]
|
||||||
|
fn check_constraints(&self, values_name: &str, values: &[bool]) {
|
||||||
|
let unconstrained_variables_value = self.solver.unconstrained_variables_value();
|
||||||
|
let v = |variable: Variable| values[variable.index()];
|
||||||
|
for &constraint in &self.constraints {
|
||||||
|
let satisfied = match constraint {
|
||||||
|
Constraint::MaximallyConstrained { variable } => {
|
||||||
|
v(variable) != unconstrained_variables_value
|
||||||
|
}
|
||||||
|
Constraint::Equal { dest, src } => v(dest) == v(src),
|
||||||
|
Constraint::And { dest, src } => v(dest) == v(dest) & v(src),
|
||||||
|
Constraint::Or { dest, src } => v(dest) == v(dest) | v(src),
|
||||||
|
};
|
||||||
|
if !satisfied {
|
||||||
|
self.error(format_args!(
|
||||||
|
"{values_name} don't satisfy constraint: {constraint:#?}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn error(&self, msg: fmt::Arguments<'_>) -> ! {
|
||||||
|
let Self {
|
||||||
|
variable_count,
|
||||||
|
expected_values,
|
||||||
|
ref constraints,
|
||||||
|
ref variables,
|
||||||
|
ref values,
|
||||||
|
ref solver,
|
||||||
|
} = *self;
|
||||||
|
let values = values.as_ref();
|
||||||
|
panic!(
|
||||||
|
"{msg}\n\
|
||||||
|
values={values:#?}\n\
|
||||||
|
constraints={constraints:#?}\n\
|
||||||
|
solver={solver:#?}",
|
||||||
|
values = fmt::from_fn(|f| {
|
||||||
|
let mut debug_map = f.debug_map();
|
||||||
|
for i in 0..variable_count {
|
||||||
|
debug_map.key(&variables[i]);
|
||||||
|
if let Some(value) = values.get(i) {
|
||||||
|
if let Some(expected_values) = expected_values {
|
||||||
|
debug_map.value(&format_args!(
|
||||||
|
"{value:?} (expected: {:?})",
|
||||||
|
expected_values[i],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
debug_map.value(value);
|
||||||
|
}
|
||||||
|
} else if let Some(expected_values) = expected_values {
|
||||||
|
debug_map.value(&format_args!("(expected: {:?})", expected_values[i]));
|
||||||
|
} else {
|
||||||
|
debug_map.value(&format_args!("None"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_map.finish()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test_case<I: IntoIterator<Item = Constraint>>(
|
||||||
|
test_case: TestCase<'_, impl FnOnce(&[Variable]) -> I, (), ()>,
|
||||||
|
) {
|
||||||
|
let mut test_case = test_case.get_constraints_and_variables();
|
||||||
|
test_case.add_and_check_constraints();
|
||||||
|
let test_case = test_case.get_values();
|
||||||
|
test_case.check_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_simple() {
|
||||||
|
test_case(TestCase::new_expected(false, &[], |_| []));
|
||||||
|
test_case(TestCase::new_expected(true, &[], |_| []));
|
||||||
|
test_case(TestCase::new_expected(false, &[false], |_| []));
|
||||||
|
test_case(TestCase::new_expected(true, &[true], |_| []));
|
||||||
|
test_case(TestCase::new_expected(false, &[true], |v| {
|
||||||
|
[Constraint::MaximallyConstrained { variable: v[0] }]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false], |v| {
|
||||||
|
[Constraint::MaximallyConstrained { variable: v[0] }]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(false, &[true, true], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Equal {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false, false], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Equal {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(false, &[true, false], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::And {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false, false], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::And {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(false, &[true, true], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::And {
|
||||||
|
dest: v[0],
|
||||||
|
src: v[1],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false, true], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::And {
|
||||||
|
dest: v[0],
|
||||||
|
src: v[1],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(false, &[true, true], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Or {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false, true], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Or {
|
||||||
|
dest: v[1],
|
||||||
|
src: v[0],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(false, &[true, false], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Or {
|
||||||
|
dest: v[0],
|
||||||
|
src: v[1],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
test_case(TestCase::new_expected(true, &[false, false], |v| {
|
||||||
|
[
|
||||||
|
Constraint::MaximallyConstrained { variable: v[0] },
|
||||||
|
Constraint::Or {
|
||||||
|
dest: v[0],
|
||||||
|
src: v[1],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Rng {
|
||||||
|
state: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
fn new(test_case_index: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
state: (test_case_index as u64) << 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn next_u64(&mut self) -> u64 {
|
||||||
|
self.state += 1;
|
||||||
|
// 4 random primes and 4 random rotate amounts
|
||||||
|
self.state
|
||||||
|
.wrapping_mul(0xA3C7_8807_EA6D_A4F9)
|
||||||
|
.rotate_left(43)
|
||||||
|
.wrapping_mul(0x1CCA_797A_6BF8_8C63)
|
||||||
|
.rotate_left(8)
|
||||||
|
.wrapping_mul(0xCC50_AA59_7C41_946F)
|
||||||
|
.rotate_left(12)
|
||||||
|
.wrapping_mul(0xFB2A_0137_F878_C4B5)
|
||||||
|
.rotate_left(58)
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn next_u64_in_range(&mut self, range: std::ops::Range<u64>) -> u64 {
|
||||||
|
let Some(len) = range.end.checked_sub(range.start).and_then(NonZero::new) else {
|
||||||
|
panic!("empty range: {range:?}");
|
||||||
|
};
|
||||||
|
let max_quotient = u64::MAX / len;
|
||||||
|
loop {
|
||||||
|
let next_u64 = self.next_u64();
|
||||||
|
let quotient = next_u64 / len;
|
||||||
|
let remainder = next_u64 % len;
|
||||||
|
if quotient < max_quotient {
|
||||||
|
return remainder + range.start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn next_usize_in_range(&mut self, range: std::ops::Range<usize>) -> usize {
|
||||||
|
self.next_u64_in_range(range.start as u64..range.end as u64) as usize
|
||||||
|
}
|
||||||
|
#[track_caller]
|
||||||
|
fn next_from_slice<'a, T>(&mut self, slice: &'a [T]) -> &'a T {
|
||||||
|
assert!(!slice.is_empty());
|
||||||
|
&slice[self.next_usize_in_range(0..slice.len())]
|
||||||
|
}
|
||||||
|
fn next_bool(&mut self) -> bool {
|
||||||
|
(self.next_u64() & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test_bool_fixed_point_solver_random_case(test_case_index: u32) {
|
||||||
|
println!("test_bool_fixed_point_solver_random_case({test_case_index})");
|
||||||
|
let mut rng = Rng::new(test_case_index);
|
||||||
|
// bias towards smaller problems to make them easier to debug
|
||||||
|
let variable_count = rng
|
||||||
|
.next_u64_in_range(1..1_000_000)
|
||||||
|
.pow(2)
|
||||||
|
.div_ceil(1_000_000_000) as usize;
|
||||||
|
let constraint_count =
|
||||||
|
rng.next_usize_in_range(0..(variable_count * variable_count).clamp(0, 10000));
|
||||||
|
let solver = BoolFixedPointSolver::new(rng.next_bool());
|
||||||
|
test_case(TestCase {
|
||||||
|
variable_count,
|
||||||
|
expected_values: None,
|
||||||
|
constraints: |variables: &[Variable]| {
|
||||||
|
Vec::from_iter(
|
||||||
|
(0..constraint_count).map(|_| match rng.next_usize_in_range(0..4) {
|
||||||
|
0 => Constraint::MaximallyConstrained {
|
||||||
|
variable: *rng.next_from_slice(variables),
|
||||||
|
},
|
||||||
|
1 => Constraint::Equal {
|
||||||
|
dest: *rng.next_from_slice(variables),
|
||||||
|
src: *rng.next_from_slice(variables),
|
||||||
|
},
|
||||||
|
2 => Constraint::And {
|
||||||
|
dest: *rng.next_from_slice(variables),
|
||||||
|
src: *rng.next_from_slice(variables),
|
||||||
|
},
|
||||||
|
3 => Constraint::Or {
|
||||||
|
dest: *rng.next_from_slice(variables),
|
||||||
|
src: *rng.next_from_slice(variables),
|
||||||
|
},
|
||||||
|
4.. => unreachable!(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
variables: (),
|
||||||
|
values: (),
|
||||||
|
solver,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const CASES_FULL_RANGE: std::ops::Range<u32> = 0..100_000;
|
||||||
|
|
||||||
|
fn mul_div(v: u32, factor: u32, divisor: u32) -> u32 {
|
||||||
|
((v as u64 * factor as u64) / divisor as u64) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases(split_index: u32) {
|
||||||
|
assert!(split_index < CASES_SPLIT_COUNT);
|
||||||
|
let full_range_len = CASES_FULL_RANGE.end - CASES_FULL_RANGE.start;
|
||||||
|
let start = mul_div(split_index, full_range_len, CASES_SPLIT_COUNT);
|
||||||
|
let end = mul_div(split_index + 1, full_range_len, CASES_SPLIT_COUNT);
|
||||||
|
for test_case_index in start..end {
|
||||||
|
test_bool_fixed_point_solver_random_case(test_case_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CASES_SPLIT_COUNT: u32 = 10;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_0() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_1() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_2() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_3() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_4() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_5() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_6() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_7() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_8() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bool_fixed_point_solver_random_cases_9() {
|
||||||
|
test_bool_fixed_point_solver_random_cases(9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::{DeserializeOwned, Error, Unexpected},
|
||||||
|
};
|
||||||
use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr};
|
use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr};
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
|
|
@ -9,7 +13,17 @@ mod sealed {
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// the only implementation is `ConstBool<Self::VALUE>`
|
/// the only implementation is `ConstBool<Self::VALUE>`
|
||||||
pub unsafe trait GenericConstBool:
|
pub unsafe trait GenericConstBool:
|
||||||
sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync
|
sealed::Sealed
|
||||||
|
+ Copy
|
||||||
|
+ Ord
|
||||||
|
+ Hash
|
||||||
|
+ Default
|
||||||
|
+ Debug
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ Serialize
|
||||||
|
+ DeserializeOwned
|
||||||
{
|
{
|
||||||
const VALUE: bool;
|
const VALUE: bool;
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +44,32 @@ unsafe impl<const VALUE: bool> GenericConstBool for ConstBool<VALUE> {
|
||||||
const VALUE: bool = VALUE;
|
const VALUE: bool = VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const VALUE: bool> Serialize for ConstBool<VALUE> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
VALUE.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, const VALUE: bool> Deserialize<'de> for ConstBool<VALUE> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = bool::deserialize(deserializer)?;
|
||||||
|
if value == VALUE {
|
||||||
|
Ok(ConstBool)
|
||||||
|
} else {
|
||||||
|
Err(D::Error::invalid_value(
|
||||||
|
Unexpected::Bool(value),
|
||||||
|
&if VALUE { "true" } else { "false" },
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ConstBoolDispatchTag {
|
pub trait ConstBoolDispatchTag {
|
||||||
type Type<Select: GenericConstBool>;
|
type Type<Select: GenericConstBool>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::{DeserializeOwned, Error, Unexpected},
|
||||||
|
};
|
||||||
use std::{fmt::Debug, hash::Hash};
|
use std::{fmt::Debug, hash::Hash};
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
|
|
@ -8,7 +12,17 @@ mod sealed {
|
||||||
|
|
||||||
/// the only implementation is `ConstUsize<Self::VALUE>`
|
/// the only implementation is `ConstUsize<Self::VALUE>`
|
||||||
pub trait GenericConstUsize:
|
pub trait GenericConstUsize:
|
||||||
sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync
|
sealed::Sealed
|
||||||
|
+ Copy
|
||||||
|
+ Ord
|
||||||
|
+ Hash
|
||||||
|
+ Default
|
||||||
|
+ Debug
|
||||||
|
+ 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ Serialize
|
||||||
|
+ DeserializeOwned
|
||||||
{
|
{
|
||||||
const VALUE: usize;
|
const VALUE: usize;
|
||||||
}
|
}
|
||||||
|
|
@ -27,3 +41,29 @@ impl<const VALUE: usize> sealed::Sealed for ConstUsize<VALUE> {}
|
||||||
impl<const VALUE: usize> GenericConstUsize for ConstUsize<VALUE> {
|
impl<const VALUE: usize> GenericConstUsize for ConstUsize<VALUE> {
|
||||||
const VALUE: usize = VALUE;
|
const VALUE: usize = VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const VALUE: usize> Serialize for ConstUsize<VALUE> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
VALUE.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, const VALUE: usize> Deserialize<'de> for ConstUsize<VALUE> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = usize::deserialize(deserializer)?;
|
||||||
|
if value == VALUE {
|
||||||
|
Ok(ConstUsize)
|
||||||
|
} else {
|
||||||
|
Err(D::Error::invalid_value(
|
||||||
|
Unexpected::Unsigned(value as u64),
|
||||||
|
&&*VALUE.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue