Compare commits

..

77 commits

Author SHA1 Message Date
Jacob Lifshay 3d5d8c54b6
add repository to cache key
All checks were successful
/ deps (push) Successful in 10m39s
/ test (push) Successful in 5m14s
2024-10-30 20:55:02 -07:00
Jacob Lifshay ee15fd2b94
support #[hdl] type aliases
All checks were successful
/ deps (push) Successful in 11m28s
/ test (push) Successful in 4m40s
2024-10-30 20:47:10 -07:00
Jacob Lifshay 20cf0abbcc
fix using #[hdl] types like S<{ 1 + 2 }> 2024-10-30 20:46:11 -07:00
Jacob Lifshay 5bd0de48b7
change to version 0.2.1 2024-10-30 19:36:05 -07:00
Jacob Lifshay 0c9c48a066
split out deps into separate workflow with better caching using deps.yml from cpu.git
All checks were successful
/ deps (push) Successful in 21s
/ test (push) Successful in 19m32s
2024-10-17 21:05:18 -07:00
Jacob Lifshay cb17913004
limit sby to one thread each since it seems not to respect job count in parallel mode
All checks were successful
/ test (push) Successful in 46m32s
2024-10-15 21:32:38 -07:00
Jacob Lifshay 42effd1132
switch to using a make job server for managing test parallelism
Some checks failed
/ test (push) Failing after 39m16s
2024-10-15 20:32:33 -07:00
Jacob Lifshay 3d0f95cfe5
formal: add workaround for wires disappearing because yosys optimizes them out
All checks were successful
/ test (push) Successful in 39m13s
2024-10-15 01:48:48 -07:00
Jacob Lifshay 3939ce2360
add Bundle and Enum to prelude
All checks were successful
/ test (push) Successful in 39m33s
2024-10-14 17:47:58 -07:00
Jacob Lifshay d0229fbcfb
get #[hdl] struct S<A: KnownSize, B: KnownSize> to work
All checks were successful
/ test (push) Successful in 39m2s
2024-10-11 17:30:49 -07:00
Jacob Lifshay 4909724995
add more thorough checks that bounds are properly handled on #[hdl] structs
All checks were successful
/ test (push) Successful in 37m37s
2024-10-10 23:34:46 -07:00
Jacob Lifshay d0694cbd52
add disabled test for #[hdl] struct S4<W: KnownSize, W2: KnownSize> which type errors
Some checks failed
/ test (push) Has been cancelled
2024-10-10 22:58:15 -07:00
Jacob Lifshay 1a2149b040
silence warnings for field names that start with _
All checks were successful
/ test (push) Successful in 37m30s
2024-10-10 20:53:29 -07:00
Jacob Lifshay 59cef3f398
add PhantomData as a hdl bundle
Some checks failed
/ test (push) Has been cancelled
2024-10-10 20:48:09 -07:00
Jacob Lifshay bf907c3872
cache results of formal proofs
All checks were successful
/ test (push) Successful in 39m54s
2024-10-07 23:31:24 -07:00
Jacob Lifshay 99180eb3b4
fix clippy lints in generated code
All checks were successful
/ test (push) Successful in 37m34s
2024-10-07 22:06:59 -07:00
Jacob Lifshay 017c14a2f1
don't use #[allow(..., reason = "...")] since that's not stable yet on rust 1.80.1 2024-10-07 22:06:59 -07:00
Jacob Lifshay ed1aea41f3
clean up some clippy warnings
Some checks failed
/ test (push) Failing after 3m41s
2024-10-07 21:49:18 -07:00
Jacob Lifshay f12322aa2a
remove interning contexts 2024-10-07 21:33:56 -07:00
Jacob Lifshay 44ca1a607a
remove unused AGCContext 2024-10-07 21:23:13 -07:00
Jacob Lifshay 30b9a5e48d
change NameId to have an opaque Id so output firrtl doesn't depend on how many modules of the same name were ever created
All checks were successful
/ test (push) Successful in 39m6s
2024-10-07 19:06:01 -07:00
Jacob Lifshay eed0afc6ab
add some utility From<Interned<T>> impls 2024-10-07 19:05:20 -07:00
Jacob Lifshay aec383c0af
try to fix ccache
All checks were successful
/ test (push) Successful in 1h19m11s
2024-10-06 20:57:42 -07:00
Jacob Lifshay f403eed7c0
only run tests once, since they are quite slow
Some checks failed
/ test (push) Has been cancelled
2024-10-06 20:08:39 -07:00
Jacob Lifshay 2e8b73d2fc
rename fire/fire_data to firing/firing_data 2024-10-06 19:04:48 -07:00
Jacob Lifshay e05c368688
change register names to end in _reg by convention 2024-10-06 18:50:09 -07:00
Jacob Lifshay ec77559e2b
fix cache action name
All checks were successful
/ test (push) Successful in 1h44m43s
2024-10-04 17:10:06 -07:00
Jacob Lifshay b7f1101164
reduce parallelism to fit within the number of available cpus even when running sby in prove mode (which likes to run 2 smt solvers in parallel)
Some checks failed
/ test (push) Has been cancelled
2024-10-04 17:03:51 -07:00
Jacob Lifshay 487af07154
yosys build runs out of memory
Some checks failed
/ test (push) Failing after 1h8m27s
2024-10-04 01:03:17 -07:00
Jacob Lifshay c0d4de56a9
try to make yosys build faster
Some checks failed
/ test (push) Failing after 24m17s
2024-10-03 23:40:44 -07:00
Jacob Lifshay 9f154e6b96
try caching ccache manually
Some checks are pending
/ test (push) Waiting to run
2024-10-03 23:36:39 -07:00
Jacob Lifshay 0d54b9a2a9
queue formal proof passes!
Some checks are pending
/ test (push) Has started running
2024-10-03 23:07:14 -07:00
Jacob Lifshay 343805f80b
fix #[hdl] to work with unusual identifier hygiene from macros 2024-10-03 23:04:14 -07:00
Jacob Lifshay 15a28aa7a7
install python3-click -- needed by symbiyosys
All checks were successful
/ test (push) Successful in 33m5s
2024-10-03 01:44:06 -07:00
Jacob Lifshay 4084a70485
switch default solver to z3 2024-10-03 01:43:46 -07:00
Jacob Lifshay 3e2fb9b94f
WIP getting queue formal to pass -- passes for capacity <= 2
Some checks failed
/ test (push) Has been cancelled
2024-10-03 01:08:01 -07:00
Jacob Lifshay bc26fe32fd
add ccache and clean up deps
Some checks failed
/ test (push) Has been cancelled
2024-10-03 01:01:06 -07:00
Jacob Lifshay eb65bec26e
add yosys deps
Some checks failed
/ test (push) Has been cancelled
2024-10-03 00:44:04 -07:00
Jacob Lifshay 4497f09ea0
fix wrong build steps
Some checks failed
/ test (push) Failing after 1m2s
2024-10-03 00:39:18 -07:00
Jacob Lifshay 1c63a441a9
add needed tools to CI
Some checks failed
/ test (push) Failing after 1m5s
2024-10-03 00:35:43 -07:00
Jacob Lifshay 0cf01600b3
add mod formal and move assert/assume/cover stuff to it 2024-10-01 19:56:17 -07:00
Jacob Lifshay f3d6528f5b
make annotations easier to use 2024-10-01 19:54:17 -07:00
Jacob Lifshay f35d88d2bb
remove unused valueless.rs 2024-10-01 18:41:41 -07:00
Jacob Lifshay e8c393f3bb
sort pub mod items 2024-10-01 18:40:52 -07:00
Jacob Lifshay d0b406d288
add more annotation kinds
All checks were successful
/ test (push) Successful in 4m45s
2024-10-01 18:33:32 -07:00
Jacob Lifshay 2a25dd9d7b
fix annotations getting lost 2024-10-01 18:31:44 -07:00
Jacob Lifshay 6e0b6c000d
remove stray debugging prints 2024-10-01 18:30:46 -07:00
Jacob Lifshay d089095667
change default to --simplify-enums=replace-with-bundle-of-uints
All checks were successful
/ test (push) Successful in 4m42s
2024-10-01 00:07:48 -07:00
Jacob Lifshay 9d66fcc548
improve ExportOptions support in assert_export_firrtl! 2024-10-01 00:05:39 -07:00
Jacob Lifshay 186488a82e
remove FIXME now that simplify_enums is fixed 2024-09-30 23:31:45 -07:00
Jacob Lifshay edcea1adc3
add firrtl comments when connecting expressions with different types
All checks were successful
/ test (push) Successful in 4m44s
2024-09-30 22:33:27 -07:00
Jacob Lifshay 30a38bc8da
fix simplify_enums to properly handle nested enums and connects with different types 2024-09-30 22:31:16 -07:00
Jacob Lifshay 1e2831da47
add validation of connects and matches when validating module
this is useful for catching errors in transformation passes
2024-09-30 21:20:35 -07:00
Jacob Lifshay d2ba313f0f
fix simplify_memories trying to connect Bool with UInt 2024-09-30 21:19:20 -07:00
Jacob Lifshay 04752c5037
add test for connect_any with nested enums with different-sized variant bodies
All checks were successful
/ test (push) Successful in 4m42s
simplify_enums is currently broken in that case
2024-09-25 21:55:52 -07:00
Jacob Lifshay e661aeab11
add WIP formal proof for queue()
All checks were successful
/ test (push) Successful in 5m27s
2024-09-25 02:00:06 -07:00
Jacob Lifshay 5fc7dbd6e9
add assert_formal helper for running formal proofs in rust tests 2024-09-25 02:00:06 -07:00
Jacob Lifshay 45dbb554d0
add formal subcommand 2024-09-25 02:00:06 -07:00
Jacob Lifshay bb860d54cc
add command line options for selecting which transforms to apply when generating firrtl 2024-09-25 02:00:06 -07:00
Jacob Lifshay efc3a539ed
support redirecting subprocesses' stdout/stderr to print!() so it gets captured for rust tests 2024-09-25 02:00:06 -07:00
Jacob Lifshay f32c0a7863
switch to #[derive(Parser)] instead of #[derive(Args)] 2024-09-25 01:28:11 -07:00
Jacob Lifshay 4ff01690a7
clean up deps and move missed deps to workspace 2024-09-25 01:22:35 -07:00
Jacob Lifshay 28aad19bf5
add assert/assume/cover
All checks were successful
/ test (push) Successful in 4m33s
2024-09-23 19:10:51 -07:00
Jacob Lifshay 716c65edcd
add WIP version of queue()
All checks were successful
/ test (push) Successful in 4m36s
2024-09-22 18:59:12 -07:00
Jacob Lifshay f6146048d1
add memory::splat_mask to generate mask types from a Bool 2024-09-22 18:57:30 -07:00
Jacob Lifshay a701f99fd6
add repeat() 2024-09-22 18:56:26 -07:00
Jacob Lifshay 78edfc97b2
split int::IntCmp into expr::HdlPartialEq and expr::HdlPartialOrd
All checks were successful
/ test (push) Successful in 4m32s
2024-09-22 17:28:46 -07:00
Jacob Lifshay 9ad4ec0f39
add ty.uninit()
All checks were successful
/ test (push) Successful in 4m30s
2024-09-22 17:26:23 -07:00
Jacob Lifshay 8449854cac
add ToExpr for usize/isize/NonZero<T>
All checks were successful
/ test (push) Successful in 4m32s
2024-09-22 17:19:58 -07:00
Jacob Lifshay 790bb15408
remove reset_default from proc-macro, forgot to remove when removing from RegBuilder 2024-09-22 16:03:20 -07:00
Jacob Lifshay bdbc6d89bd
add check-copyright to CI
All checks were successful
/ test (push) Successful in 4m33s
2024-09-22 15:30:53 -07:00
Jacob Lifshay 10ae95fac1
add missing copyright headers 2024-09-22 15:30:05 -07:00
Jacob Lifshay 053391b010
add script for checking copyright headers 2024-09-22 15:29:28 -07:00
Jacob Lifshay 51ce7b079e
add ReadyValid<T>
All checks were successful
/ test (push) Successful in 4m26s
2024-09-20 19:11:30 -07:00
Jacob Lifshay ff269e5def
add utility functions on HdlOption, inspired by Option's API 2024-09-20 18:49:12 -07:00
Jacob Lifshay df55a514e4
add support for incomplete_wire -- a wire that you can supply the type of later 2024-09-20 18:46:56 -07:00
Jacob Lifshay ff94dda922
support #[hdl] on functions -- enables #[hdl] usage in function body 2024-09-20 18:42:24 -07:00
71 changed files with 6417 additions and 1762 deletions

View file

@ -0,0 +1,77 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on:
workflow_call:
outputs:
cache-primary-key:
value: ${{ jobs.deps.outputs.cache-primary-key }}
jobs:
deps:
runs-on: debian-12
outputs:
cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }}
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
with:
fetch-depth: 0
- uses: https://code.forgejo.org/actions/cache/restore@v3
id: restore-deps
with:
path: deps
key: ${{ github.repository }}-deps-${{ runner.os }}-${{ hashFiles('.forgejo/workflows/deps.yml') }}
lookup-only: true
- name: Install Apt packages
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
g++ \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
zlib1g-dev
- name: Install Firtool
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
mkdir -p deps
wget -O deps/firrtl.tar.gz https://github.com/llvm/circt/releases/download/firtool-1.86.0/firrtl-bin-linux-x64.tar.gz
sha256sum -c - <<<'bf6f4ab18ae76f135c944efbd81e25391c31c1bd0617c58ab0592640abefee14 deps/firrtl.tar.gz'
tar -C deps -xvaf deps/firrtl.tar.gz
rm -rf deps/firtool
mv deps/firtool-1.86.0 deps/firtool
- name: Get SymbiYosys
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --branch=yosys-0.45 https://github.com/YosysHQ/sby.git deps/sby
- name: Build Z3
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --recursive --branch=z3-4.13.3 https://github.com/Z3Prover/z3.git deps/z3
(cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local)
make -C deps/z3/build -j"$(nproc)"
- name: Build Yosys
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
git clone --depth=1 --recursive --branch=0.45 https://github.com/YosysHQ/yosys.git deps/yosys
make -C deps/yosys -j"$(nproc)"
- uses: https://code.forgejo.org/actions/cache/save@v3
if: steps.restore-deps.outputs.cache-hit != 'true'
with:
path: deps
key: ${{ steps.restore-deps.outputs.cache-primary-key }}

View file

@ -1,19 +1,60 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on: [push, pull_request]
jobs:
deps:
uses: ./.forgejo/workflows/deps.yml
test:
runs-on: debian-12
needs: deps
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
with:
fetch-depth: 0
- run: |
scripts/check-copyright.sh
- run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
z3 \
zlib1g-dev
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1
source "$HOME/.cargo/env"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://code.forgejo.org/actions/cache/restore@v3
with:
path: deps
key: ${{ needs.deps.outputs.cache-primary-key }}
fail-on-cache-miss: true
- run: |
make -C deps/z3/build install
make -C deps/sby install
make -C deps/yosys install
export PATH="$(realpath deps/firtool/bin):$PATH"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://github.com/Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: cargo test
- run: cargo test --features=unstable-doc
- run: cargo build --tests --features=unstable-doc
- run: cargo doc --features=unstable-doc

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
/target
.vscode

190
Cargo.lock generated
View file

@ -56,7 +56,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -66,9 +66,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"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]]
name = "autocfg"
version = "1.1.0"
@ -109,6 +121,20 @@ dependencies = [
"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]]
name = "block-buffer"
version = "0.10.4"
@ -118,6 +144,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -170,6 +205,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -189,6 +230,27 @@ dependencies = [
"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]]
name = "digest"
version = "0.10.7"
@ -218,7 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -239,32 +301,37 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fayalite"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"bitvec",
"blake3",
"clap",
"ctor",
"eyre",
"fayalite-proc-macros",
"fayalite-visit-gen",
"hashbrown",
"jobslot",
"num-bigint",
"num-traits",
"os_pipe",
"serde",
"serde_json",
"tempfile",
"trybuild",
"which",
]
[[package]]
name = "fayalite-proc-macros"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"fayalite-proc-macros-impl",
]
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"base16ct",
"num-bigint",
@ -278,7 +345,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"indexmap",
"prettyplease",
@ -306,6 +373,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -334,7 +412,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -366,6 +444,20 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobslot"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.59.0",
]
[[package]]
name = "libc"
version = "0.2.153"
@ -413,6 +505,16 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "os_pipe"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "prettyplease"
version = "0.2.20"
@ -457,7 +559,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -466,6 +568,12 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.202"
@ -509,6 +617,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
@ -541,7 +655,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -612,6 +726,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "6.0.1"
@ -665,14 +785,24 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.4"
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -681,45 +811,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
version = "0.52.6"
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]]
name = "windows_i686_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winsafe"

View file

@ -5,7 +5,7 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.2.0"
version = "0.2.1"
license = "LGPL-3.0-or-later"
edition = "2021"
repository = "https://git.libre-chip.org/libre-chip/fayalite"
@ -14,15 +14,21 @@ categories = ["simulation", "development-tools", "compilers"]
rust-version = "1.80.1"
[workspace.dependencies]
fayalite-proc-macros = { version = "=0.2.0", path = "crates/fayalite-proc-macros" }
fayalite-proc-macros-impl = { version = "=0.2.0", path = "crates/fayalite-proc-macros-impl" }
fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" }
fayalite-proc-macros = { version = "=0.2.1", path = "crates/fayalite-proc-macros" }
fayalite-proc-macros-impl = { version = "=0.2.1", path = "crates/fayalite-proc-macros-impl" }
fayalite-visit-gen = { version = "=0.2.1", path = "crates/fayalite-visit-gen" }
base16ct = "0.2.0"
bitvec = { version = "1.0.1", features = ["serde"] }
blake3 = { version = "1.5.4", features = ["serde"] }
clap = { version = "4.5.9", features = ["derive", "env", "string"] }
ctor = "0.2.8"
eyre = "0.6.12"
hashbrown = "0.14.3"
indexmap = { version = "2.2.6", features = ["serde"] }
jobslot = "0.2.19"
num-bigint = "0.4.4"
num-traits = "0.2.16"
os_pipe = "1.2.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"
quote = "1.0.36"
@ -33,3 +39,4 @@ syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"]
tempfile = "3.10.1"
thiserror = "1.0.61"
trybuild = "1.0"
which = "6.0.1"

View file

@ -1,3 +1,7 @@
<!--
SPDX-License-Identifier: LGPL-3.0-or-later
See Notices.txt for copyright information
-->
# 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/).

View file

