1
0
Fork 0

Compare commits

..

54 commits

Author SHA1 Message Date
fbe4585578
add FillInDefaultedGenerics<Type = Self> bound for SizeType 2025-12-10 20:10:39 -08:00
e4210a672f
Check copyright header in Rocq files.
If we ever add Verilog files, we can "or" both results, I guess.
2025-12-09 07:45:35 -03:00
e54558d848
Demonstrates state with multiple variables and hidden state. 2025-12-08 22:00:49 -03:00
46f3519c76
Demonstrate a preliminary mapping from HDL to Rocq.
Starts with a very simple example, including a proof by induction.
2025-12-08 19:32:05 -03:00
9e803223d0
support operations directly on SimValue, UIntValue, and SIntValue, and shared references to those 2025-11-24 00:14:53 -08:00
2a65aa2bd5
fix DynShr[SU]'s literal bits to properly shift right instead of left 2025-11-20 02:25:05 -08:00
2817cd3d58
support Rust's default binding modes when destructuring with #[hdl(sim)] let/match 2025-11-14 00:20:54 -08:00
053c1b2b10
implement Display for SimValue<T> 2025-11-14 00:20:54 -08:00
17b58e8edb
add utility impls for SimValue<ArrayType<_, _>> 2025-11-13 20:21:07 -08:00
df020e9c9b
add ExternModuleSimulatorState::read_past() and more output when simulator trace is enabled 2025-11-12 22:31:45 -08:00
45fea70c18
add ExternModuleSimulationState::fork_join_scope 2025-11-07 02:18:43 -08:00
fbc8ffa5ae
fix private fields in #[hdl] pub struct 2025-11-06 20:23:16 -08:00
26a7090178
add ParsedVisibility 2025-11-06 20:22:53 -08:00
0b77d1bea0
fix Simulator panicking when you use PhantomConst 2025-11-05 22:44:43 -08:00
840c5e1895
add ExternModuleSimulationState::resettable helper for procedural simulations that have a reset input. 2025-11-03 23:59:36 -08:00
c11a1743f9
add sim.fork_join() and fix Simulator to handle running futures with arbitrary wakers 2025-10-30 21:16:05 -07:00
0be9f9ce23
fix JobGraph::run to not busy-wait 2025-10-27 22:57:12 -07:00
0b82178740
add PhantomConstGet to the known Type bounds for #[hdl] struct/enum 2025-10-27 20:08:22 -07:00
4b24a88641
add docs for #[hdl] and particularly for #[hdl] type aliases 2025-10-26 03:25:35 -07:00
094c77e26e
add #[hdl(get(|v| ...))] type GetStuff<P: PhantomConstGet<MyStruct>> = MyType or DynSize; 2025-10-26 03:25:35 -07:00
d2c8b023bf
deny broken docs 2025-10-26 03:25:35 -07:00
c043ee54d0
fix rustdoc warning for link in readme 2025-10-26 03:25:35 -07:00
edcc5927a5
don't cache external job failures if they could be caused by the user killing processes 2025-10-24 02:27:20 -07:00
7dc4417874
add test_many_memories so we catch if memories are iterated in an inconsistent order like in 838bd469ce 2025-10-24 01:40:30 -07:00
838bd469ce
change SimulationImpl::trace_memories to a BTreeMap for consistent iteration order 2025-10-24 00:53:13 -07:00
b6e4cd0614
move FormalMode to crate::testing and add to prelude 2025-10-24 00:14:04 -07:00
3e5b2f126a
make UIntInRange[Inclusive][Type] castable from/to any UInt<N> and add methods to get bit_width, start, and end 2025-10-23 23:52:41 -07:00
040cefea21
add tx_only_uart example to readme 2025-10-22 20:31:25 -07:00
3267cb38c4
build tx_only_uart in CI 2025-10-22 20:12:08 -07:00
b3cc28e2b6
add transmit-only UART example 2025-10-22 20:11:02 -07:00
26840daf13
arty_a7: add divided clocks as available input peripherals so you're not stuck with 100MHz 2025-10-22 20:11:02 -07:00
4d9e8d3b47
Add building blinky example to the readme 2025-10-21 23:00:16 -07:00
c6feea6d51
properly handle all XilinxAnnotations, this makes nextpnr-xilinx properly pick up the clock frequency 2025-10-21 22:24:02 -07:00
409992961c
switch to using verilog for reset synchronizer so we can use attributes on FDPE instances 2025-10-21 22:24:02 -07:00
2bdc8a7c72
WIP adding xdc create_clock -- nextpnr-xilinx currently ignores it 2025-10-19 23:13:28 -07:00
477a1f2d29
Add peripherals and Arty A7 platforms -- blinky works correctly now on arty-a7-100t! 2025-10-19 23:13:28 -07:00
4d54f903be
move vendor module to top level 2025-10-17 15:00:19 -07:00
3f5dd61e46
WIP adding Platform 2025-10-17 05:55:22 -07:00
def406ab52
group all xilinx annotations together 2025-10-16 04:53:58 -07:00
a565be1b09
do some clean up 2025-10-16 04:32:56 -07:00
676c1e3b7d
WIP adding annotations for generating the .xdc file for yosys-nextpnr-prjxray 2025-10-15 04:29:00 -07:00
169be960f8
generate Arty A7 100T .bit file for blinky example in CI 2025-10-15 04:29:00 -07:00
2b52799f5c
try building .bit file 2025-10-15 04:29:00 -07:00
35f98f3229
fix redirects 2025-10-15 04:29:00 -07:00
8a63ea89d0
WIP adding yosys-nextpnr-xray xilinx fpga toolchain -- blinky works on arty a7 100t (except for inverted reset) 2025-10-15 04:29:00 -07:00
84c5978eaf
WIP build Xilinx FPGA dependencies in CI 2025-10-15 04:29:00 -07:00
42e3179a60
change cache directory name to be fayalite-specific 2025-10-15 04:29:00 -07:00
53ae3ff670
mark create-unix-shell-script as incomplete in CLI 2025-10-15 04:29:00 -07:00
7af9abfb6f
switch to using new crate::build system 2025-10-15 04:29:00 -07:00
aacd05378f
WIP converting from cli.rs to build/*.rs 2025-10-15 04:29:00 -07:00
908ccef674
added automatically-added dependencies; added caching for external jobs 2025-10-15 04:29:00 -07:00
057670c12a
WIP adding FPGA support -- build module should be complete 2025-10-15 04:29:00 -07:00
f8ac78abd6
Remove extraneous #[automatically_derived] annotations that are causing warnings as reported by Tobias 2025-10-15 04:17:47 -07:00
64ec6c0dcc
switch to use server's new actions org 2025-10-09 23:48:17 -07:00
129 changed files with 53020 additions and 4371 deletions

View file

@ -1,77 +0,0 @@
# 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://git.libre-chip.org/mirrors/checkout@v3
with:
fetch-depth: 0
- uses: https://git.libre-chip.org/mirrors/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://git.libre-chip.org/mirrors/sby 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://git.libre-chip.org/mirrors/z3 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://git.libre-chip.org/mirrors/yosys deps/yosys
make -C deps/yosys -j"$(nproc)"
- uses: https://git.libre-chip.org/mirrors/cache/save@v3
if: steps.restore-deps.outputs.cache-hit != 'true'
with:
path: deps
key: ${{ steps.restore-deps.outputs.cache-primary-key }}

View file

@ -3,57 +3,16 @@
on: [push, pull_request]
jobs:
deps:
runs-on: debian-12
uses: ./.forgejo/workflows/deps.yml
test:
runs-on: debian-12
needs: deps
container:
image: git.libre-chip.org/libre-chip/fayalite-deps:latest
steps:
- uses: https://git.libre-chip.org/mirrors/checkout@v3
- uses: 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.89.0
source "$HOME/.cargo/env"
rustup component add rust-src
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://git.libre-chip.org/mirrors/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://git.libre-chip.org/mirrors/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
@ -62,3 +21,5 @@ jobs:
- run: cargo test --doc --features=unstable-doc
- run: cargo doc --features=unstable-doc
- run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher
- run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out
- run: cargo run --example tx_only_uart yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/tx_only_uart-out

128
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "allocator-api2"
@ -25,9 +25,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.7"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
@ -81,6 +81,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "basic-toml"
version = "0.1.8"
@ -149,9 +155,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.9"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
@ -159,9 +165,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.9"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
@ -170,10 +176,19 @@ dependencies = [
]
[[package]]
name = "clap_derive"
version = "4.5.8"
name = "clap_complete"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
@ -183,9 +198,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
@ -291,9 +306,11 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
name = "fayalite"
version = "0.3.0"
dependencies = [
"base64",
"bitvec",
"blake3",
"clap",
"clap_complete",
"ctor",
"eyre",
"fayalite-proc-macros",
@ -302,7 +319,7 @@ dependencies = [
"jobslot",
"num-bigint",
"num-traits",
"os_pipe",
"ordered-float",
"petgraph",
"serde",
"serde_json",
@ -377,12 +394,13 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.14"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
@ -449,23 +467,23 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobslot"
version = "0.2.19"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "linux-raw-sys"
@ -508,13 +526,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "os_pipe"
version = "1.2.1"
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"libc",
"windows-sys 0.59.0",
"num-traits",
"rand",
"serde",
]
[[package]]
@ -557,12 +576,37 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
"serde",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"serde",
]
[[package]]
name = "rustix"
version = "0.38.31"
@ -748,9 +792,21 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "which"
@ -795,6 +851,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -806,11 +868,11 @@ dependencies = [
[[package]]
name = "windows-sys"
version = "0.59.0"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-targets",
"windows-link",
]
[[package]]
@ -883,6 +945,12 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "wyz"
version = "0.5.1"

View file

@ -18,17 +18,19 @@ fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros
fayalite-proc-macros-impl = { version = "=0.3.0", path = "crates/fayalite-proc-macros-impl" }
fayalite-visit-gen = { version = "=0.3.0", path = "crates/fayalite-visit-gen" }
base16ct = "0.2.0"
base64 = "0.22.1"
bitvec = { version = "1.0.1", features = ["serde"] }
blake3 = { version = "1.5.4", features = ["serde"] }
clap = { version = "4.5.9", features = ["derive", "env", "string"] }
clap_complete = "4.5.58"
ctor = "0.2.8"
eyre = "0.6.12"
hashbrown = "0.15.2"
indexmap = { version = "2.5.0", features = ["serde"] }
jobslot = "0.2.19"
jobslot = "0.2.23"
num-bigint = "0.4.6"
num-traits = "0.2.16"
os_pipe = "1.2.1"
ordered-float = { version = "5.1.0", features = ["serde"] }
petgraph = "0.8.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"

View file

@ -8,6 +8,73 @@ Fayalite is a library for designing digital hardware -- a hardware description l
[FIRRTL]: https://github.com/chipsalliance/firrtl-spec
# Building the [Blinky example] for the Arty A7 100T on Linux
[Blinky example]: crates/fayalite/examples/blinky.rs
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in <https://git.libre-chip.org/libre-chip/fayalite-deps>
Steps:
Install podman (or docker).
Run:
```bash
podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out
```
To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS:
[openFPGALoader]: https://github.com/trabucayre/openFPGALoader
On Debian 12:
```bash
sudo apt update && sudo apt install openfpgaloader
```
Then program the FPGA:
```bash
sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit
```
This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled.
To program the Flash also, so it stays programmed when power-cycling the board:
```bash
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
```
# Building the [Transmit-only UART example] for the Arty A7 100T on Linux
[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs
Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`.
View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`.
Find the correct USB device:
```bash
sudo tio --list
```
You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port):
`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0`
Connect to the serial port:
```bash
sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here
```
You'll see (repeating endlessly):
```text
Hello World from Fayalite!!!
Hello World from Fayalite!!!
Hello World from Fayalite!!!
```
Press Ctrl+T then `q` to exit tio.
# Funding
## NLnet Grants

View file

@ -87,7 +87,11 @@ impl ParsedBundle {
no_static: _,
no_runtime_generics: _,
cmp_eq: _,
ref get,
} = options.body;
if let Some((get, ..)) = get {
errors.error(get, "#[hdl(get(...))] is not allowed on structs");
}
let mut fields = match fields {
syn::Fields::Named(fields) => fields,
syn::Fields::Unnamed(fields) => {
@ -220,7 +224,7 @@ impl Builder {
.args
.push_value(match get_field_state(field_index) {
BuilderFieldState::Unfilled => parse_quote_spanned! {self.ident.span()=>
::fayalite::bundle::Unfilled<#ty>
()
},
BuilderFieldState::Generic => {
let type_var = type_var_for_field_name(ident);
@ -345,7 +349,6 @@ impl ToTokens for Builder {
}
}));
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, non_snake_case, dead_code)]
impl #impl_generics #unfilled_ty
#where_clause
@ -380,7 +383,7 @@ impl ToTokens for Builder {
fn default() -> Self {
#ident {
#phantom_field_name: ::fayalite::__std::marker::PhantomData,
#(#field_idents: ::fayalite::__std::default::Default::default(),)*
#(#field_idents: (),)*
}
}
}
@ -392,16 +395,30 @@ impl ToTokens for Builder {
let type_generics = self.generics.split_for_impl().1;
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
impl #filled_impl_generics ::fayalite::expr::ToExpr for #filled_ty
#[allow(non_camel_case_types, dead_code, private_interfaces)]
impl #filled_impl_generics ::fayalite::expr::ValueType for #filled_ty
#filled_where_clause
{
type Type = #target #type_generics;
type ValueCategory = ::fayalite::expr::value_category::ValueCategoryExpr;
fn ty(&self) -> <Self as ::fayalite::expr::ValueType>::Type {
#target {
#(#field_idents: ::fayalite::expr::ValueType::ty(&self.#field_idents),)*
}
}
}
#[automatically_derived]
#[allow(non_camel_case_types, dead_code, private_interfaces)]
impl #filled_impl_generics ::fayalite::expr::ToExpr for #filled_ty
#filled_where_clause
{
fn to_expr(
&self,
) -> ::fayalite::expr::Expr<<Self as ::fayalite::expr::ToExpr>::Type> {
) -> ::fayalite::expr::Expr<<Self as ::fayalite::expr::ValueType>::Type> {
let __ty = #target {
#(#field_idents: ::fayalite::expr::Expr::ty(self.#field_idents),)*
#(#field_idents: ::fayalite::expr::ValueType::ty(&self.#field_idents),)*
};
let __field_values = [
#(::fayalite::expr::Expr::canonical(self.#field_idents),)*
@ -446,6 +463,7 @@ impl ToTokens for ParsedBundle {
no_static,
no_runtime_generics,
cmp_eq,
get: _,
} = &options.body;
let target = get_target(target, ident);
let mut item_attrs = attrs.clone();
@ -495,7 +513,6 @@ impl ToTokens for ParsedBundle {
};
builder.to_tokens(tokens);
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 { ty, .. } in &mut mask_type_fields.named {
*ty = parse_quote_spanned! {span=>
@ -513,8 +530,6 @@ impl ToTokens for ParsedBundle {
mask_type_builder.to_tokens(tokens);
let unfilled_mask_type_builder_ty =
mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Unfilled);
let filled_mask_type_builder_ty =
mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Filled);
ItemStruct {
attrs: vec![
common_derives(span),
@ -694,10 +709,10 @@ impl ToTokens for ParsedBundle {
v.field(&value.#ident);
}
}));
let to_sim_value_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let value_type_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {span=>
#ident: ::fayalite::sim::value::SimValue::ty(&self.#ident),
#ident: ::fayalite::expr::ValueType::ty(&self.#ident),
}
}));
let fields_len = fields.named().into_iter().len();
@ -781,7 +796,6 @@ impl ToTokens for ParsedBundle {
#where_clause
{
type Builder = #unfilled_mask_type_builder_ty;
type FilledBuilder = #filled_mask_type_builder_ty;
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
@ -806,28 +820,39 @@ impl ToTokens for ParsedBundle {
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValue for #mask_type_sim_value_ident #type_generics
impl #impl_generics ::fayalite::expr::ValueType for #mask_type_sim_value_ident #type_generics
#where_clause
{
type Type = #mask_type_ident #type_generics;
type ValueCategory = ::fayalite::expr::value_category::ValueCategorySimValue;
fn ty(&self) -> <Self as ::fayalite::expr::ValueType>::Type {
#mask_type_ident {
#(#value_type_fields)*
}
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValue for #mask_type_sim_value_ident #type_generics
#where_clause
{
fn to_sim_value(
&self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
let ty = #mask_type_ident {
#(#to_sim_value_fields)*
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self))
}
fn into_sim_value(
self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
let ty = #mask_type_ident {
#(#to_sim_value_fields)*
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, self)
}
@ -931,7 +956,6 @@ impl ToTokens for ParsedBundle {
#where_clause
{
type Builder = #unfilled_builder_ty;
type FilledBuilder = #filled_builder_ty;
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
@ -956,28 +980,39 @@ impl ToTokens for ParsedBundle {
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValue for #sim_value_ident #type_generics
impl #impl_generics ::fayalite::expr::ValueType for #sim_value_ident #type_generics
#where_clause
{
type Type = #target #type_generics;
type ValueCategory = ::fayalite::expr::value_category::ValueCategorySimValue;
fn ty(&self) -> <Self as ::fayalite::expr::ValueType>::Type {
#target {
#(#value_type_fields)*
}
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValue for #sim_value_ident #type_generics
#where_clause
{
fn to_sim_value(
&self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
let ty = #target {
#(#to_sim_value_fields)*
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self))
}
fn into_sim_value(
self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
let ty = #target {
#(#to_sim_value_fields)*
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, self)
}
@ -1003,91 +1038,172 @@ impl ToTokens for ParsedBundle {
}
.to_tokens(tokens);
if let Some((cmp_eq,)) = cmp_eq {
let mut expr_where_clause =
let mut cmp_eq_where_clause =
Generics::from(generics)
.where_clause
.unwrap_or_else(|| syn::WhereClause {
where_token: Token![where](span),
predicates: Punctuated::new(),
});
let mut sim_value_where_clause = expr_where_clause.clone();
let mut fields_sim_value_eq = vec![];
let mut fields_cmp_eq = vec![];
let mut fields_cmp_ne = vec![];
let mut fields_value_eq = vec![];
let mut fields_value_ne = vec![];
let mut fields_expr_eq = vec![];
let mut fields_expr_ne = vec![];
let mut fields_valueless_eq = vec![];
let mut fields_valueless_ne = vec![];
for field in fields.named() {
let field_ident = field.ident();
let field_ty = field.ty();
expr_where_clause
cmp_eq_where_clause
.predicates
.push(parse_quote_spanned! {cmp_eq.span=>
#field_ty: ::fayalite::expr::ops::ExprPartialEq<#field_ty>
#field_ty: ::fayalite::expr::HdlPartialEqImpl<#field_ty>
});
sim_value_where_clause
.predicates
.push(parse_quote_spanned! {cmp_eq.span=>
#field_ty: ::fayalite::sim::value::SimValuePartialEq<#field_ty>
});
fields_sim_value_eq.push(quote_spanned! {span=>
::fayalite::sim::value::SimValuePartialEq::sim_value_eq(&__lhs.#field_ident, &__rhs.#field_ident)
fields_value_eq.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_value_eq(
__lhs.#field_ident,
::fayalite::__std::borrow::Cow::Borrowed(&__lhs_value.#field_ident),
__rhs.#field_ident,
::fayalite::__std::borrow::Cow::Borrowed(&__rhs_value.#field_ident),
)
});
fields_cmp_eq.push(quote_spanned! {span=>
::fayalite::expr::ops::ExprPartialEq::cmp_eq(__lhs.#field_ident, __rhs.#field_ident)
fields_value_ne.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_value_ne(
__lhs.#field_ident,
::fayalite::__std::borrow::Cow::Borrowed(&__lhs_value.#field_ident),
__rhs.#field_ident,
::fayalite::__std::borrow::Cow::Borrowed(&__rhs_value.#field_ident),
)
});
fields_cmp_ne.push(quote_spanned! {span=>
::fayalite::expr::ops::ExprPartialEq::cmp_ne(__lhs.#field_ident, __rhs.#field_ident)
fields_expr_eq.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_expr_eq(
__lhs.#field_ident,
__rhs.#field_ident,
)
});
fields_expr_ne.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_expr_ne(
__lhs.#field_ident,
__rhs.#field_ident,
)
});
fields_valueless_eq.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_valueless_eq(
::fayalite::expr::Valueless::new(__lhs.#field_ident),
::fayalite::expr::Valueless::new(__rhs.#field_ident),
)
});
fields_valueless_ne.push(quote_spanned! {span=>
::fayalite::expr::HdlPartialEqImpl::cmp_valueless_ne(
::fayalite::expr::Valueless::new(__lhs.#field_ident),
::fayalite::expr::Valueless::new(__rhs.#field_ident),
)
});
}
let sim_value_eq_body;
let cmp_eq_body;
let cmp_ne_body;
let value_eq_body;
let value_ne_body;
let expr_eq_body;
let expr_ne_body;
let valueless_eq_body;
let valueless_ne_body;
if fields_len == 0 {
sim_value_eq_body = quote_spanned! {span=>
value_eq_body = quote_spanned! {span=>
true
};
cmp_eq_body = quote_spanned! {span=>
value_ne_body = quote_spanned! {span=>
false
};
expr_eq_body = quote_spanned! {span=>
::fayalite::expr::ToExpr::to_expr(&true)
};
cmp_ne_body = quote_spanned! {span=>
expr_ne_body = quote_spanned! {span=>
::fayalite::expr::ToExpr::to_expr(&false)
};
valueless_eq_body = quote_spanned! {span=>
::fayalite::expr::Valueless::new(::fayalite::int::Bool)
};
valueless_ne_body = quote_spanned! {span=>
::fayalite::expr::Valueless::new(::fayalite::int::Bool)
};
} else {
sim_value_eq_body = quote_spanned! {span=>
#(#fields_sim_value_eq)&&*
value_eq_body = quote_spanned! {span=>
#(#fields_value_eq)&*
};
cmp_eq_body = quote_spanned! {span=>
#(#fields_cmp_eq)&*
value_ne_body = quote_spanned! {span=>
#(#fields_value_ne)|*
};
cmp_ne_body = quote_spanned! {span=>
#(#fields_cmp_ne)|*
expr_eq_body = quote_spanned! {span=>
#(#fields_expr_eq)&*
};
expr_ne_body = quote_spanned! {span=>
#(#fields_expr_ne)|*
};
valueless_eq_body = quote_spanned! {span=>
let __lhs = ::fayalite::expr::ValueType::ty(&__lhs);
let __rhs = ::fayalite::expr::ValueType::ty(&__rhs);
#(#fields_valueless_eq)|*
};
valueless_ne_body = quote_spanned! {span=>
let __lhs = ::fayalite::expr::ValueType::ty(&__lhs);
let __rhs = ::fayalite::expr::ValueType::ty(&__rhs);
#(#fields_valueless_ne)|*
};
};
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::expr::ops::ExprPartialEq<Self> for #target #type_generics
#expr_where_clause
impl #impl_generics ::fayalite::expr::HdlPartialEqImpl<Self> for #target #type_generics
#cmp_eq_where_clause
{
fn cmp_eq(
#[track_caller]
fn cmp_value_eq(
__lhs: Self,
__lhs_value: ::fayalite::__std::borrow::Cow<'_, <Self as ::fayalite::ty::Type>::SimValue>,
__rhs: Self,
__rhs_value: ::fayalite::__std::borrow::Cow<'_, <Self as ::fayalite::ty::Type>::SimValue>,
) -> ::fayalite::__std::primitive::bool {
#value_eq_body
}
#[track_caller]
fn cmp_value_ne(
__lhs: Self,
__lhs_value: ::fayalite::__std::borrow::Cow<'_, <Self as ::fayalite::ty::Type>::SimValue>,
__rhs: Self,
__rhs_value: ::fayalite::__std::borrow::Cow<'_, <Self as ::fayalite::ty::Type>::SimValue>,
) -> ::fayalite::__std::primitive::bool {
#value_ne_body
}
#[track_caller]
fn cmp_expr_eq(
__lhs: ::fayalite::expr::Expr<Self>,
__rhs: ::fayalite::expr::Expr<Self>,
) -> ::fayalite::expr::Expr<::fayalite::int::Bool> {
#cmp_eq_body
#expr_eq_body
}
fn cmp_ne(
#[track_caller]
fn cmp_expr_ne(
__lhs: ::fayalite::expr::Expr<Self>,
__rhs: ::fayalite::expr::Expr<Self>,
) -> ::fayalite::expr::Expr<::fayalite::int::Bool> {
#cmp_ne_body
#expr_ne_body
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::SimValuePartialEq<Self> for #target #type_generics
#sim_value_where_clause
{
fn sim_value_eq(
__lhs: &::fayalite::sim::value::SimValue<Self>,
__rhs: &::fayalite::sim::value::SimValue<Self>,
) -> bool {
#sim_value_eq_body
#[track_caller]
fn cmp_valueless_eq(
__lhs: ::fayalite::expr::Valueless<Self>,
__rhs: ::fayalite::expr::Valueless<Self>,
) -> ::fayalite::expr::Valueless<::fayalite::int::Bool> {
#valueless_eq_body
}
#[track_caller]
fn cmp_valueless_ne(
__lhs: ::fayalite::expr::Valueless<Self>,
__rhs: ::fayalite::expr::Valueless<Self>,
) -> ::fayalite::expr::Valueless<::fayalite::int::Bool> {
#valueless_ne_body
}
}
}

View file

@ -159,10 +159,14 @@ impl ParsedEnum {
no_static: _,
no_runtime_generics: _,
cmp_eq,
ref get,
} = options.body;
if let Some((cmp_eq,)) = cmp_eq {
errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums");
}
if let Some((get, ..)) = get {
errors.error(get, "#[hdl(get(...))] is not allowed on enums");
}
attrs.retain(|attr| {
if attr.path().is_ident("repr") {
errors.error(attr, "#[repr] is not supported on #[hdl] enums");
@ -225,6 +229,7 @@ impl ToTokens for ParsedEnum {
no_static,
no_runtime_generics,
cmp_eq: _, // TODO: implement cmp_eq for enums
get: _,
} = &options.body;
let target = get_target(target, ident);
let mut struct_attrs = attrs.clone();
@ -549,7 +554,6 @@ impl ToTokens for ParsedEnum {
for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() {
if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
@ -571,7 +575,6 @@ impl ToTokens for ParsedEnum {
)
}
}
#[automatically_derived]
impl #impl_generics #sim_builder_ident #type_generics
#where_clause
{
@ -593,7 +596,6 @@ impl ToTokens for ParsedEnum {
}
} else {
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
@ -608,7 +610,6 @@ impl ToTokens for ParsedEnum {
)
}
}
#[automatically_derived]
impl #impl_generics #sim_builder_ident #type_generics
#where_clause
{
@ -1023,16 +1024,26 @@ impl ToTokens for ParsedEnum {
<::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES;
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::sim::value::ToSimValue
impl #static_impl_generics ::fayalite::expr::ValueType
for #sim_value_ident #static_type_generics
#static_where_clause
{
type Type = #target #static_type_generics;
type ValueCategory = ::fayalite::expr::value_category::ValueCategorySimValue;
fn ty(&self) -> <Self as ::fayalite::expr::ValueType>::Type {
::fayalite::ty::StaticType::TYPE
}
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::sim::value::ToSimValue
for #sim_value_ident #static_type_generics
#static_where_clause
{
fn to_sim_value(
&self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
::fayalite::sim::value::SimValue::from_value(
::fayalite::ty::StaticType::TYPE,
@ -1042,7 +1053,7 @@ impl ToTokens for ParsedEnum {
fn into_sim_value(
self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::sim::value::ToSimValue>::Type,
<Self as ::fayalite::expr::ValueType>::Type,
> {
::fayalite::sim::value::SimValue::from_value(
::fayalite::ty::StaticType::TYPE,

View file

@ -3,29 +3,264 @@
use crate::{
Errors, HdlAttr,
hdl_type_common::{
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, TypesParser,
get_target,
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType,
PhantomConstGetBound, TypesParser, WrappedInConst, common_derives, get_target, known_items,
},
kw,
};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{Attribute, Generics, Ident, ItemType, Token, Type, Visibility, parse_quote_spanned};
use quote::{ToTokens, format_ident, quote_spanned};
use syn::{
Attribute, Expr, Fields, GenericParam, Generics, Ident, ItemStruct, ItemType, Token, Type,
TypeGroup, TypeParam, TypeParen, Visibility, parse_quote_spanned, punctuated::Pair,
token::Paren,
};
#[derive(Clone, Debug)]
pub(crate) struct 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![;],
pub(crate) struct PhantomConstAccessorTypeParam {
attrs: Vec<Attribute>,
ident: Ident,
colon_token: Token![:],
phantom_const_get_bound: PhantomConstGetBound,
plus_token: Option<Token![+]>,
}
impl From<PhantomConstAccessorTypeParam> for TypeParam {
fn from(value: PhantomConstAccessorTypeParam) -> Self {
let PhantomConstAccessorTypeParam {
attrs,
ident,
colon_token,
phantom_const_get_bound,
plus_token,
} = value;
TypeParam {
attrs,
ident,
colon_token: Some(colon_token),
bounds: FromIterator::from_iter([Pair::new(
phantom_const_get_bound.into(),
plus_token,
)]),
eq_token: None,
default: None,
}
}
}
impl From<PhantomConstAccessorTypeParam> for GenericParam {
fn from(value: PhantomConstAccessorTypeParam) -> Self {
TypeParam::from(value).into()
}
}
impl PhantomConstAccessorTypeParam {
fn parse_opt(generic_param: GenericParam) -> Option<Self> {
let GenericParam::Type(TypeParam {
attrs,
ident,
colon_token,
bounds,
eq_token: None,
default: None,
}) = generic_param
else {
return None;
};
let colon_token = colon_token.unwrap_or(Token![:](ident.span()));
let mut bounds = bounds.into_pairs();
let (bound, plus_token) = bounds.next()?.into_tuple();
let phantom_const_get_bound = PhantomConstGetBound::parse_type_param_bound(bound)
.ok()?
.ok()?;
let None = bounds.next() else {
return None;
};
Some(Self {
attrs,
ident,
colon_token,
phantom_const_get_bound,
plus_token,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct PhantomConstAccessorGenerics {
lt_token: Token![<],
type_param: PhantomConstAccessorTypeParam,
comma_token: Option<Token![,]>,
gt_token: Token![>],
}
impl From<PhantomConstAccessorGenerics> for Generics {
fn from(value: PhantomConstAccessorGenerics) -> Self {
let PhantomConstAccessorGenerics {
lt_token,
type_param,
comma_token,
gt_token,
} = value;
Generics {
lt_token: Some(lt_token),
params: FromIterator::from_iter([Pair::new(type_param.into(), comma_token)]),
gt_token: Some(gt_token),
where_clause: None,
}
}
}
impl<'a> From<&'a PhantomConstAccessorGenerics> for Generics {
fn from(value: &'a PhantomConstAccessorGenerics) -> Self {
value.clone().into()
}
}
impl PhantomConstAccessorGenerics {
fn parse_opt(generics: Generics) -> Option<Self> {
let Generics {
lt_token,
params,
gt_token,
where_clause: None,
} = generics
else {
return None;
};
let mut params = params.into_pairs();
let (generic_param, comma_token) = params.next()?.into_tuple();
let type_param = PhantomConstAccessorTypeParam::parse_opt(generic_param)?;
let span = type_param.ident.span();
let lt_token = lt_token.unwrap_or(Token![<](span));
let gt_token = gt_token.unwrap_or(Token![>](span));
let None = params.next() else {
return None;
};
Some(Self {
lt_token,
type_param,
comma_token,
gt_token,
})
}
}
#[derive(Clone, Debug)]
pub(crate) enum ParsedTypeAlias {
TypeAlias {
attrs: Vec<Attribute>,
options: HdlAttr<ItemOptions, kw::hdl>,
vis: Visibility,
type_token: Token![type],
ident: Ident,
generics: MaybeParsed<ParsedGenerics, Generics>,
eq_token: Token![=],
ty: MaybeParsed<ParsedType, Type>,
semi_token: Token![;],
},
PhantomConstAccessor {
attrs: Vec<Attribute>,
options: HdlAttr<ItemOptions, kw::hdl>,
get: (kw::get, Paren, Expr),
vis: Visibility,
type_token: Token![type],
ident: Ident,
generics: PhantomConstAccessorGenerics,
eq_token: Token![=],
ty: Type,
ty_is_dyn_size: Option<known_items::DynSize>,
semi_token: Token![;],
},
}
impl ParsedTypeAlias {
fn ty_is_dyn_size(ty: &Type) -> Option<known_items::DynSize> {
match ty {
Type::Group(TypeGroup {
group_token: _,
elem,
}) => Self::ty_is_dyn_size(elem),
Type::Paren(TypeParen {
paren_token: _,
elem,
}) => Self::ty_is_dyn_size(elem),
Type::Path(syn::TypePath { qself: None, path }) => {
known_items::DynSize::parse_path(path.clone()).ok()
}
_ => None,
}
}
fn parse_phantom_const_accessor(
item: ItemType,
mut errors: Errors,
options: HdlAttr<ItemOptions, kw::hdl>,
get: (kw::get, Paren, Expr),
) -> syn::Result<Self> {
let ItemType {
attrs,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
} = item;
let ItemOptions {
outline_generated: _,
ref target,
custom_bounds,
no_static,
no_runtime_generics,
cmp_eq,
get: _,
} = options.body;
if let Some((no_static,)) = no_static {
errors.error(no_static, "no_static is not valid on type aliases");
}
if let Some((target, ..)) = target {
errors.error(
target,
"target is not implemented on PhantomConstGet type aliases",
);
}
if let Some((no_runtime_generics,)) = no_runtime_generics {
errors.error(
no_runtime_generics,
"no_runtime_generics is not implemented on PhantomConstGet type aliases",
);
}
if let Some((cmp_eq,)) = cmp_eq {
errors.error(cmp_eq, "cmp_eq is not valid on type aliases");
}
if let Some((custom_bounds,)) = custom_bounds {
errors.error(
custom_bounds,
"custom_bounds is not implemented on PhantomConstGet type aliases",
);
}
let Some(generics) = PhantomConstAccessorGenerics::parse_opt(generics) else {
errors.error(ident, "#[hdl(get(...))] type alias must be of the form:\ntype MyTypeGetter<P: PhantomConstGet<MyType>> = RetType;");
errors.finish()?;
unreachable!();
};
errors.finish()?;
let ty_is_dyn_size = Self::ty_is_dyn_size(&ty);
Ok(Self::PhantomConstAccessor {
attrs,
options,
get,
vis,
type_token,
ident,
generics,
eq_token,
ty: *ty,
ty_is_dyn_size,
semi_token,
})
}
fn parse(item: ItemType) -> syn::Result<Self> {
let ItemType {
mut attrs,
@ -51,7 +286,25 @@ impl ParsedTypeAlias {
no_static,
no_runtime_generics: _,
cmp_eq,
ref mut get,
} = options.body;
if let Some(get) = get.take() {
return Self::parse_phantom_const_accessor(
ItemType {
attrs,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
},
errors,
options,
get,
);
}
if let Some((no_static,)) = no_static {
errors.error(no_static, "no_static is not valid on type aliases");
}
@ -67,7 +320,7 @@ impl ParsedTypeAlias {
};
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
errors.finish()?;
Ok(Self {
Ok(Self::TypeAlias {
attrs,
options,
vis,
@ -83,54 +336,155 @@ impl ParsedTypeAlias {
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,
cmp_eq: _,
} = &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)
})
match self {
Self::TypeAlias {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
} => {
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static: _,
no_runtime_generics,
cmp_eq: _,
get: _,
} = &options.body;
let target = get_target(target, ident);
let mut type_attrs = attrs.clone();
type_attrs.push(parse_quote_spanned! {ident.span()=>
#[allow(type_alias_bounds)]
});
ItemType {
attrs: type_attrs,
vis: vis.clone(),
type_token: *type_token,
ident: ident.clone(),
generics: generics.into(),
eq_token: *eq_token,
ty: Box::new(ty.clone().into()),
semi_token: *semi_token,
}
.to_tokens(tokens);
if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) =
(generics, ty, no_runtime_generics)
{
generics.make_runtime_generics(tokens, vis, ident, &target, |context| {
ty.make_hdl_type_expr(context)
})
}
}
Self::PhantomConstAccessor {
attrs,
options,
get: (_get_kw, _get_paren, get_expr),
vis,
type_token,
ident,
generics,
eq_token,
ty,
ty_is_dyn_size,
semi_token,
} => {
let ItemOptions {
outline_generated: _,
target: _,
custom_bounds: _,
no_static: _,
no_runtime_generics: _,
cmp_eq: _,
get: _,
} = &options.body;
let span = ident.span();
let mut type_attrs = attrs.clone();
type_attrs.push(parse_quote_spanned! {span=>
#[allow(type_alias_bounds)]
});
let type_param_ident = &generics.type_param.ident;
let syn_generics = Generics::from(generics);
ItemType {
attrs: type_attrs,
vis: vis.clone(),
type_token: *type_token,
ident: ident.clone(),
generics: syn_generics.clone(),
eq_token: *eq_token,
ty: parse_quote_spanned! {span=>
<#ty as ::fayalite::phantom_const::ReturnSelfUnchanged<#type_param_ident>>::Type
},
semi_token: *semi_token,
}
.to_tokens(tokens);
let generics_accumulation_ident =
format_ident!("__{}__GenericsAccumulation", ident);
ItemStruct {
attrs: vec![
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types)]
},
],
vis: vis.clone(),
struct_token: Token![struct](span),
ident: generics_accumulation_ident.clone(),
generics: Generics::default(),
fields: Fields::Unnamed(parse_quote_spanned! {span=>
(())
}),
semi_token: Some(Token![;](span)),
}
.to_tokens(tokens);
quote_spanned! {span=>
#[allow(non_upper_case_globals, dead_code)]
#vis const #ident: #generics_accumulation_ident = #generics_accumulation_ident(());
}
.to_tokens(tokens);
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let (impl_generics, _type_generics, where_clause) = syn_generics.split_for_impl();
let phantom_const_get_ty = &generics.type_param.phantom_const_get_bound.ty;
let index_output = if let Some(ty_is_dyn_size) = ty_is_dyn_size {
known_items::usize(ty_is_dyn_size.span).to_token_stream()
} else {
ty.to_token_stream()
};
quote_spanned! {span=>
#[allow(non_upper_case_globals)]
#[automatically_derived]
impl #impl_generics ::fayalite::__std::ops::Index<#type_param_ident>
for #generics_accumulation_ident
#where_clause
{
type Output = #index_output;
fn index(&self, __param: #type_param_ident) -> &Self::Output {
::fayalite::phantom_const::type_alias_phantom_const_get_helper::<#phantom_const_get_ty, #index_output>(
__param,
#get_expr,
)
}
}
}
.to_tokens(tokens);
}
}
}
}
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
let item = ParsedTypeAlias::parse(item)?;
let outline_generated = item.options.body.outline_generated;
let outline_generated = match &item {
ParsedTypeAlias::TypeAlias { options, .. }
| ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated,
};
let mut contents = item.to_token_stream();
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "hdl-type-alias-");

View file

@ -8,9 +8,9 @@ use syn::{
AngleBracketedGenericArguments, Attribute, Block, ConstParam, Expr, ExprBlock, ExprGroup,
ExprIndex, ExprParen, ExprPath, ExprTuple, Field, FieldMutability, Fields, FieldsNamed,
FieldsUnnamed, GenericArgument, GenericParam, Generics, Ident, ImplGenerics, Index, ItemStruct,
Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, Turbofish, Type,
TypeGenerics, TypeGroup, TypeParam, TypeParen, TypePath, TypeTuple, Visibility, WhereClause,
WherePredicate,
Path, PathArguments, PathSegment, PredicateType, QSelf, Stmt, Token, TraitBound, Turbofish,
Type, TypeGenerics, TypeGroup, TypeParam, TypeParamBound, TypeParen, TypePath, TypeTuple,
Visibility, WhereClause, WherePredicate,
parse::{Parse, ParseStream},
parse_quote, parse_quote_spanned,
punctuated::{Pair, Punctuated},
@ -27,6 +27,7 @@ crate::options! {
NoStatic(no_static),
NoRuntimeGenerics(no_runtime_generics),
CmpEq(cmp_eq),
Get(get, Expr),
}
}
@ -2045,6 +2046,7 @@ pub(crate) mod known_items {
impl_known_item!(::fayalite::int::Size);
impl_known_item!(::fayalite::int::UInt);
impl_known_item!(::fayalite::int::UIntType);
impl_known_item!(::fayalite::phantom_const::PhantomConstGet);
impl_known_item!(::fayalite::reset::ResetType);
impl_known_item!(::fayalite::ty::CanonicalType);
impl_known_item!(::fayalite::ty::StaticType);
@ -2063,6 +2065,174 @@ pub(crate) mod known_items {
);
}
#[derive(Clone, Debug)]
pub(crate) struct PhantomConstGetBound {
pub(crate) phantom_const_get: known_items::PhantomConstGet,
pub(crate) colon2_token: Option<Token![::]>,
pub(crate) lt_token: Token![<],
pub(crate) ty: Type,
pub(crate) comma_token: Option<Token![,]>,
pub(crate) gt_token: Token![>],
}
impl PhantomConstGetBound {
pub(crate) fn parse_path_with_arguments(path: Path) -> syn::Result<Result<Self, Path>> {
match known_items::PhantomConstGet::parse_path_with_arguments(path) {
Ok((phantom_const_get, arguments)) => {
Self::parse_path_and_arguments(phantom_const_get, arguments).map(Ok)
}
Err(path) => Ok(Err(path)),
}
}
pub(crate) fn parse_path_and_arguments(
phantom_const_get: known_items::PhantomConstGet,
arguments: PathArguments,
) -> syn::Result<Self> {
let error = |arguments: PathArguments, message: &str| {
let mut path = phantom_const_get.path.clone();
path.segments.last_mut().expect("known to exist").arguments = arguments;
syn::Error::new_spanned(path, message)
};
match arguments {
PathArguments::None => Err(error(arguments, "missing generics for PhantomConstGet")),
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token,
lt_token,
args,
gt_token,
}) => {
let error = |args: Punctuated<GenericArgument, Token![,]>, message| {
error(
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token,
lt_token,
args,
gt_token,
}),
message,
)
};
let mut args = args.into_pairs().peekable();
let Some((generic_argument, comma_token)) = args.next().map(Pair::into_tuple)
else {
return Err(error(
Default::default(),
"PhantomConstGet takes a type argument but no generic arguments were supplied",
));
};
if args.peek().is_some() {
return Err(error(
[Pair::new(generic_argument, comma_token)]
.into_iter()
.chain(args)
.collect(),
"PhantomConstGet takes a single type argument but too many generic arguments were supplied",
));
};
let GenericArgument::Type(ty) = generic_argument else {
return Err(error(
Punctuated::from_iter([Pair::new(generic_argument, comma_token)]),
"PhantomConstGet requires a type argument",
));
};
Ok(Self {
phantom_const_get,
colon2_token,
lt_token,
ty,
comma_token,
gt_token,
})
}
PathArguments::Parenthesized(_) => Err(error(
arguments,
"parenthetical generics are not valid for PhantomConstGet",
)),
}
}
pub(crate) fn parse_type_param_bound(
bound: TypeParamBound,
) -> syn::Result<Result<Self, TypeParamBound>> {
let TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path,
}) = bound
else {
return Ok(Err(bound));
};
Ok(match Self::parse_path_with_arguments(path)? {
Ok(v) => Ok(v),
Err(path) => Err(TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path,
})),
})
}
}
impl ToTokens for PhantomConstGetBound {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
phantom_const_get,
colon2_token,
lt_token,
ty,
comma_token,
gt_token,
} = self;
phantom_const_get.to_tokens(tokens);
colon2_token.to_tokens(tokens);
lt_token.to_tokens(tokens);
ty.to_tokens(tokens);
comma_token.to_tokens(tokens);
gt_token.to_tokens(tokens);
}
}
impl From<PhantomConstGetBound> for Path {
fn from(value: PhantomConstGetBound) -> Self {
let PhantomConstGetBound {
phantom_const_get,
colon2_token,
lt_token,
ty,
comma_token,
gt_token,
} = value;
let mut path = phantom_const_get.path;
path.segments.last_mut().expect("known to exist").arguments =
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token,
lt_token,
args: FromIterator::from_iter([Pair::new(GenericArgument::Type(ty), comma_token)]),
gt_token,
});
path
}
}
impl From<PhantomConstGetBound> for TraitBound {
fn from(value: PhantomConstGetBound) -> Self {
let path = Path::from(value);
TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path,
}
}
}
impl From<PhantomConstGetBound> for TypeParamBound {
fn from(value: PhantomConstGetBound) -> Self {
TraitBound::from(value).into()
}
}
macro_rules! impl_bounds {
(
#[struct = $struct_type:ident]
@ -2070,6 +2240,10 @@ macro_rules! impl_bounds {
$(
$Variant:ident,
)*
$(
#[has_body]
$VariantHasBody:ident($variant_has_body_ty:ty),
)*
$(
#[unknown]
$Unknown:ident,
@ -2079,6 +2253,7 @@ macro_rules! impl_bounds {
#[derive(Clone, Debug)]
$vis enum $enum_type {
$($Variant(known_items::$Variant),)*
$($VariantHasBody($variant_has_body_ty),)*
$($Unknown(syn::TypeParamBound),)?
}
@ -2088,31 +2263,42 @@ macro_rules! impl_bounds {
}
})*
$(impl From<$variant_has_body_ty> for $enum_type {
fn from(v: $variant_has_body_ty) -> Self {
Self::$VariantHasBody(v)
}
})*
impl ToTokens for $enum_type {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
$(Self::$Variant(v) => v.to_tokens(tokens),)*
$(Self::$VariantHasBody(v) => v.to_tokens(tokens),)*
$(Self::$Unknown(v) => v.to_tokens(tokens),)?
}
}
}
impl $enum_type {
$vis fn parse_path(path: Path) -> Result<Self, Path> {
$vis fn parse_path_with_arguments(path: Path) -> syn::Result<Result<Self, Path>> {
#![allow(unreachable_code)]
$(let path = match known_items::$Variant::parse_path(path) {
Ok(v) => return Ok(Self::$Variant(v)),
Ok(v) => return Ok(Ok(Self::$Variant(v))),
Err(path) => path,
};)*
$(return Ok(Self::$Unknown(syn::TraitBound {
$(let path = match <$variant_has_body_ty>::parse_path_with_arguments(path)? {
Ok(v) => return Ok(Ok(Self::$VariantHasBody(v))),
Err(path) => path,
};)*
$(return Ok(Ok(Self::$Unknown(syn::TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: None,
path,
}.into()));)?
Err(path)
}.into())));)?
Ok(Err(path))
}
$vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result<Self, syn::TypeParamBound> {
$vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> syn::Result<Result<Self, syn::TypeParamBound>> {
#![allow(unreachable_code)]
if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound {
if let syn::TraitBound {
@ -2121,24 +2307,24 @@ macro_rules! impl_bounds {
lifetimes: None,
path: _,
} = trait_bound {
match Self::parse_path(trait_bound.path) {
Ok(retval) => return Ok(retval),
match Self::parse_path_with_arguments(trait_bound.path)? {
Ok(retval) => return Ok(Ok(retval)),
Err(path) => trait_bound.path = path,
}
}
type_param_bound = trait_bound.into();
}
$(return Ok(Self::$Unknown(type_param_bound));)?
Err(type_param_bound)
$(return Ok(Ok(Self::$Unknown(type_param_bound)));)?
Ok(Err(type_param_bound))
}
}
impl Parse for $enum_type {
fn parse(input: ParseStream) -> syn::Result<Self> {
Self::parse_type_param_bound(input.parse()?)
Self::parse_type_param_bound(input.parse()?)?
.map_err(|type_param_bound| syn::Error::new_spanned(
type_param_bound,
format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")),
format_args!("expected one of: {}", [$(stringify!($Variant),)* $(stringify!($VariantHasBody)),*].join(", ")),
))
}
}
@ -2147,6 +2333,7 @@ macro_rules! impl_bounds {
#[allow(non_snake_case)]
$vis struct $struct_type {
$($vis $Variant: Option<known_items::$Variant>,)*
$($vis $VariantHasBody: Option<$variant_has_body_ty>,)*
$($vis $Unknown: Vec<syn::TypeParamBound>,)?
}
@ -2159,6 +2346,11 @@ macro_rules! impl_bounds {
separator = Some(<Token![+]>::default());
v.to_tokens(tokens);
})*
$(if let Some(v) = &self.$VariantHasBody {
separator.to_tokens(tokens);
separator = Some(<Token![+]>::default());
v.to_tokens(tokens);
})*
$(for v in &self.$Unknown {
separator.to_tokens(tokens);
separator = Some(<Token![+]>::default());
@ -2172,6 +2364,7 @@ macro_rules! impl_bounds {
#[allow(non_snake_case)]
$vis struct Iter {
$($Variant: Option<known_items::$Variant>,)*
$($VariantHasBody: Option<$variant_has_body_ty>,)*
$($Unknown: std::vec::IntoIter<syn::TypeParamBound>,)?
}
@ -2182,6 +2375,7 @@ macro_rules! impl_bounds {
fn into_iter(self) -> Self::IntoIter {
Iter {
$($Variant: self.$Variant,)*
$($VariantHasBody: self.$VariantHasBody,)*
$($Unknown: self.$Unknown.into_iter(),)?
}
}
@ -2196,6 +2390,11 @@ macro_rules! impl_bounds {
return Some($enum_type::$Variant(value));
}
)*
$(
if let Some(value) = self.$VariantHasBody.take() {
return Some($enum_type::$VariantHasBody(value));
}
)*
$(
if let Some(value) = self.$Unknown.next() {
return Some($enum_type::$Unknown(value));
@ -2211,6 +2410,11 @@ macro_rules! impl_bounds {
init = f(init, $enum_type::$Variant(value));
}
)*
$(
if let Some(value) = self.$VariantHasBody.take() {
init = f(init, $enum_type::$VariantHasBody(value));
}
)*
$(
if let Some(value) = self.$Unknown.next() {
init = f(init, $enum_type::$Unknown(value));
@ -2227,6 +2431,9 @@ macro_rules! impl_bounds {
$($enum_type::$Variant(v) => {
self.$Variant = Some(v);
})*
$($enum_type::$VariantHasBody(v) => {
self.$VariantHasBody = Some(v);
})*
$($enum_type::$Unknown(v) => {
self.$Unknown.push(v);
})?
@ -2248,6 +2455,9 @@ macro_rules! impl_bounds {
$(if let Some(v) = v.$Variant {
self.$Variant = Some(v);
})*
$(if let Some(v) = v.$VariantHasBody {
self.$VariantHasBody = Some(v);
})*
$(self.$Unknown.extend(v.$Unknown);)*
});
}
@ -2302,6 +2512,8 @@ impl_bounds! {
Size,
StaticType,
Type,
#[has_body]
PhantomConstGet(PhantomConstGetBound),
#[unknown]
Unknown,
}
@ -2317,6 +2529,8 @@ impl_bounds! {
ResetType,
StaticType,
Type,
#[has_body]
PhantomConstGet(PhantomConstGetBound),
#[unknown]
Unknown,
}
@ -2332,6 +2546,7 @@ impl From<ParsedTypeBound> for ParsedBound {
ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v),
ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v),
ParsedTypeBound::Type(v) => ParsedBound::Type(v),
ParsedTypeBound::PhantomConstGet(v) => ParsedBound::PhantomConstGet(v),
ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v),
}
}
@ -2347,6 +2562,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
ResetType,
StaticType,
Type,
PhantomConstGet,
Unknown,
} = value;
Self {
@ -2359,6 +2575,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
Size: None,
StaticType,
Type,
PhantomConstGet,
Unknown,
}
}
@ -2395,6 +2612,10 @@ impl ParsedTypeBound {
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]),
Self::PhantomConstGet(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]),
}
}
@ -2430,6 +2651,7 @@ impl From<ParsedSizeTypeBounds> for ParsedBounds {
Size,
StaticType: None,
Type: None,
PhantomConstGet: None,
Unknown: vec![],
}
}
@ -2532,6 +2754,9 @@ impl ParsedBound {
Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)),
Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)),
Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)),
Self::PhantomConstGet(v) => {
ParsedBoundCategory::Type(ParsedTypeBound::PhantomConstGet(v))
}
Self::Unknown(v) => ParsedBoundCategory::Unknown(v),
}
}
@ -3417,7 +3642,8 @@ impl ParsedGenerics {
| ParsedTypeBound::BundleType(_)
| ParsedTypeBound::EnumType(_)
| ParsedTypeBound::IntType(_)
| ParsedTypeBound::ResetType(_) => {
| ParsedTypeBound::ResetType(_)
| ParsedTypeBound::PhantomConstGet(_) => {
errors.error(bound, "bounds on mask types are not implemented");
}
ParsedTypeBound::StaticType(bound) => {
@ -4386,3 +4612,124 @@ impl MakeHdlTypeExpr for ParsedTypeTuple {
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParsedSimpleVisibility {
Public(Token![pub]),
PubCrate {
pub_token: Token![pub],
paren_token: Paren,
crate_token: Token![crate],
},
Inherited,
}
impl From<ParsedSimpleVisibility> for Visibility {
fn from(value: ParsedSimpleVisibility) -> Self {
match value {
ParsedSimpleVisibility::Public(v) => Visibility::Public(v),
ParsedSimpleVisibility::PubCrate {
pub_token,
paren_token,
crate_token,
} => Visibility::Restricted(syn::VisRestricted {
pub_token,
paren_token,
in_token: None,
path: Box::new(Ident::new("crate", crate_token.span).into()),
}),
ParsedSimpleVisibility::Inherited => Visibility::Inherited,
}
}
}
impl PartialOrd for ParsedSimpleVisibility {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ParsedSimpleVisibility {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.visibility_level().cmp(&other.visibility_level())
}
}
impl ParsedSimpleVisibility {
const VISIBILITY_LEVEL_INHERITED: u8 = 0;
const VISIBILITY_LEVEL_RESTRICTED: u8 = 1 + Self::VISIBILITY_LEVEL_INHERITED;
const VISIBILITY_LEVEL_PUB_CRATE: u8 = 1 + Self::VISIBILITY_LEVEL_RESTRICTED;
const VISIBILITY_LEVEL_PUB: u8 = 1 + Self::VISIBILITY_LEVEL_PUB_CRATE;
fn visibility_level(self) -> u8 {
match self {
Self::Public(_) => Self::VISIBILITY_LEVEL_PUB,
Self::PubCrate { .. } => Self::VISIBILITY_LEVEL_PUB_CRATE,
Self::Inherited => Self::VISIBILITY_LEVEL_INHERITED,
}
}
pub(crate) fn parse(vis: Visibility) -> Result<Self, syn::VisRestricted> {
match vis {
Visibility::Public(v) => Ok(Self::Public(v)),
Visibility::Restricted(syn::VisRestricted {
pub_token,
paren_token,
in_token: None,
path,
}) if path.is_ident("crate") => Ok(Self::PubCrate {
pub_token,
paren_token,
crate_token: Token![crate](path.get_ident().expect("just checked").span()),
}),
Visibility::Restricted(v) => Err(v),
Visibility::Inherited => Ok(Self::Inherited),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParsedVisibility {
Simple(ParsedSimpleVisibility),
Restricted(syn::VisRestricted),
}
impl From<ParsedVisibility> for Visibility {
fn from(value: ParsedVisibility) -> Self {
match value {
ParsedVisibility::Simple(v) => v.into(),
ParsedVisibility::Restricted(v) => Visibility::Restricted(v),
}
}
}
impl PartialOrd for ParsedVisibility {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(ParsedVisibility::Simple(l), ParsedVisibility::Simple(r)) => Some(l.cmp(r)),
(ParsedVisibility::Simple(l), ParsedVisibility::Restricted(_)) => Some(
l.visibility_level()
.cmp(&ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED),
),
(ParsedVisibility::Restricted(_), ParsedVisibility::Simple(r)) => {
Some(ParsedSimpleVisibility::VISIBILITY_LEVEL_RESTRICTED.cmp(&r.visibility_level()))
}
(ParsedVisibility::Restricted(l), ParsedVisibility::Restricted(r)) => {
(l == r).then_some(std::cmp::Ordering::Equal)
}
}
}
}
impl ParsedVisibility {
#[allow(dead_code)]
pub(crate) fn parse(vis: Visibility) -> Self {
match ParsedSimpleVisibility::parse(vis) {
Ok(simple) => Self::Simple(simple),
Err(restricted) => Self::Restricted(restricted),
}
}
#[allow(dead_code)]
pub(crate) fn min<'a>(&'a self, other: &'a Self) -> Option<&'a Self> {
self.partial_cmp(other)
.map(|ord| if ord.is_lt() { self } else { other })
}
}

View file

@ -66,6 +66,7 @@ mod kw {
}
custom_keyword!(__evaluated_cfgs);
custom_keyword!(add_platform_io);
custom_keyword!(all);
custom_keyword!(any);
custom_keyword!(cfg);
@ -75,6 +76,7 @@ mod kw {
custom_keyword!(connect_inexact);
custom_keyword!(custom_bounds);
custom_keyword!(flip);
custom_keyword!(get);
custom_keyword!(hdl);
custom_keyword!(hdl_module);
custom_keyword!(incomplete_wire);
@ -885,7 +887,13 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
}
}
let _print_on_panic = PrintOnPanic(&contents);
let contents = prettyplease::unparse(&parse_quote! { #contents });
let mut parse_err = None;
let (Ok(contents) | Err(contents)) = syn::parse2(contents.clone())
.map(|file| prettyplease::unparse(&file))
.map_err(|e| {
parse_err = Some(e);
contents.to_string()
});
let hash = <sha2::Sha256 as sha2::Digest>::digest(&contents);
let hash = base16ct::HexDisplay(&hash[..5]);
file.write_all(contents.as_bytes()).unwrap();
@ -897,9 +905,26 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
e.unwrap();
}
}
eprintln!("generated {}", dest_file.display());
let log_msg = if let Some(parse_err) = parse_err {
format!(
"fayalite-proc-macros-impl internal error:\nfailed to parse generated output: {parse_err}\nunformatted output is in: {}\n",
dest_file.display()
)
} else {
format!("generated {}\n", dest_file.display())
};
// write message atomically if possible
let mut stderr = std::io::stderr().lock();
let write_result = stderr.write_all(log_msg.as_bytes());
let flush_result = stderr.flush();
drop(stderr); // unlock before we try to panic
write_result.unwrap();
flush_result.unwrap();
std::io::stderr()
.lock()
.write_all(log_msg.as_bytes())
.unwrap();
let dest_file = dest_file.to_str().unwrap();
quote! {
include!(#dest_file);
}

View file

@ -4,7 +4,7 @@ use crate::{
Errors, HdlAttr, PairsIterExt,
hdl_type_common::{ParsedGenerics, SplitForImpl},
kw,
module::transform_body::{HdlLet, HdlLetKindIO},
module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO},
options,
};
use proc_macro2::TokenStream;
@ -39,7 +39,7 @@ pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Res
if name == "m" {
Err(Error::new_spanned(
name,
"name conflicts with implicit `m: &mut ModuleBuilder<_>`",
"name conflicts with implicit `m: &ModuleBuilder`",
))
} else {
Ok(())
@ -67,7 +67,7 @@ struct ModuleFnModule {
vis: Visibility,
sig: Signature,
block: Box<Block>,
struct_generics: ParsedGenerics,
struct_generics: Option<ParsedGenerics>,
the_struct: TokenStream,
}
@ -290,7 +290,7 @@ impl ModuleFn {
paren_token,
body,
} => {
debug_assert!(io.is_empty());
debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty()));
return Ok(Self(ModuleFnImpl::Fn {
attrs,
config_options: HdlAttr {
@ -322,6 +322,21 @@ impl ModuleFn {
body,
},
};
let io = match io {
ModuleIOOrAddPlatformIO::ModuleIO(io) => io,
ModuleIOOrAddPlatformIO::AddPlatformIO => {
return Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
attrs,
config_options,
module_kind: module_kind.unwrap(),
vis,
sig,
block,
struct_generics: None,
the_struct: TokenStream::new(),
})));
}
};
let (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
struct_generics.split_for_impl();
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
@ -364,7 +379,7 @@ impl ModuleFn {
vis,
sig,
block,
struct_generics,
struct_generics: Some(struct_generics),
the_struct,
})))
}
@ -433,9 +448,14 @@ impl ModuleFn {
ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal },
};
let fn_name = &outer_sig.ident;
let (_struct_impl_generics, struct_type_generics, _struct_where_clause) =
struct_generics.split_for_impl();
let struct_ty = quote! {#fn_name #struct_type_generics};
let struct_ty = match struct_generics {
Some(struct_generics) => {
let (_struct_impl_generics, struct_type_generics, _struct_where_clause) =
struct_generics.split_for_impl();
quote! {#fn_name #struct_type_generics}
}
None => quote! {::fayalite::bundle::Bundle},
};
body_sig.ident = parse_quote! {__body};
body_sig
.inputs

View file

@ -39,6 +39,7 @@ options! {
pub(crate) enum LetFnKind {
Input(input),
Output(output),
AddPlatformIO(add_platform_io),
Instance(instance),
RegBuilder(reg_builder),
Wire(wire),
@ -216,6 +217,49 @@ impl HdlLetKindToTokens for HdlLetKindInstance {
}
}
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindAddPlatformIO {
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) add_platform_io: kw::add_platform_io,
pub(crate) paren: Paren,
pub(crate) platform_io_builder: Box<Expr>,
}
impl ParseTypes<Self> for HdlLetKindAddPlatformIO {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindAddPlatformIO<> {
m: kw::m,
dot_token: Token![.],
add_platform_io: kw::add_platform_io,
paren: Paren,
platform_io_builder: Box<Expr>,
}
}
impl HdlLetKindToTokens for HdlLetKindAddPlatformIO {
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
m,
dot_token,
add_platform_io,
paren,
platform_io_builder,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
add_platform_io.to_tokens(tokens);
paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens));
}
}
#[derive(Clone, Debug)]
pub(crate) struct RegBuilderClockDomain {
pub(crate) dot_token: Token![.],
@ -711,6 +755,7 @@ impl HdlLetKindMemory {
#[derive(Clone, Debug)]
pub(crate) enum HdlLetKind<IOType = ParsedType> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
AddPlatformIO(HdlLetKindAddPlatformIO),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
@ -721,6 +766,7 @@ pub(crate) enum HdlLetKind<IOType = ParsedType> {
impl_fold! {
enum HdlLetKind<IOType,> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
AddPlatformIO(HdlLetKindAddPlatformIO),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
@ -736,6 +782,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::AddPlatformIO(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::AddPlatformIO)
}
HdlLetKind::Incomplete(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete)
}
@ -861,6 +910,23 @@ impl HdlLetKindParse for HdlLetKind<Type> {
ModuleIOKind::Output(output),
)
.map(Self::IO),
LetFnKind::AddPlatformIO((add_platform_io,)) => {
if let Some(parsed_ty) = parsed_ty {
return Err(Error::new_spanned(
parsed_ty.1,
"type annotation not allowed for instance",
));
}
let (m, dot_token) = unwrap_m_dot(m_dot, kind)?;
let paren_contents;
Ok(Self::AddPlatformIO(HdlLetKindAddPlatformIO {
m,
dot_token,
add_platform_io,
paren: parenthesized!(paren_contents in input),
platform_io_builder: paren_contents.call(parse_single_fn_arg)?,
}))
}
LetFnKind::Instance((instance,)) => {
if let Some(parsed_ty) = parsed_ty {
return Err(Error::new_spanned(
@ -936,6 +1002,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn ty_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.ty_to_tokens(tokens),
HdlLetKind::AddPlatformIO(v) => v.ty_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens),
HdlLetKind::Instance(v) => v.ty_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens),
@ -947,6 +1014,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.expr_to_tokens(tokens),
HdlLetKind::AddPlatformIO(v) => v.expr_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens),
HdlLetKind::Instance(v) => v.expr_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens),
@ -1149,7 +1217,7 @@ impl<T: ToString> ToTokens for ImplicitName<T> {
struct Visitor<'a> {
module_kind: Option<ModuleKind>,
errors: Errors,
io: Vec<ModuleIO>,
io: ModuleIOOrAddPlatformIO,
block_depth: usize,
parsed_generics: &'a ParsedGenerics,
}
@ -1289,7 +1357,81 @@ impl Visitor<'_> {
}),
semi_token: hdl_let.semi_token,
};
self.io.push(hdl_let);
match &mut self.io {
ModuleIOOrAddPlatformIO::ModuleIO(io) => io.push(hdl_let),
ModuleIOOrAddPlatformIO::AddPlatformIO => {
self.errors.error(
kind,
"can't have other inputs/outputs in a module using m.add_platform_io()",
);
}
}
let_stmt
}
fn process_hdl_let_add_platform_io(
&mut self,
hdl_let: HdlLet<HdlLetKindAddPlatformIO>,
) -> Local {
let HdlLet {
mut attrs,
hdl_attr: _,
let_token,
mut_token,
ref name,
eq_token,
kind:
HdlLetKindAddPlatformIO {
m,
dot_token,
add_platform_io,
paren,
platform_io_builder,
},
semi_token,
} = hdl_let;
let mut expr = quote! {#m #dot_token #add_platform_io};
paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
name,
span: name.span(),
};
quote_spanned! {name.span()=>
#name_str, #platform_io_builder
}
.to_tokens(expr);
});
self.require_module(add_platform_io);
attrs.push(parse_quote_spanned! {let_token.span=>
#[allow(unused_variables)]
});
let let_stmt = Local {
attrs,
let_token,
pat: parse_quote! { #mut_token #name },
init: Some(LocalInit {
eq_token,
expr: parse_quote! { #expr },
diverge: None,
}),
semi_token,
};
match &mut self.io {
ModuleIOOrAddPlatformIO::ModuleIO(io) => {
for io in io {
self.errors.error(
io.kind.kind,
"can't have other inputs/outputs in a module using m.add_platform_io()",
);
}
}
ModuleIOOrAddPlatformIO::AddPlatformIO => {
self.errors.error(
add_platform_io,
"can't use m.add_platform_io() more than once in a single module",
);
}
}
self.io = ModuleIOOrAddPlatformIO::AddPlatformIO;
let_stmt
}
fn process_hdl_let_instance(&mut self, hdl_let: HdlLet<HdlLetKindInstance>) -> Local {
@ -1510,6 +1652,7 @@ impl Visitor<'_> {
}
the_match! {
IO => process_hdl_let_io,
AddPlatformIO => process_hdl_let_add_platform_io,
Incomplete => process_hdl_let_incomplete,
Instance => process_hdl_let_instance,
RegBuilder => process_hdl_let_reg_builder,
@ -1753,15 +1896,20 @@ impl Fold for Visitor<'_> {
}
}
pub(crate) enum ModuleIOOrAddPlatformIO {
ModuleIO(Vec<ModuleIO>),
AddPlatformIO,
}
pub(crate) fn transform_body(
module_kind: Option<ModuleKind>,
mut body: Box<Block>,
parsed_generics: &ParsedGenerics,
) -> syn::Result<(Box<Block>, Vec<ModuleIO>)> {
) -> syn::Result<(Box<Block>, ModuleIOOrAddPlatformIO)> {
let mut visitor = Visitor {
module_kind,
errors: Errors::new(),
io: vec![],
io: ModuleIOOrAddPlatformIO::ModuleIO(vec![]),
block_depth: 0,
parsed_generics,
};

View file

@ -88,6 +88,9 @@ impl Visitor<'_> {
field.expr = parse_quote_spanned! {field.member.span()=>
::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr))
};
field
.colon_token
.get_or_insert(Token![:](field.member.span()));
}
return parse_quote_spanned! {name_span=>
{

View file

@ -10,7 +10,7 @@ use crate::{
};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt, format_ident, quote_spanned};
use std::collections::BTreeSet;
use std::collections::BTreeMap;
use syn::{
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr,
PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment,
@ -24,65 +24,65 @@ use syn::{
macro_rules! visit_trait {
(
$($vis:vis fn $fn:ident($state:ident: _, $value:ident: &$Value:ty) $block:block)*
$($vis:vis fn $fn:ident($state:ident: _, $value:ident: &mut $Value:ty) $block:block)*
) => {
trait VisitMatchPat<'a> {
$(fn $fn(&mut self, $value: &'a $Value) {
$(fn $fn(&mut self, $value: &'a mut $Value) {
$fn(self, $value);
})*
}
$($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a $Value) $block)*
$($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a mut $Value) $block)*
};
}
visit_trait! {
fn visit_match_pat_binding(_state: _, v: &MatchPatBinding) {
let MatchPatBinding { ident: _ } = v;
fn visit_match_pat_binding(_state: _, v: &mut MatchPatBinding) {
let MatchPatBinding { mutability: _, ident: _ } = v;
}
fn visit_match_pat_wild(_state: _, v: &MatchPatWild) {
fn visit_match_pat_wild(_state: _, v: &mut MatchPatWild) {
let MatchPatWild { underscore_token: _ } = v;
}
fn visit_match_pat_rest(_state: _, v: &MatchPatRest) {
fn visit_match_pat_rest(_state: _, v: &mut MatchPatRest) {
let MatchPatRest { dot2_token: _ } = v;
}
fn visit_match_pat_paren(state: _, v: &MatchPatParen<MatchPat>) {
fn visit_match_pat_paren(state: _, v: &mut MatchPatParen<MatchPat>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat(pat);
}
fn visit_match_pat_paren_simple(state: _, v: &MatchPatParen<MatchPatSimple>) {
fn visit_match_pat_paren_simple(state: _, v: &mut MatchPatParen<MatchPatSimple>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_or(state: _, v: &MatchPatOr<MatchPat>) {
fn visit_match_pat_or(state: _, v: &mut MatchPatOr<MatchPat>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat(v);
}
}
fn visit_match_pat_or_simple(state: _, v: &MatchPatOr<MatchPatSimple>) {
fn visit_match_pat_or_simple(state: _, v: &mut MatchPatOr<MatchPatSimple>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_struct_field(state: _, v: &MatchPatStructField) {
fn visit_match_pat_struct_field(state: _, v: &mut MatchPatStructField) {
let MatchPatStructField { field_name: _, colon_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_struct(state: _, v: &MatchPatStruct) {
fn visit_match_pat_struct(state: _, v: &mut MatchPatStruct) {
let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v;
for v in fields {
state.visit_match_pat_struct_field(v);
}
}
fn visit_match_pat_tuple(state: _, v: &MatchPatTuple) {
fn visit_match_pat_tuple(state: _, v: &mut MatchPatTuple) {
let MatchPatTuple { paren_token: _, fields } = v;
for v in fields {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) {
fn visit_match_pat_enum_variant(state: _, v: &mut MatchPatEnumVariant) {
let MatchPatEnumVariant {
match_span:_,
sim:_,
@ -95,7 +95,7 @@ visit_trait! {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_simple(state: _, v: &MatchPatSimple) {
fn visit_match_pat_simple(state: _, v: &mut MatchPatSimple) {
match v {
MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v),
MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v),
@ -104,7 +104,7 @@ visit_trait! {
MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v),
}
}
fn visit_match_pat(state: _, v: &MatchPat) {
fn visit_match_pat(state: _, v: &mut MatchPat) {
match v {
MatchPat::Simple(v) => state.visit_match_pat_simple(v),
MatchPat::Or(v) => state.visit_match_pat_or(v),
@ -118,13 +118,15 @@ visit_trait! {
with_debug_clone_and_fold! {
struct MatchPatBinding<> {
mutability: Option<Token![mut]>,
ident: Ident,
}
}
impl ToTokens for MatchPatBinding {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { ident } = self;
let Self { mutability, ident } = self;
mutability.to_tokens(tokens);
ident.to_tokens(tokens);
}
}
@ -211,12 +213,20 @@ impl ToTokens for MatchPatStructField {
colon_token,
pat,
} = self;
field_name.to_tokens(tokens);
if let (None, MatchPatSimple::Binding(MatchPatBinding { ident })) = (colon_token, pat) {
if let (
None,
MatchPatSimple::Binding(MatchPatBinding {
mutability: _,
ident,
}),
) = (colon_token, pat)
{
if field_name == ident {
pat.to_tokens(tokens);
return;
}
}
field_name.to_tokens(tokens);
colon_token
.unwrap_or_else(|| Token![:](field_name.span()))
.to_tokens(tokens);
@ -450,7 +460,7 @@ trait ParseMatchPat: Sized {
Pat::Ident(PatIdent {
attrs: _,
by_ref,
mutability,
mut mutability,
ident,
subpat,
}) => {
@ -459,10 +469,13 @@ trait ParseMatchPat: Sized {
.errors
.error(by_ref, "ref not allowed in #[hdl] patterns");
}
if let Some(mutability) = mutability {
state
.errors
.error(mutability, "mut not allowed in #[hdl] patterns");
if let Some(mut_token) = mutability {
if state.sim.is_none() {
state
.errors
.error(mut_token, "mut not allowed in #[hdl] patterns");
mutability = None; // avoid duplicate errors
}
}
if let Some((at_token, _)) = subpat {
state
@ -474,18 +487,26 @@ trait ParseMatchPat: Sized {
variant_path,
enum_path,
variant_name,
}) => Self::enum_variant(
state,
MatchPatEnumVariant {
match_span: state.match_span,
sim: state.sim,
variant_path,
enum_path,
variant_name,
field: None,
},
),
}) => {
if let Some(mut_token) = mutability {
state
.errors
.error(mut_token, "mut not allowed on unit variants");
}
Self::enum_variant(
state,
MatchPatEnumVariant {
match_span: state.match_span,
sim: state.sim,
variant_path,
enum_path,
variant_name,
field: None,
},
)
}
Err(ident) => Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
mutability,
ident,
}))),
}
@ -980,15 +1001,16 @@ struct HdlMatchParseState<'a> {
struct HdlLetPatVisitState<'a> {
errors: &'a mut Errors,
bindings: BTreeSet<&'a Ident>,
bindings: BTreeMap<Ident, MatchPatBinding>,
}
impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
fn visit_match_pat_binding(&mut self, v: &'a MatchPatBinding) {
self.bindings.insert(&v.ident);
fn visit_match_pat_binding(&mut self, v: &'a mut MatchPatBinding) {
self.bindings.insert(v.ident.clone(), v.clone());
v.mutability = None;
}
fn visit_match_pat_or(&mut self, v: &'a MatchPatOr<MatchPat>) {
fn visit_match_pat_or(&mut self, v: &'a mut MatchPatOr<MatchPat>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
@ -998,7 +1020,7 @@ impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
visit_match_pat_or(self, v);
}
fn visit_match_pat_or_simple(&mut self, v: &'a MatchPatOr<MatchPatSimple>) {
fn visit_match_pat_or_simple(&mut self, v: &'a mut MatchPatOr<MatchPatSimple>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
@ -1008,7 +1030,7 @@ impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
visit_match_pat_or_simple(self, v);
}
fn visit_match_pat_enum_variant(&mut self, v: &'a MatchPatEnumVariant) {
fn visit_match_pat_enum_variant(&mut self, v: &'a mut MatchPatEnumVariant) {
self.errors.error(v, "refutable pattern in let statement");
}
}
@ -1048,7 +1070,7 @@ impl Visitor<'_> {
.error(else_, "#[hdl] let ... else { ... } is not implemented");
return empty_let();
}
let Ok(pat) = MatchPat::parse(
let Ok(mut pat) = MatchPat::parse(
&mut HdlMatchParseState {
sim,
match_span: span,
@ -1060,20 +1082,27 @@ impl Visitor<'_> {
};
let mut state = HdlLetPatVisitState {
errors: &mut self.errors,
bindings: BTreeSet::new(),
bindings: BTreeMap::new(),
};
state.visit_match_pat(&pat);
state.visit_match_pat(&mut pat);
let HdlLetPatVisitState {
errors: _,
bindings,
} = state;
let bindings_idents = bindings.keys();
let bindings = bindings.values();
let retval = if sim.is_some() {
parse_quote_spanned! {span=>
let (#(#bindings,)*) = {
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
let __match_value = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr));
#let_token #pat #eq_token ::fayalite::sim::value::SimValue::into_value(__match_value) #semi_token
(#(#bindings,)*)
let __match_value = #expr;
let __match_value = {
use ::fayalite::sim::value::match_sim_value::*;
// use method syntax to deduce the correct trait to call
::fayalite::sim::value::match_sim_value::MatchSimValueHelper::new(__match_value).__fayalite_match_sim_value()
};
#let_token #pat #eq_token __match_value #semi_token
(#(#bindings_idents,)*)
};
}
} else {
@ -1105,7 +1134,7 @@ impl Visitor<'_> {
__match_variant,
);
#let_token #pat #eq_token __match_variant #semi_token
(#(#bindings,)* __scope,)
(#(#bindings_idents,)* __scope,)
};
}
};
@ -1142,8 +1171,13 @@ impl Visitor<'_> {
quote_spanned! {span=>
{
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
let __match_expr = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr));
#match_token ::fayalite::sim::value::SimValue::into_value(__match_expr) {
let __match_value = #expr;
let __match_value = {
use ::fayalite::sim::value::match_sim_value::*;
// use method syntax to deduce the correct trait to call
::fayalite::sim::value::match_sim_value::MatchSimValueHelper::new(__match_value).__fayalite_match_sim_value()
};
#match_token __match_value {
#(#arms)*
}
}

View file

@ -14,9 +14,11 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
base64.workspace = true
bitvec.workspace = true
blake3.workspace = true
clap.workspace = true
clap_complete.workspace = true
ctor.workspace = true
eyre.workspace = true
fayalite-proc-macros.workspace = true
@ -24,7 +26,7 @@ hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
os_pipe.workspace = true
ordered-float.workspace = true
petgraph.workspace = true
serde_json.workspace = true
serde.workspace = true

View file

@ -1,47 +1,64 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use clap::Parser;
use fayalite::{cli, prelude::*};
use fayalite::prelude::*;
#[hdl_module]
fn blinky(clock_frequency: u64) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let rst: SyncReset = m.input();
fn blinky(platform_io_builder: PlatformIOBuilder<'_>) {
let clk_input =
platform_io_builder.peripherals_with_type::<peripherals::ClockInput>()[0].use_peripheral();
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
let cd = #[hdl]
ClockDomain {
clk,
rst: rst.to_reset(),
clk: clk_input.clk,
rst,
};
let max_value = clock_frequency / 2 - 1;
let max_value = (clk_input.ty().frequency() / 2.0).round_ties_even() as u64 - 1;
let int_ty = UInt::range_inclusive(0..=max_value);
#[hdl]
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]
let rgb_output_reg = reg_builder().clock_domain(cd).reset(
#[hdl]
peripherals::RgbLed {
r: false,
g: false,
b: false,
},
);
#[hdl]
if counter_reg.cmp_eq(max_value) {
connect_any(counter_reg, 0u8);
connect(output_reg, !output_reg);
connect(rgb_output_reg.r, !rgb_output_reg.r);
#[hdl]
if rgb_output_reg.r {
connect(rgb_output_reg.g, !rgb_output_reg.g);
#[hdl]
if rgb_output_reg.g {
connect(rgb_output_reg.b, !rgb_output_reg.b);
}
}
} else {
connect_any(counter_reg, counter_reg + 1_hdl_u1);
}
for led in platform_io_builder.peripherals_with_type::<peripherals::Led>() {
if let Ok(led) = led.try_use_peripheral() {
connect(led.on, output_reg);
}
}
for rgb_led in platform_io_builder.peripherals_with_type::<peripherals::RgbLed>() {
if let Ok(rgb_led) = rgb_led.try_use_peripheral() {
connect(rgb_led, rgb_output_reg);
}
}
#[hdl]
let led: Bool = m.output();
connect(led, output_reg);
let io = m.add_platform_io(platform_io_builder);
}
#[derive(Parser)]
struct Cli {
/// clock frequency in hertz
#[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))]
clock_frequency: u64,
#[command(subcommand)]
cli: cli::Cli,
}
fn main() -> cli::Result {
let cli = Cli::parse();
cli.cli.run(blinky(cli.clock_frequency))
fn main() {
<BuildCli>::main("blinky", |_, platform, _| {
Ok(JobParams::new(platform.wrap_main_module(blinky)))
});
}

View file

@ -0,0 +1,188 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use clap::builder::TypedValueParser;
use fayalite::{
build::{ToArgs, WriteArgs},
platform::PeripheralRef,
prelude::*,
};
use ordered_float::NotNan;
fn pick_clock<'a>(
platform_io_builder: &PlatformIOBuilder<'a>,
) -> PeripheralRef<'a, peripherals::ClockInput> {
let mut clks = platform_io_builder.peripherals_with_type::<peripherals::ClockInput>();
clks.sort_by_key(|clk| {
// sort clocks by preference, smaller return values means higher preference
let mut frequency = clk.ty().frequency();
let priority;
if frequency < 10e6 {
frequency = -frequency; // prefer bigger frequencies
priority = 1;
} else if frequency > 50e6 {
// prefer smaller frequencies
priority = 2; // least preferred
} else {
priority = 0; // most preferred
frequency = (frequency - 25e6).abs(); // prefer closer to 25MHz
}
(priority, NotNan::new(frequency).expect("should be valid"))
});
clks[0]
}
#[hdl_module]
fn tx_only_uart(
platform_io_builder: PlatformIOBuilder<'_>,
divisor: f64,
message: impl AsRef<[u8]>,
) {
let message = message.as_ref();
let clk_input = pick_clock(&platform_io_builder).use_peripheral();
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
let cd = #[hdl]
ClockDomain {
clk: clk_input.clk,
rst,
};
let numerator = 1u128 << 16;
let denominator = (divisor * numerator as f64).round() as u128;
#[hdl]
let remainder_reg: UInt<128> = reg_builder().clock_domain(cd).reset(0u128);
#[hdl]
let sum: UInt<128> = wire();
connect_any(sum, remainder_reg + numerator);
#[hdl]
let tick_reg = reg_builder().clock_domain(cd).reset(false);
connect(tick_reg, false);
#[hdl]
let next_remainder: UInt<128> = wire();
connect(remainder_reg, next_remainder);
#[hdl]
if sum.cmp_ge(denominator) {
connect_any(next_remainder, sum - denominator);
connect(tick_reg, true);
} else {
connect(next_remainder, sum);
}
#[hdl]
let uart_state_reg = reg_builder().clock_domain(cd).reset(0_hdl_u4);
#[hdl]
let next_uart_state: UInt<4> = wire();
connect_any(next_uart_state, uart_state_reg + 1u8);
#[hdl]
let message_mem: Array<UInt<8>> = wire(Array[UInt::new_static()][message.len()]);
for (message, message_mem) in message.iter().zip(message_mem) {
connect(message_mem, *message);
}
#[hdl]
let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32);
#[hdl]
let next_addr: UInt<32> = wire();
connect(next_addr, addr_reg);
#[hdl]
let tx = reg_builder().clock_domain(cd).reset(true);
#[hdl]
let tx_bits: Array<Bool, 10> = wire();
connect(tx_bits[0], false); // start bit
connect(tx_bits[9], true); // stop bit
for i in 0..8 {
connect(tx_bits[i + 1], message_mem[addr_reg][i]); // data bits
}
connect(tx, tx_bits[uart_state_reg]);
#[hdl]
if uart_state_reg.cmp_eq(tx_bits.ty().len() - 1) {
connect(next_uart_state, 0_hdl_u4);
let next_addr_val = addr_reg + 1u8;
#[hdl]
if next_addr_val.cmp_lt(message.len()) {
connect_any(next_addr, next_addr_val);
} else {
connect(next_addr, 0u32);
}
}
#[hdl]
if tick_reg {
connect(uart_state_reg, next_uart_state);
connect(addr_reg, next_addr);
}
for uart in platform_io_builder.peripherals_with_type::<peripherals::Uart>() {
connect(uart.use_peripheral().tx, tx);
}
#[hdl]
let io = m.add_platform_io(platform_io_builder);
}
fn parse_baud_rate(
v: impl AsRef<str>,
) -> Result<NotNan<f64>, Box<dyn std::error::Error + Send + Sync>> {
let retval: NotNan<f64> = v
.as_ref()
.parse()
.map_err(|_| "invalid baud rate, must be a finite positive floating-point value")?;
if *retval > 0.0 && retval.is_finite() {
Ok(retval)
} else {
Err("baud rate must be finite and positive".into())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct ExtraArgs {
#[arg(long, value_parser = clap::builder::StringValueParser::new().try_map(parse_baud_rate), default_value = "115200")]
pub baud_rate: NotNan<f64>,
#[arg(long, default_value = "Hello World from Fayalite!!!\r\n", value_parser = clap::builder::NonEmptyStringValueParser::new())]
pub message: String,
}
impl ToArgs for ExtraArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self { baud_rate, message } = self;
args.write_display_arg(format_args!("--baud-rate={baud_rate}"));
args.write_long_option_eq("message", message);
}
}
fn main() {
type Cli = BuildCli<ExtraArgs>;
Cli::main(
"tx_only_uart",
|_, platform, ExtraArgs { baud_rate, message }| {
Ok(JobParams::new(platform.try_wrap_main_module(|io| {
let clk = pick_clock(&io).ty();
let divisor = clk.frequency() / *baud_rate;
let baud_rate_error = |msg| {
<Cli as clap::CommandFactory>::command()
.error(clap::error::ErrorKind::ValueValidation, msg)
};
const HUGE_DIVISOR: f64 = u64::MAX as f64;
match divisor {
divisor if !divisor.is_finite() => {
return Err(baud_rate_error("bad baud rate"));
}
HUGE_DIVISOR.. => return Err(baud_rate_error("baud rate is too small")),
4.0.. => {}
_ => return Err(baud_rate_error("baud rate is too large")),
}
Ok(tx_only_uart(io, divisor, message))
})?))
},
);
}

View file

@ -31,3 +31,81 @@
//! }
//! }
//! ```
//!
//! You can also use `#[hdl(sim)] let` to destructure [`SimValue`]s (or anything that implements [`ToSimValue`]).
//!
//! [`SimValue`]: crate::sim::value::SimValue
//! [`ToSimValue`]: crate::sim::value::ToSimValue
//!
//! ```
//! # use fayalite::prelude::*;
//! #[hdl]
//! struct MyStruct<T> {
//! a: UInt<8>,
//! b: Bool,
//! c: T,
//! }
//!
//! #[hdl]
//! fn destructure<T: Type>(v: SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! mut b,
//! c,
//! } = v;
//!
//! // that gives these types:
//! let _: SimValue<UInt<8>> = a;
//! let _: SimValue<Bool> = b;
//! let _: SimValue<T> = c;
//! *b = false; // can modify b since mut was used
//! }
//!
//! #[hdl]
//! fn destructure_ref<'a, T: Type>(v: &'a SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! b,
//! c,
//! } = v;
//!
//! // that gives these types:
//! let _: &'a SimValue<UInt<8>> = a;
//! let _: &'a SimValue<Bool> = b;
//! let _: &'a SimValue<T> = c;
//! }
//!
//! #[hdl]
//! fn destructure_mut<'a, T: Type>(v: &'a mut SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! b,
//! c,
//! } = v;
//!
//! **b = true; // you can modify v by modifying b which borrows from it
//!
//! // that gives these types:
//! let _: &'a mut SimValue<UInt<8>> = a;
//! let _: &'a mut SimValue<Bool> = b;
//! let _: &'a mut SimValue<T> = c;
//! }
//!
//! #[hdl]
//! fn destructure_to_sim_value<'a, T: Type>(v: impl ToSimValue<Type = MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! b,
//! c,
//! } = v;
//!
//! // that gives these types:
//! let _: SimValue<UInt<8>> = a;
//! let _: SimValue<Bool> = b;
//! let _: SimValue<T> = c;
//! }
//! ```

View file

@ -9,3 +9,78 @@
//!
//! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now,
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.
//!
//! You can also use `#[hdl(sim)] match` to match [`SimValue`]s (or anything that implements [`ToSimValue`]).
//!
//! `#[hdl(sim)] match` statements' bodies may have any type, unlike `#[hdl] match`.
//!
//! [`SimValue`]: crate::sim::value::SimValue
//! [`ToSimValue`]: crate::sim::value::ToSimValue
//!
//! ```
//! # use fayalite::prelude::*;
//! #[hdl]
//! enum MyEnum<T> {
//! A,
//! B(Bool),
//! C(T),
//! }
//!
//! #[hdl]
//! fn match_move<T: Type>(v: SimValue<MyEnum<T>>) -> String {
//! #[hdl(sim)]
//! match v {
//! MyEnum::<T>::A => String::from("got A"),
//! MyEnum::<T>::B(mut b) => {
//! let _: SimValue<Bool> = b; // b has this type
//! let text = format!("got B({b})");
//! *b = true; // can modify b since mut was used
//! text
//! }
//! _ => String::from("something else"),
//! }
//! }
//!
//! #[hdl]
//! fn match_ref<'a, T: Type>(v: &'a SimValue<MyEnum<T>>) -> u32 {
//! #[hdl(sim)]
//! match v {
//! MyEnum::<T>::A => 1,
//! MyEnum::<T>::B(b) => {
//! let _: &'a SimValue<Bool> = b; // b has this type
//! println!("got B({b})");
//! 5
//! }
//! _ => 42,
//! }
//! }
//!
//! #[hdl]
//! fn match_mut<'a, T: Type>(v: &'a mut SimValue<MyEnum<T>>) -> Option<&'a mut SimValue<T>> {
//! #[hdl(sim)]
//! match v {
//! MyEnum::<T>::A => None,
//! MyEnum::<T>::B(b) => {
//! println!("got B({b})");
//! **b = true; // you can modify v by modifying b which borrows from it
//! let _: &'a mut SimValue<Bool> = b; // b has this type
//! None
//! }
//! MyEnum::<T>::C(v) => Some(v), // you can return matched values
//! _ => None, // HDL enums can have invalid discriminants, so we need this extra match arm
//! }
//! }
//!
//! #[hdl]
//! fn match_to_sim_value<'a, T: Type>(v: impl ToSimValue<Type = MyEnum<T>>) {
//! #[hdl(sim)]
//! match v {
//! MyEnum::<T>::A => println!("got A"),
//! MyEnum::<T>::B(b) => {
//! let _: SimValue<Bool> = b; // b has this type
//! println!("got B({b})");
//! }
//! _ => println!("something else"),
//! }
//! }
//! ```

View file

@ -145,52 +145,73 @@ pub struct DocStringAnnotation {
macro_rules! make_annotation_enum {
(
#[$non_exhaustive:ident]
$(#[$meta:meta])*
$vis:vis enum $Annotation:ident {
$($Variant:ident($T:ident),)*
$vis:vis enum $AnnotationEnum:ident {
$($Variant:ident($T:ty),)*
}
) => {
crate::annotations::make_annotation_enum!(@require_non_exhaustive $non_exhaustive);
#[$non_exhaustive]
$(#[$meta])*
$vis enum $Annotation {
#[derive(Clone, PartialEq, Eq, Hash)]
$vis enum $AnnotationEnum {
$($Variant($T),)*
}
$(impl IntoAnnotations for $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(self)]
impl std::fmt::Debug for $AnnotationEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(Self::$Variant(v) => v.fmt(f),)*
}
}
}
impl IntoAnnotations for &'_ $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
$(impl From<$T> for crate::annotations::Annotation {
fn from(v: $T) -> Self {
$AnnotationEnum::$Variant(v).into()
}
}
impl IntoAnnotations for &'_ mut $T {
type IntoAnnotations = [$Annotation; 1];
impl crate::annotations::IntoAnnotations for $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
[self.into()]
}
}
impl IntoAnnotations for Box<$T> {
type IntoAnnotations = [$Annotation; 1];
impl crate::annotations::IntoAnnotations for &'_ $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
[crate::annotations::Annotation::from(self.clone())]
}
}
impl crate::annotations::IntoAnnotations for &'_ mut $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[crate::annotations::Annotation::from(self.clone())]
}
}
impl crate::annotations::IntoAnnotations for Box<$T> {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[crate::annotations::Annotation::from(*self)]
}
})*
};
(@require_non_exhaustive non_exhaustive) => {};
}
pub(crate) use make_annotation_enum;
make_annotation_enum! {
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Annotation {
DontTouch(DontTouchAnnotation),
@ -199,6 +220,7 @@ make_annotation_enum! {
BlackBoxPath(BlackBoxPathAnnotation),
DocString(DocStringAnnotation),
CustomFirrtl(CustomFirrtlAnnotation),
Xilinx(crate::vendor::xilinx::XilinxAnnotation),
}
}

View file

@ -3,13 +3,13 @@
use crate::{
expr::{
CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr,
ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq},
CastToBits, Expr, HdlPartialEq, HdlPartialEqImpl, ReduceBits, ToExpr, ValueType, Valueless,
ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator},
},
int::{Bool, DYN_SIZE, DynSize, KnownSize, Size, SizeType},
intern::{Intern, Interned, LazyInterned},
module::transform::visit::{Fold, Folder, Visit, Visitor},
sim::value::{SimValue, SimValuePartialEq},
sim::value::SimValue,
source_location::SourceLocation,
ty::{
CanonicalType, MatchVariantWithoutScope, OpaqueSimValueSlice, OpaqueSimValueWriter,
@ -19,7 +19,7 @@ use crate::{
util::ConstUsize,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::{iter::FusedIterator, ops::Index};
use std::{borrow::Cow, iter::FusedIterator, ops::Index};
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
@ -221,7 +221,7 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
let value = AsMut::<[SimValue<T>]>::as_mut(value);
assert_eq!(self.len(), value.len());
for element_value in value {
assert_eq!(SimValue::ty(element_value), element_ty);
assert_eq!(element_value.ty(), element_ty);
let (element_opaque, rest) = opaque.split_at(element_size);
SimValue::opaque_mut(element_value).clone_from_slice(element_opaque);
opaque = rest;
@ -238,7 +238,7 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
let value = AsRef::<[SimValue<T>]>::as_ref(value);
assert_eq!(self.len(), value.len());
for element_value in value {
assert_eq!(SimValue::ty(element_value), element_ty);
assert_eq!(element_value.ty(), element_ty);
writer.fill_prefix_with(element_size, |writer| {
writer.fill_cloned_from_slice(SimValue::opaque(element_value).as_slice())
});
@ -326,14 +326,30 @@ impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
}
}
impl<Lhs: Type, Rhs: Type, Len: Size> ExprPartialEq<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
impl<Lhs: Type, Rhs: Type, Len: Size> HdlPartialEqImpl<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
where
Lhs: ExprPartialEq<Rhs>,
Lhs: HdlPartialEqImpl<Rhs>,
{
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
let lhs_ty = Expr::ty(lhs);
let rhs_ty = Expr::ty(rhs);
assert_eq!(lhs_ty.len(), rhs_ty.len());
fn cmp_value_eq(
lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
rhs: ArrayType<Rhs, Len>,
rhs_value: Cow<'_, <ArrayType<Rhs, Len> as Type>::SimValue>,
) -> bool {
assert_eq!(lhs.len(), rhs.len());
let lhs = lhs.element();
let rhs = rhs.element();
let lhs_value: &[_] = (*lhs_value).as_ref();
let rhs_value: &[_] = (*rhs_value).as_ref();
for (lhs_value, rhs_value) in lhs_value.iter().zip(rhs_value) {
if !Lhs::cmp_value_eq(lhs, Cow::Borrowed(lhs_value), rhs, Cow::Borrowed(rhs_value)) {
return false;
}
}
true
}
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
lhs.into_iter()
.zip(rhs)
.map(|(l, r)| l.cmp_eq(r))
@ -341,11 +357,8 @@ where
.cast_to_bits()
.all_one_bits()
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
let lhs_ty = Expr::ty(lhs);
let rhs_ty = Expr::ty(rhs);
assert_eq!(lhs_ty.len(), rhs_ty.len());
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
lhs.into_iter()
.zip(rhs)
.map(|(l, r)| l.cmp_ne(r))
@ -353,17 +366,19 @@ where
.cast_to_bits()
.any_one_bits()
}
}
impl<Lhs: Type, Rhs: Type, Len: Size> SimValuePartialEq<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
where
Lhs: SimValuePartialEq<Rhs>,
{
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<ArrayType<Rhs, Len>>) -> bool {
AsRef::<[_]>::as_ref(&**this)
.iter()
.zip(AsRef::<[_]>::as_ref(&**other))
.all(|(l, r)| SimValuePartialEq::sim_value_eq(l, r))
fn cmp_valueless_eq(
lhs: Valueless<Self>,
rhs: Valueless<ArrayType<Rhs, Len>>,
) -> Valueless<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
Valueless::new(Bool)
}
fn cmp_valueless_ne(
lhs: Valueless<Self>,
rhs: Valueless<ArrayType<Rhs, Len>>,
) -> Valueless<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
Valueless::new(Bool)
}
}
@ -374,7 +389,7 @@ impl<T: Type, Len: Size> ExprIntoIterator for ArrayType<T, Len> {
fn expr_into_iter(e: Expr<Self>) -> Self::ExprIntoIter {
ExprArrayIter {
base: e,
indexes: 0..Expr::ty(e).len(),
indexes: 0..e.ty().len(),
}
}
}

2803
crates/fayalite/src/build.rs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,128 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{
BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies,
JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams,
ToArgs, WriteArgs,
},
firrtl::{ExportOptions, FileBackend},
intern::{Intern, InternSlice, Interned},
util::job_server::AcquiredJob,
};
use clap::Args;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct FirrtlJobKind;
#[derive(Args, Debug, Clone, Hash, PartialEq, Eq)]
#[group(id = "Firrtl")]
#[non_exhaustive]
pub struct FirrtlArgs {
#[command(flatten)]
pub export_options: ExportOptions,
}
impl ToArgs for FirrtlArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self { export_options } = self;
export_options.to_args(args);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Firrtl {
base: BaseJob,
export_options: ExportOptions,
}
impl Firrtl {
fn make_firrtl_file_backend(&self) -> FileBackend {
FileBackend {
dir_path: PathBuf::from(&*self.base.output_dir()),
top_fir_file_stem: Some(self.base.file_stem().into()),
circuit_name: None,
}
}
pub fn firrtl_file(&self) -> Interned<Path> {
self.base.file_with_ext("fir")
}
}
impl JobKind for FirrtlJobKind {
type Args = FirrtlArgs;
type Job = Firrtl;
type Dependencies = JobKindAndDependencies<BaseJobKind>;
fn dependencies(self) -> Self::Dependencies {
JobKindAndDependencies::new(BaseJobKind)
}
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(
params,
global_params,
|_kind, FirrtlArgs { export_options }, dependencies| {
Ok(Firrtl {
base: dependencies.get_job::<BaseJob, _>().clone(),
export_options,
})
},
)
}
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.base.output_dir(),
}]
.intern_slice()
}
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.firrtl_file(),
}]
.intern_slice()
}
fn name(self) -> Interned<str> {
"firrtl".intern()
}
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
None
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
let [JobItem::Path { path: input_path }] = *inputs else {
panic!("wrong inputs, expected a single `Path`");
};
assert_eq!(input_path, job.base.output_dir());
crate::firrtl::export(
job.make_firrtl_file_backend(),
params.main_module(),
job.export_options,
)?;
Ok(vec![JobItem::Path {
path: job.firrtl_file(),
}])
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[DynJobKind::new(FirrtlJobKind)]
}

View file

@ -0,0 +1,388 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind},
},
intern::{Intern, InternSlice, Interned},
module::NameId,
testing::FormalMode,
util::job_server::AcquiredJob,
};
use clap::Args;
use eyre::Context;
use serde::{Deserialize, Serialize};
use std::{
ffi::{OsStr, OsString},
fmt::{self, Write},
path::Path,
};
#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FormalArgs {
#[arg(long = "sby-extra-arg", value_name = "ARG")]
pub sby_extra_args: Vec<OsString>,
#[arg(long, default_value_t)]
pub formal_mode: FormalMode,
#[arg(long, default_value_t = Self::DEFAULT_DEPTH)]
pub formal_depth: u64,
#[arg(long, default_value = Self::DEFAULT_SOLVER)]
pub formal_solver: String,
#[arg(long = "smtbmc-extra-arg", value_name = "ARG")]
pub smtbmc_extra_args: Vec<OsString>,
}
impl FormalArgs {
pub const DEFAULT_DEPTH: u64 = 20;
pub const DEFAULT_SOLVER: &'static str = "z3";
}
impl ToArgs for FormalArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
sby_extra_args,
formal_mode,
formal_depth,
formal_solver,
smtbmc_extra_args,
} = self;
for arg in sby_extra_args {
args.write_long_option_eq("sby-extra-arg", arg);
}
args.write_display_args([
format_args!("--formal-mode={formal_mode}"),
format_args!("--formal-depth={formal_depth}"),
format_args!("--formal-solver={formal_solver}"),
]);
for arg in smtbmc_extra_args {
args.write_long_option_eq("smtbmc-extra-arg", arg);
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct WriteSbyFileJobKind;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct WriteSbyFileJob {
sby_extra_args: Interned<[Interned<OsStr>]>,
formal_mode: FormalMode,
formal_depth: u64,
formal_solver: Interned<str>,
smtbmc_extra_args: Interned<[Interned<OsStr>]>,
sby_file: Interned<Path>,
output_dir: Interned<Path>,
main_verilog_file: Interned<Path>,
}
impl WriteSbyFileJob {
pub fn sby_extra_args(&self) -> Interned<[Interned<OsStr>]> {
self.sby_extra_args
}
pub fn formal_mode(&self) -> FormalMode {
self.formal_mode
}
pub fn formal_depth(&self) -> u64 {
self.formal_depth
}
pub fn formal_solver(&self) -> Interned<str> {
self.formal_solver
}
pub fn smtbmc_extra_args(&self) -> Interned<[Interned<OsStr>]> {
self.smtbmc_extra_args
}
pub fn sby_file(&self) -> Interned<Path> {
self.sby_file
}
pub fn output_dir(&self) -> Interned<Path> {
self.output_dir
}
pub fn main_verilog_file(&self) -> Interned<Path> {
self.main_verilog_file
}
fn write_sby(
&self,
output: &mut OsString,
additional_files: &[Interned<Path>],
main_module_name_id: NameId,
) -> eyre::Result<()> {
let Self {
sby_extra_args: _,
formal_mode,
formal_depth,
formal_solver,
smtbmc_extra_args,
sby_file: _,
output_dir: _,
main_verilog_file,
} = self;
write!(
output,
"[options]\n\
mode {formal_mode}\n\
depth {formal_depth}\n\
wait on\n\
\n\
[engines]\n\
smtbmc {formal_solver} -- --"
)
.expect("writing to OsString can't fail");
for i in smtbmc_extra_args {
output.push(" ");
output.push(i);
}
output.push(
"\n\
\n\
[script]\n",
);
for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? {
output.push("read_verilog -sv -formal \"");
output.push(verilog_file);
output.push("\"\n");
}
let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id);
// workaround for wires disappearing -- set `keep` on all wires
writeln!(
output,
"hierarchy -top {circuit_name}\n\
proc\n\
setattr -set keep 1 w:\\*\n\
prep",
)
.expect("writing to OsString can't fail");
Ok(())
}
}
impl JobKind for WriteSbyFileJobKind {
type Args = FormalArgs;
type Job = WriteSbyFileJob;
type Dependencies = JobKindAndDependencies<VerilogJobKind>;
fn dependencies(self) -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.dependencies
.dependencies
.args
.args
.additional_args
.verilog_dialect
.get_or_insert(VerilogDialect::Yosys);
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let FormalArgs {
sby_extra_args,
formal_mode,
formal_depth,
formal_solver,
smtbmc_extra_args,
} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(WriteSbyFileJob {
sby_extra_args: sby_extra_args.into_iter().map(Interned::from).collect(),
formal_mode,
formal_depth,
formal_solver: formal_solver.intern_deref(),
smtbmc_extra_args: smtbmc_extra_args.into_iter().map(Interned::from).collect(),
sby_file: base_job.file_with_ext("sby"),
output_dir: base_job.output_dir(),
main_verilog_file: dependencies.get_job::<VerilogJob, _>().main_verilog_file(),
})
})
}
fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::DynamicPaths {
source_job_name: VerilogJobKind.name(),
}]
.intern_slice()
}
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::Path { path: job.sby_file }].intern_slice()
}
fn name(self) -> Interned<str> {
"write-sby-file".intern()
}
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
None
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
let [additional_files] = inputs else {
unreachable!();
};
let additional_files = VerilogJob::unwrap_additional_files(additional_files);
let mut contents = OsString::new();
job.write_sby(
&mut contents,
additional_files,
params.main_module().name_id(),
)?;
let path = job.sby_file;
std::fs::write(path, contents.as_encoded_bytes())
.wrap_err_with(|| format!("writing {path:?} failed"))?;
Ok(vec![JobItem::Path { path }])
}
fn subcommand_hidden(self) -> bool {
true
}
}
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct Formal {
#[serde(flatten)]
write_sby_file: WriteSbyFileJob,
sby_file_name: Interned<OsStr>,
}
impl fmt::Debug for Formal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
write_sby_file:
WriteSbyFileJob {
sby_extra_args,
formal_mode,
formal_depth,
formal_solver,
smtbmc_extra_args,
sby_file,
output_dir: _,
main_verilog_file,
},
sby_file_name,
} = self;
f.debug_struct("Formal")
.field("sby_extra_args", sby_extra_args)
.field("formal_mode", formal_mode)
.field("formal_depth", formal_depth)
.field("formal_solver", formal_solver)
.field("smtbmc_extra_args", smtbmc_extra_args)
.field("sby_file", sby_file)
.field("sby_file_name", sby_file_name)
.field("main_verilog_file", main_verilog_file)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Symbiyosys;
impl ExternalProgramTrait for Symbiyosys {
fn default_program_name() -> Interned<str> {
"sby".intern()
}
}
#[derive(Clone, Hash, PartialEq, Eq, Debug, Args)]
pub struct FormalAdditionalArgs {}
impl ToArgs for FormalAdditionalArgs {
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
let Self {} = self;
}
}
impl ExternalCommand for Formal {
type AdditionalArgs = FormalAdditionalArgs;
type AdditionalJobData = Formal;
type BaseJobPosition = GetJobPositionDependencies<
GetJobPositionDependencies<
GetJobPositionDependencies<<UnadjustedVerilog as ExternalCommand>::BaseJobPosition>,
>,
>;
type Dependencies = JobKindAndDependencies<WriteSbyFileJobKind>;
type ExternalProgram = Symbiyosys;
fn dependencies() -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let FormalAdditionalArgs {} = args.additional_args;
let write_sby_file = dependencies.get_job::<WriteSbyFileJob, _>().clone();
Ok(Formal {
sby_file_name: write_sby_file
.sby_file()
.interned_file_name()
.expect("known to have file name"),
write_sby_file,
})
})
}
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
[
JobItemName::Path {
path: job.additional_job_data().write_sby_file.sby_file(),
},
JobItemName::Path {
path: job.additional_job_data().write_sby_file.main_verilog_file(),
},
JobItemName::DynamicPaths {
source_job_name: VerilogJobKind.name(),
},
]
.intern_slice()
}
fn output_paths(_job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
Interned::default()
}
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
// args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode
args.write_arg("-f");
args.write_interned_arg(job.additional_job_data().sby_file_name);
args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args());
}
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
Some(job.output_dir())
}
fn job_kind_name() -> Interned<str> {
"formal".intern()
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[
DynJobKind::new(WriteSbyFileJobKind),
DynJobKind::new(ExternalCommandJobKind::<Formal>::new()),
]
}

View file

@ -0,0 +1,855 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{
DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs,
},
intern::Interned,
platform::DynPlatform,
util::{HashMap, HashSet, job_server::AcquiredJob},
};
use eyre::{ContextCompat, eyre};
use petgraph::{
algo::{DfsSpace, kosaraju_scc, toposort},
graph::DiGraph,
visit::{GraphBase, Visitable},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq};
use std::{
cell::OnceCell,
collections::{BTreeMap, BTreeSet, VecDeque},
convert::Infallible,
ffi::OsStr,
fmt::{self, Write},
panic,
rc::Rc,
str::Utf8Error,
sync::mpsc,
thread::{self, ScopedJoinHandle},
};
macro_rules! write_str {
($s:expr, $($rest:tt)*) => {
write!($s, $($rest)*).expect("String::write_fmt can't fail")
};
}
#[derive(Clone, Debug)]
enum JobGraphNode {
Job(DynJob),
Item {
#[allow(dead_code, reason = "name used for debugging")]
name: JobItemName,
source_job: Option<DynJob>,
},
}
type JobGraphInner = DiGraph<JobGraphNode, ()>;
#[derive(Clone, Default)]
pub struct JobGraph {
jobs: HashMap<DynJob, <JobGraphInner as GraphBase>::NodeId>,
items: HashMap<JobItemName, <JobGraphInner as GraphBase>::NodeId>,
graph: JobGraphInner,
topological_order: Vec<<JobGraphInner as GraphBase>::NodeId>,
space: DfsSpace<<JobGraphInner as GraphBase>::NodeId, <JobGraphInner as Visitable>::Map>,
}
impl fmt::Debug for JobGraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
jobs: _,
items: _,
graph,
topological_order,
space: _,
} = self;
f.debug_struct("JobGraph")
.field("graph", graph)
.field("topological_order", topological_order)
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub enum JobGraphError {
CycleError {
job: DynJob,
output: JobItemName,
},
MultipleJobsCreateSameOutput {
output_item: JobItemName,
existing_job: DynJob,
new_job: DynJob,
},
}
impl std::error::Error for JobGraphError {}
impl fmt::Display for JobGraphError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CycleError { job, output } => write!(
f,
"job can't be added to job graph because it would introduce a cyclic dependency through this job output:\n\
{output:?}\n\
job:\n{job:?}",
),
JobGraphError::MultipleJobsCreateSameOutput {
output_item,
existing_job,
new_job,
} => write!(
f,
"job can't be added to job graph because the new job has an output that is also produced by an existing job.\n\
conflicting output:\n\
{output_item:?}\n\
existing job:\n\
{existing_job:?}\n\
new job:\n\
{new_job:?}",
),
}
}
}
#[derive(Copy, Clone, Debug)]
enum EscapeForUnixShellState {
DollarSingleQuote,
SingleQuote,
Unquoted,
}
#[derive(Clone)]
pub struct EscapeForUnixShell<'a> {
state: EscapeForUnixShellState,
prefix: [u8; 3],
bytes: &'a [u8],
}
impl<'a> fmt::Debug for EscapeForUnixShell<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<'a> fmt::Display for EscapeForUnixShell<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.clone() {
f.write_char(c)?;
}
Ok(())
}
}
impl<'a> EscapeForUnixShell<'a> {
pub fn new(s: &'a (impl ?Sized + AsRef<OsStr>)) -> Self {
Self::from_bytes(s.as_ref().as_encoded_bytes())
}
fn make_prefix(bytes: &[u8]) -> [u8; 3] {
let mut prefix = [0; 3];
prefix[..bytes.len()].copy_from_slice(bytes);
prefix
}
pub fn from_bytes(bytes: &'a [u8]) -> Self {
let mut needs_single_quote = bytes.is_empty();
for &b in bytes {
match b {
b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true,
0..0x20 | 0x7F.. => {
return Self {
state: EscapeForUnixShellState::DollarSingleQuote,
prefix: Self::make_prefix(b"$'"),
bytes,
};
}
_ => {}
}
}
if needs_single_quote {
Self {
state: EscapeForUnixShellState::SingleQuote,
prefix: Self::make_prefix(b"'"),
bytes,
}
} else {
Self {
state: EscapeForUnixShellState::Unquoted,
prefix: Self::make_prefix(b""),
bytes,
}
}
}
}
impl Iterator for EscapeForUnixShell<'_> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.prefix {
[0, 0, 0] => {}
[0, 0, v] | // find first
[0, v, _] | // non-zero byte
[v, _, _] => {
let retval = *v as char;
*v = 0;
return Some(retval);
}
}
let Some(&next_byte) = self.bytes.split_off_first() else {
return match self.state {
EscapeForUnixShellState::DollarSingleQuote
| EscapeForUnixShellState::SingleQuote => {
self.state = EscapeForUnixShellState::Unquoted;
Some('\'')
}
EscapeForUnixShellState::Unquoted => None,
};
};
match self.state {
EscapeForUnixShellState::DollarSingleQuote => match next_byte {
b'\'' | b'\\' => {
self.prefix = Self::make_prefix(&[next_byte]);
Some('\\')
}
b'\t' => {
self.prefix = Self::make_prefix(b"t");
Some('\\')
}
b'\n' => {
self.prefix = Self::make_prefix(b"n");
Some('\\')
}
b'\r' => {
self.prefix = Self::make_prefix(b"r");
Some('\\')
}
0x20..=0x7E => Some(next_byte as char),
_ => {
self.prefix = [
b'x',
char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range")
as u8,
char::from_digit(next_byte as u32 & 0xF, 0x10)
.expect("known to be in range") as u8,
];
Some('\\')
}
},
EscapeForUnixShellState::SingleQuote => {
if next_byte == b'\'' {
self.prefix = Self::make_prefix(b"\\''");
Some('\'')
} else {
Some(next_byte as char)
}
}
EscapeForUnixShellState::Unquoted => match next_byte {
b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b','
| b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|'
| b'}' | b'~' => {
self.prefix = Self::make_prefix(&[next_byte]);
Some('\\')
}
_ => Some(next_byte as char),
},
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum UnixMakefileEscapeKind {
NonRecipe,
RecipeWithoutShellEscaping,
RecipeWithShellEscaping,
}
#[derive(Copy, Clone)]
pub struct EscapeForUnixMakefile<'a> {
s: &'a OsStr,
kind: UnixMakefileEscapeKind,
}
impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<'a> fmt::Display for EscapeForUnixMakefile<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.do_write(
f,
fmt::Write::write_str,
fmt::Write::write_char,
|_, _| Ok(()),
|_| unreachable!("already checked that the input causes no UTF-8 errors"),
)
}
}
impl<'a> EscapeForUnixMakefile<'a> {
fn do_write<S: ?Sized, E>(
&self,
state: &mut S,
write_str: impl Fn(&mut S, &str) -> Result<(), E>,
write_char: impl Fn(&mut S, char) -> Result<(), E>,
add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>,
utf8_error: impl Fn(Utf8Error) -> E,
) -> Result<(), E> {
let escape_recipe_char = |c| match c {
'$' => write_str(state, "$$"),
'\0'..='\x1F' | '\x7F' => {
panic!("can't escape a control character for Unix Makefile: {c:?}");
}
_ => write_char(state, c),
};
match self.kind {
UnixMakefileEscapeKind::NonRecipe => str::from_utf8(self.s.as_encoded_bytes())
.map_err(&utf8_error)?
.chars()
.try_for_each(|c| match c {
'=' => {
add_variable(state, "EQUALS = =")?;
write_str(state, "$(EQUALS)")
}
';' => panic!("can't escape a semicolon (;) for Unix Makefile"),
'$' => write_str(state, "$$"),
'\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => {
write_char(state, '\\')?;
write_char(state, c)
}
'\0'..='\x1F' | '\x7F' => {
panic!("can't escape a control character for Unix Makefile: {c:?}");
}
_ => write_char(state, c),
}),
UnixMakefileEscapeKind::RecipeWithoutShellEscaping => {
str::from_utf8(self.s.as_encoded_bytes())
.map_err(&utf8_error)?
.chars()
.try_for_each(escape_recipe_char)
}
UnixMakefileEscapeKind::RecipeWithShellEscaping => {
EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char)
}
}
}
pub fn new(
s: &'a (impl ?Sized + AsRef<OsStr>),
kind: UnixMakefileEscapeKind,
needed_variables: &mut BTreeSet<&'static str>,
) -> Result<Self, Utf8Error> {
let s = s.as_ref();
let retval = Self { s, kind };
retval.do_write(
needed_variables,
|_, _| Ok(()),
|_, _| Ok(()),
|needed_variables, variable| {
needed_variables.insert(variable);
Ok(())
},
|e| e,
)?;
Ok(retval)
}
}
impl JobGraph {
pub fn new() -> Self {
Self::default()
}
fn try_add_item_node(
&mut self,
name: JobItemName,
new_source_job: Option<DynJob>,
new_nodes: &mut HashSet<<JobGraphInner as GraphBase>::NodeId>,
) -> Result<<JobGraphInner as GraphBase>::NodeId, JobGraphError> {
use hashbrown::hash_map::Entry;
match self.items.entry(name) {
Entry::Occupied(item_entry) => {
let node_id = *item_entry.get();
let JobGraphNode::Item {
name: _,
source_job,
} = &mut self.graph[node_id]
else {
unreachable!("known to be an item");
};
if let Some(new_source_job) = new_source_job {
if let Some(source_job) = source_job {
return Err(JobGraphError::MultipleJobsCreateSameOutput {
output_item: item_entry.key().clone(),
existing_job: source_job.clone(),
new_job: new_source_job,
});
} else {
*source_job = Some(new_source_job);
}
}
Ok(node_id)
}
Entry::Vacant(item_entry) => {
let node_id = self.graph.add_node(JobGraphNode::Item {
name,
source_job: new_source_job,
});
new_nodes.insert(node_id);
item_entry.insert(node_id);
Ok(node_id)
}
}
}
pub fn try_add_jobs<I: IntoIterator<Item = DynJob>>(
&mut self,
jobs: I,
) -> Result<(), JobGraphError> {
use hashbrown::hash_map::Entry;
let jobs = jobs.into_iter();
struct RemoveNewNodesOnError<'a> {
this: &'a mut JobGraph,
new_nodes: HashSet<<JobGraphInner as GraphBase>::NodeId>,
}
impl Drop for RemoveNewNodesOnError<'_> {
fn drop(&mut self) {
for node in self.new_nodes.drain() {
self.this.graph.remove_node(node);
}
}
}
let mut remove_new_nodes_on_error = RemoveNewNodesOnError {
this: self,
new_nodes: HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default()),
};
let new_nodes = &mut remove_new_nodes_on_error.new_nodes;
let this = &mut *remove_new_nodes_on_error.this;
for job in jobs {
let Entry::Vacant(job_entry) = this.jobs.entry(job.clone()) else {
continue;
};
let job_node_id = this
.graph
.add_node(JobGraphNode::Job(job_entry.key().clone()));
new_nodes.insert(job_node_id);
job_entry.insert(job_node_id);
for name in job.outputs() {
let item_node_id = this.try_add_item_node(name, Some(job.clone()), new_nodes)?;
this.graph.add_edge(job_node_id, item_node_id, ());
}
for name in job.inputs() {
let item_node_id = this.try_add_item_node(name, None, new_nodes)?;
this.graph.add_edge(item_node_id, job_node_id, ());
}
}
match toposort(&this.graph, Some(&mut this.space)) {
Ok(v) => {
this.topological_order = v;
// no need to remove any of the new nodes on drop since we didn't encounter any errors
remove_new_nodes_on_error.new_nodes.clear();
Ok(())
}
Err(_) => {
// there's at least one cycle, find one!
let cycle = kosaraju_scc(&this.graph)
.into_iter()
.find_map(|scc| {
if scc.len() <= 1 {
// can't be a cycle since our graph is bipartite --
// jobs only connect to items, never jobs to jobs or items to items
None
} else {
Some(scc)
}
})
.expect("we know there's a cycle");
let cycle_set = HashSet::from_iter(cycle.iter().copied());
let job = cycle
.into_iter()
.find_map(|node_id| {
if let JobGraphNode::Job(job) = &this.graph[node_id] {
Some(job.clone())
} else {
None
}
})
.expect("a job must be part of the cycle");
let output = job
.outputs()
.into_iter()
.find(|output| cycle_set.contains(&this.items[output]))
.expect("an output must be part of the cycle");
Err(JobGraphError::CycleError { job, output })
}
}
}
#[track_caller]
pub fn add_jobs<I: IntoIterator<Item = DynJob>>(&mut self, jobs: I) {
match self.try_add_jobs(jobs) {
Ok(()) => {}
Err(e) => panic!("error: {e}"),
}
}
pub fn to_unix_makefile(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
self.to_unix_makefile_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_makefile_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
let mut retval = String::new();
let mut needed_variables = BTreeSet::new();
let mut phony_targets = BTreeSet::new();
for &node_id in &self.topological_order {
let JobGraphNode::Job(job) = &self.graph[node_id] else {
continue;
};
let outputs = job.outputs();
if outputs.is_empty() {
retval.push_str(":");
} else {
for output in job.outputs() {
match output {
JobItemName::Path { path } => {
write_str!(
retval,
"{} ",
EscapeForUnixMakefile::new(
&str::from_utf8(path.as_os_str().as_encoded_bytes())?,
UnixMakefileEscapeKind::NonRecipe,
&mut needed_variables
)?
);
}
JobItemName::DynamicPaths { source_job_name } => {
write_str!(
retval,
"{} ",
EscapeForUnixMakefile::new(
&source_job_name,
UnixMakefileEscapeKind::NonRecipe,
&mut needed_variables
)?
);
phony_targets.insert(Interned::into_inner(source_job_name));
}
}
}
if outputs.len() == 1 {
retval.push_str(":");
} else {
retval.push_str("&:");
}
}
for input in job.inputs() {
match input {
JobItemName::Path { path } => {
write_str!(
retval,
" {}",
EscapeForUnixMakefile::new(
&str::from_utf8(path.as_os_str().as_encoded_bytes())?,
UnixMakefileEscapeKind::NonRecipe,
&mut needed_variables
)?
);
}
JobItemName::DynamicPaths { source_job_name } => {
write_str!(
retval,
" {}",
EscapeForUnixMakefile::new(
&source_job_name,
UnixMakefileEscapeKind::NonRecipe,
&mut needed_variables
)?
);
phony_targets.insert(Interned::into_inner(source_job_name));
}
}
}
retval.push_str("\n\t");
job.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| {
write_str!(
output,
"{}",
EscapeForUnixMakefile::new(
arg,
UnixMakefileEscapeKind::RecipeWithShellEscaping,
&mut needed_variables
)?
);
Ok(())
})?;
retval.push_str("\n\n");
}
if !phony_targets.is_empty() {
retval.push_str("\n.PHONY:");
for phony_target in phony_targets {
write_str!(
retval,
" {}",
EscapeForUnixMakefile::new(
phony_target,
UnixMakefileEscapeKind::NonRecipe,
&mut needed_variables
)?
);
}
retval.push_str("\n");
}
if !needed_variables.is_empty() {
retval.insert_str(
0,
&String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))),
);
}
Ok(retval)
}
pub fn to_unix_shell_script(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
self.to_unix_shell_script_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_shell_script_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
let mut retval = String::from(
"#!/bin/sh\n\
set -ex\n",
);
for &node_id in &self.topological_order {
let JobGraphNode::Job(job) = &self.graph[node_id] else {
continue;
};
let Ok(()) = job
.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> {
write_str!(output, "{}", EscapeForUnixShell::new(&arg));
Ok(())
});
retval.push_str("\n");
}
retval
}
pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> {
// use scope to auto-join threads on errors
thread::scope(|scope| {
struct WaitingJobState {
job_node_id: <JobGraphInner as GraphBase>::NodeId,
job: DynJob,
inputs: BTreeMap<JobItemName, OnceCell<JobItem>>,
}
let mut ready_jobs = VecDeque::new();
let mut item_name_to_waiting_jobs_map = HashMap::<_, Vec<_>>::default();
for &node_id in &self.topological_order {
let JobGraphNode::Job(job) = &self.graph[node_id] else {
continue;
};
let waiting_job = WaitingJobState {
job_node_id: node_id,
job: job.clone(),
inputs: job
.inputs()
.iter()
.map(|&name| (name, OnceCell::new()))
.collect(),
};
if waiting_job.inputs.is_empty() {
ready_jobs.push_back(waiting_job);
} else {
let waiting_job = Rc::new(waiting_job);
for &input_item in waiting_job.inputs.keys() {
item_name_to_waiting_jobs_map
.entry(input_item)
.or_default()
.push(waiting_job.clone());
}
}
}
struct RunningJob<'scope> {
job: DynJob,
thread: ScopedJoinHandle<'scope, eyre::Result<Vec<JobItem>>>,
}
let mut running_jobs = HashMap::default();
let (finished_jobs_sender, finished_jobs_receiver) = mpsc::channel();
let mut next_finished_job = None;
loop {
if let Some(finished_job) = next_finished_job
.take()
.or_else(|| finished_jobs_receiver.try_recv().ok())
{
let Some(RunningJob { job, thread }) = running_jobs.remove(&finished_job)
else {
unreachable!();
};
let output_items = thread.join().map_err(panic::resume_unwind)??;
assert!(
output_items.iter().map(JobItem::name).eq(job.outputs()),
"job's run() method returned the wrong output items:\n\
output items:\n\
{output_items:?}\n\
expected outputs:\n\
{:?}\n\
job:\n\
{job:?}",
job.outputs(),
);
for output_item in output_items {
for waiting_job in item_name_to_waiting_jobs_map
.remove(&output_item.name())
.unwrap_or_default()
{
let Ok(()) =
waiting_job.inputs[&output_item.name()].set(output_item.clone())
else {
unreachable!();
};
if let Some(waiting_job) = Rc::into_inner(waiting_job) {
ready_jobs.push_back(waiting_job);
}
}
}
continue;
}
if let Some(WaitingJobState {
job_node_id,
job,
inputs,
}) = ready_jobs.pop_front()
{
struct RunningJobInThread<'a> {
job_node_id: <JobGraphInner as GraphBase>::NodeId,
job: DynJob,
inputs: Vec<JobItem>,
params: &'a JobParams,
global_params: &'a GlobalParams,
acquired_job: AcquiredJob,
finished_jobs_sender: mpsc::Sender<<JobGraphInner as GraphBase>::NodeId>,
}
impl RunningJobInThread<'_> {
fn run(mut self) -> eyre::Result<Vec<JobItem>> {
self.job.run(
&self.inputs,
self.params,
self.global_params,
&mut self.acquired_job,
)
}
}
impl Drop for RunningJobInThread<'_> {
fn drop(&mut self) {
let _ = self.finished_jobs_sender.send(self.job_node_id);
}
}
let name = job.kind().name();
let running_job_in_thread = RunningJobInThread {
job_node_id,
job: job.clone(),
inputs: Result::from_iter(job.inputs().iter().map(|input_name| {
inputs.get(input_name).and_then(|v| v.get().cloned()).wrap_err_with(|| {
eyre!("failed when trying to run job {name}: nothing provided the input item: {input_name:?}")
})
}))?,
params,
global_params,
acquired_job: AcquiredJob::acquire()?,
finished_jobs_sender: finished_jobs_sender.clone(),
};
running_jobs.insert(
job_node_id,
RunningJob {
job,
thread: thread::Builder::new()
.name(format!("job:{name}"))
.spawn_scoped(scope, move || running_job_in_thread.run())
.expect("failed to spawn thread for job"),
},
);
continue;
}
if running_jobs.is_empty() {
assert!(item_name_to_waiting_jobs_map.is_empty());
assert!(ready_jobs.is_empty());
return Ok(());
}
// nothing to do yet, block to avoid busy waiting
next_finished_job = finished_jobs_receiver.recv().ok();
}
})
}
}
impl Extend<DynJob> for JobGraph {
#[track_caller]
fn extend<T: IntoIterator<Item = DynJob>>(&mut self, iter: T) {
self.add_jobs(iter);
}
}
impl FromIterator<DynJob> for JobGraph {
#[track_caller]
fn from_iter<T: IntoIterator<Item = DynJob>>(iter: T) -> Self {
let mut retval = Self::new();
retval.add_jobs(iter);
retval
}
}
impl Serialize for JobGraph {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?;
for &node_id in &self.topological_order {
let JobGraphNode::Job(job) = &self.graph[node_id] else {
continue;
};
serializer.serialize_element(job)?;
}
serializer.end()
}
}
impl<'de> Deserialize<'de> for JobGraph {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let jobs = Vec::<DynJob>::deserialize(deserializer)?;
let mut retval = JobGraph::new();
retval.try_add_jobs(jobs).map_err(D::Error::custom)?;
Ok(retval)
}
}

View file

@ -0,0 +1,313 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{DynJobKind, JobKind, built_in_job_kinds},
intern::Interned,
util::InternedStrCompareAsStr,
};
use std::{
collections::BTreeMap,
fmt,
sync::{Arc, OnceLock, RwLock, RwLockWriteGuard},
};
impl DynJobKind {
pub fn registry() -> JobKindRegistrySnapshot {
JobKindRegistrySnapshot(JobKindRegistry::get())
}
#[track_caller]
pub fn register(self) {
JobKindRegistry::register(JobKindRegistry::lock(), self);
}
}
#[derive(Clone, Debug)]
struct JobKindRegistry {
job_kinds: BTreeMap<InternedStrCompareAsStr, DynJobKind>,
}
enum JobKindRegisterError {
SameName {
name: InternedStrCompareAsStr,
old_job_kind: DynJobKind,
new_job_kind: DynJobKind,
},
}
impl fmt::Display for JobKindRegisterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SameName {
name,
old_job_kind,
new_job_kind,
} => write!(
f,
"two different `JobKind` can't share the same name:\n\
{name:?}\n\
old job kind:\n\
{old_job_kind:?}\n\
new job kind:\n\
{new_job_kind:?}",
),
}
}
}
trait JobKindRegistryRegisterLock {
type Locked;
fn lock(self) -> Self::Locked;
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry;
}
impl JobKindRegistryRegisterLock for &'static RwLock<Arc<JobKindRegistry>> {
type Locked = RwLockWriteGuard<'static, Arc<JobKindRegistry>>;
fn lock(self) -> Self::Locked {
self.write().expect("shouldn't be poisoned")
}
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry {
Arc::make_mut(locked)
}
}
impl JobKindRegistryRegisterLock for &'_ mut JobKindRegistry {
type Locked = Self;
fn lock(self) -> Self::Locked {
self
}
fn make_mut(locked: &mut Self::Locked) -> &mut JobKindRegistry {
locked
}
}
impl JobKindRegistry {
fn lock() -> &'static RwLock<Arc<Self>> {
static REGISTRY: OnceLock<RwLock<Arc<JobKindRegistry>>> = OnceLock::new();
REGISTRY.get_or_init(Default::default)
}
fn try_register<L: JobKindRegistryRegisterLock>(
lock: L,
job_kind: DynJobKind,
) -> Result<(), JobKindRegisterError> {
use std::collections::btree_map::Entry;
let name = InternedStrCompareAsStr(job_kind.name());
// run user code only outside of lock
let mut locked = lock.lock();
let this = L::make_mut(&mut locked);
let result = match this.job_kinds.entry(name) {
Entry::Occupied(entry) => Err(JobKindRegisterError::SameName {
name,
old_job_kind: entry.get().clone(),
new_job_kind: job_kind,
}),
Entry::Vacant(entry) => {
entry.insert(job_kind);
Ok(())
}
};
drop(locked);
// outside of lock now, so we can test if it's the same DynJobKind
match result {
Err(JobKindRegisterError::SameName {
name: _,
old_job_kind,
new_job_kind,
}) if old_job_kind == new_job_kind => Ok(()),
result => result,
}
}
#[track_caller]
fn register<L: JobKindRegistryRegisterLock>(lock: L, job_kind: DynJobKind) {
match Self::try_register(lock, job_kind) {
Err(e) => panic!("{e}"),
Ok(()) => {}
}
}
fn get() -> Arc<Self> {
Self::lock().read().expect("shouldn't be poisoned").clone()
}
}
impl Default for JobKindRegistry {
fn default() -> Self {
let mut retval = Self {
job_kinds: BTreeMap::new(),
};
for job_kind in built_in_job_kinds() {
Self::register(&mut retval, job_kind);
}
retval
}
}
#[derive(Clone, Debug)]
pub struct JobKindRegistrySnapshot(Arc<JobKindRegistry>);
impl JobKindRegistrySnapshot {
pub fn get() -> Self {
JobKindRegistrySnapshot(JobKindRegistry::get())
}
pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynJobKind> {
self.0.job_kinds.get(name)
}
pub fn iter_with_names(&self) -> JobKindRegistryIterWithNames<'_> {
JobKindRegistryIterWithNames(self.0.job_kinds.iter())
}
pub fn iter(&self) -> JobKindRegistryIter<'_> {
JobKindRegistryIter(self.0.job_kinds.values())
}
}
impl<'a> IntoIterator for &'a JobKindRegistrySnapshot {
type Item = &'a DynJobKind;
type IntoIter = JobKindRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut JobKindRegistrySnapshot {
type Item = &'a DynJobKind;
type IntoIter = JobKindRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Clone, Debug)]
pub struct JobKindRegistryIter<'a>(
std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynJobKind>,
);
impl<'a> Iterator for JobKindRegistryIter<'a> {
type Item = &'a DynJobKind;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n)
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for JobKindRegistryIter<'a> {}
impl<'a> ExactSizeIterator for JobKindRegistryIter<'a> {}
impl<'a> DoubleEndedIterator for JobKindRegistryIter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth_back(n)
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.rfold(init, f)
}
}
#[derive(Clone, Debug)]
pub struct JobKindRegistryIterWithNames<'a>(
std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynJobKind>,
);
impl<'a> Iterator for JobKindRegistryIterWithNames<'a> {
type Item = (Interned<str>, &'a DynJobKind);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(name, job_kind)| (name.0, job_kind))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last().map(|(name, job_kind)| (name.0, job_kind))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n).map(|(name, job_kind)| (name.0, job_kind))
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, job_kind)| (name.0, job_kind))
.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for JobKindRegistryIterWithNames<'a> {}
impl<'a> ExactSizeIterator for JobKindRegistryIterWithNames<'a> {}
impl<'a> DoubleEndedIterator for JobKindRegistryIterWithNames<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.next_back()
.map(|(name, job_kind)| (name.0, job_kind))
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0
.nth_back(n)
.map(|(name, job_kind)| (name.0, job_kind))
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, job_kind)| (name.0, job_kind))
.rfold(init, f)
}
}
#[track_caller]
pub fn register_job_kind<K: JobKind>(kind: K) {
DynJobKind::new(kind).register();
}

View file

@ -0,0 +1,418 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob,
GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem,
JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
firrtl::{Firrtl, FirrtlJobKind},
},
intern::{Intern, InternSlice, Interned},
util::job_server::AcquiredJob,
};
use clap::Args;
use eyre::{Context, bail};
use serde::{Deserialize, Serialize};
use std::{
ffi::{OsStr, OsString},
fmt, mem,
path::Path,
};
/// based on [LLVM Circt's recommended lowering options][lowering-options]
///
/// [lowering-options]: https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target
#[derive(clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum VerilogDialect {
Questa,
Spyglass,
Verilator,
Vivado,
Yosys,
}
impl fmt::Display for VerilogDialect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl VerilogDialect {
pub fn as_str(self) -> &'static str {
match self {
VerilogDialect::Questa => "questa",
VerilogDialect::Spyglass => "spyglass",
VerilogDialect::Verilator => "verilator",
VerilogDialect::Vivado => "vivado",
VerilogDialect::Yosys => "yosys",
}
}
pub fn firtool_extra_args(self) -> &'static [&'static str] {
match self {
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
VerilogDialect::Spyglass => {
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
}
VerilogDialect::Verilator => &[
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
],
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
VerilogDialect::Yosys => {
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
}
}
}
}
#[derive(Args, Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct UnadjustedVerilogArgs {
#[arg(long = "firtool-extra-arg", value_name = "ARG")]
pub firtool_extra_args: Vec<OsString>,
/// adapt the generated Verilog for a particular toolchain
#[arg(long)]
pub verilog_dialect: Option<VerilogDialect>,
#[arg(long)]
pub verilog_debug: bool,
}
impl ToArgs for UnadjustedVerilogArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
ref firtool_extra_args,
verilog_dialect,
verilog_debug,
} = *self;
for arg in firtool_extra_args {
args.write_long_option_eq("firtool-extra-arg", arg);
}
if let Some(verilog_dialect) = verilog_dialect {
args.write_long_option_eq("verilog-dialect", verilog_dialect.as_str());
}
if verilog_debug {
args.write_arg("--verilog-debug");
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Firtool;
impl ExternalProgramTrait for Firtool {
fn default_program_name() -> Interned<str> {
"firtool".intern()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)]
pub struct UnadjustedVerilog {
firrtl_file: Interned<Path>,
firrtl_file_name: Interned<OsStr>,
unadjusted_verilog_file: Interned<Path>,
unadjusted_verilog_file_name: Interned<OsStr>,
firtool_extra_args: Interned<[Interned<OsStr>]>,
verilog_dialect: Option<VerilogDialect>,
verilog_debug: bool,
}
impl UnadjustedVerilog {
pub fn firrtl_file(&self) -> Interned<Path> {
self.firrtl_file
}
pub fn unadjusted_verilog_file(&self) -> Interned<Path> {
self.unadjusted_verilog_file
}
pub fn firtool_extra_args(&self) -> Interned<[Interned<OsStr>]> {
self.firtool_extra_args
}
pub fn verilog_dialect(&self) -> Option<VerilogDialect> {
self.verilog_dialect
}
pub fn verilog_debug(&self) -> bool {
self.verilog_debug
}
}
impl ExternalCommand for UnadjustedVerilog {
type AdditionalArgs = UnadjustedVerilogArgs;
type AdditionalJobData = UnadjustedVerilog;
type BaseJobPosition = GetJobPositionDependencies<GetJobPositionJob>;
type Dependencies = JobKindAndDependencies<FirrtlJobKind>;
type ExternalProgram = Firtool;
fn dependencies() -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
)> {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let UnadjustedVerilogArgs {
firtool_extra_args,
verilog_dialect,
verilog_debug,
} = args.additional_args;
let unadjusted_verilog_file = dependencies
.dependencies
.job
.job
.file_with_ext("unadjusted.v");
let firrtl_job = dependencies.get_job::<Firrtl, _>();
Ok(UnadjustedVerilog {
firrtl_file: firrtl_job.firrtl_file(),
firrtl_file_name: firrtl_job
.firrtl_file()
.interned_file_name()
.expect("known to have file name"),
unadjusted_verilog_file,
unadjusted_verilog_file_name: unadjusted_verilog_file
.interned_file_name()
.expect("known to have file name"),
firtool_extra_args: firtool_extra_args.into_iter().map(Interned::from).collect(),
verilog_dialect,
verilog_debug,
})
})
}
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.additional_job_data().firrtl_file,
}]
.intern_slice()
}
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
[job.additional_job_data().unadjusted_verilog_file].intern_slice()
}
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
let UnadjustedVerilog {
firrtl_file: _,
firrtl_file_name,
unadjusted_verilog_file: _,
unadjusted_verilog_file_name,
firtool_extra_args,
verilog_dialect,
verilog_debug,
} = *job.additional_job_data();
args.write_interned_arg(firrtl_file_name);
args.write_arg("-o");
args.write_interned_arg(unadjusted_verilog_file_name);
if verilog_debug {
args.write_args(["-g", "--preserve-values=all"]);
}
if let Some(dialect) = verilog_dialect {
args.write_args(dialect.firtool_extra_args().iter().copied());
}
args.write_interned_args(firtool_extra_args);
}
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
Some(job.output_dir())
}
fn job_kind_name() -> Interned<str> {
"unadjusted-verilog".intern()
}
fn subcommand_hidden() -> bool {
true
}
fn run_even_if_cached_arg_name() -> Interned<str> {
"firtool-run-even-if-cached".intern()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VerilogJobKind;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Args)]
#[non_exhaustive]
pub struct VerilogJobArgs {}
impl ToArgs for VerilogJobArgs {
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
let Self {} = self;
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VerilogJob {
output_dir: Interned<Path>,
unadjusted_verilog_file: Interned<Path>,
main_verilog_file: Interned<Path>,
}
impl VerilogJob {
pub fn output_dir(&self) -> Interned<Path> {
self.output_dir
}
pub fn unadjusted_verilog_file(&self) -> Interned<Path> {
self.unadjusted_verilog_file
}
pub fn main_verilog_file(&self) -> Interned<Path> {
self.main_verilog_file
}
#[track_caller]
pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned<Path>] {
match additional_files {
JobItem::DynamicPaths {
paths,
source_job_name,
} if *source_job_name == VerilogJobKind.name() => paths,
v => panic!("expected VerilogJob's additional files JobItem: {v:?}"),
}
}
pub fn all_verilog_files(
main_verilog_file: Interned<Path>,
additional_files: &[Interned<Path>],
) -> eyre::Result<Interned<[Interned<Path>]>> {
let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1));
for verilog_file in [main_verilog_file].iter().chain(additional_files) {
if !["v", "sv"]
.iter()
.any(|extension| verilog_file.extension() == Some(extension.as_ref()))
{
continue;
}
let verilog_file = std::path::absolute(verilog_file).wrap_err_with(|| {
format!("converting {verilog_file:?} to an absolute path failed")
})?;
if verilog_file
.as_os_str()
.as_encoded_bytes()
.iter()
.any(|&ch| (ch != b' ' && ch != b'\t' && ch.is_ascii_whitespace()) || ch == b'"')
{
bail!("verilog file path contains characters that aren't permitted");
}
retval.push(verilog_file.intern_deref());
}
Ok(retval.intern_slice())
}
}
impl JobKind for VerilogJobKind {
type Args = VerilogJobArgs;
type Job = VerilogJob;
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<UnadjustedVerilog>>;
fn dependencies(self) -> Self::Dependencies {
Default::default()
}
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let VerilogJobArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(VerilogJob {
output_dir: base_job.output_dir(),
unadjusted_verilog_file: dependencies
.job
.job
.additional_job_data()
.unadjusted_verilog_file(),
main_verilog_file: base_job.file_with_ext("v"),
})
})
}
fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[JobItemName::Path {
path: job.unadjusted_verilog_file,
}]
.intern_slice()
}
fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> {
[
JobItemName::Path {
path: job.main_verilog_file,
},
JobItemName::DynamicPaths {
source_job_name: self.name(),
},
]
.intern_slice()
}
fn name(self) -> Interned<str> {
"verilog".intern()
}
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
None
}
fn run(
self,
job: &Self::Job,
inputs: &[JobItem],
_params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
let input = std::fs::read_to_string(job.unadjusted_verilog_file())?;
let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
let file_separator_suffix = "\" ----- 8< -----\n\n";
let mut input = &*input;
let main_verilog_file = job.main_verilog_file();
let mut file_name = Some(main_verilog_file);
let mut additional_outputs = Vec::new();
loop {
let (chunk, next_file_name) = if let Some((chunk, rest)) =
input.split_once(file_separator_prefix)
{
let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else {
bail!(
"parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}"
);
};
input = rest;
let next_file_name = job.output_dir.join(next_file_name).intern_deref();
additional_outputs.push(next_file_name);
(chunk, Some(next_file_name))
} else {
(mem::take(&mut input), None)
};
let Some(file_name) = mem::replace(&mut file_name, next_file_name) else {
break;
};
std::fs::write(&file_name, chunk)?;
}
Ok(vec![
JobItem::Path {
path: main_verilog_file,
},
JobItem::DynamicPaths {
paths: additional_outputs,
source_job_name: self.name(),
},
])
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
[
DynJobKind::new(ExternalCommandJobKind::<UnadjustedVerilog>::new()),
DynJobKind::new(VerilogJobKind),
]
}

View file

@ -3,12 +3,14 @@
use crate::{
expr::{
CastToBits, Expr, ReduceBits, ToExpr,
ops::{ArrayLiteral, BundleLiteral, ExprPartialEq},
CastToBits, Expr, HdlPartialEqImpl, ReduceBits, ToExpr, ToSimValueInner, ValueType,
Valueless,
ops::{ArrayLiteral, BundleLiteral},
value_category::{ValueCategoryCommon, ValueCategoryExpr, ValueCategoryValue},
},
int::{Bool, DynSize},
intern::{Intern, Interned},
sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType},
intern::{Intern, InternSlice, Interned},
sim::value::{SimValue, SimValueEq, ToSimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, OpaqueSimValueSize,
@ -18,7 +20,7 @@ use crate::{
util::HashMap,
};
use serde::{Deserialize, Serialize};
use std::{fmt, marker::PhantomData};
use std::{borrow::Cow, fmt, marker::PhantomData};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleField {
@ -271,7 +273,6 @@ impl Type for Bundle {
pub trait BundleType: Type<BaseType = Bundle> {
type Builder: Default;
type FilledBuilder: ToExpr<Type = Self>;
fn fields(&self) -> Interned<[BundleField]>;
}
@ -318,7 +319,7 @@ impl<'a> BundleSimValueFromOpaque<'a> {
#[track_caller]
pub fn field_clone_from_opaque<T: Type>(&mut self, field_value: &mut SimValue<T>) {
let (field_ty, field_opaque) = self.field_ty_and_opaque::<T>();
assert_eq!(field_ty, SimValue::ty(field_value));
assert_eq!(field_ty, field_value.ty());
SimValue::opaque_mut(field_value).clone_from_slice(field_opaque);
}
}
@ -354,7 +355,7 @@ impl<'a> BundleSimValueToOpaque<'a> {
else {
panic!("tried to write too many fields with BundleSimValueToOpaque");
};
assert_eq!(T::from_canonical(ty), SimValue::ty(field_value));
assert_eq!(T::from_canonical(ty), field_value.ty());
self.writer.fill_prefix_with(ty.size(), |writer| {
writer.fill_cloned_from_slice(SimValue::opaque(field_value).as_slice())
});
@ -374,17 +375,8 @@ impl<'a> BundleSimValueToOpaque<'a> {
#[derive(Default)]
pub struct NoBuilder;
pub struct Unfilled<T: Type>(PhantomData<T>);
impl<T: Type> Default for Unfilled<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl BundleType for Bundle {
type Builder = NoBuilder;
type FilledBuilder = Expr<Bundle>;
fn fields(&self) -> Interned<[BundleField]> {
self.0.fields
}
@ -420,15 +412,14 @@ macro_rules! impl_tuple_builder_fields {
) => {
impl<
$($head_type_var,)*
$cur_type_var: Type,
$($tail_type_var,)*
> TupleBuilder<(
$($head_type_var,)*
Unfilled<$cur_type_var>,
(),
$($tail_type_var,)*
)>
{
pub fn $cur_field(self, $cur_var: impl ToExpr<Type = $cur_type_var>) -> TupleBuilder<(
pub fn $cur_field<$cur_type_var: Type>(self, $cur_var: impl ToExpr<Type = $cur_type_var>) -> TupleBuilder<(
$($head_type_var,)*
Expr<$cur_type_var>,
$($tail_type_var,)*
@ -452,6 +443,12 @@ macro_rules! impl_tuple_builder_fields {
($global:tt []) => {};
}
macro_rules! get_unit_ty {
($($tt:tt)*) => {
()
};
}
macro_rules! impl_tuples {
(
[$({
@ -545,11 +542,10 @@ macro_rules! impl_tuples {
}
}
impl<$($T: Type,)*> BundleType for ($($T,)*) {
type Builder = TupleBuilder<($(Unfilled<$T>,)*)>;
type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>;
type Builder = TupleBuilder<($(get_unit_ty!($T),)*)>;
fn fields(&self) -> Interned<[BundleField]> {
let ($($var,)*) = self;
[$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*][..].intern()
[$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice()
}
}
impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) {
@ -572,25 +568,56 @@ macro_rules! impl_tuples {
builder.finish()
};
}
impl<$($T: ToExpr,)*> ToExpr for ($($T,)*) {
impl<'a, $($T: ToSimValue,)*> ToSimValueInner<'a> for ($($T,)*)
where
Self: ValueType<Type = ($($T::Type,)*)>,
{
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
let ($($var,)*) = this;
Cow::Owned(($($var.to_sim_value(),)*))
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
let ($($var,)*) = this;
Cow::Owned(($($var.into_sim_value(),)*))
}
}
impl<$($T: ValueType,)*> ValueType for ($($T,)*)
where
ValueCategoryValue: ValueCategoryCommon<($($T::ValueCategory,)*)>,
{
type Type = ($($T::Type,)*);
type ValueCategory = <ValueCategoryValue as ValueCategoryCommon<($($T::ValueCategory,)*)>>::Common;
fn ty(&self) -> Self::Type {
let ($($var,)*) = self;
($($var.ty(),)*)
}
}
impl<$($T: ToExpr,)*> ToExpr for ($($T,)*)
where
Self: ValueType<Type = ($($T::Type,)*)>,
{
fn to_expr(&self) -> Expr<Self::Type> {
let ($($var,)*) = self;
$(let $var = $var.to_expr();)*
let ty = ($(Expr::ty($var),)*);
let ty = ($($var.ty(),)*);
let field_values = [$(Expr::canonical($var)),*];
BundleLiteral::new(ty, field_values[..].intern()).to_expr()
BundleLiteral::new(ty, field_values.intern_slice()).to_expr()
}
}
impl<$($T: Type,)*> ValueType for TupleBuilder<($(Expr<$T>,)*)> {
type Type = ($($T,)*);
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
let ($($var,)*) = self.0;
($($var.ty(),)*)
}
}
impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> {
type Type = ($($T,)*);
fn to_expr(&self) -> Expr<Self::Type> {
let ($($var,)*) = self.0;
let ty = ($(Expr::ty($var),)*);
let ty = ($($var.ty(),)*);
let field_values = [$(Expr::canonical($var)),*];
BundleLiteral::new(ty, field_values[..].intern()).to_expr()
BundleLiteral::new(ty, field_values.intern_slice()).to_expr()
}
}
impl<$($T: ToSimValueWithType<CanonicalType>,)*> ToSimValueWithType<CanonicalType> for ($($T,)*) {
@ -624,7 +651,7 @@ macro_rules! impl_tuples {
};
let mut opaque = OpaqueSimValue::empty();
$(let $var = $var.into_sim_value_with_type($ty_var.ty);
assert_eq!(SimValue::ty(&$var), $ty_var.ty);
assert_eq!($var.ty(), $ty_var.ty);
opaque.extend_from_slice(SimValue::opaque(&$var).as_slice());
)*
SimValue::from_opaque(ty, opaque)
@ -646,53 +673,82 @@ macro_rules! impl_tuples {
SimValue::from_value(ty, ($($var,)*))
}
}
impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) {
type Type = ($($T::Type,)*);
impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*)
where
Self: ValueType<Type = ($($T::Type,)*)>,
{
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self::Type> {
let ($($var,)*) = self;
$(let $var = $var.to_sim_value();)*
SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*))
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
}
#[track_caller]
fn into_sim_value(self) -> SimValue<Self::Type> {
let ($($var,)*) = self;
$(let $var = $var.to_sim_value();)*
SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*))
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
}
}
impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
impl<$($Lhs: Type + HdlPartialEqImpl<$Rhs>, $Rhs: Type,)*> HdlPartialEqImpl<($($Rhs,)*)> for ($($Lhs,)*) {
#[track_caller]
fn cmp_value_eq(
lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
rhs: ($($Rhs,)*),
rhs_value: Cow<'_, <($($Rhs,)*) as Type>::SimValue>,
) -> bool {
#![allow(unused_variables)]
let ($($lhs_var,)*) = &*lhs_value;
let ($($rhs_var,)*) = &*rhs_value;
let retval = true;
$(let retval = retval && $Lhs::cmp_value_eq(lhs.$num, Cow::Borrowed($lhs_var), rhs.$num, Cow::Borrowed($rhs_var));)*
retval
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
let ($($lhs_var,)*) = *lhs;
let ($($rhs_var,)*) = *rhs;
ArrayLiteral::<Bool, DynSize>::new(
Bool,
FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_eq($lhs_var, $rhs_var)),)*]),
FromIterator::from_iter([$(Expr::canonical($Lhs::cmp_expr_eq($lhs_var, $rhs_var)),)*]),
)
.cast_to_bits()
.all_one_bits()
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
let ($($lhs_var,)*) = *lhs;
let ($($rhs_var,)*) = *rhs;
ArrayLiteral::<Bool, DynSize>::new(
Bool,
FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_ne($lhs_var, $rhs_var)),)*]),
FromIterator::from_iter([$(Expr::canonical($Lhs::cmp_expr_ne($lhs_var, $rhs_var)),)*]),
)
.cast_to_bits()
.any_one_bits()
}
}
impl<$($Lhs: SimValuePartialEq<$Rhs>, $Rhs: Type,)*> SimValuePartialEq<($($Rhs,)*)> for ($($Lhs,)*) {
fn sim_value_eq(lhs: &SimValue<Self>, rhs: &SimValue<($($Rhs,)*)>) -> bool {
let ($($lhs_var,)*) = &**lhs;
let ($($rhs_var,)*) = &**rhs;
let retval = true;
$(let retval = retval && $lhs_var == $rhs_var;)*
retval
#[track_caller]
fn cmp_valueless_eq(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
let ($($lhs_var,)*) = lhs.ty();
let ($($rhs_var,)*) = rhs.ty();
// let them check that the types can be compared
$($Lhs::cmp_valueless_eq(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
Valueless::new(Bool)
}
#[track_caller]
fn cmp_valueless_ne(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
let ($($lhs_var,)*) = lhs.ty();
let ($($rhs_var,)*) = rhs.ty();
// let them check that the types can be compared
$($Lhs::cmp_valueless_ne(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
Valueless::new(Bool)
}
}
impl<$($T: SimValueEq + HdlPartialEqImpl<$T>,)*> SimValueEq for ($($T,)*) {}
};
([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => {
impl_tuples!([$($lhs)*] []);
@ -781,9 +837,16 @@ impl<T: ?Sized + Send + Sync + 'static> Default for PhantomDataBuilder<T> {
}
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomDataBuilder<T> {
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomDataBuilder<T> {
type Type = PhantomData<T>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
PhantomData
}
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomDataBuilder<T> {
fn to_expr(&self) -> Expr<Self::Type> {
PhantomData.to_expr()
}
@ -791,7 +854,6 @@ impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomDataBuilder<T> {
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()
}
@ -810,17 +872,22 @@ impl<T: ?Sized + Send + Sync + 'static> StaticType for PhantomData<T> {
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomData<T> {
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomData<T> {
type Type = PhantomData<T>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
PhantomData
}
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomData<T> {
fn to_expr(&self) -> Expr<Self::Type> {
BundleLiteral::new(PhantomData, Interned::default()).to_expr()
}
}
impl<T: ?Sized + Send + Sync + 'static> ToSimValue for PhantomData<T> {
type Type = PhantomData<T>;
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self> {
SimValue::from_value(*self, *self)

View file

@ -1,806 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
bundle::{Bundle, BundleType},
firrtl::{self, ExportOptions},
intern::Interned,
module::Module,
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
};
use clap::{
Parser, Subcommand, ValueEnum, ValueHint,
builder::{OsStringValueParser, TypedValueParser},
};
use eyre::{Report, eyre};
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>;
pub struct CliError(Report);
impl fmt::Debug for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl error::Error for CliError {}
impl From<io::Error> for CliError {
fn from(value: io::Error) -> Self {
CliError(Report::new(value))
}
}
pub trait RunPhase<Arg> {
type Output;
fn run(&self, arg: Arg) -> Result<Self::Output> {
self.run_with_job(arg, &mut AcquiredJob::acquire())
}
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>;
}
#[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, 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 {
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,
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(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 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>,
_acquired_job: &mut AcquiredJob,
) -> Result<FirrtlOutput> {
let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?;
let firrtl::FileBackend {
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_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_with_job(
&self,
top_module: Interned<Module<T>>,
acquired_job: &mut AcquiredJob,
) -> Result<Self::Output> {
self.run_with_job(*top_module, acquired_job)
}
}
/// based on [LLVM Circt's recommended lowering options
/// ](https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target)
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum VerilogDialect {
Questa,
Spyglass,
Verilator,
Vivado,
Yosys,
}
impl fmt::Display for VerilogDialect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl VerilogDialect {
pub fn as_str(self) -> &'static str {
match self {
VerilogDialect::Questa => "questa",
VerilogDialect::Spyglass => "spyglass",
VerilogDialect::Verilator => "verilator",
VerilogDialect::Vivado => "vivado",
VerilogDialect::Yosys => "yosys",
}
}
pub fn firtool_extra_args(self) -> &'static [&'static str] {
match self {
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
VerilogDialect::Spyglass => {
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
}
VerilogDialect::Verilator => &[
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
],
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
VerilogDialect::Yosys => {
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
}
}
}
}
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct VerilogArgs {
#[command(flatten)]
pub firrtl: FirrtlArgs,
#[arg(
long,
default_value = "firtool",
env = "FIRTOOL",
value_hint = ValueHint::CommandName,
value_parser = OsStringValueParser::new().try_map(which)
)]
pub firtool: PathBuf,
#[arg(long)]
pub firtool_extra_args: Vec<OsString>,
/// adapt the generated Verilog for a particular toolchain
#[arg(long)]
pub verilog_dialect: Option<VerilogDialect>,
#[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 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 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(firtool);
cmd.arg(output.firrtl.firrtl_file());
cmd.arg("-o");
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(firtool_extra_args);
cmd.current_dir(&output.firrtl.output_dir);
let status = firrtl.base.run_external_command(acquired_job, cmd, None)?;
if status.success() {
self.process_unadjusted_verilog_file(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
self.firtool.display()
)))
}
}
}
impl<Arg> RunPhase<Arg> for VerilogArgs
where
FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>,
{
type Output = VerilogOutput;
fn run_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)
}
}
fn which(v: std::ffi::OsString) -> which::Result<PathBuf> {
#[cfg(not(miri))]
return which::which(v);
#[cfg(miri)]
return Ok(Path::new("/").join(v));
}
#[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)
)]
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()
)))
};
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)
}
}
#[derive(Subcommand, Debug)]
enum CliCommand {
/// Generate FIRRTL
Firrtl(FirrtlArgs),
/// Generate Verilog
Verilog(VerilogArgs),
/// Run a formal proof
Formal(FormalArgs),
}
/// a simple CLI
///
/// Use like:
///
/// ```no_run
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use fayalite::cli;
///
/// fn main() -> cli::Result {
/// cli::Cli::parse().run(my_module())
/// }
/// ```
///
/// You can also use it with a larger [`clap`]-based CLI like so:
///
/// ```no_run
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use clap::{Subcommand, Parser};
/// use fayalite::cli;
///
/// #[derive(Subcommand)]
/// pub enum Cmd {
/// #[command(flatten)]
/// Fayalite(cli::Cli),
/// MySpecialCommand {
/// #[arg(long)]
/// foo: bool,
/// },
/// }
///
/// #[derive(Parser)]
/// pub struct Cli {
/// #[command(subcommand)]
/// cmd: Cmd, // or just use cli::Cli directly if you don't need more subcommands
/// }
///
/// fn main() -> cli::Result {
/// match Cli::parse().cmd {
/// Cmd::Fayalite(v) => v.run(my_module())?,
/// Cmd::MySpecialCommand { foo } => println!("special: foo={foo}"),
/// }
/// Ok(())
/// }
/// ```
#[derive(Parser, Debug)]
// clear things that would be crate-specific
#[command(name = "Fayalite Simple CLI", about = None, long_about = None)]
pub struct Cli {
#[command(subcommand)]
subcommand: CliCommand,
}
impl clap::Subcommand for Cli {
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
CliCommand::augment_subcommands(cmd)
}
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
CliCommand::augment_subcommands_for_update(cmd)
}
fn has_subcommand(name: &str) -> bool {
CliCommand::has_subcommand(name)
}
}
impl<T> RunPhase<T> for Cli
where
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
{
type Output = ();
fn run_with_job(&self, arg: T, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
match &self.subcommand {
CliCommand::Firrtl(c) => {
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Verilog(c) => {
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Formal(c) => {
c.run_with_job(arg, acquired_job)?;
}
}
Ok(())
}
}
impl Cli {
/// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`]
pub fn parse() -> Self {
clap::Parser::parse()
}
/// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`]
pub fn run<T>(&self, top_module: T) -> Result<()>
where
Self: RunPhase<T, Output = ()>,
{
RunPhase::run(self, top_module)
}
}

View file

@ -1,10 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
expr::{Expr, ValueType},
hdl,
int::Bool,
reset::{Reset, ResetType},
sim::value::SimValue,
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
@ -91,29 +92,34 @@ impl StaticType for Clock {
}
pub trait ToClock {
fn to_clock(&self) -> Expr<Clock>;
type Output: ValueType<Type = Clock>;
fn to_clock(&self) -> Self::Output;
}
impl<T: ?Sized + ToClock> ToClock for &'_ T {
fn to_clock(&self) -> Expr<Clock> {
type Output = T::Output;
fn to_clock(&self) -> Self::Output {
(**self).to_clock()
}
}
impl<T: ?Sized + ToClock> ToClock for &'_ mut T {
fn to_clock(&self) -> Expr<Clock> {
type Output = T::Output;
fn to_clock(&self) -> Self::Output {
(**self).to_clock()
}
}
impl<T: ?Sized + ToClock> ToClock for Box<T> {
fn to_clock(&self) -> Expr<Clock> {
type Output = T::Output;
fn to_clock(&self) -> Self::Output {
(**self).to_clock()
}
}
impl ToClock for Expr<Clock> {
fn to_clock(&self) -> Expr<Clock> {
type Output = Expr<Clock>;
fn to_clock(&self) -> Self::Output {
*self
}
}
@ -125,7 +131,25 @@ pub struct ClockDomain<R: ResetType = Reset> {
}
impl ToClock for bool {
fn to_clock(&self) -> Expr<Clock> {
self.to_expr().to_clock()
type Output = SimValue<Clock>;
fn to_clock(&self) -> Self::Output {
SimValue::from_value(Clock, *self)
}
}
impl ToClock for SimValue<Bool> {
type Output = SimValue<Clock>;
fn to_clock(&self) -> Self::Output {
SimValue::from_value(Clock, **self)
}
}
impl ToClock for SimValue<Clock> {
type Output = SimValue<Clock>;
fn to_clock(&self) -> Self::Output {
self.clone()
}
}

View file

@ -2,10 +2,7 @@
// See Notices.txt for copyright information
use crate::{
expr::{
Expr, ToExpr,
ops::{ExprPartialEq, VariantAccess},
},
expr::{Expr, HdlPartialEq, HdlPartialEqImpl, ToExpr, ValueType, ops::VariantAccess},
hdl,
int::{Bool, UIntValue},
intern::{Intern, Interned},
@ -13,7 +10,7 @@ use crate::{
EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, connect,
enum_match_variants_helper, incomplete_wire, wire,
},
sim::value::{SimValue, SimValuePartialEq},
sim::value::SimValue,
source_location::SourceLocation,
ty::{
CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, OpaqueSimValueSize,
@ -24,7 +21,7 @@ use crate::{
};
use bitvec::{order::Lsb0, slice::BitSlice, view::BitView};
use serde::{Deserialize, Serialize};
use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc};
use std::{borrow::Cow, convert::Infallible, fmt, iter::FusedIterator, sync::Arc};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct EnumVariant {
@ -599,7 +596,7 @@ impl<'a> EnumSimValueFromOpaque<'a> {
let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(true) else {
self.usage_error(true);
};
assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty));
assert_eq!(value.ty(), T::from_canonical(variant_ty));
SimValue::bits_mut(value)
.bits_mut()
.copy_from_bitslice(variant_bits);
@ -711,7 +708,7 @@ impl<'a> EnumSimValueToOpaque<'a> {
let Some(variant_ty) = self.variants[discriminant].ty else {
panic!("expected variant to have no field");
};
assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty));
assert_eq!(value.ty(), T::from_canonical(variant_ty));
self.known_variant(discriminant, Some(SimValue::opaque(value)), padding)
}
}
@ -732,9 +729,38 @@ pub enum HdlOption<T: Type> {
HdlSome(T),
}
impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> for HdlOption<Lhs> {
impl<Lhs: Type + HdlPartialEqImpl<Rhs>, Rhs: Type> HdlPartialEqImpl<HdlOption<Rhs>>
for HdlOption<Lhs>
{
fn cmp_value_eq(
lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
rhs: HdlOption<Rhs>,
rhs_value: Cow<'_, <HdlOption<Rhs> as Type>::SimValue>,
) -> bool {
type SimValueMatch<T> = <T as Type>::SimValue;
match (&*lhs_value, &*rhs_value) {
(SimValueMatch::<Self>::HdlNone(_), SimValueMatch::<HdlOption<Rhs>>::HdlNone(_)) => {
true
}
(SimValueMatch::<Self>::HdlSome(..), SimValueMatch::<HdlOption<Rhs>>::HdlNone(_))
| (SimValueMatch::<Self>::HdlNone(_), SimValueMatch::<HdlOption<Rhs>>::HdlSome(..)) => {
false
}
(
SimValueMatch::<Self>::HdlSome(l, _),
SimValueMatch::<HdlOption<Rhs>>::HdlSome(r, _),
) => HdlPartialEqImpl::cmp_value_eq(
lhs.HdlSome,
Cow::Borrowed(&**l),
rhs.HdlSome,
Cow::Borrowed(&**r),
),
}
}
#[hdl]
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[hdl]
let cmp_eq = wire();
#[hdl]
@ -743,7 +769,7 @@ impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> fo
{
#[hdl]
match rhs {
HdlSome(rhs) => connect(cmp_eq, ExprPartialEq::cmp_eq(lhs, rhs)),
HdlSome(rhs) => connect(cmp_eq, lhs.cmp_eq(rhs)),
HdlNone => connect(cmp_eq, false),
}
}
@ -760,7 +786,7 @@ impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> fo
}
#[hdl]
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[hdl]
let cmp_ne = wire();
#[hdl]
@ -769,7 +795,7 @@ impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> fo
{
#[hdl]
match rhs {
HdlSome(rhs) => connect(cmp_ne, ExprPartialEq::cmp_ne(lhs, rhs)),
HdlSome(rhs) => connect(cmp_ne, lhs.cmp_ne(rhs)),
HdlNone => connect(cmp_ne, true),
}
}
@ -786,25 +812,6 @@ impl<Lhs: Type + ExprPartialEq<Rhs>, Rhs: Type> ExprPartialEq<HdlOption<Rhs>> fo
}
}
impl<Lhs: SimValuePartialEq<Rhs>, Rhs: Type> SimValuePartialEq<HdlOption<Rhs>> for HdlOption<Lhs> {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<HdlOption<Rhs>>) -> bool {
type SimValueMatch<T> = <T as Type>::SimValue;
match (&**this, &**other) {
(SimValueMatch::<Self>::HdlNone(_), SimValueMatch::<HdlOption<Rhs>>::HdlNone(_)) => {
true
}
(SimValueMatch::<Self>::HdlSome(..), SimValueMatch::<HdlOption<Rhs>>::HdlNone(_))
| (SimValueMatch::<Self>::HdlNone(_), SimValueMatch::<HdlOption<Rhs>>::HdlSome(..)) => {
false
}
(
SimValueMatch::<Self>::HdlSome(l, _),
SimValueMatch::<HdlOption<Rhs>>::HdlSome(r, _),
) => l == r,
}
}
}
#[allow(non_snake_case)]
pub fn HdlNone<T: StaticType>() -> Expr<HdlOption<T>> {
HdlOption[T::TYPE].HdlNone()
@ -813,7 +820,7 @@ pub fn HdlNone<T: StaticType>() -> Expr<HdlOption<T>> {
#[allow(non_snake_case)]
pub fn HdlSome<T: Type>(value: impl ToExpr<Type = T>) -> Expr<HdlOption<T>> {
let value = value.to_expr();
HdlOption[Expr::ty(value)].HdlSome(value)
HdlOption[value.ty()].HdlSome(value)
}
impl<T: Type> HdlOption<T> {
@ -853,7 +860,7 @@ impl<T: Type> HdlOption<T> {
let value = f(value).inspect_err(|_| {
and_then_out.complete(()); // avoid error
})?;
let and_then_out = and_then_out.complete(Expr::ty(value));
let and_then_out = and_then_out.complete(value.ty());
connect(and_then_out, value);
drop(some_scope);
let (Wrap::<<Self as Type>::MatchVariant>::HdlNone, none_scope) =
@ -861,7 +868,7 @@ impl<T: Type> HdlOption<T> {
else {
unreachable!();
};
connect(and_then_out, Expr::ty(and_then_out).HdlNone());
connect(and_then_out, and_then_out.ty().HdlNone());
drop(none_scope);
Ok(and_then_out)
}
@ -879,8 +886,8 @@ impl<T: Type> HdlOption<T> {
#[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());
let and_out = wire(opt_b.ty());
connect(and_out, opt_b.ty().HdlNone());
#[hdl]
if let HdlSome(_) = expr {
connect(and_out, opt_b);
@ -894,8 +901,8 @@ impl<T: Type> HdlOption<T> {
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 filtered = wire(expr.ty());
connect(filtered, expr.ty().HdlNone());
let mut f = Some(f);
#[hdl]
if let HdlSome(v) = expr {
@ -969,7 +976,7 @@ impl<T: Type> HdlOption<T> {
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<R> {
#[hdl]
let mapped = wire(Expr::ty(default));
let mapped = wire(default.ty());
let mut f = Some(f);
#[hdl]
match expr {
@ -994,12 +1001,12 @@ impl<T: Type> HdlOption<T> {
match expr {
HdlSome(v) => {
let v = f.take().unwrap()(v);
let mapped = *retval.get_or_insert_with(|| mapped.complete(Expr::ty(v)));
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
connect(mapped, v);
}
HdlNone => {
let v = default.take().unwrap()();
let mapped = *retval.get_or_insert_with(|| mapped.complete(Expr::ty(v)));
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
connect(mapped, v);
}
}
@ -1009,7 +1016,7 @@ impl<T: Type> HdlOption<T> {
#[track_caller]
pub fn or(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
#[hdl]
let or_out = wire(Expr::ty(expr));
let or_out = wire(expr.ty());
connect(or_out, opt_b);
#[hdl]
if let HdlSome(_) = expr {
@ -1021,7 +1028,7 @@ impl<T: Type> HdlOption<T> {
#[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));
let or_else_out = wire(expr.ty());
connect(or_else_out, f());
#[hdl]
if let HdlSome(_) = expr {
@ -1033,7 +1040,7 @@ impl<T: Type> HdlOption<T> {
#[track_caller]
pub fn unwrap_or(expr: Expr<Self>, default: Expr<T>) -> Expr<T> {
#[hdl]
let unwrap_or_else_out = wire(Expr::ty(default));
let unwrap_or_else_out = wire(default.ty());
connect(unwrap_or_else_out, default);
#[hdl]
if let HdlSome(v) = expr {
@ -1045,7 +1052,7 @@ impl<T: Type> HdlOption<T> {
#[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);
let unwrap_or_else_out = wire(expr.ty().HdlSome);
connect(unwrap_or_else_out, f());
#[hdl]
if let HdlSome(v) = expr {
@ -1057,14 +1064,14 @@ impl<T: Type> HdlOption<T> {
#[track_caller]
pub fn xor(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
#[hdl]
let xor_out = wire(Expr::ty(expr));
let xor_out = wire(expr.ty());
#[hdl]
if let HdlSome(_) = expr {
#[hdl]
if let HdlNone = opt_b {
connect(xor_out, expr);
} else {
connect(xor_out, Expr::ty(expr).HdlNone());
connect(xor_out, expr.ty().HdlNone());
}
} else {
connect(xor_out, opt_b);
@ -1075,8 +1082,8 @@ impl<T: Type> HdlOption<T> {
#[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());
let zip_out = wire(HdlOption[(expr.ty().HdlSome, other.ty().HdlSome)]);
connect(zip_out, zip_out.ty().HdlNone());
#[hdl]
if let HdlSome(l) = expr {
#[hdl]
@ -1093,11 +1100,11 @@ impl<T: Type> HdlOption<HdlOption<T>> {
#[track_caller]
pub fn flatten(expr: Expr<Self>) -> Expr<HdlOption<T>> {
#[hdl]
let flattened = wire(Expr::ty(expr).HdlSome);
let flattened = wire(expr.ty().HdlSome);
#[hdl]
match expr {
HdlSome(v) => connect(flattened, v),
HdlNone => connect(flattened, Expr::ty(expr).HdlSome.HdlNone()),
HdlNone => connect(flattened, expr.ty().HdlSome.HdlNone()),
}
flattened
}
@ -1107,7 +1114,7 @@ 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;
let (t, u) = expr.ty().HdlSome;
#[hdl]
let unzipped = wire((HdlOption[t], HdlOption[u]));
connect(unzipped, (HdlOption[t].HdlNone(), HdlOption[u].HdlNone()));

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,222 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{expr::Valueless, expr::ops::make_impls, prelude::*};
use std::num::NonZero;
macro_rules! assert_neg_impls {
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
const _: () = {
fn _check_neg_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
-> impl ValueType<
ValueCategory = <$ty as ValueType>::ValueCategory,
Type = <<Valueless<<$ty as ValueType>::Type> as std::ops::Neg>::Output as ValueType>::Type,
> {
std::ops::Neg::neg(v)
}
};
};
}
make_impls! {
#[kinds((sint_local<'a, Width>))]
assert_neg_impls! {}
}
macro_rules! assert_not_impls {
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
const _: () = {
fn _check_not_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
-> impl ValueType<
ValueCategory = <$ty as ValueType>::ValueCategory,
Type = <<Valueless<<$ty as ValueType>::Type> as std::ops::Not>::Output as ValueType>::Type,
> {
std::ops::Not::not(v)
}
};
};
}
make_impls! {
#[kinds((int_local<'a, Width>), (bool<'a>))]
assert_not_impls! {}
}
macro_rules! assert_cast_to_bits_impls {
([$($Lifetimes:tt)*][$($Bounds:tt)*] ($ty:ty),) => {
const _: () = {
fn _check_cast_to_bits_impl<$($Lifetimes)*$($Bounds)*>(v: $ty)
-> impl ValueType<
ValueCategory = <$ty as ValueType>::ValueCategory,
Type = <<Valueless<<$ty as ValueType>::Type> as CastToBits>::Output as ValueType>::Type,
> {
<$ty as CastToBits>::cast_to_bits(&v)
}
};
};
}
make_impls! {
#[kinds((uint<'a, Width>), (sint<'a, Width>), (bool<'a>))]
assert_cast_to_bits_impls! {}
}
macro_rules! assert_simple_bin_op_impls {
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] ($($R:tt)*), !$Trait:ident::$f:ident) => {
#[expect(dead_code)]
const _: () = {
trait HasImpl {
fn check_impl(self);
}
trait NoImpl {
fn check_impl(&self) -> &'static dyn NoImpl;
}
impl<L, R> NoImpl for (L, R) {
fn check_impl(&self) -> &'static dyn NoImpl {
unreachable!()
}
}
impl<L: std::ops::$Trait<R>, R> HasImpl for (L, R) {
fn check_impl(self) {}
}
fn check_simple_bin_op_no_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: $($R)*) {
let _: &'static dyn NoImpl = (l, r).check_impl();
}
};
};
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] (usize), Shl::shl) => {
const _: () = {
#[expect(dead_code)]
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: usize)
-> impl ValueType<
ValueCategory = <($($L)*, usize) as ValueType>::ValueCategory,
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::Shl<usize>>::Output as ValueType>::Type,
> {
std::ops::Shl::shl(l, r)
}
};
};
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] (usize), Shr::shr) => {
const _: () = {
#[expect(dead_code)]
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: usize)
-> impl ValueType<
ValueCategory = <($($L)*, usize) as ValueType>::ValueCategory,
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::Shr<usize>>::Output as ValueType>::Type,
> {
std::ops::Shr::shr(l, r)
}
};
};
([$($LLifetimes:tt)*][$($LBounds:tt)*] ($($L:tt)*), [$($RLifetimes:tt)*][$($RBounds:tt)*] ($($R:tt)*), $Trait:ident::$f:ident) => {
const _: () = {
#[expect(dead_code)]
fn check_simple_bin_op_impl<$($LLifetimes)* $($RLifetimes)* $($LBounds)* $($RBounds)*>(l: $($L)*, r: $($R)*)
-> impl ValueType<
ValueCategory = <($($L)*, $($R)*) as ValueType>::ValueCategory,
Type = <<Valueless<<$($L)* as ValueType>::Type> as std::ops::$Trait<Valueless<<$($R)* as ValueType>::Type>>>::Output as ValueType>::Type,
> {
std::ops::$Trait::$f(l, r)
}
};
};
($LLifetimes:tt $LBounds:tt ($($L:tt)*), $RLifetimes:tt $RBounds:tt ($($R:tt)*), !$FirstTrait:ident::$first_f:ident, $($rest:tt)*) => {
assert_simple_bin_op_impls! {
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), !$FirstTrait::$first_f
}
assert_simple_bin_op_impls! {
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $($rest)*
}
};
($LLifetimes:tt $LBounds:tt ($($L:tt)*), $RLifetimes:tt $RBounds:tt ($($R:tt)*), $FirstTrait:ident::$first_f:ident, $($rest:tt)*) => {
assert_simple_bin_op_impls! {
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $FirstTrait::$first_f
}
assert_simple_bin_op_impls! {
$LLifetimes $LBounds ($($L)*), $RLifetimes $RBounds ($($R)*), $($rest)*
}
};
}
make_impls! {
#[kinds((uint_local<'l, L>))]
#[kinds((uint<'r, R>))]
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((uint<'l, L>))]
#[kinds((uint_local<'r, R>))]
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor, Shl::shl, Shr::shr}
}
make_impls! {
#[kinds((sint_local<'l, L>))]
#[kinds((sint<'r, R>))]
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((sint<'l, L>))]
#[kinds((sint_local<'r, R>))]
assert_simple_bin_op_impls! {Add::add, Sub::sub, Mul::mul, Div::div, Rem::rem, BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((sint<'l, L>))]
#[kinds((uint_local<'r, R>))]
assert_simple_bin_op_impls! {Shl::shl, Shr::shr}
}
make_impls! {
#[kinds((uint_local<'l, L>))]
assert_simple_bin_op_impls! {[][] (usize), Shl::shl, Shr::shr}
}
make_impls! {
#[kinds((sint_local<'l, L>))]
assert_simple_bin_op_impls! {[][] (usize), Shl::shl, Shr::shr}
}
make_impls! {
#[kinds((bool_local<'l>))]
#[kinds((bool_local<'r>))]
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((bool<'l>))]
#[kinds((Valueless<Bool>))]
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((Valueless<Bool>))]
#[kinds((bool<'r>))]
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
make_impls! {
#[kinds((bool_at_most_sim_value<'l>))]
#[kinds((bool_at_most_sim_value<'r>))]
assert_simple_bin_op_impls! {BitAnd::bitand, BitOr::bitor, BitXor::bitxor}
}
// assert there are no impls of BitAnd/BitOr/BitXor between Expr<Bool> and Rust's bool,
// since that helps avoid using `==`/`!=` in hdl boolean expressions, which doesn't do
// what is usually wanted.
make_impls! {
#[kinds((Expr<Bool>))]
#[kinds(bool)]
assert_simple_bin_op_impls! {!BitAnd::bitand, !BitOr::bitor, !BitXor::bitxor}
}
make_impls! {
#[kinds(bool)]
#[kinds((Expr<Bool>))]
assert_simple_bin_op_impls! {!BitAnd::bitand, !BitOr::bitor, !BitXor::bitxor}
}

View file

@ -3,7 +3,7 @@
use crate::{
array::Array,
bundle::{Bundle, BundleField},
expr::{Expr, Flow, ToExpr},
expr::{Expr, Flow, ToExpr, ValueType, value_category::ValueCategoryExpr},
intern::{Intern, Interned},
memory::{DynPortType, MemPort},
module::{Instance, ModuleIO, TargetName},
@ -196,9 +196,18 @@ macro_rules! impl_target_base {
}
}
impl ToExpr for $TargetBase {
impl ValueType for $TargetBase {
type Type = CanonicalType;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
match self {
$(Self::$Variant(v) => v.ty().canonical(),)*
}
}
}
impl ToExpr for $TargetBase {
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$(Self::$Variant(v) => Expr::canonical(v.to_expr()),)*

View file

@ -0,0 +1,378 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{expr::ValueType, ty::Type};
trait ValueCategorySealed {}
#[expect(private_bounds)]
pub trait ValueCategory:
ValueCategorySealed
+ Copy
+ Ord
+ Default
+ std::fmt::Debug
+ std::hash::Hash
+ 'static
+ Send
+ Sync
{
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>>: ValueType<Type = D::Type, ValueCategory: ValueCategoryIsValueSimValueExprOrValueless>;
#[track_caller]
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> Self::DispatchOutput<D>;
#[track_caller]
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> ValueCategoryDispatchOutputEnum<D>;
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
pub struct ValueCategoryValue;
impl ValueCategorySealed for ValueCategoryValue {}
impl ValueCategory for ValueCategoryValue {
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Value;
#[track_caller]
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> Self::DispatchOutput<D> {
dispatch.dispatch_value()
}
#[track_caller]
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> ValueCategoryDispatchOutputEnum<D> {
ValueCategoryDispatchOutputEnum::Value(dispatch.dispatch_value())
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
pub struct ValueCategorySimValue;
impl ValueCategorySealed for ValueCategorySimValue {}
impl ValueCategory for ValueCategorySimValue {
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::SimValue;
#[track_caller]
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> Self::DispatchOutput<D> {
dispatch.dispatch_sim_value()
}
#[track_caller]
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> ValueCategoryDispatchOutputEnum<D> {
ValueCategoryDispatchOutputEnum::SimValue(dispatch.dispatch_sim_value())
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
pub struct ValueCategoryExpr;
impl ValueCategorySealed for ValueCategoryExpr {}
impl ValueCategory for ValueCategoryExpr {
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Expr;
#[track_caller]
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> Self::DispatchOutput<D> {
dispatch.dispatch_expr()
}
#[track_caller]
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> ValueCategoryDispatchOutputEnum<D> {
ValueCategoryDispatchOutputEnum::Expr(dispatch.dispatch_expr())
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)]
pub struct ValueCategoryValueless;
impl ValueCategorySealed for ValueCategoryValueless {}
impl ValueCategory for ValueCategoryValueless {
type DispatchOutput<D: ValueCategoryDispatch<InputValueCategory = Self>> = D::Valueless;
#[track_caller]
fn dispatch<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> Self::DispatchOutput<D> {
dispatch.dispatch_valueless()
}
#[track_caller]
fn dispatch_enum<D: ValueCategoryDispatch<InputValueCategory = Self>>(
dispatch: D,
) -> ValueCategoryDispatchOutputEnum<D> {
ValueCategoryDispatchOutputEnum::Valueless(dispatch.dispatch_valueless())
}
}
pub trait ValueCategoryIsValue: ValueCategoryIsValueOrSimValue {}
impl ValueCategoryIsValue for ValueCategoryValue {}
pub trait ValueCategoryIsValueOrSimValue: ValueCategoryIsValueSimValueOrExpr {}
impl ValueCategoryIsValueOrSimValue for ValueCategoryValue {}
impl ValueCategoryIsValueOrSimValue for ValueCategorySimValue {}
pub trait ValueCategoryIsValueSimValueOrExpr: ValueCategoryIsValueSimValueExprOrValueless {}
impl ValueCategoryIsValueSimValueOrExpr for ValueCategoryValue {}
impl ValueCategoryIsValueSimValueOrExpr for ValueCategorySimValue {}
impl ValueCategoryIsValueSimValueOrExpr for ValueCategoryExpr {}
pub trait ValueCategoryIsValueSimValueExprOrValueless: ValueCategory {}
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryValue {}
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategorySimValue {}
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryExpr {}
impl ValueCategoryIsValueSimValueExprOrValueless for ValueCategoryValueless {}
pub trait ValueCategoryIsValueless: ValueCategoryIsExprOrValueless {}
impl ValueCategoryIsValueless for ValueCategoryValueless {}
pub trait ValueCategoryIsExprOrValueless: ValueCategoryIsValueSimValueExprOrValueless {}
impl ValueCategoryIsExprOrValueless for ValueCategoryExpr {}
impl ValueCategoryIsExprOrValueless for ValueCategoryValueless {}
pub trait ValueCategoryIsSimValueExprOrValueless:
ValueCategoryIsValueSimValueExprOrValueless
{
}
impl ValueCategoryIsSimValueExprOrValueless for ValueCategorySimValue {}
impl ValueCategoryIsSimValueExprOrValueless for ValueCategoryExpr {}
impl ValueCategoryIsSimValueExprOrValueless for ValueCategoryValueless {}
trait ValueCategoryCommonSealed {}
#[expect(private_bounds)]
pub trait ValueCategoryCommon<
T: ValueCategoryCommonSealed
+ Copy
+ Ord
+ Default
+ std::fmt::Debug
+ std::hash::Hash
+ 'static
+ Send
+ Sync,
>: ValueCategory
{
type Common: ValueCategory;
}
macro_rules! impl_value_category_common {
($($T:ident),* $(,)?) => {
impl_value_category_common!([$($T,)*]);
};
([$($A:ident,)?]) => {};
([$FirstT:ident, $($T:ident,)+]) => {
impl_value_category_common!([$($T,)*]);
impl<$FirstT: ValueCategory, $($T: ValueCategory),*> ValueCategoryCommonSealed for ($FirstT, $($T),*) {}
impl<This, $FirstT, $($T),*> ValueCategoryCommon<($FirstT, $($T),*)> for This
where
$FirstT: ValueCategory,
$($T: ValueCategory,)*
This: ValueCategoryCommon<($FirstT,), Common: ValueCategoryCommon<($($T,)*)>>,
{
type Common = <<This as ValueCategoryCommon<($FirstT,)>>::Common as ValueCategoryCommon<($($T,)*)>>::Common;
}
};
}
impl_value_category_common!(T11, T10, T9, T8, T7, T6, T5, T4, T3, T2, T1, T0);
impl<T: ValueCategory> ValueCategoryCommonSealed for (T,) {}
impl ValueCategoryCommonSealed for () {}
impl<T: ValueCategory> ValueCategoryCommon<()> for T {
type Common = T;
}
impl<T: ValueCategoryIsValue> ValueCategoryCommon<(ValueCategoryValue,)> for T {
type Common = ValueCategoryValue;
}
impl<T: ValueCategoryIsValueOrSimValue> ValueCategoryCommon<(ValueCategorySimValue,)> for T {
type Common = ValueCategorySimValue;
}
impl<T: ValueCategoryIsValue> ValueCategoryCommon<(T,)> for ValueCategorySimValue {
type Common = ValueCategorySimValue;
}
impl<T: ValueCategoryIsValueSimValueOrExpr> ValueCategoryCommon<(ValueCategoryExpr,)> for T {
type Common = ValueCategoryExpr;
}
impl<T: ValueCategoryIsValueOrSimValue> ValueCategoryCommon<(T,)> for ValueCategoryExpr {
type Common = ValueCategoryExpr;
}
impl<T: ValueCategoryIsValueSimValueExprOrValueless> ValueCategoryCommon<(ValueCategoryValueless,)>
for T
{
type Common = ValueCategoryValueless;
}
impl<T: ValueCategoryIsValueSimValueOrExpr> ValueCategoryCommon<(T,)> for ValueCategoryValueless {
type Common = ValueCategoryValueless;
}
pub trait ValueCategoryDispatch: Sized {
type Type: Type;
type InputValueCategory: ValueCategory;
type Value: ValueType<ValueCategory: ValueCategoryIsValueSimValueExprOrValueless, Type = Self::Type>;
type SimValue: ValueType<ValueCategory: ValueCategoryIsSimValueExprOrValueless, Type = Self::Type>;
type Expr: ValueType<ValueCategory: ValueCategoryIsExprOrValueless, Type = Self::Type>;
type Valueless: ValueType<ValueCategory: ValueCategoryIsValueless, Type = Self::Type>;
fn dispatch_value(self) -> Self::Value
where
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryValue>;
fn dispatch_sim_value(self) -> Self::SimValue
where
Self: ValueCategoryDispatch<InputValueCategory = ValueCategorySimValue>;
fn dispatch_expr(self) -> Self::Expr
where
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryExpr>;
fn dispatch_valueless(self) -> Self::Valueless
where
Self: ValueCategoryDispatch<InputValueCategory = ValueCategoryValueless>;
fn dispatch(self) -> <Self::InputValueCategory as ValueCategory>::DispatchOutput<Self> {
Self::InputValueCategory::dispatch(self)
}
fn dispatch_enum(self) -> ValueCategoryDispatchOutputEnum<Self> {
Self::InputValueCategory::dispatch_enum(self)
}
}
pub enum ValueCategoryDispatchOutputEnum<T: ValueCategoryDispatch> {
Value(T::Value),
SimValue(T::SimValue),
Expr(T::Expr),
Valueless(T::Valueless),
}
impl<T: ValueCategoryDispatch> ValueCategoryDispatchOutputEnum<T> {
pub fn try_into_value(self) -> Result<T::Value, Self> {
if let Self::Value(v) = self {
Ok(v)
} else {
Err(self)
}
}
pub fn try_into_sim_value(self) -> Result<T::SimValue, Self> {
if let Self::SimValue(v) = self {
Ok(v)
} else {
Err(self)
}
}
pub fn try_into_expr(self) -> Result<T::Expr, Self> {
if let Self::Expr(v) = self {
Ok(v)
} else {
Err(self)
}
}
pub fn try_into_valueless(self) -> Result<T::Valueless, Self> {
if let Self::Valueless(v) = self {
Ok(v)
} else {
Err(self)
}
}
#[track_caller]
#[cold]
fn unwrap_invalid(self, expected_kind: &'static str) -> ! {
match self {
Self::Value(_) => panic!("expected {expected_kind}, got Value"),
Self::SimValue(_) => panic!("expected {expected_kind}, got SimValue"),
Self::Expr(_) => panic!("expected {expected_kind}, got Expr"),
Self::Valueless(_) => panic!("expected {expected_kind}, got Valueless"),
}
}
#[track_caller]
pub fn into_value_unwrap(self) -> T::Value {
let Self::Value(v) = self else {
self.unwrap_invalid("Value");
};
v
}
#[track_caller]
pub fn into_sim_value_unwrap(self) -> T::SimValue {
let Self::SimValue(v) = self else {
self.unwrap_invalid("SimValue");
};
v
}
#[track_caller]
pub fn into_expr_unwrap(self) -> T::Expr {
let Self::Expr(v) = self else {
self.unwrap_invalid("Expr");
};
v
}
#[track_caller]
pub fn into_valueless_unwrap(self) -> T::Valueless {
let Self::Valueless(v) = self else {
self.unwrap_invalid("Valueless");
};
v
}
pub fn ty(&self) -> T::Type {
match self {
Self::Value(v) => v.ty(),
Self::SimValue(v) => v.ty(),
Self::Expr(v) => v.ty(),
Self::Valueless(v) => v.ty(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type Identity<T> = T;
macro_rules! check_categories {
($($Categories:ident,)+) => {
check_categories!([] [$($Categories,)+]);
};
([$($Categories:ident,)*] []) => {};
([$($WeakerCategories:ident,)*] [$Category:ident, $($StrongerCategories:ident,)*]) => {
$(const _: $Category = Identity::<<$WeakerCategories as ValueCategoryCommon<($Category,)>>::Common> {};
const _: $Category = Identity::<<$Category as ValueCategoryCommon<($WeakerCategories,)>>::Common> {};)*
const _: $Category = Identity::<<$Category as ValueCategoryCommon<($Category,)>>::Common> {};
check_categories!([$($WeakerCategories,)* $Category,] [$($StrongerCategories,)*]);
};
}
check_categories!(
ValueCategoryValue,
ValueCategorySimValue,
ValueCategoryExpr,
ValueCategoryValueless,
);
}

View file

@ -4,14 +4,15 @@
use crate::{
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::Array,
build::{ToArgs, WriteArgs},
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},
expr::{
CastBitsTo, Expr, ExprEnum,
CastBitsTo, Expr, ExprEnum, ToExpr, ValueType,
ops::{self, VariantAccess},
target::{
Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement,
@ -23,9 +24,9 @@ use crate::{
memory::{Mem, PortKind, PortName, ReadUnderWrite},
module::{
AnnotatedModuleIO, Block, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Module, ModuleBody, NameOptId, NormalModuleBody, Stmt,
StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg,
StmtWire,
ExternModuleParameterValue, Module, ModuleBody, ModuleIO, NameId, NameOptId,
NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance,
StmtMatch, StmtReg, StmtWire,
transform::{
simplify_enums::{SimplifyEnumsError, SimplifyEnumsKind, simplify_enums},
simplify_memories::simplify_memories,
@ -38,21 +39,23 @@ use crate::{
BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet,
const_str_array_is_strictly_ascending,
},
vendor::xilinx::XilinxAnnotation,
};
use bitvec::slice::BitSlice;
use clap::value_parser;
use num_traits::Signed;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::{
cell::{Cell, RefCell},
cmp::Ordering,
collections::{BTreeMap, VecDeque},
error::Error,
ffi::OsString,
fmt::{self, Write},
fs,
hash::Hash,
io,
ops::Range,
ops::{ControlFlow, Range},
path::{Path, PathBuf},
rc::Rc,
};
@ -404,10 +407,10 @@ impl TypeState {
self.next_type_name.set(id + 1);
Ident(Intern::intern_owned(format!("Ty{id}")))
}
fn get_bundle_field(&mut self, ty: Bundle, name: Interned<str>) -> Result<Ident> {
fn get_bundle_field(&mut self, ty: Bundle, name: Interned<str>) -> Result<Ident, FirrtlError> {
Ok(self.bundle_ns(ty)?.borrow_mut().get(name))
}
fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc<RefCell<Namespace>>)> {
fn bundle_def(&self, ty: Bundle) -> Result<(Ident, Rc<RefCell<Namespace>>), FirrtlError> {
self.bundle_defs.get_or_make(ty, |&ty, definitions| {
let mut ns = Namespace::default();
let mut body = String::new();
@ -428,13 +431,13 @@ impl TypeState {
Ok((name, Rc::new(RefCell::new(ns))))
})
}
fn bundle_ty(&self, ty: Bundle) -> Result<Ident> {
fn bundle_ty(&self, ty: Bundle) -> Result<Ident, FirrtlError> {
Ok(self.bundle_def(ty)?.0)
}
fn bundle_ns(&self, ty: Bundle) -> Result<Rc<RefCell<Namespace>>> {
fn bundle_ns(&self, ty: Bundle) -> Result<Rc<RefCell<Namespace>>, FirrtlError> {
Ok(self.bundle_def(ty)?.1)
}
fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc<EnumDef>)> {
fn enum_def(&self, ty: Enum) -> Result<(Ident, Rc<EnumDef>), FirrtlError> {
self.enum_defs.get_or_make(ty, |&ty, definitions| {
let mut variants = Namespace::default();
let mut body = String::new();
@ -461,13 +464,13 @@ impl TypeState {
))
})
}
fn enum_ty(&self, ty: Enum) -> Result<Ident> {
fn enum_ty(&self, ty: Enum) -> Result<Ident, FirrtlError> {
Ok(self.enum_def(ty)?.0)
}
fn get_enum_variant(&mut self, ty: Enum, name: Interned<str>) -> Result<Ident> {
fn get_enum_variant(&mut self, ty: Enum, name: Interned<str>) -> Result<Ident, FirrtlError> {
Ok(self.enum_def(ty)?.1.variants.borrow_mut().get(name))
}
fn ty<T: Type>(&self, ty: T) -> Result<String> {
fn ty<T: Type>(&self, ty: T) -> Result<String, FirrtlError> {
Ok(match ty.canonical() {
CanonicalType::Bundle(ty) => self.bundle_ty(ty)?.to_string(),
CanonicalType::Enum(ty) => self.enum_ty(ty)?.to_string(),
@ -485,7 +488,7 @@ impl TypeState {
CanonicalType::Reset(Reset {}) => "Reset".into(),
CanonicalType::PhantomConst(_) => "{}".into(),
CanonicalType::DynSimOnly(_) => {
return Err(FirrtlError::SimOnlyValuesAreNotPermitted.into());
return Err(FirrtlError::SimOnlyValuesAreNotPermitted);
}
})
}
@ -874,7 +877,7 @@ impl<'a> Exporter<'a> {
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
let from_ty = Expr::ty(value);
let from_ty = value.ty();
let mut value = self.expr(Expr::canonical(value), definitions, const_ty)?;
if from_ty.width().checked_add(1) == Some(to_ty.width())
&& !FromTy::Signed::VALUE
@ -923,7 +926,7 @@ impl<'a> Exporter<'a> {
definitions: &RcDefinitions,
const_ty: bool,
) -> Result<String> {
let base_width = Expr::ty(base).width();
let base_width = base.ty().width();
let base = self.expr(Expr::canonical(base), definitions, const_ty)?;
if range.is_empty() {
Ok(format!("tail({base}, {base_width})"))
@ -1206,9 +1209,7 @@ impl<'a> Exporter<'a> {
| CanonicalType::AsyncReset(_)
| CanonicalType::Reset(_) => Ok(format!("asUInt({value_str})")),
CanonicalType::PhantomConst(_) => Ok("UInt<0>(0)".into()),
CanonicalType::DynSimOnly(_) => {
Err(FirrtlError::SimOnlyValuesAreNotPermitted.into())
}
CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()),
}
}
fn expr_cast_bits_to_bundle(
@ -1429,9 +1430,7 @@ impl<'a> Exporter<'a> {
definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}"));
return Ok(retval.to_string());
}
CanonicalType::DynSimOnly(_) => {
Err(FirrtlError::SimOnlyValuesAreNotPermitted.into())
}
CanonicalType::DynSimOnly(_) => Err(FirrtlError::SimOnlyValuesAreNotPermitted.into()),
}
}
fn expr_unary<T: Type>(
@ -1471,7 +1470,7 @@ impl<'a> Exporter<'a> {
ExprEnum::SIntLiteral(literal) => Ok(self.sint_literal(&literal)),
ExprEnum::BoolLiteral(literal) => Ok(self.bool_literal(literal)),
ExprEnum::PhantomConst(ty) => self.expr(
UInt[0].zero().cast_bits_to(ty.canonical()),
UInt[0].zero().to_expr().cast_bits_to(ty.canonical()),
definitions,
const_ty,
),
@ -1654,7 +1653,7 @@ impl<'a> Exporter<'a> {
let value_str = self.expr(expr.arg(), definitions, const_ty)?;
self.expr_cast_to_bits(
value_str,
Expr::ty(expr.arg()),
expr.arg().ty(),
definitions,
Indent {
indent_depth: &Cell::new(0),
@ -1777,7 +1776,7 @@ impl<'a> Exporter<'a> {
let mut out = self.expr(Expr::canonical(expr.base()), definitions, const_ty)?;
let name = self
.type_state
.get_bundle_field(Expr::ty(expr.base()), expr.field_name())?;
.get_bundle_field(expr.base().ty(), expr.field_name())?;
write!(out, ".{name}").unwrap();
Ok(out)
}
@ -1884,7 +1883,11 @@ impl<'a> Exporter<'a> {
}
fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) {
let data = match annotation {
Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch,
Annotation::DontTouch(DontTouchAnnotation {}) => {
// TODO: error if the annotated thing was renamed because of a naming conflict,
// unless Target::base() is one of the ports of the top-level module since that's handled by ScalarizedModuleABI
AnnotationData::DontTouch
}
Annotation::SVAttribute(SVAttributeAnnotation { text }) => {
AnnotationData::AttributeAnnotation { description: *text }
}
@ -1907,6 +1910,9 @@ impl<'a> Exporter<'a> {
class: str::to_string(class),
additional_fields: (*additional_fields).into(),
},
Annotation::Xilinx(XilinxAnnotation::XdcLocation(_))
| Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_))
| Annotation::Xilinx(XilinxAnnotation::XdcCreateClock(_)) => return,
};
self.annotations.push(FirrtlAnnotation {
data,
@ -2095,12 +2101,12 @@ impl<'a> Exporter<'a> {
rhs,
source_location,
}) => {
if Expr::ty(lhs) != Expr::ty(rhs) {
if lhs.ty() != rhs.ty() {
writeln!(
body,
"{indent}; connect different types:\n{indent}; lhs: {:?}\n{indent}; rhs: {:?}",
Expr::ty(lhs),
Expr::ty(rhs),
lhs.ty(),
rhs.ty(),
)
.unwrap();
}
@ -2194,7 +2200,7 @@ impl<'a> Exporter<'a> {
FileInfo::new(source_location),
)
.unwrap();
let enum_ty = Expr::ty(expr);
let enum_ty = expr.ty();
let match_arms_indent = indent.push();
for ((variant_index, variant), match_arm_block) in
enum_ty.variants().iter().enumerate().zip(blocks)
@ -2320,6 +2326,7 @@ impl<'a> Exporter<'a> {
ModuleBody::Extern(ExternModuleBody {
verilog_name,
parameters,
clocks_for_past: _,
simulation: _,
}) => {
let verilog_name = Ident(verilog_name);
@ -2454,7 +2461,7 @@ impl<T: ?Sized + FileBackendTrait> FileBackendTrait for &'_ mut T {
pub struct FileBackend {
pub dir_path: PathBuf,
pub circuit_name: Option<String>,
pub top_fir_file_stem: Option<String>,
pub top_fir_file_stem: Option<OsString>,
}
impl FileBackend {
@ -2503,7 +2510,7 @@ impl FileBackendTrait for FileBackend {
) -> Result<(), Self::Error> {
let top_fir_file_stem = self
.top_fir_file_stem
.get_or_insert_with(|| circuit_name.clone());
.get_or_insert_with(|| circuit_name.clone().into());
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()) {
@ -2677,21 +2684,12 @@ impl FileBackendTrait for TestBackend {
fn export_impl(
file_backend: &mut dyn WrappedFileBackendTrait,
mut top_module: Interned<Module<Bundle>>,
top_module: Interned<Module<Bundle>>,
options: ExportOptions,
) -> Result<(), WrappedError> {
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 top_module = options
.prepare_top_module(top_module)
.map_err(|e| file_backend.simplify_enums_error(e))?;
let indent_depth = Cell::new(0);
let mut global_ns = Namespace::default();
let circuit_name = global_ns.get(top_module.name_id());
@ -2753,14 +2751,23 @@ impl clap::builder::TypedValueParser for OptionSimplifyEnumsKindValueParser {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExportOptionsPrivate(());
#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash)]
impl ExportOptionsPrivate {
fn private_new() -> Self {
Self(())
}
}
#[derive(clap::Parser, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ExportOptions {
#[clap(long = "no-simplify-memories", action = clap::ArgAction::SetFalse)]
#[serde(default = "ExportOptions::default_simplify_memories")]
pub simplify_memories: bool,
#[clap(long, value_parser = OptionSimplifyEnumsKindValueParser, default_value = "replace-with-bundle-of-uints")]
pub simplify_enums: std::option::Option<SimplifyEnumsKind>,
#[serde(default = "ExportOptions::default_simplify_enums")]
pub simplify_enums: std::option::Option<SimplifyEnumsKind>, // use std::option::Option instead of Option to avoid clap mis-parsing
#[doc(hidden)]
#[clap(skip = ExportOptionsPrivate(()))]
#[serde(skip, default = "ExportOptionsPrivate::private_new")]
/// `#[non_exhaustive]` except allowing struct update syntax
pub __private: ExportOptionsPrivate,
}
@ -2771,7 +2778,34 @@ impl fmt::Debug for ExportOptions {
}
}
impl ToArgs for ExportOptions {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
simplify_memories,
simplify_enums,
__private: ExportOptionsPrivate(()),
} = *self;
if !simplify_memories {
args.write_arg("--no-simplify-memories");
}
let simplify_enums = simplify_enums.map(|v| {
clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants")
});
let simplify_enums = match &simplify_enums {
None => OptionSimplifyEnumsKindValueParser::NONE_NAME,
Some(v) => v.get_name(),
};
args.write_long_option_eq("simplify-enums", simplify_enums);
}
}
impl ExportOptions {
fn default_simplify_memories() -> bool {
true
}
fn default_simplify_enums() -> Option<SimplifyEnumsKind> {
Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts)
}
fn debug_fmt(
&self,
f: &mut fmt::Formatter<'_>,
@ -2823,18 +2857,47 @@ impl ExportOptions {
if f.alternate() { "\n}" } else { " }" }
)
}
fn prepare_top_module_helper(
self,
mut top_module: Interned<Module<Bundle>>,
) -> Result<Interned<Module<Bundle>>, SimplifyEnumsError> {
let Self {
simplify_memories: do_simplify_memories,
simplify_enums: do_simplify_enums,
__private: _,
} = self;
if let Some(kind) = do_simplify_enums {
top_module = simplify_enums(top_module, kind)?;
}
if do_simplify_memories {
top_module = simplify_memories(top_module);
}
Ok(top_module)
}
pub fn prepare_top_module<T: BundleType>(
self,
top_module: impl AsRef<Module<T>>,
) -> Result<Interned<Module<Bundle>>, SimplifyEnumsError> {
self.prepare_top_module_helper(top_module.as_ref().canonical().intern())
}
}
impl Default for ExportOptions {
fn default() -> Self {
Self {
simplify_memories: true,
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
simplify_memories: Self::default_simplify_memories(),
simplify_enums: Self::default_simplify_enums(),
__private: ExportOptionsPrivate(()),
}
}
}
pub fn get_circuit_name(top_module_name_id: NameId) -> Interned<str> {
let mut global_ns = Namespace::default();
let circuit_name = global_ns.get(top_module_name_id);
circuit_name.0
}
pub fn export<T: BundleType, B: FileBackendTrait>(
file_backend: B,
top_module: &Module<T>,
@ -2846,6 +2909,497 @@ pub fn export<T: BundleType, B: FileBackendTrait>(
})
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ScalarizedModuleABIError {
SimOnlyValuesAreNotPermitted,
SimplifyEnumsError(SimplifyEnumsError),
}
impl fmt::Display for ScalarizedModuleABIError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted => {
FirrtlError::SimOnlyValuesAreNotPermitted.fmt(f)
}
ScalarizedModuleABIError::SimplifyEnumsError(e) => e.fmt(f),
}
}
}
impl std::error::Error for ScalarizedModuleABIError {}
impl From<SimplifyEnumsError> for ScalarizedModuleABIError {
fn from(value: SimplifyEnumsError) -> Self {
Self::SimplifyEnumsError(value)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum ScalarizedModuleABIPortItem {
Group(ScalarizedModuleABIPortGroup),
Port(ScalarizedModuleABIPort),
}
impl ScalarizedModuleABIPortItem {
pub fn module_io(self) -> ModuleIO<CanonicalType> {
*self
.target()
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
match self {
Self::Group(v) => v.target(),
Self::Port(v) => v.target(),
}
}
fn for_each_port_and_annotations_helper<
F: for<'a> FnMut(
&'a ScalarizedModuleABIPort,
ScalarizedModuleABIAnnotations<'a>,
) -> ControlFlow<B>,
B,
>(
&self,
parent: Option<&ScalarizedModuleABIAnnotations<'_>>,
f: &mut F,
) -> ControlFlow<B> {
match self {
Self::Group(v) => v.for_each_port_and_annotations_helper(parent, f),
Self::Port(port) => f(
port,
ScalarizedModuleABIAnnotations::new(parent, port.annotations.iter()),
),
}
}
pub fn for_each_port_and_annotations<
F: for<'a> FnMut(
&'a ScalarizedModuleABIPort,
ScalarizedModuleABIAnnotations<'a>,
) -> ControlFlow<B>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
self.for_each_port_and_annotations_helper(None, &mut f)
}
}
impl fmt::Debug for ScalarizedModuleABIPortItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Group(v) => v.fmt(f),
Self::Port(v) => v.fmt(f),
}
}
}
#[derive(Debug, Clone)]
pub struct ScalarizedModuleABIAnnotations<'a> {
parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>,
parent_len: usize,
annotations: std::slice::Iter<'a, TargetedAnnotation>,
}
impl<'a> ScalarizedModuleABIAnnotations<'a> {
fn new(
parent: Option<&'a ScalarizedModuleABIAnnotations<'a>>,
annotations: std::slice::Iter<'a, TargetedAnnotation>,
) -> Self {
Self {
parent,
parent_len: parent.map_or(0, |parent| parent.len()),
annotations,
}
}
}
impl<'a> Default for ScalarizedModuleABIAnnotations<'a> {
fn default() -> Self {
Self {
parent: None,
parent_len: 0,
annotations: Default::default(),
}
}
}
impl<'a> Iterator for ScalarizedModuleABIAnnotations<'a> {
type Item = &'a TargetedAnnotation;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let retval @ Some(_) = self.annotations.next() {
break retval;
}
*self = self.parent?.clone();
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
fn fold<B, F>(mut self, mut init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
loop {
let Self {
parent,
parent_len: _,
annotations,
} = self;
init = annotations.fold(init, &mut f);
let Some(next) = parent else {
break;
};
self = next.clone();
}
init
}
}
impl std::iter::FusedIterator for ScalarizedModuleABIAnnotations<'_> {}
impl ExactSizeIterator for ScalarizedModuleABIAnnotations<'_> {
fn len(&self) -> usize {
self.parent_len + self.annotations.len()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScalarizedModuleABIPortGroup {
target: Interned<Target>,
common_annotations: Interned<[TargetedAnnotation]>,
children: Interned<[ScalarizedModuleABIPortItem]>,
}
impl ScalarizedModuleABIPortGroup {
pub fn module_io(self) -> ModuleIO<CanonicalType> {
*self
.target
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
self.target
}
pub fn common_annotations(self) -> Interned<[TargetedAnnotation]> {
self.common_annotations
}
pub fn children(self) -> Interned<[ScalarizedModuleABIPortItem]> {
self.children
}
fn for_each_port_and_annotations_helper<
F: for<'a> FnMut(
&'a ScalarizedModuleABIPort,
ScalarizedModuleABIAnnotations<'a>,
) -> ControlFlow<B>,
B,
>(
&self,
parent: Option<&ScalarizedModuleABIAnnotations<'_>>,
f: &mut F,
) -> ControlFlow<B> {
let parent = ScalarizedModuleABIAnnotations::new(parent, self.common_annotations.iter());
for item in &self.children {
item.for_each_port_and_annotations_helper(Some(&parent), f)?;
}
ControlFlow::Continue(())
}
pub fn for_each_port_and_annotations<
F: for<'a> FnMut(
&'a ScalarizedModuleABIPort,
ScalarizedModuleABIAnnotations<'a>,
) -> ControlFlow<B>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
self.for_each_port_and_annotations_helper(None, &mut f)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScalarizedModuleABIPort {
target: Interned<Target>,
annotations: Interned<[TargetedAnnotation]>,
scalarized_name: Interned<str>,
}
impl ScalarizedModuleABIPort {
pub fn module_io(self) -> ModuleIO<CanonicalType> {
*self
.target
.base()
.module_io()
.expect("known to be ModuleIO")
}
pub fn target(self) -> Interned<Target> {
self.target
}
pub fn annotations(self) -> Interned<[TargetedAnnotation]> {
self.annotations
}
pub fn scalarized_name(self) -> Interned<str> {
self.scalarized_name
}
}
enum ScalarizeTreeNodeBody {
Leaf {
scalarized_name: Interned<str>,
},
Bundle {
ty: Bundle,
fields: Vec<ScalarizeTreeNode>,
},
Array {
elements: Vec<ScalarizeTreeNode>,
},
}
struct ScalarizeTreeNode {
target: Interned<Target>,
annotations: Vec<TargetedAnnotation>,
body: ScalarizeTreeNodeBody,
}
impl ScalarizeTreeNode {
#[track_caller]
fn find_target(&mut self, annotation_target: Interned<Target>) -> &mut Self {
match *annotation_target {
Target::Base(_) => {
assert_eq!(
annotation_target, self.target,
"annotation not on correct ModuleIO",
);
self
}
Target::Child(target_child) => {
let parent = self.find_target(target_child.parent());
match *target_child.path_element() {
TargetPathElement::BundleField(TargetPathBundleField { name }) => {
match parent.body {
ScalarizeTreeNodeBody::Leaf { .. } => parent,
ScalarizeTreeNodeBody::Bundle { ty, ref mut fields } => {
&mut fields[ty.name_indexes()[&name]]
}
ScalarizeTreeNodeBody::Array { .. } => {
unreachable!("types are known to match")
}
}
}
TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => {
match parent.body {
ScalarizeTreeNodeBody::Leaf { .. } => parent,
ScalarizeTreeNodeBody::Bundle { .. } => {
unreachable!("types are known to match")
}
ScalarizeTreeNodeBody::Array { ref mut elements } => {
&mut elements[index]
}
}
}
TargetPathElement::DynArrayElement(_) => {
unreachable!("annotations are only on static targets");
}
}
}
}
}
fn into_scalarized_item(self) -> ScalarizedModuleABIPortItem {
let Self {
target,
annotations,
body,
} = self;
match body {
ScalarizeTreeNodeBody::Leaf { scalarized_name } => {
ScalarizedModuleABIPortItem::Port(ScalarizedModuleABIPort {
target,
annotations: Intern::intern_owned(annotations),
scalarized_name,
})
}
ScalarizeTreeNodeBody::Bundle { fields: items, .. }
| ScalarizeTreeNodeBody::Array { elements: items } => {
ScalarizedModuleABIPortItem::Group(ScalarizedModuleABIPortGroup {
target,
common_annotations: Intern::intern_owned(annotations),
children: Interned::from_iter(
items.into_iter().map(Self::into_scalarized_item),
),
})
}
}
}
}
#[derive(Default)]
struct ScalarizeTreeBuilder {
scalarized_ns: Namespace,
type_state: TypeState,
name: String,
}
impl ScalarizeTreeBuilder {
#[track_caller]
fn build_bundle(
&mut self,
target: Interned<Target>,
ty: Bundle,
) -> Result<ScalarizeTreeNode, ScalarizedModuleABIError> {
let mut fields = Vec::with_capacity(ty.fields().len());
let original_len = self.name.len();
for BundleField { name, .. } in ty.fields() {
let firrtl_name = self
.type_state
.get_bundle_field(ty, name)
.map_err(|e| match e {
FirrtlError::SimOnlyValuesAreNotPermitted => {
ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted
}
})?
.0;
write!(self.name, "_{firrtl_name}").expect("writing to String is infallible");
fields.push(
self.build(
target
.join(TargetPathElement::intern_sized(
TargetPathBundleField { name }.into(),
))
.intern_sized(),
)?,
);
self.name.truncate(original_len);
}
Ok(ScalarizeTreeNode {
target,
annotations: Vec::new(),
body: ScalarizeTreeNodeBody::Bundle { ty, fields },
})
}
#[track_caller]
fn build(
&mut self,
target: Interned<Target>,
) -> Result<ScalarizeTreeNode, ScalarizedModuleABIError> {
Ok(match target.canonical_ty() {
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::Enum(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_) => {
let scalarized_name = self.scalarized_ns.get(str::intern(&self.name)).0;
ScalarizeTreeNode {
target,
annotations: Vec::new(),
body: ScalarizeTreeNodeBody::Leaf { scalarized_name },
}
}
CanonicalType::Array(ty) => {
let mut elements = Vec::with_capacity(ty.len());
let original_len = self.name.len();
for index in 0..ty.len() {
write!(self.name, "_{index}").expect("writing to String is infallible");
elements.push(
self.build(
target
.join(TargetPathElement::intern_sized(
TargetPathArrayElement { index }.into(),
))
.intern_sized(),
)?,
);
self.name.truncate(original_len);
}
ScalarizeTreeNode {
target,
annotations: Vec::new(),
body: ScalarizeTreeNodeBody::Array { elements },
}
}
CanonicalType::Bundle(ty) => self.build_bundle(target, ty)?,
CanonicalType::PhantomConst(_) => {
self.build_bundle(target, Bundle::new(Interned::default()))?
}
CanonicalType::DynSimOnly(_) => {
return Err(ScalarizedModuleABIError::SimOnlyValuesAreNotPermitted);
}
})
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScalarizedModuleABI {
module_io: Interned<[AnnotatedModuleIO]>,
items: Interned<[ScalarizedModuleABIPortItem]>,
}
impl ScalarizedModuleABI {
#[track_caller]
fn from_io(module_io: Interned<[AnnotatedModuleIO]>) -> Result<Self, ScalarizedModuleABIError> {
let mut firrtl_ns = Namespace::default();
let mut tree_builder = ScalarizeTreeBuilder::default();
let mut items = Vec::new();
for module_io in module_io {
let firrtl_name = firrtl_ns.get(module_io.module_io.name_id());
tree_builder.name.clear();
tree_builder.name.push_str(&firrtl_name.0);
let mut tree = tree_builder.build(Target::from(module_io.module_io).intern_sized())?;
for annotation in module_io.annotations {
tree.find_target(annotation.target())
.annotations
.push(annotation);
}
items.push(tree.into_scalarized_item());
}
Ok(Self {
module_io,
items: Intern::intern_owned(items),
})
}
#[track_caller]
pub fn new<T: BundleType>(
module: impl AsRef<Module<T>>,
options: ExportOptions,
) -> Result<Self, ScalarizedModuleABIError> {
Self::from_io(options.prepare_top_module(module)?.module_io())
}
pub fn module_io(&self) -> Interned<[AnnotatedModuleIO]> {
self.module_io
}
pub fn items(&self) -> Interned<[ScalarizedModuleABIPortItem]> {
self.items
}
pub fn for_each_port_and_annotations<
F: for<'a> FnMut(
&'a ScalarizedModuleABIPort,
ScalarizedModuleABIAnnotations<'a>,
) -> ControlFlow<B>,
B,
>(
self,
mut f: F,
) -> ControlFlow<B> {
for item in &self.items {
item.for_each_port_and_annotations_helper(None, &mut f)?;
}
ControlFlow::Continue(())
}
}
#[doc(hidden)]
#[track_caller]
pub fn assert_export_firrtl_impl<T: BundleType>(top_module: &Module<T>, expected: TestBackend) {

View file

@ -4,16 +4,19 @@
use crate::{
array::ArrayType,
expr::{
Expr, NotALiteralExpr, ToExpr, ToLiteralBits,
Expr, NotALiteralExpr, ToExpr, ToLiteralBits, ToSimValueInner, ToValueless, ValueType,
Valueless,
target::{GetTarget, Target},
value_category::ValueCategoryValue,
},
hdl,
intern::{Intern, Interned, Memoize},
sim::value::{SimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self,
CanonicalType, FillInDefaultedGenerics, OpaqueSimValueSize, OpaqueSimValueSlice,
OpaqueSimValueWriter, OpaqueSimValueWritten, StaticType, Type, TypeProperties,
impl_match_variant_as_self,
},
util::{ConstBool, ConstUsize, GenericConstBool, GenericConstUsize, interned_bit, slice_range},
};
@ -31,7 +34,7 @@ use std::{
num::NonZero,
ops::{Index, Not, Range, RangeBounds, RangeInclusive},
str::FromStr,
sync::Arc,
sync::{Arc, OnceLock},
};
mod uint_in_range;
@ -60,8 +63,62 @@ mod sealed {
pub const DYN_SIZE: usize = !0;
pub type DynSize = ConstUsize<DYN_SIZE>;
trait KnownSizeBaseSealed {}
impl<const N: usize> KnownSizeBaseSealed for [(); N] {}
#[expect(private_bounds)]
pub trait KnownSizeBase: KnownSizeBaseSealed {}
macro_rules! impl_known_size_base {
($($size:literal),* $(,)?) => {
$(impl KnownSizeBase for [(); $size] {})*
};
}
#[rustfmt::skip]
impl_known_size_base! {
0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, 0x009, 0x00A, 0x00B, 0x00C, 0x00D, 0x00E, 0x00F,
0x010, 0x011, 0x012, 0x013, 0x014, 0x015, 0x016, 0x017, 0x018, 0x019, 0x01A, 0x01B, 0x01C, 0x01D, 0x01E, 0x01F,
0x020, 0x021, 0x022, 0x023, 0x024, 0x025, 0x026, 0x027, 0x028, 0x029, 0x02A, 0x02B, 0x02C, 0x02D, 0x02E, 0x02F,
0x030, 0x031, 0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x039, 0x03A, 0x03B, 0x03C, 0x03D, 0x03E, 0x03F,
0x040, 0x041, 0x042, 0x043, 0x044, 0x045, 0x046, 0x047, 0x048, 0x049, 0x04A, 0x04B, 0x04C, 0x04D, 0x04E, 0x04F,
0x050, 0x051, 0x052, 0x053, 0x054, 0x055, 0x056, 0x057, 0x058, 0x059, 0x05A, 0x05B, 0x05C, 0x05D, 0x05E, 0x05F,
0x060, 0x061, 0x062, 0x063, 0x064, 0x065, 0x066, 0x067, 0x068, 0x069, 0x06A, 0x06B, 0x06C, 0x06D, 0x06E, 0x06F,
0x070, 0x071, 0x072, 0x073, 0x074, 0x075, 0x076, 0x077, 0x078, 0x079, 0x07A, 0x07B, 0x07C, 0x07D, 0x07E, 0x07F,
0x080, 0x081, 0x082, 0x083, 0x084, 0x085, 0x086, 0x087, 0x088, 0x089, 0x08A, 0x08B, 0x08C, 0x08D, 0x08E, 0x08F,
0x090, 0x091, 0x092, 0x093, 0x094, 0x095, 0x096, 0x097, 0x098, 0x099, 0x09A, 0x09B, 0x09C, 0x09D, 0x09E, 0x09F,
0x0A0, 0x0A1, 0x0A2, 0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0AA, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x0AF,
0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6, 0x0B7, 0x0B8, 0x0B9, 0x0BA, 0x0BB, 0x0BC, 0x0BD, 0x0BE, 0x0BF,
0x0C0, 0x0C1, 0x0C2, 0x0C3, 0x0C4, 0x0C5, 0x0C6, 0x0C7, 0x0C8, 0x0C9, 0x0CA, 0x0CB, 0x0CC, 0x0CD, 0x0CE, 0x0CF,
0x0D0, 0x0D1, 0x0D2, 0x0D3, 0x0D4, 0x0D5, 0x0D6, 0x0D7, 0x0D8, 0x0D9, 0x0DA, 0x0DB, 0x0DC, 0x0DD, 0x0DE, 0x0DF,
0x0E0, 0x0E1, 0x0E2, 0x0E3, 0x0E4, 0x0E5, 0x0E6, 0x0E7, 0x0E8, 0x0E9, 0x0EA, 0x0EB, 0x0EC, 0x0ED, 0x0EE, 0x0EF,
0x0F0, 0x0F1, 0x0F2, 0x0F3, 0x0F4, 0x0F5, 0x0F6, 0x0F7, 0x0F8, 0x0F9, 0x0FA, 0x0FB, 0x0FC, 0x0FD, 0x0FE, 0x0FF,
0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, 0x109, 0x10A, 0x10B, 0x10C, 0x10D, 0x10E, 0x10F,
0x110, 0x111, 0x112, 0x113, 0x114, 0x115, 0x116, 0x117, 0x118, 0x119, 0x11A, 0x11B, 0x11C, 0x11D, 0x11E, 0x11F,
0x120, 0x121, 0x122, 0x123, 0x124, 0x125, 0x126, 0x127, 0x128, 0x129, 0x12A, 0x12B, 0x12C, 0x12D, 0x12E, 0x12F,
0x130, 0x131, 0x132, 0x133, 0x134, 0x135, 0x136, 0x137, 0x138, 0x139, 0x13A, 0x13B, 0x13C, 0x13D, 0x13E, 0x13F,
0x140, 0x141, 0x142, 0x143, 0x144, 0x145, 0x146, 0x147, 0x148, 0x149, 0x14A, 0x14B, 0x14C, 0x14D, 0x14E, 0x14F,
0x150, 0x151, 0x152, 0x153, 0x154, 0x155, 0x156, 0x157, 0x158, 0x159, 0x15A, 0x15B, 0x15C, 0x15D, 0x15E, 0x15F,
0x160, 0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167, 0x168, 0x169, 0x16A, 0x16B, 0x16C, 0x16D, 0x16E, 0x16F,
0x170, 0x171, 0x172, 0x173, 0x174, 0x175, 0x176, 0x177, 0x178, 0x179, 0x17A, 0x17B, 0x17C, 0x17D, 0x17E, 0x17F,
0x180, 0x181, 0x182, 0x183, 0x184, 0x185, 0x186, 0x187, 0x188, 0x189, 0x18A, 0x18B, 0x18C, 0x18D, 0x18E, 0x18F,
0x190, 0x191, 0x192, 0x193, 0x194, 0x195, 0x196, 0x197, 0x198, 0x199, 0x19A, 0x19B, 0x19C, 0x19D, 0x19E, 0x19F,
0x1A0, 0x1A1, 0x1A2, 0x1A3, 0x1A4, 0x1A5, 0x1A6, 0x1A7, 0x1A8, 0x1A9, 0x1AA, 0x1AB, 0x1AC, 0x1AD, 0x1AE, 0x1AF,
0x1B0, 0x1B1, 0x1B2, 0x1B3, 0x1B4, 0x1B5, 0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x1BF,
0x1C0, 0x1C1, 0x1C2, 0x1C3, 0x1C4, 0x1C5, 0x1C6, 0x1C7, 0x1C8, 0x1C9, 0x1CA, 0x1CB, 0x1CC, 0x1CD, 0x1CE, 0x1CF,
0x1D0, 0x1D1, 0x1D2, 0x1D3, 0x1D4, 0x1D5, 0x1D6, 0x1D7, 0x1D8, 0x1D9, 0x1DA, 0x1DB, 0x1DC, 0x1DD, 0x1DE, 0x1DF,
0x1E0, 0x1E1, 0x1E2, 0x1E3, 0x1E4, 0x1E5, 0x1E6, 0x1E7, 0x1E8, 0x1E9, 0x1EA, 0x1EB, 0x1EC, 0x1ED, 0x1EE, 0x1EF,
0x1F0, 0x1F1, 0x1F2, 0x1F3, 0x1F4, 0x1F5, 0x1F6, 0x1F7, 0x1F8, 0x1F9, 0x1FA, 0x1FB, 0x1FC, 0x1FD, 0x1FE, 0x1FF,
0x200,
}
pub trait KnownSize:
GenericConstUsize + sealed::SizeTypeSealed + sealed::SizeSealed + Default
GenericConstUsize
+ sealed::SizeTypeSealed
+ sealed::SizeSealed
+ Default
+ FillInDefaultedGenerics<Type = Self>
{
const SIZE: Self;
type ArrayMatch<Element: Type>: AsRef<[Expr<Element>]>
@ -89,35 +146,15 @@ pub trait KnownSize:
+ ToSimValueWithType<ArrayType<Element, Self>>;
}
macro_rules! known_widths {
([] $($bits:literal)+) => {
impl KnownSize for ConstUsize<{
let v = 0;
$(let v = v * 2 + $bits;)*
v
}> {
const SIZE: Self = Self;
type ArrayMatch<Element: Type> = [Expr<Element>; Self::VALUE];
type ArraySimValue<Element: Type> = [SimValue<Element>; Self::VALUE];
}
};
([2 $($rest:tt)*] $($bits:literal)+) => {
known_widths!([$($rest)*] $($bits)* 0);
known_widths!([$($rest)*] $($bits)* 1);
};
([2 $($rest:tt)*]) => {
known_widths!([$($rest)*] 0);
known_widths!([$($rest)*] 1);
impl KnownSize for ConstUsize<{2 $(* $rest)*}> {
const SIZE: Self = Self;
type ArrayMatch<Element: Type> = [Expr<Element>; Self::VALUE];
type ArraySimValue<Element: Type> = [SimValue<Element>; Self::VALUE];
}
};
impl<const N: usize> KnownSize for ConstUsize<N>
where
[(); N]: KnownSizeBase,
{
const SIZE: Self = Self;
type ArrayMatch<Element: Type> = [Expr<Element>; N];
type ArraySimValue<Element: Type> = [SimValue<Element>; N];
}
known_widths!([2 2 2 2 2 2 2 2 2]);
pub trait SizeType:
sealed::SizeTypeSealed
+ Copy
@ -129,6 +166,7 @@ pub trait SizeType:
+ 'static
+ Serialize
+ DeserializeOwned
+ FillInDefaultedGenerics<Type = Self>
{
type Size: Size<SizeType = Self>;
}
@ -530,6 +568,23 @@ fn deserialize_int_value<'de, D: Deserializer<'de>>(
})
}
macro_rules! impl_valueless_op_forward {
(
#[forward_to = $ForwardTrait:path]
impl<$($G:ident: $Bound:path),*> $TraitPath:ident$(::$TraitPathRest:ident)+<$($Rhs:ty)?> for $SelfTy:ty {
$($(type $Output:ident;)?
use $forward_fn:path as $fn:ident($self:ident $(, $rhs:ident)?);)?
}
) => {
impl<$($G: $Bound),*> $TraitPath$(::$TraitPathRest)+<$($Rhs)?> for $SelfTy {
$($(type $Output = <$SelfTy as $ForwardTrait>::$Output;)?
fn $fn($self $(, $rhs: $Rhs)?) $(-> Self::$Output)? {
$forward_fn($self $(, $rhs)?)
})?
}
};
}
macro_rules! impl_int {
($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
@ -582,6 +637,176 @@ macro_rules! impl_int {
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Add<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<$name>;
fn add(self, rhs: Valueless<$name<RhsWidth>>) -> Self::Output {
Valueless::new($name::new_dyn(
self.ty()
.width()
.max(rhs.ty().width())
.checked_add(1)
.expect("width too big"),
))
}
}
impl_valueless_op_forward! {
#[forward_to = std::ops::Add<Valueless<$name<RhsWidth>>>]
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Sub<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output;
use std::ops::Add::<Valueless<$name<RhsWidth>>>::add as sub(self, rhs);
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::BitOr<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<UInt>;
fn bitor(self, rhs: Valueless<$name<RhsWidth>>) -> Self::Output {
Valueless::new(UInt::new_dyn(self.ty().width().max(rhs.ty().width())))
}
}
impl_valueless_op_forward! {
#[forward_to = std::ops::BitOr<Valueless<$name<RhsWidth>>>]
impl<LhsWidth: Size, RhsWidth: Size> std::ops::BitAnd<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output;
use std::ops::BitOr::<Valueless<$name<RhsWidth>>>::bitor as bitand(self, rhs);
}
}
impl_valueless_op_forward! {
#[forward_to = std::ops::BitOr<Valueless<$name<RhsWidth>>>]
impl<LhsWidth: Size, RhsWidth: Size> std::ops::BitXor<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output;
use std::ops::BitOr::<Valueless<$name<RhsWidth>>>::bitor as bitxor(self, rhs);
}
}
impl<Width: Size> std::ops::Not for Valueless<$name<Width>> {
type Output = Valueless<UIntType<Width>>;
fn not(self) -> Self::Output {
Valueless::new(self.ty().as_same_width_uint())
}
}
impl<Width: Size> std::ops::Not for $value<Width> {
type Output = UIntValue<Width>;
fn not(mut self) -> Self::Output {
// modifies in-place despite using `Not::not`
let _ = Not::not(self.bits_mut());
UIntValue::new(self.into_bits())
}
}
impl<Width: Size> std::ops::Not for &'_ $value<Width> {
type Output = UIntValue<Width>;
fn not(self) -> Self::Output {
$value::not(self.clone())
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Mul<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<$name>;
fn mul(self, rhs: Valueless<$name<RhsWidth>>) -> Self::Output {
Valueless::new($name::new_dyn(
self.ty()
.width()
.checked_add(rhs.ty().width())
.expect("width too big"),
))
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Rem<Valueless<$name<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<$name>;
fn rem(self, rhs: Valueless<$name<RhsWidth>>) -> Self::Output {
Valueless::new($name::new_dyn(self.ty().width().min(rhs.ty().width())))
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Shl<Valueless<UIntType<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<$name>;
#[track_caller]
fn shl(self, rhs: Valueless<UIntType<RhsWidth>>) -> Self::Output {
let Some(pow2_rhs_width) = rhs
.ty()
.width()
.try_into()
.ok()
.and_then(|v| 2usize.checked_pow(v))
else {
panic!(
"dynamic left-shift amount's bit-width is too big, try casting the shift \
amount to a smaller bit-width before shifting"
);
};
Valueless::new($name::new_dyn(
(pow2_rhs_width - 1)
.checked_add(self.ty().width())
.expect("width too big"),
))
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Shr<Valueless<UIntType<RhsWidth>>>
for Valueless<$name<LhsWidth>>
{
type Output = Valueless<$name<LhsWidth>>;
fn shr(self, _rhs: Valueless<UIntType<RhsWidth>>) -> Self::Output {
self
}
}
impl<LhsWidth: Size> std::ops::Shl<usize> for Valueless<$name<LhsWidth>> {
type Output = Valueless<$name>;
fn shl(self, rhs: usize) -> Self::Output {
Valueless::new($name::new_dyn(
self.ty().width().checked_add(rhs).expect("width too big"),
))
}
}
impl<LhsWidth: Size> std::ops::Shr<usize> for Valueless<$name<LhsWidth>> {
type Output = Valueless<$name>;
fn shr(self, rhs: usize) -> Self::Output {
let width = self.ty().width().saturating_sub(rhs);
Valueless::new($name::new_dyn(if $SIGNED && width == 0 {
1
} else {
width
}))
}
}
impl<Width: Size> sealed::BoolOrIntTypeSealed for $name<Width> {}
impl<Width: Size> BoolOrIntType for $name<Width> {
@ -843,6 +1068,12 @@ macro_rules! impl_int {
}
}
impl<'a, Width: Size> From<&'a $value<Width>> for BigInt {
fn from(v: &'a $value<Width>) -> BigInt {
v.to_bigint()
}
}
impl<Width: Size> $value<Width> {
pub fn width(&self) -> usize {
if let Some(retval) = Width::KNOWN_VALUE {
@ -861,11 +1092,6 @@ macro_rules! impl_int {
pub fn into_bits(self) -> Arc<BitVec> {
self.bits
}
pub fn ty(&self) -> $name<Width> {
$name {
width: Width::from_usize(self.width()),
}
}
pub fn as_dyn_int(self) -> $value {
$value {
bits: self.bits,
@ -878,6 +1104,38 @@ macro_rules! impl_int {
pub fn bits_mut(&mut self) -> &mut BitSlice {
Arc::<BitVec>::make_mut(&mut self.bits)
}
pub fn hdl_cmp<R: Size>(&self, other: &$value<R>) -> std::cmp::Ordering {
self.to_bigint().cmp(&other.to_bigint())
}
}
impl<Width: Size> ValueType for $value<Width> {
type Type = $name<Width>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
$name {
width: Width::from_usize(self.width()),
}
}
}
impl<'a, Width: Size> ToSimValueInner<'a> for $value<Width> {
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
Cow::Borrowed(this)
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
Cow::Owned(this)
}
}
impl<'a, Width: Size> ToSimValueInner<'a> for &'a $value<Width> {
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
Cow::Borrowed(&**this)
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
Cow::Borrowed(this)
}
}
impl<Width: Size> ToLiteralBits for $value<Width> {
@ -1015,11 +1273,58 @@ impl SInt {
}
}
macro_rules! impl_prim_int {
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Div<Valueless<UIntType<RhsWidth>>>
for Valueless<UIntType<LhsWidth>>
{
type Output = Self;
fn div(self, _rhs: Valueless<UIntType<RhsWidth>>) -> Self::Output {
self
}
}
impl<LhsWidth: Size, RhsWidth: Size> std::ops::Div<Valueless<SIntType<RhsWidth>>>
for Valueless<SIntType<LhsWidth>>
{
type Output = Valueless<SInt>;
fn div(self, _rhs: Valueless<SIntType<RhsWidth>>) -> Self::Output {
Valueless::new(SInt::new_dyn(
self.ty().width().checked_add(1).expect("width too big"),
))
}
}
impl<Width: Size> std::ops::Neg for Valueless<SIntType<Width>> {
type Output = Valueless<SInt>;
fn neg(self) -> Self::Output {
Valueless::new(SInt::new_dyn(
self.ty().width().checked_add(1).expect("width too big"),
))
}
}
impl<Width: Size> std::ops::Neg for SIntValue<Width> {
type Output = SIntValue;
fn neg(self) -> Self::Output {
-&self
}
}
impl<Width: Size> std::ops::Neg for &'_ SIntValue<Width> {
type Output = SIntValue;
fn neg(self) -> Self::Output {
let ty = self.to_valueless().neg().ty();
ty.from_bigint_wrapping(&-self.to_bigint())
}
}
macro_rules! impl_prim_int_to_expr {
(
$(#[$meta:meta])*
$prim_int:ident, $ty:ty
) => {
$(#[$meta])*
impl From<$prim_int> for <$ty as BoolOrIntType>::Value {
fn from(v: $prim_int) -> Self {
<$ty>::le_bytes_to_value_wrapping(
@ -1028,15 +1333,32 @@ macro_rules! impl_prim_int {
)
}
}
$(#[$meta])*
impl From<NonZero<$prim_int>> for <$ty as BoolOrIntType>::Value {
fn from(v: NonZero<$prim_int>) -> Self {
v.get().into()
}
}
$(#[$meta])*
impl ToExpr for $prim_int {
impl<'a> ToSimValueInner<'a> for $prim_int {
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
Self::into_sim_value_inner(*this)
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
Cow::Owned(this.into())
}
}
$(#[$meta])*
impl ValueType for $prim_int {
type Type = $ty;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
StaticType::TYPE
}
}
$(#[$meta])*
impl ToExpr for $prim_int {
fn to_expr(&self) -> Expr<Self::Type> {
<$ty>::le_bytes_to_expr_wrapping(
&self.to_le_bytes(),
@ -1045,9 +1367,25 @@ macro_rules! impl_prim_int {
}
}
$(#[$meta])*
impl ToExpr for NonZero<$prim_int> {
impl<'a> ToSimValueInner<'a> for NonZero<$prim_int> {
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
Self::into_sim_value_inner(*this)
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
Cow::Owned(this.into())
}
}
$(#[$meta])*
impl ValueType for NonZero<$prim_int> {
type Type = $ty;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
StaticType::TYPE
}
}
$(#[$meta])*
impl ToExpr for NonZero<$prim_int> {
fn to_expr(&self) -> Expr<Self::Type> {
self.get().to_expr()
}
@ -1055,28 +1393,208 @@ macro_rules! impl_prim_int {
};
}
impl_prim_int!(u8, UInt<8>);
impl_prim_int!(u16, UInt<16>);
impl_prim_int!(u32, UInt<32>);
impl_prim_int!(u64, UInt<64>);
impl_prim_int!(u128, UInt<128>);
impl_prim_int!(i8, SInt<8>);
impl_prim_int!(i16, SInt<16>);
impl_prim_int!(i32, SInt<32>);
impl_prim_int!(i64, SInt<64>);
impl_prim_int!(i128, SInt<128>);
macro_rules! impl_for_all_prim_uints {
(
#[size =, $(rest:tt)*]
$m:ident!()
) => {
impl_for_all_prim_uints!(
#[usize =, $($rest)*]
$m!()
);
};
(
#[size = size, $(rest:tt)*]
$m:ident!()
) => {
impl_for_all_prim_uints!(
#[usize = usize, $($rest)*]
$m!()
);
};
(
#[usize = $($usize:ident)?, NonZero = $NonZero:ident]
$m:ident!()
) => {
impl_for_all_prim_uints!(
#[usize = $($usize)?, NonZero = $NonZero, NonZero<usize> = $((NonZero<$usize>))?]
$m!()
);
};
(
#[usize = $($usize:ident)?, NonZero =]
$m:ident!()
) => {
impl_for_all_prim_uints!(
#[usize = $($usize)?, NonZero =, NonZero<usize> =]
$m!()
);
};
(
#[usize = $($usize:ident)?, NonZero = $($NonZero:ident)?, NonZero<usize> = $(($($NonZeroUsize:tt)*))?]
$m:ident!()
) => {
$m!(u8, UInt<8>);
$m!(u16, UInt<16>);
$m!(u32, UInt<32>);
$m!(u64, UInt<64>);
$m!(u128, UInt<128>);
$($m!($NonZero<u8>, UInt<8>);)?
$($m!($NonZero<u16>, UInt<16>);)?
$($m!($NonZero<u32>, UInt<32>);)?
$($m!($NonZero<u64>, UInt<64>);)?
$($m!($NonZero<u128>, UInt<128>);)?
$($m!(
/// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt]
$usize, UInt<64>
);)?
$($m!(
/// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt]
$($NonZeroUsize)*, UInt<64>
);)?
};
}
impl_prim_int!(
/// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt]
usize, UInt<64>
macro_rules! impl_for_all_prim_sints {
(
#[size =, $(rest:tt)*]
$m:ident!()
) => {
impl_for_all_prim_sints!(
#[isize =, $($rest)*]
$m!()
);
};
(
#[size = size, $(rest:tt)*]
$m:ident!()
) => {
impl_for_all_prim_sints!(
#[isize = isize, $($rest)*]
$m!()
);
};
(
#[isize = $($isize:ident)?, NonZero = $NonZero:ident]
$m:ident!()
) => {
impl_for_all_prim_sints!(
#[isize = $($isize)?, NonZero = $NonZero, NonZero<isize> = $((NonZero<$isize>))?]
$m!()
);
};
(
#[isize = $($isize:ident)?, NonZero =]
$m:ident!()
) => {
impl_for_all_prim_sints!(
#[isize = $($isize)?, NonZero =, NonZero<isize> =]
$m!()
);
};
(
#[isize = $($isize:ident)?, NonZero = $($NonZero:ident)?, NonZero<isize> = $(($($NonZeroIsize:tt)*))?]
$m:ident!()
) => {
$m!(i8, SInt<8>);
$m!(i16, SInt<16>);
$m!(i32, SInt<32>);
$m!(i64, SInt<64>);
$m!(i128, SInt<128>);
$($m!($NonZero<i8>, SInt<8>);)?
$($m!($NonZero<i16>, SInt<16>);)?
$($m!($NonZero<i32>, SInt<32>);)?
$($m!($NonZero<i64>, SInt<64>);)?
$($m!($NonZero<i128>, SInt<128>);)?
$($m!(
/// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt]
$isize, SInt<64>
);)?
$($m!(
/// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt]
$($NonZeroIsize)*, SInt<64>
);)?
};
}
impl_for_all_prim_uints!(
#[usize = usize, NonZero =]
impl_prim_int_to_expr!()
);
impl_prim_int!(
/// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt]
isize, SInt<64>
impl_for_all_prim_sints!(
#[isize = isize, NonZero =]
impl_prim_int_to_expr!()
);
pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
macro_rules! impl_prim_int_from_value {
($prim_int:ident, UInt<$width:literal>) => {
impl_prim_int_from_value!($prim_int, UInt<$width>, UIntValue<ConstUsize<$width>>);
};
($prim_int:ident, SInt<$width:literal>) => {
impl_prim_int_from_value!($prim_int, SInt<$width>, SIntValue<ConstUsize<$width>>);
};
($prim_int:ident, $ty:ty, $value:ty) => {
impl From<$value> for $prim_int {
fn from(value: $value) -> Self {
value.as_int()
}
}
impl<'a> From<&'a mut $value> for $prim_int {
fn from(value: &'a mut $value) -> Self {
value.as_int()
}
}
impl From<SimValue<$ty>> for $prim_int {
fn from(value: SimValue<$ty>) -> Self {
value.as_int()
}
}
impl<'a> From<&'a mut SimValue<$ty>> for $prim_int {
fn from(value: &'a mut SimValue<$ty>) -> Self {
value.as_int()
}
}
impl<'a> From<&'a SimValue<$ty>> for $prim_int {
fn from(value: &'a SimValue<$ty>) -> Self {
value.as_int()
}
}
impl<'a> From<&'a $value> for $prim_int {
fn from(value: &'a $value) -> Self {
value.as_int()
}
}
impl $value {
pub fn as_int(&self) -> $prim_int {
let mut le_bytes = (0 as $prim_int).to_le_bytes();
let bitslice = BitSlice::<u8, Lsb0>::from_slice_mut(&mut le_bytes);
bitslice.clone_from_bitslice(self.bits());
$prim_int::from_le_bytes(le_bytes)
}
}
};
}
impl_for_all_prim_uints!(
#[usize =, NonZero =]
impl_prim_int_from_value!()
);
impl_for_all_prim_sints!(
#[isize =, NonZero =]
impl_prim_int_from_value!()
);
pub trait BoolOrIntType:
Type<SimValue = <Self as BoolOrIntType>::Value> + sealed::BoolOrIntTypeSealed
{
type Width: Size;
type Signed: GenericConstBool;
type Value: Clone
@ -1301,12 +1819,80 @@ impl StaticType for Bool {
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
impl From<bool> for UIntValue<ConstUsize<1>> {
fn from(value: bool) -> Self {
static VALUES: OnceLock<[UIntValue<ConstUsize<1>>; 2]> = OnceLock::new();
let values = VALUES
.get_or_init(|| std::array::from_fn(|i| UInt::<1>::new_static().from_int_wrapping(i)));
values[value as usize].clone()
}
}
impl From<bool> for SIntValue<ConstUsize<1>> {
fn from(value: bool) -> Self {
SIntValue::new(UIntValue::<ConstUsize<1>>::from(value).into_bits())
}
}
impl From<bool> for UIntValue {
fn from(value: bool) -> Self {
UIntValue::new(UIntValue::<ConstUsize<1>>::from(value).into_bits())
}
}
impl From<bool> for SIntValue {
fn from(value: bool) -> Self {
SIntValue::new(UIntValue::<ConstUsize<1>>::from(value).into_bits())
}
}
impl ToLiteralBits for bool {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Ok(interned_bit(*self))
}
}
impl<'a> ToSimValueInner<'a> for bool {
fn to_sim_value_inner(this: &Self) -> Cow<'_, <Self::Type as Type>::SimValue> {
Cow::Owned(*this)
}
fn into_sim_value_inner(this: Self) -> Cow<'a, <Self::Type as Type>::SimValue> {
Cow::Owned(this)
}
}
impl std::ops::Not for Valueless<Bool> {
type Output = Valueless<Bool>;
fn not(self) -> Self::Output {
self
}
}
impl std::ops::BitAnd for Valueless<Bool> {
type Output = Valueless<Bool>;
fn bitand(self, _rhs: Valueless<Bool>) -> Self::Output {
self
}
}
impl std::ops::BitOr for Valueless<Bool> {
type Output = Valueless<Bool>;
fn bitor(self, _rhs: Valueless<Bool>) -> Self::Output {
self
}
}
impl std::ops::BitXor for Valueless<Bool> {
type Output = Valueless<Bool>;
fn bitxor(self, _rhs: Valueless<Bool>) -> Self::Output {
self
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -4,13 +4,13 @@
use crate::{
bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder},
expr::{
CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd,
ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd},
CastBitsTo, CastTo, CastToBits, CastToImpl, Expr, HdlPartialEq, HdlPartialEqImpl,
HdlPartialOrd, HdlPartialOrdImpl,
},
int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType},
intern::{Intern, Interned},
int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType, UIntValue},
intern::{Intern, InternSlice, Interned},
phantom_const::PhantomConst,
sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType},
sim::value::{SimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
@ -22,7 +22,7 @@ use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{Error, Visitor, value::UsizeDeserializer},
};
use std::{fmt, marker::PhantomData, ops::Index};
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Index};
const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"];
@ -96,7 +96,6 @@ impl Type for UIntInRangeMaskType {
impl BundleType for UIntInRangeMaskType {
type Builder = NoBuilder;
type FilledBuilder = Expr<UIntInRangeMaskType>;
fn fields(&self) -> Interned<[BundleField]> {
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
@ -112,8 +111,8 @@ impl BundleType for UIntInRangeMaskType {
flipped: false,
ty: range.canonical(),
},
][..]
.intern()
]
.intern_slice()
}
}
@ -136,30 +135,57 @@ impl ToSimValueWithType<UIntInRangeMaskType> for bool {
}
}
impl ExprCastTo<Bool> for UIntInRangeMaskType {
fn cast_to(src: Expr<Self>, to_type: Bool) -> Expr<Bool> {
src.cast_to_bits().cast_to(to_type)
impl CastToImpl<Bool> for UIntInRangeMaskType {
type ValueOutput = bool;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
_to_type: Bool,
) -> Self::ValueOutput {
*value
}
fn cast_expr_to(value: Expr<Self>, to_type: Bool) -> Expr<Bool> {
value.cast_to_bits().cast_to(to_type)
}
}
impl ExprCastTo<UIntInRangeMaskType> for Bool {
fn cast_to(src: Expr<Self>, to_type: UIntInRangeMaskType) -> Expr<UIntInRangeMaskType> {
src.cast_to_static::<UInt<1>>().cast_bits_to(to_type)
impl CastToImpl<UIntInRangeMaskType> for Bool {
type ValueOutput = SimValue<UIntInRangeMaskType>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: UIntInRangeMaskType,
) -> Self::ValueOutput {
SimValue::from_value(to_type, *value)
}
fn cast_expr_to(value: Expr<Self>, to_type: UIntInRangeMaskType) -> Expr<UIntInRangeMaskType> {
value.cast_to_static::<UInt<1>>().cast_bits_to(to_type)
}
}
impl ExprPartialEq<Self> for UIntInRangeMaskType {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
impl HdlPartialEqImpl<Self> for UIntInRangeMaskType {
#[track_caller]
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: Self,
rhs_value: Cow<'_, Self::SimValue>,
) -> bool {
*lhs_value == *rhs_value
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
}
}
impl SimValuePartialEq<Self> for UIntInRangeMaskType {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Self>) -> bool {
**this == **other
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
}
}
@ -300,9 +326,16 @@ macro_rules! define_uint_in_range_type {
}
}
pub fn new(start: Start::SizeType, end: End::SizeType) -> Self {
Self::from_phantom_const_range(PhantomConst::new(
$SerdeRange { start, end }.intern_sized(),
))
Self::from_phantom_const_range(PhantomConst::new_sized($SerdeRange { start, end }))
}
pub fn bit_width(self) -> usize {
self.value.width()
}
pub fn start(self) -> Start::SizeType {
self.range.get().start
}
pub fn end(self) -> End::SizeType {
self.range.get().end
}
}
@ -393,7 +426,6 @@ macro_rules! define_uint_in_range_type {
impl<Start: Size, End: Size> BundleType for $UIntInRangeType<Start, End> {
type Builder = NoBuilder;
type FilledBuilder = Expr<Self>;
fn fields(&self) -> Interned<[BundleField]> {
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
@ -409,8 +441,8 @@ macro_rules! define_uint_in_range_type {
flipped: false,
ty: range.canonical(),
},
][..]
.intern()
]
.intern_slice()
}
}
@ -477,32 +509,64 @@ macro_rules! define_uint_in_range_type {
}
}
impl<Start: Size, End: Size> ExprCastTo<UInt> for $UIntInRangeType<Start, End> {
fn cast_to(src: Expr<Self>, to_type: UInt) -> Expr<UInt> {
src.cast_to_bits().cast_to(to_type)
impl<Start: Size, End: Size, Width: Size> CastToImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
type ValueOutput = UIntValue<Width>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: UIntType<Width>,
) -> Self::ValueOutput {
value.cast_to(to_type)
}
fn cast_expr_to(value: Expr<Self>, to_type: UIntType<Width>) -> Expr<UIntType<Width>> {
value.cast_to_bits().cast_to(to_type)
}
}
impl<Start: Size, End: Size> ExprCastTo<$UIntInRangeType<Start, End>> for UInt {
fn cast_to(
src: Expr<Self>,
impl<Start: Size, End: Size, Width: Size> CastToImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
type ValueOutput = SimValue<$UIntInRangeType<Start, End>>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: $UIntInRangeType<Start, End>,
) -> Self::ValueOutput {
value.cast_to(to_type.value).cast_bits_to(to_type)
}
fn cast_expr_to(
value: Expr<Self>,
to_type: $UIntInRangeType<Start, End>,
) -> Expr<$UIntInRangeType<Start, End>> {
src.cast_bits_to(to_type)
value.cast_to(to_type.value).cast_bits_to(to_type)
}
}
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
ExprPartialEq<$UIntInRangeType<RhsStart, RhsEnd>>
HdlPartialEqImpl<$UIntInRangeType<RhsStart, RhsEnd>>
for $UIntInRangeType<LhsStart, LhsEnd>
{
fn cmp_eq(
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
*lhs_value == *rhs_value
}
fn cmp_expr_eq(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
fn cmp_ne(
fn cmp_expr_ne(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
@ -511,28 +575,60 @@ macro_rules! define_uint_in_range_type {
}
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
ExprPartialOrd<$UIntInRangeType<RhsStart, RhsEnd>>
HdlPartialOrdImpl<$UIntInRangeType<RhsStart, RhsEnd>>
for $UIntInRangeType<LhsStart, LhsEnd>
{
fn cmp_lt(
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::lt(&*lhs_value, &*rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::le(&*lhs_value, &*rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::gt(&*lhs_value, &*rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::ge(&*lhs_value, &*rhs_value)
}
fn cmp_expr_lt(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits())
}
fn cmp_le(
fn cmp_expr_le(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_le(rhs.cast_to_bits())
}
fn cmp_gt(
fn cmp_expr_gt(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits())
}
fn cmp_ge(
fn cmp_expr_ge(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
@ -540,70 +636,138 @@ macro_rules! define_uint_in_range_type {
}
}
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
SimValuePartialEq<$UIntInRangeType<RhsStart, RhsEnd>>
for $UIntInRangeType<LhsStart, LhsEnd>
{
fn sim_value_eq(
this: &SimValue<Self>,
other: &SimValue<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> bool {
**this == **other
}
}
impl<Start: Size, End: Size, Width: Size> ExprPartialEq<UIntType<Width>>
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialEq::cmp_eq(&*lhs_value, &*rhs_value)
}
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs)
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs)
}
}
impl<Start: Size, End: Size, Width: Size> ExprPartialEq<$UIntInRangeType<Start, End>>
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialEq::cmp_eq(&*lhs_value, *rhs_value)
}
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_eq(rhs.cast_to_bits())
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_ne(rhs.cast_to_bits())
}
}
impl<Start: Size, End: Size, Width: Size> ExprPartialOrd<UIntType<Width>>
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
fn cmp_lt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_lt(&*lhs_value, &*rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_le(&*lhs_value, &*rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_gt(&*lhs_value, &*rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_ge(&*lhs_value, &*rhs_value)
}
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_lt(rhs)
}
fn cmp_le(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_le(rhs)
}
fn cmp_gt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_gt(rhs)
}
fn cmp_ge(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ge(rhs)
}
}
impl<Start: Size, End: Size, Width: Size> ExprPartialOrd<$UIntInRangeType<Start, End>>
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
fn cmp_lt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_lt(&*lhs_value, *rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_le(&*lhs_value, *rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_gt(&*lhs_value, *rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_ge(&*lhs_value, *rhs_value)
}
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_lt(rhs.cast_to_bits())
}
fn cmp_le(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_le(rhs.cast_to_bits())
}
fn cmp_gt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_gt(rhs.cast_to_bits())
}
fn cmp_ge(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_ge(rhs.cast_to_bits())
}
}

View file

@ -9,11 +9,13 @@ use std::{
any::{Any, TypeId},
borrow::{Borrow, Cow},
cmp::Ordering,
ffi::{OsStr, OsString},
fmt,
hash::{BuildHasher, Hash, Hasher},
iter::FusedIterator,
marker::PhantomData,
ops::Deref,
path::{Path, PathBuf},
sync::{Mutex, RwLock},
};
@ -287,15 +289,266 @@ impl InternedCompare for BitSlice {
}
}
impl InternedCompare for str {
type InternedCompareKey = PtrEqWithMetadata<Self>;
fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey {
PtrEqWithMetadata(this)
/// Safety: `as_bytes` and `from_bytes_unchecked` must return the same pointer as the input.
/// all values returned by `as_bytes` must be valid to pass to `from_bytes_unchecked`.
/// `into_bytes` must return the exact same thing as `as_bytes`.
/// `Interned<Self>` must contain the exact same references as `Interned<[u8]>`,
/// so they can be safely interconverted without needing re-interning.
unsafe trait InternStrLike: ToOwned {
fn as_bytes(this: &Self) -> &[u8];
fn into_bytes(this: Self::Owned) -> Vec<u8>;
/// Safety: `bytes` must be a valid sequence of bytes for this type. All UTF-8 sequences are valid.
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self;
}
macro_rules! impl_intern_str_like {
($ty:ty, owned = $Owned:ty) => {
impl InternedCompare for $ty {
type InternedCompareKey = PtrEqWithMetadata<[u8]>;
fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey {
PtrEqWithMetadata(InternStrLike::as_bytes(this))
}
}
impl Intern for $ty {
fn intern(&self) -> Interned<Self> {
Self::intern_cow(Cow::Borrowed(self))
}
fn intern_cow(this: Cow<'_, Self>) -> Interned<Self> {
Interned::cast_unchecked(
<[u8]>::intern_cow(match this {
Cow::Borrowed(v) => Cow::Borrowed(<Self as InternStrLike>::as_bytes(v)),
Cow::Owned(v) => {
// verify $Owned is correct
let v: $Owned = v;
Cow::Owned(<Self as InternStrLike>::into_bytes(v))
}
}),
// Safety: guaranteed safe because we got the bytes from `as_bytes`/`into_bytes`
|v| unsafe { <Self as InternStrLike>::from_bytes_unchecked(v) },
)
}
}
impl Default for Interned<$ty> {
fn default() -> Self {
// Safety: safe because the empty sequence is valid UTF-8
unsafe { <$ty as InternStrLike>::from_bytes_unchecked(&[]) }.intern()
}
}
impl<'de> Deserialize<'de> for Interned<$ty> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Cow::<'de, $ty>::deserialize(deserializer).map(Intern::intern_cow)
}
}
impl From<$Owned> for Interned<$ty> {
fn from(v: $Owned) -> Self {
v.intern_deref()
}
}
impl From<Interned<$ty>> for $Owned {
fn from(v: Interned<$ty>) -> Self {
Interned::into_inner(v).into()
}
}
impl From<Interned<$ty>> for Box<$ty> {
fn from(v: Interned<$ty>) -> Self {
Interned::into_inner(v).into()
}
}
};
}
// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `str`
unsafe impl InternStrLike for str {
fn as_bytes(this: &Self) -> &[u8] {
this.as_bytes()
}
fn into_bytes(this: Self::Owned) -> Vec<u8> {
this.into_bytes()
}
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
// Safety: `bytes` is guaranteed UTF-8 by the caller
unsafe { str::from_utf8_unchecked(bytes) }
}
}
impl_intern_str_like!(str, owned = String);
// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr`
unsafe impl InternStrLike for OsStr {
fn as_bytes(this: &Self) -> &[u8] {
this.as_encoded_bytes()
}
fn into_bytes(this: Self::Owned) -> Vec<u8> {
this.into_encoded_bytes()
}
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
// Safety: `bytes` is guaranteed valid for `OsStr` by the caller
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
}
}
impl_intern_str_like!(OsStr, owned = OsString);
// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr`
unsafe impl InternStrLike for Path {
fn as_bytes(this: &Self) -> &[u8] {
this.as_os_str().as_encoded_bytes()
}
fn into_bytes(this: Self::Owned) -> Vec<u8> {
this.into_os_string().into_encoded_bytes()
}
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
// Safety: `bytes` is guaranteed valid for `OsStr` by the caller
unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(bytes)) }
}
}
impl_intern_str_like!(Path, owned = PathBuf);
impl Interned<str> {
pub fn from_utf8(v: Interned<[u8]>) -> Result<Self, std::str::Utf8Error> {
Interned::try_cast_unchecked(v, str::from_utf8)
}
pub fn as_interned_bytes(self) -> Interned<[u8]> {
Interned::cast_unchecked(self, str::as_bytes)
}
pub fn as_interned_os_str(self) -> Interned<OsStr> {
Interned::cast_unchecked(self, AsRef::as_ref)
}
pub fn as_interned_path(self) -> Interned<Path> {
Interned::cast_unchecked(self, AsRef::as_ref)
}
}
impl From<Interned<str>> for Interned<OsStr> {
fn from(value: Interned<str>) -> Self {
value.as_interned_os_str()
}
}
impl From<Interned<str>> for Interned<Path> {
fn from(value: Interned<str>) -> Self {
value.as_interned_path()
}
}
impl Interned<OsStr> {
pub fn as_interned_encoded_bytes(self) -> Interned<[u8]> {
Interned::cast_unchecked(self, OsStr::as_encoded_bytes)
}
pub fn to_interned_str(self) -> Option<Interned<str>> {
Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok()
}
pub fn display(self) -> std::ffi::os_str::Display<'static> {
Self::into_inner(self).display()
}
pub fn as_interned_path(self) -> Interned<Path> {
Interned::cast_unchecked(self, AsRef::as_ref)
}
}
impl From<Interned<OsStr>> for Interned<Path> {
fn from(value: Interned<OsStr>) -> Self {
value.as_interned_path()
}
}
impl Interned<Path> {
pub fn as_interned_os_str(self) -> Interned<OsStr> {
Interned::cast_unchecked(self, AsRef::as_ref)
}
pub fn to_interned_str(self) -> Option<Interned<str>> {
Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok()
}
pub fn display(self) -> std::path::Display<'static> {
Self::into_inner(self).display()
}
pub fn interned_file_name(self) -> Option<Interned<OsStr>> {
Some(self.file_name()?.intern())
}
}
impl From<Interned<Path>> for Interned<OsStr> {
fn from(value: Interned<Path>) -> Self {
value.as_interned_os_str()
}
}
pub trait InternSlice: Sized {
type Element: 'static + Send + Sync + Clone + Hash + Eq;
fn intern_slice(self) -> Interned<[Self::Element]>;
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq> InternSlice for Box<[T]> {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
self.into_vec().intern_slice()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq> InternSlice for Vec<T> {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
self.intern_deref()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq> InternSlice for &'_ [T] {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
self.intern()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq> InternSlice for &'_ mut [T] {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
self.intern()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq, const N: usize> InternSlice for [T; N] {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
(&self).intern_slice()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq, const N: usize> InternSlice for Box<[T; N]> {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
let this: Box<[T]> = self;
this.intern_slice()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq, const N: usize> InternSlice for &'_ [T; N] {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
let this: &[T] = self;
this.intern()
}
}
impl<T: 'static + Send + Sync + Clone + Hash + Eq, const N: usize> InternSlice for &'_ mut [T; N] {
type Element = T;
fn intern_slice(self) -> Interned<[Self::Element]> {
let this: &[T] = self;
this.intern()
}
}
pub trait Intern: Any + Send + Sync {
fn intern(&self) -> Interned<Self>;
fn intern_deref(self) -> Interned<Self::Target>
where
Self: Sized + Deref<Target: Intern + ToOwned<Owned = Self>>,
{
Self::Target::intern_owned(self)
}
fn intern_sized(self) -> Interned<Self>
where
Self: Clone,
@ -316,6 +569,30 @@ pub trait Intern: Any + Send + Sync {
}
}
impl<T: ?Sized + Intern + ToOwned> From<Cow<'_, T>> for Interned<T> {
fn from(value: Cow<'_, T>) -> Self {
Intern::intern_cow(value)
}
}
impl<T: ?Sized + Intern> From<&'_ T> for Interned<T> {
fn from(value: &'_ T) -> Self {
Intern::intern(value)
}
}
impl<T: Intern + Clone> From<T> for Interned<T> {
fn from(value: T) -> Self {
Intern::intern_sized(value)
}
}
impl<T: ?Sized + 'static + Send + Sync + ToOwned> From<Interned<T>> for Cow<'_, T> {
fn from(value: Interned<T>) -> Self {
Cow::Borrowed(Interned::into_inner(value))
}
}
struct InternerState<T: ?Sized + 'static + Send + Sync> {
table: HashTable<&'static T>,
hasher: DefaultBuildHasher,
@ -381,12 +658,6 @@ impl Interner<BitSlice> {
}
}
impl Interner<str> {
fn intern_str(&self, value: Cow<'_, str>) -> Interned<str> {
self.intern(|value| value.into_owned().leak(), value)
}
}
pub struct Interned<T: ?Sized + 'static + Send + Sync> {
inner: &'static T,
}
@ -416,6 +687,12 @@ forward_fmt_trait!(Pointer);
forward_fmt_trait!(UpperExp);
forward_fmt_trait!(UpperHex);
impl<T: ?Sized + 'static + Send + Sync + AsRef<U>, U: ?Sized> AsRef<U> for Interned<T> {
fn as_ref(&self) -> &U {
T::as_ref(self)
}
}
#[derive(Clone, Debug)]
pub struct InternedSliceIter<T: Clone + 'static + Send + Sync> {
slice: Interned<[T]>,
@ -485,6 +762,57 @@ where
}
}
impl<I> FromIterator<I> for Interned<str>
where
String: FromIterator<I>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
String::from_iter(iter).intern_deref()
}
}
impl<I> FromIterator<I> for Interned<Path>
where
PathBuf: FromIterator<I>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
PathBuf::from_iter(iter).intern_deref()
}
}
impl<I> FromIterator<I> for Interned<OsStr>
where
OsString: FromIterator<I>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
OsString::from_iter(iter).intern_deref()
}
}
impl From<Interned<str>> for clap::builder::Str {
fn from(value: Interned<str>) -> Self {
Interned::into_inner(value).into()
}
}
impl From<Interned<str>> for clap::builder::OsStr {
fn from(value: Interned<str>) -> Self {
Interned::into_inner(value).into()
}
}
impl From<Interned<str>> for clap::builder::StyledStr {
fn from(value: Interned<str>) -> Self {
Interned::into_inner(value).into()
}
}
impl From<Interned<str>> for clap::Id {
fn from(value: Interned<str>) -> Self {
Interned::into_inner(value).into()
}
}
impl<T: 'static + Clone + Send + Sync> From<Interned<[T]>> for Vec<T> {
fn from(value: Interned<[T]>) -> Self {
Vec::from(&*value)
@ -497,24 +825,12 @@ impl<T: 'static + Clone + Send + Sync> From<Interned<[T]>> for Box<[T]> {
}
}
impl From<Interned<str>> for String {
fn from(value: Interned<str>) -> Self {
String::from(&*value)
}
}
impl<I> Default for Interned<[I]>
where
[I]: Intern,
{
fn default() -> Self {
[][..].intern()
}
}
impl Default for Interned<str> {
fn default() -> Self {
"".intern()
Intern::intern(&[])
}
}
@ -645,15 +961,6 @@ impl<'de> Deserialize<'de> for Interned<BitSlice> {
}
}
impl<'de> Deserialize<'de> for Interned<str> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer).map(Intern::intern_owned)
}
}
impl<T: Clone + Send + Sync + 'static + Hash + Eq> Intern for T {
fn intern(&self) -> Interned<Self> {
Self::intern_cow(Cow::Borrowed(self))
@ -714,26 +1021,6 @@ impl Intern for BitSlice {
}
}
impl Intern for str {
fn intern(&self) -> Interned<Self> {
Self::intern_cow(Cow::Borrowed(self))
}
fn intern_owned(this: <Self as ToOwned>::Owned) -> Interned<Self>
where
Self: ToOwned,
{
Self::intern_cow(Cow::Owned(this))
}
fn intern_cow(this: Cow<'_, Self>) -> Interned<Self>
where
Self: ToOwned,
{
Interner::get().intern_str(this)
}
}
pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy {
type InputRef<'a>: 'a + Send + Sync + Hash + Copy;
type InputOwned: 'static + Send + Sync;

View file

@ -4,6 +4,18 @@
// TODO: enable:
// #![warn(missing_docs)]
#![deny(
rustdoc::bare_urls,
rustdoc::broken_intra_doc_links,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_html_tags,
rustdoc::invalid_rust_codeblocks,
rustdoc::private_doc_tests,
rustdoc::private_intra_doc_links,
rustdoc::redundant_explicit_links,
rustdoc::unescaped_backticks
)]
//! [Main Documentation][_docs]
extern crate self as fayalite;
@ -74,6 +86,135 @@ macro_rules! __cfg_expansion_helper {
pub use fayalite_proc_macros::hdl_module;
#[doc(inline)]
/// The `#[hdl]` attribute is supported on several different kinds of [Rust Items](https://doc.rust-lang.org/reference/items.html):
///
/// # Functions and Methods
/// Enable's the stuff that you can use inside a [module's body](crate::_docs::modules::module_bodies),
/// but without being a module or changing the function's signature.
/// The only exception is that you can't use stuff that requires the automatically-provided `m` variable.
///
/// # Structs
// TODO: expand on struct docs
/// e.g.:
/// ```
/// # use fayalite::prelude::*;
/// # #[hdl]
/// # pub struct OtherStruct {}
/// #[hdl]
/// pub struct MyStruct {
/// #[hdl(flip)]
/// pub a: UInt<5>,
/// pub b: Bool,
/// #[hdl(flip)]
/// pub c: OtherStruct,
/// }
/// ```
///
/// # Enums
// TODO: expand on enum docs
/// e.g.:
/// ```
/// # use fayalite::prelude::*;
/// # #[hdl]
/// # pub struct MyStruct {}
/// #[hdl]
/// pub enum MyEnum {
/// A(UInt<3>),
/// B,
/// C(MyStruct),
/// }
/// ```
///
/// # Type Aliases
///
/// There's three different ways you can create a type alias:
///
/// # Normal Type Alias
///
/// This works exactly how you'd expect:
/// ```
/// # use fayalite::prelude::*;
/// # #[hdl]
/// # pub struct MyStruct<T: Type> {
/// # v: T,
/// # }
/// #[hdl]
/// pub type MyType<T: Type> = MyStruct<T>;
///
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
///
/// let ty = MyType[UInt[3]];
/// assert_eq!(ty, MyStruct[UInt[3]]);
/// ```
///
/// # Type Alias that gets a [`Type`] from a [`PhantomConst`]
///
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Type`] that you can use in other #[hdl] types.
///
/// ```
/// # use fayalite::prelude::*;
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
/// pub struct Config {
/// pub foo: usize,
/// pub bar: Bundle,
/// }
///
/// // the expression inside `get` is called with `Interned<Config>` and returns `Array<Bundle>`
/// #[hdl(get(|config| Array[config.bar][config.foo]))]
/// pub type GetMyArray<P: PhantomConstGet<Config>> = Array<Bundle>;
///
/// // you can then use it in other types:
///
/// #[hdl(no_static)]
/// pub struct WrapMyArray<P: PhantomConstGet<Config>> {
/// pub my_array: GetMyArray<P>,
/// }
///
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
/// let bar = Bundle::new(Default::default());
/// let config = PhantomConst::new_sized(Config { foo: 12, bar });
/// let ty = WrapMyArray[config];
/// assert_eq!(ty.my_array, Array[bar][12]);
/// ```
///
/// # Type Alias that gets a [`Size`] from a [`PhantomConst`]
///
/// This allows you to use some computed property of a [`PhantomConst`] to get a [`Size`] that you can use in other #[hdl] types.
///
/// ```
/// # use fayalite::prelude::*;
/// # #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
/// # pub struct ConfigItem {}
/// # impl ConfigItem {
/// # pub fn new() -> Self {
/// # Self {}
/// # }
/// # }
/// #[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
/// pub struct Config {
/// pub items: Vec<ConfigItem>,
/// }
///
/// // the expression inside `get` is called with `Interned<Config>` and returns `usize` (not DynSize)
/// #[hdl(get(|config| config.items.len()))]
/// pub type GetItemsLen<P: PhantomConstGet<Config>> = DynSize; // must be DynSize
///
/// // you can then use it in other types:
///
/// #[hdl(no_static)]
/// pub struct FlagPerItem<P: PhantomConstGet<Config>> {
/// pub flags: ArrayType<Bool, GetItemsLen<P>>,
/// }
///
/// // you can then use Fayalite's standard syntax for creating dynamic types at runtime:
/// let config = PhantomConst::new_sized(Config { items: vec![ConfigItem::new(); 5] });
/// let ty = FlagPerItem[config];
/// assert_eq!(ty.flags, Array[Bool][5]);
/// ```
///
/// [`PhantomConst`]: crate::phantom_const::PhantomConst
/// [`Size`]: crate::int::Size
/// [`Type`]: crate::ty::Type
pub use fayalite_proc_macros::hdl;
pub use bitvec;
@ -87,8 +228,8 @@ pub mod _docs;
pub mod annotations;
pub mod array;
pub mod build;
pub mod bundle;
pub mod cli;
pub mod clock;
pub mod enum_;
pub mod expr;
@ -99,6 +240,7 @@ pub mod intern;
pub mod memory;
pub mod module;
pub mod phantom_const;
pub mod platform;
pub mod prelude;
pub mod reg;
pub mod reset;
@ -107,4 +249,5 @@ pub mod source_location;
pub mod testing;
pub mod ty;
pub mod util;
pub mod vendor;
pub mod wire;

View file

@ -7,7 +7,10 @@ use crate::{
array::{Array, ArrayType},
bundle::{Bundle, BundleType},
clock::Clock,
expr::{Expr, Flow, ToExpr, ToLiteralBits, ops::BundleLiteral, repeat},
expr::{
Expr, Flow, ToExpr, ToLiteralBits, ValueType, ops::BundleLiteral, repeat,
value_category::ValueCategoryExpr,
},
hdl,
int::{Bool, DynSize, Size, UInt, UIntType},
intern::{Intern, Interned},
@ -366,10 +369,16 @@ impl<T: PortType> fmt::Debug for MemPort<T> {
}
}
impl<T: PortType> MemPort<T> {
pub fn ty(&self) -> T::Port {
impl<T: PortType> ValueType for MemPort<T> {
type Type = T::Port;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> T::Port {
T::port_ty(self)
}
}
impl<T: PortType> MemPort<T> {
pub fn source_location(&self) -> SourceLocation {
self.source_location
}
@ -830,7 +839,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
depth: Option<usize>,
initial_value: Expr<Array>,
) -> Interned<BitSlice> {
let initial_value_ty = Expr::ty(initial_value);
let initial_value_ty = initial_value.ty();
assert_eq!(
*mem_element_type,
Element::from_canonical(initial_value_ty.element()),
@ -1011,7 +1020,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
target.depth,
initial_value,
));
target.depth = Some(Expr::ty(initial_value).len());
target.depth = Some(initial_value.ty().len());
}
#[track_caller]
pub fn initial_value_bit_slice(&mut self, initial_value: Interned<BitSlice>) {

View file

@ -8,17 +8,19 @@ use crate::{
clock::{Clock, ClockDomain},
enum_::{Enum, EnumMatchVariantsIter, EnumType},
expr::{
Expr, Flow, ToExpr,
Expr, Flow, ToExpr, ValueType,
ops::VariantAccess,
target::{
GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField,
TargetPathElement,
},
value_category::ValueCategoryExpr,
},
formal::FormalKind,
int::{Bool, DynSize, Size},
intern::{Intern, Interned},
memory::{Mem, MemBuilder, MemBuilderTarget, PortName},
platform::PlatformIOBuilder,
reg::Reg,
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
sim::{ExternModuleSimGenerator, ExternModuleSimulation},
@ -40,7 +42,6 @@ use std::{
marker::PhantomData,
mem,
num::NonZeroU64,
ops::Deref,
rc::Rc,
sync::atomic::AtomicU64,
};
@ -66,6 +67,8 @@ pub trait ModuleBuildingStatus:
type ModuleBody: fmt::Debug;
type StmtAnnotations: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug;
type ModuleIOAnnotations;
type ExternModuleParameters: fmt::Debug;
type ExternModuleClocksForPast: fmt::Debug;
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
@ -78,6 +81,8 @@ impl ModuleBuildingStatus for ModuleBuilt {
type ModuleBody = Block;
type StmtAnnotations = Interned<[TargetedAnnotation]>;
type ModuleIOAnnotations = Interned<[TargetedAnnotation]>;
type ExternModuleParameters = Interned<[ExternModuleParameter]>;
type ExternModuleClocksForPast = Interned<[Target]>;
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
@ -90,6 +95,8 @@ impl ModuleBuildingStatus for ModuleBuilding {
type ModuleBody = BuilderModuleBody;
type StmtAnnotations = ();
type ModuleIOAnnotations = Vec<TargetedAnnotation>;
type ExternModuleParameters = Vec<ExternModuleParameter>;
type ExternModuleClocksForPast = Vec<Target>;
}
#[derive(Debug)]
@ -199,7 +206,7 @@ impl StmtConnect {
source_location,
} = *self;
assert!(
Expr::ty(lhs).can_connect(Expr::ty(rhs)),
lhs.ty().can_connect(rhs.ty()),
"can't connect types that are not equivalent:\nlhs type:\n{lhs_orig_ty:?}\nrhs type:\n{rhs_orig_ty:?}\nat: {source_location}",
);
assert!(
@ -213,14 +220,14 @@ impl StmtConnect {
match Expr::flow(rhs) {
Flow::Source | Flow::Duplex => {}
Flow::Sink => assert!(
Expr::ty(rhs).is_passive(),
rhs.ty().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));
self.assert_validity_with_original_types(self.lhs.ty(), self.rhs.ty());
}
}
@ -325,7 +332,7 @@ impl Copy for StmtMatch {}
impl StmtMatch {
#[track_caller]
fn assert_validity(&self) {
assert_eq!(Expr::ty(self.expr).variants().len(), self.blocks.len());
assert_eq!(self.expr.ty().variants().len(), self.blocks.len());
}
}
@ -759,6 +766,15 @@ impl<T: BundleType> fmt::Debug for Instance<T> {
}
}
impl<T: BundleType> ValueType for Instance<T> {
type Type = T;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> T {
self.instantiated.io_ty()
}
}
impl<T: BundleType> Instance<T> {
pub fn canonical(self) -> Instance<Bundle> {
let Self {
@ -822,9 +838,6 @@ impl<T: BundleType> Instance<T> {
pub fn must_connect_to(&self) -> bool {
true
}
pub fn ty(&self) -> T {
self.instantiated.io_ty()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
@ -833,6 +846,8 @@ pub struct AnnotatedModuleIO<S: ModuleBuildingStatus = ModuleBuilt> {
pub module_io: ModuleIO<CanonicalType>,
}
impl Copy for AnnotatedModuleIO {}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum ModuleKind {
Extern,
@ -1077,26 +1092,65 @@ impl From<NormalModuleBody> for ModuleBody {
}
}
#[track_caller]
fn validate_clock_for_past<S: ModuleBuildingStatus>(
clock_for_past: Option<Target>,
module_io: &[AnnotatedModuleIO<S>],
) -> Target {
if let Some(clock_for_past) = clock_for_past {
assert_eq!(
clock_for_past.canonical_ty(),
Clock.canonical(),
"clock_for_past: clock is not of type Clock",
);
if clock_for_past
.base()
.module_io()
.is_some_and(|v| module_io.iter().any(|module_io| module_io.module_io == *v))
{
let mut target = clock_for_past;
while let Target::Child(child) = target {
match *child.path_element() {
TargetPathElement::BundleField(_) | TargetPathElement::ArrayElement(_) => {}
TargetPathElement::DynArrayElement(_) => {
panic!(
"clock_for_past: clock must be a static target (you can't use `Expr<UInt>` array indexes):\n{clock_for_past:?}"
);
}
}
target = *child.parent();
}
return clock_for_past;
}
}
panic!("clock_for_past: clock must be some part of this module's I/O:\n{clock_for_past:?}");
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub struct ExternModuleBody<
P: Deref<Target = [ExternModuleParameter]> = Interned<[ExternModuleParameter]>,
> {
pub struct ExternModuleBody<S: ModuleBuildingStatus = ModuleBuilt> {
pub verilog_name: Interned<str>,
pub parameters: P,
pub parameters: S::ExternModuleParameters,
/// [`Clock`]s that the [`Simulation`] will store the past values of all [`ModuleIO`] for.
///
/// [`Simulation`]: crate::sim::Simulation
pub clocks_for_past: S::ExternModuleClocksForPast,
pub simulation: Option<ExternModuleSimulation>,
}
impl From<ExternModuleBody<Vec<ExternModuleParameter>>> for ExternModuleBody {
fn from(value: ExternModuleBody<Vec<ExternModuleParameter>>) -> Self {
impl From<ExternModuleBody<ModuleBuilding>> for ExternModuleBody {
fn from(value: ExternModuleBody<ModuleBuilding>) -> Self {
let ExternModuleBody {
verilog_name,
parameters,
clocks_for_past,
simulation,
} = value;
let parameters = Intern::intern_owned(parameters);
let clocks_for_past = Intern::intern_owned(clocks_for_past);
Self {
verilog_name,
parameters,
clocks_for_past,
simulation,
}
}
@ -1109,15 +1163,12 @@ impl From<ExternModuleBody> for ModuleBody {
}
#[derive(Debug)]
pub enum ModuleBody<
S: ModuleBuildingStatus = ModuleBuilt,
P: Deref<Target = [ExternModuleParameter]> = Interned<[ExternModuleParameter]>,
> {
pub enum ModuleBody<S: ModuleBuildingStatus = ModuleBuilt> {
Normal(NormalModuleBody<S>),
Extern(ExternModuleBody<P>),
Extern(ExternModuleBody<S>),
}
pub(crate) type ModuleBodyBuilding = ModuleBody<ModuleBuilding, Vec<ExternModuleParameter>>;
pub(crate) type ModuleBodyBuilding = ModuleBody<ModuleBuilding>;
impl ModuleBodyBuilding {
pub(crate) fn builder_normal_body_opt(
@ -1138,9 +1189,7 @@ impl ModuleBodyBuilding {
}
}
#[track_caller]
pub(crate) fn builder_extern_body(
&mut self,
) -> &mut ExternModuleBody<Vec<ExternModuleParameter>> {
pub(crate) fn builder_extern_body(&mut self) -> &mut ExternModuleBody<ModuleBuilding> {
if let Self::Extern(v) = self {
v
} else {
@ -1212,6 +1261,12 @@ pub struct Module<T: BundleType> {
module_annotations: Interned<[Annotation]>,
}
impl<T: BundleType> AsRef<Self> for Module<T> {
fn as_ref(&self) -> &Self {
self
}
}
#[derive(Default)]
struct DebugFmtModulesState {
seen: HashSet<NameId>,
@ -1288,11 +1343,13 @@ impl<T: BundleType> fmt::Debug for DebugModuleBody<T> {
ModuleBody::Extern(ExternModuleBody {
verilog_name,
parameters,
clocks_for_past,
simulation,
}) => {
debug_struct
.field("verilog_name", verilog_name)
.field("parameters", parameters)
.field("clocks_for_past", clocks_for_past)
.field("simulation", simulation);
}
}
@ -1771,8 +1828,13 @@ impl AssertValidityState {
ModuleBody::Extern(ExternModuleBody {
verilog_name: _,
parameters: _,
clocks_for_past,
simulation: _,
}) => {}
}) => {
for clock_for_past in clocks_for_past {
validate_clock_for_past(Some(clock_for_past), &self.module.module_io);
}
}
ModuleBody::Normal(NormalModuleBody { body }) => {
let body = self.make_block_index(body);
assert_eq!(body, 0);
@ -1802,9 +1864,17 @@ impl<T: BundleType> Module<T> {
match &mut body {
ModuleBody::Normal(_) => {}
ModuleBody::Extern(ExternModuleBody {
verilog_name: _,
parameters: _,
clocks_for_past,
simulation: Some(simulation),
..
}) => {
let mut clocks_for_past_set = HashSet::default();
*clocks_for_past = clocks_for_past
.iter()
.copied()
.filter(|clock_for_past| clocks_for_past_set.insert(*clock_for_past))
.collect();
if module_io.iter().any(|io| {
!simulation
.sim_io_to_generator_map
@ -1965,7 +2035,7 @@ impl<CD> RegBuilder<CD, (), ()> {
init: _,
ty: _,
} = self;
let ty = Expr::ty(init);
let ty = init.ty();
RegBuilder {
name,
source_location,
@ -2111,6 +2181,27 @@ impl ModuleBuilder {
self.output_with_loc(implicit_name.0, SourceLocation::caller(), ty)
}
#[track_caller]
pub fn add_platform_io_with_loc(
&self,
name: &str,
source_location: SourceLocation,
platform_io_builder: PlatformIOBuilder<'_>,
) -> Expr<Bundle> {
platform_io_builder.add_platform_io(name, source_location, self)
}
#[track_caller]
pub fn add_platform_io(
&self,
implicit_name: ImplicitName<'_>,
platform_io_builder: PlatformIOBuilder<'_>,
) -> Expr<Bundle> {
self.add_platform_io_with_loc(
implicit_name.0,
SourceLocation::caller(),
platform_io_builder,
)
}
#[track_caller]
pub fn run<T: BundleType>(
name: &str,
module_kind: ModuleKind,
@ -2156,6 +2247,7 @@ impl ModuleBuilder {
ModuleKind::Extern => ModuleBody::Extern(ExternModuleBody {
verilog_name: name.0,
parameters: vec![],
clocks_for_past: vec![],
simulation: None,
}),
ModuleKind::Normal => ModuleBody::Normal(NormalModuleBody {
@ -2278,6 +2370,20 @@ impl ModuleBuilder {
value: ExternModuleParameterValue::RawVerilog(raw_verilog.intern()),
});
}
/// registers a [`Clock`] so you can use it with the [`ExternModuleSimulationState::read_past()`] family of functions.
///
/// [`ExternModuleSimulationState::read_past()`]: crate::sim::ExternModuleSimulationState::read_past()
#[track_caller]
pub fn register_clock_for_past(&self, clock_for_past: impl ToExpr<Type = Clock>) {
let clock_for_past = clock_for_past.to_expr().target().as_deref().copied();
let mut impl_ = self.impl_.borrow_mut();
let clock_for_past = validate_clock_for_past(clock_for_past, &impl_.io);
impl_
.body
.builder_extern_body()
.clocks_for_past
.push(clock_for_past);
}
#[track_caller]
pub fn extern_module_simulation<G: ExternModuleSimGenerator>(&self, generator: G) {
let mut impl_ = self.impl_.borrow_mut();
@ -2498,7 +2604,7 @@ pub fn enum_match_variants_helper<T: EnumType>(
ModuleBuilder::with(|m| {
let mut impl_ = m.impl_.borrow_mut();
let body = impl_.body.builder_normal_body();
let enum_ty = Expr::ty(base);
let enum_ty = base.ty();
let outer_block: BlockId = m.block_stack.top();
let blocks = Interned::from_iter(enum_ty.variants().iter().map(|_| body.new_block()));
body.block(outer_block).stmts.push(
@ -2550,7 +2656,7 @@ pub fn connect_any_with_loc<Lhs: ToExpr, Rhs: ToExpr>(
rhs,
source_location,
};
connect.assert_validity_with_original_types(Expr::ty(lhs_orig), Expr::ty(rhs_orig));
connect.assert_validity_with_original_types(lhs_orig.ty(), rhs_orig.ty());
ModuleBuilder::with(|m| {
m.impl_
.borrow_mut()
@ -2665,7 +2771,7 @@ pub fn memory_with_init_and_loc<Element: Type, Len: Size>(
source_location: SourceLocation,
) -> MemBuilder<Element, Len> {
let initial_value = initial_value.to_expr();
let mut retval = memory_array_with_loc(name, Expr::ty(initial_value), source_location);
let mut retval = memory_array_with_loc(name, initial_value.ty(), source_location);
retval.initial_value(initial_value);
retval
}
@ -2708,6 +2814,15 @@ pub struct ModuleIO<T: Type> {
source_location: SourceLocation,
}
impl<T: Type> ValueType for ModuleIO<T> {
type Type = T;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
self.ty
}
}
impl<T: Type> fmt::Debug for ModuleIO<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ModuleIO")
@ -2735,6 +2850,22 @@ impl<T: Type> ModuleIO<T> {
source_location,
}
}
pub fn from_canonical(canonical_module_io: ModuleIO<CanonicalType>) -> Self {
let ModuleIO {
containing_module_name,
bundle_field,
id,
ty,
source_location,
} = canonical_module_io;
Self {
containing_module_name,
bundle_field,
id,
ty: T::from_canonical(ty),
source_location,
}
}
pub fn bundle_field(&self) -> BundleField {
self.bundle_field
}
@ -2793,9 +2924,6 @@ impl<T: Type> ModuleIO<T> {
unreachable!()
}
}
pub fn ty(&self) -> T {
self.ty
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]

View file

@ -6,7 +6,7 @@ use crate::{
bundle::{BundleField, BundleType},
enum_::{EnumType, EnumVariant},
expr::{
ExprEnum,
ExprEnum, ValueType,
ops::{self, ArrayLiteral},
target::{
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
@ -521,7 +521,7 @@ impl State {
Entry::Vacant(entry) => (
*entry.insert(Resets::with_new_nodes(
&mut self.reset_graph,
Expr::ty(expr),
expr.ty(),
source_location,
)),
true,
@ -1020,7 +1020,7 @@ fn cast_bit_op<P: Pass, T: Type, A: Type>(
}
macro_rules! match_arg_ty {
($($Variant:ident),*) => {
*match Expr::ty(arg) {
*match arg.ty() {
CanonicalType::Array(_)
| CanonicalType::Enum(_)
| CanonicalType::Bundle(_)
@ -1660,7 +1660,8 @@ impl RunPassDispatch for AnyReg {
let clock_domain = Expr::<Bundle>::from_canonical(
Expr::canonical(reg.clock_domain()).run_pass(pass_args)?.0,
);
match Expr::ty(clock_domain)
match clock_domain
.ty()
.field_by_name("rst".intern())
.expect("ClockDomain has rst field")
.ty
@ -1802,6 +1803,7 @@ impl_run_pass_clone!([] ExternModuleParameter);
impl_run_pass_clone!([] SIntValue);
impl_run_pass_clone!([] std::ops::Range<usize>);
impl_run_pass_clone!([] UIntValue);
impl_run_pass_clone!([] crate::vendor::xilinx::XilinxAnnotation);
impl_run_pass_copy!([] BlackBoxInlineAnnotation);
impl_run_pass_copy!([] BlackBoxPathAnnotation);
impl_run_pass_copy!([] bool);
@ -2072,6 +2074,7 @@ impl_run_pass_for_struct! {
impl[] RunPass for ExternModuleBody {
verilog_name: _,
parameters: _,
clocks_for_past: _,
simulation: _,
}
}
@ -2217,6 +2220,7 @@ impl_run_pass_for_enum! {
BlackBoxPath(v),
DocString(v),
CustomFirrtl(v),
Xilinx(v),
}
}

View file

@ -5,12 +5,12 @@ use crate::{
bundle::{Bundle, BundleField, BundleType},
enum_::{Enum, EnumType, EnumVariant},
expr::{
CastBitsTo, CastTo, CastToBits, Expr, ExprEnum, HdlPartialEq, ToExpr,
CastBitsTo, CastTo, CastToBits, Expr, ExprEnum, HdlPartialEq, ToExpr, ValueType,
ops::{self, EnumLiteral},
},
hdl,
int::UInt,
intern::{Intern, Interned, Memoize},
intern::{Intern, InternSlice, Interned, Memoize},
memory::{DynPortType, Mem, MemPort},
module::{
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire,
@ -22,6 +22,7 @@ use crate::{
wire::Wire,
};
use core::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub enum SimplifyEnumsError {
@ -527,7 +528,7 @@ fn connect_port(
rhs: Expr<CanonicalType>,
source_location: SourceLocation,
) {
if Expr::ty(lhs) == Expr::ty(rhs) {
if lhs.ty() == rhs.ty() {
stmts.push(
StmtConnect {
lhs,
@ -538,7 +539,7 @@ fn connect_port(
);
return;
}
match (Expr::ty(lhs), Expr::ty(rhs)) {
match (lhs.ty(), rhs.ty()) {
(CanonicalType::Bundle(lhs_type), CanonicalType::UInt(_) | CanonicalType::Bool(_)) => {
let lhs = Expr::<Bundle>::from_canonical(lhs);
for field in lhs_type.fields() {
@ -585,8 +586,8 @@ fn connect_port(
| (CanonicalType::PhantomConst(_), _)
| (CanonicalType::DynSimOnly(_), _) => unreachable!(
"trying to connect memory ports:\n{:?}\n{:?}",
Expr::ty(lhs),
Expr::ty(rhs),
lhs.ty(),
rhs.ty(),
),
}
}
@ -606,20 +607,23 @@ fn match_int_tag(
};
};
let mut retval = StmtIf {
cond: int_tag_expr
.cmp_eq(Expr::ty(int_tag_expr).from_int_wrapping(next_to_last_variant_index)),
cond: int_tag_expr.cmp_eq(
int_tag_expr
.ty()
.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)),
cond: int_tag_expr.cmp_eq(int_tag_expr.ty().from_int_wrapping(variant_index)),
source_location,
blocks: [
block,
Block {
memories: Default::default(),
stmts: [Stmt::from(retval)][..].intern(),
stmts: [Stmt::from(retval)].intern_slice(),
},
],
};
@ -656,7 +660,7 @@ impl Folder for State {
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()),
op.base().ty(),
folded_base_expr,
op.variant_index(),
)?))
@ -866,7 +870,7 @@ impl Folder for State {
}) => {
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)
self.handle_match(expr.ty(), folded_expr, source_location, &folded_blocks)
}
Stmt::Connect(StmtConnect {
lhs,
@ -877,8 +881,8 @@ impl Folder for State {
let folded_rhs = rhs.fold(self)?;
let mut output_stmts = vec![];
self.handle_stmt_connect(
Expr::ty(lhs),
Expr::ty(rhs),
lhs.ty(),
rhs.ty(),
folded_lhs,
folded_rhs,
source_location,
@ -955,12 +959,15 @@ impl Folder for State {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, clap::ValueEnum)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, clap::ValueEnum, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SimplifyEnumsKind {
SimplifyToEnumsWithNoBody,
#[clap(name = "replace-with-bundle-of-uints")]
#[serde(rename = "replace-with-bundle-of-uints")]
ReplaceWithBundleOfUInts,
#[clap(name = "replace-with-uint")]
#[serde(rename = "replace-with-uint")]
ReplaceWithUInt,
}

View file

@ -4,7 +4,7 @@ use crate::{
annotations::TargetedAnnotation,
array::Array,
bundle::{Bundle, BundleType},
expr::{CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr},
expr::{CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr, ValueType},
int::{Bool, SInt, Size, UInt},
intern::{Intern, Interned},
memory::{Mem, MemPort, PortType},
@ -530,7 +530,7 @@ impl ModuleState {
connect_read(
output_stmts,
wire_read,
Expr::<UInt>::from_canonical(port_read).cast_bits_to(Expr::ty(wire_read)),
Expr::<UInt>::from_canonical(port_read).cast_bits_to(wire_read.ty()),
);
};
let connect_write_enum =

View file

@ -11,7 +11,7 @@ use crate::{
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},
expr::{
Expr, ExprEnum, ops,
Expr, ExprEnum, ValueType, ops,
target::{
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
TargetPathDynArrayElement, TargetPathElement,
@ -33,6 +33,9 @@ use crate::{
sim::{ExternModuleSimulation, value::DynSimOnly},
source_location::SourceLocation,
ty::{CanonicalType, Type},
vendor::xilinx::{
XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation,
},
wire::Wire,
};
use num_bigint::{BigInt, BigUint};

View file

@ -2,13 +2,10 @@
// See Notices.txt for copyright information
use crate::{
expr::{
Expr, ToExpr,
ops::{ExprPartialEq, ExprPartialOrd},
},
expr::{Expr, HdlPartialEqImpl, HdlPartialOrdImpl, ToExpr, ValueType},
int::Bool,
intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize},
sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType},
sim::value::{SimValue, ToSimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
@ -22,6 +19,7 @@ use serde::{
};
use std::{
any::Any,
borrow::Cow,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
@ -131,7 +129,7 @@ impl<T: Type + PhantomConstValue> Index<T> for PhantomConstWithoutGenerics {
type Output = PhantomConst<T>;
fn index(&self, value: T) -> &Self::Output {
Interned::into_inner(PhantomConst::new(value.intern()).intern_sized())
Interned::into_inner(PhantomConst::new(&value).intern_sized())
}
}
@ -222,11 +220,26 @@ impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T,
}
impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
pub fn new(value: Interned<T>) -> Self {
pub fn new_interned(value: Interned<T>) -> Self {
Self {
value: LazyInterned::Interned(value),
}
}
pub fn new_sized(value: T) -> Self
where
T: Clone,
{
Self::new_interned(value.intern_sized())
}
pub fn new(value: &T) -> Self {
Self::new_interned(value.intern())
}
pub fn new_deref<U: Intern + std::ops::Deref<Target = T>>(value: U) -> Self
where
T: ToOwned<Owned = U>,
{
Self::new_interned(value.intern_deref())
}
pub const fn new_lazy(v: &'static dyn LazyInternedTrait<T>) -> Self {
Self {
value: LazyInterned::new_lazy(v),
@ -245,7 +258,7 @@ impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
if let Some(&retval) = <dyn Any>::downcast_ref::<PhantomConst>(&self) {
return retval;
}
<PhantomConst>::new(
<PhantomConst>::new_interned(
PhantomConstCanonicalMemoize::<T, false>(PhantomData).get_owned(self.get()),
)
}
@ -253,7 +266,7 @@ impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
if let Some(&retval) = <dyn Any>::downcast_ref::<Self>(&canonical_type) {
return retval;
}
Self::new(
Self::new_interned(
PhantomConstCanonicalMemoize::<T, true>(PhantomData).get_owned(canonical_type.get()),
)
}
@ -346,7 +359,9 @@ impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst<T> {
D: Deserializer<'de>,
{
match SerdeType::<T>::deserialize(deserializer)? {
SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => Ok(Self::new(value)),
SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => {
Ok(Self::new_interned(value))
}
ty => Err(Error::invalid_value(
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
&"a PhantomConst",
@ -355,50 +370,102 @@ impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst<T> {
}
}
impl<T: ?Sized + PhantomConstValue> ExprPartialEq<Self> for PhantomConst<T> {
fn cmp_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
true.to_expr()
}
fn cmp_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
false.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> ExprPartialOrd<Self> for PhantomConst<T> {
fn cmp_lt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
false.to_expr()
}
fn cmp_le(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
true.to_expr()
}
fn cmp_gt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
false.to_expr()
}
fn cmp_ge(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(Expr::ty(lhs), Expr::ty(rhs));
true.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> SimValuePartialEq<Self> for PhantomConst<T> {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Self>) -> bool {
assert_eq!(SimValue::ty(this), SimValue::ty(other));
impl<T: ?Sized + PhantomConstValue> HdlPartialEqImpl<Self> for PhantomConst<T> {
#[track_caller]
fn cmp_value_eq(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> HdlPartialOrdImpl<Self> for PhantomConst<T> {
#[track_caller]
fn cmp_value_lt(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
false
}
#[track_caller]
fn cmp_value_le(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_value_gt(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
false
}
#[track_caller]
fn cmp_value_ge(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
#[track_caller]
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
#[track_caller]
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
#[track_caller]
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> ToSimValue for PhantomConst<T> {
type Type = PhantomConst<T>;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(*self, *self)
}
@ -415,3 +482,71 @@ impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<CanonicalType> for Phanto
SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self))
}
}
mod sealed {
pub trait Sealed<T: ?Sized> {}
}
pub trait PhantomConstGet<T: ?Sized + PhantomConstValue>: sealed::Sealed<T> {
fn get(&self) -> Interned<T>;
}
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
sealed::Sealed<T> for This
{
}
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
PhantomConstGet<T> for This
{
fn get(&self) -> Interned<T> {
This::Target::get(&**self)
}
}
macro_rules! impl_phantom_const_get {
(
impl PhantomConstGet<$T:ident> for $ty:ty {
fn $get:ident(&$get_self:ident) -> _ $get_body:block
}
) => {
impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {}
impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty {
fn $get(&$get_self) -> Interned<$T> $get_body
}
};
}
impl_phantom_const_get! {
impl PhantomConstGet<T> for PhantomConst<T> {
fn get(&self) -> _ {
PhantomConst::get(*self)
}
}
}
impl_phantom_const_get! {
impl PhantomConstGet<T> for Expr<PhantomConst<T>> {
fn get(&self) -> _ {
PhantomConst::get(self.ty())
}
}
}
#[doc(hidden)]
pub trait ReturnSelfUnchanged<T: ?Sized> {
type Type: ?Sized;
}
impl<This: ?Sized, T: ?Sized> ReturnSelfUnchanged<T> for This {
type Type = This;
}
#[doc(hidden)]
pub fn type_alias_phantom_const_get_helper<T: ?Sized + PhantomConstValue, R: Intern + Clone>(
param: impl PhantomConstGet<T>,
get: impl FnOnce(Interned<T>) -> R,
) -> &'static R {
Interned::into_inner(get(param.get()).intern_sized())
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::prelude::*;
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClockInputProperties {
pub frequency: NotNan<f64>,
}
#[hdl(no_runtime_generics, no_static)]
pub struct ClockInput {
pub clk: Clock,
pub properties: PhantomConst<ClockInputProperties>,
}
impl ClockInput {
#[track_caller]
pub fn new(frequency: f64) -> Self {
assert!(
frequency > 0.0 && frequency.is_finite(),
"invalid clock frequency: {frequency}"
);
Self {
clk: Clock,
properties: PhantomConst::new_sized(ClockInputProperties {
frequency: NotNan::new(frequency).expect("just checked"),
}),
}
}
pub fn frequency(self) -> f64 {
self.properties.get().frequency.into_inner()
}
}
#[hdl]
pub struct Led {
pub on: Bool,
}
#[hdl]
pub struct RgbLed {
pub r: Bool,
pub g: Bool,
pub b: Bool,
}
#[hdl]
/// UART, used as an output from the FPGA
pub struct Uart {
/// transmit from the FPGA's perspective
pub tx: Bool,
/// receive from the FPGA's perspective
#[hdl(flip)]
pub rx: Bool,
}

View file

@ -7,13 +7,13 @@ pub use crate::{
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
},
array::{Array, ArrayType},
build::{BuildCli, JobParams, RunBuild},
bundle::Bundle,
cli::Cli,
clock::{Clock, ClockDomain, ToClock},
enum_::{Enum, HdlNone, HdlOption, HdlSome},
expr::{
CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr,
ReduceBits, ToExpr, repeat,
ReduceBits, ToExpr, ValueType, repeat,
},
formal::{
MakeFormalExpr, all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset,
@ -27,7 +27,8 @@ pub use crate::{
Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance,
memory, memory_array, memory_with_init, reg_builder, wire,
},
phantom_const::PhantomConst,
phantom_const::{PhantomConst, PhantomConstGet},
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
sim::{
@ -36,6 +37,7 @@ pub use crate::{
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
},
source_location::SourceLocation,
testing::{FormalMode, assert_formal},
ty::{AsMask, CanonicalType, Type},
util::{ConstUsize, GenericConstUsize},
wire::Wire,

View file

@ -2,7 +2,7 @@
// See Notices.txt for copyright information
use crate::{
clock::ClockDomain,
expr::{Expr, Flow},
expr::{Expr, Flow, ValueType, value_category::ValueCategoryExpr},
intern::Interned,
module::{NameId, ScopedNameId},
reset::{Reset, ResetType},
@ -20,7 +20,16 @@ pub struct Reg<T: Type, R: ResetType = Reset> {
init: Option<Expr<T>>,
}
impl<T: Type + fmt::Debug, R: ResetType> fmt::Debug for Reg<T, R> {
impl<T: Type, R: ResetType> ValueType for Reg<T, R> {
type Type = T;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
self.ty
}
}
impl<T: Type, R: ResetType> fmt::Debug for Reg<T, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
name,
@ -68,7 +77,7 @@ impl<T: Type, R: ResetType> Reg<T, R> {
"register type must be a storable type"
);
if let Some(init) = init {
assert_eq!(ty, Expr::ty(init), "register's type must match init type");
assert_eq!(ty, init.ty(), "register's type must match init type");
}
Self {
name: scoped_name,
@ -78,9 +87,6 @@ impl<T: Type, R: ResetType> Reg<T, R> {
init,
}
}
pub fn ty(&self) -> T {
self.ty
}
pub fn source_location(&self) -> SourceLocation {
self.source_location
}

View file

@ -2,13 +2,15 @@
// See Notices.txt for copyright information
use crate::{
clock::Clock,
expr::{Expr, ToExpr, ops},
int::{Bool, SInt, UInt},
expr::{CastToImpl, Expr, ValueType},
int::{Bool, SInt, SIntValue, UInt, UIntValue},
sim::value::SimValue,
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self,
},
util::ConstUsize,
};
use bitvec::{bits, order::Lsb0};
@ -19,15 +21,15 @@ mod sealed {
pub trait ResetType:
StaticType<MaskType = Bool>
+ sealed::ResetTypeSealed
+ ops::ExprCastTo<Bool>
+ ops::ExprCastTo<Reset>
+ ops::ExprCastTo<SyncReset>
+ ops::ExprCastTo<AsyncReset>
+ ops::ExprCastTo<Clock>
+ ops::ExprCastTo<UInt<1>>
+ ops::ExprCastTo<SInt<1>>
+ ops::ExprCastTo<UInt>
+ ops::ExprCastTo<SInt>
+ CastToImpl<Bool, ValueOutput = bool>
+ CastToImpl<Reset, ValueOutput = SimValue<Reset>>
+ CastToImpl<SyncReset, ValueOutput = SimValue<SyncReset>>
+ CastToImpl<AsyncReset, ValueOutput = SimValue<AsyncReset>>
+ CastToImpl<Clock, ValueOutput = SimValue<Clock>>
+ CastToImpl<UInt<1>, ValueOutput = UIntValue<ConstUsize<1>>>
+ CastToImpl<SInt<1>, ValueOutput = SIntValue<ConstUsize<1>>>
+ CastToImpl<UInt, ValueOutput = UIntValue>
+ CastToImpl<SInt, ValueOutput = SIntValue>
{
fn dispatch<D: ResetTypeDispatch>(input: D::Input<Self>, dispatch: D) -> D::Output<Self>;
}
@ -132,29 +134,34 @@ macro_rules! reset_type {
}
pub trait $Trait {
fn $trait_fn(&self) -> Expr<$name>;
type Output: ValueType<Type = $name>;
fn $trait_fn(&self) -> Self::Output;
}
impl<T: ?Sized + $Trait> $Trait for &'_ T {
fn $trait_fn(&self) -> Expr<$name> {
type Output = T::Output;
fn $trait_fn(&self) -> Self::Output {
(**self).$trait_fn()
}
}
impl<T: ?Sized + $Trait> $Trait for &'_ mut T {
fn $trait_fn(&self) -> Expr<$name> {
type Output = T::Output;
fn $trait_fn(&self) -> Self::Output {
(**self).$trait_fn()
}
}
impl<T: ?Sized + $Trait> $Trait for Box<T> {
fn $trait_fn(&self) -> Expr<$name> {
type Output = T::Output;
fn $trait_fn(&self) -> Self::Output {
(**self).$trait_fn()
}
}
$($impl_trait $Trait for Expr<$name> {
fn $trait_fn(&self) -> Expr<$name> {
type Output = Expr<$name>;
fn $trait_fn(&self) -> Self::Output {
*self
}
})?
@ -171,13 +178,15 @@ reset_type!(
);
impl ToSyncReset for bool {
fn to_sync_reset(&self) -> Expr<SyncReset> {
self.to_expr().to_sync_reset()
type Output = SimValue<SyncReset>;
fn to_sync_reset(&self) -> Self::Output {
SimValue::from_value(SyncReset, *self)
}
}
impl ToAsyncReset for bool {
fn to_async_reset(&self) -> Expr<AsyncReset> {
self.to_expr().to_async_reset()
type Output = SimValue<AsyncReset>;
fn to_async_reset(&self) -> Self::Output {
SimValue::from_value(AsyncReset, *self)
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,14 @@ use crate::{
bundle::{BundleField, BundleType},
enum_::{EnumType, EnumVariant},
expr::{
ExprEnum, Flow, ops,
ExprEnum, Flow, ValueType, ops,
target::{
GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField,
TargetPathElement,
},
},
int::BoolOrIntType,
intern::{Intern, Interned, Memoize},
intern::{Intern, InternSlice, Interned, Memoize},
memory::PortKind,
module::{
AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId,
@ -28,12 +28,12 @@ use crate::{
ExternModuleSimulation, SimTrace, SimTraceKind, SimTraces, TraceArray, TraceAsyncReset,
TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields,
TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId,
TraceMemoryLocation, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalarId,
TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire,
TraceMemoryLocation, TraceModule, TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt,
TraceScalarId, TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire,
interpreter::{
Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding,
InsnsBuildingDone, InsnsBuildingKind, Label, SmallUInt, StatePartArrayIndex,
StatePartArrayIndexed,
self, Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding,
InsnsBuildingDone, InsnsBuildingKind, Label, PrefixLinesWrapper, SmallUInt,
StatePartArrayIndex, StatePartArrayIndexed,
parts::{
MemoryData, SlotDebugData, StatePartIndex, StatePartIndexRange, StatePartKind,
StatePartKindBigSlots, StatePartKindMemories, StatePartKindSimOnlySlots,
@ -82,19 +82,73 @@ pub(crate) struct CompiledBundleField {
pub(crate) ty: CompiledTypeLayout<CanonicalType>,
}
impl CompiledBundleField {
fn with_prefixed_debug_names(self, prefix: &str) -> Self {
let Self { offset, ty } = self;
Self {
offset,
ty: ty.with_prefixed_debug_names(prefix),
}
}
fn with_anonymized_debug_info(self) -> Self {
let Self { offset, ty } = self;
Self {
offset,
ty: ty.with_anonymized_debug_info(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub(crate) enum CompiledTypeLayoutBody {
Scalar,
PhantomConst,
Array {
/// debug names are ignored, use parent's layout instead
element: Interned<CompiledTypeLayout<CanonicalType>>,
/// always has at least one element even for zero-sized arrays
elements_non_empty: Interned<[CompiledTypeLayout<CanonicalType>]>,
},
Bundle {
/// debug names are ignored, use parent's layout instead
fields: Interned<[CompiledBundleField]>,
},
}
impl CompiledTypeLayoutBody {
fn with_prefixed_debug_names(self, prefix: &str) -> Self {
match self {
CompiledTypeLayoutBody::Scalar | CompiledTypeLayoutBody::PhantomConst => self,
CompiledTypeLayoutBody::Array { elements_non_empty } => CompiledTypeLayoutBody::Array {
elements_non_empty: elements_non_empty
.iter()
.map(|element| element.with_prefixed_debug_names(prefix))
.collect(),
},
CompiledTypeLayoutBody::Bundle { fields } => CompiledTypeLayoutBody::Bundle {
fields: fields
.iter()
.map(|field| field.with_prefixed_debug_names(prefix))
.collect(),
},
}
}
fn with_anonymized_debug_info(self) -> Self {
match self {
CompiledTypeLayoutBody::Scalar | CompiledTypeLayoutBody::PhantomConst => self,
CompiledTypeLayoutBody::Array { elements_non_empty } => CompiledTypeLayoutBody::Array {
elements_non_empty: elements_non_empty
.iter()
.map(|element| element.with_anonymized_debug_info())
.collect(),
},
CompiledTypeLayoutBody::Bundle { fields } => CompiledTypeLayoutBody::Bundle {
fields: fields
.iter()
.map(|field| field.with_anonymized_debug_info())
.collect(),
},
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub(crate) struct CompiledTypeLayout<T: Type> {
pub(crate) ty: T,
@ -108,7 +162,7 @@ impl<T: Type> CompiledTypeLayout<T> {
Self {
ty,
layout: layout.with_prefixed_debug_names(prefix),
body,
body: body.with_prefixed_debug_names(prefix),
}
}
fn with_anonymized_debug_info(self) -> Self {
@ -116,7 +170,7 @@ impl<T: Type> CompiledTypeLayout<T> {
Self {
ty,
layout: layout.with_anonymized_debug_info(),
body,
body: body.with_anonymized_debug_info(),
}
}
fn get(ty: T) -> Self {
@ -151,28 +205,29 @@ impl<T: Type> CompiledTypeLayout<T> {
}
CanonicalType::Array(array) => {
let mut layout = TypeLayout::empty();
let element = CompiledTypeLayout::get(array.element()).intern_sized();
let element = CompiledTypeLayout::get(array.element());
let mut elements_non_empty = vec![];
for index in 0..array.len() {
layout.allocate(
&element
.layout
.with_prefixed_debug_names(&format!("[{index}]")),
);
let element = element.with_prefixed_debug_names(&format!("[{index}]"));
layout.allocate(&element.layout);
elements_non_empty.push(element);
}
if array.is_empty() {
elements_non_empty.push(element.with_prefixed_debug_names("[<none>]"));
}
CompiledTypeLayout {
ty: *input,
layout: layout.into(),
body: CompiledTypeLayoutBody::Array { element },
}
}
CanonicalType::PhantomConst(_) => {
let unit_layout = CompiledTypeLayout::get(());
CompiledTypeLayout {
ty: *input,
layout: unit_layout.layout,
body: unit_layout.body,
body: CompiledTypeLayoutBody::Array {
elements_non_empty: elements_non_empty.intern_deref(),
},
}
}
CanonicalType::PhantomConst(_) => CompiledTypeLayout {
ty: *input,
layout: TypeLayout::empty(),
body: CompiledTypeLayoutBody::PhantomConst,
},
CanonicalType::Bundle(bundle) => {
let mut layout = TypeLayout::empty();
let fields = bundle
@ -184,13 +239,9 @@ impl<T: Type> CompiledTypeLayout<T> {
flipped: _,
ty,
}| {
let ty = CompiledTypeLayout::get(*ty);
let offset = layout
.allocate(
&ty.layout
.with_prefixed_debug_names(&format!(".{name}")),
)
.start();
let ty = CompiledTypeLayout::get(*ty)
.with_prefixed_debug_names(&format!(".{name}"));
let offset = layout.allocate(&ty.layout).start();
CompiledBundleField { offset, ty }
},
)
@ -273,6 +324,39 @@ impl<T: Type> CompiledValue<T> {
}
}
pub(crate) struct DebugCompiledValueStateAsMap<'a> {
pub(crate) compiled_value: CompiledValue<CanonicalType>,
pub(crate) state_layout: &'a interpreter::parts::StateLayout<InsnsBuildingDone>,
pub(crate) state: &'a interpreter::State,
}
impl fmt::Debug for DebugCompiledValueStateAsMap<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use fmt::Write;
if self.compiled_value.range.is_empty() {
return f.write_str("{}");
}
writeln!(f, "{{")?;
let mut f = PrefixLinesWrapper::new(f, true, " ");
macro_rules! debug_fmt {
(
type_plural_fields = [$($type_plural_field:ident,)*];
) => {
$(for slot in self.compiled_value.range.$type_plural_field.iter() {
slot.debug_fmt(&mut f, ":", " ", " ", "", Some(self.state_layout), Some(self.state))?;
writeln!(f, ",")?;
})*
};
}
get_state_part_kinds! {
debug_fmt! {
type_plural_fields;
}
}
write!(f.into_inner(), "}}")
}
}
impl CompiledValue<Bundle> {
fn field_by_index(self, field_index: usize) -> CompiledValue<CanonicalType> {
self.map(|layout, range| {
@ -301,10 +385,13 @@ impl CompiledValue<Bundle> {
impl CompiledValue<Array> {
pub(crate) fn element(self, index: usize) -> CompiledValue<CanonicalType> {
self.map(|layout, range| {
let CompiledTypeLayoutBody::Array { element } = layout.body else {
let CompiledTypeLayoutBody::Array { elements_non_empty } = layout.body else {
unreachable!();
};
(*element, range.index_array(element.layout.len(), index))
(
elements_non_empty[index],
range.index_array(elements_non_empty[index].layout.len(), index),
)
})
}
fn element_dyn(
@ -557,10 +644,11 @@ impl CompiledExpr<Array> {
self,
index_slot: StatePartIndex<StatePartKindSmallSlots>,
) -> CompiledExpr<CanonicalType> {
let CompiledTypeLayoutBody::Array { element } = self.static_part.layout.body else {
let CompiledTypeLayoutBody::Array { elements_non_empty } = self.static_part.layout.body
else {
unreachable!();
};
let stride = element.layout.len();
let stride = elements_non_empty[0].layout.len();
let indexes = self.indexes.join(TypeArrayIndex::from_parts(
index_slot,
self.static_part.layout.ty.len(),
@ -568,10 +656,10 @@ impl CompiledExpr<Array> {
));
CompiledExpr {
static_part: self.static_part.map(|layout, range| {
let CompiledTypeLayoutBody::Array { element } = layout.body else {
let CompiledTypeLayoutBody::Array { elements_non_empty } = layout.body else {
unreachable!();
};
(*element, range.index_array(stride, 0))
(elements_non_empty[0], range.index_array(stride, 0))
}),
indexes,
}
@ -1550,6 +1638,13 @@ struct ClockTrigger {
source_location: SourceLocation,
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) struct ExternModuleClockForPast {
pub(crate) clock_for_past: CompiledValue<Clock>,
pub(crate) current_to_past_map:
Interned<[(CompiledValue<CanonicalType>, CompiledValue<CanonicalType>)]>,
}
#[derive(Debug)]
struct Register {
value: CompiledValue<CanonicalType>,
@ -1619,7 +1714,7 @@ impl MakeTraceDeclTarget {
}
fn ty(self) -> CanonicalType {
match self {
MakeTraceDeclTarget::Expr(expr) => Expr::ty(expr),
MakeTraceDeclTarget::Expr(expr) => expr.ty(),
MakeTraceDeclTarget::Memory { ty, .. } => ty,
}
}
@ -1637,7 +1732,9 @@ impl<T> fmt::Debug for DebugOpaque<T> {
pub(crate) struct CompiledExternModule {
pub(crate) module_io_targets: Interned<[Target]>,
pub(crate) module_io: Interned<[CompiledValue<CanonicalType>]>,
pub(crate) clocks_for_past: Interned<[ExternModuleClockForPast]>,
pub(crate) simulation: ExternModuleSimulation,
pub(crate) debug_name: Interned<str>,
}
#[derive(Debug)]
@ -1681,18 +1778,23 @@ macro_rules! impl_compiler {
instantiated_module: InstantiatedModule,
target: MakeTraceDeclTarget,
source_location: SourceLocation,
empty_kind: impl FnOnce() -> SimTraceKind,
$($type_singular_field: impl FnOnce(StatePartIndex<$type_kind>) -> SimTraceKind,)*
) -> TraceLocation {
match target {
MakeTraceDeclTarget::Expr(target) => {
let compiled_value = self.compile_expr(instantiated_module, target);
let compiled_value = self.compiled_expr_to_value(compiled_value, source_location);
TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() {
$(Some(TypeLenSingle::$type_singular_variant) => {
$type_singular_field(compiled_value.range.$type_plural_field.start)
})*
None => unreachable!(),
}))
if compiled_value.range.is_empty() {
TraceLocation::Scalar(self.new_sim_trace(empty_kind()))
} else {
TraceLocation::Scalar(self.new_sim_trace(match compiled_value.range.len().as_single() {
$(Some(TypeLenSingle::$type_singular_variant) => {
$type_singular_field(compiled_value.range.$type_plural_field.start)
})*
None => unreachable!(),
}))
}
}
MakeTraceDeclTarget::Memory {
id,
@ -1723,9 +1825,10 @@ macro_rules! impl_compiler {
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallUInt { index, ty },
|index| SimTraceKind::BigUInt { index, ty },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
ty,
@ -1737,9 +1840,10 @@ macro_rules! impl_compiler {
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallSInt { index, ty },
|index| SimTraceKind::BigSInt { index, ty },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
ty,
@ -1751,9 +1855,10 @@ macro_rules! impl_compiler {
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallBool { index },
|index| SimTraceKind::BigBool { index },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
flow,
@ -1798,15 +1903,16 @@ macro_rules! impl_compiler {
}
.into()
}
CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(),
CanonicalType::Bundle(_) => unreachable!(),
CanonicalType::AsyncReset(_) => TraceAsyncReset {
location: self.make_trace_scalar_helper(
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallAsyncReset { index },
|index| SimTraceKind::BigAsyncReset { index },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
flow,
@ -1817,9 +1923,10 @@ macro_rules! impl_compiler {
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallSyncReset { index },
|index| SimTraceKind::BigSyncReset { index },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
flow,
@ -1831,21 +1938,38 @@ macro_rules! impl_compiler {
instantiated_module,
target,
source_location,
|| unreachable!(),
|index| SimTraceKind::SmallClock { index },
|index| SimTraceKind::BigClock { index },
|_| unreachable!(""),
|_| unreachable!(),
),
name,
flow,
}
.into(),
CanonicalType::PhantomConst(ty) => TracePhantomConst {
location: self.make_trace_scalar_helper(
instantiated_module,
target,
source_location,
|| SimTraceKind::PhantomConst { ty },
|_| unreachable!(),
|_| unreachable!(),
|_| unreachable!(),
),
name,
ty,
flow,
}
.into(),
CanonicalType::DynSimOnly(ty) => TraceSimOnly {
location: self.make_trace_scalar_helper(
instantiated_module,
target,
source_location,
|_| unreachable!(""),
|_| unreachable!(""),
|| unreachable!(),
|_| unreachable!(),
|_| unreachable!(),
|index| SimTraceKind::SimOnly { index, ty },
),
name,
@ -2295,16 +2419,10 @@ impl Compiler {
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_)
| CanonicalType::DynSimOnly(_) => {
| CanonicalType::DynSimOnly(_)
| CanonicalType::PhantomConst(_) => {
self.make_trace_scalar(instantiated_module, target, name, source_location)
}
CanonicalType::PhantomConst(_) => TraceBundle {
name,
fields: Interned::default(),
ty: Bundle::new(Interned::default()),
flow: target.flow(),
}
.into(),
}
}
fn make_trace_decl(
@ -2653,7 +2771,7 @@ impl Compiler {
let retval = parts
.into_iter()
.map(|part| part.cast_to_bits())
.reduce(|accumulator, part| accumulator | (part << Expr::ty(accumulator).width))
.reduce(|accumulator, part| accumulator | (part << accumulator.ty().width))
.unwrap_or_else(|| UInt[0].zero().to_expr());
let retval = self.compile_expr(instantiated_module, Expr::canonical(retval));
let retval = self
@ -2665,7 +2783,7 @@ impl Compiler {
instantiated_module: InstantiatedModule,
expr: ops::CastToBits,
) -> CompiledValue<UInt> {
match Expr::ty(expr.arg()) {
match expr.arg().ty() {
CanonicalType::UInt(_) => {
self.compile_cast_scalar_to_bits(instantiated_module, expr.arg(), |arg| arg)
}
@ -2831,7 +2949,7 @@ impl Compiler {
return retval;
}
let mut cast_bit = |arg: Expr<CanonicalType>| {
let src_signed = match Expr::ty(arg) {
let src_signed = match arg.ty() {
CanonicalType::UInt(_) => false,
CanonicalType::SInt(_) => true,
CanonicalType::Bool(_) => false,
@ -2845,7 +2963,7 @@ impl Compiler {
CanonicalType::PhantomConst(_) => unreachable!(),
CanonicalType::DynSimOnly(_) => unreachable!(),
};
let dest_signed = match Expr::ty(expr) {
let dest_signed = match expr.ty() {
CanonicalType::UInt(_) => false,
CanonicalType::SInt(_) => true,
CanonicalType::Bool(_) => false,
@ -2859,22 +2977,23 @@ impl Compiler {
CanonicalType::PhantomConst(_) => unreachable!(),
CanonicalType::DynSimOnly(_) => unreachable!(),
};
self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| {
match (src_signed, dest_signed) {
(false, false) | (true, true) => {
vec![Insn::Copy { dest, src }]
}
(false, true) => vec![Insn::CastToSInt {
dest,
src,
dest_width: 1,
}],
(true, false) => vec![Insn::CastToUInt {
dest,
src,
dest_width: 1,
}],
self.simple_nary_big_expr(instantiated_module, expr.ty(), [arg], |dest, [src]| match (
src_signed,
dest_signed,
) {
(false, false) | (true, true) => {
vec![Insn::Copy { dest, src }]
}
(false, true) => vec![Insn::CastToSInt {
dest,
src,
dest_width: 1,
}],
(true, false) => vec![Insn::CastToUInt {
dest,
src,
dest_width: 1,
}],
})
.into()
};
@ -2914,21 +3033,13 @@ impl Compiler {
})
.into(),
ExprEnum::PhantomConst(_) => self
.compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default())
.compile_aggregate_literal(instantiated_module, expr.ty(), Interned::default())
.into(),
ExprEnum::BundleLiteral(literal) => self
.compile_aggregate_literal(
instantiated_module,
Expr::ty(expr),
literal.field_values(),
)
.compile_aggregate_literal(instantiated_module, expr.ty(), literal.field_values())
.into(),
ExprEnum::ArrayLiteral(literal) => self
.compile_aggregate_literal(
instantiated_module,
Expr::ty(expr),
literal.element_values(),
)
.compile_aggregate_literal(instantiated_module, expr.ty(), literal.element_values())
.into(),
ExprEnum::EnumLiteral(expr) => {
let enum_bits_ty = UInt[expr.ty().type_properties().bit_width];
@ -2956,13 +3067,13 @@ impl Compiler {
ExprEnum::NotU(expr) => self
.simple_nary_big_expr(
instantiated_module,
Expr::ty(expr.arg()).canonical(),
expr.arg().ty().canonical(),
[Expr::canonical(expr.arg())],
|dest, [src]| {
vec![Insn::NotU {
dest,
src,
width: Expr::ty(expr.arg()).width(),
width: expr.arg().ty().width(),
}]
},
)
@ -2970,7 +3081,7 @@ impl Compiler {
ExprEnum::NotS(expr) => self
.simple_nary_big_expr(
instantiated_module,
Expr::ty(expr.arg()).canonical(),
expr.arg().ty().canonical(),
[Expr::canonical(expr.arg())],
|dest, [src]| vec![Insn::NotS { dest, src }],
)
@ -2978,7 +3089,7 @@ impl Compiler {
ExprEnum::NotB(expr) => self
.simple_nary_big_expr(
instantiated_module,
Expr::ty(expr.arg()).canonical(),
expr.arg().ty().canonical(),
[Expr::canonical(expr.arg())],
|dest, [src]| {
vec![Insn::NotU {
@ -3482,12 +3593,12 @@ impl Compiler {
.map_ty(Bundle::from_canonical)
.field_by_index(expr.field_index()),
ExprEnum::VariantAccess(variant_access) => {
let start = Expr::ty(variant_access.base()).discriminant_bit_width();
let len = Expr::ty(expr).bit_width();
let start = variant_access.base().ty().discriminant_bit_width();
let len = expr.ty().bit_width();
self.compile_expr(
instantiated_module,
variant_access.base().cast_to_bits()[start..start + len]
.cast_bits_to(Expr::ty(expr)),
.cast_bits_to(expr.ty()),
)
}
ExprEnum::ArrayIndex(expr) => self
@ -3509,45 +3620,39 @@ impl Compiler {
.map_ty(Array::from_canonical)
.element_dyn(index_slot)
}
ExprEnum::ReduceBitAndU(expr) => if Expr::ty(expr.arg()).width() == 0 {
ExprEnum::ReduceBitAndU(expr) => if expr.arg().ty().width() == 0 {
self.compile_expr(instantiated_module, Expr::canonical(true.to_expr()))
} else {
self.compile_expr(
instantiated_module,
Expr::canonical(
expr.arg()
.cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)),
),
Expr::canonical(expr.arg().cmp_eq(expr.arg().ty().from_int_wrapping(-1))),
)
}
.into(),
ExprEnum::ReduceBitAndS(expr) => if Expr::ty(expr.arg()).width() == 0 {
ExprEnum::ReduceBitAndS(expr) => if expr.arg().ty().width() == 0 {
self.compile_expr(instantiated_module, Expr::canonical(true.to_expr()))
} else {
self.compile_expr(
instantiated_module,
Expr::canonical(
expr.arg()
.cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)),
),
Expr::canonical(expr.arg().cmp_eq(expr.arg().ty().from_int_wrapping(-1))),
)
}
.into(),
ExprEnum::ReduceBitOrU(expr) => if Expr::ty(expr.arg()).width() == 0 {
ExprEnum::ReduceBitOrU(expr) => if expr.arg().ty().width() == 0 {
self.compile_expr(instantiated_module, Expr::canonical(false.to_expr()))
} else {
self.compile_expr(
instantiated_module,
Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))),
Expr::canonical(expr.arg().cmp_ne(expr.arg().ty().from_int_wrapping(0))),
)
}
.into(),
ExprEnum::ReduceBitOrS(expr) => if Expr::ty(expr.arg()).width() == 0 {
ExprEnum::ReduceBitOrS(expr) => if expr.arg().ty().width() == 0 {
self.compile_expr(instantiated_module, Expr::canonical(false.to_expr()))
} else {
self.compile_expr(
instantiated_module,
Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))),
Expr::canonical(expr.arg().cmp_ne(expr.arg().ty().from_int_wrapping(0))),
)
}
.into(),
@ -3560,7 +3665,7 @@ impl Compiler {
vec![Insn::ReduceBitXor {
dest,
src,
input_width: Expr::ty(expr.arg()).width(),
input_width: expr.arg().ty().width(),
}]
},
)
@ -3574,7 +3679,7 @@ impl Compiler {
vec![Insn::ReduceBitXor {
dest,
src,
input_width: Expr::ty(expr.arg()).width(),
input_width: expr.arg().ty().width(),
}]
},
)
@ -3672,8 +3777,8 @@ impl Compiler {
mut rhs: Expr<CanonicalType>,
source_location: SourceLocation,
) {
if Expr::ty(lhs) != Expr::ty(rhs) || !Expr::ty(lhs).is_passive() {
match Expr::ty(lhs) {
if lhs.ty() != rhs.ty() || !lhs.ty().is_passive() {
match lhs.ty() {
CanonicalType::UInt(lhs_ty) => {
rhs = Expr::canonical(Expr::<UInt>::from_canonical(rhs).cast_to(lhs_ty));
}
@ -3682,7 +3787,7 @@ impl Compiler {
}
CanonicalType::Bool(_) => unreachable!(),
CanonicalType::Array(lhs_ty) => {
let CanonicalType::Array(rhs_ty) = Expr::ty(rhs) else {
let CanonicalType::Array(rhs_ty) = rhs.ty() else {
unreachable!();
};
assert_eq!(lhs_ty.len(), rhs_ty.len());
@ -3702,13 +3807,13 @@ impl Compiler {
return;
}
CanonicalType::Enum(lhs_ty) => {
let CanonicalType::Enum(rhs_ty) = Expr::ty(rhs) else {
let CanonicalType::Enum(rhs_ty) = rhs.ty() else {
unreachable!();
};
todo!("handle connect with different enum types");
}
CanonicalType::Bundle(lhs_ty) => {
let CanonicalType::Bundle(rhs_ty) = Expr::ty(rhs) else {
let CanonicalType::Bundle(rhs_ty) = rhs.ty() else {
unreachable!();
};
assert_eq!(lhs_ty.fields().len(), rhs_ty.fields().len());
@ -3896,18 +4001,15 @@ impl Compiler {
self.enum_discriminants.insert(enum_value, retval);
retval
}
fn compile_stmt_reg<R: ResetType>(
fn compile_reg<R: ResetType>(
&mut self,
stmt_reg: StmtReg<R>,
clk: CompiledValue<Clock>,
reset_and_init: Option<(Expr<R>, CompiledValue<CanonicalType>)>,
source_location: SourceLocation,
instantiated_module: InstantiatedModule,
value: CompiledValue<CanonicalType>,
) {
let StmtReg { annotations, reg } = stmt_reg;
let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk));
let clk = self
.compiled_expr_to_value(clk, reg.source_location())
.map_ty(Clock::from_canonical);
let clk = self.compile_clock(clk, reg.source_location());
let clk = self.compile_clock(clk, source_location);
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = ();
@ -3926,18 +4028,15 @@ impl Compiler {
true
}
}
let reset = if let Some(init) = reg.init() {
let init = self.compile_expr(instantiated_module, init);
let init = self.compiled_expr_to_value(init, reg.source_location());
let rst =
self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().rst));
let rst = self.compiled_expr_to_value(rst, reg.source_location());
let rst = self.compiled_value_bool_dest_is_small(rst, reg.source_location());
let reset = if let Some((rst_expr, init)) = reset_and_init {
let rst = self.compile_expr(instantiated_module, Expr::canonical(rst_expr));
let rst = self.compiled_expr_to_value(rst, source_location);
let rst = self.compiled_value_bool_dest_is_small(rst, source_location);
let is_async = R::dispatch((), Dispatch);
if is_async {
let cond = Expr::canonical(reg.clock_domain().rst.cast_to(Bool));
let cond = Expr::canonical(rst_expr.cast_to(Bool));
let cond = self.compile_expr(instantiated_module, cond);
let cond = self.compiled_expr_to_value(cond, reg.source_location());
let cond = self.compiled_expr_to_value(cond, source_location);
let cond = cond.map_ty(Bool::from_canonical);
// write to the register's current value since asynchronous reset is combinational
let lhs = CompiledValue {
@ -3949,12 +4048,12 @@ impl Compiler {
self.compile_simple_connect(
[Cond {
body: CondBody::IfTrue { cond },
source_location: reg.source_location(),
}][..]
.intern(),
source_location: source_location,
}]
.intern_slice(),
lhs,
init,
reg.source_location(),
source_location,
);
}
Some(RegisterReset {
@ -3969,9 +4068,33 @@ impl Compiler {
value,
clk_triggered: clk.clk_triggered,
reset,
source_location: reg.source_location(),
source_location,
});
}
fn compile_stmt_reg<R: ResetType>(
&mut self,
stmt_reg: StmtReg<R>,
instantiated_module: InstantiatedModule,
value: CompiledValue<CanonicalType>,
) {
let StmtReg { annotations, reg } = stmt_reg;
let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk));
let clk = self
.compiled_expr_to_value(clk, reg.source_location())
.map_ty(Clock::from_canonical);
let reset_and_init = reg.init().map(|init| {
let init = self.compile_expr(instantiated_module, init);
let init = self.compiled_expr_to_value(init, reg.source_location());
(reg.clock_domain().rst, init)
});
self.compile_reg(
clk,
reset_and_init,
reg.source_location(),
instantiated_module,
value,
);
}
fn compile_declaration(
&mut self,
declaration: StmtDeclaration,
@ -4237,24 +4360,24 @@ impl Compiler {
insns.push(end_label.into());
}
}
CompiledTypeLayoutBody::Array { element } => {
CompiledTypeLayoutBody::Array { elements_non_empty } => {
let CompiledTypeLayoutBody::Array {
element: mask_element,
elements_non_empty: mask_elements_non_empty,
} = mask_layout.body
else {
unreachable!();
};
let ty = <Array>::from_canonical(data_layout.ty);
let element_bit_width = ty.element().bit_width();
let element_size = element.layout.len();
let mask_element_size = mask_element.layout.len();
let element_size = elements_non_empty[0].layout.len();
let mask_element_size = mask_elements_non_empty[0].layout.len();
for element_index in 0..ty.len() {
self.compile_memory_port_rw_helper(
memory,
stride,
start,
*element,
*mask_element,
elements_non_empty[element_index],
mask_elements_non_empty[element_index],
read.as_mut().map(
|MemoryPortReadInsns {
addr,
@ -4293,6 +4416,7 @@ impl Compiler {
start += element_bit_width;
}
}
CompiledTypeLayoutBody::PhantomConst => {}
CompiledTypeLayoutBody::Bundle { fields } => {
let CompiledTypeLayoutBody::Bundle {
fields: mask_fields,
@ -4860,6 +4984,88 @@ impl Compiler {
}
}
}
fn compile_extern_module_clock_for_past(
&mut self,
instantiated_module: InstantiatedModule,
clock_for_past: Target,
) -> ExternModuleClockForPast {
let clock_for_past = TargetInInstantiatedModule {
instantiated_module,
target: clock_for_past,
};
let clock_for_past = self
.compile_value(clock_for_past)
.map_ty(Clock::from_canonical);
let clock_for_past_debug_name = match clock_for_past
.range
.len()
.as_single()
.expect("Clock is a single slot")
{
TypeLenSingle::BigSlot => {
self.insns
.state_layout
.ty
.big_slots
.debug_data(clock_for_past.range.start().big_slots)
.name
}
TypeLenSingle::SmallSlot => {
self.insns
.state_layout
.ty
.small_slots
.debug_data(clock_for_past.range.start().small_slots)
.name
}
TypeLenSingle::SimOnlySlot => {
unreachable!()
}
};
let module_prefix = format!("{instantiated_module:?}.");
let trimmed_clock_for_past_debug_name = clock_for_past_debug_name
.strip_prefix(&module_prefix)
.unwrap_or(&clock_for_past_debug_name);
let current_to_past_map = instantiated_module
.leaf_module()
.module_io()
.iter()
.map(
|&AnnotatedModuleIO {
annotations: _,
module_io,
}| {
let target_base = TargetBase::from(module_io);
let current = self.compile_value(TargetInInstantiatedModule {
instantiated_module,
target: target_base.into(),
});
let unprefixed_layout = CompiledTypeLayout::get(module_io.ty());
let past_layout = unprefixed_layout.with_prefixed_debug_names(&format!(
"{module_prefix}{:?}$past({trimmed_clock_for_past_debug_name})",
target_base.target_name(),
));
let past = CompiledValue {
range: self.insns.allocate_variable(&past_layout.layout),
layout: past_layout,
write: Some((current.layout, current.range)),
};
self.compile_reg::<SyncReset>(
clock_for_past,
None,
module_io.source_location(),
instantiated_module,
past,
);
(current, past)
},
)
.collect();
ExternModuleClockForPast {
clock_for_past,
current_to_past_map,
}
}
fn compile_module(&mut self, module: Interned<InstantiatedModule>) -> &CompiledModule {
let mut trace_decls = Vec::new();
let module_io = module
@ -4888,6 +5094,7 @@ impl Compiler {
ModuleBody::Extern(ExternModuleBody {
verilog_name: _,
parameters: _,
clocks_for_past,
simulation,
}) => {
let Some(simulation) = simulation else {
@ -4904,10 +5111,18 @@ impl Compiler {
Target::from(*simulation.sim_io_to_generator_map[&v.module_io.intern()])
})
.collect();
let clocks_for_past = clocks_for_past
.iter()
.map(|clock_for_past| {
self.compile_extern_module_clock_for_past(*module, *clock_for_past)
})
.collect();
self.extern_modules.push(CompiledExternModule {
module_io_targets,
module_io,
clocks_for_past,
simulation,
debug_name: format!("{module:?}").intern_deref(),
});
}
}

View file

@ -2,6 +2,7 @@
// See Notices.txt for copyright information
use crate::{
expr::ValueType,
int::{BoolOrIntType, SInt, UInt},
intern::{Intern, Interned, Memoize},
sim::interpreter::parts::{
@ -196,13 +197,27 @@ impl fmt::Debug for Insn {
}
}
struct PrefixLinesWrapper<'a, W> {
pub(crate) struct PrefixLinesWrapper<'a, W> {
writer: W,
at_beginning_of_line: bool,
blank_line_prefix: &'a str,
line_prefix: &'a str,
}
impl<'a, W> PrefixLinesWrapper<'a, W> {
pub(crate) fn new(writer: W, at_beginning_of_line: bool, line_prefix: &'a str) -> Self {
Self {
writer,
at_beginning_of_line,
blank_line_prefix: line_prefix.trim_end(),
line_prefix,
}
}
pub(crate) fn into_inner(self) -> W {
self.writer
}
}
impl<T: fmt::Write> fmt::Write for PrefixLinesWrapper<'_, T> {
fn write_str(&mut self, input: &str) -> fmt::Result {
for part in input.split_inclusive('\n') {
@ -239,12 +254,7 @@ impl Insn {
if fields.len() == 0 {
return Ok(());
}
let mut f = PrefixLinesWrapper {
writer: f,
at_beginning_of_line: false,
blank_line_prefix: "",
line_prefix: " ",
};
let mut f = PrefixLinesWrapper::new(f, false, " ");
writeln!(f, " {{")?;
for (field_name, field) in fields {
write!(f, "{field_name}: ")?;
@ -320,7 +330,7 @@ impl Insn {
}
writeln!(f, ",")?;
}
write!(f.writer, "}}")
write!(f.into_inner(), "}}")
}
}

View file

@ -9,7 +9,7 @@ use crate::{
Insn, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, PrefixLinesWrapper,
SmallSInt, SmallUInt, State,
},
value::{DynSimOnlyValue, DynSimOnly},
value::{DynSimOnly, DynSimOnlyValue},
},
ty::CanonicalType,
util::{chain, const_str_cmp},
@ -435,12 +435,7 @@ impl<K: StatePartKind> StatePartIndex<K> {
if state.is_some() || debug_data.is_some() {
f.write_str(comment_start)?;
}
let mut f = PrefixLinesWrapper {
writer: f,
at_beginning_of_line: false,
blank_line_prefix: comment_line_start.trim_end(),
line_prefix: comment_line_start,
};
let mut f = PrefixLinesWrapper::new(f, false, comment_line_start);
if let Some(state) = state {
f.write_str("(")?;
K::debug_fmt_state_value(state, *self, &mut f)?;
@ -453,7 +448,7 @@ impl<K: StatePartKind> StatePartIndex<K> {
write!(f, "{debug_data:?}")?;
}
if state.is_some() || debug_data.is_some() {
f.writer.write_str(comment_end)?;
f.into_inner().write_str(comment_end)?;
}
Ok(())
}

View file

@ -6,7 +6,10 @@ use crate::{
bundle::{Bundle, BundleType},
clock::Clock,
enum_::{Enum, EnumType},
expr::{CastBitsTo, Expr, ToExpr},
expr::{
CastBitsTo, Expr, HdlPartialEq, HdlPartialEqImpl, HdlPartialOrd, ToExpr, ValueType,
value_category::{ValueCategorySimValue, ValueCategoryValue},
},
int::{Bool, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue},
reset::{AsyncReset, Reset, SyncReset},
source_location::SourceLocation,
@ -23,10 +26,11 @@ use bitvec::{slice::BitSlice, vec::BitVec};
use hashbrown::hash_map::Entry;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error as _};
use std::{
borrow::Cow,
borrow::{Borrow, BorrowMut, Cow},
fmt::{self, Write},
hash::{BuildHasher, Hash, Hasher, RandomState},
ops::{Deref, DerefMut},
num::NonZero,
ops::{Deref, DerefMut, Index, IndexMut},
sync::{Arc, Mutex},
};
@ -136,7 +140,7 @@ impl<T: Type<SimValue: Serialize> + Serialize> Serialize for SimValue<T> {
S: Serializer,
{
SerdeSimValue {
ty: SimValue::ty(self),
ty: self.ty(),
value: std::borrow::Cow::Borrowed(&*self),
}
.serialize(serializer)
@ -157,7 +161,22 @@ pub struct SimValue<T: Type> {
inner: AlternatingCell<SimValueInner<T>>,
}
impl<T: Type + Clone> Clone for SimValue<T> {
impl<T: Type> ValueType for SimValue<T> {
type Type = T;
type ValueCategory = ValueCategorySimValue;
fn ty(&self) -> Self::Type {
self.inner.share().ty
}
}
impl<T: Type<SimValue: fmt::Display>> fmt::Display for SimValue<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
T::SimValue::fmt(self, f)
}
}
impl<T: Type> Clone for SimValue<T> {
fn clone(&self) -> Self {
Self {
inner: AlternatingCell::new_unique(self.inner.share().clone()),
@ -212,9 +231,6 @@ impl<T: Type> SimValue<T> {
inner: AlternatingCell::new_unique(inner),
}
}
pub fn ty(this: &Self) -> T {
this.inner.share().ty
}
pub fn into_opaque(this: Self) -> OpaqueSimValue {
this.inner.into_inner().into_opaque()
}
@ -253,7 +269,7 @@ impl<T: Type> SimValue<T> {
SimValue::from_opaque(ty.canonical(), opaque)
}
pub fn canonical(this: &Self) -> SimValue<CanonicalType> {
SimValue::from_opaque(Self::ty(this).canonical(), Self::opaque(this).clone())
SimValue::from_opaque(this.ty().canonical(), Self::opaque(this).clone())
}
#[track_caller]
pub fn from_dyn_int(v: SimValue<T::Dyn>) -> Self
@ -274,7 +290,7 @@ impl<T: Type> SimValue<T> {
where
T: IntType,
{
SimValue::from_opaque(Self::ty(this).as_dyn_int(), Self::opaque(&this).clone())
SimValue::from_opaque(this.ty().as_dyn_int(), Self::opaque(&this).clone())
}
#[track_caller]
pub fn from_bundle(v: SimValue<Bundle>) -> Self
@ -296,7 +312,7 @@ impl<T: Type> SimValue<T> {
T: BundleType,
{
SimValue::from_opaque(
Bundle::from_canonical(Self::ty(this).canonical()),
Bundle::from_canonical(this.ty().canonical()),
Self::opaque(&this).clone(),
)
}
@ -320,7 +336,7 @@ impl<T: Type> SimValue<T> {
T: EnumType,
{
SimValue::from_opaque(
Enum::from_canonical(Self::ty(this).canonical()),
Enum::from_canonical(this.ty().canonical()),
Self::opaque(&this).clone(),
)
}
@ -351,8 +367,6 @@ impl<T: Type> fmt::Debug for SimValue<T> {
}
impl<T: Type> ToExpr for SimValue<T> {
type Type = T;
#[track_caller]
fn to_expr(&self) -> Expr<Self::Type> {
let inner = self.inner.share();
@ -360,43 +374,297 @@ impl<T: Type> ToExpr for SimValue<T> {
inner.sim_only_values_len, 0,
"can't convert sim-only values to Expr"
);
inner.opaque.bits().cast_bits_to(inner.ty)
inner.opaque.bits().to_expr().cast_bits_to(inner.ty)
}
}
pub trait SimValuePartialEq<T: Type = Self>: Type {
#[track_caller]
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<T>) -> bool;
impl<T: Type, Len: Size, I> Index<I> for SimValue<ArrayType<T, Len>>
where
[SimValue<T>]: Index<I>,
{
type Output = <[SimValue<T>] as Index<I>>::Output;
fn index(&self, index: I) -> &Self::Output {
(**self).borrow().index(index)
}
}
impl<T: SimValuePartialEq<U>, U: Type> PartialEq<SimValue<U>> for SimValue<T> {
impl<T: Type, Len: Size, I> IndexMut<I> for SimValue<ArrayType<T, Len>>
where
[SimValue<T>]: IndexMut<I>,
{
fn index_mut(&mut self, index: I) -> &mut Self::Output {
(**self).borrow_mut().index_mut(index)
}
}
impl<T: Type, Len: Size> SimValue<ArrayType<T, Len>> {
pub fn iter(&self) -> std::slice::Iter<'_, SimValue<T>> {
(**self).borrow().iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, SimValue<T>> {
(**self).borrow_mut().iter_mut()
}
}
impl<T: Type, Len: Size> IntoIterator for SimValue<ArrayType<T, Len>> {
type Item = SimValue<T>;
type IntoIter = std::vec::IntoIter<SimValue<T>>;
fn into_iter(self) -> Self::IntoIter {
Vec::into_iter(Self::into_value(self).into())
}
}
impl<'a, T: Type, Len: Size> IntoIterator for &'a SimValue<ArrayType<T, Len>> {
type Item = &'a SimValue<T>;
type IntoIter = std::slice::Iter<'a, SimValue<T>>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T: Type, Len: Size> IntoIterator for &'a mut SimValue<ArrayType<T, Len>> {
type Item = &'a mut SimValue<T>;
type IntoIter = std::slice::IterMut<'a, SimValue<T>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T: Type, Len: Size> AsRef<[SimValue<T>]> for SimValue<ArrayType<T, Len>> {
fn as_ref(&self) -> &[SimValue<T>] {
(**self).as_ref()
}
}
impl<T: Type, Len: Size> AsMut<[SimValue<T>]> for SimValue<ArrayType<T, Len>> {
fn as_mut(&mut self) -> &mut [SimValue<T>] {
(**self).as_mut()
}
}
impl<T: Type, U: Type> PartialEq<SimValue<U>> for SimValue<T>
where
Self: for<'a> HdlPartialEq<&'a SimValue<U>, Output = SimValue<Bool>>,
{
#[track_caller]
fn eq(&self, other: &SimValue<U>) -> bool {
T::sim_value_eq(self, other)
*self.cmp_eq(other)
}
#[track_caller]
fn ne(&self, other: &SimValue<U>) -> bool {
*self.cmp_ne(other)
}
}
impl<Width: Size> SimValuePartialEq<Self> for UIntType<Width> {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Self>) -> bool {
**this == **other
pub trait SimValueEq: Type
where
for<'a> SimValue<Self>: HdlPartialEq<&'a SimValue<Self>, Output = SimValue<Bool>>,
{
}
impl<T: SimValueEq> Eq for SimValue<T> where
for<'a> SimValue<T>: HdlPartialEq<&'a SimValue<T>, Output = SimValue<Bool>>
{
}
impl<T: Type, U: Type> PartialOrd<SimValue<U>> for SimValue<T>
where
Self: for<'a> HdlPartialOrd<&'a SimValue<U>, Output = SimValue<Bool>>,
{
#[track_caller]
fn partial_cmp(&self, other: &SimValue<U>) -> Option<std::cmp::Ordering> {
if *self.cmp_eq(other) {
Some(std::cmp::Ordering::Equal)
} else if *self.cmp_lt(other) {
Some(std::cmp::Ordering::Less)
} else if *self.cmp_gt(other) {
Some(std::cmp::Ordering::Greater)
} else {
None
}
}
#[track_caller]
fn lt(&self, other: &SimValue<U>) -> bool {
*self.cmp_lt(other)
}
#[track_caller]
fn le(&self, other: &SimValue<U>) -> bool {
*self.cmp_le(other)
}
#[track_caller]
fn gt(&self, other: &SimValue<U>) -> bool {
*self.cmp_gt(other)
}
#[track_caller]
fn ge(&self, other: &SimValue<U>) -> bool {
*self.cmp_ge(other)
}
}
impl<Width: Size> SimValuePartialEq<Self> for SIntType<Width> {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Self>) -> bool {
**this == **other
pub trait SimValueOrd: SimValueEq
where
for<'a> SimValue<Self>: HdlPartialOrd<&'a SimValue<Self>, Output = SimValue<Bool>>,
{
}
impl<T: SimValueOrd> Ord for SimValue<T>
where
for<'a> SimValue<T>: HdlPartialOrd<&'a SimValue<T>, Output = SimValue<Bool>>,
{
#[track_caller]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if *self.cmp_eq(other) {
std::cmp::Ordering::Equal
} else if *self.cmp_lt(other) {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
}
}
impl SimValuePartialEq<Bool> for Bool {
fn sim_value_eq(this: &SimValue<Self>, other: &SimValue<Bool>) -> bool {
**this == **other
impl<Width: Size> SimValueEq for UIntType<Width> {}
impl<Width: Size> SimValueOrd for UIntType<Width> {}
impl<Width: Size> SimValueEq for SIntType<Width> {}
impl<Width: Size> SimValueOrd for SIntType<Width> {}
macro_rules! impl_sim_value_cmp_as_bool {
($ty:ident) => {
impl SimValueEq for $ty {}
impl SimValueOrd for $ty {}
};
}
impl_sim_value_cmp_as_bool!(Bool);
impl_sim_value_cmp_as_bool!(Clock);
impl_sim_value_cmp_as_bool!(Reset);
impl_sim_value_cmp_as_bool!(SyncReset);
impl_sim_value_cmp_as_bool!(AsyncReset);
#[doc(hidden)]
pub mod match_sim_value {
use crate::{
sim::value::{SimValue, ToSimValue},
ty::Type,
};
#[doc(hidden)]
pub struct MatchSimValueHelper<T>(Option<T>);
impl<T> MatchSimValueHelper<T> {
pub fn new(v: T) -> Self {
Self(Some(v))
}
}
#[doc(hidden)]
pub trait MatchSimValue {
type MatchValue;
/// use `self` so it comes first in the method resolution order
fn __fayalite_match_sim_value(self) -> Self::MatchValue
where
Self: Sized;
}
impl<T: Type> MatchSimValue for MatchSimValueHelper<SimValue<T>> {
type MatchValue = T::SimValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
SimValue::into_value(self.0.expect("should be Some"))
}
}
impl<'a, T: Type> MatchSimValue for MatchSimValueHelper<&'a SimValue<T>> {
type MatchValue = &'a T::SimValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
SimValue::value(self.0.expect("should be Some"))
}
}
impl<'a, T: Type> MatchSimValue for MatchSimValueHelper<&'a mut SimValue<T>> {
type MatchValue = &'a mut T::SimValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
SimValue::value_mut(self.0.expect("should be Some"))
}
}
impl<'a, T> MatchSimValue for MatchSimValueHelper<&'_ &'a T>
where
MatchSimValueHelper<&'a T>: MatchSimValue,
{
type MatchValue = <MatchSimValueHelper<&'a T> as MatchSimValue>::MatchValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
MatchSimValue::__fayalite_match_sim_value(MatchSimValueHelper(self.0.map(|v| *v)))
}
}
impl<'a, T> MatchSimValue for MatchSimValueHelper<&'_ mut &'a T>
where
MatchSimValueHelper<&'a T>: MatchSimValue,
{
type MatchValue = <MatchSimValueHelper<&'a T> as MatchSimValue>::MatchValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
MatchSimValue::__fayalite_match_sim_value(MatchSimValueHelper(self.0.map(|v| *v)))
}
}
impl<'a, T> MatchSimValue for MatchSimValueHelper<&'a &'_ mut T>
where
MatchSimValueHelper<&'a T>: MatchSimValue,
{
type MatchValue = <MatchSimValueHelper<&'a T> as MatchSimValue>::MatchValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
MatchSimValue::__fayalite_match_sim_value(MatchSimValueHelper(self.0.map(|v| &**v)))
}
}
impl<'a, T> MatchSimValue for MatchSimValueHelper<&'a mut &'_ mut T>
where
MatchSimValueHelper<&'a mut T>: MatchSimValue,
{
type MatchValue = <MatchSimValueHelper<&'a mut T> as MatchSimValue>::MatchValue;
fn __fayalite_match_sim_value(self) -> Self::MatchValue {
MatchSimValue::__fayalite_match_sim_value(MatchSimValueHelper(self.0.map(|v| &mut **v)))
}
}
#[doc(hidden)]
pub trait MatchSimValueFallback {
type MatchValue;
/// use `&mut self` so it comes later in the method resolution order than MatchSimValue
fn __fayalite_match_sim_value(&mut self) -> Self::MatchValue;
}
impl<T: ToSimValue> MatchSimValueFallback for MatchSimValueHelper<T> {
type MatchValue = <T::Type as Type>::SimValue;
fn __fayalite_match_sim_value(&mut self) -> Self::MatchValue {
SimValue::into_value(self.0.take().expect("should be Some").into_sim_value())
}
}
}
pub trait ToSimValue: ToSimValueWithType<<Self as ToSimValue>::Type> {
type Type: Type;
pub trait ToSimValue: ToSimValueWithType<<Self as ValueType>::Type> + ValueType {
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self::Type>;
#[track_caller]
@ -438,31 +706,31 @@ pub trait ToSimValueWithType<T: Type> {
macro_rules! forward_to_sim_value_with_type {
([$($generics:tt)*] $ty:ty) => {
impl<$($generics)*> ToSimValueWithType<<Self as ToSimValue>::Type> for $ty {
fn to_sim_value_with_type(&self, ty: <Self as ToSimValue>::Type) -> SimValue<<Self as ToSimValue>::Type> {
impl<$($generics)*> ToSimValueWithType<<Self as ValueType>::Type> for $ty {
fn to_sim_value_with_type(&self, ty: <Self as ValueType>::Type) -> SimValue<<Self as ValueType>::Type> {
let retval = Self::to_sim_value(self);
assert_eq!(SimValue::ty(&retval), ty);
assert_eq!(retval.ty(), ty);
retval
}
#[track_caller]
fn into_sim_value_with_type(self, ty: <Self as ToSimValue>::Type) -> SimValue<<Self as ToSimValue>::Type>
fn into_sim_value_with_type(self, ty: <Self as ValueType>::Type) -> SimValue<<Self as ValueType>::Type>
where
Self: Sized,
{
let retval = Self::into_sim_value(self);
assert_eq!(SimValue::ty(&retval), ty);
assert_eq!(retval.ty(), ty);
retval
}
#[track_caller]
fn arc_into_sim_value_with_type(self: Arc<Self>, ty: <Self as ToSimValue>::Type) -> SimValue<<Self as ToSimValue>::Type> {
fn arc_into_sim_value_with_type(self: Arc<Self>, ty: <Self as ValueType>::Type) -> SimValue<<Self as ValueType>::Type> {
let retval = Self::arc_into_sim_value(self);
assert_eq!(SimValue::ty(&retval), ty);
assert_eq!(retval.ty(), ty);
retval
}
#[track_caller]
fn arc_to_sim_value_with_type(self: &Arc<Self>, ty: <Self as ToSimValue>::Type) -> SimValue<<Self as ToSimValue>::Type> {
fn arc_to_sim_value_with_type(self: &Arc<Self>, ty: <Self as ValueType>::Type) -> SimValue<<Self as ValueType>::Type> {
let retval = Self::arc_to_sim_value(self);
assert_eq!(SimValue::ty(&retval), ty);
assert_eq!(retval.ty(), ty);
retval
}
}
@ -470,7 +738,6 @@ macro_rules! forward_to_sim_value_with_type {
}
impl<T: Type> ToSimValue for SimValue<T> {
type Type = T;
fn to_sim_value(&self) -> SimValue<Self::Type> {
self.clone()
}
@ -526,9 +793,7 @@ impl<T: Type> ToSimValueWithType<T> for BitSlice {
}
}
impl<This: ?Sized + ToSimValue> ToSimValue for &'_ This {
type Type = This::Type;
impl<'a, This: ?Sized + ToSimValue> ToSimValue for &'a This {
fn to_sim_value(&self) -> SimValue<Self::Type> {
This::to_sim_value(self)
}
@ -541,8 +806,6 @@ impl<This: ?Sized + ToSimValueWithType<T>, T: Type> ToSimValueWithType<T> for &'
}
impl<This: ?Sized + ToSimValue> ToSimValue for &'_ mut This {
type Type = This::Type;
fn to_sim_value(&self) -> SimValue<Self::Type> {
This::to_sim_value(self)
}
@ -555,8 +818,6 @@ impl<This: ?Sized + ToSimValueWithType<T>, T: Type> ToSimValueWithType<T> for &'
}
impl<This: ?Sized + ToSimValue> ToSimValue for Arc<This> {
type Type = This::Type;
fn to_sim_value(&self) -> SimValue<Self::Type> {
This::arc_to_sim_value(self)
}
@ -577,7 +838,6 @@ impl<This: ?Sized + ToSimValueWithType<T>, T: Type> ToSimValueWithType<T> for Ar
impl<This: ?Sized + ToSimValue + Send + Sync + 'static> ToSimValue
for crate::intern::Interned<This>
{
type Type = This::Type;
fn to_sim_value(&self) -> SimValue<Self::Type> {
This::to_sim_value(self)
}
@ -592,8 +852,6 @@ impl<This: ?Sized + ToSimValueWithType<T> + Send + Sync + 'static, T: Type> ToSi
}
impl<This: ToSimValue> ToSimValue for Box<This> {
type Type = This::Type;
fn to_sim_value(&self) -> SimValue<Self::Type> {
This::to_sim_value(self)
}
@ -636,8 +894,6 @@ impl<Element: ToSimValueWithType<T>, T: Type> ToSimValueWithType<Array<T>> for [
}
impl<Element: ToSimValue<Type: StaticType>> ToSimValue for [Element] {
type Type = Array<Element::Type>;
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self)
@ -673,8 +929,6 @@ impl<Element: ToSimValue<Type: StaticType>, const N: usize> ToSimValue for [Elem
where
ConstUsize<N>: KnownSize,
{
type Type = Array<Element::Type, N>;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_array_elements(StaticType::TYPE, self)
}
@ -728,8 +982,6 @@ impl<Element: ToSimValueWithType<T>, T: Type> ToSimValueWithType<Array<T>> for V
}
impl<Element: ToSimValue<Type: StaticType>> ToSimValue for Vec<Element> {
type Type = Array<Element::Type>;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self)
}
@ -770,8 +1022,6 @@ impl<Element: ToSimValueWithType<T>, T: Type> ToSimValueWithType<Array<T>> for B
}
impl<Element: ToSimValue<Type: StaticType>> ToSimValue for Box<[Element]> {
type Type = Array<Element::Type>;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_array_elements(ArrayType::new_dyn(StaticType::TYPE, self.len()), self)
}
@ -801,11 +1051,10 @@ impl<Element: ToSimValueWithType<CanonicalType>> ToSimValueWithType<CanonicalTyp
}
impl<T: Type> ToSimValue for Expr<T> {
type Type = T;
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_bitslice(
Expr::ty(*self),
self.ty(),
&crate::expr::ToLiteralBits::to_literal_bits(self)
.expect("must be a literal expression"),
)
@ -825,8 +1074,6 @@ macro_rules! impl_to_sim_value_for_bool_like {
}
impl ToSimValue for bool {
type Type = Bool;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(Bool, *self)
}
@ -864,10 +1111,8 @@ impl ToSimValueWithType<CanonicalType> for bool {
}
macro_rules! impl_to_sim_value_for_primitive_int {
($prim:ident) => {
($prim:ty) => {
impl ToSimValue for $prim {
type Type = <$prim as ToExpr>::Type;
#[track_caller]
fn to_sim_value(
&self,
@ -878,15 +1123,15 @@ macro_rules! impl_to_sim_value_for_primitive_int {
forward_to_sim_value_with_type!([] $prim);
impl ToSimValueWithType<<<$prim as ToExpr>::Type as IntType>::Dyn> for $prim {
impl ToSimValueWithType<<<$prim as ValueType>::Type as IntType>::Dyn> for $prim {
#[track_caller]
fn to_sim_value_with_type(
&self,
ty: <<$prim as ToExpr>::Type as IntType>::Dyn,
) -> SimValue<<<$prim as ToExpr>::Type as IntType>::Dyn> {
ty: <<$prim as ValueType>::Type as IntType>::Dyn,
) -> SimValue<<<$prim as ValueType>::Type as IntType>::Dyn> {
SimValue::from_value(
ty,
<<$prim as ToExpr>::Type as Type>::SimValue::from(*self).as_dyn_int(),
<<$prim as ValueType>::Type as Type>::SimValue::from(*self).as_dyn_int(),
)
}
}
@ -894,7 +1139,7 @@ macro_rules! impl_to_sim_value_for_primitive_int {
impl ToSimValueWithType<CanonicalType> for $prim {
#[track_caller]
fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue<CanonicalType> {
let ty: <<$prim as ToExpr>::Type as IntType>::Dyn = Type::from_canonical(ty);
let ty: <<$prim as ValueType>::Type as IntType>::Dyn = Type::from_canonical(ty);
SimValue::into_canonical(self.to_sim_value_with_type(ty))
}
}
@ -913,12 +1158,22 @@ impl_to_sim_value_for_primitive_int!(i32);
impl_to_sim_value_for_primitive_int!(i64);
impl_to_sim_value_for_primitive_int!(i128);
impl_to_sim_value_for_primitive_int!(isize);
impl_to_sim_value_for_primitive_int!(NonZero<u8>);
impl_to_sim_value_for_primitive_int!(NonZero<u16>);
impl_to_sim_value_for_primitive_int!(NonZero<u32>);
impl_to_sim_value_for_primitive_int!(NonZero<u64>);
impl_to_sim_value_for_primitive_int!(NonZero<u128>);
impl_to_sim_value_for_primitive_int!(NonZero<usize>);
impl_to_sim_value_for_primitive_int!(NonZero<i8>);
impl_to_sim_value_for_primitive_int!(NonZero<i16>);
impl_to_sim_value_for_primitive_int!(NonZero<i32>);
impl_to_sim_value_for_primitive_int!(NonZero<i64>);
impl_to_sim_value_for_primitive_int!(NonZero<i128>);
impl_to_sim_value_for_primitive_int!(NonZero<isize>);
macro_rules! impl_to_sim_value_for_int_value {
($IntValue:ident, $Int:ident, $IntType:ident) => {
impl<Width: Size> ToSimValue for $IntValue<Width> {
type Type = $IntType<Width>;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(self.ty(), self.clone())
}
@ -1279,8 +1534,6 @@ impl<T: SimOnlyValueTrait> ToSimValueWithType<SimOnly<T>> for SimOnlyValue<T> {
}
impl ToSimValue for DynSimOnlyValue {
type Type = DynSimOnly;
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(self.ty(), self.clone())
}
@ -1290,15 +1543,58 @@ impl ToSimValue for DynSimOnlyValue {
}
}
impl<T: SimOnlyValueTrait> ToSimValue for SimOnlyValue<T> {
impl<T: SimOnlyValueTrait> ValueType for SimOnlyValue<T> {
type Type = SimOnly<T>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
SimOnly::new()
}
}
impl<T: SimOnlyValueTrait> ToSimValue for SimOnlyValue<T> {
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(Default::default(), self.clone())
SimValue::from_value(self.ty(), self.clone())
}
fn into_sim_value(self) -> SimValue<Self::Type> {
SimValue::from_value(Default::default(), self)
SimValue::from_value(self.ty(), self)
}
}
impl HdlPartialEqImpl<Self> for DynSimOnly {
#[track_caller]
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: Self,
rhs_value: Cow<'_, Self::SimValue>,
) -> bool {
*lhs_value == *rhs_value
}
#[track_caller]
fn cmp_expr_eq(_lhs: Expr<Self>, _rhs: Expr<Self>) -> Expr<Bool> {
panic!("can't compare Expr<DynSimOnly>");
}
}
impl<L: SimOnlyValueTrait + PartialEq<R>, R: SimOnlyValueTrait> HdlPartialEqImpl<SimOnly<R>>
for SimOnly<L>
{
#[track_caller]
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: SimOnly<R>,
rhs_value: Cow<'_, <SimOnly<R> as Type>::SimValue>,
) -> bool {
**lhs_value == **rhs_value
}
#[track_caller]
fn cmp_expr_eq(_lhs: Expr<Self>, _rhs: Expr<SimOnly<R>>) -> Expr<Bool> {
panic!("can't compare Expr<SimOnly<_>>");
}
}

View file

@ -3,6 +3,7 @@
//! `unsafe` parts of [`DynSimOnlyValue`]
use crate::expr::{ValueType, value_category::ValueCategoryValue};
use serde::{Serialize, de::DeserializeOwned};
use std::{
any::{self, TypeId},
@ -206,9 +207,25 @@ impl<T: SimOnlyValueTrait> Default for SimOnly<T> {
}
/// a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL
#[derive(Clone, Eq, PartialEq, Hash, Default, PartialOrd, Ord)]
#[derive(Clone, Eq, Hash, Default, Ord)]
pub struct SimOnlyValue<T: SimOnlyValueTrait>(Rc<T>);
impl<T: SimOnlyValueTrait + PartialEq<U>, U: SimOnlyValueTrait> PartialEq<SimOnlyValue<U>>
for SimOnlyValue<T>
{
fn eq(&self, other: &SimOnlyValue<U>) -> bool {
<T as PartialEq<U>>::eq(self, other)
}
}
impl<T: SimOnlyValueTrait + PartialOrd<U>, U: SimOnlyValueTrait> PartialOrd<SimOnlyValue<U>>
for SimOnlyValue<T>
{
fn partial_cmp(&self, other: &SimOnlyValue<U>) -> Option<std::cmp::Ordering> {
<T as PartialOrd<U>>::partial_cmp(self, other)
}
}
impl<T: SimOnlyValueTrait> SimOnlyValue<T> {
pub fn with_dyn_ref<F: FnOnce(&DynSimOnlyValue) -> R, R>(&self, f: F) -> R {
// Safety: creating a copied `Rc<T>` is safe as long as the copy isn't dropped and isn't changed
@ -279,10 +296,16 @@ impl<T: SimOnlyValueTrait> From<SimOnlyValue<T>> for DynSimOnlyValue {
}
}
impl DynSimOnlyValue {
pub fn ty(&self) -> DynSimOnly {
impl ValueType for DynSimOnlyValue {
type Type = DynSimOnly;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
self.0.ty()
}
}
impl DynSimOnlyValue {
pub fn type_id(&self) -> TypeId {
self.0.type_id_dyn()
}

View file

@ -6,12 +6,14 @@ use crate::{
expr::Flow,
int::UInt,
intern::{Intern, Interned},
prelude::PhantomConst,
sim::{
TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl,
TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance,
TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule,
TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSimOnly,
TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls,
TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, TraceScalar, TraceScalarId,
TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, TraceWriter,
TraceWriterDecls,
time::{SimDuration, SimInstant},
value::DynSimOnlyValue,
},
@ -283,6 +285,7 @@ impl WriteTrace for TraceScalar {
Self::Clock(v) => v.write_trace(writer, arg),
Self::SyncReset(v) => v.write_trace(writer, arg),
Self::AsyncReset(v) => v.write_trace(writer, arg),
Self::PhantomConst(v) => v.write_trace(writer, arg),
Self::SimOnly(v) => v.write_trace(writer, arg),
}
}
@ -549,6 +552,33 @@ impl WriteTrace for TraceAsyncReset {
}
}
impl WriteTrace for TracePhantomConst {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type: _,
sink_var_type: _,
duplex_var_type: _,
properties,
scope,
} = arg.in_type();
let Self {
location,
name,
ty: _,
flow: _,
} = self;
write_vcd_var(
properties,
MemoryElementPartBody::Scalar,
writer,
"string",
1,
location,
scope.new_identifier(name),
)
}
}
impl WriteTrace for TraceSimOnly {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
@ -1091,6 +1121,16 @@ impl<W: io::Write> TraceWriter for VcdWriter<W> {
write_enum_discriminant_value_change(&mut self.writer, variant_index, ty, id.as_usize())
}
fn set_signal_phantom_const(
&mut self,
id: TraceScalarId,
ty: PhantomConst,
) -> Result<(), Self::Error> {
// avoid multi-line strings because GTKWave can't display them properly:
// https://github.com/gtkwave/gtkwave/issues/460
write_string_value_change(&mut self.writer, format_args!("{ty:?}"), id.as_usize())
}
fn set_signal_sim_only_value(
&mut self,
id: TraceScalarId,

View file

@ -1,25 +1,54 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
cli::{FormalArgs, FormalMode, FormalOutput, RunPhase},
build::{
BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams,
NoArgs, RunBuild,
external::{ExternalCommandArgs, ExternalCommandJobKind},
firrtl::{FirrtlArgs, FirrtlJobKind},
formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind},
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
},
bundle::BundleType,
firrtl::ExportOptions,
module::Module,
util::HashMap,
};
use clap::Parser;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::{
fmt::Write,
fmt::{self, 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(
clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize,
)]
#[non_exhaustive]
pub enum FormalMode {
#[default]
BMC,
Prove,
Live,
Cover,
}
impl FormalMode {
pub fn as_str(self) -> &'static str {
match self {
FormalMode::BMC => "bmc",
FormalMode::Prove => "prove",
FormalMode::Live => "live",
FormalMode::Cover => "cover",
}
}
}
impl fmt::Display for FormalMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Deserialize)]
@ -97,26 +126,99 @@ fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf {
.join(dir)
}
#[track_caller]
pub fn assert_formal<M>(
test_name: impl std::fmt::Display,
module: M,
mode: FormalMode,
depth: u64,
fn make_assert_formal_args(
test_name: &dyn std::fmt::Display,
formal_mode: FormalMode,
formal_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");
) -> eyre::Result<JobArgsAndDependencies<ExternalCommandJobKind<Formal>>> {
let args = JobKindAndArgs {
kind: BaseJobKind,
args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None),
};
let dependencies = JobArgsAndDependencies {
args,
dependencies: (),
};
let args = JobKindAndArgs {
kind: FirrtlJobKind,
args: FirrtlArgs { export_options },
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::resolve_program_path(
None,
UnadjustedVerilogArgs {
firtool_extra_args: vec![],
verilog_dialect: None,
verilog_debug: true,
},
)?,
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: VerilogJobKind,
args: VerilogJobArgs {},
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: WriteSbyFileJobKind,
args: FormalArgs {
sby_extra_args: vec![],
formal_mode,
formal_depth,
formal_solver: solver.unwrap_or(FormalArgs::DEFAULT_SOLVER).into(),
smtbmc_extra_args: vec![],
},
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?,
};
Ok(JobArgsAndDependencies { args, dependencies })
}
pub fn try_assert_formal<M: AsRef<Module<T>>, T: BundleType>(
test_name: impl std::fmt::Display,
module: M,
formal_mode: FormalMode,
formal_depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) -> eyre::Result<()> {
const APP_NAME: &'static str = "fayalite::testing::assert_formal";
make_assert_formal_args(
&test_name,
formal_mode,
formal_depth,
solver,
export_options,
)?
.run_without_platform(
|NoArgs {}| Ok(JobParams::new(module)),
&GlobalParams::new(None, APP_NAME),
)
}
#[track_caller]
pub fn assert_formal<M: AsRef<Module<T>>, T: BundleType>(
test_name: impl std::fmt::Display,
module: M,
formal_mode: FormalMode,
formal_depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) {
try_assert_formal(
test_name,
module,
formal_mode,
formal_depth,
solver,
export_options,
)
.expect("testing::assert_formal() failed");
}

View file

@ -11,7 +11,7 @@ use crate::{
intern::{Intern, Interned},
phantom_const::PhantomConst,
reset::{AsyncReset, Reset, SyncReset},
sim::value::{DynSimOnlyValue, DynSimOnly, SimValue, ToSimValueWithType},
sim::value::{DynSimOnly, DynSimOnlyValue, SimValue, ToSimValueWithType},
source_location::SourceLocation,
util::{ConstUsize, slice_range, try_slice_range},
};

View file

@ -127,7 +127,7 @@ impl From<SerdeCanonicalType> for CanonicalType {
SerdeCanonicalType::Reset => Self::Reset(Reset),
SerdeCanonicalType::Clock => Self::Clock(Clock),
SerdeCanonicalType::PhantomConst(value) => {
Self::PhantomConst(PhantomConst::new(value.0))
Self::PhantomConst(PhantomConst::new_interned(value.0))
}
SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value),
}

View file

@ -33,12 +33,15 @@ pub use const_cmp::{
#[doc(inline)]
pub use scoped_ref::ScopedRef;
pub(crate) use misc::chain;
#[doc(inline)]
pub use misc::{
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, interned_bit,
iter_eq_by, slice_range, try_slice_range,
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter,
SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest,
SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix,
os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty,
serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range,
};
pub(crate) use misc::{InternedStrCompareAsStr, chain};
pub mod job_server;
pub mod prefix_sum;

View file

@ -1,192 +1,156 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use ctor::ctor;
use jobslot::{Acquired, Client};
use ctor::{ctor, dtor};
use jobslot::Client;
use std::{
ffi::OsString,
mem,
io, mem,
num::NonZeroUsize,
sync::{Condvar, Mutex, Once, OnceLock},
thread::spawn,
sync::{Mutex, MutexGuard},
};
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(),
}
};
#[ctor]
static CLIENT: Mutex<Option<Option<Client>>> = unsafe { Mutex::new(Some(Client::from_env())) };
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;
#[dtor]
fn drop_client() {
drop(
match CLIENT.lock() {
Ok(v) => v,
Err(e) => e.into_inner(),
}
.take(),
);
}
fn get_or_make_client() -> Client {
CLIENT
.lock()
.expect("shouldn't have panicked")
.as_mut()
.expect("shutting down")
.get_or_insert_with(|| {
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;
}
_ => {}
}
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")
})
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")
})
.clone()
}
struct State {
obtained_count: usize,
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 {
obtained_count: 0,
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,
client: Client,
}
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();
pub fn acquire() -> io::Result<Self> {
let client = get_or_make_client();
struct Waiting {}
impl Waiting {
fn done(self) -> MutexGuard<'static, State> {
mem::forget(self);
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),
});
state.waiting_count -= 1;
state
}
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")
impl Drop for Waiting {
fn drop(&mut self) {
STATE.lock().unwrap().waiting_count -= 1;
}
}
let mut state = STATE.lock().unwrap();
if state.obtained_count == 0 && state.waiting_count == 0 {
state.obtained_count = 1; // get implicit token
return Ok(Self { client });
}
state.waiting_count += 1;
drop(state);
let waiting = Waiting {};
client.acquire_raw()?;
state = waiting.done();
state.obtained_count = state
.obtained_count
.checked_add(1)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "obtained_count overflowed"))?;
drop(state);
Ok(Self { client })
}
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)
self.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();
match &mut *state {
State {
obtained_count: 0, ..
} => unreachable!(),
State {
obtained_count: obtained_count @ 1,
waiting_count,
} => {
*obtained_count = 0; // drop implicit token
let any_waiting = *waiting_count != 0;
drop(state);
if any_waiting {
// we have the implicit token, but some other thread is trying to acquire a token,
// release the implicit token so they can acquire it.
let _ = self.client.release_raw(); // we're in drop, just ignore errors since we at least tried
}
}
AcquiredJobInner::ImplicitJob => {
state.implicit_available = true;
if state.waiting_count > state.available.len() {
COND_VAR.notify_all();
}
State { obtained_count, .. } => {
*obtained_count = obtained_count.saturating_sub(1);
drop(state);
let _ = self.client.release_raw(); // we're in drop, just ignore errors since we at least tried
}
}
}

View file

@ -4,7 +4,9 @@ use crate::intern::{Intern, Interned};
use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView};
use std::{
cell::Cell,
ffi::OsStr,
fmt::{self, Debug, Write},
io,
ops::{Bound, Range, RangeBounds},
rc::Rc,
sync::{Arc, OnceLock},
@ -243,3 +245,370 @@ pub fn try_slice_range<R: RangeBounds<usize>>(range: R, size: usize) -> Option<R
pub fn slice_range<R: RangeBounds<usize>>(range: R, size: usize) -> Range<usize> {
try_slice_range(range, size).expect("range out of bounds")
}
pub trait SerdeJsonEscapeIfTest {
fn char_needs_escape(&mut self, ch: char) -> serde_json::Result<bool>;
}
pub trait SerdeJsonEscapeIfTestResult {
fn to_result(self) -> serde_json::Result<bool>;
}
impl SerdeJsonEscapeIfTestResult for bool {
fn to_result(self) -> serde_json::Result<bool> {
Ok(self)
}
}
impl<E: Into<serde_json::Error>> SerdeJsonEscapeIfTestResult for Result<bool, E> {
fn to_result(self) -> serde_json::Result<bool> {
self.map_err(Into::into)
}
}
impl<T: ?Sized + FnMut(char) -> R, R: SerdeJsonEscapeIfTestResult> SerdeJsonEscapeIfTest for T {
fn char_needs_escape(&mut self, ch: char) -> serde_json::Result<bool> {
self(ch).to_result()
}
}
pub trait SerdeJsonEscapeIfFormatter: serde_json::ser::Formatter {
fn write_unicode_escape<W>(&mut self, writer: &mut W, ch: char) -> io::Result<()>
where
W: ?Sized + io::Write,
{
for utf16 in ch.encode_utf16(&mut [0; 2]) {
write!(writer, "\\u{utf16:04x}")?;
}
Ok(())
}
}
impl SerdeJsonEscapeIfFormatter for serde_json::ser::CompactFormatter {}
impl SerdeJsonEscapeIfFormatter for serde_json::ser::PrettyFormatter<'_> {}
pub struct SerdeJsonEscapeIf<Test, Base = serde_json::ser::CompactFormatter> {
pub base: Base,
pub test: Test,
}
impl<Test: SerdeJsonEscapeIfTest, Base: SerdeJsonEscapeIfFormatter> serde_json::ser::Formatter
for SerdeJsonEscapeIf<Test, Base>
{
fn write_null<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_null(writer)
}
fn write_bool<W>(&mut self, writer: &mut W, value: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_bool(writer, value)
}
fn write_i8<W>(&mut self, writer: &mut W, value: i8) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_i8(writer, value)
}
fn write_i16<W>(&mut self, writer: &mut W, value: i16) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_i16(writer, value)
}
fn write_i32<W>(&mut self, writer: &mut W, value: i32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_i32(writer, value)
}
fn write_i64<W>(&mut self, writer: &mut W, value: i64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_i64(writer, value)
}
fn write_i128<W>(&mut self, writer: &mut W, value: i128) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_i128(writer, value)
}
fn write_u8<W>(&mut self, writer: &mut W, value: u8) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_u8(writer, value)
}
fn write_u16<W>(&mut self, writer: &mut W, value: u16) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_u16(writer, value)
}
fn write_u32<W>(&mut self, writer: &mut W, value: u32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_u32(writer, value)
}
fn write_u64<W>(&mut self, writer: &mut W, value: u64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_u64(writer, value)
}
fn write_u128<W>(&mut self, writer: &mut W, value: u128) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_u128(writer, value)
}
fn write_f32<W>(&mut self, writer: &mut W, value: f32) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_f32(writer, value)
}
fn write_f64<W>(&mut self, writer: &mut W, value: f64) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_f64(writer, value)
}
fn write_number_str<W>(&mut self, writer: &mut W, value: &str) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_number_str(writer, value)
}
fn begin_string<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_string(writer)
}
fn end_string<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_string(writer)
}
fn write_string_fragment<W>(&mut self, writer: &mut W, mut fragment: &str) -> io::Result<()>
where
W: ?Sized + io::Write,
{
while let Some((next_escape_index, next_escape_char)) = fragment
.char_indices()
.find_map(|(index, ch)| match self.test.char_needs_escape(ch) {
Ok(false) => None,
Ok(true) => Some(Ok((index, ch))),
Err(e) => Some(Err(e)),
})
.transpose()?
{
let (no_escapes, rest) = fragment.split_at(next_escape_index);
fragment = &rest[next_escape_char.len_utf8()..];
self.base.write_string_fragment(writer, no_escapes)?;
self.base.write_unicode_escape(writer, next_escape_char)?;
}
self.base.write_string_fragment(writer, fragment)
}
fn write_char_escape<W>(
&mut self,
writer: &mut W,
char_escape: serde_json::ser::CharEscape,
) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_char_escape(writer, char_escape)
}
fn write_byte_array<W>(&mut self, writer: &mut W, value: &[u8]) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_byte_array(writer, value)
}
fn begin_array<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_array(writer)
}
fn end_array<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_array(writer)
}
fn begin_array_value<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_array_value(writer, first)
}
fn end_array_value<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_array_value(writer)
}
fn begin_object<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_object(writer)
}
fn end_object<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_object(writer)
}
fn begin_object_key<W>(&mut self, writer: &mut W, first: bool) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_object_key(writer, first)
}
fn end_object_key<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_object_key(writer)
}
fn begin_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.begin_object_value(writer)
}
fn end_object_value<W>(&mut self, writer: &mut W) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.end_object_value(writer)
}
fn write_raw_fragment<W>(&mut self, writer: &mut W, fragment: &str) -> io::Result<()>
where
W: ?Sized + io::Write,
{
self.base.write_raw_fragment(writer, fragment)
}
}
fn serialize_to_json_ascii_helper<F: SerdeJsonEscapeIfFormatter, S: serde::Serialize + ?Sized>(
v: &S,
base: F,
) -> serde_json::Result<String> {
let mut retval = Vec::new();
v.serialize(&mut serde_json::ser::Serializer::with_formatter(
&mut retval,
SerdeJsonEscapeIf {
base,
test: |ch| ch < '\x20' || ch > '\x7F',
},
))?;
String::from_utf8(retval).map_err(|_| serde::ser::Error::custom("invalid UTF-8"))
}
pub fn serialize_to_json_ascii<T: serde::Serialize + ?Sized>(v: &T) -> serde_json::Result<String> {
serialize_to_json_ascii_helper(v, serde_json::ser::CompactFormatter)
}
pub fn serialize_to_json_ascii_pretty<T: serde::Serialize + ?Sized>(
v: &T,
) -> serde_json::Result<String> {
serialize_to_json_ascii_helper(v, serde_json::ser::PrettyFormatter::new())
}
pub fn serialize_to_json_ascii_pretty_with_indent<T: serde::Serialize + ?Sized>(
v: &T,
indent: &str,
) -> serde_json::Result<String> {
serialize_to_json_ascii_helper(
v,
serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()),
)
}
pub fn os_str_strip_prefix<'a>(os_str: &'a OsStr, prefix: impl AsRef<str>) -> Option<&'a OsStr> {
os_str
.as_encoded_bytes()
.strip_prefix(prefix.as_ref().as_bytes())
.map(|bytes| {
// Safety: we removed a UTF-8 prefix so bytes starts with a valid boundary
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
})
}
pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef<str>) -> Option<&'a OsStr> {
os_str
.as_encoded_bytes()
.strip_suffix(suffix.as_ref().as_bytes())
.map(|bytes| {
// Safety: we removed a UTF-8 suffix so bytes ends with a valid boundary
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
})
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned<str>);
impl fmt::Debug for InternedStrCompareAsStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Ord for InternedStrCompareAsStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
str::cmp(&self.0, &other.0)
}
}
impl PartialOrd for InternedStrCompareAsStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::borrow::Borrow<str> for InternedStrCompareAsStr {
fn borrow(&self) -> &str {
&self.0
}
}

View file

@ -25,7 +25,7 @@ impl<T: Type> ReadyValid<T> {
#[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;
let option_ty = expr.ty().data;
#[hdl]
let firing_data = wire(option_ty);
connect(firing_data, option_ty.HdlNone());
@ -42,7 +42,7 @@ impl<T: Type> ReadyValid<T> {
) -> Expr<ReadyValid<R>> {
let data = HdlOption::map(expr.data, f);
#[hdl]
let mapped = wire(ReadyValid[Expr::ty(data).HdlSome]);
let mapped = wire(ReadyValid[data.ty().HdlSome]);
connect(mapped.data, data);
connect(expr.ready, mapped.ready);
mapped
@ -81,7 +81,7 @@ pub fn queue<T: Type>(
let count: UInt = m.output(count_ty);
#[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(0.cast_to(index_ty));
let inp_index_reg: UInt = 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]
@ -212,9 +212,7 @@ pub fn queue<T: Type>(
mod tests {
use super::*;
use crate::{
cli::FormalMode, firrtl::ExportOptions,
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
ty::StaticType,
firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind, ty::StaticType,
};
use std::num::NonZero;

View file

@ -0,0 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod xilinx;
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
xilinx::built_in_job_kinds()
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
xilinx::built_in_platforms()
}

207
crates/fayalite/src/vendor/xilinx.rs vendored Normal file
View file

@ -0,0 +1,207 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
annotations::make_annotation_enum,
build::{GlobalParams, ToArgs, WriteArgs},
intern::Interned,
prelude::{DynPlatform, Platform},
};
use clap::ValueEnum;
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use std::fmt;
pub mod arty_a7;
pub mod primitives;
pub mod yosys_nextpnr_prjxray;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcIOStandardAnnotation {
pub value: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcLocationAnnotation {
pub location: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcCreateClockAnnotation {
/// clock period in nanoseconds
pub period: NotNan<f64>,
}
make_annotation_enum! {
#[non_exhaustive]
pub enum XilinxAnnotation {
XdcIOStandard(XdcIOStandardAnnotation),
XdcLocation(XdcLocationAnnotation),
XdcCreateClock(XdcCreateClockAnnotation),
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct XilinxArgs {
#[arg(long)]
pub device: Option<Device>,
}
impl XilinxArgs {
pub fn require_device(
&self,
platform: Option<&DynPlatform>,
global_params: &GlobalParams,
) -> clap::error::Result<Device> {
if let Some(device) = self.device {
return Ok(device);
}
if let Some(device) =
platform.and_then(|platform| platform.aspects().get_single_by_type::<Device>().copied())
{
return Ok(device);
}
Err(global_params.clap_error(
clap::error::ErrorKind::MissingRequiredArgument,
"missing --device option",
))
}
}
impl ToArgs for XilinxArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
if let Some(device) = self.device {
args.write_long_option_eq("device", device.as_str());
}
}
}
macro_rules! make_device_enum {
($vis:vis enum $Device:ident {
$(
#[
name = $name:literal,
xray_part = $xray_part:literal,
xray_device = $xray_device:literal,
xray_family = $xray_family:literal,
]
$variant:ident,
)*
}) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
$vis enum $Device {
$(
#[value(name = $name, alias = $xray_part)]
$variant,
)*
}
impl $Device {
$vis fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $name,)*
}
}
$vis fn xray_part(self) -> &'static str {
match self {
$(Self::$variant => $xray_part,)*
}
}
$vis fn xray_device(self) -> &'static str {
match self {
$(Self::$variant => $xray_device,)*
}
}
$vis fn xray_family(self) -> &'static str {
match self {
$(Self::$variant => $xray_family,)*
}
}
}
struct DeviceVisitor;
impl<'de> serde::de::Visitor<'de> for DeviceVisitor {
type Value = $Device;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Xilinx device string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match $Device::from_str(v, false) {
Ok(v) => Ok(v),
Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)),
}
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) {
Some(v) => Ok(v),
None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)),
}
}
}
impl<'de> Deserialize<'de> for $Device {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(DeviceVisitor)
}
}
impl Serialize for $Device {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
arty_a7::built_in_job_kinds()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_job_kinds())
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
arty_a7::built_in_platforms()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_platforms())
}

View file

@ -0,0 +1,404 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
intern::{Intern, Interned},
module::{instance_with_loc, reg_builder_with_loc, wire_with_loc},
platform::{
DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory,
PeripheralsBuilderFinished, Platform, PlatformAspectSet,
peripherals::{ClockInput, Led, RgbLed, Uart},
},
prelude::*,
vendor::xilinx::{
Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation,
primitives,
},
};
use ordered_float::NotNan;
use std::sync::OnceLock;
macro_rules! arty_a7_platform {
(
$vis:vis enum $ArtyA7Platform:ident {
$(#[name = $name:literal, device = $device:ident]
$Variant:ident,)*
}
) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
$vis enum $ArtyA7Platform {
$($Variant,)*
}
impl $ArtyA7Platform {
$vis const VARIANTS: &'static [Self] = &[$(Self::$Variant,)*];
$vis fn device(self) -> Device {
match self {
$(Self::$Variant => Device::$device,)*
}
}
$vis const fn as_str(self) -> &'static str {
match self {
$(Self::$Variant => $name,)*
}
}
fn get_aspects(self) -> &'static PlatformAspectSet {
match self {
$(Self::$Variant => {
static ASPECTS_SET: OnceLock<PlatformAspectSet> = OnceLock::new();
ASPECTS_SET.get_or_init(|| self.make_aspects())
})*
}
}
}
};
}
arty_a7_platform! {
pub enum ArtyA7Platform {
#[name = "arty-a7-35t", device = Xc7a35ticsg324_1l]
ArtyA7_35T,
#[name = "arty-a7-100t", device = Xc7a100ticsg324_1l]
ArtyA7_100T,
}
}
#[derive(Debug)]
pub struct ArtyA7Peripherals {
clk100_div_pow2: [Peripheral<ClockInput>; 4],
rst: Peripheral<Reset>,
rst_sync: Peripheral<SyncReset>,
ld0: Peripheral<RgbLed>,
ld1: Peripheral<RgbLed>,
ld2: Peripheral<RgbLed>,
ld3: Peripheral<RgbLed>,
ld4: Peripheral<Led>,
ld5: Peripheral<Led>,
ld6: Peripheral<Led>,
ld7: Peripheral<Led>,
uart: Peripheral<Uart>,
// TODO: add rest of peripherals when we need them
}
impl Peripherals for ArtyA7Peripherals {
fn append_peripherals<'a>(&'a self, peripherals: &mut Vec<PeripheralRef<'a, CanonicalType>>) {
let Self {
clk100_div_pow2,
rst,
rst_sync,
ld0,
ld1,
ld2,
ld3,
ld4,
ld5,
ld6,
ld7,
uart,
} = self;
clk100_div_pow2.append_peripherals(peripherals);
rst.append_peripherals(peripherals);
rst_sync.append_peripherals(peripherals);
ld0.append_peripherals(peripherals);
ld1.append_peripherals(peripherals);
ld2.append_peripherals(peripherals);
ld3.append_peripherals(peripherals);
ld4.append_peripherals(peripherals);
ld5.append_peripherals(peripherals);
ld6.append_peripherals(peripherals);
ld7.append_peripherals(peripherals);
uart.append_peripherals(peripherals);
}
}
impl ArtyA7Platform {
fn make_aspects(self) -> PlatformAspectSet {
let mut retval = PlatformAspectSet::new();
retval.insert_new(self.device());
retval
}
}
#[hdl_module(extern)]
fn reset_sync() {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let inp: Bool = m.input();
#[hdl]
let out: SyncReset = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_arty_a7_reset_sync.v".intern(),
text: r#"module __fayalite_arty_a7_reset_sync(input clk, input inp, output out);
wire reset_0_out;
(* ASYNC_REG = "TRUE" *)
FDPE #(
.INIT(1'b1)
) reset_0 (
.Q(reset_0_out),
.C(clk),
.CE(1'b1),
.PRE(inp),
.D(1'b0)
);
(* ASYNC_REG = "TRUE" *)
FDPE #(
.INIT(1'b1)
) reset_1 (
.Q(out),
.C(clk),
.CE(1'b1),
.PRE(inp),
.D(reset_0_out)
);
endmodule
"#
.intern(),
});
m.verilog_name("__fayalite_arty_a7_reset_sync");
}
impl Platform for ArtyA7Platform {
type Peripherals = ArtyA7Peripherals;
fn name(&self) -> Interned<str> {
self.as_str().intern()
}
fn new_peripherals<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) {
let mut builder = builder_factory.builder();
let clk100_div_pow2 = std::array::from_fn(|log2_divisor| {
let divisor = 1u64 << log2_divisor;
let name = if divisor != 1 {
format!("clk100_div_{divisor}")
} else {
"clk100".into()
};
builder.input_peripheral(name, ClockInput::new(100e6 / divisor as f64))
});
builder.add_conflicts(Vec::from_iter(clk100_div_pow2.iter().map(|v| v.id())));
(
ArtyA7Peripherals {
clk100_div_pow2,
rst: builder.input_peripheral("rst", Reset),
rst_sync: builder.input_peripheral("rst_sync", SyncReset),
ld0: builder.output_peripheral("ld0", RgbLed),
ld1: builder.output_peripheral("ld1", RgbLed),
ld2: builder.output_peripheral("ld2", RgbLed),
ld3: builder.output_peripheral("ld3", RgbLed),
ld4: builder.output_peripheral("ld4", Led),
ld5: builder.output_peripheral("ld5", Led),
ld6: builder.output_peripheral("ld6", Led),
ld7: builder.output_peripheral("ld7", Led),
uart: builder.output_peripheral("uart", Uart),
},
builder.finish(),
)
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) {
let ArtyA7Peripherals {
clk100_div_pow2,
rst,
rst_sync,
ld0,
ld1,
ld2,
ld3,
ld4,
ld5,
ld6,
ld7,
uart,
} = peripherals;
let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| {
let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool);
annotate(
pin,
XdcLocationAnnotation {
location: location.intern(),
},
);
annotate(
pin,
XdcIOStandardAnnotation {
value: io_standard.intern(),
},
);
let buf = instance_with_loc(
&format!("{name}_buf"),
primitives::IBUF(),
SourceLocation::builtin(),
);
connect(buf.I, pin);
if invert { !buf.O } else { buf.O }
};
let make_buffered_output = |name: &str, location: &str, io_standard: &str| {
let pin = m.output_with_loc(name, SourceLocation::builtin(), Bool);
annotate(
pin,
XdcLocationAnnotation {
location: location.intern(),
},
);
annotate(
pin,
XdcIOStandardAnnotation {
value: io_standard.intern(),
},
);
let buf = instance_with_loc(
&format!("{name}_buf"),
primitives::OBUFT(),
SourceLocation::builtin(),
);
connect(pin, buf.O);
connect(buf.T, false);
buf.I
};
let mut frequency = clk100_div_pow2[0].ty().frequency();
let mut log2_divisor = 0;
let mut clk = None;
for (cur_log2_divisor, p) in clk100_div_pow2.into_iter().enumerate() {
let Some(p) = p.into_used() else {
continue;
};
debug_assert!(
clk.is_none(),
"conflict-handling logic should ensure at most one clock is used",
);
frequency = p.ty().frequency();
clk = Some(p);
log2_divisor = cur_log2_divisor;
}
let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false);
let startup = instance_with_loc(
"startup",
primitives::STARTUPE2_default_inputs(),
SourceLocation::builtin(),
);
let clk_global_buf = instance_with_loc(
"clk_global_buf",
primitives::BUFGCE(),
SourceLocation::builtin(),
);
connect(clk_global_buf.CE, startup.EOS);
let mut clk_global_buf_in = clk100_buf.to_clock();
for prev_log2_divisor in 0..log2_divisor {
let prev_divisor = 1u64 << prev_log2_divisor;
let clk_in = wire_with_loc(
&format!("clk_div_{prev_divisor}"),
SourceLocation::builtin(),
Clock,
);
connect(clk_in, clk_global_buf_in);
annotate(
clk_in,
XdcCreateClockAnnotation {
period: NotNan::new(1e9 / (100e6 / prev_divisor as f64))
.expect("known to be valid"),
},
);
annotate(clk_in, DontTouchAnnotation);
let cd = wire_with_loc(
&format!("clk_div_{prev_divisor}_in"),
SourceLocation::builtin(),
ClockDomain[AsyncReset],
);
connect(cd.clk, clk_in);
connect(cd.rst, (!startup.EOS).to_async_reset());
let divider = reg_builder_with_loc("divider", SourceLocation::builtin())
.clock_domain(cd)
.reset(false)
.build();
connect(divider, !divider);
clk_global_buf_in = divider.to_clock();
}
connect(clk_global_buf.I, clk_global_buf_in);
let clk_out = wire_with_loc("clk_out", SourceLocation::builtin(), Clock);
connect(clk_out, clk_global_buf.O);
annotate(
clk_out,
XdcCreateClockAnnotation {
period: NotNan::new(1e9 / frequency).expect("known to be valid"),
},
);
annotate(clk_out, DontTouchAnnotation);
if let Some(clk) = clk {
connect(clk.instance_io_field().clk, clk_out);
}
let rst_value = {
let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true);
let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin());
connect(rst_sync.clk, clk_out);
connect(rst_sync.inp, rst_buf | !startup.EOS);
rst_sync.out
};
if let Some(rst) = rst.into_used() {
connect(rst.instance_io_field(), rst_value.to_reset());
}
if let Some(rst_sync) = rst_sync.into_used() {
connect(rst_sync.instance_io_field(), rst_value);
}
let rgb_leds = [
(ld0, ("G6", "F6", "E1")),
(ld1, ("G3", "J4", "G4")),
(ld2, ("J3", "J2", "H4")),
(ld3, ("K1", "H6", "K2")),
];
for (rgb_led, (r_loc, g_loc, b_loc)) in rgb_leds {
let r = make_buffered_output(&format!("{}_r", rgb_led.name()), r_loc, "LVCMOS33");
let g = make_buffered_output(&format!("{}_g", rgb_led.name()), g_loc, "LVCMOS33");
let b = make_buffered_output(&format!("{}_b", rgb_led.name()), b_loc, "LVCMOS33");
if let Some(rgb_led) = rgb_led.into_used() {
connect(r, rgb_led.instance_io_field().r);
connect(g, rgb_led.instance_io_field().g);
connect(b, rgb_led.instance_io_field().b);
} else {
connect(r, false);
connect(g, false);
connect(b, false);
}
}
let leds = [(ld4, "H5"), (ld5, "J5"), (ld6, "T9"), (ld7, "T10")];
for (led, loc) in leds {
let o = make_buffered_output(&led.name(), loc, "LVCMOS33");
if let Some(led) = led.into_used() {
connect(o, led.instance_io_field().on);
} else {
connect(o, false);
}
}
let uart_tx = make_buffered_output("uart_tx", "D10", "LVCMOS33");
let uart_rx = make_buffered_input("uart_rx", "A9", "LVCMOS33", false);
if let Some(uart) = uart.into_used() {
connect(uart_tx, uart.instance_io_field().tx);
connect(uart.instance_io_field().rx, uart_rx);
} else {
connect(uart_tx, true); // idle
}
}
fn aspects(&self) -> PlatformAspectSet {
self.get_aspects().clone()
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
[]
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = DynPlatform> {
ArtyA7Platform::VARIANTS
.iter()
.map(|&v| DynPlatform::new(v))
}

View file

@ -0,0 +1,50 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#![allow(non_snake_case)]
use crate::prelude::*;
#[hdl_module(extern)]
pub fn IBUF() {
m.verilog_name("IBUF");
#[hdl]
let O: Bool = m.output();
#[hdl]
let I: Bool = m.input();
}
#[hdl_module(extern)]
pub fn OBUFT() {
m.verilog_name("OBUFT");
#[hdl]
let O: Bool = m.output();
#[hdl]
let I: Bool = m.input();
#[hdl]
let T: Bool = m.input();
}
#[hdl_module(extern)]
pub fn BUFGCE() {
m.verilog_name("BUFGCE");
#[hdl]
let O: Clock = m.output();
#[hdl]
let CE: Bool = m.input();
#[hdl]
let I: Clock = m.input();
}
#[hdl_module(extern)]
pub fn STARTUPE2_default_inputs() {
m.verilog_name("STARTUPE2");
#[hdl]
let CFGCLK: Clock = m.output();
#[hdl]
let CFGMCLK: Clock = m.output();
#[hdl]
let EOS: Bool = m.output();
#[hdl]
let PREQ: Bool = m.output();
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, Flow, ToExpr},
expr::{Expr, Flow, ToExpr, ValueType, value_category::ValueCategoryExpr},
intern::Interned,
module::{IncompleteDeclaration, NameId, ScopedNameId, StmtDeclaration, StmtWire},
source_location::SourceLocation,
@ -16,7 +16,16 @@ pub struct Wire<T: Type> {
ty: T,
}
impl<T: Type + fmt::Debug> fmt::Debug for Wire<T> {
impl<T: Type> ValueType for Wire<T> {
type Type = T;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
self.ty
}
}
impl<T: Type> fmt::Debug for Wire<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Wire({:?}: ", self.name)?;
self.ty.fmt(f)?;
@ -49,9 +58,6 @@ impl<T: Type> Wire<T> {
ty: T::from_canonical(ty),
}
}
pub fn ty(&self) -> T {
self.ty
}
pub fn new_unchecked(
scoped_name: ScopedNameId,
source_location: SourceLocation,

View file

@ -2,19 +2,7 @@
// See Notices.txt for copyright information
//! Formal tests in Fayalite
use fayalite::{
cli::FormalMode,
clock::{Clock, ClockDomain},
expr::{CastTo, HdlPartialEq},
firrtl::ExportOptions,
formal::{any_const, any_seq, formal_reset, hdl_assert, hdl_assume},
hdl, hdl_module,
int::{Bool, DynSize, Size, UInt, UIntType},
module::{connect, connect_any, instance, memory, reg_builder, wire},
reset::ToReset,
testing::assert_formal,
ty::StaticType,
};
use fayalite::prelude::*;
/// Test hidden state
///
@ -119,7 +107,7 @@ mod hidden_state {
FormalMode::Prove,
16,
None,
ExportOptions::default(),
Default::default(),
);
// here a couple of cycles is enough
assert_formal(
@ -128,7 +116,7 @@ mod hidden_state {
FormalMode::Prove,
2,
None,
ExportOptions::default(),
Default::default(),
);
}
}
@ -242,7 +230,7 @@ mod memory {
#[hdl]
let wr: WritePort<DynSize> = wire(WritePort[n]);
connect(wr.addr, any_seq(UInt[n]));
connect(wr.data, any_seq(UInt::<8>::TYPE));
connect(wr.data, any_seq(UInt::<8>::new_static()));
connect(wr.en, any_seq(Bool));
#[hdl]
let dut = instance(example_sram(n));
@ -289,7 +277,7 @@ mod memory {
FormalMode::Prove,
2,
None,
ExportOptions::default(),
Default::default(),
);
}
}

View file

@ -4,7 +4,6 @@ use fayalite::{
bundle::BundleType,
enum_::EnumType,
int::{BoolOrIntType, IntType},
phantom_const::PhantomConst,
prelude::*,
ty::StaticType,
};
@ -197,3 +196,51 @@ check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: 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 +>);
#[derive(Clone, PartialEq, Eq, Hash, Debug, serde::Serialize, serde::Deserialize)]
pub struct MyPhantomConstInner {
pub a: usize,
pub b: UInt,
}
#[hdl(outline_generated, get(|v| v.a))]
pub type GetA<P: PhantomConstGet<MyPhantomConstInner>> = DynSize;
#[hdl(outline_generated, get(|v| v.b))]
pub type GetB<P: PhantomConstGet<MyPhantomConstInner>> = UInt;
#[hdl(outline_generated, no_static)]
pub struct MyTypeWithPhantomConstParameter<P: PhantomConstGet<MyPhantomConstInner>> {
pub a: ArrayType<Bool, GetA<P>>,
pub b: HdlOption<GetB<P>>,
}
#[hdl(outline_generated)]
struct MyPrivateType {}
#[hdl(outline_generated)]
pub(crate) struct MyPubCrateType {}
#[hdl(outline_generated)]
pub struct MyTypeWithPrivateMembers {
a: MyPrivateType,
pub(crate) b: MyPubCrateType,
pub c: Bool,
}
#[hdl(outline_generated)]
struct MyPrivateTypeWithArg<T> {
v: T,
}
#[hdl(outline_generated)]
pub(crate) struct MyPubCrateTypeWithArg<T> {
v: T,
}
#[hdl(outline_generated)]
pub struct MyTypeWithPrivateMembersWithArg<T> {
a: MyPrivateTypeWithArg<T>,
pub(crate) b: MyPubCrateTypeWithArg<T>,
pub c: T,
}

View file

@ -6,6 +6,7 @@ use fayalite::{
int::{UIntInRange, UIntInRangeInclusive},
intern::Intern,
module::transform::simplify_enums::SimplifyEnumsKind,
platform::PlatformIOBuilder,
prelude::*,
reset::ResetType,
ty::StaticType,
@ -214,7 +215,7 @@ where
let o: Array<T, N> = m.output();
let bytes = v.to_string().as_bytes().to_expr();
#[hdl]
let o2: Array<UInt<8>> = m.output(Expr::ty(bytes));
let o2: Array<UInt<8>> = m.output(bytes.ty());
connect(
o,
#[hdl]
@ -1175,7 +1176,7 @@ pub fn check_memory_init() {
let waddr2: UInt<4> = m.input();
#[hdl]
let wdata2: UInt<31> = m.input();
let mem_init2 = Vec::from_iter((0..0x10u32).map(|i| (i * i * i).cast_to_static()));
let mem_init2 = Vec::from_iter((0..0x10u32).map(|i| (i * i * i).cast_to_static::<UInt<31>>()));
#[hdl]
let mut mem2 = memory_with_init(mem_init2);
let read_port2 = mem2.new_read_port();
@ -1783,7 +1784,7 @@ pub fn check_memory_of_bundle_of_arrays() {
[(i * i).wrapping_mul(2), (i * i).wrapping_mul(3)],
[(i * i).wrapping_mul(i), i * 2],
],
i.cast_to_static(),
i.cast_to_static::<UInt<2>>(),
)
}));
#[hdl]
@ -1953,9 +1954,9 @@ pub fn check_memory_of_array_of_bundle() {
let clk: Clock = m.input();
let mem_init = Vec::from_iter((0..0x10u8).map(|i| {
[
(i, i.cast_to_static()),
((i * i), (i / 2).cast_to_static()),
((i * i).wrapping_mul(i), (i / 4).cast_to_static()),
(i, i.cast_to_static::<SInt<1>>()),
((i * i), (i / 2).cast_to_static::<SInt<1>>()),
((i * i).wrapping_mul(i), (i / 4).cast_to_static::<SInt<1>>()),
]
}));
#[hdl]
@ -2268,7 +2269,8 @@ pub fn check_memory_of_bundle() {
let wmask: (Bool, Bool) = m.input();
#[hdl]
let clk: Clock = m.input();
let mem_init = Vec::from_iter((0..0x10u8).map(|i| (i ^ 3, (i ^ (i / 2)).cast_to_static())));
let mem_init =
Vec::from_iter((0..0x10u8).map(|i| (i ^ 3, (i ^ (i / 2)).cast_to_static::<SInt<1>>())));
#[hdl]
let mut mem = memory_with_init(mem_init);
let read_port = mem.new_read_port();
@ -4631,3 +4633,55 @@ circuit check_uint_in_range:
",
};
}
#[hdl_module(outline_generated)]
pub fn check_platform_io(platform_io_builder: PlatformIOBuilder<'_>) {
#[hdl]
let io = m.add_platform_io(platform_io_builder);
}
#[cfg(todo)]
#[test]
fn test_platform_io() {
let _n = SourceLocation::normalize_files_for_tests();
let m = check_platform_io(todo!());
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
"/test/check_platform_io.fir": r"FIRRTL version 3.2.0
circuit check_platform_io:
type Ty0 = {value: UInt<0>, range: {}}
type Ty1 = {value: UInt<1>, range: {}}
type Ty2 = {value: UInt<2>, range: {}}
type Ty3 = {value: UInt<2>, range: {}}
type Ty4 = {value: UInt<3>, range: {}}
type Ty5 = {value: UInt<3>, range: {}}
type Ty6 = {value: UInt<4>, range: {}}
type Ty7 = {value: UInt<0>, range: {}}
type Ty8 = {value: UInt<1>, range: {}}
type Ty9 = {value: UInt<2>, range: {}}
type Ty10 = {value: UInt<2>, range: {}}
type Ty11 = {value: UInt<3>, range: {}}
type Ty12 = {value: UInt<3>, range: {}}
type Ty13 = {value: UInt<4>, range: {}}
type Ty14 = {value: UInt<4>, range: {}}
module check_platform_io: @[module-XXXXXXXXXX.rs 1:1]
input i_0_to_1: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input i_0_to_2: Ty1 @[module-XXXXXXXXXX.rs 3:1]
input i_0_to_3: Ty2 @[module-XXXXXXXXXX.rs 4:1]
input i_0_to_4: Ty3 @[module-XXXXXXXXXX.rs 5:1]
input i_0_to_7: Ty4 @[module-XXXXXXXXXX.rs 6:1]
input i_0_to_8: Ty5 @[module-XXXXXXXXXX.rs 7:1]
input i_0_to_9: Ty6 @[module-XXXXXXXXXX.rs 8:1]
input i_0_through_0: Ty7 @[module-XXXXXXXXXX.rs 9:1]
input i_0_through_1: Ty8 @[module-XXXXXXXXXX.rs 10:1]
input i_0_through_2: Ty9 @[module-XXXXXXXXXX.rs 11:1]
input i_0_through_3: Ty10 @[module-XXXXXXXXXX.rs 12:1]
input i_0_through_4: Ty11 @[module-XXXXXXXXXX.rs 13:1]
input i_0_through_7: Ty12 @[module-XXXXXXXXXX.rs 14:1]
input i_0_through_8: Ty13 @[module-XXXXXXXXXX.rs 15:1]
input i_0_through_9: Ty14 @[module-XXXXXXXXXX.rs 16:1]
",
};
}

View file

@ -3,7 +3,7 @@
use fayalite::{
memory::{ReadStruct, ReadWriteStruct, WriteStruct},
module::{instance_with_loc, reg_builder_with_loc},
module::{instance_with_loc, memory_with_init_and_loc, reg_builder_with_loc},
prelude::*,
reset::ResetType,
sim::vcd::VcdWriterDecls,
@ -86,7 +86,7 @@ pub fn mod1() {
#[hdl]
let child = instance(mod1_child());
#[hdl]
let o: mod1_child = m.output(Expr::ty(child));
let o: mod1_child = m.output(child.ty());
connect(o, child);
}
@ -506,10 +506,10 @@ fn test_enums() {
data_out: _,
b_out: _,
b2_out: _,
} = expected;
sim.write(sim.io().en, &en);
sim.write(sim.io().which_in, &which_in);
sim.write(sim.io().data_in, &data_in);
} = &expected;
sim.write(sim.io().en, en);
sim.write(sim.io().which_in, which_in);
sim.write(sim.io().data_in, data_in);
let io = #[hdl(sim)]
IO::<_> {
en,
@ -528,7 +528,7 @@ fn test_enums() {
);
// make sure matching on SimValue<SomeEnum> works
#[hdl(sim)]
match io.b_out {
match &io.b_out {
HdlNone => println!("io.b_out is HdlNone"),
HdlSome(v) => println!("io.b_out is HdlSome(({:?}, {:?}))", *v.0, *v.1),
}
@ -706,13 +706,13 @@ fn test_memories() {
w_en,
w_data,
w_mask,
} = expected;
sim.write(sim.io().r.addr, &r_addr);
sim.write(sim.io().r.en, &r_en);
sim.write(sim.io().w.addr, &w_addr);
sim.write(sim.io().w.en, &w_en);
sim.write(sim.io().w.data, &w_data);
sim.write(sim.io().w.mask, &w_mask);
} = &expected;
sim.write(sim.io().r.addr, r_addr);
sim.write(sim.io().r.en, r_en);
sim.write(sim.io().w.addr, w_addr);
sim.write(sim.io().w.en, w_en);
sim.write(sim.io().w.data, w_data);
sim.write(sim.io().w.mask, w_mask);
let io = #[hdl(sim)]
IO {
r_addr,
@ -979,10 +979,10 @@ fn test_memories2() {
},
) in io_cycles.into_iter().enumerate()
{
sim.write_bool_or_int(sim.io().rw.addr, addr.cast_to_static());
sim.write_bool_or_int(sim.io().rw.addr, addr.cast_to_static::<UInt<3>>());
sim.write_bool(sim.io().rw.en, en);
sim.write_bool(sim.io().rw.wmode, wmode);
sim.write_bool_or_int(sim.io().rw.wdata, wdata.cast_to_static());
sim.write_bool_or_int(sim.io().rw.wdata, wdata.cast_to_static::<UInt<2>>());
sim.write_bool(sim.io().rw.wmask, wmask);
sim.advance_time(SimDuration::from_nanos(250));
sim.write_clock(sim.io().rw.clk, true);
@ -1195,9 +1195,9 @@ fn test_memories3() {
w_data: [0; 8],
w_mask: [false; 8],
});
sim.write_bool_or_int(sim.io().r.addr, r_addr.cast_to_static());
sim.write_bool_or_int(sim.io().r.addr, r_addr.cast_to_static::<UInt<3>>());
sim.write_bool(sim.io().r.en, r_en);
sim.write_bool_or_int(sim.io().w.addr, w_addr.cast_to_static());
sim.write_bool_or_int(sim.io().w.addr, w_addr.cast_to_static::<UInt<3>>());
sim.write_bool(sim.io().w.en, w_en);
for (i, v) in w_data.into_iter().enumerate() {
sim.write_bool_or_int(sim.io().w.data[i], v);
@ -1261,6 +1261,310 @@ fn test_memories3() {
}
}
#[hdl_module(outline_generated)]
pub fn many_memories() {
#[hdl]
let r: Array<ReadStruct<Bool, ConstUsize<4>>, 8> = m.input();
#[hdl]
let w: Array<WriteStruct<Bool, ConstUsize<4>>, 8> = m.input();
for (mem_index, (r, w)) in r.into_iter().zip(w).enumerate() {
let mut mem = memory_with_init_and_loc(
&format!("mem_{mem_index}"),
(0..16)
.map(|bit_index| mem_index.pow(5).to_expr()[bit_index])
.collect::<Vec<_>>(),
SourceLocation::caller(),
);
connect_any(mem.new_read_port(), r);
connect_any(mem.new_write_port(), w);
}
}
#[hdl]
#[test]
fn test_many_memories() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(many_memories());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
for r in sim.io().r {
sim.write_clock(r.clk, false);
}
for w in sim.io().w {
sim.write_clock(w.clk, false);
}
#[hdl(cmp_eq)]
struct IO {
r_addr: UInt<4>,
r_en: Bool,
r_data: Array<Bool, 8>,
w_addr: UInt<4>,
w_en: Bool,
w_data: Array<Bool, 8>,
w_mask: Array<Bool, 8>,
}
let io_cycles = [
#[hdl(sim)]
IO {
r_addr: 0_hdl_u4,
r_en: false,
r_data: [false; 8],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [false; 8],
},
#[hdl(sim)]
IO {
r_addr: 0_hdl_u4,
r_en: true,
r_data: [false, true, false, true, false, true, false, true],
w_addr: 0_hdl_u4,
w_en: true,
w_data: [true; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0_hdl_u4,
r_en: true,
r_data: [true; 8],
w_addr: 0_hdl_u4,
w_en: true,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0_hdl_u4,
r_en: true,
r_data: [false; 8],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 1_hdl_u4,
r_en: true,
r_data: [false, false, false, true, false, false, false, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 2_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, true, false, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 3_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, false, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 4_hdl_u4,
r_en: true,
r_data: [false, false, false, true, false, true, false, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 5_hdl_u4,
r_en: true,
r_data: [false, false, true, true, false, true, true, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 6_hdl_u4,
r_en: true,
r_data: [false, false, false, true, false, false, true, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 7_hdl_u4,
r_en: true,
r_data: [false, false, false, true, false, false, false, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 8_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, false, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 9_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, true, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xA_hdl_u4,
r_en: true,
r_data: [false, false, false, false, true, true, true, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xB_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, true, true, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xC_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, true, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xD_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, false, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xE_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, false, true],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
#[hdl(sim)]
IO {
r_addr: 0xF_hdl_u4,
r_en: true,
r_data: [false, false, false, false, false, false, false, false],
w_addr: 0_hdl_u4,
w_en: false,
w_data: [false; 8],
w_mask: [true; 8],
},
];
for (cycle, expected) in io_cycles.into_iter().enumerate() {
#[hdl(sim)]
let IO {
r_addr,
r_en,
r_data: _,
w_addr,
w_en,
w_data,
w_mask,
} = &expected;
for (((r, w), w_data), w_mask) in sim
.io()
.r
.into_iter()
.zip(sim.io().w)
.zip(w_data.iter())
.zip(w_mask.iter())
{
sim.write(r.addr, r_addr);
sim.write(r.en, r_en);
sim.write(w.addr, w_addr);
sim.write(w.en, w_en);
sim.write(w.data, w_data);
sim.write(w.mask, w_mask);
}
let io = #[hdl(sim)]
IO {
r_addr,
r_en,
r_data: std::array::from_fn(|i| sim.read(sim.io().r[i].data)),
w_addr,
w_en,
w_data,
w_mask,
};
assert_eq!(
expected,
io,
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
sim.advance_time(SimDuration::from_micros(1));
for r in sim.io().r {
sim.write_clock(r.clk, true);
}
for w in sim.io().w {
sim.write_clock(w.clk, true);
}
sim.advance_time(SimDuration::from_micros(1));
for r in sim.io().r {
sim.write_clock(r.clk, false);
}
for w in sim.io().w {
sim.write_clock(w.clk, false);
}
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/many_memories.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/many_memories.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn duplicate_names() {
#[hdl]
@ -1722,3 +2026,472 @@ fn test_sim_only_connects() {
panic!();
}
}
#[hdl_module(outline_generated, extern)]
pub fn sim_fork_join<const N: usize>()
where
ConstUsize<N>: KnownSize,
{
#[hdl]
let clocks: Array<Clock, N> = m.input();
#[hdl]
let outputs: Array<UInt<8>, N> = m.output();
m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move {
sim.write(outputs, [0u8; N]).await;
loop {
sim.fork_join(
clocks
.into_iter()
.zip(outputs)
.map(|(clock, output)| {
move |mut sim: ExternModuleSimulationState| async move {
sim.wait_for_clock_edge(clock).await;
let v = sim
.read_bool_or_int(output)
.await
.to_bigint()
.try_into()
.expect("known to be in range");
sim.write(output, 1u8.wrapping_add(v)).await;
}
})
.collect::<Vec<_>>(),
)
.await;
}
});
}
#[test]
fn test_sim_fork_join() {
let _n = SourceLocation::normalize_files_for_tests();
const N: usize = 3;
let mut sim = Simulation::new(sim_fork_join::<N>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().clocks, [false; N]);
let mut clocks_triggered = [false; N];
let mut expected = [0u8; N];
for i0 in 0..N {
for i1 in 0..N {
for i2 in 0..N {
for i3 in 0..N {
let indexes = [i0, i1, i2, i3];
for i in indexes {
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], true);
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], false);
if !clocks_triggered[i] {
expected[i] = expected[i].wrapping_add(1);
}
clocks_triggered[i] = true;
if clocks_triggered == [true; N] {
clocks_triggered = [false; N];
}
let output = sim.read(sim.io().outputs);
assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}");
}
}
}
}
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_fork_join.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_fork_join.txt") {
panic!();
}
}
#[hdl_module(outline_generated, extern)]
pub fn sim_fork_join_scope<const N: usize>()
where
ConstUsize<N>: KnownSize,
{
#[hdl]
let clocks: Array<Clock, N> = m.input();
#[hdl]
let outputs: Array<UInt<8>, N> = m.output();
m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move {
sim.write(outputs, [0u8; N]).await;
loop {
let written = vec![std::cell::Cell::new(false); N]; // test shared scope
let written = &written; // work around move in async move
sim.fork_join_scope(|scope, _| async move {
let mut spawned = vec![];
for i in 0..N {
let join_handle =
scope.spawn(move |_, mut sim: ExternModuleSimulationState| async move {
sim.wait_for_clock_edge(clocks[i]).await;
let v = sim
.read_bool_or_int(outputs[i])
.await
.to_bigint()
.try_into()
.expect("known to be in range");
sim.write(outputs[i], 1u8.wrapping_add(v)).await;
written[i].set(true);
i
});
if i % 2 == 0 && i < N - 1 {
spawned.push((i, join_handle));
}
}
for (i, join_handle) in spawned {
assert_eq!(i, join_handle.join().await);
}
})
.await;
for written in written {
assert!(written.get());
}
}
});
}
#[test]
fn test_sim_fork_join_scope() {
let _n = SourceLocation::normalize_files_for_tests();
const N: usize = 3;
let mut sim = Simulation::new(sim_fork_join_scope::<N>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().clocks, [false; N]);
let mut clocks_triggered = [false; N];
let mut expected = [0u8; N];
for i0 in 0..N {
for i1 in 0..N {
for i2 in 0..N {
for i3 in 0..N {
let indexes = [i0, i1, i2, i3];
for i in indexes {
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], true);
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], false);
if !clocks_triggered[i] {
expected[i] = expected[i].wrapping_add(1);
}
clocks_triggered[i] = true;
if clocks_triggered == [true; N] {
clocks_triggered = [false; N];
}
let output = sim.read(sim.io().outputs);
assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}");
}
}
}
}
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_fork_join_scope.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_fork_join_scope.txt") {
panic!();
}
}
#[hdl_module(outline_generated, extern)]
pub fn sim_resettable_counter<R: ResetType>() {
#[hdl]
let cd: ClockDomain<R> = m.input();
#[hdl]
let out: UInt<8> = m.output();
m.extern_module_simulation_fn((cd, out), |(cd, out), mut sim| async move {
sim.resettable(
cd,
|mut sim: ExternModuleSimulationState| async move {
sim.write(out, 0u8).await;
},
|mut sim: ExternModuleSimulationState, ()| async move {
loop {
sim.wait_for_clock_edge(cd.clk).await;
let v: u8 = sim
.read(out)
.await
.to_bigint()
.try_into()
.expect("known to be in range");
sim.write(out, v.wrapping_add(1)).await;
}
},
)
.await
});
}
fn test_sim_resettable_counter_helper<R: ResetType>(
sim: &mut Simulation<sim_resettable_counter<R>>,
immediate_reset: bool,
) {
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, immediate_reset);
for _ in 0..2 {
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
for expected in 0..3u8 {
assert_eq!(sim.read(sim.io().out), expected.to_sim_value());
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, false);
}
}
}
#[test]
fn test_sim_resettable_counter_sync() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(sim_resettable_counter::<SyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
test_sim_resettable_counter_helper(&mut sim, false);
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_resettable_counter_sync.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync.txt") {
panic!();
}
}
#[test]
fn test_sim_resettable_counter_sync_immediate_reset() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(sim_resettable_counter::<SyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
test_sim_resettable_counter_helper(&mut sim, true);
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.txt") {
panic!();
}
}
#[test]
fn test_sim_resettable_counter_async() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(sim_resettable_counter::<AsyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
test_sim_resettable_counter_helper(&mut sim, false);
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_resettable_counter_async.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_resettable_counter_async.txt") {
panic!();
}
}
#[test]
fn test_sim_resettable_counter_async_immediate_reset() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(sim_resettable_counter::<AsyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
test_sim_resettable_counter_helper(&mut sim, true);
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn phantom_const() {
#[hdl]
let out: Array<PhantomConst<Vec<String>>, 2> =
m.output(Array::new_static(PhantomConst::new_sized(vec![
"a".into(),
"b".into(),
])));
let _ = out;
#[hdl]
let mut mem = memory(PhantomConst::new("mem_element"));
mem.depth(1);
let port = mem.new_read_port();
connect_any(port.addr, 0u8);
connect(port.clk, false.to_clock());
connect(port.en, false);
}
#[test]
fn test_phantom_const() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(phantom_const());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/phantom_const.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/phantom_const.txt") {
panic!();
}
}
#[hdl_module(outline_generated, extern)]
pub fn sim_read_past<const N: usize>()
where
ConstUsize<N>: KnownSize,
{
#[hdl]
let clocks: Array<Clock, N> = m.input();
#[hdl]
let outputs: Array<UInt<8>, N> = m.output();
#[hdl]
let past_clocks: Array<Clock, N> = m.output();
#[hdl]
let past_outputs: Array<UInt<8>, N> = m.output();
for clock in clocks {
m.register_clock_for_past(clock);
}
m.extern_module_simulation_fn(
(clocks, outputs, past_clocks, past_outputs),
|(clocks, outputs, past_clocks, past_outputs), mut sim| async move {
sim.write(outputs, [0u8; N]).await;
sim.write(past_clocks, [false; N]).await;
sim.write(past_outputs, [0u8; N]).await;
loop {
sim.fork_join_scope(|scope, _| async move {
for (clock, output) in clocks.into_iter().zip(outputs) {
scope.spawn_detached(
move |_, mut sim: ExternModuleSimulationState| async move {
sim.wait_for_clock_edge(clock).await;
dbg!(clock);
let v = sim
.read_bool_or_int(output)
.await
.to_bigint()
.try_into()
.expect("known to be in range");
sim.write(output, 1u8.wrapping_add(v)).await;
let past_outputs_v = sim.read_past(outputs, clock).await;
dbg!(&past_outputs_v);
sim.write(past_outputs, past_outputs_v).await;
let past_clocks_v = sim.read_past(clocks, clock).await;
dbg!(&past_clocks_v);
sim.write(past_clocks, past_clocks_v).await;
},
);
}
})
.await;
}
},
);
}
#[test]
fn test_sim_read_past() {
let _n = SourceLocation::normalize_files_for_tests();
const N: usize = 3;
let mut sim = Simulation::new(sim_read_past::<N>());
// sim.set_breakpoints_unstable(Default::default(), true);
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().clocks, [false; N]);
let mut clocks_triggered = [false; N];
let mut expected = [0u8; N];
let mut past_clocks_expected = [false; N];
let mut past_expected = expected;
for i0 in 0..N {
for i1 in 0..N {
for i2 in 0..N {
for i3 in 0..N {
let indexes = [i0, i1, i2, i3];
for i in indexes {
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], true);
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().clocks[i], false);
if !clocks_triggered[i] {
past_expected = expected;
expected[i] = expected[i].wrapping_add(1);
past_clocks_expected = [false; N];
past_clocks_expected[i] = true;
}
dbg!(past_expected);
clocks_triggered[i] = true;
if clocks_triggered == [true; N] {
clocks_triggered = [false; N];
}
let output = sim.read(sim.io().outputs);
assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}");
let past_clocks = sim.read(sim.io().past_clocks);
assert_eq!(
past_clocks,
past_clocks_expected
.to_sim_value_with_type(Array::<Clock, N>::default()),
"indexes={indexes:?} i={i}"
);
let past_outputs = sim.read(sim.io().past_outputs);
dbg!(&past_outputs);
assert_eq!(
past_outputs,
past_expected.to_sim_value(),
"indexes={indexes:?} i={i}"
);
}
}
}
}
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/sim_read_past.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/sim_read_past.txt") {
panic!();
}
}

View file

@ -826,9 +826,9 @@ Simulation {
}.write_index,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "array_rw",
children: [
@ -1699,7 +1699,12 @@ Simulation {
},
),
],
instant: 34 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 34 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -122,9 +122,9 @@ Simulation {
}.i,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "conditional_assignment_last",
children: [
@ -177,7 +177,12 @@ Simulation {
},
),
],
instant: 2 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 2 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -98,9 +98,9 @@ Simulation {
}.o,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "connect_const",
children: [
@ -130,7 +130,12 @@ Simulation {
],
trace_memories: {},
trace_writers: [],
instant: 0 s,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 0 s,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -21,7 +21,7 @@ Simulation {
},
SlotDebugData {
name: "",
ty: Bool,
ty: UInt<1>,
},
SlotDebugData {
name: "",
@ -51,12 +51,12 @@ Simulation {
insns: [
// at: module-XXXXXXXXXX.rs:1:1
0: Const {
dest: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool },
dest: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: UInt<1> },
value: 0x1,
},
1: Copy {
dest: StatePartIndex<BigSlots>(3), // (0x1) SlotDebugData { name: "", ty: AsyncReset },
src: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "", ty: UInt<1> },
},
// at: module-XXXXXXXXXX.rs:4:1
2: Copy {
@ -141,9 +141,9 @@ Simulation {
}.reset_out,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "connect_const_reset",
children: [
@ -197,7 +197,12 @@ Simulation {
},
),
],
instant: 1 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 1 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -100,51 +100,51 @@ Simulation {
dest: StatePartIndex<SmallSlots>(3), // (0x0 0) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::cd.rst", ty: AsyncReset },
},
3: IsNonZeroDestIsSmall {
dest: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::cd.clk", ty: Clock },
},
4: AndSmall {
dest: StatePartIndex<SmallSlots>(1), // (0x0 0) SlotDebugData { name: "", ty: Bool },
lhs: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
rhs: StatePartIndex<SmallSlots>(0), // (0x0 0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:1:1
3: Const {
5: Const {
dest: StatePartIndex<BigSlots>(5), // (0x3) SlotDebugData { name: "", ty: UInt<4> },
value: 0x3,
},
// at: module-XXXXXXXXXX.rs:3:1
4: BranchIfZero {
target: 6,
6: BranchIfZero {
target: 8,
value: StatePartIndex<BigSlots>(6), // (0x0) SlotDebugData { name: "", ty: Bool },
},
5: Copy {
7: Copy {
dest: StatePartIndex<BigSlots>(3), // (0x3) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::count_reg", ty: UInt<4> },
src: StatePartIndex<BigSlots>(5), // (0x3) SlotDebugData { name: "", ty: UInt<4> },
},
// at: module-XXXXXXXXXX.rs:1:1
6: Add {
8: Add {
dest: StatePartIndex<BigSlots>(8), // (0x4) SlotDebugData { name: "", ty: UInt<5> },
lhs: StatePartIndex<BigSlots>(3), // (0x3) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::count_reg", ty: UInt<4> },
rhs: StatePartIndex<BigSlots>(7), // (0x1) SlotDebugData { name: "", ty: UInt<1> },
},
7: CastToUInt {
9: CastToUInt {
dest: StatePartIndex<BigSlots>(9), // (0x4) SlotDebugData { name: "", ty: UInt<4> },
src: StatePartIndex<BigSlots>(8), // (0x4) SlotDebugData { name: "", ty: UInt<5> },
dest_width: 4,
},
// at: module-XXXXXXXXXX.rs:4:1
8: Copy {
10: Copy {
dest: StatePartIndex<BigSlots>(4), // (0x4) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::count_reg$next", ty: UInt<4> },
src: StatePartIndex<BigSlots>(9), // (0x4) SlotDebugData { name: "", ty: UInt<4> },
},
// at: module-XXXXXXXXXX.rs:6:1
9: Copy {
11: Copy {
dest: StatePartIndex<BigSlots>(2), // (0x3) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::count", ty: UInt<4> },
src: StatePartIndex<BigSlots>(3), // (0x3) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::count_reg", ty: UInt<4> },
},
// at: module-XXXXXXXXXX.rs:3:1
10: IsNonZeroDestIsSmall {
dest: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::cd.clk", ty: Clock },
},
11: AndSmall {
dest: StatePartIndex<SmallSlots>(1), // (0x0 0) SlotDebugData { name: "", ty: Bool },
lhs: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
rhs: StatePartIndex<SmallSlots>(0), // (0x0 0) SlotDebugData { name: "", ty: Bool },
},
12: BranchIfSmallNonZero {
target: 16,
value: StatePartIndex<SmallSlots>(3), // (0x0 0) SlotDebugData { name: "", ty: Bool },
@ -261,9 +261,9 @@ Simulation {
}.count,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "counter",
children: [
@ -329,7 +329,7 @@ Simulation {
index: StatePartIndex<BigSlots>(0),
},
state: 0x1,
last_state: 0x1,
last_state: 0x0,
},
SimTrace {
id: TraceScalarId(1),
@ -355,7 +355,7 @@ Simulation {
ty: UInt<4>,
},
state: 0x3,
last_state: 0x3,
last_state: 0x2,
},
],
trace_memories: {},
@ -368,9 +368,14 @@ Simulation {
},
),
],
instant: 66 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(1),
],
event_queue: EventQueue(EventQueueData {
instant: 66 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -26,192 +26,192 @@ b11 $
0!
#3000000
1!
b100 $
b100 #
b100 $
#4000000
0!
#5000000
1!
b101 $
b101 #
b101 $
#6000000
0!
#7000000
1!
b110 $
b110 #
b110 $
#8000000
0!
#9000000
1!
b111 $
b111 #
b111 $
#10000000
0!
#11000000
1!
b1000 $
b1000 #
b1000 $
#12000000
0!
#13000000
1!
b1001 $
b1001 #
b1001 $
#14000000
0!
#15000000
1!
b1010 $
b1010 #
b1010 $
#16000000
0!
#17000000
1!
b1011 $
b1011 #
b1011 $
#18000000
0!
#19000000
1!
b1100 $
b1100 #
b1100 $
#20000000
0!
#21000000
1!
b1101 $
b1101 #
b1101 $
#22000000
0!
#23000000
1!
b1110 $
b1110 #
b1110 $
#24000000
0!
#25000000
1!
b1111 $
b1111 #
b1111 $
#26000000
0!
#27000000
1!
b0 $
b0 #
b0 $
#28000000
0!
#29000000
1!
b1 $
b1 #
b1 $
#30000000
0!
#31000000
1!
b10 $
b10 #
b10 $
#32000000
0!
#33000000
1!
b11 $
b11 #
b11 $
#34000000
0!
#35000000
1!
b100 $
b100 #
b100 $
#36000000
0!
#37000000
1!
b101 $
b101 #
b101 $
#38000000
0!
#39000000
1!
b110 $
b110 #
b110 $
#40000000
0!
#41000000
1!
b111 $
b111 #
b111 $
#42000000
0!
#43000000
1!
b1000 $
b1000 #
b1000 $
#44000000
0!
#45000000
1!
b1001 $
b1001 #
b1001 $
#46000000
0!
#47000000
1!
b1010 $
b1010 #
b1010 $
#48000000
0!
#49000000
1!
b1011 $
b1011 #
b1011 $
#50000000
0!
#51000000
1!
b1100 $
b1100 #
b1100 $
#52000000
0!
#53000000
1!
b1101 $
b1101 #
b1101 $
#54000000
0!
#55000000
1!
b1110 $
b1110 #
b1110 $
#56000000
0!
#57000000
1!
b1111 $
b1111 #
b1111 $
#58000000
0!
#59000000
1!
b0 $
b0 #
b0 $
#60000000
0!
#61000000
1!
b1 $
b1 #
b1 $
#62000000
0!
#63000000
1!
b10 $
b10 #
b10 $
#64000000
0!
#65000000
1!
b11 $
b11 #
b11 $
#66000000

View file

@ -112,21 +112,21 @@ Simulation {
dest: StatePartIndex<SmallSlots>(3), // (0x0 0) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::cd.rst", ty: SyncReset },
},
// at: module-XXXXXXXXXX.rs:1:1
6: Const {
dest: StatePartIndex<BigSlots>(5), // (0x3) SlotDebugData { name: "", ty: UInt<4> },
value: 0x3,
},
// at: module-XXXXXXXXXX.rs:3:1
7: IsNonZeroDestIsSmall {
6: IsNonZeroDestIsSmall {
dest: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(counter: counter).counter::cd.clk", ty: Clock },
},
8: AndSmall {
7: AndSmall {
dest: StatePartIndex<SmallSlots>(1), // (0x0 0) SlotDebugData { name: "", ty: Bool },
lhs: StatePartIndex<SmallSlots>(2), // (0x1 1) SlotDebugData { name: "", ty: Bool },
rhs: StatePartIndex<SmallSlots>(0), // (0x0 0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:1:1
8: Const {
dest: StatePartIndex<BigSlots>(5), // (0x3) SlotDebugData { name: "", ty: UInt<4> },
value: 0x3,
},
// at: module-XXXXXXXXXX.rs:3:1
9: BranchIfSmallZero {
target: 14,
value: StatePartIndex<SmallSlots>(1), // (0x0 0) SlotDebugData { name: "", ty: Bool },
@ -242,9 +242,9 @@ Simulation {
}.count,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "counter",
children: [
@ -310,7 +310,7 @@ Simulation {
index: StatePartIndex<BigSlots>(0),
},
state: 0x1,
last_state: 0x1,
last_state: 0x0,
},
SimTrace {
id: TraceScalarId(1),
@ -336,7 +336,7 @@ Simulation {
ty: UInt<4>,
},
state: 0x3,
last_state: 0x3,
last_state: 0x2,
},
],
trace_memories: {},
@ -349,9 +349,14 @@ Simulation {
},
),
],
instant: 66 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(1),
],
event_queue: EventQueue(EventQueueData {
instant: 66 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -16,199 +16,199 @@ b0 $
$end
#1000000
1!
b11 $
b11 #
b11 $
0"
#2000000
0!
#3000000
1!
b100 $
b100 #
b100 $
#4000000
0!
#5000000
1!
b101 $
b101 #
b101 $
#6000000
0!
#7000000
1!
b110 $
b110 #
b110 $
#8000000
0!
#9000000
1!
b111 $
b111 #
b111 $
#10000000
0!
#11000000
1!
b1000 $
b1000 #
b1000 $
#12000000
0!
#13000000
1!
b1001 $
b1001 #
b1001 $
#14000000
0!
#15000000
1!
b1010 $
b1010 #
b1010 $
#16000000
0!
#17000000
1!
b1011 $
b1011 #
b1011 $
#18000000
0!
#19000000
1!
b1100 $
b1100 #
b1100 $
#20000000
0!
#21000000
1!
b1101 $
b1101 #
b1101 $
#22000000
0!
#23000000
1!
b1110 $
b1110 #
b1110 $
#24000000
0!
#25000000
1!
b1111 $
b1111 #
b1111 $
#26000000
0!
#27000000
1!
b0 $
b0 #
b0 $
#28000000
0!
#29000000
1!
b1 $
b1 #
b1 $
#30000000
0!
#31000000
1!
b10 $
b10 #
b10 $
#32000000
0!
#33000000
1!
b11 $
b11 #
b11 $
#34000000
0!
#35000000
1!
b100 $
b100 #
b100 $
#36000000
0!
#37000000
1!
b101 $
b101 #
b101 $
#38000000
0!
#39000000
1!
b110 $
b110 #
b110 $
#40000000
0!
#41000000
1!
b111 $
b111 #
b111 $
#42000000
0!
#43000000
1!
b1000 $
b1000 #
b1000 $
#44000000
0!
#45000000
1!
b1001 $
b1001 #
b1001 $
#46000000
0!
#47000000
1!
b1010 $
b1010 #
b1010 $
#48000000
0!
#49000000
1!
b1011 $
b1011 #
b1011 $
#50000000
0!
#51000000
1!
b1100 $
b1100 #
b1100 $
#52000000
0!
#53000000
1!
b1101 $
b1101 #
b1101 $
#54000000
0!
#55000000
1!
b1110 $
b1110 #
b1110 $
#56000000
0!
#57000000
1!
b1111 $
b1111 #
b1111 $
#58000000
0!
#59000000
1!
b0 $
b0 #
b0 $
#60000000
0!
#61000000
1!
b1 $
b1 #
b1 $
#62000000
0!
#63000000
1!
b10 $
b10 #
b10 $
#64000000
0!
#65000000
1!
b11 $
b11 #
b11 $
#66000000

View file

@ -102,9 +102,9 @@ Simulation {
uninitialized_ios: {},
io_targets: {},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "duplicate_names",
children: [
@ -160,7 +160,12 @@ Simulation {
},
),
],
instant: 1 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 1 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -1003,65 +1003,64 @@ Simulation {
dest: StatePartIndex<SmallSlots>(5), // (0x0 0) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(1), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.rst", ty: SyncReset },
},
97: IsNonZeroDestIsSmall {
dest: StatePartIndex<SmallSlots>(4), // (0x1 1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.clk", ty: Clock },
},
98: AndSmall {
dest: StatePartIndex<SmallSlots>(3), // (0x0 0) SlotDebugData { name: "", ty: Bool },
lhs: StatePartIndex<SmallSlots>(4), // (0x1 1) SlotDebugData { name: "", ty: Bool },
rhs: StatePartIndex<SmallSlots>(2), // (0x0 0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:1:1
97: Const {
99: Const {
dest: StatePartIndex<BigSlots>(25), // (0x0) SlotDebugData { name: "", ty: UInt<6> },
value: 0x0,
},
98: Copy {
100: Copy {
dest: StatePartIndex<BigSlots>(26), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
src: StatePartIndex<BigSlots>(25), // (0x0) SlotDebugData { name: "", ty: UInt<6> },
},
// at: module-XXXXXXXXXX.rs:12:1
99: BranchIfZero {
target: 107,
101: BranchIfZero {
target: 109,
value: StatePartIndex<BigSlots>(2), // (0x1) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::en", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:13:1
100: BranchIfZero {
target: 102,
102: BranchIfZero {
target: 104,
value: StatePartIndex<BigSlots>(46), // (0x0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:14:1
101: Copy {
103: Copy {
dest: StatePartIndex<BigSlots>(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
src: StatePartIndex<BigSlots>(26), // (0x0) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
},
// at: module-XXXXXXXXXX.rs:13:1
102: BranchIfNonZero {
target: 107,
104: BranchIfNonZero {
target: 109,
value: StatePartIndex<BigSlots>(46), // (0x0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:15:1
103: BranchIfZero {
target: 105,
105: BranchIfZero {
target: 107,
value: StatePartIndex<BigSlots>(48), // (0x0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:16:1
104: Copy {
106: Copy {
dest: StatePartIndex<BigSlots>(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
src: StatePartIndex<BigSlots>(65), // (0xd) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
},
// at: module-XXXXXXXXXX.rs:15:1
105: BranchIfNonZero {
target: 107,
107: BranchIfNonZero {
target: 109,
value: StatePartIndex<BigSlots>(48), // (0x0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:17:1
106: Copy {
108: Copy {
dest: StatePartIndex<BigSlots>(24), // (0x3e) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::the_reg$next", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
src: StatePartIndex<BigSlots>(87), // (0x3e) SlotDebugData { name: "", ty: Enum {A, B(Bundle {0: UInt<1>, 1: Bool}), C(Bundle {a: Array<UInt<1>, 2>, b: SInt<2>})} },
},
// at: module-XXXXXXXXXX.rs:11:1
107: IsNonZeroDestIsSmall {
dest: StatePartIndex<SmallSlots>(4), // (0x1 1) SlotDebugData { name: "", ty: Bool },
src: StatePartIndex<BigSlots>(0), // (0x1) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::cd.clk", ty: Clock },
},
108: AndSmall {
dest: StatePartIndex<SmallSlots>(3), // (0x0 0) SlotDebugData { name: "", ty: Bool },
lhs: StatePartIndex<SmallSlots>(4), // (0x1 1) SlotDebugData { name: "", ty: Bool },
rhs: StatePartIndex<SmallSlots>(2), // (0x0 0) SlotDebugData { name: "", ty: Bool },
},
// at: module-XXXXXXXXXX.rs:10:1
109: Copy {
dest: StatePartIndex<BigSlots>(15), // (0x0) SlotDebugData { name: "InstantiatedModule(enums: enums).enums::b2_out", ty: Enum {HdlNone, HdlSome(Bundle {0: UInt<1>, 1: Bool})} },
@ -1454,9 +1453,9 @@ Simulation {
}.which_out,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "enums",
children: [
@ -1744,7 +1743,7 @@ Simulation {
index: StatePartIndex<BigSlots>(0),
},
state: 0x1,
last_state: 0x1,
last_state: 0x0,
},
SimTrace {
id: TraceScalarId(1),
@ -1924,9 +1923,14 @@ Simulation {
},
),
],
instant: 16 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(3),
],
event_queue: EventQueue(EventQueueData {
instant: 16 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -102,6 +102,7 @@ Simulation {
}.o,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [
SimulationExternModuleState {
@ -136,6 +137,7 @@ Simulation {
},
},
did_initial_settle: true,
clocks_for_past: {},
},
sim: ExternModuleSimulation {
generator: SimGeneratorFn {
@ -186,14 +188,8 @@ Simulation {
running_generator: Some(
...,
),
wait_targets: {
Instant(
20.500000000000 μs,
),
},
},
],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "extern_module",
children: [
@ -234,7 +230,7 @@ Simulation {
index: StatePartIndex<BigSlots>(1),
},
state: 0x1,
last_state: 0x1,
last_state: 0x0,
},
],
trace_memories: {},
@ -247,7 +243,21 @@ Simulation {
},
),
],
instant: 20 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 20 μs,
events: {
Event {
instant: 20.500000000000 μs,
kind: ExternModule(
0,
),
}: Wakers(
1,
),
},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -6,8 +6,9 @@ $upscope $end
$enddefinitions $end
$dumpvars
0!
1"
0"
$end
1"
#500000
#1500000
0"

View file

@ -121,6 +121,7 @@ Simulation {
}.o,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [
SimulationExternModuleState {
@ -167,6 +168,7 @@ Simulation {
},
},
did_initial_settle: true,
clocks_for_past: {},
},
sim: ExternModuleSimulation {
generator: SimGeneratorFn {
@ -234,55 +236,8 @@ Simulation {
running_generator: Some(
...,
),
wait_targets: {
Change {
key: CompiledValue {
layout: CompiledTypeLayout {
ty: Clock,
layout: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 1,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk",
ty: Clock,
},
],
..
},
sim_only_slots: StatePartLayout<SimOnlySlots> {
len: 0,
debug_data: [],
layout_data: [],
..
},
},
body: Scalar,
},
range: TypeIndexRange {
small_slots: StatePartIndexRange<SmallSlots> { start: 0, len: 0 },
big_slots: StatePartIndexRange<BigSlots> { start: 1, len: 1 },
sim_only_slots: StatePartIndexRange<SimOnlySlots> { start: 0, len: 0 },
},
write: None,
},
value: SimValue {
ty: Clock,
value: OpaqueSimValue {
bits: 0x1_u1,
sim_only_values: [],
},
},
},
},
},
],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "extern_module2",
children: [
@ -356,7 +311,113 @@ Simulation {
},
),
],
instant: 60 μs,
clocks_triggered: [],
event_queue: EventQueue(EventQueueData {
instant: 60 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {
SensitivitySet {
id: 59,
values: {
CompiledValue {
layout: CompiledTypeLayout {
ty: Clock,
layout: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 1,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk",
ty: Clock,
},
],
..
},
sim_only_slots: StatePartLayout<SimOnlySlots> {
len: 0,
debug_data: [],
layout_data: [],
..
},
},
body: Scalar,
},
range: TypeIndexRange {
small_slots: StatePartIndexRange<SmallSlots> { start: 0, len: 0 },
big_slots: StatePartIndexRange<BigSlots> { start: 1, len: 1 },
sim_only_slots: StatePartIndexRange<SimOnlySlots> { start: 0, len: 0 },
},
write: None,
}: SimValue {
ty: Clock,
value: OpaqueSimValue {
bits: 0x1_u1,
sim_only_values: [],
},
},
},
changed: Cell {
value: false,
},
..
},
},
waiting_sensitivity_sets_by_compiled_value: {
CompiledValue {
layout: CompiledTypeLayout {
ty: Clock,
layout: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
len: 1,
debug_data: [
SlotDebugData {
name: "InstantiatedModule(extern_module2: extern_module2).extern_module2::clk",
ty: Clock,
},
],
..
},
sim_only_slots: StatePartLayout<SimOnlySlots> {
len: 0,
debug_data: [],
layout_data: [],
..
},
},
body: Scalar,
},
range: TypeIndexRange {
small_slots: StatePartIndexRange<SmallSlots> { start: 0, len: 0 },
big_slots: StatePartIndexRange<BigSlots> { start: 1, len: 1 },
sim_only_slots: StatePartIndexRange<SimOnlySlots> { start: 0, len: 0 },
},
write: None,
}: (
SimValue {
ty: Clock,
value: OpaqueSimValue {
bits: 0x1_u1,
sim_only_values: [],
},
},
{
SensitivitySet {
id: 59,
..
},
},
),
},
..
}

View file

@ -8,8 +8,9 @@ $enddefinitions $end
$dumpvars
1!
0"
b1001000 #
b0 #
$end
b1001000 #
#1000000
1"
b1100101 #

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -719,9 +719,9 @@ Simulation {
}.w.mask.1,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "memories",
children: [
@ -1616,10 +1616,15 @@ Simulation {
},
),
],
instant: 22 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(1),
StatePartIndex<SmallSlots>(6),
],
event_queue: EventQueue(EventQueueData {
instant: 22 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -234,13 +234,13 @@ b100000 6
b10000 9
b100000 I
1#
1(
1/
14
b10000 $
b100000 %
1(
1/
b10000 0
b100000 1
14
#4000000
0#
0(
@ -256,11 +256,11 @@ b1000000 6
b10000 9
b1000000 I
1#
b1000000 %
1(
1/
14
b1000000 %
b1000000 1
14
#6000000
0#
0(
@ -278,11 +278,11 @@ b1100000 6
b1010000 9
b1000000 I
1#
b1010000 $
1(
1/
14
b1010000 $
b1010000 0
14
#8000000
0#
0(

View file

@ -677,9 +677,9 @@ Simulation {
}.rw.wmode,
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "memories2",
children: [
@ -1260,9 +1260,14 @@ Simulation {
},
),
],
instant: 22 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(3),
],
event_queue: EventQueue(EventQueueData {
instant: 22 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -100,8 +100,8 @@ $end
1)
#1250000
1#
1*
b11 $
1*
sHdlSome\x20(1) +
1,
#1500000
@ -113,8 +113,8 @@ sHdlSome\x20(1) +
0)
#2250000
1#
1*
b0 $
1*
sHdlNone\x20(0) +
0,
#2500000
@ -303,8 +303,8 @@ b11 !
b11 (
#17250000
1#
1*
b11 $
1*
sHdlSome\x20(1) +
1,
#17500000
@ -316,8 +316,8 @@ b10 !
b10 (
#18250000
1#
1*
b0 $
1*
sHdlNone\x20(0) +
0,
#18500000
@ -339,8 +339,8 @@ b1 !
b1 (
#20250000
1#
1*
b1 $
1*
sHdlSome\x20(1) +
#20500000
#20750000
@ -353,8 +353,8 @@ b0 (
0)
#21250000
1#
1*
b0 $
1*
sHdlNone\x20(0) +
#21500000
#21750000

View file

@ -1761,9 +1761,9 @@ Simulation {
}.w.mask[7],
},
did_initial_settle: true,
clocks_for_past: {},
},
extern_modules: [],
state_ready_to_run: false,
trace_decls: TraceModule {
name: "memories3",
children: [
@ -3275,10 +3275,15 @@ Simulation {
},
),
],
instant: 15 μs,
clocks_triggered: [
StatePartIndex<SmallSlots>(1),
StatePartIndex<SmallSlots>(6),
],
event_queue: EventQueue(EventQueueData {
instant: 15 μs,
events: {},
}),
waiting_sensitivity_sets_by_address: {},
waiting_sensitivity_sets_by_compiled_value: {},
..
}

View file

@ -420,6 +420,10 @@ b10000 T
1\
#3250000
1#
b110100 %
b1111000 '
b10011010 (
b11110000 +
1.
1A
b110100 C
@ -427,10 +431,6 @@ b1111000 E
b10011010 F
b11110000 I
1L
b110100 %
b1111000 '
b10011010 (
b11110000 +
#3500000
#3750000
0#
@ -508,6 +508,14 @@ b1010100 '"
b110010 /"
b10000 7"
1#
b11111110 $
b11011100 %
b10111010 &
b10011000 '
b1110110 (
b1010100 )
b110010 *
b10000 +
1.
1A
b11111110 B
@ -519,14 +527,6 @@ b1010100 G
b110010 H
b10000 I
1L
b11111110 $
b11011100 %
b10111010 &
b10011000 '
b1110110 (
b1010100 )
b110010 *
b10000 +
#6500000
#6750000
0#
@ -562,6 +562,14 @@ b1000110 ("
b10001010 0"
b11001110 8"
1#
b0 $
b0 %
b0 &
b0 '
b0 (
b0 )
b0 *
b0 +
1.
1A
b0 B
@ -573,14 +581,6 @@ b0 G
b0 H
b0 I
1L
b0 $
b0 %
b0 &
b0 '
b0 (
b0 )
b0 *
b0 +
#7500000
#7750000
0#
@ -688,6 +688,14 @@ b1 !
b1 ?
#10250000
1#
b11111110 $
b11011100 %
b10111010 &
b10011000 '
b1110110 (
b1010100 )
b110010 *
b10000 +
1.
1A
b11111110 B
@ -699,14 +707,6 @@ b1010100 G
b110010 H
b10000 I
1L
b11111110 $
b11011100 %
b10111010 &
b10011000 '
b1110110 (
b1010100 )
b110010 *
b10000 +
#10500000
#10750000
0#
@ -718,6 +718,14 @@ b10 !
b10 ?
#11250000
1#
b10011 $
b1010111 %
b10011011 &
b11011111 '
b10 (
b1000110 )
b10001010 *
b11001110 +
1.
1A
b10011 B
@ -729,14 +737,6 @@ b1000110 G
b10001010 H
b11001110 I
1L
b10011 $
b1010111 %
b10011011 &
b11011111 '
b10 (
b1000110 )
b10001010 *
b11001110 +
#11500000
#11750000
0#
@ -748,6 +748,14 @@ b11 !
b11 ?
#12250000
1#
b1110100 $
b1100101 %
b1110011 &
b1110100 '
b1101001 (
b1101110 )
b1100111 *
b100001 +
1.
1A
b1110100 B
@ -759,14 +767,6 @@ b1101110 G
b1100111 H
b100001 I
1L
b1110100 $
b1100101 %
b1110011 &
b1110100 '
b1101001 (
b1101110 )
b1100111 *
b100001 +
#12500000
#12750000
0#
@ -780,6 +780,14 @@ b0 ?
0@
#13250000
1#
b1101101 $
b1101111 %
b1110010 &
b1100101 '
b100000 (
b1110100 )
b1110011 *
b1110100 +
1.
1A
b1101101 B
@ -791,14 +799,6 @@ b1110100 G
b1110011 H
b1110100 I
1L
b1101101 $
b1101111 %
b1110010 &
b1100101 '
b100000 (
b1110100 )
b1110011 *
b1110100 +
#13500000
#13750000
0#
@ -808,6 +808,14 @@ b1110100 +
#14000000
#14250000
1#
b0 $
b0 %
b0 &
b0 '
b0 (
b0 )
b0 *
b0 +
1.
1A
b0 B
@ -819,14 +827,6 @@ b0 G
b0 H
b0 I
1L
b0 $
b0 %
b0 &
b0 '
b0 (
b0 )
b0 *
b0 +
#14500000
#14750000
0#

Some files were not shown because too many files have changed in this diff Show more