@ -13,11 +13,11 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
base16ct = { workspace = true }
num-bigint = { workspace = true }
prettyplease = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
sha2 = { workspace = true }
syn = { workspace = true }
tempfile = { workspace = true }
base16ct.workspace = true
num-bigint.workspace = true
prettyplease.workspace = true
proc-macro2.workspace = true
quote.workspace = true
sha2.workspace = true
syn.workspace = true
tempfile.workspace = true

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField,
@ -19,13 +21,13 @@ use syn::{
#[derive(Clone, Debug)]
pub(crate) struct ParsedBundle {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) struct_token: Token![struct],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) fields: MaybeParsed<ParsedFieldsNamed, FieldsNamed>,
pub(crate) field_flips: Vec<Option<HdlAttr<kw::flip>>>,
pub(crate) field_flips: Vec<Option<HdlAttr<kw::flip, kw::hdl>>>,
pub(crate) mask_type_ident: Ident,
pub(crate) mask_type_match_variant_ident: Ident,
pub(crate) match_variant_ident: Ident,
@ -38,7 +40,7 @@ impl ParsedBundle {
errors: &mut Errors,
field: &mut Field,
index: usize,
) -> Option<HdlAttr<kw::flip>> {
) -> Option<HdlAttr<kw::flip, kw::hdl>> {
let Field {
attrs,
vis: _,
@ -56,8 +58,7 @@ impl ParsedBundle {
}
*mutability = FieldMutability::None;
colon_token.get_or_insert(Token![:](ident.span()));
let options = errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs));
options
errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs))
}
fn parse(item: ItemStruct) -> syn::Result<Self> {
let ItemStruct {
@ -71,7 +72,9 @@ impl ParsedBundle {
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions>::parse_and_take_attr(&mut attrs))
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
@ -337,7 +340,7 @@ impl ToTokens for Builder {
}));
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
#[allow(non_camel_case_types, non_snake_case, dead_code)]
impl #impl_generics #unfilled_ty
#where_clause
{
@ -427,6 +430,7 @@ impl ToTokens for ParsedBundle {
builder_ident,
mask_type_builder_ident,
} = self;
let span = ident.span();
let ItemOptions {
outline_generated: _,
target,
@ -436,7 +440,7 @@ impl ToTokens for ParsedBundle {
} = &options.body;
let target = get_target(target, ident);
let mut item_attrs = attrs.clone();
item_attrs.push(common_derives(ident.span()));
item_attrs.push(common_derives(span));
ItemStruct {
attrs: item_attrs,
vis: vis.clone(),
@ -458,19 +462,19 @@ impl ToTokens for ParsedBundle {
.map(|ParsedField { ident, ty, .. }| {
let ident = ident.as_ref().unwrap();
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: #expr,
}
})
.collect();
parse_quote_spanned! {ident.span()=>
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let builder = Builder {
vis: vis.clone(),
@ -484,9 +488,8 @@ impl ToTokens for ParsedBundle {
let unfilled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Unfilled);
let filled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Filled);
let mut mask_type_fields = FieldsNamed::from(fields.clone());
for Field { ident, ty, .. } in &mut mask_type_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
for Field { ty, .. } in &mut mask_type_fields.named {
*ty = parse_quote_spanned! {span=>
<#ty as ::fayalite::ty::Type>::MaskType
};
}
@ -505,8 +508,8 @@ impl ToTokens for ParsedBundle {
mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Filled);
ItemStruct {
attrs: vec![
common_derives(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -519,16 +522,15 @@ impl ToTokens for ParsedBundle {
}
.to_tokens(tokens);
let mut mask_type_match_variant_fields = mask_type_fields;
for Field { ident, ty, .. } in &mut mask_type_match_variant_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
for Field { ty, .. } in &mut mask_type_match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -541,16 +543,15 @@ impl ToTokens for ParsedBundle {
}
.to_tokens(tokens);
let mut match_variant_fields = FieldsNamed::from(fields.clone());
for Field { ident, ty, .. } in &mut match_variant_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
for Field { ty, .. } in &mut match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -562,17 +563,20 @@ impl ToTokens for ParsedBundle {
semi_token: None,
}
.to_tokens(tokens);
let this_token = Ident::new("__this", span);
let fields_token = Ident::new("__fields", span);
let self_token = Token![self](span);
let match_variant_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
quote_spanned! {ident.span()=>
#ident: ::fayalite::expr::Expr::field(__this, #ident_str),
quote_spanned! {span=>
#ident: ::fayalite::expr::Expr::field(#this_token, #ident_str),
}
}));
let mask_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {ident.span()=>
#ident: ::fayalite::ty::Type::mask_type(&self.#ident),
quote_spanned! {span=>
#ident: ::fayalite::ty::Type::mask_type(&#self_token.#ident),
}
}));
let from_canonical_body_fields =
@ -580,16 +584,16 @@ impl ToTokens for ParsedBundle {
|((index, field), flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let flipped = flip.is_some();
quote_spanned! {ident.span()=>
let not_flipped = flip.is_none().then(|| Token![!](span));
quote_spanned! {span=>
#ident: {
let ::fayalite::bundle::BundleField {
name: __name,
flipped: __flipped,
ty: __ty,
} = __fields[#index];
} = #fields_token[#index];
::fayalite::__std::assert_eq!(&*__name, #ident_str);
::fayalite::__std::assert_eq!(__flipped, #flipped);
::fayalite::__std::assert!(#not_flipped __flipped);
::fayalite::ty::Type::from_canonical(__ty)
},
}
@ -600,17 +604,17 @@ impl ToTokens for ParsedBundle {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let flipped = flip.is_some();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::bundle::BundleField {
name: ::fayalite::intern::Intern::intern(#ident_str),
flipped: #flipped,
ty: ::fayalite::ty::Type::canonical(&self.#ident),
ty: ::fayalite::ty::Type::canonical(&#self_token.#ident),
},
}
},
));
let fields_len = fields.named().into_iter().len();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #mask_type_ident #type_generics
#where_clause
@ -626,7 +630,7 @@ impl ToTokens for ParsedBundle {
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
__this: ::fayalite::expr::Expr<Self>,
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #mask_type_match_variant_ident {
@ -634,19 +638,19 @@ impl ToTokens for ParsedBundle {
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
*self
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
*#self_token
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let __fields = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(__fields.len(), #fields_len, "bundle has wrong number of fields");
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
@ -661,7 +665,7 @@ impl ToTokens for ParsedBundle {
{
type Builder = #unfilled_mask_type_builder_ty;
type FilledBuilder = #filled_mask_type_builder_ty;
fn fields(&self) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
@ -676,12 +680,12 @@ impl ToTokens for ParsedBundle {
impl #impl_generics ::fayalite::ty::TypeWithDeref for #mask_type_ident #type_generics
#where_clause
{
fn expr_deref(__this: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let __this = *__this;
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #mask_type_match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::<_>::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
#[automatically_derived]
@ -699,7 +703,7 @@ impl ToTokens for ParsedBundle {
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
__this: ::fayalite::expr::Expr<Self>,
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #match_variant_ident {
@ -707,21 +711,21 @@ impl ToTokens for ParsedBundle {
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
#mask_type_ident {
#(#mask_type_body_fields)*
}
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let __fields = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(__fields.len(), #fields_len, "bundle has wrong number of fields");
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
@ -736,7 +740,7 @@ impl ToTokens for ParsedBundle {
{
type Builder = #unfilled_builder_ty;
type FilledBuilder = #filled_builder_ty;
fn fields(&self) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
@ -751,12 +755,12 @@ impl ToTokens for ParsedBundle {
impl #impl_generics ::fayalite::ty::TypeWithDeref for #target #type_generics
#where_clause
{
fn expr_deref(__this: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let __this = *__this;
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::<_>::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
}
@ -768,7 +772,7 @@ impl ToTokens for ParsedBundle {
let static_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::TYPE,
}
}));
@ -776,28 +780,26 @@ impl ToTokens for ParsedBundle {
Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::MASK_TYPE,
}
}));
let type_properties = format_ident!("__type_properties", span = ident.span());
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES);
}
}));
let type_properties_mask_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::MASK_TYPE_PROPERTIES);
}
}));
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType for #mask_type_ident #static_type_generics
#static_where_clause

View file

@ -1,9 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics,
ParsedType, SplitForImpl, TypesParser, WrappedInConst,
},
Errors, HdlAttr, PairsIterExt,
kw, Errors, HdlAttr, PairsIterExt,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote_spanned, ToTokens};
@ -29,7 +31,7 @@ crate::options! {
pub(crate) struct ParsedVariantField {
pub(crate) paren_token: Paren,
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<FieldOptions>,
pub(crate) options: HdlAttr<FieldOptions, kw::hdl>,
pub(crate) ty: MaybeParsed<ParsedType, Type>,
pub(crate) comma_token: Option<Token![,]>,
}
@ -37,7 +39,7 @@ pub(crate) struct ParsedVariantField {
#[derive(Clone, Debug)]
pub(crate) struct ParsedVariant {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<VariantOptions>,
pub(crate) options: HdlAttr<VariantOptions, kw::hdl>,
pub(crate) ident: Ident,
pub(crate) field: Option<ParsedVariantField>,
}
@ -119,7 +121,7 @@ impl ParsedVariant {
#[derive(Clone, Debug)]
pub(crate) struct ParsedEnum {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) enum_token: Token![enum],
pub(crate) ident: Ident,
@ -142,7 +144,9 @@ impl ParsedEnum {
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions>::parse_and_take_attr(&mut attrs))
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
@ -200,6 +204,7 @@ impl ToTokens for ParsedEnum {
variants,
match_variant_ident,
} = self;
let span = ident.span();
let ItemOptions {
outline_generated: _,
target,
@ -209,8 +214,8 @@ impl ToTokens for ParsedEnum {
} = &options.body;
let target = get_target(target, ident);
let mut struct_attrs = attrs.clone();
struct_attrs.push(common_derives(ident.span()));
struct_attrs.push(parse_quote_spanned! {ident.span()=>
struct_attrs.push(common_derives(span));
struct_attrs.push(parse_quote_spanned! {span=>
#[allow(non_snake_case)]
});
let struct_fields = Punctuated::from_iter(variants.pairs().map_pair_value_ref(
@ -234,8 +239,8 @@ impl ToTokens for ParsedEnum {
colon_token = Token![:](paren_token.span.open());
ty.clone().into()
} else {
colon_token = Token![:](ident.span());
parse_quote_spanned! {ident.span()=>
colon_token = Token![:](span);
parse_quote_spanned! {span=>
()
}
};
@ -278,30 +283,30 @@ impl ToTokens for ParsedEnum {
}) = field
{
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: #expr,
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: (),
}
}
})
.collect();
parse_quote_spanned! {ident.span()=>
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
{
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {ident.span()=>
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code)]
});
ItemEnum {
@ -350,7 +355,7 @@ impl ToTokens for ParsedEnum {
.to_tokens(tokens);
}
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {ident.span()=>
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code, non_camel_case_types)]
});
ItemEnum {
@ -385,7 +390,7 @@ impl ToTokens for ParsedEnum {
mutability: FieldMutability::None,
ident: None,
colon_token: None,
ty: parse_quote_spanned! {ident.span()=>
ty: parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
},
},
@ -399,21 +404,22 @@ impl ToTokens for ParsedEnum {
)),
}
.to_tokens(tokens);
let self_token = Token![self](span);
for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() {
if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident<__V: ::fayalite::expr::ToExpr<Type = #ty>>(
self,
#self_token,
v: __V,
) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
self,
#self_token,
#index,
::fayalite::__std::option::Option::Some(
::fayalite::expr::Expr::canonical(
@ -426,16 +432,16 @@ impl ToTokens for ParsedEnum {
}
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident(self) -> ::fayalite::expr::Expr<Self> {
#vis fn #ident(#self_token) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
self,
#self_token,
#index,
::fayalite::__std::option::Option::None,
),
@ -446,46 +452,48 @@ impl ToTokens for ParsedEnum {
}
.to_tokens(tokens);
}
let variants_token = Ident::new("variants", span);
let from_canonical_body_fields = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
let ident_str = ident.to_string();
let val = if field.is_some() {
let missing_value_msg = format!("expected variant {ident} to have a field");
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::ty::Type::from_canonical(ty.expect(#missing_value_msg))
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::assert!(ty.is_none());
}
};
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: {
let ::fayalite::enum_::EnumVariant {
name,
ty,
} = variants[#index];
} = #variants_token[#index];
::fayalite::__std::assert_eq!(&*name, #ident_str);
#val
},
}
},
));
let variant_access_token = Ident::new("variant_access", span);
let match_active_scope_match_arms = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
if field.is_some() {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#index => #match_variant_ident::#ident(
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::VariantAccess::new_by_index(
variant_access.base(),
variant_access.variant_index(),
#variant_access_token.base(),
#variant_access_token.variant_index(),
),
),
),
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#index => #match_variant_ident::#ident,
}
}
@ -503,16 +511,16 @@ impl ToTokens for ParsedEnum {
match field {
Some(ParsedVariantField { options, .. }) => {
let FieldOptions {} = options.body;
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::Some(
::fayalite::ty::Type::canonical(&self.#ident),
::fayalite::ty::Type::canonical(&#self_token.#ident),
),
},
}
}
None => quote_spanned! {ident.span()=>
None => quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::None,
@ -522,7 +530,7 @@ impl ToTokens for ParsedEnum {
},
));
let variants_len = variants.len();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #target #type_generics
#where_clause
@ -540,11 +548,11 @@ impl ToTokens for ParsedEnum {
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
::fayalite::module::enum_match_variants_helper(this, source_location)
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
::fayalite::int::Bool
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(#self_token)))
}
#[track_caller]
#[allow(non_snake_case)]
@ -552,8 +560,8 @@ impl ToTokens for ParsedEnum {
let ::fayalite::ty::CanonicalType::Enum(enum_) = canonical_type else {
::fayalite::__std::panic!("expected enum");
};
let variants = ::fayalite::enum_::EnumType::variants(&enum_);
::fayalite::__std::assert_eq!(variants.len(), #variants_len, "enum has wrong number of variants");
let #variants_token = ::fayalite::enum_::EnumType::variants(&enum_);
::fayalite::__std::assert_eq!(#variants_token.len(), #variants_len, "enum has wrong number of variants");
Self {
#(#from_canonical_body_fields)*
}
@ -569,16 +577,16 @@ impl ToTokens for ParsedEnum {
fn match_activate_scope(
v: <Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
) -> (<Self as ::fayalite::ty::Type>::MatchVariant, <Self as ::fayalite::ty::Type>::MatchActiveScope) {
let (variant_access, scope) = v.activate();
let (#variant_access_token, scope) = v.activate();
(
match variant_access.variant_index() {
match #variant_access_token.variant_index() {
#(#match_active_scope_match_arms)*
#variants_len.. => ::fayalite::__std::panic!("invalid variant index"),
},
scope,
)
}
fn variants(&self) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> {
fn variants(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> {
::fayalite::intern::Intern::intern(&[
#(#variants_body_variants)*
][..])
@ -592,35 +600,35 @@ impl ToTokens for ParsedEnum {
static_generics.split_for_impl();
let static_type_body_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| {
if let Some(_) = field {
quote_spanned! {ident.span()=>
if field.is_some() {
quote_spanned! {span=>
#ident: ::fayalite::ty::StaticType::TYPE,
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: (),
}
}
}));
let type_properties = format_ident!("__type_properties", span = ident.span());
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| {
Vec::from_iter(variants.iter().map(|ParsedVariant { field, .. }| {
let variant = if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::option::Option::Some(
<#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES,
)
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::option::Option::None
}
};
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.variant(#variant);
}
}));
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType
for #target #static_type_generics

View file

@ -0,0 +1,133 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType,
TypesParser,
},
kw, Errors, HdlAttr,
};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{parse_quote_spanned, Attribute, Generics, Ident, ItemType, Token, Type, Visibility};
#[derive(Clone, Debug)]
pub(crate) struct ParsedTypeAlias {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) type_token: Token![type],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) eq_token: Token![=],
pub(crate) ty: MaybeParsed<ParsedType, Type>,
pub(crate) semi_token: Token![;],
}
impl ParsedTypeAlias {
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: _,
} = options.body;
if let Some((no_static,)) = no_static {
errors.error(no_static, "no_static 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 {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
})
}
}
impl ToTokens for ParsedTypeAlias {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
} = self;
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static: _,
no_runtime_generics,
} = &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)
})
}
}
}
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
let item = ParsedTypeAlias::parse(item)?;
let outline_generated = item.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)
}

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{fold::impl_fold, kw, Errors, HdlAttr, PairsIterExt};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote_spanned, ToTokens};
@ -64,6 +66,7 @@ impl Drop for WrappedInConst<'_> {
fn drop(&mut self) {
let inner = &self.inner;
quote_spanned! {self.span=>
#[allow(clippy::type_complexity)]
const _: () = {
#inner
};
@ -1270,6 +1273,130 @@ make_parsed_type_or_const! {
}
}
#[derive(Debug, Clone)]
pub(crate) struct ParsedTypePhantomData {
pub(crate) phantom_data: known_items::PhantomData,
pub(crate) lt_token: Token![<],
pub(crate) ty: Box<ParsedType>,
pub(crate) gt_token: Token![>],
}
impl_fold! {
struct ParsedTypePhantomData<> {
phantom_data: known_items::PhantomData,
lt_token: Token![<],
ty: Box<ParsedType>,
gt_token: Token![>],
}
}
impl ParsedTypePhantomData {
pub(crate) fn try_from_named(
named: ParsedTypeNamed,
parser: &mut TypesParser<'_>,
) -> Result<Result<Self, ParsedTypeNamed>, ParseFailed> {
let ParsedTypeNamed { path, args } = named;
let parsed_path = known_items::PhantomData::parse_path(path);
let phantom_data = match parsed_path {
Ok(phantom_data) => phantom_data,
Err(path) => return Ok(Err(ParsedTypeNamed { path, args })),
};
let Some(ParsedGenericArguments {
colon2_token: _,
lt_token,
args,
gt_token,
}) = args
else {
parser
.errors()
.error(phantom_data, "PhantomData requires generic arguments");
return Err(ParseFailed);
};
let args_len = args.len();
if args_len != 1 {
parser.errors().error(
phantom_data,
format_args!(
"wrong number of generic arguments supplied: got {args_len}, expected 1"
),
);
return Err(ParseFailed);
}
let ty = args.into_iter().next().unwrap();
let ParsedGenericArgument::Type(ty) = ty else {
parser.errors().error(ty, "expected a type");
return Err(ParseFailed);
};
Ok(Ok(Self {
phantom_data,
lt_token,
ty: Box::new(ty),
gt_token,
}))
}
}
impl From<ParsedTypePhantomData> for Type {
fn from(value: ParsedTypePhantomData) -> Type {
let ParsedTypePhantomData {
phantom_data,
lt_token,
ty,
gt_token,
} = value;
let path = phantom_data.path;
let mut args = Punctuated::new();
args.push(GenericArgument::Type(ty.into()));
let args = AngleBracketedGenericArguments {
colon2_token: Some(Token![::](lt_token.span)),
lt_token,
args,
gt_token,
};
let mut segments = path.segments;
segments.last_mut().unwrap().arguments = PathArguments::AngleBracketed(args);
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: path.leading_colon,
segments,
},
})
}
}
impl MakeHdlTypeExpr for ParsedTypePhantomData {
fn make_hdl_type_expr(&self, _context: &MakeHdlTypeExprContext) -> Expr {
let ParsedTypePhantomData {
phantom_data,
lt_token: _,
ty: _,
gt_token: _,
} = self;
Expr::Path(ExprPath {
attrs: vec![],
qself: None,
path: phantom_data.path.clone(),
})
}
}
impl ToTokens for ParsedTypePhantomData {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
phantom_data,
lt_token,
ty,
gt_token,
} = self;
phantom_data.to_tokens(tokens);
lt_token.to_tokens(tokens);
ty.to_tokens(tokens);
gt_token.to_tokens(tokens);
}
}
#[derive(Debug, Clone)]
pub(crate) enum ParsedType {
Delimited(ParsedTypeDelimited),
@ -1277,6 +1404,7 @@ pub(crate) enum ParsedType {
NamedParam(ParsedTypeNamedParam),
Tuple(ParsedTypeTuple),
ConstUsize(ParsedTypeConstUsize),
PhantomData(ParsedTypePhantomData),
Array(ParsedTypeArray),
UInt(ParsedTypeUInt),
SInt(ParsedTypeSInt),
@ -1291,6 +1419,7 @@ impl_fold! {
NamedParam(ParsedTypeNamedParam),
Tuple(ParsedTypeTuple),
ConstUsize(ParsedTypeConstUsize),
PhantomData(ParsedTypePhantomData),
Array(ParsedTypeArray),
UInt(ParsedTypeUInt),
SInt(ParsedTypeSInt),
@ -1307,6 +1436,7 @@ impl From<ParsedType> for Type {
ParsedType::NamedParam(v) => v.into(),
ParsedType::Tuple(v) => v.into(),
ParsedType::ConstUsize(v) => v.into(),
ParsedType::PhantomData(v) => v.into(),
ParsedType::Array(v) => v.into(),
ParsedType::UInt(v) => v.into(),
ParsedType::SInt(v) => v.into(),
@ -1357,6 +1487,7 @@ impl ToTokens for ParsedType {
ParsedType::Named(ty) => ty.to_tokens(tokens),
ParsedType::Tuple(ty) => ty.to_tokens(tokens),
ParsedType::ConstUsize(ty) => ty.to_tokens(tokens),
ParsedType::PhantomData(ty) => ty.to_tokens(tokens),
ParsedType::Array(ty) => ty.to_tokens(tokens),
ParsedType::UInt(ty) => ty.to_tokens(tokens),
ParsedType::SInt(ty) => ty.to_tokens(tokens),
@ -1408,7 +1539,7 @@ impl ParseTypes<Path> for ParsedType {
let mut args = None;
let segments = Punctuated::from_iter(segments.pairs_mut().map_pair_value_mut(|segment| {
let PathSegment { ident, arguments } = segment;
if let Some(_) = args {
if args.is_some() {
parser
.errors()
.error(&ident, "associated types/consts are not yet implemented");
@ -1464,6 +1595,10 @@ impl ParseTypes<Path> for ParsedType {
Ok(v) => return Ok(Self::ConstUsize(v)),
Err(named) => named,
};
let named = match ParsedTypePhantomData::try_from_named(named, parser)? {
Ok(v) => return Ok(Self::PhantomData(v)),
Err(named) => named,
};
let named = match ParsedTypeUInt::try_from_named(named, parser)? {
Ok(v) => return Ok(Self::UInt(v)),
Err(named) => named,
@ -1592,7 +1727,7 @@ impl ParseTypes<Path> for ParsedConstGenericType {
let mut args = None;
let segments = Punctuated::from_iter(segments.pairs_mut().map_pair_value_mut(|segment| {
let PathSegment { ident, arguments } = segment;
if let Some(_) = args {
if args.is_some() {
parser
.errors()
.error(&ident, "associated types/consts are not yet implemented");
@ -1745,7 +1880,7 @@ impl<T: ParseTypes<I>, I, P: Clone> ParseTypes<Punctuated<I, P>> for Punctuated<
pub(crate) enum UnparsedGenericParam {
Type {
attrs: Vec<Attribute>,
options: HdlAttr<TypeParamOptions>,
options: HdlAttr<TypeParamOptions, kw::hdl>,
ident: Ident,
colon_token: Token![:],
bounds: ParsedBounds,
@ -1753,7 +1888,7 @@ pub(crate) enum UnparsedGenericParam {
},
Const {
attrs: Vec<Attribute>,
options: HdlAttr<ConstParamOptions>,
options: HdlAttr<ConstParamOptions, kw::hdl>,
const_token: Token![const],
ident: Ident,
colon_token: Token![:],
@ -1781,7 +1916,7 @@ pub(crate) mod known_items {
#[allow(non_snake_case, dead_code)]
pub(crate) fn $known_item(span: Span) -> $known_item {
let segments = $known_item::PATH_SEGMENTS.iter()
let segments = $known_item::PATH_SEGMENTS[0].iter()
.copied()
.map(|seg| PathSegment::from(Ident::new(seg, span)))
.collect();
@ -1807,21 +1942,22 @@ pub(crate) mod known_items {
return Ok(Self { span: ident.span(), path });
}
}
if path.segments.len() == Self::PATH_SEGMENTS.len()
for &path_segments in Self::PATH_SEGMENTS.iter() {
if path.segments.len() == path_segments.len()
&& path
.segments
.iter()
.zip(Self::PATH_SEGMENTS)
.zip(path_segments)
.all(|(seg, expected)| {
matches!(seg.arguments, PathArguments::None)
&& seg.ident == *expected
})
{
Ok(Self { span: path.segments.last().unwrap().ident.span(), path })
} else {
Err(path)
return Ok(Self { span: path.segments.last().unwrap().ident.span(), path });
}
}
Err(path)
}
#[allow(dead_code)]
pub(crate) fn parse_path_with_arguments(mut path: Path) -> Result<(Self, PathArguments), Path> {
let Some(last_segment) = path.segments.last_mut() else {
@ -1875,25 +2011,31 @@ pub(crate) mod known_items {
}
macro_rules! impl_known_item {
($([$(::$head:ident)*])? ::$next:ident $(::$tail:ident)+) => {
impl_known_item!([$($(::$head)*)? ::$next] $(::$tail)+);
};
([$(::$seg:ident)+] ::$known_item:ident) => {
($(#[alias = $(::$alias:ident)+])* [$(::$seg:ident)+] ::$known_item:ident) => {
impl_known_item_body!($known_item);
impl $known_item {
pub(crate) const PATH_SEGMENTS: &'static [&'static str] = &[
pub(crate) const PATH_SEGMENTS: &'static [&'static [&'static str]] = &[
&[
$(stringify!($seg),)+
stringify!($known_item),
],
$(&[
$(stringify!($alias),)+
],)*
];
}
};
($(#[alias = $(::$alias:ident)+])* $([$(::$head:ident)*])? ::$next:ident $(::$tail:ident)+) => {
impl_known_item!($(#[alias = $(::$alias)+])* [$($(::$head)*)? ::$next] $(::$tail)+);
};
}
impl_known_item!(::fayalite::array::Array);
impl_known_item!(::fayalite::array::ArrayType);
impl_known_item!(::fayalite::bundle::BundleType);
impl_known_item!(::fayalite::enum_::EnumType);
impl_known_item!(::fayalite::int::BoolOrIntType);
impl_known_item!(::fayalite::int::DynSize);
impl_known_item!(::fayalite::int::IntType);
impl_known_item!(::fayalite::int::KnownSize);
@ -1907,7 +2049,16 @@ pub(crate) mod known_items {
impl_known_item!(::fayalite::ty::Type);
impl_known_item!(::fayalite::ty::Type::MaskType);
impl_known_item!(::fayalite::util::ConstUsize);
impl_known_item!(::fayalite::__std::primitive::usize);
impl_known_item!(
#[alias = ::std::primitive::usize]
#[alias = ::core::primitive::usize]
::fayalite::__std::primitive::usize
);
impl_known_item!(
#[alias = ::std::marker::PhantomData]
#[alias = ::core::marker::PhantomData]
::fayalite::__std::marker::PhantomData
);
}
macro_rules! impl_bounds {
@ -2083,6 +2234,7 @@ macro_rules! impl_bounds {
impl_bounds! {
#[struct = ParsedBounds]
pub(crate) enum ParsedBound {
BoolOrIntType,
BundleType,
EnumType,
IntType,
@ -2096,6 +2248,7 @@ impl_bounds! {
impl_bounds! {
#[struct = ParsedTypeBounds]
pub(crate) enum ParsedTypeBound {
BoolOrIntType,
BundleType,
EnumType,
IntType,
@ -2107,6 +2260,7 @@ impl_bounds! {
impl From<ParsedTypeBound> for ParsedBound {
fn from(value: ParsedTypeBound) -> Self {
match value {
ParsedTypeBound::BoolOrIntType(v) => ParsedBound::BoolOrIntType(v),
ParsedTypeBound::BundleType(v) => ParsedBound::BundleType(v),
ParsedTypeBound::EnumType(v) => ParsedBound::EnumType(v),
ParsedTypeBound::IntType(v) => ParsedBound::IntType(v),
@ -2119,6 +2273,7 @@ impl From<ParsedTypeBound> for ParsedBound {
impl From<ParsedTypeBounds> for ParsedBounds {
fn from(value: ParsedTypeBounds) -> Self {
let ParsedTypeBounds {
BoolOrIntType,
BundleType,
EnumType,
IntType,
@ -2126,6 +2281,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
Type,
} = value;
Self {
BoolOrIntType,
BundleType,
EnumType,
IntType,
@ -2141,6 +2297,10 @@ impl ParsedTypeBound {
fn implied_bounds(self) -> ParsedTypeBounds {
let span = self.span();
match self {
Self::BoolOrIntType(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::BundleType(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::Type(known_items::Type(span)),
@ -2151,6 +2311,7 @@ impl ParsedTypeBound {
]),
Self::IntType(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::BoolOrIntType(known_items::BoolOrIntType(span)),
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::StaticType(v) => ParsedTypeBounds::from_iter([
@ -2183,6 +2344,7 @@ impl From<ParsedSizeTypeBounds> for ParsedBounds {
fn from(value: ParsedSizeTypeBounds) -> Self {
let ParsedSizeTypeBounds { KnownSize, Size } = value;
Self {
BoolOrIntType: None,
BundleType: None,
EnumType: None,
IntType: None,
@ -2258,6 +2420,7 @@ pub(crate) enum ParsedBoundCategory {
impl ParsedBound {
fn categorize(self) -> ParsedBoundCategory {
match self {
Self::BoolOrIntType(v) => ParsedBoundCategory::Type(ParsedTypeBound::BoolOrIntType(v)),
Self::BundleType(v) => ParsedBoundCategory::Type(ParsedTypeBound::BundleType(v)),
Self::EnumType(v) => ParsedBoundCategory::Type(ParsedTypeBound::EnumType(v)),
Self::IntType(v) => ParsedBoundCategory::Type(ParsedTypeBound::IntType(v)),
@ -2278,7 +2441,7 @@ impl ParsedBound {
#[derive(Debug, Clone)]
pub(crate) struct ParsedTypeParam {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<TypeParamOptions>,
pub(crate) options: HdlAttr<TypeParamOptions, kw::hdl>,
pub(crate) ident: Ident,
pub(crate) colon_token: Token![:],
pub(crate) bounds: ParsedTypeBounds,
@ -2312,7 +2475,7 @@ impl ToTokens for ParsedTypeParam {
#[derive(Debug, Clone)]
pub(crate) struct ParsedSizeTypeParam {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<TypeParamOptions>,
pub(crate) options: HdlAttr<TypeParamOptions, kw::hdl>,
pub(crate) ident: Ident,
pub(crate) colon_token: Token![:],
pub(crate) bounds: ParsedSizeTypeBounds,
@ -2356,7 +2519,7 @@ pub(crate) struct ParsedConstParamWhereBounds {
#[derive(Debug, Clone)]
pub(crate) struct ParsedConstParam {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ConstParamOptions>,
pub(crate) options: HdlAttr<ConstParamOptions, kw::hdl>,
pub(crate) const_token: Token![const],
pub(crate) ident: Ident,
pub(crate) colon_token: Token![:],
@ -2413,7 +2576,7 @@ impl ParsedGenericParam {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub(crate) struct ParsedGenerics {
pub(crate) lt_token: Option<Token![<]>,
pub(crate) params: Punctuated<ParsedGenericParam, Token![,]>,
@ -2573,6 +2736,7 @@ impl ParsedGenerics {
}
})
.collect();
let param_token = Ident::new("__param", ident.span());
for (param_count, (generics_accumulation_type, next_param)) in generics_accumulation_types
.iter()
.zip(&self.params)
@ -2621,7 +2785,7 @@ impl ParsedGenerics {
next_generics.split_for_impl();
let next_turbofish = next_type_generics.as_turbofish();
let mut param: Expr = parse_quote_spanned! {ident.span()=>
__param
#param_token
};
let mut generics = next_generics.clone();
let mut index_type = param_ident.clone();
@ -2636,7 +2800,7 @@ impl ParsedGenerics {
is_const: false,
});
param = parse_quote_spanned! {ident.span()=>
::fayalite::ty::TypeOrDefault::get(__param, || #default_expr)
::fayalite::ty::TypeOrDefault::get(#param_token, || #default_expr)
};
let context = MakeHdlTypeExprContext {
named_param_values: self_members[..param_count]
@ -2701,8 +2865,8 @@ impl ParsedGenerics {
{
type Output = #next_target #next_type_generics;
fn index(&self, __param: #index_type) -> &Self::Output {
::fayalite::intern::Interned::<_>::into_inner(
fn index(&self, #param_token: #index_type) -> &Self::Output {
::fayalite::intern::Interned::into_inner(
::fayalite::intern::Intern::intern_sized(#output_expr),
)
}
@ -2722,7 +2886,7 @@ impl ParsedGenerics {
.iter()
.cloned()
.chain([parse_quote_spanned! {ident.span()=>
__param
#param_token
}])
.collect(),
is_const: false,
@ -2761,8 +2925,8 @@ impl ParsedGenerics {
{
type Output = #next_target #next_target_args;
fn index(&self, __param: #param_ident) -> &Self::Output {
::fayalite::intern::Interned::<_>::into_inner(
fn index(&self, #param_token: #param_ident) -> &Self::Output {
::fayalite::intern::Interned::into_inner(
::fayalite::intern::Intern::intern_sized(#output_expr),
)
}
@ -2789,7 +2953,7 @@ impl ParsedGenerics {
.iter()
.cloned()
.chain([parse_quote_spanned! {ident.span()=>
__param
#param_token
}])
.collect(),
is_const: false,
@ -2831,8 +2995,8 @@ impl ParsedGenerics {
{
type Output = #next_target #next_target_args;
fn index(&self, __param: __Param) -> &Self::Output {
::fayalite::intern::Interned::<_>::into_inner(
fn index(&self, #param_token: __Param) -> &Self::Output {
::fayalite::intern::Interned::into_inner(
::fayalite::intern::Intern::intern_sized(#output_expr),
)
}
@ -2863,9 +3027,11 @@ impl ParsedGenerics {
let (input_param, punct) = input_param.into_tuple();
let (unparsed_param, late_parsed_param) = match input_param {
GenericParam::Lifetime(param) => {
errors.unwrap_or_default(HdlAttr::<LifetimeParamOptions>::parse_and_take_attr(
errors.unwrap_or_default(
HdlAttr::<LifetimeParamOptions, kw::hdl>::parse_and_take_attr(
&mut param.attrs,
));
),
);
errors.error(param, "lifetime generics are not supported by #[hdl]");
continue;
}
@ -2879,7 +3045,9 @@ impl ParsedGenerics {
}) => {
let span = ident.span();
let options = errors
.unwrap_or_default(HdlAttr::<TypeParamOptions>::parse_and_take_attr(attrs))
.unwrap_or_default(
HdlAttr::<TypeParamOptions, kw::hdl>::parse_and_take_attr(attrs),
)
.unwrap_or_default();
let colon_token = colon_token.unwrap_or_else(|| Token![:](span));
if !bounds.is_empty() {
@ -2917,7 +3085,9 @@ impl ParsedGenerics {
default,
}) => {
let options = errors
.unwrap_or_default(HdlAttr::<ConstParamOptions>::parse_and_take_attr(attrs))
.unwrap_or_default(
HdlAttr::<ConstParamOptions, kw::hdl>::parse_and_take_attr(attrs),
)
.unwrap_or_default();
if let Some(default) = default {
let _ = eq_token;
@ -3137,16 +3307,21 @@ impl ParsedGenerics {
.Type
.get_or_insert_with(|| known_items::Type(bound.span()));
match bound {
ParsedTypeBound::BundleType(_)
ParsedTypeBound::BoolOrIntType(_)
| ParsedTypeBound::BundleType(_)
| ParsedTypeBound::EnumType(_)
| ParsedTypeBound::IntType(_) => {
errors.error(bound, "bound on mask type not implemented");
}
ParsedTypeBound::StaticType(bound) => {
if bounds.StaticType.is_none() {
errors.error(bound, "StaticType bound on mask type without corresponding StaticType bound on original type is not implemented");
errors.error(
bound,
"StaticType bound on mask type without corresponding \
StaticType bound on original type is not implemented",
);
}
}
},
ParsedTypeBound::Type(_) => {}
}
}
@ -3527,7 +3702,7 @@ impl SplitForImpl for Generics {
Self::TypeGenerics<'_>,
Self::WhereClause<'_>,
) {
Generics::split_for_impl(&self)
Generics::split_for_impl(self)
}
}
@ -3941,6 +4116,7 @@ impl MakeHdlTypeExpr for ParsedType {
Self::NamedParam(v) => v.make_hdl_type_expr(context),
Self::Tuple(v) => v.make_hdl_type_expr(context),
Self::ConstUsize(v) => v.make_hdl_type_expr(context),
Self::PhantomData(v) => v.make_hdl_type_expr(context),
Self::Array(v) => v.make_hdl_type_expr(context),
Self::UInt(v) => v.make_hdl_type_expr(context),
Self::SInt(v) => v.make_hdl_type_expr(context),
@ -3987,7 +4163,13 @@ impl MakeHdlTypeExpr for ParsedExpr {
match self {
ParsedExpr::Delimited(expr) => expr.make_hdl_type_expr(context),
ParsedExpr::NamedParamConst(expr) => expr.make_hdl_type_expr(context),
ParsedExpr::Other(expr) => (**expr).clone(),
ParsedExpr::Other(expr) => {
let span = expr.span();
let const_usize = known_items::ConstUsize(span);
parse_quote_spanned! {expr.span()=>
#const_usize::<{ #expr }>
}
}
}
}
}

View file

@ -9,16 +9,30 @@ use syn::{
parse::{Parse, ParseStream, Parser},
parse_quote,
punctuated::Pair,
AttrStyle, Attribute, Error, Item, Token,
spanned::Spanned,
AttrStyle, Attribute, Error, Item, ItemFn, Token,
};
mod fold;
mod hdl_bundle;
mod hdl_enum;
mod hdl_type_alias;
mod hdl_type_common;
mod module;
//mod value_derive_common;
//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 {
pub(crate) use syn::token::Extern as extern_;
@ -38,6 +52,10 @@ mod kw {
}
crate::fold::no_op_fold!($kw);
impl crate::CustomToken for $kw {
const IDENT_STR: &'static str = stringify!($kw);
}
};
}
@ -46,7 +64,9 @@ mod kw {
custom_keyword!(custom_bounds);
custom_keyword!(flip);
custom_keyword!(hdl);
custom_keyword!(hdl_module);
custom_keyword!(input);
custom_keyword!(incomplete_wire);
custom_keyword!(instance);
custom_keyword!(m);
custom_keyword!(memory);
@ -59,7 +79,6 @@ mod kw {
custom_keyword!(output);
custom_keyword!(reg_builder);
custom_keyword!(reset);
custom_keyword!(reset_default);
custom_keyword!(skip);
custom_keyword!(target);
custom_keyword!(wire);
@ -68,34 +87,34 @@ mod kw {
type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676
#[derive(Clone, Debug)]
pub(crate) struct HdlAttr<T> {
pub(crate) struct HdlAttr<T, KW> {
pub(crate) pound_token: Pound,
pub(crate) style: AttrStyle,
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) body: T,
}
crate::fold::impl_fold! {
struct HdlAttr<T,> {
struct HdlAttr<T, KW,> {
pound_token: Pound,
style: AttrStyle,
bracket_token: syn::token::Bracket,
hdl: kw::hdl,
kw: KW,
paren_token: Option<syn::token::Paren>,
body: T,
}
}
#[allow(dead_code)]
impl<T> HdlAttr<T> {
pub(crate) fn split_body(self) -> (HdlAttr<()>, T) {
impl<T, KW> HdlAttr<T, KW> {
pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -104,19 +123,19 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
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 {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: _,
} = self;
@ -124,17 +143,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
}
}
pub(crate) fn as_ref(&self) -> HdlAttr<&T> {
pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW>
where
KW: Clone,
{
let Self {
pound_token,
style,
bracket_token,
hdl,
ref kw,
paren_token,
ref body,
} = *self;
@ -142,17 +164,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw: kw.clone(),
paren_token,
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 {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -160,17 +185,17 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
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 {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -178,7 +203,7 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: f(body),
}
@ -186,31 +211,32 @@ impl<T> HdlAttr<T> {
fn to_attr(&self) -> Attribute
where
T: ToTokens,
KW: ToTokens,
{
parse_quote! { #self }
}
}
impl<T: Default> Default for HdlAttr<T> {
impl<T: Default, KW: Default> Default for HdlAttr<T, KW> {
fn default() -> Self {
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 {
HdlAttr {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
hdl: Default::default(),
kw: Default::default(),
paren_token: Default::default(),
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) {
self.pound_token.to_tokens(tokens);
match self.style {
@ -218,7 +244,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
AttrStyle::Outer => {}
};
self.bracket_token.surround(tokens, |tokens| {
self.hdl.to_tokens(tokens);
self.kw.to_tokens(tokens);
match self.paren_token {
Some(paren_token) => {
paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens))
@ -226,7 +252,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
None => {
let body = self.body.to_token_stream();
if !body.is_empty() {
syn::token::Paren(self.hdl.span)
syn::token::Paren(self.kw.span())
.surround(tokens, |tokens| tokens.extend([body]));
}
}
@ -235,18 +261,24 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
}
}
fn is_hdl_attr(attr: &Attribute) -> bool {
attr.path().is_ident("hdl")
fn is_hdl_attr<KW: CustomToken>(attr: &Attribute) -> bool {
attr.path().is_ident(KW::IDENT_STR)
}
impl<T: Parse> HdlAttr<T> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>> {
impl<T: Parse, KW: Parse> HdlAttr<T, KW> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>>
where
KW: ToTokens,
{
let mut retval = None;
let mut errors = Errors::new();
attrs.retain(|attr| {
if is_hdl_attr(attr) {
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
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)));
false
@ -257,13 +289,19 @@ impl<T: Parse> HdlAttr<T> {
errors.finish()?;
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 errors = Errors::new();
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() {
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)));
}
@ -284,7 +322,7 @@ impl<T: Parse> HdlAttr<T> {
) -> syn::Result<Self> {
let bracket_content;
let bracket_token = bracketed!(bracket_content in input);
let hdl = bracket_content.parse()?;
let kw = bracket_content.parse()?;
let paren_content;
let body;
let paren_token;
@ -305,7 +343,7 @@ impl<T: Parse> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
})
@ -813,6 +851,7 @@ macro_rules! options {
};
}
use crate::hdl_type_alias::hdl_type_alias_impl;
pub(crate) use options;
pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream {
@ -852,25 +891,33 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
}
}
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let options = syn::parse2::<module::ConfigOptions>(attr)?;
let options = HdlAttr::from(options);
let func = syn::parse2::<module::ModuleFn>(quote! { #options #item })?;
fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
let func = module::ModuleFn::parse_from_fn(item)?;
let options = func.config_options();
let mut contents = func.generate();
if options.body.outline_generated.is_some() {
if options.outline_generated.is_some() {
contents = outline_generated(contents, "module-");
}
Ok(contents)
}
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let kw = kw::hdl_module::default();
hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?)
}
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let item = syn::parse2::<Item>(quote! { #[hdl(#attr)] #item })?;
let kw = kw::hdl::default();
let item = quote! { #[#kw(#attr)] #item };
let item = syn::parse2::<Item>(item)?;
match item {
Item::Enum(item) => hdl_enum::hdl_enum(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(
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",
)),
}
}

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{ParsedGenerics, SplitForImpl},
kw,
module::transform_body::{HdlLet, HdlLetKindIO},
options, Errors, HdlAttr, PairsIterExt,
};
@ -9,7 +10,6 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::HashSet;
use syn::{
parse::{Parse, ParseStream},
parse_quote,
visit::{visit_pat, Visit},
Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
@ -59,9 +59,9 @@ impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> {
pub(crate) type ModuleIO = HdlLet<HdlLetKindIO>;
pub(crate) struct ModuleFn {
struct ModuleFnModule {
attrs: Vec<Attribute>,
config_options: HdlAttr<ConfigOptions>,
config_options: HdlAttr<ConfigOptions, kw::hdl_module>,
module_kind: ModuleKind,
vis: Visibility,
sig: Signature,
@ -70,6 +70,26 @@ pub(crate) struct ModuleFn {
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)]
pub(crate) enum ModuleKind {
Extern,
@ -89,14 +109,25 @@ impl Visit<'_> for ContainsSkippedIdent<'_> {
}
}
impl Parse for ModuleFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
impl ModuleFn {
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 {
mut attrs,
vis,
mut sig,
block,
} = input.parse()?;
} = item;
let Signature {
ref constness,
ref asyncness,
@ -111,17 +142,33 @@ impl Parse for ModuleFn {
ref output,
} = sig;
let mut errors = Errors::new();
let config_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let Some(mut config_options) =
errors.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 {
outline_generated: _,
extern_,
} = config_options.body;
let module_kind = match extern_ {
Some(_) => ModuleKind::Extern,
None => ModuleKind::Normal,
let module_kind = match (config_options.kw, extern_) {
(HdlOrHdlModule::Hdl(_), None) => None,
(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 {
match fn_arg {
FnArg::Receiver(_) => {
@ -149,20 +196,24 @@ impl Parse for ModuleFn {
if let Some(abi) = abi {
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
}
}
let mut skipped_idents = HashSet::new();
let struct_generic_params = generics
.params
.pairs_mut()
.filter_map_pair_value_mut(|v| match v {
GenericParam::Lifetime(LifetimeParam { attrs, .. }) => {
errors
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs));
errors.unwrap_or_default(
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
);
None
}
GenericParam::Type(TypeParam { attrs, ident, .. })
| GenericParam::Const(ConstParam { attrs, ident, .. }) => {
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()
{
skipped_idents.insert(ident.clone());
@ -176,6 +227,7 @@ impl Parse for ModuleFn {
let struct_where_clause = generics
.where_clause
.as_mut()
.filter(|_| matches!(config_options.kw, HdlOrHdlModule::HdlModule(_)))
.map(|where_clause| WhereClause {
where_token: where_clause.where_token,
predicates: where_clause
@ -198,7 +250,8 @@ impl Parse for ModuleFn {
})
.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,
params: struct_generic_params,
gt_token: generics.gt_token,
@ -213,7 +266,10 @@ impl Parse for ModuleFn {
"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| {
errors.ok(transform_body::transform_body(
module_kind,
@ -224,6 +280,47 @@ impl Parse for ModuleFn {
errors.finish()?;
let struct_generics = struct_generics.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!(io.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 (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
struct_generics.split_for_impl();
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
@ -259,22 +356,22 @@ impl Parse for ModuleFn {
}
};
let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?;
Ok(Self {
Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
attrs,
config_options,
module_kind,
module_kind: module_kind.unwrap(),
vis,
sig,
block,
struct_generics,
the_struct,
})
})))
}
}
impl ModuleFn {
pub(crate) fn generate(self) -> TokenStream {
let Self {
let ModuleFnModule {
attrs,
config_options,
module_kind,
@ -283,7 +380,28 @@ impl ModuleFn {
block,
struct_generics,
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 {
outline_generated: _,
extern_: _,
@ -332,12 +450,21 @@ impl ModuleFn {
let fn_name_str = fn_name.to_string();
let (_, body_type_generics, _) = body_fn.sig.generics.split_for_impl();
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! {{
#body_fn
::fayalite::module::ModuleBuilder::run(
#fn_name_str,
#module_kind_value,
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*),
#body_lambda,
)
}};
let outer_fn = ItemFn {

View file

@ -34,6 +34,7 @@ options! {
Instance(instance),
RegBuilder(reg_builder),
Wire(wire),
IncompleteWire(incomplete_wire),
Memory(memory),
MemoryArray(memory_array),
MemoryWithInit(memory_with_init),
@ -264,11 +265,6 @@ pub(crate) enum RegBuilderReset {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
impl_fold! {
@ -285,11 +281,6 @@ impl_fold! {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
}
@ -311,11 +302,6 @@ impl Parse for RegBuilderReset {
paren: parenthesized!(paren_contents in input),
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 +329,6 @@ impl ToTokens for RegBuilderReset {
reset.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 +377,6 @@ make_builder_method_enum! {
NoReset(no_reset),
#[cond = need_reset]
Reset(reset),
#[cond = need_reset]
ResetDefault(reset_default),
}
}
@ -444,17 +419,13 @@ impl HdlLetKindRegBuilder {
let mut clock_domain = None;
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => {}
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => {}
}
let reset = input.parse()?;
if clock_domain.is_none() {
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => unreachable!(),
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => unreachable!(),
}
}
Ok(Self {
@ -533,6 +504,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! {
pub(crate) enum MemoryFnName {
Memory(memory),
@ -697,6 +703,7 @@ impl HdlLetKindMemory {
#[derive(Clone, Debug)]
pub(crate) enum HdlLetKind<IOType = ParsedType> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -706,6 +713,7 @@ pub(crate) enum HdlLetKind<IOType = ParsedType> {
impl_fold! {
enum HdlLetKind<IOType,> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -720,6 +728,9 @@ impl<T: ParseTypes<I>, I> ParseTypes<HdlLetKind<I>> for HdlLetKind<T> {
) -> Result<Self, ParseFailed> {
match input {
HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO),
HdlLetKind::Incomplete(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete)
}
HdlLetKind::Instance(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance)
}
@ -871,6 +882,20 @@ impl HdlLetKindParse for HdlLetKind<Type> {
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(
input,
parsed_ty,
@ -903,6 +928,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn ty_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.ty_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens),
HdlLetKind::Instance(v) => v.ty_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens),
HdlLetKind::Wire(v) => v.ty_to_tokens(tokens),
@ -913,6 +939,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.expr_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens),
HdlLetKind::Instance(v) => v.expr_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens),
HdlLetKind::Wire(v) => v.expr_to_tokens(tokens),
@ -925,7 +952,7 @@ with_debug_clone_and_fold! {
#[allow(dead_code)]
pub(crate) struct HdlLet<Kind = HdlLetKind> {
pub(crate) attrs: Vec<Attribute>,
pub(crate) hdl_attr: HdlAttr<Nothing>,
pub(crate) hdl_attr: HdlAttr<Nothing, kw::hdl>,
pub(crate) let_token: Token![let],
pub(crate) mut_token: Option<Token![mut]>,
pub(crate) name: Ident,
@ -1112,7 +1139,7 @@ impl<T: ToString> ToTokens for ImplicitName<T> {
}
struct Visitor<'a> {
module_kind: ModuleKind,
module_kind: Option<ModuleKind>,
errors: Errors,
io: Vec<ModuleIO>,
block_depth: usize,
@ -1120,22 +1147,33 @@ struct Visitor<'a> {
}
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(
HdlAttr::parse_and_take_attr(attrs),
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 {
ModuleKind::Extern => {
Some(ModuleKind::Extern) => {
self.errors
.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<Nothing, kw::hdl>, expr_if: ExprIf) -> Expr {
let ExprIf {
attrs,
if_token,
@ -1143,7 +1181,7 @@ impl Visitor<'_> {
then_branch,
else_branch,
} = expr_if;
self.require_normal_module(if_token);
self.require_normal_module_or_fn(if_token);
let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr {
Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if),
expr => expr,
@ -1208,11 +1246,12 @@ impl Visitor<'_> {
.to_tokens(expr);
});
let mut attrs = hdl_let.attrs.clone();
self.require_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)]
}),
ModuleKind::Normal => {}
Some(ModuleKind::Normal) | None => {}
}
let let_stmt = Local {
attrs,
@ -1249,7 +1288,7 @@ impl Visitor<'_> {
},
semi_token,
} = hdl_let;
self.require_normal_module(instance);
self.require_normal_module_or_fn(instance);
let mut expr = instance.to_token_stream();
paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
@ -1276,7 +1315,7 @@ impl Visitor<'_> {
fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet<HdlLetKindRegBuilder>) -> Local {
let name = &hdl_let.name;
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();
hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
@ -1301,7 +1340,7 @@ impl Visitor<'_> {
no_reset.to_tokens(&mut 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);
}
}
@ -1327,7 +1366,7 @@ impl Visitor<'_> {
fn process_hdl_let_wire(&mut self, hdl_let: HdlLet<HdlLetKindWire>) -> Local {
let name = &hdl_let.name;
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 mut expr = wire.to_token_stream();
hdl_let.kind.paren.surround(&mut expr, |expr| {
@ -1357,11 +1396,36 @@ impl Visitor<'_> {
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 {
let name = &hdl_let.name;
let memory_fn = hdl_let.kind.memory_fn;
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 (paren, arg) = match memory_fn {
MemoryFn::Memory {
@ -1426,6 +1490,7 @@ impl Visitor<'_> {
}
the_match! {
IO => process_hdl_let_io,
Incomplete => process_hdl_let_incomplete,
Instance => process_hdl_let_instance,
RegBuilder => process_hdl_let_reg_builder,
Wire => process_hdl_let_wire,
@ -1543,7 +1608,7 @@ impl Fold for Visitor<'_> {
}
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
if is_hdl_attr(&attr) {
if is_hdl_attr::<kw::hdl>(&attr) {
self.errors
.error(&attr, "#[hdl] attribute not supported here");
}
@ -1610,8 +1675,9 @@ impl Fold for Visitor<'_> {
fn fold_local(&mut self, let_stmt: Local) -> Local {
match self
.errors
.ok(HdlAttr::<Nothing>::parse_and_leave_attr(&let_stmt.attrs))
{
.ok(HdlAttr::<Nothing, kw::hdl>::parse_and_leave_attr(
&let_stmt.attrs,
)) {
None => return empty_let(),
Some(None) => return fold_local(self, let_stmt),
Some(Some(HdlAttr { .. })) => {}
@ -1646,7 +1712,7 @@ impl Fold for Visitor<'_> {
}
pub(crate) fn transform_body(
module_kind: ModuleKind,
module_kind: Option<ModuleKind>,
mut body: Box<Block>,
parsed_generics: &ParsedGenerics,
) -> syn::Result<(Box<Block>, Vec<ModuleIO>)> {

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{module::transform_body::Visitor, HdlAttr};
use crate::{kw, module::transform_body::Visitor, HdlAttr};
use quote::{format_ident, quote_spanned};
use syn::{
parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath,
@ -10,10 +10,10 @@ use syn::{
impl Visitor<'_> {
pub(crate) fn process_hdl_array(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
mut expr_array: ExprArray,
) -> Expr {
self.require_normal_module(hdl_attr);
self.require_normal_module_or_fn(hdl_attr);
for elem in &mut expr_array.elems {
*elem = parse_quote_spanned! {elem.span()=>
::fayalite::expr::ToExpr::to_expr(&(#elem))
@ -23,10 +23,10 @@ impl Visitor<'_> {
}
pub(crate) fn process_hdl_repeat(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
mut expr_repeat: ExprRepeat,
) -> Expr {
self.require_normal_module(hdl_attr);
self.require_normal_module_or_fn(hdl_attr);
let repeated_value = &expr_repeat.expr;
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
::fayalite::expr::ToExpr::to_expr(&(#repeated_value))
@ -35,10 +35,10 @@ impl Visitor<'_> {
}
pub(crate) fn process_hdl_struct(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_struct: ExprStruct,
) -> Expr {
self.require_normal_module(&hdl_attr);
self.require_normal_module_or_fn(&hdl_attr);
let name_span = expr_struct.path.segments.last().unwrap().ident.span();
let builder_ident = format_ident!("__builder", span = name_span);
let empty_builder = if expr_struct.qself.is_some()
@ -91,10 +91,10 @@ impl Visitor<'_> {
}
pub(crate) fn process_hdl_tuple(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_tuple: ExprTuple,
) -> Expr {
self.require_normal_module(hdl_attr);
self.require_normal_module_or_fn(hdl_attr);
parse_quote_spanned! {expr_tuple.span()=>
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
}

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
use crate::{
fold::{impl_fold, DoFold},
kw,
module::transform_body::{with_debug_clone_and_fold, Visitor},
Errors, HdlAttr, PairsIterExt,
};
@ -749,7 +750,7 @@ struct HdlMatchParseState<'a> {
impl Visitor<'_> {
pub(crate) fn process_hdl_match(
&mut self,
_hdl_attr: HdlAttr<Nothing>,
_hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_match: ExprMatch,
) -> Expr {
let span = expr_match.match_token.span();
@ -761,7 +762,7 @@ impl Visitor<'_> {
brace_token: _,
arms,
} = expr_match;
self.require_normal_module(match_token);
self.require_normal_module_or_fn(match_token);
let mut state = HdlMatchParseState {
match_span: span,
errors: &mut self.errors,

View file

@ -16,4 +16,4 @@ version.workspace = true
proc-macro = true
[dependencies]
fayalite-proc-macros-impl = { workspace = true }
fayalite-proc-macros-impl.workspace = true

View file

@ -13,11 +13,11 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
indexmap = { workspace = true }
prettyplease = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
syn = { workspace = true }
thiserror = { workspace = true }
indexmap.workspace = true
prettyplease.workspace = true
proc-macro2.workspace = true
quote.workspace = true
serde.workspace = true
serde_json.workspace = true
syn.workspace = true
thiserror.workspace = true

View file

@ -14,22 +14,27 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
bitvec = { workspace = true }
hashbrown = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
fayalite-proc-macros = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { version = "4.5.9", features = ["derive", "env"] }
eyre = "0.6.12"
which = "6.0.1"
bitvec.workspace = true
blake3.workspace = true
clap.workspace = true
ctor.workspace = true
eyre.workspace = true
fayalite-proc-macros.workspace = true
hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
os_pipe.workspace = true
serde_json.workspace = true
serde.workspace = true
tempfile.workspace = true
which.workspace = true
[dev-dependencies]
trybuild = { workspace = true }
trybuild.workspace = true
[build-dependencies]
fayalite-visit-gen = { workspace = true }
fayalite-visit-gen.workspace = true
[features]
unstable-doc = []

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use clap::Parser;
use fayalite::{cli, prelude::*};
@ -15,15 +17,15 @@ fn blinky(clock_frequency: u64) {
let max_value = clock_frequency / 2 - 1;
let int_ty = UInt::range_inclusive(0..=max_value);
#[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]
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
#[hdl]
if counter.cmp_eq(max_value) {
connect_any(counter, 0u8);
if counter_reg.cmp_eq(max_value) {
connect_any(counter_reg, 0u8);
connect(output_reg, !output_reg);
} else {
connect_any(counter, counter + 1_hdl_u1);
connect_any(counter_reg, counter_reg + 1_hdl_u1);
}
#[hdl]
let led: Bool = m.output();

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#![doc = include_str!("../README.md")]
//!

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Fayalite Modules
//!
//! The [`#[hdl_module]`][`crate::hdl_module`] attribute is applied to a Rust

View file

@ -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
//! some other language, such as Verilog.
//!

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Module Function Bodies
//!
//! The `#[hdl_module]` attribute lets you have statements/expressions with `#[hdl]` annotations

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl]` Array Expressions
//!
//! `#[hdl]` can be used on Array Expressions to construct an [`Array<[T; N]>`][type@Array] expression:

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl] if` Statements
//!
//! `#[hdl] if` statements behave similarly to Rust `if` statements, except they end up as muxes

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ## `#[hdl] let` statements
pub mod inputs_outputs;

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Inputs/Outputs
//!
//! Inputs/Outputs create a Rust variable with type [`Expr<T>`] where `T` is the type of the input/output.

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Module Instances
//!
//! module instances are kinda like the hardware equivalent of calling a function,

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Memories
//!
//! Memories are optimized for storing large amounts of data.

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Registers
//!
//! 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.
//!
//! 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::*;
//! # #[hdl_module]
@ -16,11 +21,11 @@
//! #[hdl]
//! let cd: ClockDomain = m.input();
//! #[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]
//! if v {
//! // my_register is only changed when both `v` is set and `cd`'s clock edge occurs.
//! connect(my_register, 0x45_hdl_u8);
//! // my_reg is only changed when both `v` is set and `cd`'s clock edge occurs.
//! connect(my_reg, 0x45_hdl_u8);
//! }
//! # }
//! ```

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Wires
//!
//! Wires are kinda like variables, but unlike registers,

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `_hdl`-suffixed literals
//!
//! You can have integer literals with an arbitrary number of bits like so:

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl] match` Statements
//!
//! `#[hdl] match` statements behave similarly to Rust `match` statements, except they end up as muxes

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl]` Struct/Variant Expressions
//!
//! Note: Structs are also known as [Bundles] when used in Fayalite, the Bundle name comes from [FIRRTL].

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Normal Modules
//!
//! See also: [Extern Modules][`super::extern_module`]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Fayalite Semantics
//!
//! Fayalite's semantics are based on [FIRRTL]. Due to their significance, some of the semantics are also documented here.

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Connection Semantics
//!
//! Fayalite's connection semantics are unlike assignments in software, so be careful!

View file

@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use std::{
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
ops::Deref,
};
@ -118,12 +119,88 @@ pub struct CustomFirrtlAnnotation {
pub additional_fields: CustomFirrtlAnnotationFields,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct DontTouchAnnotation;
#[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 {
(
$(#[$meta:meta])*
$vis:vis enum $Annotation:ident {
$($Variant:ident($T:ident),)*
}
) => {
$(#[$meta])*
$vis enum $Annotation {
$($Variant($T),)*
}
$(impl IntoAnnotations for $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(self)]
}
}
impl IntoAnnotations for &'_ $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
}
impl IntoAnnotations for &'_ mut $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
}
impl IntoAnnotations for Box<$T> {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
})*
};
}
make_annotation_enum! {
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Annotation {
DontTouch,
DontTouch(DontTouchAnnotation),
SVAttribute(SVAttributeAnnotation),
BlackBoxInline(BlackBoxInlineAnnotation),
BlackBoxPath(BlackBoxPathAnnotation),
DocString(DocStringAnnotation),
CustomFirrtl(CustomFirrtlAnnotation),
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct TargetedAnnotation {
@ -187,10 +264,70 @@ impl IntoAnnotations for &'_ mut Annotation {
}
}
impl<T: IntoIterator<Item = Annotation>> IntoAnnotations for T {
type IntoAnnotations = Self;
pub struct IterIntoAnnotations<T: Iterator<Item: IntoAnnotations>> {
outer: T,
inner: Option<<<T::Item as IntoAnnotations>::IntoAnnotations as IntoIterator>::IntoIter>,
}
impl<T: Iterator<Item: IntoAnnotations>> Iterator for IterIntoAnnotations<T> {
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 {
self
IterIntoAnnotations {
outer: self.into_iter(),
inner: None,
}
}
}

View file

@ -85,7 +85,7 @@ 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 {
Self::new(element, Len::SizeType::default())
}
@ -187,7 +187,7 @@ impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
let base = Expr::as_dyn_array(*this);
let base_ty = Expr::ty(base);
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
Interned::<_>::into_inner(Intern::intern_sized(
Interned::into_inner(Intern::intern_sized(
Len::ArrayMatch::<T>::try_from(retval)
.ok()
.expect("unreachable"),
@ -202,7 +202,7 @@ impl<T: Type> Index<T> for ArrayWithoutGenerics {
type Output = ArrayWithoutLen<T>;
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 +215,6 @@ impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
type Output = ArrayType<T, L::Size>;
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)))
}
}

View file

@ -144,6 +144,12 @@ impl BundleTypePropertiesBuilder {
}
}
impl Default for BundleTypePropertiesBuilder {
fn default() -> Self {
Self::new()
}
}
impl Bundle {
#[track_caller]
pub fn new(fields: Interned<[BundleField]>) -> Self {
@ -342,6 +348,7 @@ macro_rules! impl_tuples {
std::iter::once(MatchVariantWithoutScope(($(Expr::field(this, stringify!($num)),)*)))
}
fn mask_type(&self) -> Self::MaskType {
#![allow(clippy::unused_unit)]
let ($($var,)*) = self;
($($var.mask_type(),)*)
}
@ -350,6 +357,7 @@ macro_rules! impl_tuples {
}
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
#![allow(clippy::unused_unit)]
let CanonicalType::Bundle(bundle) = canonical_type else {
panic!("expected bundle");
};
@ -358,7 +366,7 @@ macro_rules! impl_tuples {
};
$(let BundleField { name, flipped, ty } = $var;
assert_eq!(&*name, stringify!($num));
assert_eq!(flipped, false);
assert!(!flipped);
let $var = $T::from_canonical(ty);)*
($($var,)*)
}
@ -377,7 +385,7 @@ macro_rules! impl_tuples {
impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) {
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
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,)*) {
@ -438,3 +446,85 @@ impl_tuples! {
{#[num = 11, field = field_11] v11: T11}
]
}
impl<T: ?Sized + Send + Sync + 'static> Type for PhantomData<T> {
type BaseType = Bundle;
type MaskType = ();
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()
}
}
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> ToExpr for PhantomDataBuilder<T> {
type Type = PhantomData<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>;
type FilledBuilder = 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> ToExpr for PhantomData<T> {
type Type = PhantomData<T>;
fn to_expr(&self) -> Expr<Self::Type> {
BundleLiteral::new(PhantomData, Interned::default()).to_expr()
}
}

View file

@ -1,15 +1,27 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
bundle::{Bundle, BundleType},
firrtl,
firrtl::{self, ExportOptions},
intern::Interned,
module::Module,
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
};
use clap::{
builder::{OsStringValueParser, TypedValueParser},
Args, Parser, Subcommand, ValueEnum, ValueHint,
Parser, Subcommand, ValueEnum, ValueHint,
};
use eyre::{eyre, Report};
use std::{error, ffi::OsString, fmt, io, path::PathBuf, process};
use serde::{Deserialize, Serialize};
use std::{
error,
ffi::OsString,
fmt::{self, Write},
fs, io, mem,
path::{Path, PathBuf},
process,
};
use tempfile::TempDir;
pub type Result<T = (), E = CliError> = std::result::Result<T, E>;
@ -37,74 +49,157 @@ impl From<io::Error> for CliError {
pub trait RunPhase<Arg> {
type Output;
fn run(&self, arg: Arg) -> Result<Self::Output>;
fn run(&self, arg: Arg) -> Result<Self::Output> {
self.run_with_job(arg, &mut AcquiredJob::acquire())
}
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>;
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[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,
#[arg(short, long, value_hint = ValueHint::DirPath, required = true)]
pub output: Option<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>,
#[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")]
pub keep_temp_dir: bool,
#[arg(skip = false)]
pub redirect_output_for_rust_test: bool,
}
impl BaseArgs {
pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend {
fn make_firrtl_file_backend(&self) -> Result<(firrtl::FileBackend, Option<TempDir>)> {
let (dir_path, temp_dir) = match &self.output {
Some(output) => (output.clone(), None),
None => {
let temp_dir = TempDir::new()?;
if self.keep_temp_dir {
let temp_dir = temp_dir.into_path();
println!("created temporary directory: {}", temp_dir.display());
(temp_dir, None)
} else {
(temp_dir.path().to_path_buf(), Some(temp_dir))
}
}
};
Ok((
firrtl::FileBackend {
dir_path: self.output.clone(),
dir_path,
top_fir_file_stem: self.file_stem.clone(),
circuit_name: None,
},
temp_dir,
))
}
/// handles possibly redirecting the command's output for Rust tests
pub fn run_external_command(
&self,
_acquired_job: &mut AcquiredJob,
mut command: process::Command,
mut captured_output: Option<&mut String>,
) -> io::Result<process::ExitStatus> {
if self.redirect_output_for_rust_test || captured_output.is_some() {
let (reader, writer) = os_pipe::pipe()?;
let mut reader = io::BufReader::new(reader);
command.stderr(writer.try_clone()?);
command.stdout(writer); // must not leave writer around after spawning child
command.stdin(process::Stdio::null());
let mut child = command.spawn()?;
drop(command); // close writers
Ok(loop {
let status = child.try_wait()?;
streaming_read_utf8(&mut reader, |s| {
if let Some(captured_output) = captured_output.as_deref_mut() {
captured_output.push_str(s);
}
// use print! so output goes to Rust test output capture
print!("{s}");
io::Result::Ok(())
})?;
if let Some(status) = status {
break status;
}
})
} else {
command.status()
}
}
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct FirrtlArgs {
#[command(flatten)]
pub base: BaseArgs,
#[command(flatten)]
pub export_options: ExportOptions,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FirrtlOutput {
pub file_stem: String,
pub top_module: String,
pub output_dir: PathBuf,
pub temp_dir: Option<TempDir>,
}
impl FirrtlOutput {
pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf {
let mut retval = args.base.output.join(&self.file_stem);
retval.set_extension("fir");
pub fn file_with_ext(&self, ext: &str) -> PathBuf {
let mut retval = self.output_dir.join(&self.file_stem);
retval.set_extension(ext);
retval
}
pub fn firrtl_file(&self) -> PathBuf {
self.file_with_ext("fir")
}
}
impl FirrtlArgs {
fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> {
fn run_impl(
&self,
top_module: Module<Bundle>,
_acquired_job: &mut AcquiredJob,
) -> Result<FirrtlOutput> {
let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?;
let firrtl::FileBackend {
top_fir_file_stem, ..
} = firrtl::export(self.base.to_firrtl_file_backend(), &top_module)?;
top_fir_file_stem,
circuit_name,
dir_path,
} = firrtl::export(file_backend, &top_module, self.export_options)?;
Ok(FirrtlOutput {
file_stem: top_fir_file_stem.expect(
"export is known to set the file stem from the circuit name if not provided",
),
top_module: circuit_name.expect("export is known to set the circuit name"),
output_dir: dir_path,
temp_dir,
})
}
}
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())
fn run_with_job(
&self,
top_module: Module<T>,
acquired_job: &mut AcquiredJob,
) -> Result<Self::Output> {
self.run_impl(top_module.canonical(), acquired_job)
}
}
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)
fn run_with_job(
&self,
top_module: Interned<Module<T>>,
acquired_job: &mut AcquiredJob,
) -> Result<Self::Output> {
self.run_with_job(*top_module, acquired_job)
}
}
@ -120,7 +215,22 @@ pub enum VerilogDialect {
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"],
@ -138,7 +248,7 @@ impl VerilogDialect {
}
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct VerilogArgs {
#[command(flatten)]
@ -156,39 +266,94 @@ pub struct VerilogArgs {
/// adapt the generated Verilog for a particular toolchain
#[arg(long)]
pub verilog_dialect: Option<VerilogDialect>,
#[arg(long, short = 'g')]
pub debug: bool,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct VerilogOutput {
pub firrtl: FirrtlOutput,
pub verilog_files: Vec<PathBuf>,
pub contents_hash: Option<blake3::Hash>,
}
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
pub fn main_verilog_file(&self) -> PathBuf {
self.firrtl.file_with_ext("v")
}
fn unadjusted_verilog_file(&self) -> PathBuf {
self.firrtl.file_with_ext("unadjusted.v")
}
}
impl VerilogArgs {
fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> {
fn process_unadjusted_verilog_file(&self, mut output: VerilogOutput) -> Result<VerilogOutput> {
let input = fs::read_to_string(output.unadjusted_verilog_file())?;
let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
let file_separator_suffix = "\" ----- 8< -----\n\n";
let mut input = &*input;
output.contents_hash = Some(blake3::hash(input.as_bytes()));
let main_verilog_file = output.main_verilog_file();
let mut file_name: Option<&Path> = Some(&main_verilog_file);
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 {
return Err(CliError(eyre!("parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}")));
};
input = rest;
(chunk, Some(next_file_name.as_ref()))
} else {
(mem::take(&mut input), None)
};
let Some(file_name) = mem::replace(&mut file_name, next_file_name) else {
break;
};
let file_name = output.firrtl.output_dir.join(file_name);
fs::write(&file_name, chunk)?;
if let Some(extension) = file_name.extension() {
if extension == "v" || extension == "sv" {
output.verilog_files.push(file_name);
}
}
}
Ok(output)
}
fn run_impl(
&self,
firrtl_output: FirrtlOutput,
acquired_job: &mut AcquiredJob,
) -> Result<VerilogOutput> {
let Self {
firrtl,
firtool,
firtool_extra_args,
verilog_dialect,
debug,
} = self;
let output = VerilogOutput {
firrtl: firrtl_output,
verilog_files: vec![],
contents_hash: None,
};
let mut cmd = process::Command::new(&self.firtool);
cmd.arg(output.firrtl.firrtl_file(&self.firrtl));
let mut cmd = process::Command::new(firtool);
cmd.arg(output.firrtl.firrtl_file());
cmd.arg("-o");
cmd.arg(output.verilog_file(self));
if let Some(dialect) = self.verilog_dialect {
cmd.arg(output.unadjusted_verilog_file());
if *debug {
cmd.arg("-g");
cmd.arg("--preserve-values=all");
}
if let Some(dialect) = 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()?;
cmd.args(firtool_extra_args);
cmd.current_dir(&output.firrtl.output_dir);
let status = firrtl.base.run_external_command(acquired_job, cmd, None)?;
if status.success() {
Ok(output)
self.process_unadjusted_verilog_file(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
@ -203,9 +368,318 @@ 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)
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
let firrtl_output = self.firrtl.run_with_job(arg, acquired_job)?;
self.run_impl(firrtl_output, acquired_job)
}
}
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[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(Clone)]
struct FormalAdjustArgs;
impl clap::FromArgMatches for FormalAdjustArgs {
fn from_arg_matches(_matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
Ok(Self)
}
fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
Ok(())
}
}
impl clap::Args for FormalAdjustArgs {
fn augment_args(cmd: clap::Command) -> clap::Command {
cmd.mut_arg("output", |arg| arg.required(false))
.mut_arg("verilog_dialect", |arg| {
arg.default_value(VerilogDialect::Yosys.to_string())
.hide(true)
})
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
}
#[derive(Parser, Clone)]
#[non_exhaustive]
pub struct FormalArgs {
#[command(flatten)]
pub verilog: VerilogArgs,
#[arg(
long,
default_value = "sby",
env = "SBY",
value_hint = ValueHint::CommandName,
value_parser = OsStringValueParser::new().try_map(which::which)
)]
pub sby: PathBuf,
#[arg(long)]
pub sby_extra_args: Vec<String>,
#[arg(long, default_value_t)]
pub mode: FormalMode,
#[arg(long, default_value_t = Self::DEFAULT_DEPTH)]
pub depth: u64,
#[arg(long, default_value = "z3")]
pub solver: String,
#[arg(long)]
pub smtbmc_extra_args: Vec<String>,
#[arg(long, default_value_t = true, env = "FAYALITE_CACHE_RESULTS")]
pub cache_results: bool,
#[command(flatten)]
_formal_adjust_args: FormalAdjustArgs,
}
impl fmt::Debug for FormalArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
verilog,
sby,
sby_extra_args,
mode,
depth,
solver,
smtbmc_extra_args,
cache_results,
_formal_adjust_args: _,
} = self;
f.debug_struct("FormalArgs")
.field("verilog", verilog)
.field("sby", sby)
.field("sby_extra_args", sby_extra_args)
.field("mode", mode)
.field("depth", depth)
.field("solver", solver)
.field("smtbmc_extra_args", smtbmc_extra_args)
.field("cache_results", cache_results)
.finish_non_exhaustive()
}
}
impl FormalArgs {
pub const DEFAULT_DEPTH: u64 = 20;
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FormalOutput {
pub verilog: VerilogOutput,
}
impl FormalOutput {
pub fn sby_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("sby")
}
pub fn cache_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("cache.json")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCacheOutput {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FormalCacheVersion {
V1,
}
impl FormalCacheVersion {
pub const CURRENT: Self = Self::V1;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCache {
pub version: FormalCacheVersion,
pub contents_hash: blake3::Hash,
pub stdout_stderr: String,
pub result: Result<FormalCacheOutput, String>,
}
impl FormalCache {
pub fn new(
version: FormalCacheVersion,
contents_hash: blake3::Hash,
stdout_stderr: String,
result: Result<FormalCacheOutput, String>,
) -> Self {
Self {
version,
contents_hash,
stdout_stderr,
result,
}
}
}
impl FormalArgs {
fn sby_contents(&self, output: &FormalOutput) -> Result<String> {
let Self {
verilog: _,
sby: _,
sby_extra_args: _,
mode,
depth,
smtbmc_extra_args,
solver,
cache_results: _,
_formal_adjust_args: _,
} = self;
let smtbmc_options = smtbmc_extra_args.join(" ");
let top_module = &output.verilog.firrtl.top_module;
let mut retval = format!(
"[options]\n\
mode {mode}\n\
depth {depth}\n\
wait on\n\
\n\
[engines]\n\
smtbmc {solver} -- -- {smtbmc_options}\n\
\n\
[script]\n"
);
for verilog_file in &output.verilog.verilog_files {
let verilog_file = verilog_file
.to_str()
.ok_or_else(|| CliError(eyre!("verilog file path is not UTF-8")))?;
if verilog_file.contains(|ch: char| {
(ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"'
}) {
return Err(CliError(eyre!(
"verilog file path contains characters that aren't permitted"
)));
}
writeln!(retval, "read_verilog -sv -formal \"{verilog_file}\"").unwrap();
}
// workaround for wires disappearing -- set `keep` on all wires
writeln!(retval, "hierarchy -top {top_module}").unwrap();
writeln!(retval, "proc").unwrap();
writeln!(retval, "setattr -set keep 1 w:\\*").unwrap();
writeln!(retval, "prep").unwrap();
Ok(retval)
}
fn run_impl(
&self,
verilog_output: VerilogOutput,
acquired_job: &mut AcquiredJob,
) -> Result<FormalOutput> {
let output = FormalOutput {
verilog: verilog_output,
};
let sby_file = output.sby_file();
let sby_contents = self.sby_contents(&output)?;
let contents_hash = output.verilog.contents_hash.map(|verilog_hash| {
let mut hasher = blake3::Hasher::new();
hasher.update(verilog_hash.as_bytes());
hasher.update(sby_contents.as_bytes());
hasher.update(&(self.sby_extra_args.len() as u64).to_le_bytes());
for sby_extra_arg in self.sby_extra_args.iter() {
hasher.update(&(sby_extra_arg.len() as u64).to_le_bytes());
hasher.update(sby_extra_arg.as_bytes());
}
hasher.finalize()
});
std::fs::write(&sby_file, sby_contents)?;
let mut cmd = process::Command::new(&self.sby);
cmd.arg("-j1"); // sby seems not to respect job count in parallel mode
cmd.arg("-f");
cmd.arg(sby_file.file_name().unwrap());
cmd.args(&self.sby_extra_args);
cmd.current_dir(&output.verilog.firrtl.output_dir);
let mut captured_output = String::new();
let cache_file = output.cache_file();
let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) {
if let Some(FormalCache {
version: FormalCacheVersion::CURRENT,
contents_hash: cache_contents_hash,
stdout_stderr,
result,
}) = fs::read(&cache_file)
.ok()
.and_then(|v| serde_json::from_slice(&v).ok())
{
if cache_contents_hash == contents_hash {
println!("Using cached formal result:\n{stdout_stderr}");
return match result {
Ok(FormalCacheOutput {}) => Ok(output),
Err(error) => Err(CliError(eyre::Report::msg(error))),
};
}
}
true
} else {
false
};
let _ = fs::remove_file(&cache_file);
let status = self.verilog.firrtl.base.run_external_command(
acquired_job,
cmd,
do_cache.then_some(&mut captured_output),
)?;
let result = if status.success() {
Ok(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
self.sby.display()
)))
};
if do_cache {
fs::write(
cache_file,
serde_json::to_string_pretty(&FormalCache {
version: FormalCacheVersion::CURRENT,
contents_hash: contents_hash.unwrap(),
stdout_stderr: captured_output,
result: match &result {
Ok(FormalOutput { verilog: _ }) => Ok(FormalCacheOutput {}),
Err(error) => Err(error.to_string()),
},
})
.expect("serialization shouldn't ever fail"),
)?;
}
result
}
}
impl<Arg> RunPhase<Arg> for FormalArgs
where
VerilogArgs: RunPhase<Arg, Output = VerilogOutput>,
{
type Output = FormalOutput;
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
let verilog_output = self.verilog.run_with_job(arg, acquired_job)?;
self.run_impl(verilog_output, acquired_job)
}
}
@ -215,6 +689,8 @@ enum CliCommand {
Firrtl(FirrtlArgs),
/// Generate Verilog
Verilog(VerilogArgs),
/// Run a formal proof
Formal(FormalArgs),
}
/// a simple CLI
@ -292,13 +768,16 @@ where
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
{
type Output = ();
fn run(&self, arg: T) -> Result<Self::Output> {
fn run_with_job(&self, arg: T, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
match &self.subcommand {
CliCommand::Firrtl(c) => {
c.run(arg)?;
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Verilog(c) => {
c.run(arg)?;
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Formal(c) => {
c.run_with_job(arg, acquired_job)?;
}
}
Ok(())

View file

@ -7,14 +7,14 @@ use crate::{
int::Bool,
intern::{Intern, Interned},
module::{
enum_match_variants_helper, EnumMatchVariantAndInactiveScopeImpl,
EnumMatchVariantsIterImpl, Scope,
connect, enum_match_variants_helper, incomplete_wire, wire,
EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope,
},
source_location::SourceLocation,
ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties},
};
use hashbrown::HashMap;
use std::{fmt, iter::FusedIterator};
use std::{convert::Infallible, fmt, iter::FusedIterator};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct EnumVariant {
@ -169,6 +169,12 @@ impl EnumTypePropertiesBuilder {
}
}
impl Default for EnumTypePropertiesBuilder {
fn default() -> Self {
Self::new()
}
}
impl Enum {
#[track_caller]
pub fn new(variants: Interned<[EnumVariant]>) -> Self {
@ -364,3 +370,307 @@ pub fn HdlSome<T: Type>(value: impl ToExpr<Type = T>) -> Expr<HdlOption<T>> {
let value = value.to_expr();
HdlOption[Expr::ty(value)].HdlSome(value)
}
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(Expr::ty(value));
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, Expr::ty(and_then_out).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(Expr::ty(opt_b));
connect(and_out, Expr::ty(opt_b).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(expr));
connect(filtered, Expr::ty(expr).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(Expr::ty(default));
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(Expr::ty(v)));
connect(mapped, v);
}
HdlNone => {
let v = default.take().unwrap()();
let mapped = *retval.get_or_insert_with(|| mapped.complete(Expr::ty(v)));
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(expr));
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(expr));
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(Expr::ty(default));
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(expr).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(expr));
#[hdl]
if let HdlSome(_) = expr {
#[hdl]
if let HdlNone = opt_b {
connect(xor_out, expr);
} else {
connect(xor_out, Expr::ty(expr).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(expr).HdlSome, Expr::ty(other).HdlSome)]);
connect(zip_out, Expr::ty(zip_out).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(expr).HdlSome);
#[hdl]
match expr {
HdlSome(v) => connect(flattened, v),
HdlNone => connect(flattened, Expr::ty(expr).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(expr).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
}
}

View file

@ -9,7 +9,7 @@ use crate::{
ops::ExprCastTo,
target::{GetTarget, Target},
},
int::{Bool, DynSize, IntType, SIntType, SIntValue, Size, UInt, UIntType, UIntValue},
int::{Bool, DynSize, IntType, SIntType, SIntValue, Size, SizeType, UInt, UIntType, UIntValue},
intern::{Intern, Interned},
memory::{DynPortType, MemPort, PortType},
module::{
@ -111,6 +111,7 @@ expr_enum! {
BundleLiteral(ops::BundleLiteral),
ArrayLiteral(ops::ArrayLiteral<CanonicalType, DynSize>),
EnumLiteral(ops::EnumLiteral),
Uninit(ops::Uninit),
NotU(ops::NotU),
NotS(ops::NotS),
NotB(ops::NotB),
@ -640,6 +641,18 @@ impl<T: PortType> GetTarget for MemPort<T> {
}
}
pub trait HdlPartialEq<Rhs> {
fn cmp_eq(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ne(self, rhs: Rhs) -> Expr<Bool>;
}
pub trait HdlPartialOrd<Rhs>: HdlPartialEq<Rhs> {
fn cmp_lt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_le(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_gt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ge(self, rhs: Rhs) -> Expr<Bool>;
}
pub trait ReduceBits {
type UIntOutput;
type BoolOutput;
@ -697,3 +710,28 @@ pub fn check_match_expr<T: Type>(
_check_fn: impl FnOnce(T::MatchVariant, Infallible),
) {
}
pub trait MakeUninitExpr: Type {
fn uninit(self) -> Expr<Self>;
}
impl<T: Type> MakeUninitExpr for T {
fn uninit(self) -> Expr<Self> {
ops::Uninit::new(self).to_expr()
}
}
pub fn repeat<T: Type, L: SizeType>(
element: impl ToExpr<Type = T>,
len: L,
) -> Expr<ArrayType<T, L::Size>> {
let element = element.to_expr();
let canonical_element = Expr::canonical(element);
ops::ArrayLiteral::new(
Expr::ty(element),
std::iter::repeat(canonical_element)
.take(L::Size::as_usize(len))
.collect(),
)
.to_expr()
}

View file

@ -11,11 +11,12 @@ use crate::{
GetTarget, Target, TargetPathArrayElement, TargetPathBundleField,
TargetPathDynArrayElement, TargetPathElement,
},
CastTo, Expr, ExprEnum, Flow, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits,
CastTo, Expr, ExprEnum, Flow, HdlPartialEq, HdlPartialOrd, NotALiteralExpr, ReduceBits,
ToExpr, ToLiteralBits,
},
int::{
Bool, BoolOrIntType, DynSize, IntCmp, IntType, KnownSize, SInt, SIntType, SIntValue, Size,
UInt, UIntType, UIntValue,
Bool, BoolOrIntType, DynSize, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt,
UIntType, UIntValue,
},
intern::{Intern, Interned},
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
@ -1236,10 +1237,11 @@ macro_rules! impl_dyn_shl {
}
}
impl_binary_op_trait! {
#[generics(LhsWidth: Size, RhsWidth: Size)]
fn Shl::shl(lhs: $ty<LhsWidth>, rhs: UIntType<RhsWidth>) -> $ty {
$name::new(Expr::as_dyn_int(lhs), Expr::as_dyn_int(rhs)).to_expr()
impl<LhsWidth: Size, RhsWidth: Size> Shl<Expr<UIntType<RhsWidth>>> for Expr<$ty<LhsWidth>> {
type Output = Expr<$ty>;
fn shl(self, rhs: Expr<UIntType<RhsWidth>>) -> Self::Output {
$name::new(Expr::as_dyn_int(self), Expr::as_dyn_int(rhs)).to_expr()
}
}
};
@ -1308,10 +1310,11 @@ macro_rules! impl_dyn_shr {
}
}
impl_binary_op_trait! {
#[generics(LhsWidth: Size, RhsWidth: Size)]
fn Shr::shr(lhs: $ty<LhsWidth>, rhs: UIntType<RhsWidth>) -> $ty<LhsWidth> {
$name::new(lhs, Expr::as_dyn_int(rhs)).to_expr()
impl<LhsWidth: Size, RhsWidth: Size> Shr<Expr<UIntType<RhsWidth>>> for Expr<$ty<LhsWidth>> {
type Output = Expr<$ty<LhsWidth>>;
fn shr(self, rhs: Expr<UIntType<RhsWidth>>) -> Self::Output {
$name::new(self, Expr::as_dyn_int(rhs)).to_expr()
}
}
};
@ -1420,36 +1423,45 @@ forward_value_to_expr_binary_op_trait! {
Shr::shr
}
pub trait IntCmpExpr<Rhs: Type>: Type {
pub trait ExprPartialEq<Rhs: Type>: Type {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
}
pub trait ExprPartialOrd<Rhs: Type>: ExprPartialEq<Rhs> {
fn cmp_lt(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
fn cmp_le(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
fn cmp_gt(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
fn cmp_ge(lhs: Expr<Self>, rhs: Expr<Rhs>) -> Expr<Bool>;
}
impl<Lhs: ToExpr, Rhs: ToExpr> IntCmp<Rhs> for Lhs
impl<Lhs: ToExpr, Rhs: ToExpr> HdlPartialEq<Rhs> for Lhs
where
Lhs::Type: IntCmpExpr<Rhs::Type>,
Lhs::Type: ExprPartialEq<Rhs::Type>,
{
fn cmp_eq(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_eq(self.to_expr(), rhs.to_expr())
ExprPartialEq::cmp_eq(self.to_expr(), rhs.to_expr())
}
fn cmp_ne(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_ne(self.to_expr(), rhs.to_expr())
ExprPartialEq::cmp_ne(self.to_expr(), rhs.to_expr())
}
}
impl<Lhs: ToExpr, Rhs: ToExpr> HdlPartialOrd<Rhs> for Lhs
where
Lhs::Type: ExprPartialOrd<Rhs::Type>,
{
fn cmp_lt(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_lt(self.to_expr(), rhs.to_expr())
ExprPartialOrd::cmp_lt(self.to_expr(), rhs.to_expr())
}
fn cmp_le(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_le(self.to_expr(), rhs.to_expr())
ExprPartialOrd::cmp_le(self.to_expr(), rhs.to_expr())
}
fn cmp_gt(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_gt(self.to_expr(), rhs.to_expr())
ExprPartialOrd::cmp_gt(self.to_expr(), rhs.to_expr())
}
fn cmp_ge(self, rhs: Rhs) -> Expr<Bool> {
IntCmpExpr::cmp_ge(self.to_expr(), rhs.to_expr())
ExprPartialOrd::cmp_ge(self.to_expr(), rhs.to_expr())
}
}
@ -1459,6 +1471,7 @@ macro_rules! impl_compare_op {
#[dyn_type($DynTy:ident)]
#[to_dyn_type($lhs:ident => $dyn_lhs:expr, $rhs:ident => $dyn_rhs:expr)]
#[type($Lhs:ty, $Rhs:ty)]
#[trait($Trait:ident)]
$(
struct $name:ident;
fn $method:ident();
@ -1510,7 +1523,7 @@ macro_rules! impl_compare_op {
}
})*
impl$(<$LhsWidth: Size, $RhsWidth: Size>)? IntCmpExpr<$Rhs> for $Lhs {
impl$(<$LhsWidth: Size, $RhsWidth: Size>)? $Trait<$Rhs> for $Lhs {
$(fn $method($lhs: Expr<Self>, $rhs: Expr<$Rhs>) -> Expr<Bool> {
$name::new($dyn_lhs, $dyn_rhs).to_expr()
})*
@ -1522,8 +1535,16 @@ impl_compare_op! {
#[dyn_type(Bool)]
#[to_dyn_type(lhs => lhs, rhs => rhs)]
#[type(Bool, Bool)]
#[trait(ExprPartialEq)]
struct CmpEqB; fn cmp_eq(); PartialEq::eq();
struct CmpNeB; fn cmp_ne(); PartialEq::ne();
}
impl_compare_op! {
#[dyn_type(Bool)]
#[to_dyn_type(lhs => lhs, rhs => rhs)]
#[type(Bool, Bool)]
#[trait(ExprPartialOrd)]
struct CmpLtB; fn cmp_lt(); PartialOrd::lt();
struct CmpLeB; fn cmp_le(); PartialOrd::le();
struct CmpGtB; fn cmp_gt(); PartialOrd::gt();
@ -1535,8 +1556,17 @@ impl_compare_op! {
#[dyn_type(UInt)]
#[to_dyn_type(lhs => Expr::as_dyn_int(lhs), rhs => Expr::as_dyn_int(rhs))]
#[type(UIntType<LhsWidth>, UIntType<RhsWidth>)]
#[trait(ExprPartialEq)]
struct CmpEqU; fn cmp_eq(); PartialEq::eq();
struct CmpNeU; fn cmp_ne(); PartialEq::ne();
}
impl_compare_op! {
#[width(LhsWidth, RhsWidth)]
#[dyn_type(UInt)]
#[to_dyn_type(lhs => Expr::as_dyn_int(lhs), rhs => Expr::as_dyn_int(rhs))]
#[type(UIntType<LhsWidth>, UIntType<RhsWidth>)]
#[trait(ExprPartialOrd)]
struct CmpLtU; fn cmp_lt(); PartialOrd::lt();
struct CmpLeU; fn cmp_le(); PartialOrd::le();
struct CmpGtU; fn cmp_gt(); PartialOrd::gt();
@ -1548,8 +1578,17 @@ impl_compare_op! {
#[dyn_type(SInt)]
#[to_dyn_type(lhs => Expr::as_dyn_int(lhs), rhs => Expr::as_dyn_int(rhs))]
#[type(SIntType<LhsWidth>, SIntType<RhsWidth>)]
#[trait(ExprPartialEq)]
struct CmpEqS; fn cmp_eq(); PartialEq::eq();
struct CmpNeS; fn cmp_ne(); PartialEq::ne();
}
impl_compare_op! {
#[width(LhsWidth, RhsWidth)]
#[dyn_type(SInt)]
#[to_dyn_type(lhs => Expr::as_dyn_int(lhs), rhs => Expr::as_dyn_int(rhs))]
#[type(SIntType<LhsWidth>, SIntType<RhsWidth>)]
#[trait(ExprPartialOrd)]
struct CmpLtS; fn cmp_lt(); PartialOrd::lt();
struct CmpLeS; fn cmp_le(); PartialOrd::le();
struct CmpGtS; fn cmp_gt(); PartialOrd::gt();
@ -2018,7 +2057,7 @@ impl<ElementType: Type, Len: Size> ExprIndex<usize> for ArrayType<ElementType, L
#[track_caller]
fn expr_index(this: &Expr<Self>, index: usize) -> &Expr<Self::Output> {
Interned::<_>::into_inner(
Interned::into_inner(
ArrayIndex::<ElementType>::new(Expr::as_dyn_array(*this), index)
.to_expr()
.intern_sized(),
@ -2115,7 +2154,7 @@ impl<ElementType: Type, Len: Size, Width: Size> ExprIndex<Expr<UIntType<Width>>>
type Output = ElementType;
fn expr_index(this: &Expr<Self>, index: Expr<UIntType<Width>>) -> &Expr<Self::Output> {
Interned::<_>::into_inner(
Interned::into_inner(
DynArrayIndex::<ElementType>::new(Expr::as_dyn_array(*this), Expr::as_dyn_int(index))
.to_expr()
.intern_sized(),
@ -2240,7 +2279,7 @@ macro_rules! impl_int_slice {
let base = Expr::as_dyn_int(*this);
let base_ty = Expr::ty(base);
let range = base_ty.slice_index_to_range(index);
Interned::<_>::into_inner($name::new(base, range).to_expr().intern_sized())
Interned::into_inner($name::new(base, range).to_expr().intern_sized())
}
}
@ -2252,7 +2291,7 @@ macro_rules! impl_int_slice {
let base = Expr::as_dyn_int(*this);
let base_ty = Expr::ty(base);
assert!(index < base_ty.width());
Interned::<_>::into_inner(
Interned::into_inner(
$name::new(base, index..(index + 1))
.to_expr()
.cast_to_static::<Bool>()
@ -2527,3 +2566,41 @@ impl<T: Type> ToExpr for CastBitsTo<T> {
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Uninit<T: Type = CanonicalType> {
ty: T,
}
impl<T: Type> Uninit<T> {
#[track_caller]
pub fn new(ty: T) -> Self {
Self { ty }
}
pub fn ty(self) -> T {
self.ty
}
}
impl<T: Type> ToLiteralBits for Uninit<T> {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Err(NotALiteralExpr)
}
}
impl_get_target_none!([T: Type] Uninit<T>);
impl<T: Type> ToExpr for Uninit<T> {
type Type = T;
fn to_expr(&self) -> Expr<Self::Type> {
Expr {
__enum: ExprEnum::Uninit(Uninit {
ty: self.ty.canonical(),
})
.intern(),
__ty: self.ty,
__flow: Flow::Source,
}
}
}

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
array::Array,
bundle::{Bundle, BundleField},

View file

@ -2,7 +2,10 @@
// See Notices.txt for copyright information
#![allow(clippy::type_complexity)]
use crate::{
annotations::CustomFirrtlAnnotation,
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
},
array::Array,
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
@ -14,14 +17,19 @@ use crate::{
},
Expr, ExprEnum,
},
formal::FormalKind,
int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue},
intern::{Intern, Interned},
memory::{Mem, PortKind, PortName, ReadUnderWrite},
module::{
transform::simplify_memories::simplify_memories, AnnotatedModuleIO, Block,
ExternModuleBody, ExternModuleParameter, ExternModuleParameterValue, Module, ModuleBody,
NameId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtIf, StmtInstance,
StmtMatch, StmtReg, StmtWire,
transform::{
simplify_enums::{simplify_enums, SimplifyEnumsError, SimplifyEnumsKind},
simplify_memories::simplify_memories,
},
AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Module, ModuleBody, NameOptId, NormalModuleBody, Stmt,
StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg,
StmtWire,
},
reset::{AsyncReset, Reset, SyncReset},
source_location::SourceLocation,
@ -32,6 +40,7 @@ use crate::{
},
};
use bitvec::slice::BitSlice;
use clap::value_parser;
use hashbrown::{HashMap, HashSet};
use num_traits::Signed;
use serde::Serialize;
@ -178,9 +187,9 @@ struct NameMaker {
}
impl NameMaker {
fn make(&mut self, name: NameId) -> Ident {
let mut num: usize = name.1;
let name = String::from(&*name.0);
fn make(&mut self, name: impl Into<String>) -> Ident {
let mut num = 0usize;
let name: String = name.into();
// remove all invalid characters -- all valid characters are ASCII, so we can just remove invalid bytes
let mut name = String::from_iter(
name.bytes()
@ -212,7 +221,7 @@ impl NameMaker {
#[derive(Default)]
struct Namespace {
name_maker: NameMaker,
map: HashMap<NameId, Ident>,
map: HashMap<NameOptId, Ident>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -238,10 +247,11 @@ impl From<PortName> for Ident {
}
impl Namespace {
fn get(&mut self, name: NameId) -> Ident {
fn get(&mut self, name: impl Into<NameOptId>) -> Ident {
let name: NameOptId = name.into();
#[cold]
fn make(name_maker: &mut NameMaker, name: NameId) -> Ident {
name_maker.make(name)
fn make(name_maker: &mut NameMaker, name: NameOptId) -> Ident {
name_maker.make(name.0)
}
*self
.map
@ -249,7 +259,7 @@ impl Namespace {
.or_insert_with(|| make(&mut self.name_maker, name))
}
fn make_new(&mut self, prefix: &str) -> Ident {
self.name_maker.make(NameId(prefix.intern(), 0))
self.name_maker.make(prefix)
}
}
@ -359,7 +369,7 @@ impl TypeState {
Ident(Intern::intern_owned(format!("Ty{id}")))
}
fn get_bundle_field(&mut self, ty: Bundle, name: Interned<str>) -> Ident {
self.bundle_ns(ty).borrow_mut().get(NameId(name, 0))
self.bundle_ns(ty).borrow_mut().get(name)
}
fn bundle_def(&self, ty: Bundle) -> (Ident, Rc<RefCell<Namespace>>) {
self.bundle_defs.get_or_make(ty, |&ty, definitions| {
@ -373,7 +383,7 @@ impl TypeState {
if flipped {
body.push_str("flip ");
}
write!(body, "{}: ", ns.get(NameId(name, 0))).unwrap();
write!(body, "{}: ", ns.get(name)).unwrap();
body.push_str(&self.ty(ty));
}
body.push('}');
@ -397,7 +407,7 @@ impl TypeState {
for EnumVariant { name, ty } in ty.variants() {
body.push_str(separator);
separator = ", ";
write!(body, "{}", variants.get(NameId(name, 0))).unwrap();
write!(body, "{}", variants.get(name)).unwrap();
if let Some(ty) = ty {
body.push_str(": ");
body.push_str(&self.ty(ty));
@ -419,11 +429,7 @@ impl TypeState {
self.enum_def(ty).0
}
fn get_enum_variant(&mut self, ty: Enum, name: Interned<str>) -> Ident {
self.enum_def(ty)
.1
.variants
.borrow_mut()
.get(NameId(name, 0))
self.enum_def(ty).1.variants.borrow_mut().get(name)
}
fn ty<T: Type>(&self, ty: T) -> String {
match ty.canonical() {
@ -476,6 +482,7 @@ trait WrappedFileBackendTrait {
circuit_name: String,
contents: String,
) -> Result<(), WrappedError>;
fn simplify_enums_error(&mut self, error: SimplifyEnumsError) -> WrappedError;
}
struct WrappedFileBackend<B: FileBackendTrait> {
@ -533,6 +540,11 @@ impl<B: FileBackendTrait> WrappedFileBackendTrait for WrappedFileBackend<B> {
WrappedError
})
}
fn simplify_enums_error(&mut self, error: SimplifyEnumsError) -> WrappedError {
self.error = Err(error.into());
WrappedError
}
}
#[derive(Clone)]
@ -654,6 +666,17 @@ enum AnnotationData {
},
#[serde(rename = "firrtl.transforms.DontTouchAnnotation")]
DontTouch,
#[serde(rename = "firrtl.AttributeAnnotation")]
AttributeAnnotation { description: Interned<str> },
#[serde(rename = "firrtl.transforms.BlackBoxInlineAnno")]
BlackBoxInlineAnno {
name: Interned<str>,
text: Interned<str>,
},
#[serde(rename = "firrtl.transforms.BlackBoxPathAnno")]
BlackBoxPathAnno { path: Interned<str> },
#[serde(rename = "firrtl.DocStringAnnotation")]
DocStringAnnotation { description: Interned<str> },
#[allow(dead_code)]
#[serde(untagged)]
Other {
@ -664,7 +687,7 @@ enum AnnotationData {
}
#[derive(Serialize)]
struct Annotation {
struct FirrtlAnnotation {
#[serde(flatten)]
data: AnnotationData,
target: AnnotationTarget,
@ -679,7 +702,7 @@ struct Exporter<'a> {
module: ModuleState,
type_state: TypeState,
circuit_name: Ident,
annotations: Vec<Annotation>,
annotations: Vec<FirrtlAnnotation>,
}
struct PushIndent<'a> {
@ -903,7 +926,7 @@ impl<'a> Exporter<'a> {
) in expr.field_values().into_iter().zip(ty.fields())
{
debug_assert!(!flipped, "can't have bundle literal with flipped field -- this should have been caught in BundleLiteral::new_unchecked");
let name = bundle_ns.borrow_mut().get(NameId(name, 0));
let name = bundle_ns.borrow_mut().get(name);
let field_value = self.expr(Expr::canonical(field_value), definitions, const_ty);
definitions.add_definition_line(format_args!("connect {ident}.{name}, {field_value}"));
}
@ -912,6 +935,20 @@ impl<'a> Exporter<'a> {
}
ident.to_string()
}
fn uninit_expr(
&mut self,
expr: ops::Uninit,
definitions: &RcDefinitions,
const_ty: bool,
) -> String {
let ident = self.module.ns.make_new("_uninit_expr");
let ty = expr.ty();
let ty_ident = self.type_state.ty(ty);
let const_ = if const_ty { "const " } else { "" };
definitions.add_definition_line(format_args!("wire {ident}: {const_}{ty_ident}"));
definitions.add_definition_line(format_args!("invalidate {ident}"));
ident.to_string()
}
fn enum_literal_expr(
&mut self,
expr: ops::EnumLiteral<Enum>,
@ -1367,6 +1404,7 @@ impl<'a> Exporter<'a> {
ExprEnum::EnumLiteral(enum_literal) => {
self.enum_literal_expr(enum_literal, definitions, const_ty)
}
ExprEnum::Uninit(uninit) => self.uninit_expr(uninit, definitions, const_ty),
ExprEnum::NotU(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty),
ExprEnum::NotS(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty),
ExprEnum::NotB(expr) => self.expr_unary("not", expr.arg(), definitions, const_ty),
@ -1744,7 +1782,7 @@ impl<'a> Exporter<'a> {
memory_name.0.to_string(),
contents,
)?;
self.annotations.push(Annotation {
self.annotations.push(FirrtlAnnotation {
data: AnnotationData::MemoryFileInline {
filename,
hex_or_binary,
@ -1763,14 +1801,25 @@ impl<'a> Exporter<'a> {
});
Ok(())
}
fn annotation(
&mut self,
path: AnnotationTargetPath,
annotation: &crate::annotations::Annotation,
) {
fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) {
let data = match annotation {
crate::annotations::Annotation::DontTouch => AnnotationData::DontTouch,
crate::annotations::Annotation::CustomFirrtl(CustomFirrtlAnnotation {
Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch,
Annotation::SVAttribute(SVAttributeAnnotation { text }) => {
AnnotationData::AttributeAnnotation { description: *text }
}
Annotation::BlackBoxInline(BlackBoxInlineAnnotation { path, text }) => {
AnnotationData::BlackBoxInlineAnno {
name: *path,
text: *text,
}
}
Annotation::BlackBoxPath(BlackBoxPathAnnotation { path }) => {
AnnotationData::BlackBoxPathAnno { path: *path }
}
Annotation::DocString(DocStringAnnotation { text }) => {
AnnotationData::DocStringAnnotation { description: *text }
}
Annotation::CustomFirrtl(CustomFirrtlAnnotation {
class,
additional_fields,
}) => AnnotationData::Other {
@ -1778,7 +1827,7 @@ impl<'a> Exporter<'a> {
additional_fields: (*additional_fields).into(),
},
};
self.annotations.push(Annotation {
self.annotations.push(FirrtlAnnotation {
data,
target: AnnotationTarget {
circuit: self.circuit_name,
@ -1930,6 +1979,15 @@ impl<'a> Exporter<'a> {
rhs,
source_location,
}) => {
if Expr::ty(lhs) != Expr::ty(rhs) {
writeln!(
body,
"{indent}; connect different types:\n{indent}; lhs: {:?}\n{indent}; rhs: {:?}",
Expr::ty(lhs),
Expr::ty(rhs),
)
.unwrap();
}
let lhs = self.expr(lhs, &definitions, false);
let rhs = self.expr(rhs, &definitions, false);
writeln!(
@ -1939,6 +1997,33 @@ impl<'a> Exporter<'a> {
)
.unwrap();
}
Stmt::Formal(StmtFormal {
kind,
clk,
pred,
en,
text,
source_location,
}) => {
let clk = self.expr(Expr::canonical(clk), &definitions, false);
let pred = self.expr(Expr::canonical(pred), &definitions, false);
let en = self.expr(Expr::canonical(en), &definitions, false);
let kind = match kind {
FormalKind::Assert => "assert",
FormalKind::Assume => "assume",
FormalKind::Cover => "cover",
};
let text = EscapedString {
value: &text,
raw: false,
};
writeln!(
body,
"{indent}{kind}({clk}, {pred}, {en}, {text}){}",
FileInfo::new(source_location),
)
.unwrap();
}
Stmt::If(StmtIf {
mut cond,
mut source_location,
@ -2111,7 +2196,7 @@ impl<'a> Exporter<'a> {
} in module.module_io().iter()
{
self.targeted_annotations(module_name, vec![], annotations);
let name = self.module.ns.get(NameId(module_io.name(), 0));
let name = self.module.ns.get(module_io.name_id());
let ty = self.type_state.ty(module_io.ty());
if module_io.is_input() {
writeln!(
@ -2183,7 +2268,7 @@ impl<'a> Exporter<'a> {
}
pub trait FileBackendTrait {
type Error;
type Error: From<SimplifyEnumsError>;
type Path: AsRef<Self::Path> + fmt::Debug + ?Sized;
type PathBuf: AsRef<Self::Path> + fmt::Debug;
fn path_to_string(&mut self, path: &Self::Path) -> Result<String, Self::Error>;
@ -2258,6 +2343,7 @@ impl<T: ?Sized + FileBackendTrait> FileBackendTrait for &'_ mut T {
#[non_exhaustive]
pub struct FileBackend {
pub dir_path: PathBuf,
pub circuit_name: Option<String>,
pub top_fir_file_stem: Option<String>,
}
@ -2265,6 +2351,7 @@ impl FileBackend {
pub fn new(dir_path: impl AsRef<Path>) -> Self {
Self {
dir_path: dir_path.as_ref().to_owned(),
circuit_name: None,
top_fir_file_stem: None,
}
}
@ -2300,7 +2387,10 @@ impl FileBackendTrait for FileBackend {
circuit_name: String,
contents: String,
) -> Result<(), Self::Error> {
let top_fir_file_stem = self.top_fir_file_stem.get_or_insert(circuit_name);
let top_fir_file_stem = self
.top_fir_file_stem
.get_or_insert_with(|| circuit_name.clone());
self.circuit_name = Some(circuit_name);
let mut path = self.dir_path.join(top_fir_file_stem);
if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) {
fs::create_dir_all(parent)?;
@ -2311,15 +2401,17 @@ impl FileBackendTrait for FileBackend {
}
#[doc(hidden)]
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct TestBackendPrivate {
pub module_var_name: &'static str,
pub included_fields: &'static [&'static str],
}
impl Default for TestBackendPrivate {
fn default() -> Self {
Self {
module_var_name: "m",
included_fields: &[],
}
}
}
@ -2328,6 +2420,7 @@ impl Default for TestBackendPrivate {
pub struct TestBackend {
pub files: BTreeMap<String, String>,
pub error_after: Option<i64>,
pub options: ExportOptions,
#[doc(hidden)]
/// `#[non_exhaustive]` except allowing struct update syntax
pub __private: TestBackendPrivate,
@ -2338,7 +2431,12 @@ impl fmt::Debug for TestBackend {
let Self {
files,
error_after,
__private: TestBackendPrivate { module_var_name },
options,
__private:
TestBackendPrivate {
module_var_name,
included_fields,
},
} = self;
writeln!(
f,
@ -2346,12 +2444,44 @@ impl fmt::Debug for TestBackend {
)?;
writeln!(f, " assert_export_firrtl! {{")?;
writeln!(f, " {module_var_name} =>")?;
if *error_after != Option::default() || included_fields.contains(&"error_after") {
writeln!(f, " error_after: {error_after:?},")?;
}
if *options != ExportOptions::default() || included_fields.contains(&"options") {
struct DebugWithForceIncludeFields<'a> {
options: ExportOptions,
included_fields: &'a [&'a str],
}
impl fmt::Debug for DebugWithForceIncludeFields<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.options.debug_fmt(f, |field| {
self.included_fields.iter().any(|included_field| {
if let Some(("options", suffix)) = included_field.split_once(".") {
suffix == field
} else {
false
}
})
})
}
}
let options_str = format!(
"{:#?}",
DebugWithForceIncludeFields {
options: *options,
included_fields
}
);
let mut sep = " options: ";
for line in options_str.lines() {
write!(f, "{sep}{line}")?;
sep = "\n ";
}
writeln!(f, ",")?;
}
for (file, content) in files {
writeln!(f, " {file:?}: {:?},", DebugAsRawString(content))?;
}
if *error_after != Option::default() {
writeln!(f, " error_after: {error_after:?},")?;
}
write!(f, " }};")
}
}
@ -2367,6 +2497,12 @@ impl fmt::Display for TestBackendError {
impl Error for TestBackendError {}
impl From<SimplifyEnumsError> for TestBackendError {
fn from(value: SimplifyEnumsError) -> Self {
TestBackendError(value.to_string())
}
}
impl TestBackend {
#[track_caller]
pub fn step_error_after(&mut self, args: &dyn fmt::Debug) -> Result<(), TestBackendError> {
@ -2423,9 +2559,21 @@ impl FileBackendTrait for TestBackend {
fn export_impl(
file_backend: &mut dyn WrappedFileBackendTrait,
top_module: Interned<Module<Bundle>>,
mut top_module: Interned<Module<Bundle>>,
options: ExportOptions,
) -> Result<(), WrappedError> {
let top_module = simplify_memories(top_module);
let ExportOptions {
simplify_memories: do_simplify_memories,
simplify_enums: do_simplify_enums,
__private: _,
} = options;
if let Some(kind) = do_simplify_enums {
top_module =
simplify_enums(top_module, kind).map_err(|e| file_backend.simplify_enums_error(e))?;
}
if do_simplify_memories {
top_module = simplify_memories(top_module);
}
let indent_depth = Cell::new(0);
let mut global_ns = Namespace::default();
let circuit_name = global_ns.get(top_module.name_id());
@ -2446,20 +2594,154 @@ fn export_impl(
.run(top_module)
}
#[derive(Clone)]
struct OptionSimplifyEnumsKindValueParser;
impl OptionSimplifyEnumsKindValueParser {
const NONE_NAME: &'static str = "off";
}
impl clap::builder::TypedValueParser for OptionSimplifyEnumsKindValueParser {
type Value = Option<SimplifyEnumsKind>;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
if value == Self::NONE_NAME {
Ok(None)
} else {
Ok(Some(
value_parser!(SimplifyEnumsKind).parse_ref(cmd, arg, value)?,
))
}
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
Some(Box::new(
[Self::NONE_NAME.into()]
.into_iter()
.chain(value_parser!(SimplifyEnumsKind).possible_values()?)
.collect::<Vec<_>>()
.into_iter(),
))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExportOptionsPrivate(());
#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExportOptions {
#[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)]
pub simplify_memories: bool,
#[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")]
pub simplify_enums: std::option::Option<SimplifyEnumsKind>,
#[doc(hidden)]
#[clap(skip = ExportOptionsPrivate(()))]
/// `#[non_exhaustive]` except allowing struct update syntax
pub __private: ExportOptionsPrivate,
}
impl fmt::Debug for ExportOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug_fmt(f, |_| false)
}
}
impl ExportOptions {
fn debug_fmt(
&self,
f: &mut fmt::Formatter<'_>,
force_include_field: impl Fn(&str) -> bool,
) -> fmt::Result {
let Self {
simplify_memories,
simplify_enums,
__private: _,
} = *self;
f.write_str("ExportOptions {")?;
let mut sep = if f.alternate() { "\n " } else { " " };
let comma_sep = if f.alternate() { ",\n " } else { ", " };
let default = ExportOptions::default();
if simplify_memories != default.simplify_memories
|| force_include_field("simplify_memories")
{
write!(f, "{sep}simplify_memories: {:?}", simplify_memories)?;
sep = comma_sep;
}
if simplify_enums != default.simplify_enums || force_include_field("simplify_enums") {
write!(f, "{sep}simplify_enums: ")?;
macro_rules! debug_cases {
($($ident:ident $(($($args:tt)*))?,)*) => {
match simplify_enums {
// use more complex stringify to avoid the compiler inserting spaces
$($ident $(($($args)*))? => {
f.write_str(concat!(
stringify!($ident),
$("(",
$(stringify!($args),)*
")")?
))?;
})*
}
};
}
debug_cases! {
Some(SimplifyEnumsKind::SimplifyToEnumsWithNoBody),
Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
Some(SimplifyEnumsKind::ReplaceWithUInt),
None,
}
sep = comma_sep;
}
write!(
f,
"{sep}..ExportOptions::default(){}",
if f.alternate() { "\n}" } else { " }" }
)
}
}
impl Default for ExportOptions {
fn default() -> Self {
Self {
simplify_memories: true,
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
__private: ExportOptionsPrivate(()),
}
}
}
pub fn export<T: BundleType, B: FileBackendTrait>(
file_backend: B,
top_module: &Module<T>,
options: ExportOptions,
) -> Result<B, B::Error> {
let top_module = Intern::intern_sized(top_module.canonical());
WrappedFileBackend::with(file_backend, |file_backend| {
export_impl(file_backend, top_module)
export_impl(file_backend, top_module, options)
})
}
#[doc(hidden)]
#[track_caller]
pub fn assert_export_firrtl_impl<T: BundleType>(top_module: &Module<T>, expected: TestBackend) {
let result = export(TestBackend::default(), top_module).unwrap();
let result = export(
TestBackend {
files: BTreeMap::default(),
error_after: expected.error_after,
options: expected.options,
__private: expected.__private,
},
top_module,
expected.options,
)
.unwrap();
if result != expected {
panic!(
"assert_export_firrtl failed:\nyou can update the expected output by using:\n-------START-------\n{result:?}\n-------END-------"
@ -2476,21 +2758,69 @@ pub fn make_test_expected_files(v: &[(&str, &str)]) -> BTreeMap<String, String>
macro_rules! assert_export_firrtl {
{
$m:ident =>
$($file_name:literal: $file_contents:literal,)*
$($field:ident: $value:expr,)*
@parsed_fields($($field_strings:expr,)*)
$($file_name:literal: $file_contents:literal,)*
} => {
$crate::firrtl::assert_export_firrtl_impl(
&$m,
$crate::firrtl::TestBackend {
$($field: $value,)*
files: $crate::firrtl::make_test_expected_files(&[
$(($file_name, $file_contents),)*
]),
$($field: $value,)*
__private: $crate::firrtl::TestBackendPrivate {
module_var_name: stringify!($m),
included_fields: &[$($field_strings,)*],
},
..<$crate::firrtl::TestBackend as $crate::__std::default::Default>::default()
},
);
};
{
$m:ident =>
$($parsed_fields:ident: $parsed_field_values:expr,)*
@parsed_fields($($field_strings:expr,)*)
options: ExportOptions {
$($export_option_fields:ident: $parsed_export_option_field_values:expr,)*
..$export_option_default:expr
},
$($rest:tt)*
} => {
$crate::assert_export_firrtl!(
$m =>
$($parsed_fields: $parsed_field_values,)*
options: ExportOptions {
$($export_option_fields: $parsed_export_option_field_values,)*
..$export_option_default
},
@parsed_fields($($field_strings,)* "options", $(concat!("options.", stringify!($export_option_fields)),)*)
$($rest)*
);
};
{
$m:ident =>
$($parsed_fields:ident: $parsed_field_values:expr,)*
@parsed_fields($($field_strings:expr,)*)
$field:ident: $field_value:expr,
$($rest:tt)*
} => {
$crate::assert_export_firrtl!(
$m =>
$($parsed_fields: $parsed_field_values,)*
$field: $field_value,
@parsed_fields($($field_strings,)* stringify!($field),)
$($rest)*
);
};
{
$m:ident =>
$($rest:tt)*
} => {
$crate::assert_export_firrtl!(
$m =>
@parsed_fields()
$($rest)*
);
};
}

View file

@ -0,0 +1,247 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
int::BoolOrIntType,
intern::{Intern, Interned, Memoize},
prelude::*,
};
use std::sync::OnceLock;
#[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
);
pub trait MakeFormalExpr: Type {}
impl<T: Type> MakeFormalExpr for T {}
#[hdl]
pub fn formal_global_clock() -> Expr<Clock> {
#[hdl_module(extern)]
fn formal_global_clock() {
#[hdl]
let clk: Clock = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_global_clock.v".intern(),
text: r"module __fayalite_formal_global_clock(output clk);
(* gclk *)
reg clk;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_global_clock");
}
#[hdl]
let formal_global_clock = instance(formal_global_clock());
formal_global_clock.clk
}
#[hdl]
pub fn formal_reset() -> Expr<SyncReset> {
#[hdl_module(extern)]
fn formal_reset() {
#[hdl]
let rst: SyncReset = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_reset.v".intern(),
text: r"module __fayalite_formal_reset(output rst);
assign rst = $initstate;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_reset");
}
static MOD: OnceLock<Interned<Module<formal_reset>>> = OnceLock::new();
#[hdl]
let formal_reset = instance(*MOD.get_or_init(formal_reset));
formal_reset.rst
}
macro_rules! make_any_const_fn {
($ident:ident, $verilog_attribute:literal) => {
#[hdl]
pub fn $ident<T: BoolOrIntType>(ty: T) -> Expr<T> {
#[hdl_module(extern)]
pub(super) fn $ident<T: BoolOrIntType>(ty: T) {
#[hdl]
let out: T = m.output(ty);
let width = ty.width();
let verilog_bitslice = if width == 1 {
String::new()
} else {
format!(" [{}:0]", width - 1)
};
m.annotate_module(BlackBoxInlineAnnotation {
path: Intern::intern_owned(format!(
"fayalite_{}_{width}.v",
stringify!($ident),
)),
text: Intern::intern_owned(format!(
r"module __fayalite_{}_{width}(output{verilog_bitslice} out);
(* {} *)
reg{verilog_bitslice} out;
endmodule
",
stringify!($ident),
$verilog_attribute,
)),
});
m.verilog_name(format!("__fayalite_{}_{width}", stringify!($ident)));
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TheMemoize<T>(T);
impl<T: BoolOrIntType> Memoize for TheMemoize<T> {
type Input = ();
type InputOwned = ();
type Output = Option<Interned<Module<$ident<T>>>>;
fn inner(self, _input: &Self::Input) -> Self::Output {
if self.0.width() == 0 {
None
} else {
Some($ident(self.0))
}
}
}
let Some(module) = TheMemoize(ty).get_owned(()) else {
return 0_hdl_u0.cast_bits_to(ty);
};
#[hdl]
let $ident = instance(module);
$ident.out
}
};
}
make_any_const_fn!(any_const, "anyconst");
make_any_const_fn!(any_seq, "anyseq");
make_any_const_fn!(all_const, "allconst");
make_any_const_fn!(all_seq, "allseq");

View file

@ -18,6 +18,7 @@ use std::{
borrow::{BorrowMut, Cow},
fmt,
marker::PhantomData,
num::NonZero,
ops::{Bound, Index, Not, Range, RangeBounds, RangeInclusive},
sync::Arc,
};
@ -31,8 +32,23 @@ mod sealed {
pub const DYN_SIZE: usize = !0;
pub type DynSize = ConstUsize<DYN_SIZE>;
pub trait KnownSize: GenericConstUsize + Size<SizeType = Self> {
pub trait KnownSize:
GenericConstUsize + sealed::SizeTypeSealed + sealed::SizeSealed + Default
{
const SIZE: Self;
type ArrayMatch<Element: Type>: AsRef<[Expr<Element>]>
+ AsMut<[Expr<Element>]>
+ BorrowMut<[Expr<Element>]>
+ 'static
+ Send
+ Sync
+ Eq
+ Clone
+ std::hash::Hash
+ std::fmt::Debug
+ IntoIterator<Item = Expr<Element>>
+ TryFrom<Vec<Expr<Element>>>
+ Into<Vec<Expr<Element>>>;
}
macro_rules! known_widths {
@ -43,6 +59,7 @@ macro_rules! known_widths {
v
}> {
const SIZE: Self = Self;
type ArrayMatch<Element: Type> = [Expr<Element>; Self::VALUE];
}
};
([2 $($rest:tt)*] $($bits:literal)+) => {
@ -54,6 +71,7 @@ macro_rules! known_widths {
known_widths!([$($rest)*] 1);
impl KnownSize for ConstUsize<{2 $(* $rest)*}> {
const SIZE: Self = Self;
type ArrayMatch<Element: Type> = [Expr<Element>; Self::VALUE];
}
};
}
@ -123,30 +141,24 @@ impl<const VALUE: usize> sealed::SizeSealed for ConstUsize<VALUE> {}
impl<const VALUE: usize> sealed::SizeTypeSealed for ConstUsize<VALUE> {}
impl<const VALUE: usize> SizeType for ConstUsize<VALUE>
where
ConstUsize<VALUE>: KnownSize,
{
type Size = ConstUsize<VALUE>;
impl<T: KnownSize> SizeType for T {
type Size = T;
}
impl<const VALUE: usize> Size for ConstUsize<VALUE>
where
ConstUsize<VALUE>: KnownSize,
{
type ArrayMatch<Element: Type> = [Expr<Element>; VALUE];
impl<T: KnownSize> Size for T {
type ArrayMatch<Element: Type> = <T as KnownSize>::ArrayMatch<Element>;
const KNOWN_VALUE: Option<usize> = Some(VALUE);
const KNOWN_VALUE: Option<usize> = Some(T::VALUE);
type SizeType = ConstUsize<VALUE>;
type SizeType = T;
fn as_usize(_size_type: Self::SizeType) -> usize {
VALUE
T::VALUE
}
fn try_from_usize(v: usize) -> Option<Self::SizeType> {
if v == VALUE {
Some(Self::SizeType::default())
if v == T::VALUE {
Some(T::SIZE)
} else {
None
}
@ -251,9 +263,7 @@ macro_rules! impl_int {
impl<Width: KnownSize> $name<Width> {
pub fn new_static() -> Self {
Self {
width: Width::SizeType::default(),
}
Self { width: Width::SIZE }
}
}
@ -300,7 +310,7 @@ macro_rules! impl_int {
type Output = $name<Width::Size>;
fn index(&self, width: Width) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized($name::new(width)))
Interned::into_inner(Intern::intern_sized($name::new(width)))
}
}
@ -468,7 +478,11 @@ impl SInt {
}
macro_rules! impl_prim_int {
($prim_int:ident, $ty:ty) => {
(
$(#[$meta:meta])*
$prim_int:ident, $ty:ty
) => {
$(#[$meta])*
impl ToExpr for $prim_int {
type Type = $ty;
@ -479,6 +493,17 @@ macro_rules! impl_prim_int {
)
}
}
$(#[$meta])*
impl ToExpr for NonZero<$prim_int> {
type Type = $ty;
fn to_expr(&self) -> Expr<Self::Type> {
<$ty>::le_bytes_to_expr_wrapping(
&self.get().to_le_bytes(),
<$ty as BoolOrIntType>::Width::VALUE,
)
}
}
};
}
@ -493,6 +518,16 @@ impl_prim_int!(i32, SInt<32>);
impl_prim_int!(i64, SInt<64>);
impl_prim_int!(i128, SInt<128>);
impl_prim_int!(
/// for portability reasons, [`usize`] always translates to [`UInt<64>`]
usize, UInt<64>
);
impl_prim_int!(
/// for portability reasons, [`isize`] always translates to [`SInt<64>`]
isize, SInt<64>
);
pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
type Width: Size;
type Signed: GenericConstBool;
@ -500,9 +535,9 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
fn new(width: <Self::Width as Size>::SizeType) -> Self;
fn new_static() -> Self
where
Self::Width: KnownSize,
Self::Width: KnownSize + Size<SizeType = Self::Width>,
{
Self::new(<Self::Width as Size>::SizeType::default())
Self::new(Self::Width::default())
}
fn as_same_width_sint(self) -> SIntType<Self::Width> {
SIntType::new(Self::Width::from_usize(self.width()))
@ -534,7 +569,7 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self>;
fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr<Self> {
let bitslice = BitSlice::<u8, Lsb0>::from_slice(&bytes);
let bitslice = BitSlice::<u8, Lsb0>::from_slice(bytes);
let bitslice = &bitslice[..bit_width.min(bitslice.len())];
let mut bits = BitVec::new();
bits.extend_from_bitslice(bitslice);
@ -653,15 +688,6 @@ impl StaticType for Bool {
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
pub trait IntCmp<Rhs> {
fn cmp_eq(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ne(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_lt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_le(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_gt(self, rhs: Rhs) -> Expr<Bool>;
fn cmp_ge(self, rhs: Rhs) -> Expr<Bool>;
}
impl ToLiteralBits for bool {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Ok(interned_bit(*self))

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,6 @@ pub struct __;
#[cfg(feature = "unstable-doc")]
pub mod _docs;
// FIXME: finish
pub mod annotations;
pub mod array;
pub mod bundle;
@ -39,15 +38,16 @@ pub mod clock;
pub mod enum_;
pub mod expr;
pub mod firrtl;
pub mod formal;
pub mod int;
pub mod intern;
pub mod memory;
pub mod module;
pub mod prelude;
pub mod reg;
pub mod reset;
pub mod source_location;
pub mod testing;
pub mod ty;
pub mod util;
//pub mod valueless;
pub mod prelude;
pub mod wire;

View file

@ -7,7 +7,7 @@ use crate::{
array::{Array, ArrayType},
bundle::{Bundle, BundleType},
clock::Clock,
expr::{Expr, Flow, ToExpr, ToLiteralBits},
expr::{ops::BundleLiteral, repeat, Expr, Flow, ToExpr, ToLiteralBits},
hdl,
int::{Bool, DynSize, Size, UInt, UIntType},
intern::{Intern, Interned},
@ -634,7 +634,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
self.0.source_location
}
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>> {
self.0.initial_value
@ -987,7 +987,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
#[allow(clippy::result_unit_err)]
pub fn get_array_type(&self) -> Result<ArrayType<Element, Len>, ()> {
Ok(ArrayType::new(
self.mem_element_type.clone(),
self.mem_element_type,
Len::from_usize(self.get_depth()?),
))
}
@ -1050,3 +1050,32 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
.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(_) => 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(),
)),
}
}

View file

@ -5,7 +5,7 @@ use crate::{
annotations::{Annotation, IntoAnnotations, TargetedAnnotation},
array::ArrayType,
bundle::{Bundle, BundleField, BundleType},
clock::ClockDomain,
clock::{Clock, ClockDomain},
enum_::{Enum, EnumMatchVariantsIter, EnumType},
expr::{
ops::VariantAccess,
@ -15,6 +15,7 @@ use crate::{
},
Expr, Flow, ToExpr,
},
formal::FormalKind,
int::{Bool, DynSize, Size},
intern::{Intern, Interned},
memory::{Mem, MemBuilder, MemBuilderTarget, PortName},
@ -22,7 +23,7 @@ use crate::{
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::ScopedRef,
wire::Wire,
wire::{IncompleteWire, Wire},
};
use hashbrown::{hash_map::Entry, HashMap, HashSet};
use num_bigint::BigInt;
@ -35,9 +36,10 @@ use std::{
iter::FusedIterator,
marker::PhantomData,
mem,
num::NonZeroU64,
ops::Deref,
rc::Rc,
sync::Mutex,
sync::atomic::AtomicU64,
};
pub mod transform;
@ -118,9 +120,35 @@ pub trait BlockRef: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug {}
impl BlockRef for BlockId {}
pub(crate) enum IncompleteDeclaration {
Incomplete {
name: ScopedNameId,
source_location: SourceLocation,
},
Complete(StmtDeclaration<ModuleBuilding>),
Taken,
}
impl fmt::Debug for IncompleteDeclaration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Incomplete {
name,
source_location: _,
} => f
.debug_struct("Incomplete")
.field("name", name)
.finish_non_exhaustive(),
Self::Complete(v) => v.fmt(f),
Self::Taken => f.write_str("Taken"),
}
}
}
#[derive(Debug)]
pub struct BuilderBlock {
memories: Vec<Rc<RefCell<MemBuilderTarget>>>,
incomplete_declarations: Vec<Rc<RefCell<IncompleteDeclaration>>>,
stmts: Vec<Stmt<ModuleBuilding>>,
}
@ -159,6 +187,40 @@ pub struct StmtConnect {
pub source_location: SourceLocation,
}
impl StmtConnect {
#[track_caller]
fn assert_validity_with_original_types(&self, lhs_orig_ty: impl Type, rhs_orig_ty: impl Type) {
let Self {
lhs,
rhs,
source_location,
} = *self;
assert!(
Expr::ty(lhs).can_connect(Expr::ty(rhs)),
"can't connect types that are not equivalent:\nlhs type:\n{lhs_orig_ty:?}\nrhs type:\n{rhs_orig_ty:?}\nat: {source_location}",
);
assert!(
matches!(Expr::flow(lhs), Flow::Sink | Flow::Duplex),
"can't connect to source, connect lhs must have sink or duplex flow\nat: {source_location}"
);
assert!(
lhs.target().is_some(),
"can't connect to non-target\nat: {source_location}"
);
match Expr::flow(rhs) {
Flow::Source | Flow::Duplex => {}
Flow::Sink => assert!(
Expr::ty(rhs).is_passive(),
"can't connect from sink with non-passive type\nat: {source_location}"
),
}
}
#[track_caller]
fn assert_validity(&self) {
self.assert_validity_with_original_types(Expr::ty(self.lhs), Expr::ty(self.rhs));
}
}
impl fmt::Debug for StmtConnect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
@ -173,6 +235,48 @@ impl fmt::Debug for StmtConnect {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct StmtFormal {
pub kind: FormalKind,
pub clk: Expr<Clock>,
pub pred: Expr<Bool>,
pub en: Expr<Bool>,
pub text: Interned<str>,
pub source_location: SourceLocation,
}
impl fmt::Debug for StmtFormal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
kind,
clk,
pred,
en,
text,
source_location: _,
} = self;
f.debug_struct(kind.as_str())
.field("clk", clk)
.field("pred", pred)
.field("en", en)
.field("text", text)
.finish_non_exhaustive()
}
}
#[track_caller]
pub(crate) fn add_stmt_formal(formal: StmtFormal) {
ModuleBuilder::with(|m| {
m.impl_
.borrow_mut()
.body
.builder_normal_body()
.block(m.block_stack.top())
.stmts
.push(formal.into());
});
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct StmtIf<S: ModuleBuildingStatus = ModuleBuilt> {
pub cond: Expr<Bool>,
@ -211,6 +315,13 @@ pub struct StmtMatch<S: ModuleBuildingStatus = ModuleBuilt> {
pub blocks: Interned<[S::Block]>,
}
impl StmtMatch {
#[track_caller]
fn assert_validity(&self) {
assert_eq!(Expr::ty(self.expr).variants().len(), self.blocks.len());
}
}
impl<S: ModuleBuildingStatus> fmt::Debug for StmtMatch<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
@ -424,6 +535,8 @@ wrapper_enum! {
pub enum Stmt<S: ModuleBuildingStatus = ModuleBuilt> {
#[is = is_connect, as_ref = connect]
Connect(StmtConnect),
#[is = is_formal, as_ref = formal]
Formal(StmtFormal),
#[is = is_if, as_ref = if_]
If(StmtIf<S>),
#[is = is_match, as_ref = match_]
@ -436,7 +549,7 @@ wrapper_enum! {
impl<S: ModuleBuildingStatus> Stmt<S> {
pub fn sub_stmt_blocks(&self) -> &[S::Block] {
match self {
Stmt::Connect(_) => &[],
Stmt::Connect(_) | Stmt::Formal(_) => &[],
Stmt::If(v) => &v.blocks,
Stmt::Match(v) => &v.blocks,
Stmt::Declaration(v) => v.sub_stmt_blocks(),
@ -490,8 +603,50 @@ impl BlockStack {
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Id(NonZeroU64);
impl Id {
// returns a different value each time, so there isn't really a default value
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
Self(
NonZeroU64::new(NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
.expect("Id::new ran out of ids"),
)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct NameId(pub Interned<str>, pub usize);
pub struct NameOptId(pub Interned<str>, pub Option<Id>);
impl fmt::Debug for NameOptId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for NameOptId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<Interned<str>> for NameOptId {
fn from(name: Interned<str>) -> Self {
Self(name, None)
}
}
impl From<NameId> for NameOptId {
fn from(name_id: NameId) -> Self {
Self(name_id.0, Some(name_id.1))
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct NameId(pub Interned<str>, pub Id);
impl fmt::Debug for NameId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -501,13 +656,7 @@ impl fmt::Debug for NameId {
impl fmt::Display for NameId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
write!(f, "{}", self.1)
} else if self.1 == 0 {
f.write_str(&self.0)
} else {
write!(f, "{}_{}", self.0, self.1)
}
}
}
@ -536,98 +685,6 @@ impl fmt::Debug for TargetName {
}
}
#[derive(Default)]
pub struct NameIdGen(HashMap<Interned<str>, usize>);
impl NameIdGen {
pub fn gen(&mut self, name: Interned<str>) -> NameId {
let next_id = self.0.entry(name).or_default();
let id = *next_id;
*next_id += 1;
NameId(name, id)
}
pub fn mark_as_used(&mut self, name_id: NameId) {
let next_id = self.0.entry(name_id.0).or_default();
*next_id = (*next_id).max(name_id.1 + 1);
}
pub fn for_module(module: Module<Bundle>) -> Self {
let mut retval = Self::default();
let Module {
name: _,
source_location: _,
body,
io_ty: _,
module_io,
module_annotations: _,
} = module;
for module_io in &module_io {
retval.mark_as_used(module_io.module_io.name_id());
}
match body {
ModuleBody::Extern(ExternModuleBody {
verilog_name: _,
parameters: _,
}) => {}
ModuleBody::Normal(NormalModuleBody { body }) => {
let mut blocks = vec![body];
while let Some(block) = blocks.pop() {
let Block { memories, stmts } = block;
for memory in memories {
retval.mark_as_used(memory.scoped_name().1);
}
for stmt in &stmts {
blocks.extend_from_slice(stmt.sub_stmt_blocks());
match stmt {
Stmt::Connect(StmtConnect {
lhs: _,
rhs: _,
source_location: _,
})
| Stmt::If(StmtIf {
cond: _,
source_location: _,
blocks: _,
})
| Stmt::Match(StmtMatch {
expr: _,
source_location: _,
blocks: _,
}) => {}
Stmt::Declaration(StmtDeclaration::Instance(StmtInstance {
annotations: _,
instance,
})) => {
retval.mark_as_used(instance.name_id());
}
Stmt::Declaration(StmtDeclaration::Reg(StmtReg {
annotations: _,
reg,
})) => {
retval.mark_as_used(reg.name_id());
}
Stmt::Declaration(StmtDeclaration::Wire(StmtWire {
annotations: _,
wire,
})) => {
retval.mark_as_used(wire.name_id());
}
}
}
}
}
}
retval
}
pub fn gen_module_name(name: Interned<str>) -> NameId {
static MODULE_NAME_GEN: Mutex<Option<NameIdGen>> = Mutex::new(None);
MODULE_NAME_GEN
.lock()
.unwrap()
.get_or_insert_with(NameIdGen::default)
.gen(name)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Instance<T: BundleType> {
scoped_name: ScopedNameId,
@ -716,7 +773,6 @@ struct ModuleBuilderImpl {
body: ModuleBodyBuilding,
io: Vec<AnnotatedModuleIO<ModuleBuilding>>,
io_indexes: HashMap<ModuleIO<CanonicalType>, usize>,
name_id_gen: NameIdGen,
module_annotations: Vec<Annotation>,
}
@ -831,16 +887,38 @@ impl From<NormalModuleBody<ModuleBuilding>> for NormalModuleBody {
annotations_map: &mut HashMap<StmtDeclaration<ModuleBuilding>, Vec<TargetedAnnotation>>,
block_id: BlockId,
) -> Block {
let BuilderBlock { memories, stmts } = &mut blocks[block_id.as_usize()];
let BuilderBlock {
memories,
incomplete_declarations,
stmts,
} = &mut blocks[block_id.as_usize()];
let memories = Interned::from_iter(
memories
.drain(..)
.filter_map(|memory| memory.borrow().make_memory()),
);
let stmts = std::mem::take(stmts);
let stmts = Vec::from_iter(
incomplete_declarations
.drain(..)
.map(|decl| {
match std::mem::replace(
&mut *decl.borrow_mut(),
IncompleteDeclaration::Taken,
) {
IncompleteDeclaration::Incomplete {
name,
source_location,
} => panic!("incomplete declaration: {name:?}\nat: {source_location}"),
IncompleteDeclaration::Complete(v) => Stmt::Declaration(v),
IncompleteDeclaration::Taken => unreachable!(),
}
})
.chain(stmts.drain(..)),
);
let stmts = Interned::from_iter(stmts.into_iter().map(|stmt| {
match stmt {
Stmt::Connect(stmt) => stmt.into(),
Stmt::Formal(stmt) => stmt.into(),
Stmt::If(StmtIf {
cond,
source_location,
@ -869,7 +947,7 @@ impl From<NormalModuleBody<ModuleBuilding>> for NormalModuleBody {
Stmt::Declaration(decl) => {
let annotations = annotations_map
.remove(&decl)
.map(|v| Intern::intern_owned(v))
.map(Intern::intern_owned)
.unwrap_or_default();
match decl {
StmtDeclaration::Wire(StmtWire {
@ -908,6 +986,7 @@ impl NormalModuleBody<ModuleBuilding> {
let index = self.body.blocks.len();
self.body.blocks.push(BuilderBlock {
memories: vec![],
incomplete_declarations: vec![],
stmts: vec![],
});
BlockId(index)
@ -1538,10 +1617,7 @@ impl AssertValidityState {
let module = self.module;
if block == 0 {
for module_io in &*module.module_io {
self.insert_new_base(
TargetBase::intern_sized(module_io.module_io.clone().into()),
block,
);
self.insert_new_base(TargetBase::intern_sized(module_io.module_io.into()), block);
}
}
let Block { memories, stmts } = self.blocks[block];
@ -1552,19 +1628,23 @@ impl AssertValidityState {
}
for stmt in stmts {
match stmt {
Stmt::Connect(StmtConnect {
Stmt::Connect(connect) => {
connect.assert_validity();
let StmtConnect {
lhs,
rhs,
source_location,
}) => {
} = connect;
self.set_connect_side_written(lhs, source_location, true, block);
self.set_connect_side_written(rhs, source_location, false, block);
}
Stmt::Formal(_) => {}
Stmt::If(if_stmt) => {
let sub_blocks = if_stmt.blocks.map(|block| self.make_block_index(block));
self.process_conditional_sub_blocks(block, sub_blocks)
}
Stmt::Match(match_stmt) => {
match_stmt.assert_validity();
let sub_blocks = Vec::from_iter(
match_stmt
.blocks
@ -1794,9 +1874,7 @@ impl<T: Type> RegBuilder<Expr<ClockDomain>, Option<Expr<T>>, T> {
ty,
} = self;
ModuleBuilder::with(|module_builder| {
let mut impl_ = module_builder.impl_.borrow_mut();
let scoped_name = ScopedNameId(module_builder.name, impl_.name_id_gen.gen(name));
drop(impl_);
let scoped_name = ScopedNameId(module_builder.name, NameId(name, Id::new()));
let reg = Reg::new_unchecked(scoped_name, source_location, ty, clock_domain, init);
let retval = reg.to_expr();
// convert before borrow_mut since ModuleBuilder could be reentered by T::canonical()
@ -1844,13 +1922,18 @@ impl ModuleBuilder {
is_input: bool,
ty: IO,
) -> Expr<IO> {
let module_io =
ModuleIO::<IO>::new_unchecked(self.name, name.intern(), source_location, is_input, ty);
let module_io = ModuleIO::<IO>::new_unchecked(
self.name,
NameId(name.intern(), Id::new()),
source_location,
is_input,
ty,
);
let retval = module_io.to_expr();
let module_io = module_io.canonical();
let mut impl_ = self.impl_.borrow_mut();
let impl_ = &mut *impl_;
impl_.io_indexes.insert(module_io.clone(), impl_.io.len());
impl_.io_indexes.insert(module_io, impl_.io.len());
impl_.io.push(AnnotatedModuleIO {
annotations: vec![],
module_io,
@ -1933,7 +2016,7 @@ impl ModuleBuilder {
module_kind: ModuleKind,
f: impl FnOnce(&Self) -> Result<(), E>,
) -> Result<Interned<Module<T>>, E> {
let name = NameIdGen::gen_module_name(name.intern());
let name = NameId(name.intern(), Id::new());
let body = match module_kind {
ModuleKind::Extern => ModuleBody::Extern(ExternModuleBody {
verilog_name: name.0,
@ -1943,6 +2026,7 @@ impl ModuleBuilder {
body: BuilderModuleBody {
blocks: vec![BuilderBlock {
memories: vec![],
incomplete_declarations: vec![],
stmts: vec![],
}],
annotations_map: HashMap::new(),
@ -1957,7 +2041,6 @@ impl ModuleBuilder {
body,
io: vec![],
io_indexes: HashMap::new(),
name_id_gen: NameIdGen::default(),
module_annotations: vec![],
}),
};
@ -2121,16 +2204,15 @@ pub fn annotate<T: Type>(target: Expr<T>, annotations: impl IntoAnnotations) {
.body
.annotations_map
.entry(decl)
.or_default();
.or_default()
.extend(annotations);
});
}
#[track_caller]
pub fn wire_with_loc<T: Type>(name: &str, source_location: SourceLocation, ty: T) -> Expr<T> {
ModuleBuilder::with(|m| {
let mut impl_ = m.impl_.borrow_mut();
let scoped_name = ScopedNameId(m.name, impl_.name_id_gen.gen(name.intern()));
drop(impl_);
let scoped_name = ScopedNameId(m.name, NameId(name.intern(), Id::new()));
let wire = Wire::<T>::new_unchecked(scoped_name, source_location, ty);
let retval = wire.to_expr();
let canonical_wire = wire.canonical();
@ -2156,6 +2238,40 @@ pub fn wire<T: Type>(implicit_name: ImplicitName<'_>, ty: T) -> Expr<T> {
wire_with_loc(implicit_name.0, SourceLocation::caller(), ty)
}
#[track_caller]
fn incomplete_declaration(
name: &str,
source_location: SourceLocation,
) -> Rc<RefCell<IncompleteDeclaration>> {
ModuleBuilder::with(|m| {
let scoped_name = ScopedNameId(m.name, NameId(name.intern(), Id::new()));
let retval = Rc::new(RefCell::new(IncompleteDeclaration::Incomplete {
name: scoped_name,
source_location,
}));
let mut impl_ = m.impl_.borrow_mut();
impl_
.body
.builder_normal_body()
.block(m.block_stack.top())
.incomplete_declarations
.push(retval.clone());
retval
})
}
#[track_caller]
pub fn incomplete_wire_with_loc(name: &str, source_location: SourceLocation) -> IncompleteWire {
IncompleteWire {
declaration: incomplete_declaration(name, source_location),
}
}
#[track_caller]
pub fn incomplete_wire(implicit_name: ImplicitName<'_>) -> IncompleteWire {
incomplete_wire_with_loc(implicit_name.0, SourceLocation::caller())
}
#[track_caller]
pub fn reg_builder_with_loc(name: &str, source_location: SourceLocation) -> RegBuilder<(), (), ()> {
ModuleBuilder::with(|m| {
@ -2261,24 +2377,12 @@ pub fn connect_any_with_loc<Lhs: ToExpr, Rhs: ToExpr>(
let rhs_orig = rhs.to_expr();
let lhs = Expr::canonical(lhs_orig);
let rhs = Expr::canonical(rhs_orig);
assert!(
Expr::ty(lhs).can_connect(Expr::ty(rhs)),
"can't connect types that are not equivalent:\nlhs type:\n{:?}\nrhs type:\n{:?}",
Expr::ty(lhs_orig),
Expr::ty(rhs_orig)
);
assert!(
matches!(Expr::flow(lhs), Flow::Sink | Flow::Duplex),
"can't connect to source, connect lhs must have sink or duplex flow"
);
assert!(lhs.target().is_some(), "can't connect to non-target");
match Expr::flow(rhs) {
Flow::Source | Flow::Duplex => {}
Flow::Sink => assert!(
Expr::ty(rhs).is_passive(),
"can't connect from sink with non-passive type"
),
}
let connect = StmtConnect {
lhs,
rhs,
source_location,
};
connect.assert_validity_with_original_types(Expr::ty(lhs_orig), Expr::ty(rhs_orig));
ModuleBuilder::with(|m| {
m.impl_
.borrow_mut()
@ -2286,14 +2390,7 @@ pub fn connect_any_with_loc<Lhs: ToExpr, Rhs: ToExpr>(
.builder_normal_body()
.block(m.block_stack.top())
.stmts
.push(
StmtConnect {
lhs,
rhs,
source_location,
}
.into(),
);
.push(connect.into());
});
}
@ -2323,9 +2420,7 @@ pub fn instance_with_loc<T: BundleType>(
source_location: SourceLocation,
) -> Expr<T> {
ModuleBuilder::with(|m| {
let mut impl_ = m.impl_.borrow_mut();
let scoped_name = ScopedNameId(m.name, impl_.name_id_gen.gen(name.intern()));
drop(impl_);
let scoped_name = ScopedNameId(m.name, NameId(name.intern(), Id::new()));
let instance = Instance::<T> {
scoped_name,
instantiated,
@ -2364,9 +2459,7 @@ fn memory_impl<Element: Type, Len: Size>(
source_location: SourceLocation,
) -> MemBuilder<Element, Len> {
ModuleBuilder::with(|m| {
let mut impl_ = m.impl_.borrow_mut();
let scoped_name = ScopedNameId(m.name, impl_.name_id_gen.gen(name.intern()));
drop(impl_);
let scoped_name = ScopedNameId(m.name, NameId(name.intern(), Id::new()));
let (retval, target_mem) = MemBuilder::new(scoped_name, source_location, mem_element_type);
let mut impl_ = m.impl_.borrow_mut();
let body = impl_.body.builder_normal_body();
@ -2384,7 +2477,7 @@ pub fn memory_array_with_loc<Element: Type, Len: Size>(
mem_array_type: ArrayType<Element, Len>,
source_location: SourceLocation,
) -> MemBuilder<Element, Len> {
let mut retval = memory_impl(name, mem_array_type.element().clone(), source_location);
let mut retval = memory_impl(name, mem_array_type.element(), source_location);
retval.depth(mem_array_type.len());
retval
}
@ -2442,6 +2535,7 @@ pub fn memory<Element: Type>(
pub struct ModuleIO<T: Type> {
containing_module_name: NameId,
bundle_field: BundleField,
id: Id,
ty: T,
source_location: SourceLocation,
}
@ -2461,12 +2555,14 @@ impl<T: Type> ModuleIO<T> {
let Self {
containing_module_name,
bundle_field,
id,
ty: _,
source_location,
} = *self;
ModuleIO {
containing_module_name,
bundle_field,
id,
ty: bundle_field.ty,
source_location,
}
@ -2490,7 +2586,7 @@ impl<T: Type> ModuleIO<T> {
self.bundle_field.name
}
pub fn name_id(&self) -> NameId {
NameId(self.bundle_field.name, 0)
NameId(self.bundle_field.name, self.id)
}
pub fn scoped_name(&self) -> ScopedNameId {
ScopedNameId(self.containing_module_name, self.name_id())
@ -2500,7 +2596,7 @@ impl<T: Type> ModuleIO<T> {
}
pub fn new_unchecked(
containing_module_name: NameId,
name: Interned<str>,
name: NameId,
source_location: SourceLocation,
is_input: bool,
ty: T,
@ -2508,10 +2604,11 @@ impl<T: Type> ModuleIO<T> {
Self {
containing_module_name,
bundle_field: BundleField {
name,
name: name.0,
flipped: is_input,
ty: ty.canonical(),
},
id: name.1,
ty,
source_location,
}

View file

@ -2,19 +2,19 @@
// See Notices.txt for copyright information
use crate::{
array::{Array, ArrayType},
bundle::{Bundle, BundleType},
bundle::{Bundle, BundleField, BundleType},
enum_::{Enum, EnumType, EnumVariant},
expr::{
ops::{self, EnumLiteral},
CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr,
CastBitsTo, CastTo, CastToBits, Expr, ExprEnum, HdlPartialEq, ToExpr,
},
hdl,
int::{DynSize, IntCmp, Size, UInt, UIntType},
intern::{Intern, Interned},
int::UInt,
intern::{Intern, Interned, Memoize},
memory::{DynPortType, Mem, MemPort},
module::{
transform::visit::{Fold, Folder},
Block, Module, NameIdGen, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire,
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire,
},
source_location::SourceLocation,
ty::{CanonicalType, Type},
@ -41,25 +41,70 @@ impl fmt::Display for SimplifyEnumsError {
impl std::error::Error for SimplifyEnumsError {}
impl From<SimplifyEnumsError> for std::io::Error {
fn from(value: SimplifyEnumsError) -> Self {
std::io::Error::new(std::io::ErrorKind::Other, value)
}
}
fn contains_any_enum_types(ty: CanonicalType) -> bool {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TheMemoize;
impl Memoize for TheMemoize {
type Input = CanonicalType;
type InputOwned = CanonicalType;
type Output = bool;
fn inner(self, ty: &Self::Input) -> Self::Output {
match *ty {
CanonicalType::Array(array_type) => contains_any_enum_types(array_type.element()),
CanonicalType::Enum(_) => true,
CanonicalType::Bundle(bundle) => bundle
.fields()
.iter()
.any(|field| contains_any_enum_types(field.ty)),
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_) => false,
}
}
}
TheMemoize.get_owned(ty)
}
#[hdl]
struct TagAndBody<T, BodyWidth: Size> {
tag: T,
body: UIntType<BodyWidth>,
struct TagAndBody<Tag, Body> {
tag: Tag,
body: Body,
}
#[derive(Clone, Debug)]
enum EnumTypeState {
TagEnumAndBody(TagAndBody<Enum, DynSize>),
TagUIntAndBody(TagAndBody<UInt, DynSize>),
TagEnumAndBody(TagAndBody<Enum, UInt>),
TagUIntAndBody(TagAndBody<UInt, UInt>),
UInt(UInt),
Unchanged,
}
struct ModuleState {
module_name: NameId,
}
impl ModuleState {
fn gen_name(&mut self, name: &str) -> ScopedNameId {
ScopedNameId(self.module_name, NameId(name.intern(), Id::new()))
}
}
struct State {
enum_types: HashMap<Enum, EnumTypeState>,
replacement_mem_ports: HashMap<MemPort<DynPortType>, Wire<CanonicalType>>,
kind: SimplifyEnumsKind,
name_id_gen: NameIdGen,
module_state_stack: Vec<ModuleState>,
}
impl State {
@ -107,6 +152,369 @@ impl State {
self.enum_types.insert(enum_type, retval.clone());
Ok(retval)
}
#[hdl]
fn handle_enum_literal(
&mut self,
unfolded_enum_type: Enum,
variant_index: usize,
folded_variant_value: Option<Expr<CanonicalType>>,
) -> Result<Expr<CanonicalType>, SimplifyEnumsError> {
Ok(
match self.get_or_make_enum_type_state(unfolded_enum_type)? {
EnumTypeState::TagEnumAndBody(TagAndBody { tag, body }) => Expr::canonical(
#[hdl]
TagAndBody {
tag: EnumLiteral::new_by_index(tag, variant_index, None),
body: match folded_variant_value {
Some(variant_value) => variant_value.cast_to_bits().cast_to(body),
None => body.zero().to_expr(),
},
},
),
EnumTypeState::TagUIntAndBody(TagAndBody { tag, body }) => Expr::canonical(
#[hdl]
TagAndBody {
tag: tag.from_int_wrapping(variant_index),
body: match folded_variant_value {
Some(folded_variant_value) => {
folded_variant_value.cast_to_bits().cast_to(body)
}
None => body.zero().to_expr(),
},
},
),
EnumTypeState::UInt(_) => {
let tag = UInt[unfolded_enum_type.discriminant_bit_width()];
let body = UInt[unfolded_enum_type.type_properties().bit_width - tag.width()];
Expr::canonical(
(#[hdl]
TagAndBody {
tag: tag.from_int_wrapping(variant_index),
body: match folded_variant_value {
Some(folded_variant_value) => {
folded_variant_value.cast_to_bits().cast_to(body)
}
None => body.zero().to_expr(),
},
})
.cast_to_bits(),
)
}
EnumTypeState::Unchanged => Expr::canonical(
ops::EnumLiteral::new_by_index(
unfolded_enum_type,
variant_index,
folded_variant_value,
)
.to_expr(),
),
},
)
}
fn handle_variant_access(
&mut self,
unfolded_enum_type: Enum,
folded_base_expr: Expr<CanonicalType>,
variant_index: usize,
) -> Result<Expr<CanonicalType>, SimplifyEnumsError> {
let unfolded_variant_type = unfolded_enum_type.variants()[variant_index].ty;
Ok(
match self.get_or_make_enum_type_state(unfolded_enum_type)? {
EnumTypeState::TagEnumAndBody(_) | EnumTypeState::TagUIntAndBody(_) => {
match unfolded_variant_type {
Some(variant_type) => Expr::canonical(
Expr::<TagAndBody<CanonicalType, UInt>>::from_canonical(
folded_base_expr,
)
.body[..variant_type.bit_width()]
.cast_bits_to(variant_type.fold(self)?),
),
None => Expr::canonical(().to_expr()),
}
}
EnumTypeState::UInt(_) => match unfolded_variant_type {
Some(variant_type) => {
let base_int = Expr::<UInt>::from_canonical(folded_base_expr);
let variant_type_bit_width = variant_type.bit_width();
Expr::canonical(
base_int[unfolded_enum_type.discriminant_bit_width()..]
[..variant_type_bit_width]
.cast_bits_to(variant_type.fold(self)?),
)
}
None => Expr::canonical(().to_expr()),
},
EnumTypeState::Unchanged => match unfolded_variant_type {
Some(_) => ops::VariantAccess::new_by_index(
Expr::from_canonical(folded_base_expr),
variant_index,
)
.to_expr(),
None => Expr::canonical(().to_expr()),
},
},
)
}
fn handle_match(
&mut self,
unfolded_enum_type: Enum,
folded_expr: Expr<CanonicalType>,
source_location: SourceLocation,
folded_blocks: &[Block],
) -> Result<Stmt, SimplifyEnumsError> {
match self.get_or_make_enum_type_state(unfolded_enum_type)? {
EnumTypeState::TagEnumAndBody(_) => Ok(StmtMatch {
expr: Expr::<TagAndBody<Enum, UInt>>::from_canonical(folded_expr).tag,
source_location,
blocks: folded_blocks.intern(),
}
.into()),
EnumTypeState::TagUIntAndBody(_) => {
let int_tag_expr = Expr::<TagAndBody<UInt, UInt>>::from_canonical(folded_expr).tag;
Ok(match_int_tag(int_tag_expr, source_location, folded_blocks).into())
}
EnumTypeState::UInt(_) => {
let int_tag_expr = Expr::<UInt>::from_canonical(folded_expr)
[..unfolded_enum_type.discriminant_bit_width()];
Ok(match_int_tag(int_tag_expr, source_location, folded_blocks).into())
}
EnumTypeState::Unchanged => Ok(StmtMatch {
expr: Expr::from_canonical(folded_expr),
source_location,
blocks: folded_blocks.intern(),
}
.into()),
}
}
fn handle_stmt_connect_array(
&mut self,
unfolded_lhs_ty: Array,
unfolded_rhs_ty: Array,
folded_lhs: Expr<Array>,
folded_rhs: Expr<Array>,
source_location: SourceLocation,
output_stmts: &mut Vec<Stmt>,
) -> Result<(), SimplifyEnumsError> {
assert_eq!(unfolded_lhs_ty.len(), unfolded_rhs_ty.len());
let unfolded_lhs_element_ty = unfolded_lhs_ty.element();
let unfolded_rhs_element_ty = unfolded_rhs_ty.element();
for array_index in 0..unfolded_lhs_ty.len() {
self.handle_stmt_connect(
unfolded_lhs_element_ty,
unfolded_rhs_element_ty,
folded_lhs[array_index],
folded_rhs[array_index],
source_location,
output_stmts,
)?;
}
Ok(())
}
fn handle_stmt_connect_bundle(
&mut self,
unfolded_lhs_ty: Bundle,
unfolded_rhs_ty: Bundle,
folded_lhs: Expr<Bundle>,
folded_rhs: Expr<Bundle>,
source_location: SourceLocation,
output_stmts: &mut Vec<Stmt>,
) -> Result<(), SimplifyEnumsError> {
let unfolded_lhs_fields = unfolded_lhs_ty.fields();
let unfolded_rhs_fields = unfolded_rhs_ty.fields();
assert_eq!(unfolded_lhs_fields.len(), unfolded_rhs_fields.len());
for (
field_index,
(
&BundleField {
name,
flipped,
ty: unfolded_lhs_field_ty,
},
unfolded_rhs_field,
),
) in unfolded_lhs_fields
.iter()
.zip(&unfolded_rhs_fields)
.enumerate()
{
assert_eq!(name, unfolded_rhs_field.name);
assert_eq!(flipped, unfolded_rhs_field.flipped);
let folded_lhs_field =
ops::FieldAccess::new_by_index(folded_lhs, field_index).to_expr();
let folded_rhs_field =
ops::FieldAccess::new_by_index(folded_rhs, field_index).to_expr();
if flipped {
// swap lhs/rhs
self.handle_stmt_connect(
unfolded_rhs_field.ty,
unfolded_lhs_field_ty,
folded_rhs_field,
folded_lhs_field,
source_location,
output_stmts,
)?;
} else {
self.handle_stmt_connect(
unfolded_lhs_field_ty,
unfolded_rhs_field.ty,
folded_lhs_field,
folded_rhs_field,
source_location,
output_stmts,
)?;
}
}
Ok(())
}
fn handle_stmt_connect_enum(
&mut self,
unfolded_lhs_ty: Enum,
unfolded_rhs_ty: Enum,
folded_lhs: Expr<CanonicalType>,
folded_rhs: Expr<CanonicalType>,
source_location: SourceLocation,
output_stmts: &mut Vec<Stmt>,
) -> Result<(), SimplifyEnumsError> {
let unfolded_lhs_variants = unfolded_lhs_ty.variants();
let unfolded_rhs_variants = unfolded_rhs_ty.variants();
assert_eq!(unfolded_lhs_variants.len(), unfolded_rhs_variants.len());
let mut folded_blocks = vec![];
for (
variant_index,
(
&EnumVariant {
name,
ty: unfolded_lhs_variant_ty,
},
unfolded_rhs_variant,
),
) in unfolded_lhs_variants
.iter()
.zip(&unfolded_rhs_variants)
.enumerate()
{
let mut output_stmts = vec![];
assert_eq!(name, unfolded_rhs_variant.name);
assert_eq!(
unfolded_lhs_variant_ty.is_some(),
unfolded_rhs_variant.ty.is_some()
);
let folded_variant_value =
if let (Some(unfolded_lhs_variant_ty), Some(unfolded_rhs_variant_ty)) =
(unfolded_lhs_variant_ty, unfolded_rhs_variant.ty)
{
let lhs_wire = Wire::new_unchecked(
self.module_state_stack
.last_mut()
.unwrap()
.gen_name("__connect_variant_body"),
source_location,
unfolded_lhs_variant_ty.fold(self)?,
);
output_stmts.push(
StmtWire {
annotations: Interned::default(),
wire: lhs_wire,
}
.into(),
);
let lhs_wire = lhs_wire.to_expr();
let folded_rhs_variant =
self.handle_variant_access(unfolded_rhs_ty, folded_rhs, variant_index)?;
self.handle_stmt_connect(
unfolded_lhs_variant_ty,
unfolded_rhs_variant_ty,
lhs_wire,
folded_rhs_variant,
source_location,
&mut output_stmts,
)?;
Some(lhs_wire)
} else {
None
};
output_stmts.push(
StmtConnect {
lhs: folded_lhs,
rhs: self.handle_enum_literal(
unfolded_lhs_ty,
variant_index,
folded_variant_value,
)?,
source_location,
}
.into(),
);
folded_blocks.push(Block {
memories: Interned::default(),
stmts: Intern::intern_owned(output_stmts),
});
}
output_stmts.push(self.handle_match(
unfolded_rhs_ty,
folded_rhs,
source_location,
&folded_blocks,
)?);
Ok(())
}
fn handle_stmt_connect(
&mut self,
unfolded_lhs_ty: CanonicalType,
unfolded_rhs_ty: CanonicalType,
folded_lhs: Expr<CanonicalType>,
folded_rhs: Expr<CanonicalType>,
source_location: SourceLocation,
output_stmts: &mut Vec<Stmt>,
) -> Result<(), SimplifyEnumsError> {
let needs_expansion = unfolded_lhs_ty != unfolded_rhs_ty
&& (contains_any_enum_types(unfolded_lhs_ty)
|| contains_any_enum_types(unfolded_rhs_ty));
if !needs_expansion {
output_stmts.push(
StmtConnect {
lhs: folded_lhs,
rhs: folded_rhs,
source_location,
}
.into(),
);
return Ok(());
}
match unfolded_lhs_ty {
CanonicalType::Array(unfolded_lhs_ty) => self.handle_stmt_connect_array(
unfolded_lhs_ty,
Array::from_canonical(unfolded_rhs_ty),
Expr::from_canonical(folded_lhs),
Expr::from_canonical(folded_rhs),
source_location,
output_stmts,
),
CanonicalType::Enum(unfolded_lhs_ty) => self.handle_stmt_connect_enum(
unfolded_lhs_ty,
Enum::from_canonical(unfolded_rhs_ty),
folded_lhs,
folded_rhs,
source_location,
output_stmts,
),
CanonicalType::Bundle(unfolded_lhs_ty) => self.handle_stmt_connect_bundle(
unfolded_lhs_ty,
Bundle::from_canonical(unfolded_rhs_ty),
Expr::from_canonical(folded_lhs),
Expr::from_canonical(folded_rhs),
source_location,
output_stmts,
),
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_) => unreachable!(),
}
}
}
fn connect_port(
@ -117,11 +525,11 @@ fn connect_port(
) {
if Expr::ty(lhs) == Expr::ty(rhs) {
stmts.push(
dbg!(StmtConnect {
StmtConnect {
lhs,
rhs,
source_location,
})
}
.into(),
);
return;
@ -177,6 +585,42 @@ fn connect_port(
}
}
fn match_int_tag(
int_tag_expr: Expr<UInt>,
source_location: SourceLocation,
folded_blocks: &[Block],
) -> StmtIf {
let mut blocks_iter = folded_blocks.iter().copied().enumerate();
let (_, last_block) = blocks_iter.next_back().unwrap_or_default();
let Some((next_to_last_variant_index, next_to_last_block)) = blocks_iter.next_back() else {
return StmtIf {
cond: true.to_expr(),
source_location,
blocks: [last_block, Block::default()],
};
};
let mut retval = StmtIf {
cond: int_tag_expr
.cmp_eq(Expr::ty(int_tag_expr).from_int_wrapping(next_to_last_variant_index)),
source_location,
blocks: [next_to_last_block, last_block],
};
for (variant_index, block) in blocks_iter.rev() {
retval = StmtIf {
cond: int_tag_expr.cmp_eq(Expr::ty(int_tag_expr).from_int_wrapping(variant_index)),
source_location,
blocks: [
block,
Block {
memories: Default::default(),
stmts: [Stmt::from(retval)][..].intern(),
},
],
};
}
retval
}
impl Folder for State {
type Error = SimplifyEnumsError;
@ -185,96 +629,32 @@ impl Folder for State {
}
fn fold_module<T: BundleType>(&mut self, v: Module<T>) -> Result<Module<T>, Self::Error> {
let old_name_id_gen =
std::mem::replace(&mut self.name_id_gen, NameIdGen::for_module(v.canonical()));
self.module_state_stack.push(ModuleState {
module_name: v.name_id(),
});
let retval = Fold::default_fold(v, self);
self.name_id_gen = old_name_id_gen;
self.module_state_stack.pop();
retval
}
fn fold_expr_enum(&mut self, op: ExprEnum) -> Result<ExprEnum, Self::Error> {
match op {
ExprEnum::EnumLiteral(op) => Ok(match self.get_or_make_enum_type_state(op.ty())? {
EnumTypeState::TagEnumAndBody(TagAndBody { tag, body }) => *Expr::expr_enum(
<TagAndBody<Enum, DynSize> as BundleType>::Builder::default()
.field_tag(EnumLiteral::new_by_index(tag, op.variant_index(), None))
.field_body(match op.variant_value() {
Some(variant_value) => variant_value.fold(self)?.cast_to_bits(),
None => body.zero().to_expr(),
})
.to_expr(),
),
EnumTypeState::TagUIntAndBody(TagAndBody { tag, body }) => *Expr::expr_enum(
<TagAndBody<UInt, DynSize> as BundleType>::Builder::default()
.field_tag(tag.from_int_wrapping(op.variant_index()))
.field_body(match op.variant_value() {
Some(variant_value) => variant_value.fold(self)?.cast_to_bits(),
None => body.zero().to_expr(),
})
.to_expr(),
),
EnumTypeState::UInt(_) => *Expr::expr_enum(
<TagAndBody<UInt, DynSize> as BundleType>::Builder::default()
.field_tag(
UIntType::new(op.ty().discriminant_bit_width())
.from_int_wrapping(op.variant_index()),
)
.field_body(match op.variant_value() {
Some(variant_value) => variant_value.fold(self)?.cast_to_bits(),
None => UIntType::new(
op.ty().type_properties().bit_width
- op.ty().discriminant_bit_width(),
)
.zero()
.to_expr(),
})
.cast_to_bits(),
),
EnumTypeState::Unchanged => ExprEnum::EnumLiteral(ops::EnumLiteral::new_by_index(
ExprEnum::EnumLiteral(op) => {
let folded_variant_value = op.variant_value().map(|v| v.fold(self)).transpose()?;
Ok(*Expr::expr_enum(self.handle_enum_literal(
op.ty(),
op.variant_index(),
op.variant_value().map(|v| v.fold(self)).transpose()?,
)),
}),
ExprEnum::VariantAccess(op) => Ok(
match self.get_or_make_enum_type_state(Expr::ty(op.base()))? {
EnumTypeState::TagEnumAndBody(_) | EnumTypeState::TagUIntAndBody(_) => {
match op.variant_type() {
Some(variant_type) => *Expr::expr_enum(
Expr::<TagAndBody<CanonicalType, DynSize>>::from_canonical(
(*Expr::expr_enum(op.base())).fold(self)?.to_expr(),
)
.body[..variant_type.bit_width()]
.cast_bits_to(variant_type),
),
None => *Expr::expr_enum(().to_expr()),
folded_variant_value,
)?))
}
}
EnumTypeState::UInt(_) => match op.variant_type() {
Some(variant_type) => {
let base_int = Expr::<UInt>::from_canonical(
(*Expr::expr_enum(op.base())).fold(self)?.to_expr(),
);
dbg!(base_int);
let base_ty = Expr::ty(op.base());
let variant_type_bit_width = variant_type.bit_width();
*Expr::expr_enum(
base_int[base_ty.discriminant_bit_width()..]
[..variant_type_bit_width]
.cast_bits_to(variant_type),
)
}
None => *Expr::expr_enum(().to_expr()),
},
EnumTypeState::Unchanged => match op.variant_type() {
Some(_) => ExprEnum::VariantAccess(ops::VariantAccess::new_by_index(
op.base().fold(self)?,
ExprEnum::VariantAccess(op) => {
let folded_base_expr = Expr::canonical(op.base()).fold(self)?;
Ok(*Expr::expr_enum(self.handle_variant_access(
Expr::ty(op.base()),
folded_base_expr,
op.variant_index(),
)),
None => *Expr::expr_enum(().to_expr()),
},
},
),
)?))
}
ExprEnum::MemPort(mem_port) => Ok(
if let Some(&wire) = self.replacement_mem_ports.get(&mem_port) {
ExprEnum::Wire(wire)
@ -287,6 +667,7 @@ impl Folder for State {
| ExprEnum::BoolLiteral(_)
| ExprEnum::BundleLiteral(_)
| ExprEnum::ArrayLiteral(_)
| ExprEnum::Uninit(_)
| ExprEnum::NotU(_)
| ExprEnum::NotS(_)
| ExprEnum::NotB(_)
@ -417,11 +798,15 @@ impl Folder for State {
if wire_ty == new_port_ty {
continue;
}
let wire_name = self.name_id_gen.gen(
(*format!("{}_{}", memory.scoped_name().1 .0, port.port_name())).intern(),
);
let wire = Wire::new_unchecked(
ScopedNameId(memory.scoped_name().0, wire_name),
self.module_state_stack
.last_mut()
.unwrap()
.gen_name(&format!(
"{}_{}",
memory.scoped_name().1 .0,
port.port_name()
)),
port.source_location(),
wire_ty,
);
@ -464,80 +849,50 @@ impl Folder for State {
}
fn fold_stmt(&mut self, stmt: Stmt) -> Result<Stmt, Self::Error> {
fn match_int_tag(
state: &mut State,
int_tag_expr: Expr<UInt>,
source_location: SourceLocation,
blocks: Interned<[Block]>,
) -> Result<StmtIf, SimplifyEnumsError> {
let mut blocks_iter = blocks.iter().copied().enumerate();
let (_, last_block) = blocks_iter.next_back().unwrap_or_default();
let Some((next_to_last_variant_index, next_to_last_block)) = blocks_iter.next_back()
else {
return Ok(StmtIf {
cond: true.to_expr(),
source_location,
blocks: [last_block.fold(state)?, Block::default()],
});
};
let mut retval = StmtIf {
cond: int_tag_expr
.cmp_eq(Expr::ty(int_tag_expr).from_int_wrapping(next_to_last_variant_index)),
source_location,
blocks: [next_to_last_block.fold(state)?, last_block.fold(state)?],
};
for (variant_index, block) in blocks_iter.rev() {
retval = StmtIf {
cond: int_tag_expr
.cmp_eq(Expr::ty(int_tag_expr).from_int_wrapping(variant_index)),
source_location,
blocks: [
block.fold(state)?,
Block {
memories: Default::default(),
stmts: [Stmt::from(retval)][..].intern(),
},
],
};
}
Ok(retval)
}
match stmt {
Stmt::Match(StmtMatch {
expr,
source_location,
blocks,
}) => match self.get_or_make_enum_type_state(Expr::ty(expr))? {
EnumTypeState::TagEnumAndBody(_) => Ok(StmtMatch {
expr: Expr::<TagAndBody<Enum, DynSize>>::from_canonical(
Expr::canonical(expr).fold(self)?,
)
.tag,
}) => {
let folded_expr = Expr::canonical(expr).fold(self)?;
let folded_blocks = blocks.fold(self)?;
self.handle_match(Expr::ty(expr), folded_expr, source_location, &folded_blocks)
}
Stmt::Connect(StmtConnect {
lhs,
rhs,
source_location,
blocks: blocks.fold(self)?,
}
.into()),
EnumTypeState::TagUIntAndBody(_) => {
let int_tag_expr = Expr::<TagAndBody<UInt, DynSize>>::from_canonical(
Expr::canonical(expr).fold(self)?,
)
.tag;
Ok(match_int_tag(self, int_tag_expr, source_location, blocks)?.into())
}
EnumTypeState::UInt(_) => {
let int_tag_expr =
Expr::<UInt>::from_canonical(Expr::canonical(expr).fold(self)?)
[..Expr::ty(expr).discriminant_bit_width()];
Ok(match_int_tag(self, int_tag_expr, source_location, blocks)?.into())
}
EnumTypeState::Unchanged => Ok(StmtMatch {
expr: expr.fold(self)?,
}) => {
let folded_lhs = lhs.fold(self)?;
let folded_rhs = rhs.fold(self)?;
let mut output_stmts = vec![];
self.handle_stmt_connect(
Expr::ty(lhs),
Expr::ty(rhs),
folded_lhs,
folded_rhs,
source_location,
blocks: blocks.fold(self)?,
}
.into()),
&mut output_stmts,
)?;
if output_stmts.len() == 1 {
Ok(output_stmts.pop().unwrap())
} else {
Ok(StmtIf {
cond: true.to_expr(),
source_location,
blocks: [
Block {
memories: Interned::default(),
stmts: Intern::intern_owned(output_stmts),
},
Stmt::Connect(_) | Stmt::If(_) | Stmt::Declaration(_) => stmt.default_fold(self),
Block::default(),
],
}
.into())
}
}
Stmt::Formal(_) | Stmt::If(_) | Stmt::Declaration(_) => stmt.default_fold(self),
}
}
@ -574,13 +929,10 @@ impl Folder for State {
unreachable!()
}
fn fold_enum_literal<T: EnumType>(
fn fold_enum_literal<T: EnumType + Fold<Self>>(
&mut self,
_v: ops::EnumLiteral<T>,
) -> Result<ops::EnumLiteral<T>, Self::Error>
where
T: Fold<Self>,
{
) -> Result<ops::EnumLiteral<T>, Self::Error> {
unreachable!()
}
@ -592,10 +944,12 @@ impl Folder for State {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, clap::ValueEnum)]
pub enum SimplifyEnumsKind {
SimplifyToEnumsWithNoBody,
#[clap(name = "replace-with-bundle-of-uints")]
ReplaceWithBundleOfUInts,
#[clap(name = "replace-with-uint")]
ReplaceWithUInt,
}
@ -607,6 +961,6 @@ pub fn simplify_enums(
enum_types: HashMap::new(),
replacement_mem_ports: HashMap::new(),
kind,
name_id_gen: NameIdGen::default(),
module_state_stack: vec![],
})
}

View file

@ -10,7 +10,7 @@ use crate::{
memory::{Mem, MemPort, PortType},
module::{
transform::visit::{Fold, Folder},
Block, Module, NameId, NameIdGen, ScopedNameId, Stmt, StmtConnect, StmtWire,
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtWire,
},
source_location::SourceLocation,
ty::{CanonicalType, Type},
@ -417,7 +417,6 @@ impl SplitMemState<'_, '_> {
struct ModuleState {
output_module: Option<Interned<Module<Bundle>>>,
name_id_gen: NameIdGen,
memories: HashMap<ScopedNameId, MemState>,
}
@ -569,7 +568,7 @@ impl ModuleState {
port_wmask.map(Expr::from_canonical),
connect_read_enum,
connect_write_enum,
connect_write_enum,
connect_write,
),
CanonicalType::Array(array_type) => {
input_array_types.push(array_type);
@ -626,10 +625,10 @@ impl ModuleState {
mem_name_path: &str,
split_state: &SplitState<'_>,
) -> Mem {
let mem_name = self.name_id_gen.gen(Intern::intern_owned(format!(
"{}{mem_name_path}",
input_mem.scoped_name().1 .0
)));
let mem_name = NameId(
Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1 .0)),
Id::new(),
);
let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name);
let output_element_type = match single_type {
SingleType::UInt(ty) => ty.canonical(),
@ -753,9 +752,10 @@ impl ModuleState {
let port_ty = port.ty();
let NameId(mem_name, _) = input_mem.scoped_name().1;
let port_name = port.port_name();
let wire_name = self
.name_id_gen
.gen(Intern::intern_owned(format!("{mem_name}_{port_name}")));
let wire_name = NameId(
Intern::intern_owned(format!("{mem_name}_{port_name}")),
Id::new(),
);
let wire = Wire::new_unchecked(
ScopedNameId(input_mem.scoped_name().0, wire_name),
port.source_location(),
@ -766,7 +766,7 @@ impl ModuleState {
output_stmts.push(
StmtWire {
annotations: Default::default(),
wire: canonical_wire.clone(),
wire: canonical_wire,
}
.into(),
);
@ -887,7 +887,6 @@ impl Folder for State {
module,
ModuleState {
output_module: None,
name_id_gen: NameIdGen::for_module(*module),
memories: HashMap::new(),
},
);

View file

@ -2,7 +2,10 @@
// See Notices.txt for copyright information
#![allow(clippy::multiple_bound_locations)]
use crate::{
annotations::{Annotation, CustomFirrtlAnnotation, TargetedAnnotation},
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::ArrayType,
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
@ -15,14 +18,15 @@ use crate::{
},
Expr, ExprEnum,
},
formal::FormalKind,
int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue},
intern::{Intern, Interned},
memory::{Mem, MemPort, PortKind, PortName, PortType, ReadUnderWrite},
module::{
AnnotatedModuleIO, Block, BlockId, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Instance, Module, ModuleBody, ModuleIO, NameId,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtIf, StmtInstance,
StmtMatch, StmtReg, StmtWire,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf,
StmtInstance, StmtMatch, StmtReg, StmtWire,
},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset},

View file

@ -1,16 +1,30 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub use crate::{
annotations::Annotation,
annotations::{
BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
},
array::{Array, ArrayType},
bundle::Bundle,
cli::Cli,
clock::{Clock, ClockDomain, ToClock},
enum_::{HdlNone, HdlOption, HdlSome},
expr::{CastBitsTo, CastTo, CastToBits, Expr, ReduceBits, ToExpr},
enum_::{Enum, HdlNone, HdlOption, HdlSome},
expr::{
repeat, CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr,
ReduceBits, ToExpr,
},
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, MakeFormalExpr,
},
hdl, hdl_module,
int::{Bool, DynSize, IntCmp, KnownSize, SInt, SIntType, Size, UInt, UIntType},
int::{Bool, DynSize, KnownSize, SInt, SIntType, Size, UInt, UIntType},
memory::{Mem, MemBuilder, ReadUnderWrite},
module::{
annotate, connect, connect_any, instance, memory, memory_array, memory_with_init,
reg_builder, wire, Instance, Module, ModuleBuilder,
annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array,
memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder,
},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},

View file

@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
cli::{FormalArgs, FormalMode, FormalOutput, RunPhase},
firrtl::ExportOptions,
};
use clap::Parser;
use hashbrown::HashMap;
use serde::Deserialize;
use std::{
fmt::Write,
path::{Path, PathBuf},
process::Command,
sync::{Mutex, OnceLock},
};
fn assert_formal_helper() -> FormalArgs {
static FORMAL_ARGS: OnceLock<FormalArgs> = OnceLock::new();
// ensure we only run parsing once, so errors from env vars don't produce overlapping output if we're called on multiple threads
FORMAL_ARGS
.get_or_init(|| FormalArgs::parse_from(["fayalite::testing::assert_formal"]))
.clone()
}
#[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::new)
.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)
}
#[track_caller]
pub fn assert_formal<M>(
test_name: impl std::fmt::Display,
module: M,
mode: FormalMode,
depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) where
FormalArgs: RunPhase<M, Output = FormalOutput>,
{
let mut args = assert_formal_helper();
args.verilog.firrtl.base.redirect_output_for_rust_test = true;
args.verilog.firrtl.base.output = Some(get_assert_formal_target_path(&test_name));
args.verilog.firrtl.export_options = export_options;
args.verilog.debug = true;
args.mode = mode;
args.depth = depth;
if let Some(solver) = solver {
args.solver = solver.into();
}
args.run(module).expect("testing::assert_formal() failed");
}

View file

@ -210,7 +210,9 @@ impl sealed::BaseTypeSealed for CanonicalType {}
impl BaseType for CanonicalType {}
pub trait TypeOrDefault<D: Type>: sealed::TypeOrDefaultSealed {
pub trait TypeOrDefault<D: Type>:
sealed::TypeOrDefaultSealed + Copy + Eq + Hash + fmt::Debug
{
type Type: Type;
fn get<F: FnOnce() -> D>(self, default: F) -> Self::Type;
}
@ -328,6 +330,6 @@ impl<T: Type> Index<T> for AsMaskWithoutGenerics {
type Output = T::MaskType;
fn index(&self, ty: T) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized(ty.mask_type()))
Interned::into_inner(Intern::intern_sized(ty.mask_type()))
}
}

View file

@ -6,6 +6,7 @@ mod const_cmp;
mod const_usize;
mod misc;
mod scoped_ref;
pub(crate) mod streaming_read_utf8;
#[doc(inline)]
pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool};
@ -25,3 +26,6 @@ pub use scoped_ref::ScopedRef;
pub use misc::{
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
};
pub mod job_server;
pub mod ready_valid;

View file

@ -0,0 +1,193 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use ctor::ctor;
use jobslot::{Acquired, Client};
use std::{
ffi::OsString,
mem,
num::NonZeroUsize,
sync::{Condvar, Mutex, Once, OnceLock},
thread::spawn,
};
fn get_or_make_client() -> &'static Client {
#[ctor]
static CLIENT: OnceLock<Client> = unsafe {
match Client::from_env() {
Some(client) => OnceLock::from(client),
None => OnceLock::new(),
}
};
CLIENT.get_or_init(|| {
let mut available_parallelism = None;
let mut args = std::env::args_os().skip(1);
while let Some(arg) = args.next() {
const TEST_THREADS_OPTION: &'static [u8] = b"--test-threads";
if arg.as_encoded_bytes().starts_with(TEST_THREADS_OPTION) {
match arg.as_encoded_bytes().get(TEST_THREADS_OPTION.len()) {
Some(b'=') => {
let mut arg = arg.into_encoded_bytes();
arg.drain(..=TEST_THREADS_OPTION.len());
available_parallelism = Some(arg);
break;
}
None => {
available_parallelism = args.next().map(OsString::into_encoded_bytes);
break;
}
_ => {}
}
}
}
let available_parallelism = if let Some(available_parallelism) = available_parallelism
.as_deref()
.and_then(|v| std::str::from_utf8(v).ok())
.and_then(|v| v.parse().ok())
{
available_parallelism
} else if let Ok(available_parallelism) = std::thread::available_parallelism() {
available_parallelism
} else {
NonZeroUsize::new(1).unwrap()
};
Client::new_with_fifo(available_parallelism.get() - 1).expect("failed to create job server")
})
}
struct State {
waiting_count: usize,
available: Vec<Acquired>,
implicit_available: bool,
}
impl State {
fn total_available(&self) -> usize {
self.available.len() + self.implicit_available as usize
}
fn additional_waiting(&self) -> usize {
self.waiting_count.saturating_sub(self.total_available())
}
}
static STATE: Mutex<State> = Mutex::new(State {
waiting_count: 0,
available: Vec::new(),
implicit_available: true,
});
static COND_VAR: Condvar = Condvar::new();
#[derive(Debug)]
enum AcquiredJobInner {
FromJobServer(Acquired),
ImplicitJob,
}
#[derive(Debug)]
pub struct AcquiredJob {
job: AcquiredJobInner,
}
impl AcquiredJob {
fn start_acquire_thread() {
static STARTED_THREAD: Once = Once::new();
STARTED_THREAD.call_once(|| {
spawn(|| {
let mut acquired = None;
let client = get_or_make_client();
let mut state = STATE.lock().unwrap();
loop {
state = if state.additional_waiting() == 0 {
if acquired.is_some() {
drop(state);
drop(acquired.take()); // drop Acquired outside of lock
STATE.lock().unwrap()
} else {
COND_VAR.wait(state).unwrap()
}
} else if acquired.is_some() {
// allocate space before moving Acquired to ensure we
// drop Acquired outside of the lock on panic
state.available.reserve(1);
state.available.push(acquired.take().unwrap());
COND_VAR.notify_all();
state
} else {
drop(state);
acquired = Some(
client
.acquire()
.expect("can't acquire token from job server"),
);
STATE.lock().unwrap()
};
}
});
});
}
fn acquire_inner(block: bool) -> Option<Self> {
Self::start_acquire_thread();
let mut state = STATE.lock().unwrap();
loop {
if let Some(acquired) = state.available.pop() {
return Some(Self {
job: AcquiredJobInner::FromJobServer(acquired),
});
}
if state.implicit_available {
state.implicit_available = false;
return Some(Self {
job: AcquiredJobInner::ImplicitJob,
});
}
if !block {
return None;
}
state.waiting_count += 1;
state = COND_VAR.wait(state).unwrap();
state.waiting_count -= 1;
}
}
pub fn try_acquire() -> Option<Self> {
Self::acquire_inner(false)
}
pub fn acquire() -> Self {
Self::acquire_inner(true).expect("failed to acquire token")
}
pub fn run_command<R>(
&mut self,
cmd: std::process::Command,
f: impl FnOnce(&mut std::process::Command) -> std::io::Result<R>,
) -> std::io::Result<R> {
get_or_make_client().configure_make_and_run_with_fifo(cmd, f)
}
}
impl Drop for AcquiredJob {
fn drop(&mut self) {
let mut state = STATE.lock().unwrap();
match &self.job {
AcquiredJobInner::FromJobServer(_) => {
if state.waiting_count > state.available.len() + state.implicit_available as usize {
// allocate space before moving Acquired to ensure we
// drop Acquired outside of the lock on panic
state.available.reserve(1);
let AcquiredJobInner::FromJobServer(acquired) =
mem::replace(&mut self.job, AcquiredJobInner::ImplicitJob)
else {
unreachable!()
};
state.available.push(acquired);
COND_VAR.notify_all();
}
}
AcquiredJobInner::ImplicitJob => {
state.implicit_available = true;
if state.waiting_count > state.available.len() {
COND_VAR.notify_all();
}
}
}
}
}

View file

@ -0,0 +1,433 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{memory::splat_mask, prelude::*};
use std::num::NonZeroUsize;
#[hdl]
pub struct ReadyValid<T> {
pub data: HdlOption<T>,
#[hdl(flip)]
pub ready: Bool,
}
impl<T: Type> ReadyValid<T> {
#[hdl]
pub fn firing(expr: Expr<Self>) -> Expr<Bool> {
#[hdl]
let firing: Bool = wire();
#[hdl]
match expr.data {
HdlNone => connect(firing, false),
HdlSome(_) => connect(firing, expr.ready),
}
firing
}
#[hdl]
pub fn firing_data(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<T>> {
let expr = expr.to_expr();
let option_ty = Expr::ty(expr).data;
#[hdl]
let firing_data = wire(option_ty);
connect(firing_data, option_ty.HdlNone());
#[hdl]
if expr.ready {
connect(firing_data, expr.data);
}
firing_data
}
#[hdl]
pub fn map<R: Type>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<ReadyValid<R>> {
let data = HdlOption::map(expr.data, f);
#[hdl]
let mapped = wire(ReadyValid[Expr::ty(data).HdlSome]);
connect(mapped.data, data);
connect(expr.ready, mapped.ready);
mapped
}
}
#[hdl_module]
pub fn queue<T: Type>(
ty: T,
capacity: NonZeroUsize,
inp_ready_is_comb: bool,
out_valid_is_comb: bool,
) {
let count_ty = UInt::range_inclusive(0..=capacity.get());
let index_ty = UInt::range(0..capacity.get());
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let inp: ReadyValid<T> = m.input(ReadyValid[ty]);
#[hdl]
let out: ReadyValid<T> = m.output(ReadyValid[ty]);
#[hdl]
let count: UInt = m.output(count_ty);
#[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(0.cast_to(index_ty));
#[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(0.cast_to(index_ty));
#[hdl]
let maybe_full_reg = reg_builder().clock_domain(cd).reset(false);
#[hdl]
let mut mem = memory(ty);
mem.depth(capacity.get());
let read_port = mem.new_read_port();
let write_port = mem.new_write_port();
#[hdl]
let inp_firing: Bool = wire();
connect(inp_firing, ReadyValid::firing(inp));
#[hdl]
let out_firing: Bool = wire();
connect(out_firing, ReadyValid::firing(out));
#[hdl]
let indexes_equal: Bool = wire();
connect(indexes_equal, inp_index_reg.cmp_eq(out_index_reg));
#[hdl]
let empty: Bool = wire();
connect(empty, indexes_equal & !maybe_full_reg);
#[hdl]
let full: Bool = wire();
connect(full, indexes_equal & maybe_full_reg);
connect(read_port.addr, out_index_reg);
connect(read_port.en, true);
connect(read_port.clk, cd.clk);
connect(write_port.addr, inp_index_reg);
connect(write_port.en, inp_firing);
connect(write_port.clk, cd.clk);
connect(write_port.data, HdlOption::unwrap_or(inp.data, ty.uninit()));
connect(write_port.mask, splat_mask(ty, true.to_expr()));
connect(inp.ready, !full);
if inp_ready_is_comb {
#[hdl]
if out.ready {
connect(inp.ready, true);
}
}
#[hdl]
if !empty {
connect(out.data, HdlSome(read_port.data));
} else {
if out_valid_is_comb {
connect(out.data, inp.data);
} else {
connect(out.data, HdlOption[ty].HdlNone());
}
}
#[hdl]
if inp_firing.cmp_ne(out_firing) {
connect(maybe_full_reg, inp_firing);
}
#[hdl]
if inp_firing {
#[hdl]
if inp_index_reg.cmp_eq(capacity.get() - 1) {
connect_any(inp_index_reg, 0_hdl_u0);
} else {
connect_any(inp_index_reg, inp_index_reg + 1_hdl_u1);
}
}
#[hdl]
if out_firing {
#[hdl]
if out_index_reg.cmp_eq(capacity.get() - 1) {
connect_any(out_index_reg, 0_hdl_u0);
} else {
connect_any(out_index_reg, out_index_reg + 1_hdl_u1);
}
}
#[hdl]
if indexes_equal {
#[hdl]
if maybe_full_reg {
connect_any(count, capacity);
} else {
connect_any(count, 0_hdl_u0);
}
} else {
if capacity.is_power_of_two() {
debug_assert_eq!(count_ty.width(), index_ty.width() + 1);
#[hdl]
let count_lower = wire(index_ty);
connect(
count_lower,
(inp_index_reg - out_index_reg).cast_to(index_ty),
); // wrap
connect(count, count_lower.cast_to(count_ty));
} else {
debug_assert_eq!(count_ty.width(), index_ty.width());
#[hdl]
if inp_index_reg.cmp_lt(out_index_reg) {
connect(count, inp_index_reg + capacity - out_index_reg);
} else {
connect(count, inp_index_reg - out_index_reg);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cli::FormalMode, firrtl::ExportOptions,
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
ty::StaticType,
};
use std::num::NonZero;
#[track_caller]
fn test_queue(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
assert_formal(
format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"),
queue_test(capacity, inp_ready_is_comb, out_valid_is_comb),
FormalMode::Prove,
14,
None,
ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
..ExportOptions::default()
},
);
#[hdl_module]
fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let cd = wire();
connect(
cd,
#[hdl]
ClockDomain {
clk,
rst: formal_reset().to_reset(),
},
);
#[hdl]
let inp_data: HdlOption<UInt<8>> = wire();
#[hdl]
if any_seq(Bool) {
connect(inp_data, HdlSome(any_seq(UInt::<8>::TYPE)));
} else {
connect(inp_data, HdlNone());
}
#[hdl]
let out_ready: Bool = wire();
connect(out_ready, any_seq(Bool));
let index_ty: UInt<32> = UInt::TYPE;
#[hdl]
let index_to_check = wire();
connect(index_to_check, any_const(index_ty));
let index_max = !index_ty.zero();
// we saturate at index_max, so only check indexes where we properly maintain position
hdl_assume(clk, index_to_check.cmp_ne(index_max), "");
#[hdl]
let dut = instance(queue(
UInt[ConstUsize::<8>],
capacity,
inp_ready_is_comb,
out_valid_is_comb,
));
connect(dut.cd, cd);
connect(dut.inp.data, inp_data);
connect(dut.out.ready, out_ready);
hdl_assume(
clk,
index_to_check.cmp_ne(!Expr::ty(index_to_check).zero()),
"",
);
#[hdl]
let expected_count_reg = reg_builder().clock_domain(cd).reset(0u32);
#[hdl]
let next_expected_count = wire();
connect(next_expected_count, expected_count_reg);
connect(expected_count_reg, next_expected_count);
#[hdl]
if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg + 1u8);
} else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg - 1u8);
}
hdl_assert(cd.clk, expected_count_reg.cmp_eq(dut.count), "");
#[hdl]
let prev_out_ready_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_out_ready_reg,
(prev_out_ready_reg << 1) | out_ready.cast_to(UInt[1]),
);
#[hdl]
let prev_inp_valid_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_inp_valid_reg,
(prev_inp_valid_reg << 1) | HdlOption::is_some(inp_data).cast_to(UInt[1]),
);
hdl_assume(
clk,
(prev_out_ready_reg & prev_inp_valid_reg).cmp_ne(0u8),
"",
);
#[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
let stored_inp_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.inp) {
#[hdl]
if inp_index_reg.cmp_lt(index_max) {
connect_any(inp_index_reg, inp_index_reg + 1u8);
#[hdl]
if inp_index_reg.cmp_eq(index_to_check) {
connect(stored_inp_data_reg, data);
}
}
}
#[hdl]
if inp_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), "");
}
#[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
let stored_out_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.out) {
#[hdl]
if out_index_reg.cmp_lt(index_max) {
connect_any(out_index_reg, out_index_reg + 1u8);
#[hdl]
if out_index_reg.cmp_eq(index_to_check) {
connect(stored_out_data_reg, data);
}
}
}
#[hdl]
if out_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), "");
}
hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), "");
#[hdl]
if inp_index_reg.cmp_lt(index_max) & out_index_reg.cmp_lt(index_max) {
hdl_assert(
clk,
expected_count_reg.cmp_eq(inp_index_reg - out_index_reg),
"",
);
} else {
hdl_assert(
clk,
expected_count_reg.cmp_ge(inp_index_reg - out_index_reg),
"",
);
}
#[hdl]
if inp_index_reg.cmp_gt(index_to_check) & out_index_reg.cmp_gt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(stored_out_data_reg), "");
}
}
}
#[test]
fn test_1_false_false() {
test_queue(NonZero::new(1).unwrap(), false, false);
}
#[test]
fn test_1_false_true() {
test_queue(NonZero::new(1).unwrap(), false, true);
}
#[test]
fn test_1_true_false() {
test_queue(NonZero::new(1).unwrap(), true, false);
}
#[test]
fn test_1_true_true() {
test_queue(NonZero::new(1).unwrap(), true, true);
}
#[test]
fn test_2_false_false() {
test_queue(NonZero::new(2).unwrap(), false, false);
}
#[test]
fn test_2_false_true() {
test_queue(NonZero::new(2).unwrap(), false, true);
}
#[test]
fn test_2_true_false() {
test_queue(NonZero::new(2).unwrap(), true, false);
}
#[test]
fn test_2_true_true() {
test_queue(NonZero::new(2).unwrap(), true, true);
}
#[test]
fn test_3_false_false() {
test_queue(NonZero::new(3).unwrap(), false, false);
}
#[test]
fn test_3_false_true() {
test_queue(NonZero::new(3).unwrap(), false, true);
}
#[test]
fn test_3_true_false() {
test_queue(NonZero::new(3).unwrap(), true, false);
}
#[test]
fn test_3_true_true() {
test_queue(NonZero::new(3).unwrap(), true, true);
}
#[test]
fn test_4_false_false() {
test_queue(NonZero::new(4).unwrap(), false, false);
}
#[test]
fn test_4_false_true() {
test_queue(NonZero::new(4).unwrap(), false, true);
}
#[test]
fn test_4_true_false() {
test_queue(NonZero::new(4).unwrap(), true, false);
}
#[test]
fn test_4_true_true() {
test_queue(NonZero::new(4).unwrap(), true, true);
}
}

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
mod safety_boundary {
use std::{cell::Cell, ptr::NonNull};
@ -104,3 +106,9 @@ impl<T: ?Sized> ScopedRef<T> {
self.0.with_opt(f)
}
}
impl<T: ?Sized> Default for ScopedRef<T> {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::{
io::{self, BufRead},
str,
};
pub(crate) fn streaming_read_utf8<R: BufRead, E: From<io::Error>>(
reader: R,
mut callback: impl FnMut(&str) -> Result<(), E>,
) -> Result<(), E> {
let mut buf = [0; 4];
let mut buf_len = 0;
for byte in reader.bytes() {
buf[buf_len] = byte?;
buf_len += 1;
match str::from_utf8(&buf[..buf_len]) {
Ok(buf) => {
callback(buf)?;
buf_len = 0;
}
Err(e) => {
if e.error_len().is_some() {
callback("\u{FFFD}")?; // replacement character
buf_len = 0;
}
}
}
}
Ok(())
}

View file

@ -1,88 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
int::{DynIntType, DynSIntType, DynUIntType, IntTypeTrait, SIntType},
ty::{Type, Value},
};
use std::ops::RangeBounds;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
pub struct Valueless<T> {
pub ty: T,
}
impl<T: Type> Valueless<T> {
pub fn to_canonical(&self) -> Valueless<T::CanonicalType> {
Valueless {
ty: self.ty.canonical(),
}
}
pub fn from_canonical(v: Valueless<T::CanonicalType>) -> Self {
Valueless {
ty: T::from_canonical_type(v.ty),
}
}
}
mod sealed {
pub trait Sealed {}
}
pub trait ValuelessTr: sealed::Sealed {
type Type: Type<Value = Self::Value>;
type Value: Value<Type = Self::Type>;
}
impl<T> sealed::Sealed for Valueless<T> {}
impl<T: Type> ValuelessTr for Valueless<T> {
type Type = T;
type Value = T::Value;
}
impl<T: IntTypeTrait> Valueless<T> {
pub fn signum(&self) -> Valueless<SIntType<2>> {
Valueless::default()
}
pub fn as_same_width_uint(self) -> Valueless<T::SameWidthUInt> {
Valueless {
ty: self.ty.as_same_width_uint(),
}
}
pub fn as_same_width_sint(self) -> Valueless<T::SameWidthSInt> {
Valueless {
ty: self.ty.as_same_width_sint(),
}
}
pub fn as_same_value_uint(self) -> Valueless<DynUIntType> {
Valueless {
ty: self.ty.as_same_value_uint(),
}
}
pub fn as_same_value_sint(self) -> Valueless<DynSIntType> {
Valueless {
ty: self.ty.as_same_value_sint(),
}
}
pub fn concat<HighType: IntTypeTrait>(
&self,
high_part: Valueless<HighType>,
) -> Valueless<DynIntType<HighType::Signed>> {
let ty = DynIntType::new(
self.ty
.width()
.checked_add(high_part.ty.width())
.expect("result has too many bits"),
);
Valueless { ty }
}
pub fn repeat(&self, count: usize) -> Valueless<DynIntType<T::Signed>> {
let width = self.ty.width();
let ty = DynIntType::new(width.checked_mul(count).expect("result has too many bits"));
Valueless { ty }
}
pub fn slice<I: RangeBounds<usize>>(&self, index: I) -> Valueless<DynUIntType> {
let ty = self.ty.slice(index);
Valueless { ty }
}
}

View file

@ -1,13 +1,13 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::Flow,
expr::{Expr, Flow, ToExpr},
intern::Interned,
module::{NameId, ScopedNameId},
module::{IncompleteDeclaration, NameId, ScopedNameId, StmtDeclaration, StmtWire},
source_location::SourceLocation,
ty::{CanonicalType, Type},
};
use std::fmt;
use std::{cell::RefCell, fmt, rc::Rc};
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Wire<T: Type> {
@ -76,3 +76,57 @@ impl<T: Type> Wire<T> {
true
}
}
#[derive(Clone)]
pub struct IncompleteWire {
pub(crate) declaration: Rc<RefCell<IncompleteDeclaration>>,
}
impl IncompleteWire {
#[track_caller]
pub fn complete<T: Type>(&mut self, ty: T) -> Expr<T> {
let canonical_type = ty.canonical();
let mut declaration = self.declaration.borrow_mut();
if let IncompleteDeclaration::Incomplete {
name,
source_location,
} = *declaration
{
*declaration = IncompleteDeclaration::Complete(
StmtWire {
annotations: (),
wire: Wire {
name,
source_location,
ty: canonical_type,
},
}
.into(),
);
}
match *declaration {
IncompleteDeclaration::Complete(StmtDeclaration::Wire(StmtWire {
wire:
Wire {
name,
source_location,
ty: wire_ty,
},
..
})) => {
drop(declaration);
assert_eq!(wire_ty, canonical_type, "type mismatch");
Wire {
name,
source_location,
ty,
}
.to_expr()
}
IncompleteDeclaration::Taken => panic!("can't use wire outside of containing module"),
IncompleteDeclaration::Complete(_) | IncompleteDeclaration::Incomplete { .. } => {
unreachable!()
}
}
}
}

View file

@ -1,6 +1,13 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::prelude::*;
use fayalite::{
bundle::BundleType,
enum_::EnumType,
int::{BoolOrIntType, IntType},
prelude::*,
ty::StaticType,
};
use std::marker::PhantomData;
#[hdl(outline_generated)]
pub struct S<T, Len: Size, T2> {
@ -8,6 +15,7 @@ pub struct S<T, Len: Size, T2> {
b: UInt<3>,
pub(crate) c: ArrayType<UInt<1>, Len>,
pub d: T2,
pub _phantom: PhantomData<(T, Len)>,
}
#[hdl(outline_generated)]
@ -23,9 +31,163 @@ pub enum E<T> {
A,
B(UInt<3>),
C(T),
D(TyAlias2),
E(TyAlias<Bool, ConstUsize<1>, { 1 + 2 }>),
}
#[hdl(outline_generated)]
pub struct S2<T = ()> {
pub v: E<T>,
}
#[hdl(outline_generated)]
pub type TyAlias<T, Sz: Size, const C: usize, D = ()> = Array<S<T, Sz, D>, C>;
#[hdl(outline_generated)]
pub type TyAlias2 = TyAlias<UInt<8>, ConstUsize<24>, 5>;
// check that #[hdl] properly handles hygiene
macro_rules! types_in_macros {
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident, $E:ident, $F:ident) => {
#[hdl]
struct $F {}
#[hdl]
struct $A<$B, $C: Size, const $D: usize, $E = $F> {
$a: $B,
$b: UIntType<$C>,
$c: SInt<$D>,
$d: HdlOption<$E>,
$e: $E,
$f: $F,
}
#[allow(non_camel_case_types)]
#[hdl]
enum $B<$C: Size, const $D: usize, $E = $F> {
$a($A<(), $C, $D, $E>),
$b(UIntType<$C>),
$c(SInt<$D>),
$d,
$e($E),
$f($F),
}
};
// ensure every identifier has different hygiene
() => {
types_in_macros!(a);
};
($a:ident) => {
types_in_macros!($a, b);
};
($a:ident, $b:ident) => {
types_in_macros!($a, $b, c);
};
($a:ident, $b:ident, $c:ident) => {
types_in_macros!($a, $b, $c, d);
};
($a:ident, $b:ident, $c:ident, $d:ident) => {
types_in_macros!($a, $b, $c, $d, e);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident) => {
types_in_macros!($a, $b, $c, $d, $e, f);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, A);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, B);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, C);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, D);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, $D, E);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident, $E:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, $D, $E, F);
};
}
types_in_macros!();
mod bound_kind {
use fayalite::prelude::*;
#[hdl]
pub struct Type<T> {
v: T,
}
#[hdl]
pub struct Size<T: ::fayalite::int::Size> {
v: UIntType<T>,
}
}
macro_rules! check_bounds {
($name:ident<$(#[$field:ident, $kind:ident] $var:ident: $($bound:ident +)*),*>) => {
#[hdl(outline_generated)]
struct $name<$($var: $($bound +)*,)*> {
$($field: bound_kind::$kind<$var>,)*
}
};
}
check_bounds!(CheckBoundsS0<#[a, Size] A: Size +>);
check_bounds!(CheckBoundsS1<#[a, Size] A: KnownSize +>);
check_bounds!(CheckBoundsT0<#[a, Type] A: Type +>);
check_bounds!(CheckBoundsT1<#[a, Type] A: BoolOrIntType +>);
check_bounds!(CheckBoundsT2<#[a, Type] A: BundleType +>);
check_bounds!(CheckBoundsT3<#[a, Type] A: EnumType +>);
check_bounds!(CheckBoundsT4<#[a, Type] A: IntType +>);
check_bounds!(CheckBoundsT5<#[a, Type] A: StaticType +>);
check_bounds!(CheckBoundsSS0<#[a, Size] A: Size +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsSS1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsST0<#[a, Size] A: Size +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsST1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTS0<#[a, Type] A: Type +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS2<#[a, Type] A: BundleType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS3<#[a, Type] A: EnumType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS4<#[a, Type] A: IntType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS5<#[a, Type] A: StaticType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTT0<#[a, Type] A: Type +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsSSS0<#[a, Size] A: Size +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSSS1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSST0<#[a, Size] A: Size +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSST1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSTS0<#[a, Size] A: Size +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSTS1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSTT0<#[a, Size] A: Size +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSTT1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTSS0<#[a, Type] A: Type +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS2<#[a, Type] A: BundleType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS3<#[a, Type] A: EnumType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS4<#[a, Type] A: IntType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS5<#[a, Type] A: StaticType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTST0<#[a, Type] A: Type +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST2<#[a, Type] A: BundleType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST3<#[a, Type] A: EnumType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST4<#[a, Type] A: IntType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST5<#[a, Type] A: StaticType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTS0<#[a, Type] A: Type +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTT0<#[a, Type] A: Type +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
error: top-level #[hdl] can only be used on structs or enums
error: top-level #[hdl] can only be used on structs, enums, type aliases, or functions
--> tests/ui/hdl_types.rs:5:1
|
5 | #[hdl]

View file

@ -1,4 +1,8 @@
{
"license_header": [
"SPDX-License-Identifier: LGPL-3.0-or-later",
"See Notices.txt for copyright information"
],
"types": {
"Module": {
"data": {
@ -24,7 +28,7 @@
"$kind": "Struct",
"$constructor": "ModuleIO::new_unchecked",
"containing_module_name_id()": "Visible",
"name()": "Visible",
"name_id()": "Visible",
"source_location()": "Visible",
"is_input()": "Visible",
"ty()": "Visible"
@ -279,6 +283,16 @@
"fold_where": "T: Fold<State>",
"visit_where": "T: Visit<State>"
},
"ops::Uninit": {
"data": {
"$kind": "Struct",
"$constructor": "ops::Uninit::new",
"ty()": "Visible"
},
"generics": "<T: Type>",
"fold_where": "T: Fold<State>",
"visit_where": "T: Visit<State>"
},
"ops::NotU": {
"data": {
"$kind": "Struct",
@ -1053,6 +1067,7 @@
"data": {
"$kind": "Enum",
"Connect": "Visible",
"Formal": "Visible",
"If": "Visible",
"Match": "Visible",
"Declaration": "Visible"
@ -1074,6 +1089,25 @@
"source_location": "Visible"
}
},
"FormalKind": {
"data": {
"$kind": "Enum",
"Assert": null,
"Assume": null,
"Cover": null
}
},
"StmtFormal": {
"data": {
"$kind": "Struct",
"kind": "Visible",
"clk": "Visible",
"pred": "Visible",
"en": "Visible",
"text": "Visible",
"source_location": "Visible"
}
},
"StmtIf": {
"data": {
"$kind": "Struct",
@ -1129,10 +1163,44 @@
"Annotation": {
"data": {
"$kind": "Enum",
"DontTouch": null,
"DontTouch": "Visible",
"SVAttribute": "Visible",
"BlackBoxInline": "Visible",
"BlackBoxPath": "Visible",
"DocString": "Visible",
"CustomFirrtl": "Visible"
}
},
"DontTouchAnnotation": {
"data": {
"$kind": "Struct"
}
},
"SVAttributeAnnotation": {
"data": {
"$kind": "Struct",
"text": "Visible"
}
},
"BlackBoxInlineAnnotation": {
"data": {
"$kind": "Struct",
"path": "Visible",
"text": "Visible"
}
},
"BlackBoxPathAnnotation": {
"data": {
"$kind": "Struct",
"path": "Visible"
}
},
"DocStringAnnotation": {
"data": {
"$kind": "Struct",
"text": "Visible"
}
},
"CustomFirrtlAnnotation": {
"data": {
"$kind": "Opaque"

74
scripts/check-copyright.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
set -e
function fail()
{
local error="$1"
echo "error: $error" >&2
exit 1
}
function fail_file()
{
local file="$1" line="$2" error="$3"
fail "$file:$((line + 1)): $error"
}
function check_file()
{
local file="$1" regexes=("${@:2}")
local lines
mapfile -t lines < "$file"
local line
for line in "${!regexes[@]}"; do
eval '[[ "${lines[i]}" =~ '"${regexes[i]}"' ]]' ||
fail_file "$file" "$line" "doesn't match regex: ${regexes[i]}"
done
}
POUND_HEADER=('^"# SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"# See Notices.txt for copyright information"$')
SLASH_HEADER=('^"// SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"// See Notices.txt for copyright information"$')
MD_HEADER=('^"<!--"$' '^"SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"See Notices.txt for copyright information"$')
JSON_HEADER=('^"{"$' '^" \"license_header\": ["$' '^" \"SPDX-License-Identifier: LGPL-3.0-or-later\","$' '^" \"See Notices.txt for copyright information\""')
function main()
{
local IFS=$'\n'
[[ -z "$(git status --porcelain)" ]] || fail "git repo is dirty"
local file
for file in $(git ls-tree --name-only --full-tree -r HEAD); do
case "/$file" in
/Cargo.lock)
# generated file
;;
*/LICENSE.md|*/Notices.txt)
# copyright file
;;
/crates/fayalite/tests/ui/*.stderr)
# file that can't contain copyright header
;;
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
check_file "$file" "${POUND_HEADER[@]}"
;;
*.md)
check_file "$file" "${MD_HEADER[@]}"
;;
*.sh)
check_file "$file" '^'\''#!'\' "${POUND_HEADER[@]}"
;;
*.rs)
check_file "$file" "${SLASH_HEADER[@]}"
;;
*.json)
check_file "$file" "${JSON_HEADER[@]}"
;;
*)
fail_file "$file" 0 "unimplemented file kind -- you need to add it to $0"
;;
esac
done
}
main