Compare commits

...
Sign in to create a new pull request.

276 commits

Author SHA1 Message Date
a93e66d8ab
update ui test's expected output for having rust-src available 2026-03-17 20:43:46 -07:00
eb3ca59053
add rust-src to CI 2026-03-17 20:42:54 -07:00
dbed947408
change VCD id generation to be based on hashing the path, making them better for git diff 2026-02-23 20:05:10 -08:00
cb4e1f42c0
silence unused import warning 2026-02-23 16:07:46 -08:00
8c270b0e35
silence warning for enums with only one variant 2026-02-23 16:07:05 -08:00
c632e5d570
speed up simulation by optimizing SimulationImpl::read_traces
this makes cpu/crates/cpu/tests/next_pc.rs take 56s instead of 168s
2026-02-04 15:41:09 -08:00
1bc835803b
speed up LazyInterned by redoing caching using RwLock and add a thread-local cache 2026-02-03 18:00:36 -08:00
9db3240644
fix UI test's expected output 2026-02-03 18:00:36 -08:00
caa097db0b
change rust version to 1.93.0 2026-02-03 17:29:59 -08:00
a96efa9696
cache interned UInt/SInt types 2026-02-02 17:51:30 -08:00
4ac1bcbc0a
change Interner to use a sharded hash table 2026-02-02 15:49:26 -08:00
39810043ea
move Interner into new mod interner 2026-02-02 15:44:37 -08:00
26b0dc3fd8
change Interner to pub(crate) 2026-02-02 15:42:12 -08:00
11281a9842
add a thread-local cache when using TypeIdMap 2026-02-01 21:19:32 -08:00
e366793204
don't compare function pointers -- they're non-deterministic 2026-01-12 03:11:36 -08:00
a398f8f185
Define design safety, and prove it for 1-step and 2-step induction. 2025-12-22 22:30:13 -03:00
4fd4371054
Spelling. 2025-12-22 22:30:06 -03:00
c97b44d9d6
simplify SimValue Debug format, making complex structures much easier to read 2025-12-14 20:59:48 -08:00
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
c06ef56482
add NLnet grant 2024-12-324 to readme 2025-09-08 23:08:25 -07:00
db9b1c202c
add simulator support for sim-only values 2025-09-08 22:19:43 -07:00
d3dd66cbf0
add rust-src component in CI for consistent error messages 2025-09-08 22:18:10 -07:00
b5b1ee866c
converted to using get_state_part_kinds! 2025-09-05 19:10:06 -07:00
f0e3aef061
add get_state_part_kinds! macro 2025-09-05 19:07:07 -07:00
6d36698adf
move public paths of sim::{Compiled,Compiler} to sim::compiler 2025-08-26 19:23:21 -07:00
e7e831cf00
split out simulator compiler into a separate module 2025-08-26 19:17:21 -07:00
4008c311bf
format code after switching to edition 2024 2025-08-24 16:35:21 -07:00
ef85d11327
try to get actions to run 2025-08-24 16:14:03 -07:00
ae7c4be9dc
remove get_many_mut since it was stabilized in std as get_disjoint_mut 2025-08-24 15:53:21 -07:00
65f9ab32f4
switch to edition 2024 2025-08-24 15:53:21 -07:00
67e66ac3bd
upgrade to rust 1.89.0 2025-08-24 15:53:21 -07:00
668e714dc9
actually test always_zero hasher 2025-04-09 21:11:09 -07:00
88323a8c16
run some tests with always_zero hasher 2025-04-09 21:03:57 -07:00
91e1b619e8
switch to petgraph 0.8.1 now that my PR was merged and released to crates.io 2025-04-09 20:48:40 -07:00
e2d2d4110b
upgrade hashbrown to 0.15.2 2025-04-09 20:33:21 -07:00
b1f9706e4e
add custom hasher for testing 2025-04-09 20:27:22 -07:00
4eda4366c8
check types in debug mode in impl Debug for Expr, helping to catch bugs 2025-04-09 20:23:19 -07:00
122c08d3cf
add fake which for miri 2025-04-09 20:21:43 -07:00
b08a747e20
switch to using type aliases for HashMap/HashSet to allow easily switching hashers 2025-04-09 20:17:46 -07:00
e0c9939147
add test that SimValue can't be interned, since its PartialEq may ignore types 2025-04-09 19:55:09 -07:00
07725ab489
switch interning to use HashTable rather than HashMap 2025-04-09 19:30:02 -07:00
36f1b9bbb6
add derive(Debug) to all types that are interned 2025-04-09 19:24:08 -07:00
9a1b047d2f
change TypeIdMap to not use any unsafe code 2025-04-09 16:25:56 -07:00
5967e812a2
fix [SU]IntValue's PartialEq for interning
different widths must make values compare not equal otherwise interning
will e.g. substitute a 0x0_u8 for a 0x0_u2
2025-04-08 21:57:56 -07:00
001fd31451
add UIntInRange[Inclusive][Type] 2025-04-07 18:27:54 -07:00
57aae7b7fb
implement [de]serializing BaseTypes, SimValues, and support PhantomConst<T> in #[hdl] struct S<T> 2025-04-04 01:04:26 -07:00
6929352be7
re-export bitvec and add types useful for simulation to the prelude 2025-04-03 16:01:39 -07:00
62058dc141
fix cargo doc warnings -- convert urls to auto links 2025-04-01 22:22:54 -07:00
c4b6a0fee6
add support for #[hdl(sim)] enum_ty.Variant(value) and #[hdl(sim)] EnumTy::Variant(value) and non-sim variants too 2025-04-01 22:16:47 -07:00
9092e45447
fix #[hdl(sim)] match on enums 2025-03-30 01:25:07 -07:00
a40eaaa2da
expand SimValue support 2025-03-30 00:55:38 -07:00
5028401a5a
change SimValue to contain and deref to a value and not just contain bits 2025-03-27 23:44:36 -07:00
e0f978fbb6
silence unused m variable warning in #[hdl_module] with an empty body. 2025-03-27 23:17:28 -07:00
ec3a61513b
simulator read/write types must be passive 2025-03-27 23:03:44 -07:00
fdc73b5f3b
add ripple counter test to test simulating alternating circuits and extern modules 2025-03-25 18:56:26 -07:00
a115585d5a
simulator: allow external module generators to wait for value changes and/or clock edges 2025-03-25 18:26:48 -07:00
ab9ff4f2db
simplify setting an extern module simulation 2025-03-21 17:08:29 -07:00
d1bd176b28
implement simulation of extern modules 2025-03-21 01:47:14 -07:00
920d8d875f
add some missing #[track_caller] 2025-03-19 17:10:51 -07:00
d453755bb2
add ExprPartialEq/ExprPartialOrd impls for PhantomConst 2025-03-10 19:40:03 -07:00
450e1004b6
fix using fayalite as a dependency 2025-03-09 23:14:14 -07:00
c0c5b550bc
add PhantomConst 2025-03-09 21:03:47 -07:00
2fa0ea6192
make FillInDefaultedGenerics work with Sizes and not just Types 2025-03-09 20:59:21 -07:00
bd75fdfefd
add efficient prefix-sums and reductions 2025-03-02 23:04:17 -08:00
50c86e18dc
add Expr<ArrayType<T, Len>>: IntoIterator and Expr<Array<T>>: FromIterator<T> 2025-03-02 18:02:34 -08:00
60734cc9d1
switch CI to use mirrors 2025-03-02 17:43:29 -08:00
3458c21f44
add #[hdl(cmp_eq)] to implement HdlPartialEq automatically 2025-02-16 20:48:16 -08:00
43797db36e
sort custom keywords 2025-02-16 20:46:54 -08:00
cdd84953d0
support unknown trait bounds in type parameters 2025-02-13 18:35:30 -08:00
86a1bb46be
add #[hdl] let destructuring and, while at it, tuple patterns 2025-02-10 22:49:41 -08:00
209d5b5fe1
fix broken doc links 2025-02-10 22:49:16 -08:00
d4ea826051
sim: fix "label address not set" bug when the last Assignment is conditional 2025-01-15 19:04:40 -08:00
404a2ee043
tests/sim: add test_array_rw 2025-01-12 21:38:59 -08:00
e3a2ccd41c
properly handle duplicate names in vcd 2025-01-09 22:52:22 -08:00
3771cea78e
Gather the FIFO debug ports in a bundle 2024-12-29 13:17:24 -03:00
dcf865caec
Add assertions and debug ports in order for the FIFO to pass induction
As some proofs involving memories, it is necessary to add more ports to
the queue interface, to sync state. These changes are predicated on the
test environment, so normal use is not affected.

Since some speedup is achieved, use the saved time to test with a deeper
FIFO.
2024-12-29 13:12:58 -03:00
31d01046a8
Initial queue formal proof based on one-entry FIFO equivalence
For now, only check that the basic properties work in bounded model check
mode, leave the induction proof for later.

Partially replace the previously existing proof.

Remove earlier assumptions and bounds that don't apply for this proof.

Use parameterized types instead of hard-coded types.
2024-12-29 13:04:01 -03:00
c16726cee6
fix #[hdl]/#[hdl_module] attributes getting the wrong hygiene when processing #[cfg]s 2024-12-29 00:48:15 -08:00
b63676d0ca
add test for cfgs 2024-12-28 23:39:50 -08:00
7005fa3330
implement handling #[cfg] and #[cfg_attr] in proc macro inputs 2024-12-28 23:39:08 -08:00
2ab8428062
upgrade syn version 2024-12-28 23:39:08 -08:00
9b06019bf5
make sim::Compiler not print things to stdout unless you ask for it 2024-12-18 21:15:09 -08:00
36bad52978
sim: fix sim.write to struct 2024-12-18 20:50:50 -08:00
21c73051ec
sim: add SimValue and reading/writing more than just a scalar 2024-12-18 01:39:35 -08:00
304d8da0e8
Merge remote-tracking branch 'origin/master' into adding-simulator 2024-12-13 15:06:45 -08:00
2af38de900
add more memory tests 2024-12-13 15:04:48 -08:00
c756aeec70
tests/sim: add test for memory rw port 2024-12-12 20:50:41 -08:00
903ca1bf30
sim: simple memory test works! 2024-12-12 19:47:57 -08:00
8d030ac65d
sim/interpreter: add addresses to instruction listing 2024-12-12 16:25:38 -08:00
562c479b62
sim/interpreter: fix StatePartLayout name in debug output 2024-12-12 15:06:17 -08:00
393f78a14d
sim: add WIP memory test 2024-12-11 23:28:15 -08:00
8616ee4737
tests/sim: test_enums works! 2024-12-11 00:01:04 -08:00
5087f16099
sim: fix assignments graph by properly including conditions as assignment inputs 2024-12-11 00:00:21 -08:00
6b31e6d515
sim: add .dot output for Assignments graph for debugging 2024-12-10 23:40:33 -08:00
564ccb30bc
sim/vcd: fix variable identifiers to follow verilog rules 2024-12-10 23:39:17 -08:00
ca759168ff
tests/sim: add WIP test for enums 2024-12-10 23:37:26 -08:00
e4cf66adf8
sim: implement memories, still needs testing 2024-12-09 23:03:01 -08:00
cd0dd7b7ee
change memory write latency to NonZeroUsize to match read latency being usize 2024-12-09 23:01:40 -08:00
Cesar Strauss
2e7d685dc7 add module exercising formal verification of memories 2024-12-08 17:13:26 -03:00
9654167ca3
sim: WIP working on memory 2024-12-06 15:53:34 -08:00
3ed7827485
sim: WIP adding memory support 2024-12-05 21:35:23 -08:00
e504cfebfe
add BoolOrIntType::copy_bits_from_bigint_wrapping and take BigInt arguments by reference 2024-12-05 20:32:15 -08:00
9f42cab471
change to version 0.3.0 for breaking change 2024-12-05 20:26:28 -08:00
259bee39c2
tests/sim: split expected output text into separate files 2024-12-05 18:17:13 -08:00
643816d5b5
vcd: handle enums with fields 2024-12-04 21:03:29 -08:00
42afd2da0e
sim: implement enums (except for connecting unequal enum types) 2024-12-04 20:58:39 -08:00
15bc304bb6
impl ToExpr for TargetBase 2024-12-04 20:57:44 -08:00
4422157db8
WIP adding enums to simulator 2024-12-02 21:06:23 -08:00
d3f52292a1
test doc tests in CI 2024-12-01 20:21:26 -08:00
fd45465d35
sim: add support for registers 2024-12-01 20:14:13 -08:00
5e0548db26
vcd: single bit signals have no spaces in their value changes 2024-12-01 20:12:43 -08:00
12b3ba57f1
add some ExprCastTo supertraits to ResetType to make generic code easier 2024-12-01 20:10:25 -08:00
965fe53077
deduce_resets: show more debugging info on assertion failure 2024-12-01 20:09:17 -08:00
3abba7f9eb
simulating circuits with deduced resets works 2024-11-27 23:52:07 -08:00
6446b71afd
deduce_resets works! 2024-11-27 23:24:11 -08:00
d36cf92d7f
make ToReset generic over the reset type 2024-11-27 23:19:55 -08:00
d744d85c66
working on deduce_resets 2024-11-27 01:31:18 -08:00
358cdd10c8
add more expr casts 2024-11-27 01:30:28 -08:00
9128a84284
Merge remote-tracking branch 'origin/master' into adding-simulator 2024-11-26 21:28:22 -08:00
546010739a
working on deduce_resets 2024-11-26 21:26:56 -08:00
9b5f1218fd
make ClockDomain and Reg generic over reset type 2024-11-26 20:47:03 -08:00
89d84551f8
add ResetType to the list of recognized type bounds 2024-11-26 18:52:03 -08:00
c45624e3c2
Fix SInt::for_value not accounting for sign bit for positive values
Fixes: #4
2024-11-26 16:26:29 -08:00
7851bf545c
working on deduce_resets.rs 2024-11-26 00:07:11 -08:00
3e3da53bd2
working on deduce_resets 2024-11-25 00:01:02 -08:00
fa50930ff8
update petgraph dependency to include UnionFind::new_set() 2024-11-25 00:00:26 -08:00
9516fe03a1
increase rust version in CI too 2024-11-24 14:46:25 -08:00
52ab134673
increase rust version to support omitting match arms with uninhabited types 2024-11-24 14:41:39 -08:00
698b8adc23
working on deduce_resets pass 2024-11-24 14:39:32 -08:00
59be3bd645
WIP working on implementing deduce_resets pass 2024-11-24 03:44:31 -08:00
913baa37e9
WIP adding deduce_resets pass 2024-11-22 16:07:18 -08:00
11ddbc43c7
writing VCD for combinatorial circuits works! 2024-11-20 22:53:54 -08:00
c4b5d00419
WIP adding VCD output 2024-11-20 22:53:54 -08:00
09aa9fbc78
wire up simulator trace writing interface 2024-11-20 22:53:54 -08:00
288a6b71b9
WIP adding VCD output 2024-11-20 22:53:54 -08:00
0095570f19
simple combinatorial simulation works! 2024-11-20 22:53:54 -08:00
f54e55a143
Simulation::settle_step() works for simple modules 2024-11-20 22:53:54 -08:00
a6e40839ac
simulator WIP: use petgraph for topological sort over assignments 2024-11-20 22:53:54 -08:00
3106a6fff6
working on simulator... 2024-11-20 22:53:54 -08:00
f338f37d3e
working on simulator 2024-11-20 22:53:54 -08:00
277d3e0d4d
working on simulator 2024-11-20 22:53:54 -08:00
b288d6f8f2
add missing copyright headers 2024-11-20 22:53:54 -08:00
479d59b287
WIP implementing simulator 2024-11-20 22:53:54 -08:00
6f904148c4
WIP adding simulator 2024-11-20 22:53:54 -08:00
3ea0d98924
always write formal cache json 2024-11-20 22:51:40 -08:00
Cesar Strauss
c1f1a8b749 Add test module exercising formal verification. 2024-11-20 18:29:39 -03:00
3d5d8c54b6
add repository to cache key 2024-10-30 20:55:02 -07:00
ee15fd2b94
support #[hdl] type aliases 2024-10-30 20:47:10 -07:00
20cf0abbcc
fix using #[hdl] types like S<{ 1 + 2 }> 2024-10-30 20:46:11 -07:00
5bd0de48b7
change to version 0.2.1 2024-10-30 19:36:05 -07:00
0c9c48a066
split out deps into separate workflow with better caching using deps.yml from cpu.git 2024-10-17 21:05:18 -07:00
cb17913004
limit sby to one thread each since it seems not to respect job count in parallel mode 2024-10-15 21:32:38 -07:00
42effd1132
switch to using a make job server for managing test parallelism 2024-10-15 20:32:33 -07:00
3d0f95cfe5
formal: add workaround for wires disappearing because yosys optimizes them out 2024-10-15 01:48:48 -07:00
3939ce2360
add Bundle and Enum to prelude 2024-10-14 17:47:58 -07:00
d0229fbcfb
get #[hdl] struct S<A: KnownSize, B: KnownSize> to work 2024-10-11 17:30:49 -07:00
4909724995
add more thorough checks that bounds are properly handled on #[hdl] structs 2024-10-10 23:34:46 -07:00
d0694cbd52
add disabled test for #[hdl] struct S4<W: KnownSize, W2: KnownSize> which type errors 2024-10-10 22:58:15 -07:00
1a2149b040
silence warnings for field names that start with _ 2024-10-10 20:53:29 -07:00
59cef3f398
add PhantomData as a hdl bundle 2024-10-10 20:48:09 -07:00
bf907c3872
cache results of formal proofs 2024-10-07 23:31:24 -07:00
99180eb3b4
fix clippy lints in generated code 2024-10-07 22:06:59 -07:00
017c14a2f1
don't use #[allow(..., reason = "...")] since that's not stable yet on rust 1.80.1 2024-10-07 22:06:59 -07:00
ed1aea41f3
clean up some clippy warnings 2024-10-07 21:49:18 -07:00
f12322aa2a
remove interning contexts 2024-10-07 21:33:56 -07:00
44ca1a607a
remove unused AGCContext 2024-10-07 21:23:13 -07:00
30b9a5e48d
change NameId to have an opaque Id so output firrtl doesn't depend on how many modules of the same name were ever created 2024-10-07 19:06:01 -07:00
eed0afc6ab
add some utility From<Interned<T>> impls 2024-10-07 19:05:20 -07:00
aec383c0af
try to fix ccache 2024-10-06 20:57:42 -07:00
f403eed7c0
only run tests once, since they are quite slow 2024-10-06 20:08:39 -07:00
2e8b73d2fc
rename fire/fire_data to firing/firing_data 2024-10-06 19:04:48 -07:00
e05c368688
change register names to end in _reg by convention 2024-10-06 18:50:09 -07:00
ec77559e2b
fix cache action name 2024-10-04 17:10:06 -07:00
b7f1101164
reduce parallelism to fit within the number of available cpus even when running sby in prove mode (which likes to run 2 smt solvers in parallel) 2024-10-04 17:03:51 -07:00
487af07154
yosys build runs out of memory 2024-10-04 01:03:17 -07:00
c0d4de56a9
try to make yosys build faster 2024-10-03 23:40:44 -07:00
9f154e6b96
try caching ccache manually 2024-10-03 23:36:39 -07:00
0d54b9a2a9
queue formal proof passes! 2024-10-03 23:07:14 -07:00
343805f80b
fix #[hdl] to work with unusual identifier hygiene from macros 2024-10-03 23:04:14 -07:00
15a28aa7a7
install python3-click -- needed by symbiyosys 2024-10-03 01:44:06 -07:00
4084a70485
switch default solver to z3 2024-10-03 01:43:46 -07:00
3e2fb9b94f
WIP getting queue formal to pass -- passes for capacity <= 2 2024-10-03 01:08:01 -07:00
bc26fe32fd
add ccache and clean up deps 2024-10-03 01:01:06 -07:00
eb65bec26e
add yosys deps 2024-10-03 00:44:04 -07:00
4497f09ea0
fix wrong build steps 2024-10-03 00:39:18 -07:00
1c63a441a9
add needed tools to CI 2024-10-03 00:35:43 -07:00
0cf01600b3
add mod formal and move assert/assume/cover stuff to it 2024-10-01 19:56:17 -07:00
f3d6528f5b
make annotations easier to use 2024-10-01 19:54:17 -07:00
f35d88d2bb
remove unused valueless.rs 2024-10-01 18:41:41 -07:00
e8c393f3bb
sort pub mod items 2024-10-01 18:40:52 -07:00
d0b406d288
add more annotation kinds 2024-10-01 18:33:32 -07:00
2a25dd9d7b
fix annotations getting lost 2024-10-01 18:31:44 -07:00
6e0b6c000d
remove stray debugging prints 2024-10-01 18:30:46 -07:00
d089095667
change default to --simplify-enums=replace-with-bundle-of-uints 2024-10-01 00:07:48 -07:00
9d66fcc548
improve ExportOptions support in assert_export_firrtl! 2024-10-01 00:05:39 -07:00
186488a82e
remove FIXME now that simplify_enums is fixed 2024-09-30 23:31:45 -07:00
edcea1adc3
add firrtl comments when connecting expressions with different types 2024-09-30 22:33:27 -07:00
30a38bc8da
fix simplify_enums to properly handle nested enums and connects with different types 2024-09-30 22:31:16 -07:00
1e2831da47
add validation of connects and matches when validating module
this is useful for catching errors in transformation passes
2024-09-30 21:20:35 -07:00
d2ba313f0f
fix simplify_memories trying to connect Bool with UInt 2024-09-30 21:19:20 -07:00
04752c5037
add test for connect_any with nested enums with different-sized variant bodies
simplify_enums is currently broken in that case
2024-09-25 21:55:52 -07:00
e661aeab11
add WIP formal proof for queue() 2024-09-25 02:00:06 -07:00
5fc7dbd6e9
add assert_formal helper for running formal proofs in rust tests 2024-09-25 02:00:06 -07:00
45dbb554d0
add formal subcommand 2024-09-25 02:00:06 -07:00
bb860d54cc
add command line options for selecting which transforms to apply when generating firrtl 2024-09-25 02:00:06 -07:00
efc3a539ed
support redirecting subprocesses' stdout/stderr to print!() so it gets captured for rust tests 2024-09-25 02:00:06 -07:00
f32c0a7863
switch to #[derive(Parser)] instead of #[derive(Args)] 2024-09-25 01:28:11 -07:00
4ff01690a7
clean up deps and move missed deps to workspace 2024-09-25 01:22:35 -07:00
28aad19bf5
add assert/assume/cover 2024-09-23 19:10:51 -07:00
716c65edcd
add WIP version of queue() 2024-09-22 18:59:12 -07:00
f6146048d1
add memory::splat_mask to generate mask types from a Bool 2024-09-22 18:57:30 -07:00
a701f99fd6
add repeat() 2024-09-22 18:56:26 -07:00
78edfc97b2
split int::IntCmp into expr::HdlPartialEq and expr::HdlPartialOrd 2024-09-22 17:28:46 -07:00
9ad4ec0f39
add ty.uninit() 2024-09-22 17:26:23 -07:00
8449854cac
add ToExpr for usize/isize/NonZero<T> 2024-09-22 17:19:58 -07:00
790bb15408
remove reset_default from proc-macro, forgot to remove when removing from RegBuilder 2024-09-22 16:03:20 -07:00
bdbc6d89bd
add check-copyright to CI 2024-09-22 15:30:53 -07:00
10ae95fac1
add missing copyright headers 2024-09-22 15:30:05 -07:00
053391b010
add script for checking copyright headers 2024-09-22 15:29:28 -07:00
51ce7b079e
add ReadyValid<T> 2024-09-20 19:11:30 -07:00
ff269e5def
add utility functions on HdlOption, inspired by Option's API 2024-09-20 18:49:12 -07:00
df55a514e4
add support for incomplete_wire -- a wire that you can supply the type of later 2024-09-20 18:46:56 -07:00
ff94dda922
support #[hdl] on functions -- enables #[hdl] usage in function body 2024-09-20 18:42:24 -07:00
175 changed files with 109298 additions and 3797 deletions

View file

@ -1,19 +1,27 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on: [push, pull_request]
jobs:
test:
runs-on: debian-12
container:
image: git.libre-chip.org/libre-chip/fayalite-deps:latest
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1
source "$HOME/.cargo/env"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://github.com/Swatinem/rust-cache@v2
scripts/check-copyright.sh
- uses: https://git.libre-chip.org/mirrors/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: rustup override set 1.93.0
- run: rustup component add rust-src
- run: cargo test
- run: cargo test --features=unstable-doc
- run: cargo build --tests --features=unstable-doc
- run: cargo test --doc --features=unstable-doc
- run: cargo doc --features=unstable-doc
- run: 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

4
.gitignore vendored
View file

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

382
Cargo.lock generated
View file

@ -1,18 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
version = 4
[[package]]
name = "allocator-api2"
@ -37,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"
@ -56,7 +44,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -66,9 +54,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -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"
@ -109,6 +115,20 @@ dependencies = [
"wyz",
]
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"serde",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -118,6 +138,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -126,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",
@ -136,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",
@ -147,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",
@ -160,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"
@ -170,6 +208,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -189,6 +233,27 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctor"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "derive_destructure2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -218,7 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -239,32 +304,43 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fayalite"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"base64",
"bitvec",
"blake3",
"clap",
"clap_complete",
"ctor",
"eyre",
"fayalite-proc-macros",
"fayalite-visit-gen",
"hashbrown",
"jobslot",
"num-bigint",
"num-traits",
"once_cell",
"ordered-float",
"petgraph",
"serde",
"serde_json",
"sha2",
"tempfile",
"trybuild",
"vec_map",
"which",
]
[[package]]
name = "fayalite-proc-macros"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"fayalite-proc-macros-impl",
]
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"base16ct",
"num-bigint",
@ -278,7 +354,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"indexmap",
"prettyplease",
@ -290,6 +366,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "funty"
version = "2.0.0"
@ -306,6 +394,18 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -314,12 +414,13 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.14.3"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"ahash",
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
@ -334,7 +435,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -345,9 +446,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@ -367,10 +468,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "libc"
version = "0.2.153"
name = "jobslot"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "58715c67c327da7f1558708348d68c207fd54900c4ae0529e29305d04d795b8c"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.61.2",
]
[[package]]
name = "libc"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "linux-raw-sys"
@ -380,11 +495,10 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "num-bigint"
version = "0.4.4"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@ -409,9 +523,32 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.19.0"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"num-traits",
"rand",
"serde",
]
[[package]]
name = "petgraph"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06"
dependencies = [
"fixedbitset",
"hashbrown",
"indexmap",
"serde",
]
[[package]]
name = "prettyplease"
@ -425,9 +562,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.83"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@ -441,12 +578,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"
@ -457,7 +619,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -466,6 +628,12 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.202"
@ -509,6 +677,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
@ -517,9 +691,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.66"
version = "2.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
dependencies = [
"proc-macro2",
"quote",
@ -541,7 +715,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -606,12 +780,36 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "which"
version = "6.0.1"
@ -655,6 +853,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"
@ -665,14 +869,24 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.4"
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -681,45 +895,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winsafe"
@ -727,6 +947,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"
@ -735,23 +961,3 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -5,31 +5,43 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.2.0"
version = "0.3.0"
license = "LGPL-3.0-or-later"
edition = "2021"
edition = "2024"
repository = "https://git.libre-chip.org/libre-chip/fayalite"
keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"]
categories = ["simulation", "development-tools", "compilers"]
rust-version = "1.80.1"
rust-version = "1.93.0"
[workspace.dependencies]
fayalite-proc-macros = { version = "=0.2.0", path = "crates/fayalite-proc-macros" }
fayalite-proc-macros-impl = { version = "=0.2.0", path = "crates/fayalite-proc-macros-impl" }
fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" }
fayalite-proc-macros = { version = "=0.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"] }
hashbrown = "0.14.3"
indexmap = { version = "2.2.6", features = ["serde"] }
num-bigint = "0.4.4"
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.23"
num-bigint = "0.4.6"
num-traits = "0.2.16"
once_cell = "1.21.3"
ordered-float = { version = "5.1.0", features = ["serde"] }
petgraph = "0.8.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"
quote = "1.0.36"
serde = { version = "1.0.202", features = ["derive"] }
serde_json = { version = "1.0.117", features = ["preserve_order"] }
sha2 = "0.10.8"
syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"] }
syn = { version = "2.0.93", features = ["full", "fold", "visit", "extra-traits"] }
tempfile = "3.10.1"
thiserror = "1.0.61"
trybuild = "1.0"
vec_map = "0.8.2"
which = "6.0.1"

View file

@ -1,5 +1,84 @@
<!--
SPDX-License-Identifier: LGPL-3.0-or-later
See Notices.txt for copyright information
-->
# Fayalite
Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/).
[FIRRTL]: https://github.com/chipsalliance/firrtl-spec
# Building the [Blinky example] for the Arty A7 100T on Linux
[Blinky example]: crates/fayalite/examples/blinky.rs
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in <https://git.libre-chip.org/libre-chip/fayalite-deps>
Steps:
Install podman (or docker).
Run:
```bash
podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out
```
To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS:
[openFPGALoader]: https://github.com/trabucayre/openFPGALoader
On Debian 12:
```bash
sudo apt update && sudo apt install openfpgaloader
```
Then program the FPGA:
```bash
sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit
```
This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled.
To program the Flash also, so it stays programmed when power-cycling the board:
```bash
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
```
# Building the [Transmit-only UART example] for the Arty A7 100T on Linux
[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs
Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`.
View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`.
Find the correct USB device:
```bash
sudo tio --list
```
You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port):
`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0`
Connect to the serial port:
```bash
sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here
```
You'll see (repeating endlessly):
```text
Hello World from Fayalite!!!
Hello World from Fayalite!!!
Hello World from Fayalite!!!
```
Press Ctrl+T then `q` to exit tio.
# Funding
## NLnet Grants
* [Libre-Chip CPU with proof of No Spectre bugs](https://nlnet.nl/project/Libre-Chip-proof/) 2024-12-324 [(progress)](https://git.libre-chip.org/libre-chip/grant-tracking/src/branch/master/nlnet-2024-12-324/progress.md)
This project was funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund), a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) programme, under the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement &numero; [101135429](https://cordis.europa.eu/project/id/101135429). Additional funding is made available by the [Swiss State Secretariat for Education, Research and Innovation](https://www.sbfi.admin.ch/sbfi/en/home.html) (SERI).

View file

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

View file

@ -220,6 +220,7 @@ forward_fold!(syn::ExprArray => fold_expr_array);
forward_fold!(syn::ExprCall => fold_expr_call);
forward_fold!(syn::ExprIf => fold_expr_if);
forward_fold!(syn::ExprMatch => fold_expr_match);
forward_fold!(syn::ExprMethodCall => fold_expr_method_call);
forward_fold!(syn::ExprPath => fold_expr_path);
forward_fold!(syn::ExprRepeat => fold_expr_repeat);
forward_fold!(syn::ExprStruct => fold_expr_struct);

View file

@ -1,34 +1,39 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
Errors, HdlAttr, PairsIterExt,
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField,
ParsedFieldsNamed, ParsedGenerics, SplitForImpl, TypesParser, WrappedInConst,
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField, ParsedFieldsNamed, ParsedGenerics,
SplitForImpl, TypesParser, WrappedInConst, common_derives, get_target,
},
kw, Errors, HdlAttr, PairsIterExt,
kw,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote_spanned, ToTokens};
use quote::{ToTokens, format_ident, quote_spanned};
use syn::{
parse_quote, parse_quote_spanned,
AngleBracketedGenericArguments, Attribute, Field, FieldMutability, Fields, FieldsNamed,
GenericParam, Generics, Ident, ItemStruct, Path, Token, Type, Visibility, parse_quote,
parse_quote_spanned,
punctuated::{Pair, Punctuated},
spanned::Spanned,
token::Brace,
AngleBracketedGenericArguments, Attribute, Field, FieldMutability, Fields, FieldsNamed,
GenericParam, Generics, Ident, ItemStruct, Path, Token, Type, Visibility,
};
#[derive(Clone, Debug)]
pub(crate) struct ParsedBundle {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) struct_token: Token![struct],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) fields: MaybeParsed<ParsedFieldsNamed, FieldsNamed>,
pub(crate) field_flips: Vec<Option<HdlAttr<kw::flip>>>,
pub(crate) field_flips: Vec<Option<HdlAttr<kw::flip, kw::hdl>>>,
pub(crate) mask_type_ident: Ident,
pub(crate) mask_type_match_variant_ident: Ident,
pub(crate) mask_type_sim_value_ident: Ident,
pub(crate) match_variant_ident: Ident,
pub(crate) sim_value_ident: Ident,
pub(crate) builder_ident: Ident,
pub(crate) mask_type_builder_ident: Ident,
}
@ -38,7 +43,7 @@ impl ParsedBundle {
errors: &mut Errors,
field: &mut Field,
index: usize,
) -> Option<HdlAttr<kw::flip>> {
) -> Option<HdlAttr<kw::flip, kw::hdl>> {
let Field {
attrs,
vis: _,
@ -56,8 +61,7 @@ impl ParsedBundle {
}
*mutability = FieldMutability::None;
colon_token.get_or_insert(Token![:](ident.span()));
let options = errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs));
options
errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs))
}
fn parse(item: ItemStruct) -> syn::Result<Self> {
let ItemStruct {
@ -71,7 +75,9 @@ impl ParsedBundle {
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions>::parse_and_take_attr(&mut attrs))
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
@ -80,7 +86,12 @@ impl ParsedBundle {
custom_bounds,
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) => {
@ -121,7 +132,9 @@ impl ParsedBundle {
field_flips,
mask_type_ident: format_ident!("__{}__MaskType", ident),
mask_type_match_variant_ident: format_ident!("__{}__MaskType__MatchVariant", ident),
mask_type_sim_value_ident: format_ident!("__{}__MaskType__SimValue", ident),
match_variant_ident: format_ident!("__{}__MatchVariant", ident),
sim_value_ident: format_ident!("__{}__SimValue", ident),
mask_type_builder_ident: format_ident!("__{}__MaskType__Builder", ident),
builder_ident: format_ident!("__{}__Builder", ident),
ident,
@ -211,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);
@ -336,8 +349,7 @@ impl ToTokens for Builder {
}
}));
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
#[allow(non_camel_case_types, non_snake_case, dead_code)]
impl #impl_generics #unfilled_ty
#where_clause
{
@ -371,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: (),)*
}
}
}
@ -383,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),)*
@ -423,20 +449,25 @@ impl ToTokens for ParsedBundle {
field_flips,
mask_type_ident,
mask_type_match_variant_ident,
mask_type_sim_value_ident,
match_variant_ident,
sim_value_ident,
builder_ident,
mask_type_builder_ident,
} = self;
let span = ident.span();
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 item_attrs = attrs.clone();
item_attrs.push(common_derives(ident.span()));
item_attrs.push(common_derives(span));
ItemStruct {
attrs: item_attrs,
vis: vis.clone(),
@ -458,19 +489,19 @@ impl ToTokens for ParsedBundle {
.map(|ParsedField { ident, ty, .. }| {
let ident = ident.as_ref().unwrap();
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: #expr,
}
})
.collect();
parse_quote_spanned! {ident.span()=>
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let builder = Builder {
vis: vis.clone(),
@ -482,11 +513,9 @@ 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 { ident, ty, .. } in &mut mask_type_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
for Field { ty, .. } in &mut mask_type_fields.named {
*ty = parse_quote_spanned! {span=>
<#ty as ::fayalite::ty::Type>::MaskType
};
}
@ -501,12 +530,10 @@ 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(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -518,17 +545,16 @@ impl ToTokens for ParsedBundle {
semi_token: None,
}
.to_tokens(tokens);
let mut mask_type_match_variant_fields = mask_type_fields;
for Field { ident, ty, .. } in &mut mask_type_match_variant_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
let mut mask_type_match_variant_fields = mask_type_fields.clone();
for Field { ty, .. } in &mut mask_type_match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -541,16 +567,15 @@ impl ToTokens for ParsedBundle {
}
.to_tokens(tokens);
let mut match_variant_fields = FieldsNamed::from(fields.clone());
for Field { ident, ty, .. } in &mut match_variant_fields.named {
let ident = ident.as_ref().unwrap();
*ty = parse_quote_spanned! {ident.span()=>
for Field { ty, .. } in &mut match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(ident.span()),
parse_quote_spanned! {ident.span()=>
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
@ -562,17 +587,72 @@ impl ToTokens for ParsedBundle {
semi_token: None,
}
.to_tokens(tokens);
let mut mask_type_sim_value_fields = mask_type_fields;
for Field { ty, .. } in &mut mask_type_sim_value_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::sim::value::SimValue<#ty>
};
}
ItemStruct {
attrs: vec![
parse_quote_spanned! {span=>
#[::fayalite::__std::prelude::v1::derive(
::fayalite::__std::fmt::Debug,
::fayalite::__std::clone::Clone,
)]
},
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
vis: vis.clone(),
struct_token: *struct_token,
ident: mask_type_sim_value_ident.clone(),
generics: generics.into(),
fields: Fields::Named(mask_type_sim_value_fields),
semi_token: None,
}
.to_tokens(tokens);
let mut sim_value_fields = FieldsNamed::from(fields.clone());
for Field { ty, .. } in &mut sim_value_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::sim::value::SimValue<#ty>
};
}
ItemStruct {
attrs: vec![
parse_quote_spanned! {span=>
#[::fayalite::__std::prelude::v1::derive(
::fayalite::__std::fmt::Debug,
::fayalite::__std::clone::Clone,
)]
},
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
vis: vis.clone(),
struct_token: *struct_token,
ident: sim_value_ident.clone(),
generics: generics.into(),
fields: Fields::Named(sim_value_fields),
semi_token: None,
}
.to_tokens(tokens);
let this_token = Ident::new("__this", span);
let fields_token = Ident::new("__fields", span);
let self_token = Token![self](span);
let match_variant_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
quote_spanned! {ident.span()=>
#ident: ::fayalite::expr::Expr::field(__this, #ident_str),
quote_spanned! {span=>
#ident: ::fayalite::expr::Expr::field(#this_token, #ident_str),
}
}));
let mask_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {ident.span()=>
#ident: ::fayalite::ty::Type::mask_type(&self.#ident),
quote_spanned! {span=>
#ident: ::fayalite::ty::Type::mask_type(&#self_token.#ident),
}
}));
let from_canonical_body_fields =
@ -580,16 +660,16 @@ impl ToTokens for ParsedBundle {
|((index, field), flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let flipped = flip.is_some();
quote_spanned! {ident.span()=>
let not_flipped = flip.is_none().then(|| Token![!](span));
quote_spanned! {span=>
#ident: {
let ::fayalite::bundle::BundleField {
name: __name,
flipped: __flipped,
ty: __ty,
} = __fields[#index];
} = #fields_token[#index];
::fayalite::__std::assert_eq!(&*__name, #ident_str);
::fayalite::__std::assert_eq!(__flipped, #flipped);
::fayalite::__std::assert!(#not_flipped __flipped);
::fayalite::ty::Type::from_canonical(__ty)
},
}
@ -600,23 +680,50 @@ impl ToTokens for ParsedBundle {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let flipped = flip.is_some();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::bundle::BundleField {
name: ::fayalite::intern::Intern::intern(#ident_str),
flipped: #flipped,
ty: ::fayalite::ty::Type::canonical(&self.#ident),
ty: ::fayalite::ty::Type::canonical(&#self_token.#ident),
},
}
},
));
let sim_value_from_opaque_fields =
Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {span=>
#ident: v.field_from_opaque(),
}
}));
let sim_value_clone_from_opaque_fields =
Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {span=>
v.field_clone_from_opaque(&mut value.#ident);
}
}));
let sim_value_to_opaque_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {span=>
v.field(&value.#ident);
}
}));
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::expr::ValueType::ty(&self.#ident),
}
}));
let fields_len = fields.named().into_iter().len();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #mask_type_ident #type_generics
#where_clause
{
type BaseType = ::fayalite::bundle::Bundle;
type MaskType = #mask_type_ident #type_generics;
type SimValue = #mask_type_sim_value_ident #type_generics;
type MatchVariant = #mask_type_match_variant_ident #type_generics;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<
@ -626,7 +733,7 @@ impl ToTokens for ParsedBundle {
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
__this: ::fayalite::expr::Expr<Self>,
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #mask_type_match_variant_ident {
@ -634,19 +741,19 @@ impl ToTokens for ParsedBundle {
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
*self
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
*#self_token
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let __fields = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(__fields.len(), #fields_len, "bundle has wrong number of fields");
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
@ -654,14 +761,42 @@ impl ToTokens for ParsedBundle {
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
fn sim_value_from_opaque(
&self,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) -> <Self as ::fayalite::ty::Type>::SimValue {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque);
#mask_type_sim_value_ident {
#(#sim_value_from_opaque_fields)*
}
}
fn sim_value_clone_from_opaque(
&self,
value: &mut <Self as ::fayalite::ty::Type>::SimValue,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque);
#(#sim_value_clone_from_opaque_fields)*
}
fn sim_value_to_opaque<'__w>(
&self,
value: &<Self as ::fayalite::ty::Type>::SimValue,
writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>,
) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueToOpaque::new(*self, writer);
#(#sim_value_to_opaque_fields)*
v.finish()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleType for #mask_type_ident #type_generics
#where_clause
{
type Builder = #unfilled_mask_type_builder_ty;
type FilledBuilder = #filled_mask_type_builder_ty;
fn fields(&self) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
@ -676,12 +811,68 @@ impl ToTokens for ParsedBundle {
impl #impl_generics ::fayalite::ty::TypeWithDeref for #mask_type_ident #type_generics
#where_clause
{
fn expr_deref(__this: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let __this = *__this;
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #mask_type_match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::<_>::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
#[automatically_derived]
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::expr::ValueType>::Type,
> {
let ty = #mask_type_ident {
#(#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::expr::ValueType>::Type,
> {
let ty = #mask_type_ident {
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, self)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#mask_type_ident #type_generics>
for #mask_type_sim_value_ident #type_generics
#where_clause
{
fn to_sim_value_with_type(
&self,
ty: #mask_type_ident #type_generics,
) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self))
}
fn into_sim_value_with_type(
self,
ty: #mask_type_ident #type_generics,
) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, self)
}
}
#[automatically_derived]
@ -690,6 +881,7 @@ impl ToTokens for ParsedBundle {
{
type BaseType = ::fayalite::bundle::Bundle;
type MaskType = #mask_type_ident #type_generics;
type SimValue = #sim_value_ident #type_generics;
type MatchVariant = #match_variant_ident #type_generics;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<
@ -699,7 +891,7 @@ impl ToTokens for ParsedBundle {
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
__this: ::fayalite::expr::Expr<Self>,
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #match_variant_ident {
@ -707,21 +899,21 @@ impl ToTokens for ParsedBundle {
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
#mask_type_ident {
#(#mask_type_body_fields)*
}
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let __fields = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(__fields.len(), #fields_len, "bundle has wrong number of fields");
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
@ -729,14 +921,42 @@ impl ToTokens for ParsedBundle {
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
fn sim_value_from_opaque(
&self,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) -> <Self as ::fayalite::ty::Type>::SimValue {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque);
#sim_value_ident {
#(#sim_value_from_opaque_fields)*
}
}
fn sim_value_clone_from_opaque(
&self,
value: &mut <Self as ::fayalite::ty::Type>::SimValue,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueFromOpaque::new(*self, opaque);
#(#sim_value_clone_from_opaque_fields)*
}
fn sim_value_to_opaque<'__w>(
&self,
value: &<Self as ::fayalite::ty::Type>::SimValue,
writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>,
) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> {
#![allow(unused_mut, unused_variables)]
let mut v = ::fayalite::bundle::BundleSimValueToOpaque::new(*self, writer);
#(#sim_value_to_opaque_fields)*
v.finish()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleType for #target #type_generics
#where_clause
{
type Builder = #unfilled_builder_ty;
type FilledBuilder = #filled_builder_ty;
fn fields(&self) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
@ -751,16 +971,244 @@ impl ToTokens for ParsedBundle {
impl #impl_generics ::fayalite::ty::TypeWithDeref for #target #type_generics
#where_clause
{
fn expr_deref(__this: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let __this = *__this;
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::<_>::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
#[automatically_derived]
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::expr::ValueType>::Type,
> {
let ty = #target {
#(#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::expr::ValueType>::Type,
> {
let ty = #target {
#(#value_type_fields)*
};
::fayalite::sim::value::SimValue::from_value(ty, self)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics>
for #sim_value_ident #type_generics
#where_clause
{
fn to_sim_value_with_type(
&self,
ty: #target #type_generics,
) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self))
}
fn into_sim_value_with_type(
self,
ty: #target #type_generics,
) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, self)
}
}
}
.to_tokens(tokens);
if let Some((cmp_eq,)) = cmp_eq {
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 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();
cmp_eq_where_clause
.predicates
.push(parse_quote_spanned! {cmp_eq.span=>
#field_ty: ::fayalite::expr::HdlPartialEqImpl<#field_ty>
});
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_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_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 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 {
value_eq_body = quote_spanned! {span=>
true
};
value_ne_body = quote_spanned! {span=>
false
};
expr_eq_body = quote_spanned! {span=>
::fayalite::expr::ToExpr::to_expr(&true)
};
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 {
value_eq_body = quote_spanned! {span=>
#(#fields_value_eq)&*
};
value_ne_body = quote_spanned! {span=>
#(#fields_value_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::HdlPartialEqImpl<Self> for #target #type_generics
#cmp_eq_where_clause
{
#[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> {
#expr_eq_body
}
#[track_caller]
fn cmp_expr_ne(
__lhs: ::fayalite::expr::Expr<Self>,
__rhs: ::fayalite::expr::Expr<Self>,
) -> ::fayalite::expr::Expr<::fayalite::int::Bool> {
#expr_ne_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
}
}
}
.to_tokens(tokens);
}
if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) {
let static_generics = generics.clone().for_static_type();
let (static_impl_generics, static_type_generics, static_where_clause) =
@ -768,7 +1216,7 @@ impl ToTokens for ParsedBundle {
let static_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::TYPE,
}
}));
@ -776,28 +1224,34 @@ impl ToTokens for ParsedBundle {
Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::MASK_TYPE,
}
}));
let type_properties = format_ident!("__type_properties", span = ident.span());
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES);
}
}));
let type_properties_mask_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::MASK_TYPE_PROPERTIES);
}
}));
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::__std::default::Default for #mask_type_ident #static_type_generics
#static_where_clause
{
fn default() -> Self {
<Self as ::fayalite::ty::StaticType>::TYPE
}
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType for #mask_type_ident #static_type_generics
#static_where_clause
@ -820,6 +1274,15 @@ impl ToTokens for ParsedBundle {
};
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::__std::default::Default
for #target #static_type_generics
#static_where_clause
{
fn default() -> Self {
<Self as ::fayalite::ty::StaticType>::TYPE
}
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics
#static_where_clause
{

View file

@ -1,18 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics,
ParsedType, SplitForImpl, TypesParser, WrappedInConst,
},
Errors, HdlAttr, PairsIterExt,
hdl_type_common::{
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, SplitForImpl,
TypesParser, WrappedInConst, common_derives, get_target,
},
kw,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote_spanned, ToTokens};
use quote::{ToTokens, format_ident, quote_spanned};
use syn::{
parse_quote_spanned,
Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident,
ItemEnum, ItemStruct, Token, Type, Variant, Visibility, parse_quote_spanned,
punctuated::{Pair, Punctuated},
token::{Brace, Paren},
Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident,
ItemEnum, ItemStruct, Token, Type, Variant, Visibility,
};
crate::options! {
@ -29,7 +31,7 @@ crate::options! {
pub(crate) struct ParsedVariantField {
pub(crate) paren_token: Paren,
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<FieldOptions>,
pub(crate) options: HdlAttr<FieldOptions, kw::hdl>,
pub(crate) ty: MaybeParsed<ParsedType, Type>,
pub(crate) comma_token: Option<Token![,]>,
}
@ -37,7 +39,7 @@ pub(crate) struct ParsedVariantField {
#[derive(Clone, Debug)]
pub(crate) struct ParsedVariant {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<VariantOptions>,
pub(crate) options: HdlAttr<VariantOptions, kw::hdl>,
pub(crate) ident: Ident,
pub(crate) field: Option<ParsedVariantField>,
}
@ -119,7 +121,7 @@ impl ParsedVariant {
#[derive(Clone, Debug)]
pub(crate) struct ParsedEnum {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) enum_token: Token![enum],
pub(crate) ident: Ident,
@ -127,6 +129,9 @@ pub(crate) struct ParsedEnum {
pub(crate) brace_token: Brace,
pub(crate) variants: Punctuated<ParsedVariant, Token![,]>,
pub(crate) match_variant_ident: Ident,
pub(crate) sim_value_ident: Ident,
pub(crate) sim_builder_ident: Ident,
pub(crate) sim_builder_ty_field_ident: Ident,
}
impl ParsedEnum {
@ -142,7 +147,9 @@ impl ParsedEnum {
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions>::parse_and_take_attr(&mut attrs))
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
@ -151,7 +158,15 @@ impl ParsedEnum {
custom_bounds,
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");
@ -182,6 +197,9 @@ impl ParsedEnum {
brace_token,
variants,
match_variant_ident: format_ident!("__{}__MatchVariant", ident),
sim_value_ident: format_ident!("__{}__SimValue", ident),
sim_builder_ident: format_ident!("__{}__SimBuilder", ident),
sim_builder_ty_field_ident: format_ident!("__ty", span = ident.span()),
ident,
})
}
@ -199,18 +217,24 @@ impl ToTokens for ParsedEnum {
brace_token,
variants,
match_variant_ident,
sim_value_ident,
sim_builder_ident,
sim_builder_ty_field_ident,
} = self;
let span = ident.span();
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
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();
struct_attrs.push(common_derives(ident.span()));
struct_attrs.push(parse_quote_spanned! {ident.span()=>
struct_attrs.push(common_derives(span));
struct_attrs.push(parse_quote_spanned! {span=>
#[allow(non_snake_case)]
});
let struct_fields = Punctuated::from_iter(variants.pairs().map_pair_value_ref(
@ -234,8 +258,8 @@ impl ToTokens for ParsedEnum {
colon_token = Token![:](paren_token.span.open());
ty.clone().into()
} else {
colon_token = Token![:](ident.span());
parse_quote_spanned! {ident.span()=>
colon_token = Token![:](span);
parse_quote_spanned! {span=>
()
}
};
@ -278,30 +302,30 @@ impl ToTokens for ParsedEnum {
}) = field
{
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: #expr,
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: (),
}
}
})
.collect();
parse_quote_spanned! {ident.span()=>
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
{
let mut wrapped_in_const = WrappedInConst::new(tokens, ident.span());
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {ident.span()=>
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code)]
});
ItemEnum {
@ -350,7 +374,7 @@ impl ToTokens for ParsedEnum {
.to_tokens(tokens);
}
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {ident.span()=>
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code, non_camel_case_types)]
});
ItemEnum {
@ -385,7 +409,7 @@ impl ToTokens for ParsedEnum {
mutability: FieldMutability::None,
ident: None,
colon_token: None,
ty: parse_quote_spanned! {ident.span()=>
ty: parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
},
},
@ -399,21 +423,148 @@ impl ToTokens for ParsedEnum {
)),
}
.to_tokens(tokens);
let mut struct_attrs = attrs.clone();
struct_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code, non_camel_case_types)]
});
ItemStruct {
attrs: struct_attrs,
vis: vis.clone(),
struct_token: Token![struct](enum_token.span),
ident: sim_builder_ident.clone(),
generics: generics.into(),
fields: FieldsNamed {
brace_token: *brace_token,
named: Punctuated::from_iter([Field {
attrs: vec![],
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: Some(sim_builder_ty_field_ident.clone()),
colon_token: Some(Token![:](span)),
ty: parse_quote_spanned! {span=>
#target #type_generics
},
}]),
}
.into(),
semi_token: None,
}
.to_tokens(tokens);
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {span=>
#[::fayalite::__std::prelude::v1::derive(
::fayalite::__std::fmt::Debug,
::fayalite::__std::clone::Clone,
)]
});
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code, non_camel_case_types)]
});
let sim_value_has_unknown_variant = !variants.len().is_power_of_two();
let sim_value_unknown_variant_name = sim_value_has_unknown_variant.then(|| {
let mut name = String::new();
let unknown = "Unknown";
loop {
let orig_len = name.len();
name.push_str(unknown);
if variants.iter().all(|v| v.ident != name) {
break Ident::new(&name, span);
}
name.truncate(orig_len);
name.push('_');
}
});
let sim_value_unknown_variant =
sim_value_unknown_variant_name
.as_ref()
.map(|unknown_variant_name| {
Pair::End(parse_quote_spanned! {span=>
#unknown_variant_name(::fayalite::enum_::UnknownVariantSimValue)
})
});
ItemEnum {
attrs: enum_attrs,
vis: vis.clone(),
enum_token: *enum_token,
ident: sim_value_ident.clone(),
generics: generics.into(),
brace_token: *brace_token,
variants: Punctuated::from_iter(
variants
.pairs()
.map_pair_value_ref(
|ParsedVariant {
attrs,
options: _,
ident,
field,
}| Variant {
attrs: attrs.clone(),
ident: ident.clone(),
fields: match field {
Some(ParsedVariantField {
paren_token,
attrs,
options: _,
ty,
comma_token,
}) => Fields::Unnamed(FieldsUnnamed {
paren_token: *paren_token,
unnamed: Punctuated::from_iter([
Pair::new(
Field {
attrs: attrs.clone(),
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: None,
colon_token: None,
ty: parse_quote_spanned! {span=>
::fayalite::sim::value::SimValue<#ty>
},
},
Some(comma_token.unwrap_or(Token![,](ident.span()))),
),
Pair::new(
Field {
attrs: vec![],
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: None,
colon_token: None,
ty: parse_quote_spanned! {span=>
::fayalite::enum_::EnumPaddingSimValue
},
},
None,
),
]),
}),
None => Fields::Unnamed(parse_quote_spanned! {span=>
(::fayalite::enum_::EnumPaddingSimValue)
}),
},
discriminant: None,
},
)
.chain(sim_value_unknown_variant),
),
}
.to_tokens(tokens);
let self_token = Token![self](span);
for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() {
if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {ident.span()=>
#[automatically_derived]
quote_spanned! {span=>
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident<__V: ::fayalite::expr::ToExpr<Type = #ty>>(
self,
#self_token,
v: __V,
) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
self,
#self_token,
#index,
::fayalite::__std::option::Option::Some(
::fayalite::expr::Expr::canonical(
@ -424,68 +575,98 @@ impl ToTokens for ParsedEnum {
)
}
}
impl #impl_generics #sim_builder_ident #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident<__V: ::fayalite::sim::value::ToSimValueWithType<#ty>>(
#self_token,
v: __V,
) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
let v = ::fayalite::sim::value::ToSimValueWithType::into_sim_value_with_type(
v,
#self_token.#sim_builder_ty_field_ident.#ident,
);
::fayalite::sim::value::SimValue::from_value(
#self_token.#sim_builder_ty_field_ident,
#sim_value_ident::#ident(v, ::fayalite::enum_::EnumPaddingSimValue::new()),
)
}
}
}
} else {
quote_spanned! {ident.span()=>
#[automatically_derived]
quote_spanned! {span=>
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident(self) -> ::fayalite::expr::Expr<Self> {
#vis fn #ident(#self_token) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
self,
#self_token,
#index,
::fayalite::__std::option::Option::None,
),
)
}
}
impl #impl_generics #sim_builder_ident #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident(#self_token) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
::fayalite::sim::value::SimValue::from_value(
#self_token.#sim_builder_ty_field_ident,
#sim_value_ident::#ident(::fayalite::enum_::EnumPaddingSimValue::new()),
)
}
}
}
}
.to_tokens(tokens);
}
let variants_token = Ident::new("variants", span);
let from_canonical_body_fields = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
let ident_str = ident.to_string();
let val = if field.is_some() {
let missing_value_msg = format!("expected variant {ident} to have a field");
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::ty::Type::from_canonical(ty.expect(#missing_value_msg))
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::assert!(ty.is_none());
}
};
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: {
let ::fayalite::enum_::EnumVariant {
name,
ty,
} = variants[#index];
} = #variants_token[#index];
::fayalite::__std::assert_eq!(&*name, #ident_str);
#val
},
}
},
));
let variant_access_token = Ident::new("variant_access", span);
let match_active_scope_match_arms = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
if field.is_some() {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#index => #match_variant_ident::#ident(
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::VariantAccess::new_by_index(
variant_access.base(),
variant_access.variant_index(),
#variant_access_token.base(),
#variant_access_token.variant_index(),
),
),
),
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#index => #match_variant_ident::#ident,
}
}
@ -503,16 +684,16 @@ impl ToTokens for ParsedEnum {
match field {
Some(ParsedVariantField { options, .. }) => {
let FieldOptions {} = options.body;
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::Some(
::fayalite::ty::Type::canonical(&self.#ident),
::fayalite::ty::Type::canonical(&#self_token.#ident),
),
},
}
}
None => quote_spanned! {ident.span()=>
None => quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::None,
@ -521,14 +702,151 @@ impl ToTokens for ParsedEnum {
}
},
));
let sim_value_from_opaque_unknown_match_arm = if let Some(sim_value_unknown_variant_name) =
&sim_value_unknown_variant_name
{
quote_spanned! {span=>
_ => #sim_value_ident::#sim_value_unknown_variant_name(v.unknown_variant_from_opaque()),
}
} else {
quote_spanned! {span=>
_ => ::fayalite::__std::unreachable!(),
}
};
let sim_value_from_opaque_match_arms = Vec::from_iter(
variants
.iter()
.enumerate()
.map(
|(
index,
ParsedVariant {
attrs: _,
options: _,
ident,
field,
},
)| {
if let Some(_) = field {
quote_spanned! {span=>
#index => {
let (field, padding) = v.variant_with_field_from_opaque();
#sim_value_ident::#ident(field, padding)
}
}
} else {
quote_spanned! {span=>
#index => #sim_value_ident::#ident(
v.variant_no_field_from_opaque(),
),
}
}
},
)
.chain([sim_value_from_opaque_unknown_match_arm]),
);
let sim_value_clone_from_opaque_unknown_match_arm =
if let Some(sim_value_unknown_variant_name) = &sim_value_unknown_variant_name {
quote_spanned! {span=>
_ => if let #sim_value_ident::#sim_value_unknown_variant_name(value) = value {
v.unknown_variant_clone_from_opaque(value);
} else {
*value = #sim_value_ident::#sim_value_unknown_variant_name(
v.unknown_variant_from_opaque(),
);
},
}
} else {
quote_spanned! {span=>
_ => ::fayalite::__std::unreachable!(),
}
};
let sim_value_clone_from_opaque_match_arms = Vec::from_iter(
variants
.iter()
.enumerate()
.map(
|(
index,
ParsedVariant {
attrs: _,
options: _,
ident,
field,
},
)| {
if let Some(_) = field {
quote_spanned! {span=>
#index => if let #sim_value_ident::#ident(field, padding) = value {
v.variant_with_field_clone_from_opaque(field, padding);
} else {
let (field, padding) = v.variant_with_field_from_opaque();
*value = #sim_value_ident::#ident(field, padding);
},
}
} else {
quote_spanned! {span=>
#index => if let #sim_value_ident::#ident(padding) = value {
v.variant_no_field_clone_from_opaque(padding);
} else {
*value = #sim_value_ident::#ident(
v.variant_no_field_from_opaque(),
);
},
}
}
},
)
.chain([sim_value_clone_from_opaque_unknown_match_arm]),
);
let sim_value_to_opaque_match_arms = Vec::from_iter(
variants
.iter()
.enumerate()
.map(
|(
index,
ParsedVariant {
attrs: _,
options: _,
ident,
field,
},
)| {
if let Some(_) = field {
quote_spanned! {span=>
#sim_value_ident::#ident(field, padding) => {
v.variant_with_field_to_opaque(#index, field, padding)
}
}
} else {
quote_spanned! {span=>
#sim_value_ident::#ident(padding) => {
v.variant_no_field_to_opaque(#index, padding)
}
}
}
},
)
.chain(sim_value_unknown_variant_name.as_ref().map(
|sim_value_unknown_variant_name| {
quote_spanned! {span=>
#sim_value_ident::#sim_value_unknown_variant_name(value) => {
v.unknown_variant_to_opaque(value)
}
}
},
)),
);
let variants_len = variants.len();
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #target #type_generics
#where_clause
{
type BaseType = ::fayalite::enum_::Enum;
type MaskType = ::fayalite::int::Bool;
type SimValue = #sim_value_ident #type_generics;
type MatchVariant = #match_variant_ident #type_generics;
type MatchActiveScope = ::fayalite::module::Scope;
type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope<Self>;
@ -540,11 +858,11 @@ impl ToTokens for ParsedEnum {
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
::fayalite::module::enum_match_variants_helper(this, source_location)
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
::fayalite::int::Bool
}
fn canonical(&self) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(self)))
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(#self_token)))
}
#[track_caller]
#[allow(non_snake_case)]
@ -552,8 +870,8 @@ impl ToTokens for ParsedEnum {
let ::fayalite::ty::CanonicalType::Enum(enum_) = canonical_type else {
::fayalite::__std::panic!("expected enum");
};
let variants = ::fayalite::enum_::EnumType::variants(&enum_);
::fayalite::__std::assert_eq!(variants.len(), #variants_len, "enum has wrong number of variants");
let #variants_token = ::fayalite::enum_::EnumType::variants(&enum_);
::fayalite::__std::assert_eq!(#variants_token.len(), #variants_len, "enum has wrong number of variants");
Self {
#(#from_canonical_body_fields)*
}
@ -561,29 +879,87 @@ impl ToTokens for ParsedEnum {
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
fn sim_value_from_opaque(
&self,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) -> <Self as ::fayalite::ty::Type>::SimValue {
let v = ::fayalite::enum_::EnumSimValueFromOpaque::new(*self, opaque);
match v.discriminant() {
#(#sim_value_from_opaque_match_arms)*
}
}
#[allow(irrefutable_let_patterns)]
fn sim_value_clone_from_opaque(
&self,
value: &mut <Self as ::fayalite::ty::Type>::SimValue,
opaque: ::fayalite::ty::OpaqueSimValueSlice<'_>,
) {
let v = ::fayalite::enum_::EnumSimValueFromOpaque::new(*self, opaque);
match v.discriminant() {
#(#sim_value_clone_from_opaque_match_arms)*
}
}
fn sim_value_to_opaque<'__w>(
&self,
value: &<Self as ::fayalite::ty::Type>::SimValue,
writer: ::fayalite::ty::OpaqueSimValueWriter<'__w>,
) -> ::fayalite::ty::OpaqueSimValueWritten<'__w> {
let v = ::fayalite::enum_::EnumSimValueToOpaque::new(*self, writer);
match value {
#(#sim_value_to_opaque_match_arms)*
}
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics
#where_clause
{
type SimBuilder = #sim_builder_ident #type_generics;
fn match_activate_scope(
v: <Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
) -> (<Self as ::fayalite::ty::Type>::MatchVariant, <Self as ::fayalite::ty::Type>::MatchActiveScope) {
let (variant_access, scope) = v.activate();
let (#variant_access_token, scope) = v.activate();
(
match variant_access.variant_index() {
match #variant_access_token.variant_index() {
#(#match_active_scope_match_arms)*
#variants_len.. => ::fayalite::__std::panic!("invalid variant index"),
},
scope,
)
}
fn variants(&self) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> {
fn variants(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> {
::fayalite::intern::Intern::intern(&[
#(#variants_body_variants)*
][..])
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics>
for #sim_value_ident #type_generics
#where_clause
{
fn to_sim_value_with_type(
&self,
ty: #target #type_generics,
) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self))
}
fn into_sim_value_with_type(
self,
ty: #target #type_generics,
) -> ::fayalite::sim::value::SimValue<#target #type_generics> {
::fayalite::sim::value::SimValue::from_value(ty, self)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::__std::convert::From<#target #type_generics>
for #sim_builder_ident #type_generics
#where_clause
{
fn from(#sim_builder_ty_field_ident: #target #type_generics) -> Self {
Self { #sim_builder_ty_field_ident }
}
}
}
.to_tokens(tokens);
if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) {
@ -592,35 +968,44 @@ impl ToTokens for ParsedEnum {
static_generics.split_for_impl();
let static_type_body_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| {
if let Some(_) = field {
quote_spanned! {ident.span()=>
if field.is_some() {
quote_spanned! {span=>
#ident: ::fayalite::ty::StaticType::TYPE,
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#ident: (),
}
}
}));
let type_properties = format_ident!("__type_properties", span = ident.span());
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| {
Vec::from_iter(variants.iter().map(|ParsedVariant { field, .. }| {
let variant = if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::option::Option::Some(
<#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES,
)
}
} else {
quote_spanned! {ident.span()=>
quote_spanned! {span=>
::fayalite::__std::option::Option::None
}
};
quote_spanned! {ident.span()=>
quote_spanned! {span=>
let #type_properties = #type_properties.variant(#variant);
}
}));
quote_spanned! {ident.span()=>
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::__std::default::Default
for #target #static_type_generics
#static_where_clause
{
fn default() -> Self {
<Self as ::fayalite::ty::StaticType>::TYPE
}
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType
for #target #static_type_generics
@ -639,6 +1024,44 @@ impl ToTokens for ParsedEnum {
const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties =
<::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES;
}
#[automatically_derived]
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::expr::ValueType>::Type,
> {
::fayalite::sim::value::SimValue::from_value(
::fayalite::ty::StaticType::TYPE,
::fayalite::__std::clone::Clone::clone(self),
)
}
fn into_sim_value(
self,
) -> ::fayalite::sim::value::SimValue<
<Self as ::fayalite::expr::ValueType>::Type,
> {
::fayalite::sim::value::SimValue::from_value(
::fayalite::ty::StaticType::TYPE,
self,
)
}
}
}
.to_tokens(tokens);
}

View file

@ -0,0 +1,493 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
Errors, HdlAttr,
hdl_type_common::{
ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType,
PhantomConstGetBound, TypesParser, WrappedInConst, common_derives, get_target, known_items,
},
kw,
};
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote_spanned};
use syn::{
Attribute, Expr, Fields, GenericParam, Generics, Ident, ItemStruct, ItemType, Token, Type,
TypeGroup, TypeParam, TypeParen, Visibility, parse_quote_spanned, punctuated::Pair,
token::Paren,
};
#[derive(Clone, Debug)]
pub(crate) struct PhantomConstAccessorTypeParam {
attrs: Vec<Attribute>,
ident: Ident,
colon_token: Token![:],
phantom_const_get_bound: PhantomConstGetBound,
plus_token: Option<Token![+]>,
}
impl From<PhantomConstAccessorTypeParam> for TypeParam {
fn from(value: PhantomConstAccessorTypeParam) -> Self {
let PhantomConstAccessorTypeParam {
attrs,
ident,
colon_token,
phantom_const_get_bound,
plus_token,
} = value;
TypeParam {
attrs,
ident,
colon_token: Some(colon_token),
bounds: FromIterator::from_iter([Pair::new(
phantom_const_get_bound.into(),
plus_token,
)]),
eq_token: None,
default: None,
}
}
}
impl From<PhantomConstAccessorTypeParam> for GenericParam {
fn from(value: PhantomConstAccessorTypeParam) -> Self {
TypeParam::from(value).into()
}
}
impl PhantomConstAccessorTypeParam {
fn parse_opt(generic_param: GenericParam) -> Option<Self> {
let GenericParam::Type(TypeParam {
attrs,
ident,
colon_token,
bounds,
eq_token: None,
default: None,
}) = generic_param
else {
return None;
};
let colon_token = colon_token.unwrap_or(Token![:](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,
vis,
type_token,
ident,
mut generics,
eq_token,
ty,
semi_token,
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
outline_generated: _,
target: _,
custom_bounds,
no_static,
no_runtime_generics: _,
cmp_eq,
ref mut get,
} = options.body;
if let Some(get) = get.take() {
return Self::parse_phantom_const_accessor(
ItemType {
attrs,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
},
errors,
options,
get,
);
}
if let Some((no_static,)) = no_static {
errors.error(no_static, "no_static is not valid on type aliases");
}
if let Some((cmp_eq,)) = cmp_eq {
errors.error(cmp_eq, "cmp_eq is not valid on type aliases");
}
let generics = if custom_bounds.is_some() {
MaybeParsed::Unrecognized(generics)
} else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) {
MaybeParsed::Parsed(generics)
} else {
MaybeParsed::Unrecognized(generics)
};
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
errors.finish()?;
Ok(Self::TypeAlias {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
})
}
}
impl ToTokens for ParsedTypeAlias {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::TypeAlias {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
} => {
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static: _,
no_runtime_generics,
cmp_eq: _,
get: _,
} = &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 = match &item {
ParsedTypeAlias::TypeAlias { options, .. }
| ParsedTypeAlias::PhantomConstAccessor { options, .. } => options.body.outline_generated,
};
let mut contents = item.to_token_stream();
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "hdl-type-alias-");
}
Ok(contents)
}

File diff suppressed because it is too large Load diff

View file

@ -2,23 +2,43 @@
// See Notices.txt for copyright information
#![cfg_attr(test, recursion_limit = "512")]
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use std::io::{ErrorKind, Write};
use quote::{ToTokens, quote};
use std::{
collections::{HashMap, hash_map::Entry},
io::{ErrorKind, Write},
};
use syn::{
bracketed, parenthesized,
AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, bracketed,
ext::IdentExt,
parenthesized,
parse::{Parse, ParseStream, Parser},
parse_quote,
punctuated::Pair,
AttrStyle, Attribute, Error, Item, Token,
punctuated::{Pair, Punctuated},
spanned::Spanned,
token::{Bracket, Paren},
};
mod fold;
mod hdl_bundle;
mod hdl_enum;
mod hdl_type_alias;
mod hdl_type_common;
mod module;
//mod value_derive_common;
//mod value_derive_struct;
mod process_cfg;
pub(crate) trait CustomToken:
Copy
+ Spanned
+ ToTokens
+ std::fmt::Debug
+ Eq
+ std::hash::Hash
+ Default
+ quote::IdentFragment
+ Parse
{
const IDENT_STR: &'static str;
}
mod kw {
pub(crate) use syn::token::Extern as extern_;
@ -38,14 +58,28 @@ mod kw {
}
crate::fold::no_op_fold!($kw);
impl crate::CustomToken for $kw {
const IDENT_STR: &'static str = stringify!($kw);
}
};
}
custom_keyword!(__evaluated_cfgs);
custom_keyword!(add_platform_io);
custom_keyword!(all);
custom_keyword!(any);
custom_keyword!(cfg);
custom_keyword!(cfg_attr);
custom_keyword!(clock_domain);
custom_keyword!(cmp_eq);
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);
custom_keyword!(input);
custom_keyword!(instance);
custom_keyword!(m);
@ -55,11 +89,12 @@ mod kw {
custom_keyword!(no_reset);
custom_keyword!(no_runtime_generics);
custom_keyword!(no_static);
custom_keyword!(not);
custom_keyword!(outline_generated);
custom_keyword!(output);
custom_keyword!(reg_builder);
custom_keyword!(reset);
custom_keyword!(reset_default);
custom_keyword!(sim);
custom_keyword!(skip);
custom_keyword!(target);
custom_keyword!(wire);
@ -68,34 +103,34 @@ mod kw {
type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676
#[derive(Clone, Debug)]
pub(crate) struct HdlAttr<T> {
pub(crate) struct HdlAttr<T, KW> {
pub(crate) pound_token: Pound,
pub(crate) style: AttrStyle,
pub(crate) bracket_token: syn::token::Bracket,
pub(crate) hdl: kw::hdl,
pub(crate) kw: KW,
pub(crate) paren_token: Option<syn::token::Paren>,
pub(crate) body: T,
}
crate::fold::impl_fold! {
struct HdlAttr<T,> {
struct HdlAttr<T, KW,> {
pound_token: Pound,
style: AttrStyle,
bracket_token: syn::token::Bracket,
hdl: kw::hdl,
kw: KW,
paren_token: Option<syn::token::Paren>,
body: T,
}
}
#[allow(dead_code)]
impl<T> HdlAttr<T> {
pub(crate) fn split_body(self) -> (HdlAttr<()>, T) {
impl<T, KW> HdlAttr<T, KW> {
pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -104,19 +139,19 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: (),
},
body,
)
}
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2> {
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2, KW> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: _,
} = self;
@ -124,17 +159,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
}
}
pub(crate) fn as_ref(&self) -> HdlAttr<&T> {
pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW>
where
KW: Clone,
{
let Self {
pound_token,
style,
bracket_token,
hdl,
ref kw,
paren_token,
ref body,
} = *self;
@ -142,17 +180,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw: kw.clone(),
paren_token,
body,
}
}
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(self, f: F) -> Result<HdlAttr<R>, E> {
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(
self,
f: F,
) -> Result<HdlAttr<R, KW>, E> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -160,17 +201,17 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: f(body)?,
})
}
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R> {
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R, KW> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -178,7 +219,7 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: f(body),
}
@ -186,31 +227,32 @@ impl<T> HdlAttr<T> {
fn to_attr(&self) -> Attribute
where
T: ToTokens,
KW: ToTokens,
{
parse_quote! { #self }
}
}
impl<T: Default> Default for HdlAttr<T> {
impl<T: Default, KW: Default> Default for HdlAttr<T, KW> {
fn default() -> Self {
T::default().into()
}
}
impl<T> From<T> for HdlAttr<T> {
impl<T, KW: Default> From<T> for HdlAttr<T, KW> {
fn from(body: T) -> Self {
HdlAttr {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
hdl: Default::default(),
kw: Default::default(),
paren_token: Default::default(),
body,
}
}
}
impl<T: ToTokens> ToTokens for HdlAttr<T> {
impl<T: ToTokens, KW: ToTokens + Spanned> ToTokens for HdlAttr<T, KW> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.pound_token.to_tokens(tokens);
match self.style {
@ -218,7 +260,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
AttrStyle::Outer => {}
};
self.bracket_token.surround(tokens, |tokens| {
self.hdl.to_tokens(tokens);
self.kw.to_tokens(tokens);
match self.paren_token {
Some(paren_token) => {
paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens))
@ -226,7 +268,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
None => {
let body = self.body.to_token_stream();
if !body.is_empty() {
syn::token::Paren(self.hdl.span)
syn::token::Paren(self.kw.span())
.surround(tokens, |tokens| tokens.extend([body]));
}
}
@ -235,18 +277,24 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
}
}
fn is_hdl_attr(attr: &Attribute) -> bool {
attr.path().is_ident("hdl")
fn is_hdl_attr<KW: CustomToken>(attr: &Attribute) -> bool {
attr.path().is_ident(KW::IDENT_STR)
}
impl<T: Parse> HdlAttr<T> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>> {
impl<T: Parse, KW: Parse> HdlAttr<T, KW> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>>
where
KW: ToTokens,
{
let mut retval = None;
let mut errors = Errors::new();
attrs.retain(|attr| {
if is_hdl_attr(attr) {
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
errors.push(Error::new_spanned(
attr,
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
));
}
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
false
@ -257,13 +305,19 @@ impl<T: Parse> HdlAttr<T> {
errors.finish()?;
Ok(retval)
}
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>> {
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>>
where
KW: ToTokens,
{
let mut retval = None;
let mut errors = Errors::new();
for attr in attrs {
if is_hdl_attr(attr) {
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
errors.push(Error::new_spanned(
attr,
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
));
}
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
}
@ -284,7 +338,7 @@ impl<T: Parse> HdlAttr<T> {
) -> syn::Result<Self> {
let bracket_content;
let bracket_token = bracketed!(bracket_content in input);
let hdl = bracket_content.parse()?;
let kw = bracket_content.parse()?;
let paren_content;
let body;
let paren_token;
@ -305,7 +359,7 @@ impl<T: Parse> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
})
@ -813,6 +867,7 @@ macro_rules! options {
};
}
use crate::hdl_type_alias::hdl_type_alias_impl;
pub(crate) use options;
pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream {
@ -832,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();
@ -844,33 +905,397 @@ 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);
}
}
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let options = syn::parse2::<module::ConfigOptions>(attr)?;
let options = HdlAttr::from(options);
let func = syn::parse2::<module::ModuleFn>(quote! { #options #item })?;
fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
let func = module::ModuleFn::parse_from_fn(item)?;
let options = func.config_options();
let mut contents = func.generate();
if options.body.outline_generated.is_some() {
if options.outline_generated.is_some() {
contents = outline_generated(contents, "module-");
}
Ok(contents)
}
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let item = syn::parse2::<Item>(quote! { #[hdl(#attr)] #item })?;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) enum CfgExpr {
Option {
ident: Ident,
value: Option<(Token![=], LitStr)>,
},
All {
all: kw::all,
paren: Paren,
exprs: Punctuated<CfgExpr, Token![,]>,
},
Any {
any: kw::any,
paren: Paren,
exprs: Punctuated<CfgExpr, Token![,]>,
},
Not {
not: kw::not,
paren: Paren,
expr: Box<CfgExpr>,
trailing_comma: Option<Token![,]>,
},
}
impl Parse for CfgExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
match input.cursor().ident() {
Some((_, cursor)) if cursor.eof() => {
return Ok(CfgExpr::Option {
ident: input.call(Ident::parse_any)?,
value: None,
});
}
_ => {}
}
if input.peek(Ident::peek_any) && input.peek2(Token![=]) {
return Ok(CfgExpr::Option {
ident: input.call(Ident::parse_any)?,
value: Some((input.parse()?, input.parse()?)),
});
}
let contents;
if input.peek(kw::all) {
Ok(CfgExpr::All {
all: input.parse()?,
paren: parenthesized!(contents in input),
exprs: contents.call(Punctuated::parse_terminated)?,
})
} else if input.peek(kw::any) {
Ok(CfgExpr::Any {
any: input.parse()?,
paren: parenthesized!(contents in input),
exprs: contents.call(Punctuated::parse_terminated)?,
})
} else if input.peek(kw::not) {
Ok(CfgExpr::Not {
not: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
trailing_comma: contents.parse()?,
})
} else {
Err(input.error("expected cfg-pattern"))
}
}
}
impl ToTokens for CfgExpr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
CfgExpr::Option { ident, value } => {
ident.to_tokens(tokens);
if let Some((eq, value)) = value {
eq.to_tokens(tokens);
value.to_tokens(tokens);
}
}
CfgExpr::All { all, paren, exprs } => {
all.to_tokens(tokens);
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
}
CfgExpr::Any { any, paren, exprs } => {
any.to_tokens(tokens);
paren.surround(tokens, |tokens| exprs.to_tokens(tokens));
}
CfgExpr::Not {
not,
paren,
expr,
trailing_comma,
} => {
not.to_tokens(tokens);
paren.surround(tokens, |tokens| {
expr.to_tokens(tokens);
trailing_comma.to_tokens(tokens);
});
}
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct Cfg {
cfg: kw::cfg,
paren: Paren,
expr: CfgExpr,
trailing_comma: Option<Token![,]>,
}
impl Cfg {
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
syn::parse2(meta.to_token_stream())
}
}
impl ToTokens for Cfg {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
cfg,
paren,
expr,
trailing_comma,
} = self;
cfg.to_tokens(tokens);
paren.surround(tokens, |tokens| {
expr.to_tokens(tokens);
trailing_comma.to_tokens(tokens);
});
}
}
impl Parse for Cfg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
Ok(Self {
cfg: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
trailing_comma: contents.parse()?,
})
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub(crate) struct CfgAttr {
cfg_attr: kw::cfg_attr,
paren: Paren,
expr: CfgExpr,
comma: Token![,],
attrs: Punctuated<Meta, Token![,]>,
}
impl CfgAttr {
pub(crate) fn to_cfg(&self) -> Cfg {
Cfg {
cfg: kw::cfg(self.cfg_attr.span),
paren: self.paren,
expr: self.expr.clone(),
trailing_comma: None,
}
}
fn parse_meta(meta: &Meta) -> syn::Result<Self> {
syn::parse2(meta.to_token_stream())
}
}
impl Parse for CfgAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
Ok(Self {
cfg_attr: input.parse()?,
paren: parenthesized!(contents in input),
expr: contents.parse()?,
comma: contents.parse()?,
attrs: contents.call(Punctuated::parse_terminated)?,
})
}
}
pub(crate) struct CfgAndValue {
cfg: Cfg,
eq_token: Token![=],
value: LitBool,
}
impl Parse for CfgAndValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
cfg: input.parse()?,
eq_token: input.parse()?,
value: input.parse()?,
})
}
}
pub(crate) struct Cfgs<T> {
pub(crate) bracket: Bracket,
pub(crate) cfgs_map: HashMap<Cfg, T>,
pub(crate) cfgs_list: Vec<Cfg>,
}
impl<T> Default for Cfgs<T> {
fn default() -> Self {
Self {
bracket: Default::default(),
cfgs_map: Default::default(),
cfgs_list: Default::default(),
}
}
}
impl<T> Cfgs<T> {
fn insert_cfg(&mut self, cfg: Cfg, value: T) {
match self.cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
self.cfgs_list.push(entry.key().clone());
entry.insert(value);
}
}
}
}
impl Parse for Cfgs<bool> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
let bracket = bracketed!(contents in input);
let mut cfgs_map = HashMap::new();
let mut cfgs_list = Vec::new();
for CfgAndValue {
cfg,
eq_token,
value,
} in contents.call(Punctuated::<CfgAndValue, Token![,]>::parse_terminated)?
{
let _ = eq_token;
match cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
cfgs_list.push(entry.key().clone());
entry.insert(value.value);
}
}
}
Ok(Self {
bracket,
cfgs_map,
cfgs_list,
})
}
}
impl Parse for Cfgs<()> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let contents;
let bracket = bracketed!(contents in input);
let mut cfgs_map = HashMap::new();
let mut cfgs_list = Vec::new();
for cfg in contents.call(Punctuated::<Cfg, Token![,]>::parse_terminated)? {
match cfgs_map.entry(cfg) {
Entry::Occupied(_) => {}
Entry::Vacant(entry) => {
cfgs_list.push(entry.key().clone());
entry.insert(());
}
}
}
Ok(Self {
bracket,
cfgs_map,
cfgs_list,
})
}
}
impl ToTokens for Cfgs<()> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
bracket,
cfgs_map: _,
cfgs_list,
} = self;
bracket.surround(tokens, |tokens| {
for cfg in cfgs_list {
cfg.to_tokens(tokens);
<Token![,]>::default().to_tokens(tokens);
}
});
}
}
fn hdl_main(
kw: impl CustomToken,
attr: TokenStream,
item: TokenStream,
) -> syn::Result<TokenStream> {
fn parse_evaluated_cfgs_attr<R>(
input: ParseStream,
parse_inner: impl FnOnce(ParseStream) -> syn::Result<R>,
) -> syn::Result<R> {
let _: Token![#] = input.parse()?;
let bracket_content;
bracketed!(bracket_content in input);
let _: kw::__evaluated_cfgs = bracket_content.parse()?;
let paren_content;
parenthesized!(paren_content in bracket_content);
parse_inner(&paren_content)
}
let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2(
|input: ParseStream| {
let peek = input.fork();
if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() {
let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::<bool>::parse)?;
Ok((Some(evaluated_cfgs), input.parse()?))
} else {
Ok((None, input.parse()?))
}
},
item,
)?;
let cfgs = if let Some(cfgs) = evaluated_cfgs {
cfgs
} else {
let cfgs = process_cfg::collect_cfgs(syn::parse2(item.clone())?)?;
if cfgs.cfgs_list.is_empty() {
Cfgs::default()
} else {
return Ok(quote! {
::fayalite::__cfg_expansion_helper! {
[]
#cfgs
{#[::fayalite::#kw(#attr)]} { #item }
}
});
}
};
let item = syn::parse2(quote! { #[#kw(#attr)] #item })?;
let Some(item) = process_cfg::process_cfgs(item, cfgs)? else {
return Ok(TokenStream::new());
};
match item {
Item::Enum(item) => hdl_enum::hdl_enum(item),
Item::Struct(item) => hdl_bundle::hdl_bundle(item),
Item::Fn(item) => hdl_module_impl(item),
Item::Type(item) => hdl_type_alias_impl(item),
_ => Err(syn::Error::new(
Span::call_site(),
"top-level #[hdl] can only be used on structs or enums",
"top-level #[hdl] can only be used on structs, enums, type aliases, or functions",
)),
}
}
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
hdl_main(kw::hdl_module::default(), attr, item)
}
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
hdl_main(kw::hdl::default(), attr, item)
}

View file

@ -1,19 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
Errors, HdlAttr, PairsIterExt,
hdl_type_common::{ParsedGenerics, SplitForImpl},
module::transform_body::{HdlLet, HdlLetKindIO},
options, Errors, HdlAttr, PairsIterExt,
kw,
module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO},
options,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use quote::{ToTokens, format_ident, quote, quote_spanned};
use std::collections::HashSet;
use syn::{
parse::{Parse, ParseStream},
parse_quote,
visit::{visit_pat, Visit},
Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
LifetimeParam, ReturnType, Signature, TypeParam, Visibility, WhereClause, WherePredicate,
parse_quote,
visit::{Visit, visit_pat},
};
mod transform_body;
@ -38,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(())
@ -59,17 +60,37 @@ impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> {
pub(crate) type ModuleIO = HdlLet<HdlLetKindIO>;
pub(crate) struct ModuleFn {
struct ModuleFnModule {
attrs: Vec<Attribute>,
config_options: HdlAttr<ConfigOptions>,
config_options: HdlAttr<ConfigOptions, kw::hdl_module>,
module_kind: ModuleKind,
vis: Visibility,
sig: Signature,
block: Box<Block>,
struct_generics: ParsedGenerics,
struct_generics: Option<ParsedGenerics>,
the_struct: TokenStream,
}
enum ModuleFnImpl {
Module(ModuleFnModule),
Fn {
attrs: Vec<Attribute>,
config_options: HdlAttr<ConfigOptions, kw::hdl>,
vis: Visibility,
sig: Signature,
block: Box<Block>,
},
}
options! {
pub(crate) enum HdlOrHdlModule {
Hdl(hdl),
HdlModule(hdl_module),
}
}
pub(crate) struct ModuleFn(ModuleFnImpl);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub(crate) enum ModuleKind {
Extern,
@ -89,14 +110,25 @@ impl Visit<'_> for ContainsSkippedIdent<'_> {
}
}
impl Parse for ModuleFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
impl ModuleFn {
pub(crate) fn config_options(&self) -> ConfigOptions {
let (ModuleFnImpl::Module(ModuleFnModule {
config_options: HdlAttr { body, .. },
..
})
| ModuleFnImpl::Fn {
config_options: HdlAttr { body, .. },
..
}) = &self.0;
body.clone()
}
pub(crate) fn parse_from_fn(item: ItemFn) -> syn::Result<Self> {
let ItemFn {
mut attrs,
vis,
mut sig,
block,
} = input.parse()?;
} = item;
let Signature {
ref constness,
ref asyncness,
@ -111,43 +143,60 @@ impl Parse for ModuleFn {
ref output,
} = sig;
let mut errors = Errors::new();
let config_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let Some(mut config_options) =
errors.unwrap_or_default(
HdlAttr::<ConfigOptions, HdlOrHdlModule>::parse_and_take_attr(&mut attrs),
)
else {
errors.error(sig.ident, "missing #[hdl] or #[hdl_module] attribute");
errors.finish()?;
unreachable!();
};
let ConfigOptions {
outline_generated: _,
extern_,
} = config_options.body;
let module_kind = match extern_ {
Some(_) => ModuleKind::Extern,
None => ModuleKind::Normal,
let module_kind = match (config_options.kw, extern_) {
(HdlOrHdlModule::Hdl(_), None) => None,
(HdlOrHdlModule::Hdl(_), Some(extern2)) => {
config_options.body.extern_ = None;
errors.error(
extern2.0,
"extern can only be used as #[hdl_module(extern)]",
);
None
}
(HdlOrHdlModule::HdlModule(_), None) => Some(ModuleKind::Normal),
(HdlOrHdlModule::HdlModule(_), Some(_)) => Some(ModuleKind::Extern),
};
for fn_arg in inputs {
match fn_arg {
FnArg::Receiver(_) => {
errors.push(syn::Error::new_spanned(fn_arg, "self not allowed here"));
}
FnArg::Typed(fn_arg) => {
visit_pat(
&mut CheckNameConflictsWithModuleBuilderVisitor {
errors: &mut errors,
},
&fn_arg.pat,
);
if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
for fn_arg in inputs {
match fn_arg {
FnArg::Receiver(_) => {
errors.push(syn::Error::new_spanned(fn_arg, "self not allowed here"));
}
FnArg::Typed(fn_arg) => {
visit_pat(
&mut CheckNameConflictsWithModuleBuilderVisitor {
errors: &mut errors,
},
&fn_arg.pat,
);
}
}
}
}
if let Some(constness) = constness {
errors.push(syn::Error::new_spanned(constness, "const not allowed here"));
}
if let Some(asyncness) = asyncness {
errors.push(syn::Error::new_spanned(asyncness, "async not allowed here"));
}
if let Some(unsafety) = unsafety {
errors.push(syn::Error::new_spanned(unsafety, "unsafe not allowed here"));
}
if let Some(abi) = abi {
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
if let Some(constness) = constness {
errors.push(syn::Error::new_spanned(constness, "const not allowed here"));
}
if let Some(asyncness) = asyncness {
errors.push(syn::Error::new_spanned(asyncness, "async not allowed here"));
}
if let Some(unsafety) = unsafety {
errors.push(syn::Error::new_spanned(unsafety, "unsafe not allowed here"));
}
if let Some(abi) = abi {
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
}
}
let mut skipped_idents = HashSet::new();
let struct_generic_params = generics
@ -155,14 +204,17 @@ impl Parse for ModuleFn {
.pairs_mut()
.filter_map_pair_value_mut(|v| match v {
GenericParam::Lifetime(LifetimeParam { attrs, .. }) => {
errors
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs));
errors.unwrap_or_default(
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
);
None
}
GenericParam::Type(TypeParam { attrs, ident, .. })
| GenericParam::Const(ConstParam { attrs, ident, .. }) => {
if errors
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs))
.unwrap_or_default(
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
)
.is_some()
{
skipped_idents.insert(ident.clone());
@ -176,6 +228,7 @@ impl Parse for ModuleFn {
let struct_where_clause = generics
.where_clause
.as_mut()
.filter(|_| matches!(config_options.kw, HdlOrHdlModule::HdlModule(_)))
.map(|where_clause| WhereClause {
where_token: where_clause.where_token,
predicates: where_clause
@ -198,22 +251,26 @@ impl Parse for ModuleFn {
})
.collect(),
});
let struct_generics = Generics {
lt_token: generics.lt_token,
params: struct_generic_params,
gt_token: generics.gt_token,
where_clause: struct_where_clause,
let struct_generics = if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
let mut struct_generics = Generics {
lt_token: generics.lt_token,
params: struct_generic_params,
gt_token: generics.gt_token,
where_clause: struct_where_clause,
};
if let Some(variadic) = variadic {
errors.push(syn::Error::new_spanned(variadic, "... not allowed here"));
}
if !matches!(output, ReturnType::Default) {
errors.push(syn::Error::new_spanned(
output,
"return type not allowed here",
));
}
errors.ok(ParsedGenerics::parse(&mut struct_generics))
} else {
Some(ParsedGenerics::default())
};
if let Some(variadic) = variadic {
errors.push(syn::Error::new_spanned(variadic, "... not allowed here"));
}
if !matches!(output, ReturnType::Default) {
errors.push(syn::Error::new_spanned(
output,
"return type not allowed here",
));
}
let struct_generics = errors.ok(ParsedGenerics::parse(&mut { struct_generics }));
let body_results = struct_generics.as_ref().and_then(|struct_generics| {
errors.ok(transform_body::transform_body(
module_kind,
@ -224,6 +281,62 @@ impl Parse for ModuleFn {
errors.finish()?;
let struct_generics = struct_generics.unwrap();
let (block, io) = body_results.unwrap();
let config_options = match config_options {
HdlAttr {
pound_token,
style,
bracket_token,
kw: HdlOrHdlModule::Hdl((kw,)),
paren_token,
body,
} => {
debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty()));
return Ok(Self(ModuleFnImpl::Fn {
attrs,
config_options: HdlAttr {
pound_token,
style,
bracket_token,
kw,
paren_token,
body,
},
vis,
sig,
block,
}));
}
HdlAttr {
pound_token,
style,
bracket_token,
kw: HdlOrHdlModule::HdlModule((kw,)),
paren_token,
body,
} => HdlAttr {
pound_token,
style,
bracket_token,
kw,
paren_token,
body,
},
};
let io = match io {
ModuleIOOrAddPlatformIO::ModuleIO(io) => io,
ModuleIOOrAddPlatformIO::AddPlatformIO => {
return Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
attrs,
config_options,
module_kind: module_kind.unwrap(),
vis,
sig,
block,
struct_generics: None,
the_struct: TokenStream::new(),
})));
}
};
let (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
struct_generics.split_for_impl();
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
@ -259,31 +372,52 @@ impl Parse for ModuleFn {
}
};
let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?;
Ok(Self {
Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
attrs,
config_options,
module_kind,
module_kind: module_kind.unwrap(),
vis,
sig,
block,
struct_generics,
struct_generics: Some(struct_generics),
the_struct,
})
})))
}
}
impl ModuleFn {
pub(crate) fn generate(self) -> TokenStream {
let Self {
let ModuleFnModule {
attrs,
config_options,
module_kind,
vis,
sig,
block,
mut block,
struct_generics,
the_struct,
} = self;
} = match self.0 {
ModuleFnImpl::Module(v) => v,
ModuleFnImpl::Fn {
attrs,
config_options,
vis,
sig,
block,
} => {
let ConfigOptions {
outline_generated: _,
extern_: _,
} = config_options.body;
return ItemFn {
attrs,
vis,
sig,
block,
}
.into_token_stream();
}
};
let ConfigOptions {
outline_generated: _,
extern_: _,
@ -314,13 +448,24 @@ 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
.insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder });
block.stmts.insert(
0,
parse_quote! {
let _ = m;
},
);
let body_fn = ItemFn {
attrs: vec![],
vis: Visibility::Inherited,
@ -332,12 +477,21 @@ impl ModuleFn {
let fn_name_str = fn_name.to_string();
let (_, body_type_generics, _) = body_fn.sig.generics.split_for_impl();
let body_turbofish_type_generics = body_type_generics.as_turbofish();
let body_lambda = if param_names.is_empty() {
quote! {
__body #body_turbofish_type_generics
}
} else {
quote! {
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*)
}
};
let block = parse_quote! {{
#body_fn
::fayalite::module::ModuleBuilder::run(
#fn_name_str,
#module_kind_value,
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*),
#body_lambda,
)
}};
let outer_fn = ItemFn {

View file

@ -1,39 +1,49 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
fold::{impl_fold, DoFold},
Errors, HdlAttr,
fold::{DoFold, impl_fold},
hdl_type_common::{
known_items, ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser,
ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser, known_items,
},
is_hdl_attr, kw,
module::{check_name_conflicts_with_module_builder, ModuleIO, ModuleIOKind, ModuleKind},
options, Errors, HdlAttr,
module::{ModuleIO, ModuleIOKind, ModuleKind, check_name_conflicts_with_module_builder},
options,
};
use num_bigint::BigInt;
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use quote::{ToTokens, quote, quote_spanned};
use std::{borrow::Borrow, convert::Infallible};
use syn::{
fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold},
Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary,
GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp,
fold::{Fold, fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt},
parenthesized,
parse::{Nothing, Parse, ParseStream},
parse::{Parse, ParseStream},
parse_quote, parse_quote_spanned,
spanned::Spanned,
token::Paren,
Attribute, Block, Error, Expr, ExprIf, ExprLet, ExprLit, ExprRepeat, ExprUnary,
GenericArgument, Ident, Item, Lit, LitStr, Local, LocalInit, Pat, Token, Type, UnOp,
};
mod expand_aggregate_literals;
mod expand_match;
options! {
#[options = ExprOptions]
pub(crate) enum ExprOption {
Sim(sim),
}
}
options! {
pub(crate) enum LetFnKind {
Input(input),
Output(output),
AddPlatformIO(add_platform_io),
Instance(instance),
RegBuilder(reg_builder),
Wire(wire),
IncompleteWire(incomplete_wire),
Memory(memory),
MemoryArray(memory_array),
MemoryWithInit(memory_with_init),
@ -207,6 +217,49 @@ impl HdlLetKindToTokens for HdlLetKindInstance {
}
}
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindAddPlatformIO {
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) add_platform_io: kw::add_platform_io,
pub(crate) paren: Paren,
pub(crate) platform_io_builder: Box<Expr>,
}
impl ParseTypes<Self> for HdlLetKindAddPlatformIO {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindAddPlatformIO<> {
m: kw::m,
dot_token: Token![.],
add_platform_io: kw::add_platform_io,
paren: Paren,
platform_io_builder: Box<Expr>,
}
}
impl HdlLetKindToTokens for HdlLetKindAddPlatformIO {
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
m,
dot_token,
add_platform_io,
paren,
platform_io_builder,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
add_platform_io.to_tokens(tokens);
paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens));
}
}
#[derive(Clone, Debug)]
pub(crate) struct RegBuilderClockDomain {
pub(crate) dot_token: Token![.],
@ -264,11 +317,6 @@ pub(crate) enum RegBuilderReset {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
impl_fold! {
@ -285,11 +333,6 @@ impl_fold! {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
}
@ -311,11 +354,6 @@ impl Parse for RegBuilderReset {
paren: parenthesized!(paren_contents in input),
init_expr: paren_contents.call(parse_single_fn_arg)?,
}),
RegBuilderMethod::ResetDefault(reset_default) => Ok(Self::ResetDefault {
dot_token,
reset_default,
paren: parenthesized!(paren_contents in input),
}),
}
}
}
@ -343,15 +381,6 @@ impl ToTokens for RegBuilderReset {
reset.to_tokens(tokens);
paren.surround(tokens, |tokens| init_expr.to_tokens(tokens));
}
RegBuilderReset::ResetDefault {
dot_token,
reset_default,
paren,
} => {
dot_token.to_tokens(tokens);
reset_default.to_tokens(tokens);
paren.surround(tokens, |_| {});
}
}
}
}
@ -400,8 +429,6 @@ make_builder_method_enum! {
NoReset(no_reset),
#[cond = need_reset]
Reset(reset),
#[cond = need_reset]
ResetDefault(reset_default),
}
}
@ -444,17 +471,13 @@ impl HdlLetKindRegBuilder {
let mut clock_domain = None;
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => {}
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => {}
}
let reset = input.parse()?;
if clock_domain.is_none() {
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => unreachable!(),
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => unreachable!(),
}
}
Ok(Self {
@ -533,6 +556,41 @@ impl HdlLetKindToTokens for HdlLetKindWire {
}
}
options! {
pub(crate) enum LetFnKindIncomplete {
IncompleteWire(incomplete_wire),
}
}
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindIncomplete {
pub(crate) kind: LetFnKindIncomplete,
pub(crate) paren: Paren,
}
impl ParseTypes<Self> for HdlLetKindIncomplete {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindIncomplete<> {
kind: LetFnKindIncomplete,
paren: Paren,
}
}
impl HdlLetKindToTokens for HdlLetKindIncomplete {
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self { kind, paren } = self;
kind.to_tokens(tokens);
paren.surround(tokens, |_| {});
}
}
options! {
pub(crate) enum MemoryFnName {
Memory(memory),
@ -697,6 +755,8 @@ impl HdlLetKindMemory {
#[derive(Clone, Debug)]
pub(crate) enum HdlLetKind<IOType = ParsedType> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
AddPlatformIO(HdlLetKindAddPlatformIO),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -706,6 +766,8 @@ pub(crate) enum HdlLetKind<IOType = ParsedType> {
impl_fold! {
enum HdlLetKind<IOType,> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
AddPlatformIO(HdlLetKindAddPlatformIO),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -720,6 +782,12 @@ 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)
}
HdlLetKind::Instance(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance)
}
@ -842,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(
@ -871,6 +956,20 @@ impl HdlLetKindParse for HdlLetKind<Type> {
ty_expr: paren_contents.call(parse_optional_fn_arg)?,
}))
}
LetFnKind::IncompleteWire(incomplete_wire) => {
if let Some(parsed_ty) = parsed_ty {
return Err(Error::new_spanned(
parsed_ty.1,
"type annotation not allowed for incomplete_wire",
));
}
check_empty_m_dot(m_dot, kind)?;
let _paren_contents;
Ok(Self::Incomplete(HdlLetKindIncomplete {
kind: LetFnKindIncomplete::IncompleteWire(incomplete_wire),
paren: parenthesized!(_paren_contents in input),
}))
}
LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse(
input,
parsed_ty,
@ -903,6 +1002,8 @@ 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),
HdlLetKind::Wire(v) => v.ty_to_tokens(tokens),
@ -913,6 +1014,8 @@ 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),
HdlLetKind::Wire(v) => v.expr_to_tokens(tokens),
@ -925,7 +1028,7 @@ with_debug_clone_and_fold! {
#[allow(dead_code)]
pub(crate) struct HdlLet<Kind = HdlLetKind> {
pub(crate) attrs: Vec<Attribute>,
pub(crate) hdl_attr: HdlAttr<Nothing>,
pub(crate) hdl_attr: HdlAttr<syn::parse::Nothing, kw::hdl>,
pub(crate) let_token: Token![let],
pub(crate) mut_token: Option<Token![mut]>,
pub(crate) name: Ident,
@ -1082,7 +1185,7 @@ fn parse_quote_let_pat<T, R: ToTokens, C: Borrow<Token![:]>>(
}
}
fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
parse_quote_spanned! {ty.span()=>
::fayalite::expr::Expr<#ty>
}
@ -1112,30 +1215,41 @@ impl<T: ToString> ToTokens for ImplicitName<T> {
}
struct Visitor<'a> {
module_kind: ModuleKind,
module_kind: Option<ModuleKind>,
errors: Errors,
io: Vec<ModuleIO>,
io: ModuleIOOrAddPlatformIO,
block_depth: usize,
parsed_generics: &'a ParsedGenerics,
}
impl Visitor<'_> {
fn take_hdl_attr<T: Parse>(&mut self, attrs: &mut Vec<Attribute>) -> Option<HdlAttr<T>> {
fn take_hdl_attr<T: Parse>(
&mut self,
attrs: &mut Vec<Attribute>,
) -> Option<HdlAttr<T, kw::hdl>> {
self.errors.unwrap_or(
HdlAttr::parse_and_take_attr(attrs),
Some(syn::parse2::<T>(quote! {}).unwrap().into()),
)
}
fn require_normal_module(&mut self, spanned: impl ToTokens) {
fn require_normal_module_or_fn(&mut self, spanned: impl ToTokens) {
match self.module_kind {
ModuleKind::Extern => {
Some(ModuleKind::Extern) => {
self.errors
.error(spanned, "not allowed in #[hdl_module(extern)]");
}
ModuleKind::Normal => {}
Some(ModuleKind::Normal) | None => {}
}
}
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<Nothing>, expr_if: ExprIf) -> Expr {
fn require_module(&mut self, spanned: impl ToTokens) {
match self.module_kind {
None => {
self.errors.error(spanned, "not allowed in #[hdl] fn");
}
Some(_) => {}
}
}
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<ExprOptions, kw::hdl>, expr_if: ExprIf) -> Expr {
let ExprIf {
attrs,
if_token,
@ -1143,10 +1257,10 @@ impl Visitor<'_> {
then_branch,
else_branch,
} = expr_if;
self.require_normal_module(if_token);
let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr {
Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if),
expr => expr,
let (else_token, else_expr) = else_branch.unzip();
let else_expr = else_expr.map(|else_expr| match *else_expr {
Expr::If(expr_if) => Box::new(self.process_hdl_if(hdl_attr.clone(), expr_if)),
_ => else_expr,
});
if let Expr::Let(ExprLet {
attrs: let_attrs,
@ -1168,7 +1282,19 @@ impl Visitor<'_> {
},
);
}
if let Some(else_expr) = else_expr {
let ExprOptions { sim } = hdl_attr.body;
if sim.is_some() {
ExprIf {
attrs,
if_token,
cond: parse_quote_spanned! {if_token.span=>
*::fayalite::sim::value::SimValue::<::fayalite::int::Bool>::value(&::fayalite::sim::value::ToSimValue::into_sim_value(#cond))
},
then_branch,
else_branch: else_token.zip(else_expr),
}
.into()
} else if let Some(else_expr) = else_expr {
parse_quote_spanned! {if_token.span=>
#(#attrs)*
{
@ -1208,11 +1334,12 @@ impl Visitor<'_> {
.to_tokens(expr);
});
let mut attrs = hdl_let.attrs.clone();
self.require_module(kind);
match self.module_kind {
ModuleKind::Extern => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
Some(ModuleKind::Extern) => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
#[allow(unused_variables)]
}),
ModuleKind::Normal => {}
Some(ModuleKind::Normal) | None => {}
}
let let_stmt = Local {
attrs,
@ -1230,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 {
@ -1249,7 +1450,7 @@ impl Visitor<'_> {
},
semi_token,
} = hdl_let;
self.require_normal_module(instance);
self.require_normal_module_or_fn(instance);
let mut expr = instance.to_token_stream();
paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
@ -1276,7 +1477,7 @@ impl Visitor<'_> {
fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet<HdlLetKindRegBuilder>) -> Local {
let name = &hdl_let.name;
let reg_builder = hdl_let.kind.reg_builder;
self.require_normal_module(reg_builder);
self.require_normal_module_or_fn(reg_builder);
let mut expr = reg_builder.to_token_stream();
hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
@ -1301,7 +1502,7 @@ impl Visitor<'_> {
no_reset.to_tokens(&mut expr);
paren.surround(&mut expr, |expr| ty_expr.to_tokens(expr));
}
RegBuilderReset::Reset { .. } | RegBuilderReset::ResetDefault { .. } => {
RegBuilderReset::Reset { .. } => {
hdl_let.kind.reset.to_tokens(&mut expr);
}
}
@ -1327,7 +1528,7 @@ impl Visitor<'_> {
fn process_hdl_let_wire(&mut self, hdl_let: HdlLet<HdlLetKindWire>) -> Local {
let name = &hdl_let.name;
let wire = hdl_let.kind.wire;
self.require_normal_module(wire);
self.require_normal_module_or_fn(wire);
let ty_expr = unwrap_or_static_type(hdl_let.kind.ty_expr.as_ref(), wire.span());
let mut expr = wire.to_token_stream();
hdl_let.kind.paren.surround(&mut expr, |expr| {
@ -1357,11 +1558,36 @@ impl Visitor<'_> {
semi_token: hdl_let.semi_token,
}
}
fn process_hdl_let_incomplete(&mut self, hdl_let: HdlLet<HdlLetKindIncomplete>) -> Local {
let name = &hdl_let.name;
let kind = hdl_let.kind.kind;
self.require_normal_module_or_fn(kind);
let mut expr = kind.to_token_stream();
hdl_let.kind.paren.surround(&mut expr, |expr| {
ImplicitName {
name,
span: name.span(),
}
.to_tokens(expr);
});
let mut_token = &hdl_let.mut_token;
Local {
attrs: hdl_let.attrs.clone(),
let_token: hdl_let.let_token,
pat: parse_quote! { #mut_token #name },
init: Some(LocalInit {
eq_token: hdl_let.eq_token,
expr: parse_quote! { #expr },
diverge: None,
}),
semi_token: hdl_let.semi_token,
}
}
fn process_hdl_let_memory(&mut self, hdl_let: HdlLet<HdlLetKindMemory>) -> Local {
let name = &hdl_let.name;
let memory_fn = hdl_let.kind.memory_fn;
let memory_fn_name = memory_fn.name();
self.require_normal_module(memory_fn_name);
self.require_normal_module_or_fn(memory_fn_name);
let mut expr = memory_fn_name.to_token_stream();
let (paren, arg) = match memory_fn {
MemoryFn::Memory {
@ -1426,6 +1652,8 @@ 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,
Wire => process_hdl_let_wire,
@ -1521,7 +1749,7 @@ impl Visitor<'_> {
}
}
fn empty_let() -> Local {
pub(crate) fn empty_let() -> Local {
Local {
attrs: vec![],
let_token: Default::default(),
@ -1543,7 +1771,7 @@ impl Fold for Visitor<'_> {
}
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
if is_hdl_attr(&attr) {
if is_hdl_attr::<kw::hdl>(&attr) {
self.errors
.error(&attr, "#[hdl] attribute not supported here");
}
@ -1603,19 +1831,42 @@ impl Fold for Visitor<'_> {
Repeat => process_hdl_repeat,
Struct => process_hdl_struct,
Tuple => process_hdl_tuple,
MethodCall => process_hdl_method_call,
Call => process_hdl_call,
}
}
}
fn fold_local(&mut self, let_stmt: Local) -> Local {
fn fold_local(&mut self, mut let_stmt: Local) -> Local {
match self
.errors
.ok(HdlAttr::<Nothing>::parse_and_leave_attr(&let_stmt.attrs))
{
.ok(HdlAttr::<ExprOptions, kw::hdl>::parse_and_leave_attr(
&let_stmt.attrs,
)) {
None => return empty_let(),
Some(None) => return fold_local(self, let_stmt),
Some(Some(HdlAttr { .. })) => {}
};
let mut pat = &let_stmt.pat;
if let Pat::Type(pat_type) = pat {
pat = &pat_type.pat;
}
let Pat::Ident(syn::PatIdent {
attrs: _,
by_ref: None,
mutability: _,
ident: _,
subpat: None,
}) = pat
else {
let hdl_attr =
HdlAttr::<ExprOptions, kw::hdl>::parse_and_take_attr(&mut let_stmt.attrs)
.ok()
.flatten()
.expect("already checked above");
let let_stmt = fold_local(self, let_stmt);
return self.process_hdl_let_pat(hdl_attr, let_stmt);
};
let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream());
let Some(hdl_let) = self.errors.ok(hdl_let) else {
return empty_let();
@ -1645,15 +1896,20 @@ impl Fold for Visitor<'_> {
}
}
pub(crate) enum ModuleIOOrAddPlatformIO {
ModuleIO(Vec<ModuleIO>),
AddPlatformIO,
}
pub(crate) fn transform_body(
module_kind: ModuleKind,
module_kind: Option<ModuleKind>,
mut body: Box<Block>,
parsed_generics: &ParsedGenerics,
) -> syn::Result<(Box<Block>, Vec<ModuleIO>)> {
) -> 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

@ -1,45 +1,105 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{module::transform_body::Visitor, HdlAttr};
use crate::{
HdlAttr, kw,
module::transform_body::{
ExprOptions, Visitor,
expand_match::{EnumPath, parse_enum_path},
},
};
use quote::{format_ident, quote_spanned};
use std::mem;
use syn::{
parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath,
ExprRepeat, ExprStruct, ExprTuple, FieldValue, TypePath,
Expr, ExprArray, ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat,
ExprStruct, ExprTuple, FieldValue, Token, TypePath, parse_quote_spanned,
punctuated::Punctuated, spanned::Spanned, token::Paren,
};
impl Visitor<'_> {
pub(crate) fn process_hdl_array(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_array: ExprArray,
) -> Expr {
self.require_normal_module(hdl_attr);
for elem in &mut expr_array.elems {
*elem = parse_quote_spanned! {elem.span()=>
::fayalite::expr::ToExpr::to_expr(&(#elem))
};
let ExprOptions { sim } = hdl_attr.body;
let span = hdl_attr.kw.span;
if sim.is_some() {
for elem in &mut expr_array.elems {
*elem = parse_quote_spanned! {elem.span()=>
::fayalite::sim::value::ToSimValue::to_sim_value(&(#elem))
};
}
parse_quote_spanned! {span=>
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_array)
}
} else {
for elem in &mut expr_array.elems {
*elem = parse_quote_spanned! {elem.span()=>
::fayalite::expr::ToExpr::to_expr(&(#elem))
};
}
parse_quote_spanned! {span=>
::fayalite::expr::ToExpr::to_expr(&#expr_array)
}
}
parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_array)}
}
pub(crate) fn process_hdl_repeat(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_repeat: ExprRepeat,
) -> Expr {
self.require_normal_module(hdl_attr);
let repeated_value = &expr_repeat.expr;
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
::fayalite::expr::ToExpr::to_expr(&(#repeated_value))
};
parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_repeat)}
let ExprOptions { sim } = hdl_attr.body;
let span = hdl_attr.kw.span;
if sim.is_some() {
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
::fayalite::sim::value::ToSimValue::to_sim_value(&(#repeated_value))
};
parse_quote_spanned! {span=>
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_repeat)
}
} else {
*expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=>
::fayalite::expr::ToExpr::to_expr(&(#repeated_value))
};
parse_quote_spanned! {span=>
::fayalite::expr::ToExpr::to_expr(&#expr_repeat)
}
}
}
pub(crate) fn process_hdl_struct(
&mut self,
hdl_attr: HdlAttr<Nothing>,
expr_struct: ExprStruct,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_struct: ExprStruct,
) -> Expr {
self.require_normal_module(&hdl_attr);
let name_span = expr_struct.path.segments.last().unwrap().ident.span();
let ExprOptions { sim } = hdl_attr.body;
if sim.is_some() {
let ty_path = TypePath {
qself: expr_struct.qself.take(),
path: expr_struct.path,
};
expr_struct.path = parse_quote_spanned! {name_span=>
__SimValue::<#ty_path>
};
for field in &mut expr_struct.fields {
let expr = &field.expr;
field.expr = parse_quote_spanned! {field.member.span()=>
::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr))
};
field
.colon_token
.get_or_insert(Token![:](field.member.span()));
}
return parse_quote_spanned! {name_span=>
{
type __SimValue<T> = <T as ::fayalite::ty::Type>::SimValue;
let value: ::fayalite::sim::value::SimValue<#ty_path> = ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_struct);
value
}
};
}
let builder_ident = format_ident!("__builder", span = name_span);
let empty_builder = if expr_struct.qself.is_some()
|| expr_struct
@ -91,12 +151,126 @@ impl Visitor<'_> {
}
pub(crate) fn process_hdl_tuple(
&mut self,
hdl_attr: HdlAttr<Nothing>,
expr_tuple: ExprTuple,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_tuple: ExprTuple,
) -> Expr {
self.require_normal_module(hdl_attr);
parse_quote_spanned! {expr_tuple.span()=>
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
let ExprOptions { sim } = hdl_attr.body;
if sim.is_some() {
for element in &mut expr_tuple.elems {
*element = parse_quote_spanned! {element.span()=>
&(#element)
};
}
parse_quote_spanned! {expr_tuple.span()=>
::fayalite::sim::value::ToSimValue::into_sim_value(#expr_tuple)
}
} else {
parse_quote_spanned! {expr_tuple.span()=>
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
}
}
}
pub(crate) fn process_hdl_call(
&mut self,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_call: ExprCall,
) -> Expr {
let span = hdl_attr.kw.span;
let mut func = &mut *expr_call.func;
let EnumPath {
variant_path: _,
enum_path,
variant_name,
} = loop {
match func {
Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
func = &mut **expr;
}
Expr::Path(_) => {
let Expr::Path(ExprPath { attrs, qself, path }) =
mem::replace(func, Expr::PLACEHOLDER)
else {
unreachable!();
};
match parse_enum_path(TypePath { qself, path }) {
Ok(path) => break path,
Err(path) => {
self.errors.error(&path, "unsupported enum variant path");
let TypePath { qself, path } = path;
*func = ExprPath { attrs, qself, path }.into();
return expr_call.into();
}
}
}
_ => {
self.errors.error(
&expr_call.func,
"#[hdl] function call -- function must be a possibly-parenthesized path",
);
return expr_call.into();
}
}
};
self.process_hdl_method_call(
hdl_attr,
ExprMethodCall {
attrs: expr_call.attrs,
receiver: parse_quote_spanned! {span=>
<#enum_path as ::fayalite::ty::StaticType>::TYPE
},
dot_token: Token![.](span),
method: variant_name,
turbofish: None,
paren_token: expr_call.paren_token,
args: expr_call.args,
},
)
}
pub(crate) fn process_hdl_method_call(
&mut self,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut expr_method_call: ExprMethodCall,
) -> Expr {
let ExprOptions { sim } = hdl_attr.body;
let span = hdl_attr.kw.span;
// remove any number of groups and up to one paren
let mut receiver = &mut *expr_method_call.receiver;
let mut has_group = false;
let receiver = loop {
match receiver {
Expr::Group(ExprGroup { expr, .. }) => {
has_group = true;
receiver = expr;
}
Expr::Paren(ExprParen { expr, .. }) => break &mut **expr,
receiver @ Expr::Path(_) => break receiver,
_ => {
if !has_group {
self.errors.error(
&expr_method_call.receiver,
"#[hdl] on a method call needs parenthesized receiver",
);
}
break &mut *expr_method_call.receiver;
}
}
};
let func = if sim.is_some() {
parse_quote_spanned! {span=>
::fayalite::enum_::enum_type_to_sim_builder
}
} else {
parse_quote_spanned! {span=>
::fayalite::enum_::assert_is_enum_type
}
};
*expr_method_call.receiver = ExprCall {
attrs: vec![],
func,
paren_token: Paren(span),
args: Punctuated::from_iter([mem::replace(receiver, Expr::PLACEHOLDER)]),
}
.into();
expr_method_call.into()
}
}

View file

@ -1,32 +1,132 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
fold::{impl_fold, DoFold},
module::transform_body::{with_debug_clone_and_fold, Visitor},
Errors, HdlAttr, PairsIterExt,
fold::{DoFold, impl_fold},
kw,
module::transform_body::{
ExprOptions, Visitor, empty_let, with_debug_clone_and_fold, wrap_ty_with_expr,
},
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
use quote::{ToTokens, TokenStreamExt, format_ident, quote_spanned};
use std::collections::BTreeMap;
use syn::{
fold::{fold_arm, fold_expr_match, fold_pat, Fold},
parse::Nothing,
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr,
PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment,
Token, TypePath,
fold::{Fold, fold_arm, fold_expr_match, fold_local, fold_pat},
parse_quote_spanned,
punctuated::Punctuated,
spanned::Spanned,
token::{Brace, Paren},
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen,
PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath,
};
macro_rules! visit_trait {
(
$($vis:vis fn $fn:ident($state:ident: _, $value:ident: &mut $Value:ty) $block:block)*
) => {
trait VisitMatchPat<'a> {
$(fn $fn(&mut self, $value: &'a mut $Value) {
$fn(self, $value);
})*
}
$($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a mut $Value) $block)*
};
}
visit_trait! {
fn visit_match_pat_binding(_state: _, v: &mut MatchPatBinding) {
let MatchPatBinding { mutability: _, ident: _ } = v;
}
fn visit_match_pat_wild(_state: _, v: &mut MatchPatWild) {
let MatchPatWild { underscore_token: _ } = v;
}
fn visit_match_pat_rest(_state: _, v: &mut MatchPatRest) {
let MatchPatRest { dot2_token: _ } = v;
}
fn visit_match_pat_paren(state: _, v: &mut MatchPatParen<MatchPat>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat(pat);
}
fn visit_match_pat_paren_simple(state: _, v: &mut MatchPatParen<MatchPatSimple>) {
let MatchPatParen { paren_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_or(state: _, v: &mut MatchPatOr<MatchPat>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat(v);
}
}
fn visit_match_pat_or_simple(state: _, v: &mut MatchPatOr<MatchPatSimple>) {
let MatchPatOr { leading_vert: _, cases } = v;
for v in cases {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_struct_field(state: _, v: &mut MatchPatStructField) {
let MatchPatStructField { field_name: _, colon_token: _, pat } = v;
state.visit_match_pat_simple(pat);
}
fn visit_match_pat_struct(state: _, v: &mut MatchPatStruct) {
let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v;
for v in fields {
state.visit_match_pat_struct_field(v);
}
}
fn visit_match_pat_tuple(state: _, v: &mut MatchPatTuple) {
let MatchPatTuple { paren_token: _, fields } = v;
for v in fields {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_enum_variant(state: _, v: &mut MatchPatEnumVariant) {
let MatchPatEnumVariant {
match_span:_,
sim:_,
variant_path: _,
enum_path: _,
variant_name: _,
field,
} = v;
if let Some((_, v)) = field {
state.visit_match_pat_simple(v);
}
}
fn visit_match_pat_simple(state: _, v: &mut MatchPatSimple) {
match v {
MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v),
MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v),
MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v),
MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v),
MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v),
}
}
fn visit_match_pat(state: _, v: &mut MatchPat) {
match v {
MatchPat::Simple(v) => state.visit_match_pat_simple(v),
MatchPat::Or(v) => state.visit_match_pat_or(v),
MatchPat::Paren(v) => state.visit_match_pat_paren(v),
MatchPat::Struct(v) => state.visit_match_pat_struct(v),
MatchPat::Tuple(v) => state.visit_match_pat_tuple(v),
MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v),
}
}
}
with_debug_clone_and_fold! {
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);
}
}
@ -52,6 +152,15 @@ with_debug_clone_and_fold! {
}
}
impl<P> MatchPatOr<P> {
/// returns the first `|` between two patterns
fn first_inner_vert(&self) -> Option<Token![|]> {
let mut pairs = self.cases.pairs();
pairs.next_back();
pairs.next().and_then(|v| v.into_tuple().1.copied())
}
}
impl<P: ToTokens> ToTokens for MatchPatOr<P> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
@ -76,6 +185,19 @@ impl ToTokens for MatchPatWild {
}
}
with_debug_clone_and_fold! {
struct MatchPatRest<> {
dot2_token: Token![..],
}
}
impl ToTokens for MatchPatRest {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { dot2_token } = self;
dot2_token.to_tokens(tokens);
}
}
with_debug_clone_and_fold! {
struct MatchPatStructField<> {
field_name: Ident,
@ -91,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);
@ -158,9 +288,29 @@ impl ToTokens for MatchPatStruct {
}
}
with_debug_clone_and_fold! {
struct MatchPatTuple<> {
paren_token: Paren,
fields: Punctuated<MatchPatSimple, Token![,]>,
}
}
impl ToTokens for MatchPatTuple {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
paren_token,
fields,
} = self;
paren_token.surround(tokens, |tokens| {
fields.to_tokens(tokens);
})
}
}
with_debug_clone_and_fold! {
struct MatchPatEnumVariant<> {
match_span: Span,
sim: Option<(kw::sim,)>,
variant_path: Path,
enum_path: Path,
variant_name: Ident,
@ -172,6 +322,7 @@ impl ToTokens for MatchPatEnumVariant {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
match_span,
sim,
variant_path: _,
enum_path,
variant_name,
@ -181,7 +332,28 @@ impl ToTokens for MatchPatEnumVariant {
__MatchTy::<#enum_path>::#variant_name
}
.to_tokens(tokens);
if let Some((paren_token, field)) = field {
if sim.is_some() {
if let Some((paren_token, field)) = field {
paren_token.surround(tokens, |tokens| {
field.to_tokens(tokens);
match field {
MatchPatSimple::Paren(_)
| MatchPatSimple::Or(_)
| MatchPatSimple::Binding(_)
| MatchPatSimple::Wild(_) => quote_spanned! {*match_span=>
, _
}
.to_tokens(tokens),
MatchPatSimple::Rest(_) => {}
}
});
} else {
quote_spanned! {*match_span=>
(_)
}
.to_tokens(tokens);
}
} else if let Some((paren_token, field)) = field {
paren_token.surround(tokens, |tokens| field.to_tokens(tokens));
}
}
@ -193,6 +365,7 @@ enum MatchPatSimple {
Or(MatchPatOr<MatchPatSimple>),
Binding(MatchPatBinding),
Wild(MatchPatWild),
Rest(MatchPatRest),
}
impl_fold! {
@ -201,6 +374,7 @@ impl_fold! {
Or(MatchPatOr<MatchPatSimple>),
Binding(MatchPatBinding),
Wild(MatchPatWild),
Rest(MatchPatRest),
}
}
@ -211,17 +385,18 @@ impl ToTokens for MatchPatSimple {
Self::Paren(v) => v.to_tokens(tokens),
Self::Binding(v) => v.to_tokens(tokens),
Self::Wild(v) => v.to_tokens(tokens),
Self::Rest(v) => v.to_tokens(tokens),
}
}
}
struct EnumPath {
variant_path: Path,
enum_path: Path,
variant_name: Ident,
pub(crate) struct EnumPath {
pub(crate) variant_path: Path,
pub(crate) enum_path: Path,
pub(crate) variant_name: Ident,
}
fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
pub(crate) fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
let TypePath {
qself: None,
path: variant_path,
@ -277,14 +452,15 @@ trait ParseMatchPat: Sized {
fn or(v: MatchPatOr<Self>) -> Self;
fn paren(v: MatchPatParen<Self>) -> Self;
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()>;
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
-> Result<Self, ()>;
-> Result<Self, ()>;
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
match pat {
Pat::Ident(PatIdent {
attrs: _,
by_ref,
mutability,
mut mutability,
ident,
subpat,
}) => {
@ -293,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
@ -308,17 +487,26 @@ trait ParseMatchPat: Sized {
variant_path,
enum_path,
variant_name,
}) => Self::enum_variant(
state,
MatchPatEnumVariant {
match_span: state.match_span,
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,
}))),
}
@ -358,6 +546,7 @@ trait ParseMatchPat: Sized {
state,
MatchPatEnumVariant {
match_span: state.match_span,
sim: state.sim,
variant_path,
enum_path,
variant_name,
@ -442,6 +631,7 @@ trait ParseMatchPat: Sized {
state,
MatchPatEnumVariant {
match_span: state.match_span,
sim: state.sim,
variant_path,
enum_path,
variant_name,
@ -461,7 +651,34 @@ trait ParseMatchPat: Sized {
}) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild {
underscore_token,
}))),
Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => {
Pat::Tuple(PatTuple {
attrs: _,
paren_token,
elems,
}) => {
let fields = elems
.into_pairs()
.filter_map_pair_value(|field_pat| {
if let Pat::Rest(PatRest {
attrs: _,
dot2_token,
}) = field_pat
{
Some(MatchPatSimple::Rest(MatchPatRest { dot2_token }))
} else {
MatchPatSimple::parse(state, field_pat).ok()
}
})
.collect();
Self::tuple(
state,
MatchPatTuple {
paren_token,
fields,
},
)
}
Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => {
state
.errors
.error(pat, "not yet implemented in #[hdl] patterns");
@ -496,6 +713,14 @@ impl ParseMatchPat for MatchPatSimple {
Err(())
}
fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
state.errors.push(syn::Error::new(
v.paren_token.span.open(),
"matching tuples is not yet implemented inside structs/enums in #[hdl] patterns",
));
Err(())
}
fn enum_variant(
state: &mut HdlMatchParseState<'_>,
v: MatchPatEnumVariant,
@ -514,6 +739,7 @@ enum MatchPat {
Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct),
Tuple(MatchPatTuple),
EnumVariant(MatchPatEnumVariant),
}
@ -523,6 +749,7 @@ impl_fold! {
Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct),
Tuple(MatchPatTuple),
EnumVariant(MatchPatEnumVariant),
}
}
@ -544,6 +771,10 @@ impl ParseMatchPat for MatchPat {
Ok(Self::Struct(v))
}
fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result<Self, ()> {
Ok(Self::Tuple(v))
}
fn enum_variant(
_state: &mut HdlMatchParseState<'_>,
v: MatchPatEnumVariant,
@ -559,6 +790,7 @@ impl ToTokens for MatchPat {
Self::Or(v) => v.to_tokens(tokens),
Self::Paren(v) => v.to_tokens(tokens),
Self::Struct(v) => v.to_tokens(tokens),
Self::Tuple(v) => v.to_tokens(tokens),
Self::EnumVariant(v) => v.to_tokens(tokens),
}
}
@ -621,10 +853,6 @@ struct RewriteAsCheckMatch {
}
impl Fold for RewriteAsCheckMatch {
fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat {
i.colon_token = Some(Token![:](i.member.span()));
i
}
fn fold_pat(&mut self, pat: Pat) -> Pat {
match pat {
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
@ -739,17 +967,185 @@ impl Fold for RewriteAsCheckMatch {
// don't recurse into expressions
i
}
fn fold_local(&mut self, mut let_stmt: Local) -> Local {
if let Some(syn::LocalInit {
eq_token,
expr: _,
diverge,
}) = let_stmt.init.take()
{
let_stmt.init = Some(syn::LocalInit {
eq_token,
expr: parse_quote_spanned! {self.span=>
__match_value
},
diverge: diverge.map(|(else_, _expr)| {
(
else_,
parse_quote_spanned! {self.span=>
match __infallible {}
},
)
}),
});
}
fold_local(self, let_stmt)
}
}
struct HdlMatchParseState<'a> {
sim: Option<(kw::sim,)>,
match_span: Span,
errors: &'a mut Errors,
}
struct HdlLetPatVisitState<'a> {
errors: &'a mut Errors,
bindings: BTreeMap<Ident, MatchPatBinding>,
}
impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> {
fn visit_match_pat_binding(&mut self, v: &'a mut MatchPatBinding) {
self.bindings.insert(v.ident.clone(), v.clone());
v.mutability = None;
}
fn visit_match_pat_or(&mut self, v: &'a mut MatchPatOr<MatchPat>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
"or-patterns are not supported in let statements",
);
}
visit_match_pat_or(self, v);
}
fn visit_match_pat_or_simple(&mut self, v: &'a mut MatchPatOr<MatchPatSimple>) {
if let Some(first_inner_vert) = v.first_inner_vert() {
self.errors.error(
first_inner_vert,
"or-patterns are not supported in let statements",
);
}
visit_match_pat_or_simple(self, v);
}
fn visit_match_pat_enum_variant(&mut self, v: &'a mut MatchPatEnumVariant) {
self.errors.error(v, "refutable pattern in let statement");
}
}
impl Visitor<'_> {
pub(crate) fn process_hdl_let_pat(
&mut self,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
mut let_stmt: Local,
) -> Local {
let span = let_stmt.let_token.span();
let ExprOptions { sim } = hdl_attr.body;
if let Pat::Type(pat) = &mut let_stmt.pat {
*pat.ty = wrap_ty_with_expr((*pat.ty).clone());
}
let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone());
let Local {
attrs: _,
let_token,
pat,
init,
semi_token,
} = let_stmt;
let Some(syn::LocalInit {
eq_token,
expr,
diverge,
}) = init
else {
self.errors
.error(let_token, "#[hdl] let must be assigned a value");
return empty_let();
};
if let Some((else_, _)) = diverge {
// TODO: implement let-else
self.errors
.error(else_, "#[hdl] let ... else { ... } is not implemented");
return empty_let();
}
let Ok(mut pat) = MatchPat::parse(
&mut HdlMatchParseState {
sim,
match_span: span,
errors: &mut self.errors,
},
pat,
) else {
return empty_let();
};
let mut state = HdlLetPatVisitState {
errors: &mut self.errors,
bindings: BTreeMap::new(),
};
state.visit_match_pat(&mut pat);
let HdlLetPatVisitState {
errors: _,
bindings,
} = state;
let bindings_idents = bindings.keys();
let bindings = bindings.values();
let retval = if sim.is_some() {
parse_quote_spanned! {span=>
let (#(#bindings,)*) = {
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
let __match_value = #expr;
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 {
parse_quote_spanned! {span=>
let (#(#bindings,)* __scope,) = {
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
::fayalite::expr::check_match_expr(
__match_expr,
|__match_value, __infallible| {
#[allow(unused_variables)]
#check_let_stmt
match __infallible {}
},
);
let mut __match_iter = ::fayalite::module::match_(__match_expr);
let ::fayalite::__std::option::Option::Some(__match_variant) =
::fayalite::__std::iter::Iterator::next(&mut __match_iter)
else {
::fayalite::__std::unreachable!("#[hdl] let with uninhabited type");
};
let ::fayalite::__std::option::Option::None =
::fayalite::__std::iter::Iterator::next(&mut __match_iter)
else {
::fayalite::__std::unreachable!("#[hdl] let with refutable pattern");
};
let (__match_variant, __scope) =
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
__match_variant,
);
#let_token #pat #eq_token __match_variant #semi_token
(#(#bindings_idents,)* __scope,)
};
}
};
match retval {
syn::Stmt::Local(retval) => retval,
_ => unreachable!(),
}
}
pub(crate) fn process_hdl_match(
&mut self,
_hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<ExprOptions, kw::hdl>,
expr_match: ExprMatch,
) -> Expr {
let span = expr_match.match_token.span();
@ -761,8 +1157,9 @@ impl Visitor<'_> {
brace_token: _,
arms,
} = expr_match;
self.require_normal_module(match_token);
let ExprOptions { sim } = hdl_attr.body;
let mut state = HdlMatchParseState {
sim,
match_span: span,
errors: &mut self.errors,
};
@ -770,24 +1167,41 @@ impl Visitor<'_> {
arms.into_iter()
.filter_map(|arm| MatchArm::parse(&mut state, arm).ok()),
);
let expr = quote_spanned! {span=>
{
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| {
#[allow(unused_variables)]
#check_match
});
for __match_variant in ::fayalite::module::match_(__match_expr) {
let (__match_variant, __scope) =
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
__match_variant,
);
#match_token __match_variant {
let expr = if sim.is_some() {
quote_spanned! {span=>
{
type __MatchTy<T> = <T as ::fayalite::ty::Type>::SimValue;
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)*
}
}
}
} else {
quote_spanned! {span=>
{
type __MatchTy<T> = <T as ::fayalite::ty::Type>::MatchVariant;
let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr));
::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| {
#[allow(unused_variables)]
#check_match
});
for __match_variant in ::fayalite::module::match_(__match_expr) {
let (__match_variant, __scope) =
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
__match_variant,
);
#match_token __match_variant {
#(#arms)*
}
}
}
}
};
syn::parse2(expr).unwrap()
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use quote::{ToTokens, format_ident, quote};
use std::{collections::BTreeMap, fs};
use syn::{fold::Fold, parse_quote};

View file

@ -14,25 +14,38 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
bitvec = { workspace = true }
hashbrown = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
fayalite-proc-macros = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { version = "4.5.9", features = ["derive", "env"] }
eyre = "0.6.12"
which = "6.0.1"
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
hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
once_cell.workspace = true
ordered-float.workspace = true
petgraph.workspace = true
serde_json.workspace = true
serde.workspace = true
sha2.workspace = true
tempfile.workspace = true
vec_map.workspace = true
which.workspace = true
[dev-dependencies]
trybuild = { workspace = true }
trybuild.workspace = true
serde = { workspace = true, features = ["rc"] }
[build-dependencies]
fayalite-visit-gen = { workspace = true }
fayalite-visit-gen.workspace = true
[features]
unstable-doc = []
unstable-test-hasher = []
[package.metadata.docs.rs]
features = ["unstable-doc"]

View file

@ -5,6 +5,9 @@ use std::{env, fs, path::Path};
fn main() {
println!("cargo::rustc-check-cfg=cfg(todo)");
println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)");
println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)");
println!("cargo::rustc-cfg=cfg_true_for_tests");
let path = "visit_types.json";
println!("cargo::rerun-if-changed={path}");
println!("cargo::rerun-if-changed=build.rs");

View file

@ -1,45 +1,64 @@
use clap::Parser;
use fayalite::{cli, prelude::*};
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
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: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
#[hdl]
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
#[hdl]
if counter.cmp_eq(max_value) {
connect_any(counter, 0u8);
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, counter + 1_hdl_u1);
connect_any(counter_reg, counter_reg + 1_hdl_u1);
}
for led in platform_io_builder.peripherals_with_type::<peripherals::Led>() {
if let Ok(led) = led.try_use_peripheral() {
connect(led.on, output_reg);
}
}
for rgb_led in platform_io_builder.peripherals_with_type::<peripherals::RgbLed>() {
if let Ok(rgb_led) = rgb_led.try_use_peripheral() {
connect(rgb_led, rgb_output_reg);
}
}
#[hdl]
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

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

View file

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

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! These are for when you want to use modules written in
//! some other language, such as Verilog.
//!

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,111 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Destructuring Let
//!
//! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns.
//!
//! `#[hdl] let` statements can only match one level of struct/tuple pattern for now,
//! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`.
//!
//! ```
//! # use fayalite::prelude::*;
//! #[hdl]
//! struct MyStruct {
//! a: UInt<8>,
//! b: Bool,
//! }
//!
//! #[hdl_module]
//! fn my_module() {
//! #[hdl]
//! let my_input: MyStruct = m.input();
//! #[hdl]
//! let my_output: UInt<8> = m.input();
//! #[hdl]
//! let MyStruct { a, b } = my_input;
//! #[hdl]
//! if b {
//! connect(my_output, a);
//! } else {
//! connect(my_output, 0_hdl_u8);
//! }
//! }
//! ```
//!
//! You can also use `#[hdl(sim)] let` to destructure [`SimValue`]s (or anything that implements [`ToSimValue`]).
//!
//! [`SimValue`]: crate::sim::value::SimValue
//! [`ToSimValue`]: crate::sim::value::ToSimValue
//!
//! ```
//! # use fayalite::prelude::*;
//! #[hdl]
//! struct MyStruct<T> {
//! a: UInt<8>,
//! b: Bool,
//! c: T,
//! }
//!
//! #[hdl]
//! fn destructure<T: Type>(v: SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! mut b,
//! c,
//! } = v;
//!
//! // that gives these types:
//! let _: SimValue<UInt<8>> = a;
//! let _: SimValue<Bool> = b;
//! let _: SimValue<T> = c;
//! *b = false; // can modify b since mut was used
//! }
//!
//! #[hdl]
//! fn destructure_ref<'a, T: Type>(v: &'a SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! b,
//! c,
//! } = v;
//!
//! // that gives these types:
//! let _: &'a SimValue<UInt<8>> = a;
//! let _: &'a SimValue<Bool> = b;
//! let _: &'a SimValue<T> = c;
//! }
//!
//! #[hdl]
//! fn destructure_mut<'a, T: Type>(v: &'a mut SimValue<MyStruct<T>>) {
//! #[hdl(sim)]
//! let MyStruct::<T> {
//! a,
//! b,
//! c,
//! } = v;
//!
//! **b = true; // you can modify v by modifying b which borrows from it
//!
//! // that gives these types:
//! let _: &'a mut SimValue<UInt<8>> = a;
//! let _: &'a mut SimValue<Bool> = b;
//! let _: &'a mut SimValue<T> = c;
//! }
//!
//! #[hdl]
//! fn destructure_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

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

View file

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

View file

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

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Registers
//!
//! Registers are memory devices that will change their state only on a clock
@ -7,6 +9,9 @@
//!
//! Registers follow [connection semantics], which are unlike assignments in software, so you should read it.
//!
//! By convention, register names end in `_reg` -- this helps you tell which values are written
//! immediately or on the next clock edge when connecting to them.
//!
//! ```
//! # use fayalite::prelude::*;
//! # #[hdl_module]
@ -16,11 +21,11 @@
//! #[hdl]
//! let cd: ClockDomain = m.input();
//! #[hdl]
//! let my_register: UInt<8> = reg_builder().clock_domain(cd).reset(8_hdl_u8);
//! let my_reg: UInt<8> = reg_builder().clock_domain(cd).reset(8_hdl_u8);
//! #[hdl]
//! if v {
//! // my_register is only changed when both `v` is set and `cd`'s clock edge occurs.
//! connect(my_register, 0x45_hdl_u8);
//! // my_reg is only changed when both `v` is set and `cd`'s clock edge occurs.
//! connect(my_reg, 0x45_hdl_u8);
//! }
//! # }
//! ```

View file

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

View file

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

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl] match` Statements
//!
//! `#[hdl] match` statements behave similarly to Rust `match` statements, except they end up as muxes
@ -5,5 +7,80 @@
//!
//! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
//!
//! `#[hdl] match` statements can only match one level of struct/enum pattern for now,
//! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now,
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.
//!
//! 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

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

View file

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

View file

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

View file

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

View file

@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize};
use std::{
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
ops::Deref,
};
#[derive(Clone)]
#[derive(Clone, Debug)]
struct CustomFirrtlAnnotationFieldsImpl {
value: serde_json::Map<String, serde_json::Value>,
serialized: Interned<str>,
@ -118,11 +119,109 @@ pub struct CustomFirrtlAnnotation {
pub additional_fields: CustomFirrtlAnnotationFields,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Annotation {
DontTouch,
CustomFirrtl(CustomFirrtlAnnotation),
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct DontTouchAnnotation;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct SVAttributeAnnotation {
pub text: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct BlackBoxInlineAnnotation {
pub path: Interned<str>,
pub text: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct BlackBoxPathAnnotation {
pub path: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct DocStringAnnotation {
pub text: Interned<str>,
}
macro_rules! make_annotation_enum {
(
#[$non_exhaustive:ident]
$(#[$meta:meta])*
$vis:vis enum $AnnotationEnum:ident {
$($Variant:ident($T:ty),)*
}
) => {
crate::annotations::make_annotation_enum!(@require_non_exhaustive $non_exhaustive);
#[$non_exhaustive]
$(#[$meta])*
#[derive(Clone, PartialEq, Eq, Hash)]
$vis enum $AnnotationEnum {
$($Variant($T),)*
}
impl std::fmt::Debug for $AnnotationEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(Self::$Variant(v) => v.fmt(f),)*
}
}
}
$(impl From<$T> for crate::annotations::Annotation {
fn from(v: $T) -> Self {
$AnnotationEnum::$Variant(v).into()
}
}
impl crate::annotations::IntoAnnotations for $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[self.into()]
}
}
impl crate::annotations::IntoAnnotations for &'_ $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[crate::annotations::Annotation::from(self.clone())]
}
}
impl crate::annotations::IntoAnnotations for &'_ mut $T {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[crate::annotations::Annotation::from(self.clone())]
}
}
impl crate::annotations::IntoAnnotations for Box<$T> {
type IntoAnnotations = [crate::annotations::Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[crate::annotations::Annotation::from(*self)]
}
})*
};
(@require_non_exhaustive non_exhaustive) => {};
}
pub(crate) use make_annotation_enum;
make_annotation_enum! {
#[non_exhaustive]
pub enum Annotation {
DontTouch(DontTouchAnnotation),
SVAttribute(SVAttributeAnnotation),
BlackBoxInline(BlackBoxInlineAnnotation),
BlackBoxPath(BlackBoxPathAnnotation),
DocString(DocStringAnnotation),
CustomFirrtl(CustomFirrtlAnnotation),
Xilinx(crate::vendor::xilinx::XilinxAnnotation),
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
@ -187,10 +286,68 @@ impl IntoAnnotations for &'_ mut Annotation {
}
}
impl<T: IntoIterator<Item = Annotation>> IntoAnnotations for T {
type IntoAnnotations = Self;
pub struct IterIntoAnnotations<T: Iterator<Item: IntoAnnotations>> {
outer: T,
inner: Option<<<T::Item as IntoAnnotations>::IntoAnnotations as IntoIterator>::IntoIter>,
}
fn into_annotations(self) -> Self::IntoAnnotations {
self
impl<T: Iterator<Item: IntoAnnotations>> Iterator for IterIntoAnnotations<T> {
type Item = Annotation;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(inner) = &mut self.inner {
let Some(retval) = inner.next() else {
self.inner = None;
continue;
};
return Some(retval);
} else {
self.inner = Some(self.outer.next()?.into_annotations().into_iter());
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if let (0, Some(0)) = self.outer.size_hint() {
self.inner
.as_ref()
.map(|v| v.size_hint())
.unwrap_or((0, Some(0)))
} else {
(
self.inner.as_ref().map(|v| v.size_hint().0).unwrap_or(0),
None,
)
}
}
fn fold<B, F>(self, init: B, f: F) -> B
where
Self: Sized,
F: FnMut(B, Self::Item) -> B,
{
self.inner
.into_iter()
.chain(self.outer.map(|v| v.into_annotations().into_iter()))
.flatten()
.fold(init, f)
}
}
impl<
T: FusedIterator<Item: IntoAnnotations<IntoAnnotations: IntoIterator<IntoIter: FusedIterator>>>,
> FusedIterator for IterIntoAnnotations<T>
{
}
impl<T: IntoIterator<Item: IntoAnnotations>> IntoAnnotations for T {
type IntoAnnotations = IterIntoAnnotations<T::IntoIter>;
fn into_annotations(self) -> Self::IntoAnnotations {
IterIntoAnnotations {
outer: self.into_iter(),
inner: None,
}
}
}

View file

@ -2,17 +2,24 @@
// See Notices.txt for copyright information
use crate::{
expr::{ops::ArrayIndex, Expr, ToExpr},
int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE},
expr::{
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,
source_location::SourceLocation,
ty::{
CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref,
CanonicalType, MatchVariantWithoutScope, OpaqueSimValueSlice, OpaqueSimValueWriter,
OpaqueSimValueWritten, StaticType, Type, TypeProperties, TypeWithDeref,
serde_impls::SerdeCanonicalType,
},
util::ConstUsize,
};
use std::ops::Index;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::{borrow::Cow, iter::FusedIterator, ops::Index};
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
@ -41,15 +48,20 @@ impl<T: Type, Len: Size> ArrayType<T, Len> {
is_storable,
is_castable_from_bits,
bit_width,
sim_only_values_len,
} = element;
let Some(bit_width) = bit_width.checked_mul(len) else {
panic!("array too big");
};
let Some(sim_only_values_len) = sim_only_values_len.checked_mul(len) else {
panic!("array too big");
};
TypeProperties {
is_passive,
is_storable,
is_castable_from_bits,
bit_width,
sim_only_values_len,
}
}
pub fn new(element: T, len: Len::SizeType) -> Self {
@ -85,20 +97,54 @@ impl<T: Type, Len: Size> ArrayType<T, Len> {
}
}
impl<T: Type, Len: KnownSize> ArrayType<T, Len> {
impl<T: Type, Len: KnownSize + Size<SizeType = Len>> ArrayType<T, Len> {
pub fn new_static(element: T) -> Self {
Self::new(element, Len::SizeType::default())
}
}
impl<T: StaticType, Len: KnownSize> Default for ArrayType<T, Len> {
fn default() -> Self {
Self::TYPE
}
}
struct MakeType<T: StaticType>(Interned<T>);
impl<T: StaticType> From<MakeType<T>> for Interned<T> {
fn from(value: MakeType<T>) -> Self {
value.0
}
}
impl<T: StaticType> Default for MakeType<T> {
fn default() -> Self {
Self(T::TYPE.intern_sized())
}
}
struct MakeMaskType<T: StaticType>(Interned<T::MaskType>);
impl<T: StaticType> From<MakeMaskType<T>> for Interned<T::MaskType> {
fn from(value: MakeMaskType<T>) -> Self {
value.0
}
}
impl<T: StaticType> Default for MakeMaskType<T> {
fn default() -> Self {
Self(T::MASK_TYPE.intern_sized())
}
}
impl<T: StaticType, Len: KnownSize> StaticType for ArrayType<T, Len> {
const TYPE: Self = Self {
element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()),
element: LazyInterned::new_const::<MakeType<T>>(),
len: Len::SIZE,
type_properties: Self::TYPE_PROPERTIES,
};
const MASK_TYPE: Self::MaskType = ArrayType::<T::MaskType, Len> {
element: LazyInterned::new_lazy(&|| T::MASK_TYPE.intern_sized()),
element: LazyInterned::new_const::<MakeMaskType<T>>(),
len: Len::SIZE,
type_properties: Self::MASK_TYPE_PROPERTIES,
};
@ -139,6 +185,7 @@ impl<T: Type + Visit<State>, Len: Size, State: Visitor + ?Sized> Visit<State>
impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
type BaseType = Array;
type MaskType = ArrayType<T::MaskType, Len>;
type SimValue = Len::ArraySimValue<T>;
type MatchVariant = Len::ArrayMatch<T>;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Len::ArrayMatch<T>>;
@ -148,10 +195,8 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
this: Expr<Self>,
source_location: SourceLocation,
) -> Self::MatchVariantsIter {
let base = Expr::as_dyn_array(this);
let base_ty = Expr::ty(base);
let _ = source_location;
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
let retval = Vec::from_iter(this);
std::iter::once(MatchVariantWithoutScope(
Len::ArrayMatch::<T>::try_from(retval)
.ok()
@ -177,17 +222,107 @@ impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
Len::from_usize(array.len()),
)
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, mut opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
let element_ty = self.element();
let element_size = element_ty.canonical().size();
let mut value = Vec::with_capacity(self.len());
for _ in 0..self.len() {
let (element_opaque, rest) = opaque.split_at(element_size);
value.push(SimValue::from_opaque(element_ty, element_opaque.to_owned()));
opaque = rest;
}
value.try_into().ok().expect("used correct length")
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
mut opaque: OpaqueSimValueSlice<'_>,
) {
let element_ty = self.element();
let element_size = element_ty.canonical().size();
let value = AsMut::<[SimValue<T>]>::as_mut(value);
assert_eq!(self.len(), value.len());
for element_value in value {
assert_eq!(element_value.ty(), element_ty);
let (element_opaque, rest) = opaque.split_at(element_size);
SimValue::opaque_mut(element_value).clone_from_slice(element_opaque);
opaque = rest;
}
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
mut writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
let element_ty = self.element();
let element_size = element_ty.canonical().size();
let value = AsRef::<[SimValue<T>]>::as_ref(value);
assert_eq!(self.len(), value.len());
for element_value in value {
assert_eq!(element_value.ty(), element_ty);
writer.fill_prefix_with(element_size, |writer| {
writer.fill_cloned_from_slice(SimValue::opaque(element_value).as_slice())
});
}
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
}
}
impl<T: Type + Serialize, Len: Size> Serialize for ArrayType<T, Len> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeCanonicalType::<T>::Array {
element: self.element(),
len: self.len(),
}
.serialize(serializer)
}
}
impl<'de, T: Type + Deserialize<'de>, Len: Size> Deserialize<'de> for ArrayType<T, Len> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name = |len| -> String {
if let Some(len) = len {
format!("an Array<_, {len}>")
} else {
"an Array<_>".to_string()
}
};
match SerdeCanonicalType::<T>::deserialize(deserializer)? {
SerdeCanonicalType::Array { element, len } => {
if let Some(len) = Len::try_from_usize(len) {
Ok(Self::new(element, len))
} else {
Err(Error::invalid_value(
serde::de::Unexpected::Other(&name(Some(len))),
&&*name(Len::KNOWN_VALUE),
))
}
}
ty => Err(Error::invalid_value(
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
&&*name(Len::KNOWN_VALUE),
)),
}
}
}
impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
let base = Expr::as_dyn_array(*this);
let base_ty = Expr::ty(base);
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
Interned::<_>::into_inner(Intern::intern_sized(
let retval = Vec::from_iter(*this);
Interned::into_inner(Intern::intern_sized(
Len::ArrayMatch::<T>::try_from(retval)
.ok()
.expect("unreachable"),
@ -202,7 +337,7 @@ impl<T: Type> Index<T> for ArrayWithoutGenerics {
type Output = ArrayWithoutLen<T>;
fn index(&self, element: T) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized(ArrayWithoutLen { element }))
Interned::into_inner(Intern::intern_sized(ArrayWithoutLen { element }))
}
}
@ -215,6 +350,161 @@ impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
type Output = ArrayType<T, L::Size>;
fn index(&self, len: L) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
}
}
impl<Lhs: Type, Rhs: Type, Len: Size> HdlPartialEqImpl<ArrayType<Rhs, Len>> for ArrayType<Lhs, Len>
where
Lhs: HdlPartialEqImpl<Rhs>,
{
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))
.collect::<Expr<Array<Bool>>>()
.cast_to_bits()
.all_one_bits()
}
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<ArrayType<Rhs, Len>>) -> Expr<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
lhs.into_iter()
.zip(rhs)
.map(|(l, r)| l.cmp_ne(r))
.collect::<Expr<Array<Bool>>>()
.cast_to_bits()
.any_one_bits()
}
fn cmp_valueless_eq(
lhs: Valueless<Self>,
rhs: Valueless<ArrayType<Rhs, Len>>,
) -> Valueless<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
Valueless::new(Bool)
}
fn cmp_valueless_ne(
lhs: Valueless<Self>,
rhs: Valueless<ArrayType<Rhs, Len>>,
) -> Valueless<Bool> {
assert_eq!(lhs.ty().len(), rhs.ty().len());
Valueless::new(Bool)
}
}
impl<T: Type, Len: Size> ExprIntoIterator for ArrayType<T, Len> {
type Item = T;
type ExprIntoIter = ExprArrayIter<T, Len>;
fn expr_into_iter(e: Expr<Self>) -> Self::ExprIntoIter {
ExprArrayIter {
base: e,
indexes: 0..e.ty().len(),
}
}
}
#[derive(Clone, Debug)]
pub struct ExprArrayIter<T: Type, Len: Size> {
base: Expr<ArrayType<T, Len>>,
indexes: std::ops::Range<usize>,
}
impl<T: Type, Len: Size> ExprArrayIter<T, Len> {
pub fn base(&self) -> Expr<ArrayType<T, Len>> {
self.base
}
pub fn indexes(&self) -> std::ops::Range<usize> {
self.indexes.clone()
}
}
impl<T: Type, Len: Size> Iterator for ExprArrayIter<T, Len> {
type Item = Expr<T>;
fn next(&mut self) -> Option<Self::Item> {
self.indexes.next().map(|i| self.base[i])
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
fn count(self) -> usize {
self.indexes.count()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth(n).map(|i| self.base[i])
}
fn fold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes.fold(init, |b, i| f(b, self.base[i]))
}
}
impl<T: Type, Len: Size> DoubleEndedIterator for ExprArrayIter<T, Len> {
fn next_back(&mut self) -> Option<Self::Item> {
self.indexes.next_back().map(|i| self.base[i])
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth_back(n).map(|i| self.base[i])
}
fn rfold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes.rfold(init, |b, i| f(b, self.base[i]))
}
}
impl<T: Type, Len: Size> ExactSizeIterator for ExprArrayIter<T, Len> {
fn len(&self) -> usize {
self.indexes.len()
}
}
impl<T: Type, Len: Size> FusedIterator for ExprArrayIter<T, Len> {}
impl<A: StaticType> ExprFromIterator<Expr<A>> for Array<A> {
fn expr_from_iter<T: IntoIterator<Item = Expr<A>>>(iter: T) -> Expr<Self> {
ArrayLiteral::new(
A::TYPE,
iter.into_iter().map(|v| Expr::canonical(v)).collect(),
)
.to_expr()
}
}
impl<'a, A: StaticType> ExprFromIterator<&'a Expr<A>> for Array<A> {
fn expr_from_iter<T: IntoIterator<Item = &'a Expr<A>>>(iter: T) -> Expr<Self> {
iter.into_iter().copied().collect()
}
}

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

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

@ -2,18 +2,27 @@
// See Notices.txt for copyright information
use crate::{
expr::{ops::BundleLiteral, Expr, ToExpr},
intern::{Intern, Interned},
expr::{
CastToBits, Expr, HdlPartialEqImpl, ReduceBits, ToExpr, ToSimValueInner, ValueType,
Valueless,
ops::{ArrayLiteral, BundleLiteral},
value_category::{ValueCategoryCommon, ValueCategoryExpr, ValueCategoryValue},
},
int::{Bool, DynSize},
intern::{Intern, InternSlice, Interned},
sim::value::{SimValue, SimValueEq, ToSimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, StaticType, Type,
TypeProperties, TypeWithDeref,
CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, OpaqueSimValueSize,
OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, StaticType, Type,
TypeProperties, TypeWithDeref, impl_match_variant_as_self,
},
util::HashMap,
};
use hashbrown::HashMap;
use std::{fmt, marker::PhantomData};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt, marker::PhantomData};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundleField {
pub name: Interned<str>,
pub flipped: bool,
@ -62,7 +71,7 @@ impl fmt::Display for FmtDebugInStruct {
struct BundleImpl {
fields: Interned<[BundleField]>,
name_indexes: HashMap<Interned<str>, usize>,
field_offsets: Interned<[usize]>,
field_offsets: Interned<[OpaqueSimValueSize]>,
type_properties: TypeProperties,
}
@ -82,12 +91,9 @@ impl std::fmt::Debug for BundleImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Bundle ")?;
f.debug_set()
.entries(
self.fields
.iter()
.enumerate()
.map(|(index, field)| field.fmt_debug_in_struct(self.field_offsets[index])),
)
.entries(self.fields.iter().enumerate().map(|(index, field)| {
field.fmt_debug_in_struct(self.field_offsets[index].bit_width)
}))
.finish()
}
}
@ -112,6 +118,7 @@ impl BundleTypePropertiesBuilder {
is_storable: true,
is_castable_from_bits: true,
bit_width: 0,
sim_only_values_len: 0,
})
}
pub const fn clone(&self) -> Self {
@ -119,8 +126,12 @@ impl BundleTypePropertiesBuilder {
}
#[must_use]
pub const fn field(self, flipped: bool, field_props: TypeProperties) -> Self {
let Some(bit_width) = self.0.bit_width.checked_add(field_props.bit_width) else {
panic!("bundle is too big: bit-width overflowed");
let Some(OpaqueSimValueSize {
bit_width,
sim_only_values_len,
}) = self.0.size().checked_add(field_props.size())
else {
panic!("bundle is too big: size overflowed");
};
if flipped {
Self(TypeProperties {
@ -128,6 +139,7 @@ impl BundleTypePropertiesBuilder {
is_storable: false,
is_castable_from_bits: false,
bit_width,
sim_only_values_len,
})
} else {
Self(TypeProperties {
@ -136,6 +148,7 @@ impl BundleTypePropertiesBuilder {
is_castable_from_bits: self.0.is_castable_from_bits
& field_props.is_castable_from_bits,
bit_width,
sim_only_values_len,
})
}
}
@ -144,17 +157,23 @@ impl BundleTypePropertiesBuilder {
}
}
impl Default for BundleTypePropertiesBuilder {
fn default() -> Self {
Self::new()
}
}
impl Bundle {
#[track_caller]
pub fn new(fields: Interned<[BundleField]>) -> Self {
let mut name_indexes = HashMap::with_capacity(fields.len());
let mut name_indexes = HashMap::with_capacity_and_hasher(fields.len(), Default::default());
let mut field_offsets = Vec::with_capacity(fields.len());
let mut type_props_builder = BundleTypePropertiesBuilder::new();
for (index, &BundleField { name, flipped, ty }) in fields.iter().enumerate() {
if let Some(old_index) = name_indexes.insert(name, index) {
panic!("duplicate field name {name:?}: at both index {old_index} and {index}");
}
field_offsets.push(type_props_builder.0.bit_width);
field_offsets.push(type_props_builder.0.size());
type_props_builder = type_props_builder.field(flipped, ty.type_properties());
}
Self(Intern::intern_sized(BundleImpl {
@ -170,7 +189,7 @@ impl Bundle {
pub fn field_by_name(&self, name: Interned<str>) -> Option<BundleField> {
Some(self.0.fields[*self.0.name_indexes.get(&name)?])
}
pub fn field_offsets(self) -> Interned<[usize]> {
pub fn field_offsets(self) -> Interned<[OpaqueSimValueSize]> {
self.0.field_offsets
}
pub fn type_properties(self) -> TypeProperties {
@ -204,6 +223,7 @@ impl Bundle {
impl Type for Bundle {
type BaseType = Bundle;
type MaskType = Bundle;
type SimValue = OpaqueSimValue;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
Self::new(Interned::from_iter(self.0.fields.into_iter().map(
@ -227,28 +247,136 @@ impl Type for Bundle {
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(self.type_properties().size(), opaque.size());
opaque.to_owned()
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert_eq!(self.type_properties().size(), opaque.size());
assert_eq!(value.size(), opaque.size());
value.clone_from_slice(opaque);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(self.type_properties().size(), writer.size());
assert_eq!(value.size(), writer.size());
writer.fill_cloned_from_slice(value.as_slice())
}
}
pub trait BundleType: Type<BaseType = Bundle> {
type Builder: Default;
type FilledBuilder: ToExpr<Type = Self>;
fn fields(&self) -> Interned<[BundleField]>;
}
pub struct BundleSimValueFromOpaque<'a> {
fields: std::slice::Iter<'static, BundleField>,
opaque: OpaqueSimValueSlice<'a>,
}
impl<'a> BundleSimValueFromOpaque<'a> {
#[track_caller]
pub fn new<T: BundleType>(bundle_ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self {
let fields = bundle_ty.fields();
assert_eq!(
opaque.size(),
fields
.iter()
.map(|BundleField { ty, .. }| ty.size())
.sum::<OpaqueSimValueSize>()
);
Self {
fields: Interned::into_inner(fields).iter(),
opaque,
}
}
#[track_caller]
fn field_ty_and_opaque<T: Type>(&mut self) -> (T, OpaqueSimValueSlice<'a>) {
let Some(&BundleField {
name: _,
flipped: _,
ty,
}) = self.fields.next()
else {
panic!("tried to read too many fields from BundleSimValueFromBits");
};
let (field_opaque, rest) = self.opaque.split_at(ty.size());
self.opaque = rest;
(T::from_canonical(ty), field_opaque)
}
#[track_caller]
pub fn field_from_opaque<T: Type>(&mut self) -> SimValue<T> {
let (field_ty, field_opaque) = self.field_ty_and_opaque::<T>();
SimValue::from_opaque(field_ty, field_opaque.to_owned())
}
#[track_caller]
pub fn field_clone_from_opaque<T: Type>(&mut self, field_value: &mut SimValue<T>) {
let (field_ty, field_opaque) = self.field_ty_and_opaque::<T>();
assert_eq!(field_ty, field_value.ty());
SimValue::opaque_mut(field_value).clone_from_slice(field_opaque);
}
}
pub struct BundleSimValueToOpaque<'a> {
fields: std::slice::Iter<'static, BundleField>,
writer: OpaqueSimValueWriter<'a>,
}
impl<'a> BundleSimValueToOpaque<'a> {
#[track_caller]
pub fn new<T: BundleType>(bundle_ty: T, writer: OpaqueSimValueWriter<'a>) -> Self {
let fields = bundle_ty.fields();
assert_eq!(
writer.size(),
fields
.iter()
.map(|BundleField { ty, .. }| ty.size())
.sum::<OpaqueSimValueSize>()
);
Self {
fields: Interned::into_inner(fields).iter(),
writer,
}
}
#[track_caller]
pub fn field<T: Type>(&mut self, field_value: &SimValue<T>) {
let Some(&BundleField {
name: _,
flipped: _,
ty,
}) = self.fields.next()
else {
panic!("tried to write too many fields with BundleSimValueToOpaque");
};
assert_eq!(T::from_canonical(ty), field_value.ty());
self.writer.fill_prefix_with(ty.size(), |writer| {
writer.fill_cloned_from_slice(SimValue::opaque(field_value).as_slice())
});
}
#[track_caller]
pub fn finish(mut self) -> OpaqueSimValueWritten<'a> {
assert_eq!(
self.fields.next(),
None,
"wrote too few fields with BundleSimValueToOpaque"
);
self.writer
.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
}
}
#[derive(Default)]
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
}
@ -284,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,)*
@ -316,8 +443,26 @@ macro_rules! impl_tuple_builder_fields {
($global:tt []) => {};
}
macro_rules! get_unit_ty {
($($tt:tt)*) => {
()
};
}
macro_rules! impl_tuples {
([$({#[num = $num:literal, field = $field:ident] $var:ident: $T:ident})*] []) => {
(
[$({
#[
num = $num:tt,
field = $field:ident,
ty = $ty_var:ident: $Ty:ident,
lhs = $lhs_var:ident: $Lhs:ident,
rhs = $rhs_var:ident: $Rhs:ident
]
$var:ident: $T:ident
})*]
[]
) => {
impl_tuple_builder_fields! {
{}
[$({
@ -329,6 +474,7 @@ macro_rules! impl_tuples {
impl<$($T: Type,)*> Type for ($($T,)*) {
type BaseType = Bundle;
type MaskType = ($($T::MaskType,)*);
type SimValue = ($(SimValue<$T>,)*);
type MatchVariant = ($(Expr<$T>,)*);
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
@ -342,6 +488,7 @@ macro_rules! impl_tuples {
std::iter::once(MatchVariantWithoutScope(($(Expr::field(this, stringify!($num)),)*)))
}
fn mask_type(&self) -> Self::MaskType {
#![allow(clippy::unused_unit)]
let ($($var,)*) = self;
($($var.mask_type(),)*)
}
@ -350,6 +497,7 @@ macro_rules! impl_tuples {
}
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
#![allow(clippy::unused_unit)]
let CanonicalType::Bundle(bundle) = canonical_type else {
panic!("expected bundle");
};
@ -358,26 +506,52 @@ macro_rules! impl_tuples {
};
$(let BundleField { name, flipped, ty } = $var;
assert_eq!(&*name, stringify!($num));
assert_eq!(flipped, false);
assert!(!flipped);
let $var = $T::from_canonical(ty);)*
($($var,)*)
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
#![allow(unused_mut, unused_variables)]
let mut v = BundleSimValueFromOpaque::new(*self, opaque);
$(let $var = v.field_from_opaque();)*
($($var,)*)
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
#![allow(unused_mut, unused_variables)]
let mut v = BundleSimValueFromOpaque::new(*self, opaque);
let ($($var,)*) = value;
$(v.field_clone_from_opaque($var);)*
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
#![allow(unused_mut, unused_variables)]
let mut v = BundleSimValueToOpaque::new(*self, writer);
let ($($var,)*) = value;
$(v.field($var);)*
v.finish()
}
}
impl<$($T: Type,)*> BundleType for ($($T,)*) {
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,)*) {
fn expr_deref(this: &Expr<Self>) -> &Self::MatchVariant {
let _ = this;
Interned::<_>::into_inner(($(Expr::field(*this, stringify!($num)),)*).intern_sized())
Interned::into_inner(($(Expr::field(*this, stringify!($num)),)*).intern_sized())
}
}
impl<$($T: StaticType,)*> StaticType for ($($T,)*) {
@ -394,27 +568,187 @@ 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,)*) {
#[track_caller]
fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue<CanonicalType> {
SimValue::into_canonical(ToSimValueWithType::<Bundle>::to_sim_value_with_type(self, Bundle::from_canonical(ty)))
}
#[track_caller]
fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue<CanonicalType>
{
SimValue::into_canonical(ToSimValueWithType::<Bundle>::into_sim_value_with_type(self, Bundle::from_canonical(ty)))
}
}
impl<$($T: ToSimValueWithType<CanonicalType>,)*> ToSimValueWithType<Bundle> for ($($T,)*) {
#[track_caller]
fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue<Bundle> {
let ($($var,)*) = self;
let [$($ty_var,)*] = *ty.fields() else {
panic!("bundle has wrong number of fields");
};
$(let $var = $var.to_sim_value_with_type($ty_var.ty);)*
ToSimValueWithType::into_sim_value_with_type(($($var,)*), ty)
}
#[track_caller]
fn into_sim_value_with_type(self, ty: Bundle) -> SimValue<Bundle> {
#![allow(unused_mut)]
#![allow(clippy::unused_unit)]
let ($($var,)*) = self;
let [$($ty_var,)*] = *ty.fields() else {
panic!("bundle has wrong number of fields");
};
let mut opaque = OpaqueSimValue::empty();
$(let $var = $var.into_sim_value_with_type($ty_var.ty);
assert_eq!($var.ty(), $ty_var.ty);
opaque.extend_from_slice(SimValue::opaque(&$var).as_slice());
)*
SimValue::from_opaque(ty, opaque)
}
}
impl<$($T: ToSimValueWithType<$Ty>, $Ty: Type,)*> ToSimValueWithType<($($Ty,)*)> for ($($T,)*) {
#[track_caller]
fn to_sim_value_with_type(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> {
let ($($var,)*) = self;
let ($($ty_var,)*) = ty;
$(let $var = $var.to_sim_value_with_type($ty_var);)*
SimValue::from_value(ty, ($($var,)*))
}
#[track_caller]
fn into_sim_value_with_type(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> {
let ($($var,)*) = self;
let ($($ty_var,)*) = ty;
$(let $var = $var.into_sim_value_with_type($ty_var);)*
SimValue::from_value(ty, ($($var,)*))
}
}
impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*)
where
Self: ValueType<Type = ($($T::Type,)*)>,
{
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self::Type> {
let ($($var,)*) = self;
$(let $var = $var.to_sim_value();)*
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
}
#[track_caller]
fn into_sim_value(self) -> SimValue<Self::Type> {
let ($($var,)*) = self;
$(let $var = $var.to_sim_value();)*
SimValue::from_value(($($var.ty(),)*), ($($var,)*))
}
}
impl<$($Lhs: Type + HdlPartialEqImpl<$Rhs>, $Rhs: Type,)*> HdlPartialEqImpl<($($Rhs,)*)> for ($($Lhs,)*) {
#[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($Lhs::cmp_expr_eq($lhs_var, $rhs_var)),)*]),
)
.cast_to_bits()
.all_one_bits()
}
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<($($Rhs,)*)>) -> Expr<Bool> {
let ($($lhs_var,)*) = *lhs;
let ($($rhs_var,)*) = *rhs;
ArrayLiteral::<Bool, DynSize>::new(
Bool,
FromIterator::from_iter([$(Expr::canonical($Lhs::cmp_expr_ne($lhs_var, $rhs_var)),)*]),
)
.cast_to_bits()
.any_one_bits()
}
#[track_caller]
fn cmp_valueless_eq(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
let ($($lhs_var,)*) = lhs.ty();
let ($($rhs_var,)*) = rhs.ty();
// let them check that the types can be compared
$($Lhs::cmp_valueless_eq(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
Valueless::new(Bool)
}
#[track_caller]
fn cmp_valueless_ne(lhs: Valueless<Self>, rhs: Valueless<($($Rhs,)*)>) -> Valueless<Bool> {
let ($($lhs_var,)*) = lhs.ty();
let ($($rhs_var,)*) = rhs.ty();
// let them check that the types can be compared
$($Lhs::cmp_valueless_ne(Valueless::new($lhs_var), Valueless::new($rhs_var));)*
Valueless::new(Bool)
}
}
impl<$($T: SimValueEq + HdlPartialEqImpl<$T>,)*> SimValueEq for ($($T,)*) {}
};
([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => {
impl_tuples!([$($lhs)*] []);
@ -424,17 +758,162 @@ macro_rules! impl_tuples {
impl_tuples! {
[] [
{#[num = 0, field = field_0] v0: T0}
{#[num = 1, field = field_1] v1: T1}
{#[num = 2, field = field_2] v2: T2}
{#[num = 3, field = field_3] v3: T3}
{#[num = 4, field = field_4] v4: T4}
{#[num = 5, field = field_5] v5: T5}
{#[num = 6, field = field_6] v6: T6}
{#[num = 7, field = field_7] v7: T7}
{#[num = 8, field = field_8] v8: T8}
{#[num = 9, field = field_9] v9: T9}
{#[num = 10, field = field_10] v10: T10}
{#[num = 11, field = field_11] v11: T11}
{#[num = 0, field = field_0, ty = ty0: Ty0, lhs = lhs0: Lhs0, rhs = rhs0: Rhs0] v0: T0}
{#[num = 1, field = field_1, ty = ty1: Ty1, lhs = lhs1: Lhs1, rhs = rhs1: Rhs1] v1: T1}
{#[num = 2, field = field_2, ty = ty2: Ty2, lhs = lhs2: Lhs2, rhs = rhs2: Rhs2] v2: T2}
{#[num = 3, field = field_3, ty = ty3: Ty3, lhs = lhs3: Lhs3, rhs = rhs3: Rhs3] v3: T3}
{#[num = 4, field = field_4, ty = ty4: Ty4, lhs = lhs4: Lhs4, rhs = rhs4: Rhs4] v4: T4}
{#[num = 5, field = field_5, ty = ty5: Ty5, lhs = lhs5: Lhs5, rhs = rhs5: Rhs5] v5: T5}
{#[num = 6, field = field_6, ty = ty6: Ty6, lhs = lhs6: Lhs6, rhs = rhs6: Rhs6] v6: T6}
{#[num = 7, field = field_7, ty = ty7: Ty7, lhs = lhs7: Lhs7, rhs = rhs7: Rhs7] v7: T7}
{#[num = 8, field = field_8, ty = ty8: Ty8, lhs = lhs8: Lhs8, rhs = rhs8: Rhs8] v8: T8}
{#[num = 9, field = field_9, ty = ty9: Ty9, lhs = lhs9: Lhs9, rhs = rhs9: Rhs9] v9: T9}
{#[num = 10, field = field_10, ty = ty10: Ty10, lhs = lhs10: Lhs10, rhs = rhs10: Rhs10] v10: T10}
{#[num = 11, field = field_11, ty = ty11: Ty11, lhs = lhs11: Lhs11, rhs = rhs11: Rhs11] v11: T11}
]
}
impl<T: ?Sized + Send + Sync + 'static> Type for PhantomData<T> {
type BaseType = Bundle;
type MaskType = ();
type SimValue = PhantomData<T>;
type MatchVariant = PhantomData<T>;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Self::MatchVariant>;
type MatchVariantsIter = std::iter::Once<Self::MatchVariantAndInactiveScope>;
fn match_variants(
this: Expr<Self>,
source_location: SourceLocation,
) -> Self::MatchVariantsIter {
let _ = this;
let _ = source_location;
std::iter::once(MatchVariantWithoutScope(PhantomData))
}
fn mask_type(&self) -> Self::MaskType {
()
}
fn canonical(&self) -> CanonicalType {
Bundle::new(self.fields()).canonical()
}
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::Bundle(bundle) = canonical_type else {
panic!("expected bundle");
};
assert!(
bundle.fields().is_empty(),
"bundle has wrong number of fields"
);
PhantomData
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert!(opaque.is_empty());
*self
}
fn sim_value_clone_from_opaque(
&self,
_value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert!(opaque.is_empty());
}
fn sim_value_to_opaque<'w>(
&self,
_value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
}
}
pub struct PhantomDataBuilder<T: ?Sized + Send + Sync + 'static>(PhantomData<T>);
impl<T: ?Sized + Send + Sync + 'static> Default for PhantomDataBuilder<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomDataBuilder<T> {
type Type = PhantomData<T>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
PhantomData
}
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomDataBuilder<T> {
fn to_expr(&self) -> Expr<Self::Type> {
PhantomData.to_expr()
}
}
impl<T: ?Sized + Send + Sync + 'static> BundleType for PhantomData<T> {
type Builder = PhantomDataBuilder<T>;
fn fields(&self) -> Interned<[BundleField]> {
Interned::default()
}
}
impl<T: ?Sized + Send + Sync + 'static> TypeWithDeref for PhantomData<T> {
fn expr_deref(_this: &Expr<Self>) -> &Self::MatchVariant {
&PhantomData
}
}
impl<T: ?Sized + Send + Sync + 'static> StaticType for PhantomData<T> {
const TYPE: Self = PhantomData;
const MASK_TYPE: Self::MaskType = ();
const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
}
impl<T: ?Sized + Send + Sync + 'static> ValueType for PhantomData<T> {
type Type = PhantomData<T>;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
PhantomData
}
}
impl<T: ?Sized + Send + Sync + 'static> ToExpr for PhantomData<T> {
fn to_expr(&self) -> Expr<Self::Type> {
BundleLiteral::new(PhantomData, Interned::default()).to_expr()
}
}
impl<T: ?Sized + Send + Sync + 'static> ToSimValue for PhantomData<T> {
#[track_caller]
fn to_sim_value(&self) -> SimValue<Self> {
SimValue::from_value(*self, *self)
}
}
impl<T: ?Sized + Send + Sync + 'static> ToSimValueWithType<Self> for PhantomData<T> {
#[track_caller]
fn to_sim_value_with_type(&self, ty: Self) -> SimValue<Self> {
SimValue::from_value(ty, *self)
}
}
impl<T: ?Sized> ToSimValueWithType<Bundle> for PhantomData<T> {
#[track_caller]
fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue<Bundle> {
assert!(ty.fields().is_empty());
SimValue::from_opaque(ty, OpaqueSimValue::empty())
}
}
impl<T: ?Sized> ToSimValueWithType<CanonicalType> for PhantomData<T> {
#[track_caller]
fn to_sim_value_with_type(&self, canonical_ty: CanonicalType) -> SimValue<CanonicalType> {
let ty = Bundle::from_canonical(canonical_ty);
assert!(ty.fields().is_empty());
SimValue::from_opaque(canonical_ty, OpaqueSimValue::empty())
}
}

View file

@ -1,320 +0,0 @@
use crate::{
bundle::{Bundle, BundleType},
firrtl,
intern::Interned,
module::Module,
};
use clap::{
builder::{OsStringValueParser, TypedValueParser},
Args, Parser, Subcommand, ValueEnum, ValueHint,
};
use eyre::{eyre, Report};
use std::{error, ffi::OsString, fmt, io, path::PathBuf, process};
pub type Result<T = (), E = CliError> = std::result::Result<T, E>;
pub struct CliError(Report);
impl fmt::Debug for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl error::Error for CliError {}
impl From<io::Error> for CliError {
fn from(value: io::Error) -> Self {
CliError(Report::new(value))
}
}
pub trait RunPhase<Arg> {
type Output;
fn run(&self, arg: Arg) -> Result<Self::Output>;
}
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct BaseArgs {
/// the directory to put the generated main output file and associated files in
#[arg(short, long, value_hint = ValueHint::DirPath)]
pub output: PathBuf,
/// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
#[arg(long)]
pub file_stem: Option<String>,
}
impl BaseArgs {
pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend {
firrtl::FileBackend {
dir_path: self.output.clone(),
top_fir_file_stem: self.file_stem.clone(),
}
}
}
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct FirrtlArgs {
#[command(flatten)]
pub base: BaseArgs,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FirrtlOutput {
pub file_stem: String,
}
impl FirrtlOutput {
pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf {
let mut retval = args.base.output.join(&self.file_stem);
retval.set_extension("fir");
retval
}
}
impl FirrtlArgs {
fn run_impl(&self, top_module: Module<Bundle>) -> Result<FirrtlOutput> {
let firrtl::FileBackend {
top_fir_file_stem, ..
} = firrtl::export(self.base.to_firrtl_file_backend(), &top_module)?;
Ok(FirrtlOutput {
file_stem: top_fir_file_stem.expect(
"export is known to set the file stem from the circuit name if not provided",
),
})
}
}
impl<T: BundleType> RunPhase<Module<T>> for FirrtlArgs {
type Output = FirrtlOutput;
fn run(&self, top_module: Module<T>) -> Result<Self::Output> {
self.run_impl(top_module.canonical())
}
}
impl<T: BundleType> RunPhase<Interned<Module<T>>> for FirrtlArgs {
type Output = FirrtlOutput;
fn run(&self, top_module: Interned<Module<T>>) -> Result<Self::Output> {
self.run(*top_module)
}
}
/// based on [LLVM Circt's recommended lowering options
/// ](https://circt.llvm.org/docs/VerilogGeneration/#recommended-loweringoptions-by-target)
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum VerilogDialect {
Questa,
Spyglass,
Verilator,
Vivado,
Yosys,
}
impl VerilogDialect {
pub fn firtool_extra_args(self) -> &'static [&'static str] {
match self {
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
VerilogDialect::Spyglass => {
&["--lowering-options=explicitBitcast,disallowExpressionInliningInPorts"]
}
VerilogDialect::Verilator => &[
"--lowering-options=locationInfoStyle=wrapInAtSquareBracket,disallowLocalVariables",
],
VerilogDialect::Vivado => &["--lowering-options=mitigateVivadoArrayIndexConstPropBug"],
VerilogDialect::Yosys => {
&["--lowering-options=disallowLocalVariables,disallowPackedArrays"]
}
}
}
}
#[derive(Args, Debug)]
#[non_exhaustive]
pub struct VerilogArgs {
#[command(flatten)]
pub firrtl: FirrtlArgs,
#[arg(
long,
default_value = "firtool",
env = "FIRTOOL",
value_hint = ValueHint::CommandName,
value_parser = OsStringValueParser::new().try_map(which::which)
)]
pub firtool: PathBuf,
#[arg(long)]
pub firtool_extra_args: Vec<OsString>,
/// adapt the generated Verilog for a particular toolchain
#[arg(long)]
pub verilog_dialect: Option<VerilogDialect>,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct VerilogOutput {
pub firrtl: FirrtlOutput,
}
impl VerilogOutput {
pub fn verilog_file(&self, args: &VerilogArgs) -> PathBuf {
let mut retval = args.firrtl.base.output.join(&self.firrtl.file_stem);
retval.set_extension("v");
retval
}
}
impl VerilogArgs {
fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> {
let output = VerilogOutput {
firrtl: firrtl_output,
};
let mut cmd = process::Command::new(&self.firtool);
cmd.arg(output.firrtl.firrtl_file(&self.firrtl));
cmd.arg("-o");
cmd.arg(output.verilog_file(self));
if let Some(dialect) = self.verilog_dialect {
cmd.args(dialect.firtool_extra_args());
}
cmd.args(&self.firtool_extra_args);
cmd.current_dir(&self.firrtl.base.output);
let status = cmd.status()?;
if status.success() {
Ok(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
self.firtool.display()
)))
}
}
}
impl<Arg> RunPhase<Arg> for VerilogArgs
where
FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>,
{
type Output = VerilogOutput;
fn run(&self, arg: Arg) -> Result<Self::Output> {
let firrtl_output = self.firrtl.run(arg)?;
self.run_impl(firrtl_output)
}
}
#[derive(Subcommand, Debug)]
enum CliCommand {
/// Generate FIRRTL
Firrtl(FirrtlArgs),
/// Generate Verilog
Verilog(VerilogArgs),
}
/// a simple CLI
///
/// Use like:
///
/// ```no_run
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use fayalite::cli;
///
/// fn main() -> cli::Result {
/// cli::Cli::parse().run(my_module())
/// }
/// ```
///
/// You can also use it with a larger [`clap`]-based CLI like so:
///
/// ```no_run
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use clap::{Subcommand, Parser};
/// use fayalite::cli;
///
/// #[derive(Subcommand)]
/// pub enum Cmd {
/// #[command(flatten)]
/// Fayalite(cli::Cli),
/// MySpecialCommand {
/// #[arg(long)]
/// foo: bool,
/// },
/// }
///
/// #[derive(Parser)]
/// pub struct Cli {
/// #[command(subcommand)]
/// cmd: Cmd, // or just use cli::Cli directly if you don't need more subcommands
/// }
///
/// fn main() -> cli::Result {
/// match Cli::parse().cmd {
/// Cmd::Fayalite(v) => v.run(my_module())?,
/// Cmd::MySpecialCommand { foo } => println!("special: foo={foo}"),
/// }
/// Ok(())
/// }
/// ```
#[derive(Parser, Debug)]
// clear things that would be crate-specific
#[command(name = "Fayalite Simple CLI", about = None, long_about = None)]
pub struct Cli {
#[command(subcommand)]
subcommand: CliCommand,
}
impl clap::Subcommand for Cli {
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
CliCommand::augment_subcommands(cmd)
}
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
CliCommand::augment_subcommands_for_update(cmd)
}
fn has_subcommand(name: &str) -> bool {
CliCommand::has_subcommand(name)
}
}
impl<T> RunPhase<T> for Cli
where
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
{
type Output = ();
fn run(&self, arg: T) -> Result<Self::Output> {
match &self.subcommand {
CliCommand::Firrtl(c) => {
c.run(arg)?;
}
CliCommand::Verilog(c) => {
c.run(arg)?;
}
}
Ok(())
}
}
impl Cli {
/// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`]
pub fn parse() -> Self {
clap::Parser::parse()
}
/// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`]
pub fn run<T>(&self, top_module: T) -> Result<()>
where
Self: RunPhase<T, Output = ()>,
{
RunPhase::run(self, top_module)
}
}

View file

@ -1,13 +1,18 @@
// 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,
reset::{Reset, ResetType},
sim::value::SimValue,
source_location::SourceLocation,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
ty::{
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self,
},
};
use bitvec::{bits, order::Lsb0};
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct Clock;
@ -15,6 +20,7 @@ pub struct Clock;
impl Type for Clock {
type BaseType = Clock;
type MaskType = Bool;
type SimValue = bool;
impl_match_variant_as_self!();
@ -36,6 +42,31 @@ impl Type for Clock {
};
retval
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
opaque.bits()[0]
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
*value = opaque.bits()[0];
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1));
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
[bits![0], bits![1]][*value as usize],
))
}
}
impl Clock {
@ -55,46 +86,70 @@ impl StaticType for Clock {
is_storable: false,
is_castable_from_bits: true,
bit_width: 1,
sim_only_values_len: 0,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
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
}
}
#[hdl]
pub struct ClockDomain {
pub struct ClockDomain<R: ResetType = Reset> {
pub clk: Clock,
pub rst: Reset,
pub rst: R,
}
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,21 +2,28 @@
// See Notices.txt for copyright information
use crate::{
expr::{ops::VariantAccess, Expr, ToExpr},
expr::{Expr, HdlPartialEq, HdlPartialEqImpl, ToExpr, ValueType, ops::VariantAccess},
hdl,
int::Bool,
int::{Bool, UIntValue},
intern::{Intern, Interned},
module::{
enum_match_variants_helper, EnumMatchVariantAndInactiveScopeImpl,
EnumMatchVariantsIterImpl, Scope,
EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, connect,
enum_match_variants_helper, incomplete_wire, wire,
},
sim::value::SimValue,
source_location::SourceLocation,
ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties},
ty::{
CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, OpaqueSimValueSize,
OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten, StaticType, Type,
TypeProperties,
},
util::HashMap,
};
use hashbrown::HashMap;
use std::{fmt, iter::FusedIterator};
use bitvec::{order::Lsb0, slice::BitSlice, view::BitView};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, convert::Infallible, fmt, iter::FusedIterator, sync::Arc};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct EnumVariant {
pub name: Interned<str>,
pub ty: Option<CanonicalType>,
@ -111,6 +118,7 @@ impl EnumTypePropertiesBuilder {
is_storable: true,
is_castable_from_bits: true,
bit_width: 0,
sim_only_values_len: 0,
},
variant_count: 0,
}
@ -129,9 +137,14 @@ impl EnumTypePropertiesBuilder {
is_storable,
is_castable_from_bits,
bit_width,
sim_only_values_len,
}) = field_props
{
assert!(is_passive, "variant type must be a passive type");
assert!(
sim_only_values_len == 0,
"can't have `SimOnlyValue`s in an Enum"
);
type_properties = TypeProperties {
is_passive: true,
is_storable: type_properties.is_storable & is_storable,
@ -142,6 +155,7 @@ impl EnumTypePropertiesBuilder {
} else {
type_properties.bit_width
},
sim_only_values_len: 0,
};
}
Self {
@ -149,6 +163,12 @@ impl EnumTypePropertiesBuilder {
variant_count: variant_count + 1,
}
}
#[must_use]
pub fn variants(self, variants: impl IntoIterator<Item = EnumVariant>) -> Self {
variants.into_iter().fold(self, |this, variant| {
this.variant(variant.ty.map(CanonicalType::type_properties))
})
}
pub const fn finish(self) -> TypeProperties {
assert!(
self.variant_count != 0,
@ -169,10 +189,17 @@ impl EnumTypePropertiesBuilder {
}
}
impl Default for EnumTypePropertiesBuilder {
fn default() -> Self {
Self::new()
}
}
impl Enum {
#[track_caller]
pub fn new(variants: Interned<[EnumVariant]>) -> Self {
let mut name_indexes = HashMap::with_capacity(variants.len());
let mut name_indexes =
HashMap::with_capacity_and_hasher(variants.len(), Default::default());
let mut type_props_builder = EnumTypePropertiesBuilder::new();
for (index, EnumVariant { name, ty }) in variants.iter().enumerate() {
if let Some(old_index) = name_indexes.insert(*name, index) {
@ -232,13 +259,14 @@ impl Enum {
pub trait EnumType:
Type<
BaseType = Enum,
MaskType = Bool,
MatchActiveScope = Scope,
MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>,
MatchVariantsIter = EnumMatchVariantsIter<Self>,
>
BaseType = Enum,
MaskType = Bool,
MatchActiveScope = Scope,
MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>,
MatchVariantsIter = EnumMatchVariantsIter<Self>,
>
{
type SimBuilder: From<Self>;
fn variants(&self) -> Interned<[EnumVariant]>;
fn match_activate_scope(
v: Self::MatchVariantAndInactiveScope,
@ -301,7 +329,18 @@ impl<T: EnumType> DoubleEndedIterator for EnumMatchVariantsIter<T> {
}
}
pub struct NoBuilder {
_ty: Enum,
}
impl From<Enum> for NoBuilder {
fn from(_ty: Enum) -> Self {
Self { _ty }
}
}
impl EnumType for Enum {
type SimBuilder = NoBuilder;
fn match_activate_scope(
v: Self::MatchVariantAndInactiveScope,
) -> (Self::MatchVariant, Self::MatchActiveScope) {
@ -316,6 +355,7 @@ impl EnumType for Enum {
impl Type for Enum {
type BaseType = Enum;
type MaskType = Bool;
type SimValue = OpaqueSimValue;
type MatchVariant = Option<Expr<CanonicalType>>;
type MatchActiveScope = Scope;
type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope<Self>;
@ -346,6 +386,341 @@ impl Type for Enum {
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(self.type_properties().size(), opaque.size());
opaque.to_owned()
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert_eq!(self.type_properties().size(), opaque.size());
assert_eq!(value.size(), opaque.size());
value.clone_from_slice(opaque);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(self.type_properties().size(), writer.size());
assert_eq!(value.size(), writer.size());
writer.fill_cloned_from_slice(value.as_slice())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct EnumPaddingSimValue {
bits: Option<UIntValue>,
}
impl EnumPaddingSimValue {
pub const fn new() -> Self {
Self { bits: None }
}
pub fn bit_width(&self) -> Option<usize> {
self.bits.as_ref().map(UIntValue::width)
}
pub fn bits(&self) -> &Option<UIntValue> {
&self.bits
}
pub fn bits_mut(&mut self) -> &mut Option<UIntValue> {
&mut self.bits
}
pub fn into_bits(self) -> Option<UIntValue> {
self.bits
}
pub fn from_bits(bits: Option<UIntValue>) -> Self {
Self { bits }
}
pub fn from_bitslice(v: &BitSlice) -> Self {
Self {
bits: Some(UIntValue::new(Arc::new(v.to_bitvec()))),
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct UnknownVariantSimValue {
discriminant: usize,
body_bits: UIntValue,
}
impl UnknownVariantSimValue {
pub fn discriminant(&self) -> usize {
self.discriminant
}
pub fn body_bits(&self) -> &UIntValue {
&self.body_bits
}
pub fn body_bits_mut(&mut self) -> &mut UIntValue {
&mut self.body_bits
}
pub fn into_body_bits(self) -> UIntValue {
self.body_bits
}
pub fn into_parts(self) -> (usize, UIntValue) {
(self.discriminant, self.body_bits)
}
pub fn new(discriminant: usize, body_bits: UIntValue) -> Self {
Self {
discriminant,
body_bits,
}
}
}
pub struct EnumSimValueFromOpaque<'a> {
variants: Interned<[EnumVariant]>,
discriminant: usize,
body_bits: &'a BitSlice,
}
impl<'a> EnumSimValueFromOpaque<'a> {
#[track_caller]
pub fn new<T: EnumType>(ty: T, opaque: OpaqueSimValueSlice<'a>) -> Self {
let variants = ty.variants();
let size = EnumTypePropertiesBuilder::new()
.variants(variants)
.finish()
.size();
assert!(size.only_bit_width().is_some());
assert_eq!(size, opaque.size());
let (discriminant_bits, body_bits) = opaque
.bits()
.split_at(discriminant_bit_width_impl(variants.len()));
let mut discriminant = 0usize;
discriminant.view_bits_mut::<Lsb0>()[..discriminant_bits.len()]
.copy_from_bitslice(discriminant_bits);
Self {
variants,
discriminant,
body_bits,
}
}
pub fn discriminant(&self) -> usize {
self.discriminant
}
#[track_caller]
#[cold]
fn usage_error(&self, clone: bool) -> ! {
let clone = if clone { "clone_" } else { "" };
match self.variants.get(self.discriminant) {
None => {
panic!("should have called EnumSimValueFromBits::unknown_variant_{clone}from_bits");
}
Some(EnumVariant { ty: None, .. }) => {
panic!(
"should have called EnumSimValueFromBits::variant_no_field_{clone}from_bits"
);
}
Some(EnumVariant { ty: Some(_), .. }) => {
panic!(
"should have called EnumSimValueFromBits::variant_with_field_{clone}from_bits"
);
}
}
}
#[track_caller]
fn known_variant(&self, clone: bool) -> (Option<CanonicalType>, &'a BitSlice, &'a BitSlice) {
let Some(EnumVariant { ty, .. }) = self.variants.get(self.discriminant) else {
self.usage_error(clone);
};
let variant_bit_width = ty.map_or(0, CanonicalType::bit_width);
let (variant_bits, padding_bits) = self.body_bits.split_at(variant_bit_width);
(*ty, variant_bits, padding_bits)
}
#[track_caller]
pub fn unknown_variant_from_opaque(self) -> UnknownVariantSimValue {
let None = self.variants.get(self.discriminant) else {
self.usage_error(false);
};
UnknownVariantSimValue::new(
self.discriminant,
UIntValue::new(Arc::new(self.body_bits.to_bitvec())),
)
}
#[track_caller]
pub fn unknown_variant_clone_from_opaque(self, value: &mut UnknownVariantSimValue) {
let None = self.variants.get(self.discriminant) else {
self.usage_error(true);
};
value.discriminant = self.discriminant;
assert_eq!(value.body_bits.width(), self.body_bits.len());
value
.body_bits
.bits_mut()
.copy_from_bitslice(self.body_bits);
}
#[track_caller]
pub fn variant_no_field_from_opaque(self) -> EnumPaddingSimValue {
let (None, _variant_bits, padding_bits) = self.known_variant(false) else {
self.usage_error(false);
};
EnumPaddingSimValue::from_bitslice(padding_bits)
}
#[track_caller]
pub fn variant_with_field_from_opaque<T: Type>(self) -> (SimValue<T>, EnumPaddingSimValue) {
let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(false) else {
self.usage_error(false);
};
(
SimValue::from_bitslice(T::from_canonical(variant_ty), variant_bits),
EnumPaddingSimValue::from_bitslice(padding_bits),
)
}
#[track_caller]
fn clone_padding_from_bits(padding: &mut EnumPaddingSimValue, padding_bits: &BitSlice) {
match padding.bits_mut() {
None => *padding = EnumPaddingSimValue::from_bitslice(padding_bits),
Some(padding) => {
assert_eq!(padding.width(), padding_bits.len());
padding.bits_mut().copy_from_bitslice(padding_bits);
}
}
}
#[track_caller]
pub fn variant_no_field_clone_from_opaque(self, padding: &mut EnumPaddingSimValue) {
let (None, _variant_bits, padding_bits) = self.known_variant(true) else {
self.usage_error(true);
};
Self::clone_padding_from_bits(padding, padding_bits);
}
#[track_caller]
pub fn variant_with_field_clone_from_opaque<T: Type>(
self,
value: &mut SimValue<T>,
padding: &mut EnumPaddingSimValue,
) {
let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(true) else {
self.usage_error(true);
};
assert_eq!(value.ty(), T::from_canonical(variant_ty));
SimValue::bits_mut(value)
.bits_mut()
.copy_from_bitslice(variant_bits);
Self::clone_padding_from_bits(padding, padding_bits);
}
}
pub struct EnumSimValueToOpaque<'a> {
variants: Interned<[EnumVariant]>,
bit_width: usize,
discriminant_bit_width: usize,
writer: OpaqueSimValueWriter<'a>,
}
impl<'a> EnumSimValueToOpaque<'a> {
#[track_caller]
pub fn new<T: EnumType>(ty: T, writer: OpaqueSimValueWriter<'a>) -> Self {
let variants = ty.variants();
let size = EnumTypePropertiesBuilder::new()
.variants(variants)
.finish()
.size();
assert_eq!(size, writer.size());
Self {
variants,
bit_width: size
.only_bit_width()
.expect("enums should only contain bits"),
discriminant_bit_width: discriminant_bit_width_impl(variants.len()),
writer,
}
}
#[track_caller]
fn write_discriminant(&mut self, mut discriminant: usize) {
let orig_discriminant = discriminant;
let discriminant_bits =
&mut discriminant.view_bits_mut::<Lsb0>()[..self.discriminant_bit_width];
self.writer.fill_prefix_with(
OpaqueSimValueSize::from_bit_width(self.discriminant_bit_width),
|writer| {
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(discriminant_bits))
},
);
discriminant_bits.fill(false);
assert!(
discriminant == 0,
"{orig_discriminant:#x} is too big to fit in enum discriminant bits",
);
}
#[track_caller]
pub fn unknown_variant_to_opaque(
mut self,
value: &UnknownVariantSimValue,
) -> OpaqueSimValueWritten<'a> {
self.write_discriminant(value.discriminant);
let None = self.variants.get(value.discriminant) else {
panic!("can't use UnknownVariantSimValue to set known discriminant");
};
assert_eq!(
self.bit_width - self.discriminant_bit_width,
value.body_bits.width()
);
self.writer
.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(value.body_bits.bits()))
}
#[track_caller]
fn known_variant(
mut self,
discriminant: usize,
value: Option<&OpaqueSimValue>,
padding: &EnumPaddingSimValue,
) -> OpaqueSimValueWritten<'a> {
self.write_discriminant(discriminant);
let variant_ty = self.variants[discriminant].ty;
let variant_size = variant_ty.map_or(OpaqueSimValueSize::empty(), CanonicalType::size);
if let Some(value) = value {
if variant_ty.is_none() {
panic!("expected variant to have no field");
}
self.writer.fill_prefix_with(variant_size, |writer| {
writer.fill_cloned_from_slice(value.as_slice())
});
} else if variant_ty.is_some() {
panic!("expected variant to have a field");
}
if let Some(padding) = padding.bits() {
assert_eq!(padding.ty().type_properties().size(), self.writer.size());
self.writer
.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(padding.bits()))
} else {
self.writer.fill_with_zeros()
}
}
#[track_caller]
pub fn variant_no_field_to_opaque(
self,
discriminant: usize,
padding: &EnumPaddingSimValue,
) -> OpaqueSimValueWritten<'a> {
self.known_variant(discriminant, None, padding)
}
#[track_caller]
pub fn variant_with_field_to_opaque<T: Type>(
self,
discriminant: usize,
value: &SimValue<T>,
padding: &EnumPaddingSimValue,
) -> OpaqueSimValueWritten<'a> {
let Some(variant_ty) = self.variants[discriminant].ty else {
panic!("expected variant to have no field");
};
assert_eq!(value.ty(), T::from_canonical(variant_ty));
self.known_variant(discriminant, Some(SimValue::opaque(value)), padding)
}
}
#[doc(hidden)]
pub fn assert_is_enum_type<T: EnumType>(v: T) -> T {
v
}
#[doc(hidden)]
pub fn enum_type_to_sim_builder<T: EnumType>(v: T) -> T::SimBuilder {
v.into()
}
#[hdl]
@ -354,6 +729,89 @@ pub enum HdlOption<T: Type> {
HdlSome(T),
}
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_expr_eq(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[hdl]
let cmp_eq = wire();
#[hdl]
match lhs {
HdlSome(lhs) =>
{
#[hdl]
match rhs {
HdlSome(rhs) => connect(cmp_eq, lhs.cmp_eq(rhs)),
HdlNone => connect(cmp_eq, false),
}
}
HdlNone =>
{
#[hdl]
match rhs {
HdlSome(_) => connect(cmp_eq, false),
HdlNone => connect(cmp_eq, true),
}
}
}
cmp_eq
}
#[hdl]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<HdlOption<Rhs>>) -> Expr<Bool> {
#[hdl]
let cmp_ne = wire();
#[hdl]
match lhs {
HdlSome(lhs) =>
{
#[hdl]
match rhs {
HdlSome(rhs) => connect(cmp_ne, lhs.cmp_ne(rhs)),
HdlNone => connect(cmp_ne, true),
}
}
HdlNone =>
{
#[hdl]
match rhs {
HdlSome(_) => connect(cmp_ne, true),
HdlNone => connect(cmp_ne, false),
}
}
}
cmp_ne
}
}
#[allow(non_snake_case)]
pub fn HdlNone<T: StaticType>() -> Expr<HdlOption<T>> {
HdlOption[T::TYPE].HdlNone()
@ -362,5 +820,309 @@ 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> {
#[track_caller]
pub fn try_map<R: Type, E>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Result<Expr<R>, E>,
) -> Result<Expr<HdlOption<R>>, E> {
Self::try_and_then(expr, |v| Ok(HdlSome(f(v)?)))
}
#[track_caller]
pub fn map<R: Type>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<HdlOption<R>> {
Self::and_then(expr, |v| HdlSome(f(v)))
}
#[hdl]
#[track_caller]
pub fn try_and_then<R: Type, E>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Result<Expr<HdlOption<R>>, E>,
) -> Result<Expr<HdlOption<R>>, E> {
// manually run match steps so we can extract the return type to construct HdlNone
type Wrap<T> = T;
#[hdl]
let mut and_then_out = incomplete_wire();
let mut iter = Self::match_variants(expr, SourceLocation::caller());
let none = iter.next().unwrap();
let some = iter.next().unwrap();
assert!(iter.next().is_none());
let (Wrap::<<Self as Type>::MatchVariant>::HdlSome(value), some_scope) =
Self::match_activate_scope(some)
else {
unreachable!();
};
let value = f(value).inspect_err(|_| {
and_then_out.complete(()); // avoid error
})?;
let and_then_out = and_then_out.complete(value.ty());
connect(and_then_out, value);
drop(some_scope);
let (Wrap::<<Self as Type>::MatchVariant>::HdlNone, none_scope) =
Self::match_activate_scope(none)
else {
unreachable!();
};
connect(and_then_out, and_then_out.ty().HdlNone());
drop(none_scope);
Ok(and_then_out)
}
#[track_caller]
pub fn and_then<R: Type>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Expr<HdlOption<R>>,
) -> Expr<HdlOption<R>> {
match Self::try_and_then(expr, |v| Ok::<_, Infallible>(f(v))) {
Ok(v) => v,
Err(e) => match e {},
}
}
#[hdl]
#[track_caller]
pub fn and<U: Type>(expr: Expr<Self>, opt_b: Expr<HdlOption<U>>) -> Expr<HdlOption<U>> {
#[hdl]
let and_out = wire(opt_b.ty());
connect(and_out, opt_b.ty().HdlNone());
#[hdl]
if let HdlSome(_) = expr {
connect(and_out, opt_b);
}
and_out
}
#[hdl]
#[track_caller]
pub fn try_filter<E>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Result<Expr<Bool>, E>,
) -> Result<Expr<Self>, E> {
#[hdl]
let filtered = wire(expr.ty());
connect(filtered, expr.ty().HdlNone());
let mut f = Some(f);
#[hdl]
if let HdlSome(v) = expr {
#[hdl]
if f.take().unwrap()(v)? {
connect(filtered, HdlSome(v));
}
}
Ok(filtered)
}
#[hdl]
#[track_caller]
pub fn filter(expr: Expr<Self>, f: impl FnOnce(Expr<T>) -> Expr<Bool>) -> Expr<Self> {
match Self::try_filter(expr, |v| Ok::<_, Infallible>(f(v))) {
Ok(v) => v,
Err(e) => match e {},
}
}
#[hdl]
#[track_caller]
pub fn try_inspect<E>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Result<(), E>,
) -> Result<Expr<Self>, E> {
let mut f = Some(f);
#[hdl]
if let HdlSome(v) = expr {
f.take().unwrap()(v)?;
}
Ok(expr)
}
#[hdl]
#[track_caller]
pub fn inspect(expr: Expr<Self>, f: impl FnOnce(Expr<T>)) -> Expr<Self> {
let mut f = Some(f);
#[hdl]
if let HdlSome(v) = expr {
f.take().unwrap()(v);
}
expr
}
#[hdl]
#[track_caller]
pub fn is_none(expr: Expr<Self>) -> Expr<Bool> {
#[hdl]
let is_none_out: Bool = wire();
connect(is_none_out, false);
#[hdl]
if let HdlNone = expr {
connect(is_none_out, true);
}
is_none_out
}
#[hdl]
#[track_caller]
pub fn is_some(expr: Expr<Self>) -> Expr<Bool> {
#[hdl]
let is_some_out: Bool = wire();
connect(is_some_out, false);
#[hdl]
if let HdlSome(_) = expr {
connect(is_some_out, true);
}
is_some_out
}
#[hdl]
#[track_caller]
pub fn map_or<R: Type>(
expr: Expr<Self>,
default: Expr<R>,
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<R> {
#[hdl]
let mapped = wire(default.ty());
let mut f = Some(f);
#[hdl]
match expr {
HdlSome(v) => connect(mapped, f.take().unwrap()(v)),
HdlNone => connect(mapped, default),
}
mapped
}
#[hdl]
#[track_caller]
pub fn map_or_else<R: Type>(
expr: Expr<Self>,
default: impl FnOnce() -> Expr<R>,
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<R> {
#[hdl]
let mut mapped = incomplete_wire();
let mut default = Some(default);
let mut f = Some(f);
let mut retval = None;
#[hdl]
match expr {
HdlSome(v) => {
let v = f.take().unwrap()(v);
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
connect(mapped, v);
}
HdlNone => {
let v = default.take().unwrap()();
let mapped = *retval.get_or_insert_with(|| mapped.complete(v.ty()));
connect(mapped, v);
}
}
retval.unwrap()
}
#[hdl]
#[track_caller]
pub fn or(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
#[hdl]
let or_out = wire(expr.ty());
connect(or_out, opt_b);
#[hdl]
if let HdlSome(_) = expr {
connect(or_out, expr);
}
or_out
}
#[hdl]
#[track_caller]
pub fn or_else(expr: Expr<Self>, f: impl FnOnce() -> Expr<Self>) -> Expr<Self> {
#[hdl]
let or_else_out = wire(expr.ty());
connect(or_else_out, f());
#[hdl]
if let HdlSome(_) = expr {
connect(or_else_out, expr);
}
or_else_out
}
#[hdl]
#[track_caller]
pub fn unwrap_or(expr: Expr<Self>, default: Expr<T>) -> Expr<T> {
#[hdl]
let unwrap_or_else_out = wire(default.ty());
connect(unwrap_or_else_out, default);
#[hdl]
if let HdlSome(v) = expr {
connect(unwrap_or_else_out, v);
}
unwrap_or_else_out
}
#[hdl]
#[track_caller]
pub fn unwrap_or_else(expr: Expr<Self>, f: impl FnOnce() -> Expr<T>) -> Expr<T> {
#[hdl]
let unwrap_or_else_out = wire(expr.ty().HdlSome);
connect(unwrap_or_else_out, f());
#[hdl]
if let HdlSome(v) = expr {
connect(unwrap_or_else_out, v);
}
unwrap_or_else_out
}
#[hdl]
#[track_caller]
pub fn xor(expr: Expr<Self>, opt_b: Expr<Self>) -> Expr<Self> {
#[hdl]
let xor_out = wire(expr.ty());
#[hdl]
if let HdlSome(_) = expr {
#[hdl]
if let HdlNone = opt_b {
connect(xor_out, expr);
} else {
connect(xor_out, expr.ty().HdlNone());
}
} else {
connect(xor_out, opt_b);
}
xor_out
}
#[hdl]
#[track_caller]
pub fn zip<U: Type>(expr: Expr<Self>, other: Expr<HdlOption<U>>) -> Expr<HdlOption<(T, U)>> {
#[hdl]
let zip_out = wire(HdlOption[(expr.ty().HdlSome, other.ty().HdlSome)]);
connect(zip_out, zip_out.ty().HdlNone());
#[hdl]
if let HdlSome(l) = expr {
#[hdl]
if let HdlSome(r) = other {
connect(zip_out, HdlSome((l, r)));
}
}
zip_out
}
}
impl<T: Type> HdlOption<HdlOption<T>> {
#[hdl]
#[track_caller]
pub fn flatten(expr: Expr<Self>) -> Expr<HdlOption<T>> {
#[hdl]
let flattened = wire(expr.ty().HdlSome);
#[hdl]
match expr {
HdlSome(v) => connect(flattened, v),
HdlNone => connect(flattened, expr.ty().HdlSome.HdlNone()),
}
flattened
}
}
impl<T: Type, U: Type> HdlOption<(T, U)> {
#[hdl]
#[track_caller]
pub fn unzip(expr: Expr<Self>) -> Expr<(HdlOption<T>, HdlOption<U>)> {
let (t, u) = expr.ty().HdlSome;
#[hdl]
let unzipped = wire((HdlOption[t], HdlOption[u]));
connect(unzipped, (HdlOption[t].HdlNone(), HdlOption[u].HdlNone()));
#[hdl]
if let HdlSome(v) = expr {
connect(unzipped.0, HdlSome(v.0));
connect(unzipped.1, HdlSome(v.1));
}
unzipped
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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

@ -1,18 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
array::Array,
bundle::{Bundle, BundleField},
expr::Flow,
expr::{Expr, Flow, ToExpr, ValueType, value_category::ValueCategoryExpr},
intern::{Intern, Interned},
memory::{DynPortType, MemPort},
module::{Instance, ModuleIO, TargetName},
reg::Reg,
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
wire::Wire,
};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathBundleField {
pub name: Interned<str>,
}
@ -23,7 +26,7 @@ impl fmt::Display for TargetPathBundleField {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathArrayElement {
pub index: usize,
}
@ -34,7 +37,7 @@ impl fmt::Display for TargetPathArrayElement {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathDynArrayElement {}
impl fmt::Display for TargetPathDynArrayElement {
@ -43,7 +46,7 @@ impl fmt::Display for TargetPathDynArrayElement {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TargetPathElement {
BundleField(TargetPathBundleField),
ArrayElement(TargetPathArrayElement),
@ -125,6 +128,7 @@ macro_rules! impl_target_base {
$(#[$enum_meta:meta])*
$enum_vis:vis enum $TargetBase:ident {
$(
$(#[from = $from:ident])?
#[is = $is_fn:ident]
#[to = $to_fn:ident]
$(#[$variant_meta:meta])*
@ -148,19 +152,19 @@ macro_rules! impl_target_base {
}
}
$(
$($(
impl From<$VariantTy> for $TargetBase {
fn from(value: $VariantTy) -> Self {
fn $from(value: $VariantTy) -> Self {
Self::$Variant(value)
}
}
impl From<$VariantTy> for Target {
fn from(value: $VariantTy) -> Self {
fn $from(value: $VariantTy) -> Self {
$TargetBase::$Variant(value).into()
}
}
)*
)*)?
impl $TargetBase {
$(
@ -191,30 +195,88 @@ macro_rules! impl_target_base {
}
}
}
impl ValueType for $TargetBase {
type Type = CanonicalType;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
match self {
$(Self::$Variant(v) => v.ty().canonical(),)*
}
}
}
impl ToExpr for $TargetBase {
fn to_expr(&self) -> Expr<Self::Type> {
match self {
$(Self::$Variant(v) => Expr::canonical(v.to_expr()),)*
}
}
}
};
}
impl_target_base! {
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum TargetBase {
#[from = from]
#[is = is_module_io]
#[to = module_io]
ModuleIO(ModuleIO<CanonicalType>),
#[from = from]
#[is = is_mem_port]
#[to = mem_port]
MemPort(MemPort<DynPortType>),
#[is = is_reg]
#[to = reg]
Reg(Reg<CanonicalType>),
Reg(Reg<CanonicalType, Reset>),
#[is = is_reg_sync]
#[to = reg_sync]
RegSync(Reg<CanonicalType, SyncReset>),
#[is = is_reg_async]
#[to = reg_async]
RegAsync(Reg<CanonicalType, AsyncReset>),
#[from = from]
#[is = is_wire]
#[to = wire]
Wire(Wire<CanonicalType>),
#[from = from]
#[is = is_instance]
#[to = instance]
Instance(Instance<Bundle>),
}
}
impl<R: ResetType> From<Reg<CanonicalType, R>> for TargetBase {
fn from(value: Reg<CanonicalType, R>) -> Self {
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = Reg<CanonicalType, T>;
type Output<T: ResetType> = TargetBase;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
TargetBase::Reg(input)
}
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
TargetBase::RegSync(input)
}
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
TargetBase::RegAsync(input)
}
}
R::dispatch(value, Dispatch)
}
}
impl<R: ResetType> From<Reg<CanonicalType, R>> for Target {
fn from(value: Reg<CanonicalType, R>) -> Self {
TargetBase::from(value).into()
}
}
impl fmt::Display for TargetBase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.target_name())
@ -227,6 +289,8 @@ impl TargetBase {
TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None),
TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())),
TargetBase::Reg(v) => TargetName(v.scoped_name(), None),
TargetBase::RegSync(v) => TargetName(v.scoped_name(), None),
TargetBase::RegAsync(v) => TargetName(v.scoped_name(), None),
TargetBase::Wire(v) => TargetName(v.scoped_name(), None),
TargetBase::Instance(v) => TargetName(v.scoped_name(), None),
}
@ -236,6 +300,8 @@ impl TargetBase {
TargetBase::ModuleIO(v) => v.ty(),
TargetBase::MemPort(v) => v.ty().canonical(),
TargetBase::Reg(v) => v.ty(),
TargetBase::RegSync(v) => v.ty(),
TargetBase::RegAsync(v) => v.ty(),
TargetBase::Wire(v) => v.ty(),
TargetBase::Instance(v) => v.ty().canonical(),
}
@ -311,7 +377,7 @@ impl TargetChild {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Target {
Base(Interned<TargetBase>),
Child(TargetChild),

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,
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,247 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
int::BoolOrIntType,
intern::{Intern, Interned, Memoize},
prelude::*,
};
use std::sync::OnceLock;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum FormalKind {
Assert,
Assume,
Cover,
}
impl FormalKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Assert => "assert",
Self::Assume => "assume",
Self::Cover => "cover",
}
}
}
#[track_caller]
pub fn formal_stmt_with_enable_and_loc(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
crate::module::add_stmt_formal(crate::module::StmtFormal {
kind,
clk,
pred,
en: en & !formal_reset().cast_to_static::<Bool>(),
text: text.intern(),
source_location,
});
}
#[track_caller]
pub fn formal_stmt_with_enable(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
) {
formal_stmt_with_enable_and_loc(kind, clk, pred, en, text, SourceLocation::caller());
}
#[track_caller]
pub fn formal_stmt_with_loc(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_enable_and_loc(kind, clk, pred, true.to_expr(), text, source_location);
}
#[track_caller]
pub fn formal_stmt(kind: FormalKind, clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
formal_stmt_with_loc(kind, clk, pred, text, SourceLocation::caller());
}
macro_rules! make_formal {
($kind:ident, $formal_stmt_with_enable_and_loc:ident, $formal_stmt_with_enable:ident, $formal_stmt_with_loc:ident, $formal_stmt:ident) => {
#[track_caller]
pub fn $formal_stmt_with_enable_and_loc(
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_enable_and_loc(
FormalKind::$kind,
clk,
pred,
en,
text,
source_location,
);
}
#[track_caller]
pub fn $formal_stmt_with_enable(
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
) {
formal_stmt_with_enable(FormalKind::$kind, clk, pred, en, text);
}
#[track_caller]
pub fn $formal_stmt_with_loc(
clk: Expr<Clock>,
pred: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_loc(FormalKind::$kind, clk, pred, text, source_location);
}
#[track_caller]
pub fn $formal_stmt(clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
formal_stmt(FormalKind::$kind, clk, pred, text);
}
};
}
make_formal!(
Assert,
hdl_assert_with_enable_and_loc,
hdl_assert_with_enable,
hdl_assert_with_loc,
hdl_assert
);
make_formal!(
Assume,
hdl_assume_with_enable_and_loc,
hdl_assume_with_enable,
hdl_assume_with_loc,
hdl_assume
);
make_formal!(
Cover,
hdl_cover_with_enable_and_loc,
hdl_cover_with_enable,
hdl_cover_with_loc,
hdl_cover
);
pub trait MakeFormalExpr: Type {}
impl<T: Type> MakeFormalExpr for T {}
#[hdl]
pub fn formal_global_clock() -> Expr<Clock> {
#[hdl_module(extern)]
fn formal_global_clock() {
#[hdl]
let clk: Clock = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_global_clock.v".intern(),
text: r"module __fayalite_formal_global_clock(output clk);
(* gclk *)
reg clk;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_global_clock");
}
#[hdl]
let formal_global_clock = instance(formal_global_clock());
formal_global_clock.clk
}
#[hdl]
pub fn formal_reset() -> Expr<SyncReset> {
#[hdl_module(extern)]
fn formal_reset() {
#[hdl]
let rst: SyncReset = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_reset.v".intern(),
text: r"module __fayalite_formal_reset(output rst);
assign rst = $initstate;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_reset");
}
static MOD: OnceLock<Interned<Module<formal_reset>>> = OnceLock::new();
#[hdl]
let formal_reset = instance(*MOD.get_or_init(formal_reset));
formal_reset.rst
}
macro_rules! make_any_const_fn {
($ident:ident, $verilog_attribute:literal) => {
#[hdl]
pub fn $ident<T: BoolOrIntType>(ty: T) -> Expr<T> {
#[hdl_module(extern)]
pub(super) fn $ident<T: BoolOrIntType>(ty: T) {
#[hdl]
let out: T = m.output(ty);
let width = ty.width();
let verilog_bitslice = if width == 1 {
String::new()
} else {
format!(" [{}:0]", width - 1)
};
m.annotate_module(BlackBoxInlineAnnotation {
path: Intern::intern_owned(format!(
"fayalite_{}_{width}.v",
stringify!($ident),
)),
text: Intern::intern_owned(format!(
r"module __fayalite_{}_{width}(output{verilog_bitslice} out);
(* {} *)
reg{verilog_bitslice} out;
endmodule
",
stringify!($ident),
$verilog_attribute,
)),
});
m.verilog_name(format!("__fayalite_{}_{width}", stringify!($ident)));
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TheMemoize<T>(T);
impl<T: BoolOrIntType> Memoize for TheMemoize<T> {
type Input = ();
type InputOwned = ();
type Output = Option<Interned<Module<$ident<T>>>>;
fn inner(self, _input: &Self::Input) -> Self::Output {
if self.0.width() == 0 {
None
} else {
Some($ident(self.0))
}
}
}
let Some(module) = TheMemoize(ty).get_owned(()) else {
return 0_hdl_u0.cast_bits_to(ty);
};
#[hdl]
let $ident = instance(module);
$ident.out
}
};
}
make_any_const_fn!(any_const, "anyconst");
make_any_const_fn!(any_seq, "anyseq");
make_any_const_fn!(all_const, "allconst");
make_any_const_fn!(all_seq, "allseq");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,807 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder},
expr::{
CastBitsTo, CastTo, CastToBits, CastToImpl, Expr, HdlPartialEq, HdlPartialEqImpl,
HdlPartialOrd, HdlPartialOrdImpl,
},
int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType, UIntValue},
intern::{Intern, InternSlice, Interned},
phantom_const::PhantomConst,
sim::value::{SimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
StaticType, Type, TypeProperties, impl_match_variant_as_self,
},
};
use bitvec::{order::Lsb0, view::BitView};
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{Error, Visitor, value::UsizeDeserializer},
};
use std::{borrow::Cow, fmt, marker::PhantomData, ops::Index};
const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"];
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct UIntInRangeMaskType {
value: Bool,
range: PhantomConstRangeMaskType,
}
impl Type for UIntInRangeMaskType {
type BaseType = Bundle;
type MaskType = Self;
type SimValue = bool;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
*self
}
fn canonical(&self) -> CanonicalType {
CanonicalType::Bundle(Bundle::new(self.fields()))
}
fn from_canonical(canonical_type: CanonicalType) -> Self {
let fields = Bundle::from_canonical(canonical_type).fields();
let [
BundleField {
name: value_name,
flipped: false,
ty: value,
},
BundleField {
name: range_name,
flipped: false,
ty: range,
},
] = *fields
else {
panic!("expected UIntInRangeMaskType");
};
assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES);
let value = Bool::from_canonical(value);
let range = PhantomConstRangeMaskType::from_canonical(range);
Self { value, range }
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
Bool.sim_value_from_opaque(opaque)
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
Bool.sim_value_clone_from_opaque(value, opaque);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
Bool.sim_value_to_opaque(value, writer)
}
}
impl BundleType for UIntInRangeMaskType {
type Builder = NoBuilder;
fn fields(&self) -> Interned<[BundleField]> {
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
let Self { value, range } = self;
[
BundleField {
name: value_name.intern(),
flipped: false,
ty: value.canonical(),
},
BundleField {
name: range_name.intern(),
flipped: false,
ty: range.canonical(),
},
]
.intern_slice()
}
}
impl StaticType for UIntInRangeMaskType {
const TYPE: Self = Self {
value: Bool,
range: PhantomConstRangeMaskType::TYPE,
};
const MASK_TYPE: Self::MaskType = Self::TYPE;
const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new()
.field(false, Bool::TYPE_PROPERTIES)
.field(false, PhantomConstRangeMaskType::TYPE_PROPERTIES)
.finish();
const MASK_TYPE_PROPERTIES: TypeProperties = Self::TYPE_PROPERTIES;
}
impl ToSimValueWithType<UIntInRangeMaskType> for bool {
fn to_sim_value_with_type(&self, ty: UIntInRangeMaskType) -> SimValue<UIntInRangeMaskType> {
SimValue::from_value(ty, *self)
}
}
impl CastToImpl<Bool> for UIntInRangeMaskType {
type ValueOutput = bool;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
_to_type: Bool,
) -> Self::ValueOutput {
*value
}
fn cast_expr_to(value: Expr<Self>, to_type: Bool) -> Expr<Bool> {
value.cast_to_bits().cast_to(to_type)
}
}
impl CastToImpl<UIntInRangeMaskType> for Bool {
type ValueOutput = SimValue<UIntInRangeMaskType>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: UIntInRangeMaskType,
) -> Self::ValueOutput {
SimValue::from_value(to_type, *value)
}
fn cast_expr_to(value: Expr<Self>, to_type: UIntInRangeMaskType) -> Expr<UIntInRangeMaskType> {
value.cast_to_static::<UInt<1>>().cast_bits_to(to_type)
}
}
impl HdlPartialEqImpl<Self> for UIntInRangeMaskType {
#[track_caller]
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: Self,
rhs_value: Cow<'_, Self::SimValue>,
) -> bool {
*lhs_value == *rhs_value
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
}
}
type PhantomConstRangeMaskType = <PhantomConst<SerdeRange<DynSize, DynSize>> as Type>::MaskType;
#[derive(Default, Copy, Clone, Debug)]
struct RangeParseError;
macro_rules! define_uint_in_range_type {
(
$UIntInRange:ident,
$UIntInRangeType:ident,
$UIntInRangeTypeWithoutGenerics:ident,
$UIntInRangeTypeWithStart:ident,
$SerdeRange:ident,
$range_operator_str:literal,
|$uint_range_usize_start:ident, $uint_range_usize_end:ident| $uint_range_usize:expr,
) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct $SerdeRange<Start: Size, End: Size> {
start: Start::SizeType,
end: End::SizeType,
}
impl<Start: KnownSize, End: KnownSize> Default for $SerdeRange<Start, End> {
fn default() -> Self {
Self {
start: Start::SIZE,
end: End::SIZE,
}
}
}
impl std::str::FromStr for $SerdeRange<DynSize, DynSize> {
type Err = RangeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((start, end)) = s.split_once($range_operator_str) else {
return Err(RangeParseError);
};
if start.is_empty()
|| start.bytes().any(|b| !b.is_ascii_digit())
|| end.is_empty()
|| end.bytes().any(|b| !b.is_ascii_digit())
{
return Err(RangeParseError);
}
let start = start.parse().map_err(|_| RangeParseError)?;
let end = end.parse().map_err(|_| RangeParseError)?;
let retval = Self { start, end };
if retval.is_empty() {
Err(RangeParseError)
} else {
Ok(retval)
}
}
}
impl<Start: Size, End: Size> fmt::Display for $SerdeRange<Start, End> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { start, end } = *self;
write!(
f,
"{}{}{}",
Start::as_usize(start),
$range_operator_str,
End::as_usize(end),
)
}
}
impl<Start: Size, End: Size> Serialize for $SerdeRange<Start, End> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de, Start: Size, End: Size> Deserialize<'de> for $SerdeRange<Start, End> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SerdeRangeVisitor<Start: Size, End: Size>(PhantomData<(Start, End)>);
impl<'de, Start: Size, End: Size> Visitor<'de> for SerdeRangeVisitor<Start, End> {
type Value = $SerdeRange<Start, End>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a string with format \"")?;
if let Some(start) = Start::KNOWN_VALUE {
write!(f, "{start}")?;
} else {
f.write_str("<int>")?;
};
f.write_str($range_operator_str)?;
if let Some(end) = End::KNOWN_VALUE {
write!(f, "{end}")?;
} else {
f.write_str("<int>")?;
};
f.write_str("\" that is a non-empty range")
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
let $SerdeRange::<DynSize, DynSize> { start, end } =
v.parse().map_err(|_| {
Error::invalid_value(serde::de::Unexpected::Str(v), &self)
})?;
let start =
Start::SizeType::deserialize(UsizeDeserializer::<E>::new(start))?;
let end = End::SizeType::deserialize(UsizeDeserializer::<E>::new(end))?;
Ok($SerdeRange { start, end })
}
fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
match std::str::from_utf8(v) {
Ok(v) => self.visit_str(v),
Err(_) => {
Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self))
}
}
}
}
deserializer.deserialize_str(SerdeRangeVisitor(PhantomData))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct $UIntInRangeType<Start: Size, End: Size> {
value: UInt,
range: PhantomConst<$SerdeRange<Start, End>>,
}
impl<Start: Size, End: Size> $UIntInRangeType<Start, End> {
fn from_phantom_const_range(range: PhantomConst<$SerdeRange<Start, End>>) -> Self {
let $SerdeRange { start, end } = *range.get();
let $uint_range_usize_start = Start::as_usize(start);
let $uint_range_usize_end = End::as_usize(end);
Self {
value: $uint_range_usize,
range,
}
}
pub fn new(start: Start::SizeType, end: End::SizeType) -> Self {
Self::from_phantom_const_range(PhantomConst::new_sized($SerdeRange { start, end }))
}
pub fn bit_width(self) -> usize {
self.value.width()
}
pub fn start(self) -> Start::SizeType {
self.range.get().start
}
pub fn end(self) -> End::SizeType {
self.range.get().end
}
}
impl<Start: Size, End: Size> fmt::Debug for $UIntInRangeType<Start, End> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { value, range } = self;
let $SerdeRange { start, end } = *range.get();
f.debug_struct(&format!(
"{}<{}, {}>",
stringify!($UIntInRange),
Start::as_usize(start),
End::as_usize(end),
))
.field("value", value)
.finish_non_exhaustive()
}
}
impl<Start: Size, End: Size> Type for $UIntInRangeType<Start, End> {
type BaseType = Bundle;
type MaskType = UIntInRangeMaskType;
type SimValue = usize;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntInRangeMaskType::TYPE
}
fn canonical(&self) -> CanonicalType {
CanonicalType::Bundle(Bundle::new(self.fields()))
}
fn from_canonical(canonical_type: CanonicalType) -> Self {
let fields = Bundle::from_canonical(canonical_type).fields();
let [
BundleField {
name: value_name,
flipped: false,
ty: value,
},
BundleField {
name: range_name,
flipped: false,
ty: range,
},
] = *fields
else {
panic!("expected {}", stringify!($UIntInRange));
};
assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES);
let value = UInt::from_canonical(value);
let range = PhantomConst::<$SerdeRange<Start, End>>::from_canonical(range);
let retval = Self::from_phantom_const_range(range);
assert_eq!(retval, Self { value, range });
retval
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(opaque.size(), self.value.type_properties().size());
let mut retval = 0usize;
retval.view_bits_mut::<Lsb0>()[..opaque.bit_width()]
.clone_from_bitslice(opaque.bits());
retval
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
*value = self.sim_value_from_opaque(opaque);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
&value.view_bits::<Lsb0>()[..self.value.width()],
))
}
}
impl<Start: Size, End: Size> BundleType for $UIntInRangeType<Start, End> {
type Builder = NoBuilder;
fn fields(&self) -> Interned<[BundleField]> {
let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES;
let Self { value, range } = self;
[
BundleField {
name: value_name.intern(),
flipped: false,
ty: value.canonical(),
},
BundleField {
name: range_name.intern(),
flipped: false,
ty: range.canonical(),
},
]
.intern_slice()
}
}
impl<Start: KnownSize, End: KnownSize> Default for $UIntInRangeType<Start, End> {
fn default() -> Self {
Self::TYPE
}
}
impl<Start: KnownSize, End: KnownSize> StaticType for $UIntInRangeType<Start, End> {
const TYPE: Self = {
let $uint_range_usize_start = Start::VALUE;
let $uint_range_usize_end = End::VALUE;
Self {
value: $uint_range_usize,
range: PhantomConst::<$SerdeRange<Start, End>>::TYPE,
}
};
const MASK_TYPE: Self::MaskType = UIntInRangeMaskType::TYPE;
const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new()
.field(false, Self::TYPE.value.type_properties_dyn())
.field(
false,
PhantomConst::<$SerdeRange<Start, End>>::TYPE_PROPERTIES,
)
.finish();
const MASK_TYPE_PROPERTIES: TypeProperties = UIntInRangeMaskType::TYPE_PROPERTIES;
}
impl<Start: Size, End: Size> ToSimValueWithType<$UIntInRangeType<Start, End>> for usize {
fn to_sim_value_with_type(
&self,
ty: $UIntInRangeType<Start, End>,
) -> SimValue<$UIntInRangeType<Start, End>> {
SimValue::from_value(ty, *self)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct $UIntInRangeTypeWithoutGenerics;
#[allow(non_upper_case_globals)]
pub const $UIntInRangeType: $UIntInRangeTypeWithoutGenerics =
$UIntInRangeTypeWithoutGenerics;
impl<StartSize: SizeType> Index<StartSize> for $UIntInRangeTypeWithoutGenerics {
type Output = $UIntInRangeTypeWithStart<StartSize::Size>;
fn index(&self, start: StartSize) -> &Self::Output {
Interned::into_inner($UIntInRangeTypeWithStart(start).intern_sized())
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct $UIntInRangeTypeWithStart<Start: Size>(Start::SizeType);
impl<Start: Size, EndSize: SizeType<Size = End>, End: Size<SizeType = EndSize>>
Index<EndSize> for $UIntInRangeTypeWithStart<Start>
{
type Output = $UIntInRangeType<Start, End>;
fn index(&self, end: EndSize) -> &Self::Output {
Interned::into_inner($UIntInRangeType::new(self.0, end).intern_sized())
}
}
impl<Start: Size, End: Size, Width: Size> CastToImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
type ValueOutput = UIntValue<Width>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: UIntType<Width>,
) -> Self::ValueOutput {
value.cast_to(to_type)
}
fn cast_expr_to(value: Expr<Self>, to_type: UIntType<Width>) -> Expr<UIntType<Width>> {
value.cast_to_bits().cast_to(to_type)
}
}
impl<Start: Size, End: Size, Width: Size> CastToImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
type ValueOutput = SimValue<$UIntInRangeType<Start, End>>;
fn cast_value_to(
_this: Self,
value: Cow<'_, Self::SimValue>,
to_type: $UIntInRangeType<Start, End>,
) -> Self::ValueOutput {
value.cast_to(to_type.value).cast_bits_to(to_type)
}
fn cast_expr_to(
value: Expr<Self>,
to_type: $UIntInRangeType<Start, End>,
) -> Expr<$UIntInRangeType<Start, End>> {
value.cast_to(to_type.value).cast_bits_to(to_type)
}
}
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
HdlPartialEqImpl<$UIntInRangeType<RhsStart, RhsEnd>>
for $UIntInRangeType<LhsStart, LhsEnd>
{
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
*lhs_value == *rhs_value
}
fn cmp_expr_eq(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits())
}
fn cmp_expr_ne(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits())
}
}
impl<LhsStart: Size, LhsEnd: Size, RhsStart: Size, RhsEnd: Size>
HdlPartialOrdImpl<$UIntInRangeType<RhsStart, RhsEnd>>
for $UIntInRangeType<LhsStart, LhsEnd>
{
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::lt(&*lhs_value, &*rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::le(&*lhs_value, &*rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::gt(&*lhs_value, &*rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<RhsStart, RhsEnd>,
rhs_value: Cow<'_, <$UIntInRangeType<RhsStart, RhsEnd> as Type>::SimValue>,
) -> bool {
PartialOrd::ge(&*lhs_value, &*rhs_value)
}
fn cmp_expr_lt(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits())
}
fn cmp_expr_le(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_le(rhs.cast_to_bits())
}
fn cmp_expr_gt(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits())
}
fn cmp_expr_ge(
lhs: Expr<Self>,
rhs: Expr<$UIntInRangeType<RhsStart, RhsEnd>>,
) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ge(rhs.cast_to_bits())
}
}
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialEq::cmp_eq(&*lhs_value, &*rhs_value)
}
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_eq(rhs)
}
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ne(rhs)
}
}
impl<Start: Size, End: Size, Width: Size> HdlPartialEqImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
fn cmp_value_eq(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialEq::cmp_eq(&*lhs_value, *rhs_value)
}
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_eq(rhs.cast_to_bits())
}
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_ne(rhs.cast_to_bits())
}
}
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_lt(&*lhs_value, &*rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_le(&*lhs_value, &*rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_gt(&*lhs_value, &*rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: UIntType<Width>,
rhs_value: Cow<'_, UIntValue<Width>>,
) -> bool {
HdlPartialOrd::cmp_ge(&*lhs_value, &*rhs_value)
}
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_lt(rhs)
}
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_le(rhs)
}
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_gt(rhs)
}
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<UIntType<Width>>) -> Expr<Bool> {
lhs.cast_to_bits().cmp_ge(rhs)
}
}
impl<Start: Size, End: Size, Width: Size> HdlPartialOrdImpl<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
fn cmp_value_lt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_lt(&*lhs_value, *rhs_value)
}
fn cmp_value_le(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_le(&*lhs_value, *rhs_value)
}
fn cmp_value_gt(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_gt(&*lhs_value, *rhs_value)
}
fn cmp_value_ge(
_lhs: Self,
lhs_value: Cow<'_, Self::SimValue>,
_rhs: $UIntInRangeType<Start, End>,
rhs_value: Cow<'_, <$UIntInRangeType<Start, End> as Type>::SimValue>,
) -> bool {
HdlPartialOrd::cmp_ge(&*lhs_value, *rhs_value)
}
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_lt(rhs.cast_to_bits())
}
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_le(rhs.cast_to_bits())
}
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_gt(rhs.cast_to_bits())
}
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<$UIntInRangeType<Start, End>>) -> Expr<Bool> {
lhs.cmp_ge(rhs.cast_to_bits())
}
}
};
}
define_uint_in_range_type! {
UIntInRange,
UIntInRangeType,
UIntInRangeTypeWithoutGenerics,
UIntInRangeTypeWithStart,
SerdeRange,
"..",
|start, end| UInt::range_usize(start..end),
}
define_uint_in_range_type! {
UIntInRangeInclusive,
UIntInRangeInclusiveType,
UIntInRangeInclusiveTypeWithoutGenerics,
UIntInRangeInclusiveTypeWithStart,
SerdeRangeInclusive,
"..=",
|start, end| UInt::range_inclusive_usize(start..=end),
}
impl SerdeRange<DynSize, DynSize> {
fn is_empty(self) -> bool {
self.start >= self.end
}
}
impl SerdeRangeInclusive<DynSize, DynSize> {
fn is_empty(self) -> bool {
self.start > self.end
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,117 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
intern::{Interned, type_map::TypeIdMap},
util::DefaultBuildHasher,
};
use bitvec::slice::BitSlice;
use hashbrown::HashTable;
use std::{
borrow::Cow,
hash::{BuildHasher, Hash},
sync::RwLock,
};
struct InternerShard<T: ?Sized + 'static + Send + Sync> {
table: HashTable<&'static T>,
}
const LOG2_SHARD_COUNT: u32 = 6;
fn shard_index_from_hash(hash: u64) -> usize {
// number of bits used for hashbrown's Tag
const HASH_BROWN_TAG_BITS: u32 = 7;
// try to extract bits of the hash that hashbrown isn't using,
// while accounting for some hash functions only returning `usize` bits.
const SHARD_INDEX_START: u32 = usize::BITS
.saturating_sub(HASH_BROWN_TAG_BITS)
.saturating_sub(LOG2_SHARD_COUNT);
let mut shard_index = hash >> SHARD_INDEX_START;
shard_index %= 1 << LOG2_SHARD_COUNT;
shard_index as usize
}
pub(crate) struct Interner<T: ?Sized + 'static + Send + Sync> {
shards: [RwLock<InternerShard<T>>; 1 << LOG2_SHARD_COUNT],
hasher: DefaultBuildHasher,
}
impl<T: ?Sized + 'static + Send + Sync> Interner<T> {
pub(crate) fn get() -> &'static Interner<T> {
static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new();
thread_local! {
static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() };
}
TYPE_ID_MAP_CACHE.with(|cache| {
cache.get_or_insert_with(|| {
TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default()))
})
})
}
}
impl<T: ?Sized + 'static + Send + Sync> Default for Interner<T> {
fn default() -> Self {
Self {
shards: [const {
RwLock::new(InternerShard {
table: HashTable::new(),
})
}; _],
hasher: Default::default(),
}
}
}
impl<T: ?Sized + 'static + Send + Sync + Hash + Eq + ToOwned> Interner<T> {
fn intern<F: FnOnce(Cow<'_, T>) -> &'static T>(
&self,
alloc: F,
value: Cow<'_, T>,
) -> Interned<T> {
let hash = self.hasher.hash_one(&*value);
let shard_index = shard_index_from_hash(hash);
let shard = &self.shards[shard_index];
let shard_read = shard.read().unwrap();
let Some(&inner) = shard_read.table.find(hash, |k| **k == *value) else {
drop(shard_read);
return self.intern_cold(alloc, value, hash, shard);
};
Interned { inner }
}
#[cold]
fn intern_cold<F: FnOnce(Cow<'_, T>) -> &'static T>(
&self,
alloc: F,
value: Cow<'_, T>,
hash: u64,
shard: &RwLock<InternerShard<T>>,
) -> Interned<T> {
let mut shard = shard.write().unwrap();
let inner = *shard
.table
.entry(hash, |k| **k == *value, |k| self.hasher.hash_one(&**k))
.or_insert_with(|| alloc(value))
.get();
Interned { inner }
}
}
impl<T: Clone + 'static + Send + Sync + Hash + Eq> Interner<T> {
pub(crate) fn intern_sized(&self, value: Cow<'_, T>) -> Interned<T> {
self.intern(|value| Box::leak(Box::new(value.into_owned())), value)
}
}
impl<T: Clone + 'static + Send + Sync + Hash + Eq> Interner<[T]> {
pub(crate) fn intern_slice(&self, value: Cow<'_, [T]>) -> Interned<[T]> {
self.intern(|value| value.into_owned().leak(), value)
}
}
impl Interner<BitSlice> {
pub(crate) fn intern_bit_slice(&self, value: Cow<'_, BitSlice>) -> Interned<BitSlice> {
self.intern(|value| value.into_owned().leak(), value)
}
}

View file

@ -1,14 +1,12 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use hashbrown::HashMap;
use std::{
any::{Any, TypeId},
hash::{BuildHasher, Hasher},
ptr::NonNull,
sync::RwLock,
};
struct TypeIdHasher(u64);
pub(crate) struct TypeIdHasher(u64);
// assumes TypeId has at least 64 bits that is a good hash
impl Hasher for TypeIdHasher {
@ -65,7 +63,7 @@ impl Hasher for TypeIdHasher {
}
}
struct TypeIdBuildHasher;
pub(crate) struct TypeIdBuildHasher;
impl BuildHasher for TypeIdBuildHasher {
type Hasher = TypeIdHasher;
@ -75,59 +73,39 @@ impl BuildHasher for TypeIdBuildHasher {
}
}
struct Value(NonNull<dyn Any + Send + Sync>);
impl Value {
unsafe fn get_transmute_lifetime<'b>(&self) -> &'b (dyn Any + Send + Sync) {
unsafe { &*self.0.as_ptr() }
}
fn new(v: Box<dyn Any + Send + Sync>) -> Self {
unsafe { Self(NonNull::new_unchecked(Box::into_raw(v))) }
}
}
unsafe impl Send for Value {}
unsafe impl Sync for Value {}
impl Drop for Value {
fn drop(&mut self) {
unsafe { std::ptr::drop_in_place(self.0.as_ptr()) }
}
}
pub struct TypeIdMap(RwLock<HashMap<TypeId, Value, TypeIdBuildHasher>>);
pub(crate) struct TypeIdMap(
RwLock<hashbrown::HashMap<TypeId, &'static (dyn Any + Send + Sync), TypeIdBuildHasher>>,
);
impl TypeIdMap {
pub const fn new() -> Self {
Self(RwLock::new(HashMap::with_hasher(TypeIdBuildHasher)))
pub(crate) const fn new() -> Self {
Self(RwLock::new(hashbrown::HashMap::with_hasher(
TypeIdBuildHasher,
)))
}
#[cold]
unsafe fn insert_slow(
fn insert_slow(
&self,
type_id: TypeId,
make: fn() -> Box<dyn Any + Sync + Send>,
) -> &(dyn Any + Sync + Send) {
let value = Value::new(make());
make: impl FnOnce() -> &'static (dyn Any + Sync + Send),
) -> &'static (dyn Any + Sync + Send) {
let value = make();
let mut write_guard = self.0.write().unwrap();
unsafe {
write_guard
.entry(type_id)
.or_insert(value)
.get_transmute_lifetime()
}
*write_guard.entry(type_id).or_insert(value)
}
pub fn get_or_insert_default<T: Sized + Any + Send + Sync + Default>(&self) -> &T {
pub(crate) fn get_or_insert_with<T: Sized + Any + Send + Sync>(
&self,
make: impl FnOnce() -> &'static T,
) -> &'static T {
let type_id = TypeId::of::<T>();
let read_guard = self.0.read().unwrap();
let retval = read_guard
.get(&type_id)
.map(|v| unsafe { Value::get_transmute_lifetime(v) });
let retval = read_guard.get(&type_id).map(|v| *v);
drop(read_guard);
let retval = match retval {
Some(retval) => retval,
None => unsafe { self.insert_slow(type_id, move || Box::new(T::default())) },
None => self.insert_slow(type_id, move || make()),
};
unsafe { &*(retval as *const dyn Any as *const T) }
retval.downcast_ref().expect("known to have correct TypeId")
}
}

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;
@ -11,6 +23,59 @@ extern crate self as fayalite;
#[doc(hidden)]
pub use std as __std;
#[doc(hidden)]
#[macro_export]
macro_rules! __cfg_expansion_helper {
(
[
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
]
[
$cfg:ident($($expr:tt)*),
$($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)*
]
// pass as tt so we get right span for attribute
$after_evaluation_attr:tt $after_evaluation_body:tt
) => {
#[$cfg($($expr)*)]
$crate::__cfg_expansion_helper! {
[
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
$cfg($($expr)*) = true,
]
[
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
]
$after_evaluation_attr $after_evaluation_body
}
#[$cfg(not($($expr)*))]
$crate::__cfg_expansion_helper! {
[
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
$cfg($($expr)*) = false,
]
[
$($unevaluated_cfgs($($unevaluated_exprs)*),)*
]
$after_evaluation_attr $after_evaluation_body
}
};
(
[
$($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)*
]
[]
// don't use #[...] so we get right span for `#` and `[]` of attribute
{$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*}
) => {
$($after_evaluation_attr)*
#[__evaluated_cfgs([
$($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)*
])]
$($after_evaluation_body)*
};
}
#[doc(inline)]
/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates
/// a [`Module`][`::fayalite::module::Module`] when called.
@ -21,8 +86,139 @@ pub use std as __std;
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;
/// struct used as a placeholder when applying defaults
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct __;
@ -30,24 +226,28 @@ pub struct __;
#[cfg(feature = "unstable-doc")]
pub mod _docs;
// FIXME: finish
pub mod annotations;
pub mod array;
pub mod build;
pub mod bundle;
pub mod cli;
pub mod clock;
pub mod enum_;
pub mod expr;
pub mod firrtl;
pub mod formal;
pub mod int;
pub mod intern;
pub mod memory;
pub mod module;
pub mod phantom_const;
pub mod platform;
pub mod prelude;
pub mod reg;
pub mod reset;
pub mod sim;
pub mod source_location;
pub mod testing;
pub mod ty;
pub mod util;
//pub mod valueless;
pub mod prelude;
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},
expr::{
Expr, Flow, ToExpr, ToLiteralBits, ValueType, ops::BundleLiteral, repeat,
value_category::ValueCategoryExpr,
},
hdl,
int::{Bool, DynSize, Size, UInt, UIntType},
intern::{Intern, Interned},
@ -22,7 +25,7 @@ use std::{
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
num::NonZeroU32,
num::NonZeroUsize,
rc::Rc,
};
@ -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
}
@ -470,7 +479,7 @@ pub enum ReadUnderWrite {
Undefined,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct MemImpl<Element: Type, Len: Size, P> {
scoped_name: ScopedNameId,
source_location: SourceLocation,
@ -478,7 +487,7 @@ struct MemImpl<Element: Type, Len: Size, P> {
initial_value: Option<Interned<BitSlice>>,
ports: P,
read_latency: usize,
write_latency: NonZeroU32,
write_latency: NonZeroUsize,
read_under_write: ReadUnderWrite,
port_annotations: Interned<[TargetedAnnotation]>,
mem_annotations: Interned<[Annotation]>,
@ -519,7 +528,12 @@ impl<Element: Type, Len: Size> fmt::Debug for Mem<Element, Len> {
f.debug_struct("Mem")
.field("name", scoped_name)
.field("array_type", array_type)
.field("initial_value", initial_value)
.field(
"initial_value",
&initial_value.as_ref().map(|initial_value| {
DebugMemoryData::from_bit_slice(*array_type, initial_value)
}),
)
.field("read_latency", read_latency)
.field("write_latency", write_latency)
.field("read_under_write", read_under_write)
@ -562,7 +576,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
initial_value: Option<Interned<BitSlice>>,
ports: Interned<[MemPort<DynPortType>]>,
read_latency: usize,
write_latency: NonZeroU32,
write_latency: NonZeroUsize,
read_under_write: ReadUnderWrite,
port_annotations: Interned<[TargetedAnnotation]>,
mem_annotations: Interned<[Annotation]>,
@ -634,7 +648,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
self.0.source_location
}
pub fn array_type(self) -> ArrayType<Element, Len> {
self.0.array_type.clone()
self.0.array_type
}
pub fn initial_value(self) -> Option<Interned<BitSlice>> {
self.0.initial_value
@ -645,7 +659,7 @@ impl<Element: Type, Len: Size> Mem<Element, Len> {
pub fn read_latency(self) -> usize {
self.0.read_latency
}
pub fn write_latency(self) -> NonZeroU32 {
pub fn write_latency(self) -> NonZeroUsize {
self.0.write_latency
}
pub fn read_under_write(self) -> ReadUnderWrite {
@ -707,7 +721,7 @@ pub(crate) struct MemBuilderTarget {
pub(crate) initial_value: Option<Interned<BitSlice>>,
pub(crate) ports: Vec<MemPort<DynPortType>>,
pub(crate) read_latency: usize,
pub(crate) write_latency: NonZeroU32,
pub(crate) write_latency: NonZeroUsize,
pub(crate) read_under_write: ReadUnderWrite,
pub(crate) port_annotations: Vec<TargetedAnnotation>,
pub(crate) mem_annotations: Vec<Annotation>,
@ -825,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()),
@ -867,7 +881,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
initial_value: None,
ports: vec![],
read_latency: 0,
write_latency: NonZeroU32::new(1).unwrap(),
write_latency: NonZeroUsize::new(1).unwrap(),
read_under_write: ReadUnderWrite::Old,
port_annotations: vec![],
mem_annotations: vec![],
@ -987,7 +1001,7 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
#[allow(clippy::result_unit_err)]
pub fn get_array_type(&self) -> Result<ArrayType<Element, Len>, ()> {
Ok(ArrayType::new(
self.mem_element_type.clone(),
self.mem_element_type,
Len::from_usize(self.get_depth()?),
))
}
@ -1006,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>) {
@ -1030,10 +1044,10 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
pub fn read_latency(&mut self, read_latency: usize) {
self.target.borrow_mut().read_latency = read_latency;
}
pub fn get_write_latency(&self) -> NonZeroU32 {
pub fn get_write_latency(&self) -> NonZeroUsize {
self.target.borrow().write_latency
}
pub fn write_latency(&mut self, write_latency: NonZeroU32) {
pub fn write_latency(&mut self, write_latency: NonZeroUsize) {
self.target.borrow_mut().write_latency = write_latency;
}
pub fn get_read_under_write(&self) -> ReadUnderWrite {
@ -1050,3 +1064,92 @@ impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
.extend(annotations.into_annotations());
}
}
pub fn splat_mask<T: Type>(ty: T, value: Expr<Bool>) -> Expr<AsMask<T>> {
let canonical_ty = ty.canonical();
match canonical_ty {
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_)
| CanonicalType::Enum(_)
| CanonicalType::DynSimOnly(_) => Expr::from_canonical(Expr::canonical(value)),
CanonicalType::Array(array) => Expr::from_canonical(Expr::canonical(repeat(
splat_mask(array.element(), value),
array.len(),
))),
CanonicalType::Bundle(bundle) => Expr::from_canonical(Expr::canonical(
BundleLiteral::new(
bundle.mask_type(),
bundle
.fields()
.iter()
.map(|field| splat_mask(field.ty, value))
.collect(),
)
.to_expr(),
)),
CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())),
}
}
pub trait DebugMemoryDataGetElement {
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice;
}
impl<'a, F: ?Sized + Fn(usize, Array) -> &'a BitSlice> DebugMemoryDataGetElement for &'a F {
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice {
self(element_index, array_type)
}
}
#[derive(Clone)]
pub struct DebugMemoryData<GetElement: DebugMemoryDataGetElement> {
pub array_type: Array,
pub get_element: GetElement,
}
impl DebugMemoryDataGetElement for &'_ BitSlice {
fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice {
assert!(element_index < array_type.len());
let stride = array_type.element().bit_width();
let start = element_index
.checked_mul(stride)
.expect("memory is too big");
let end = start.checked_add(stride).expect("memory is too big");
&self[start..end]
}
}
impl<'a> DebugMemoryData<&'a BitSlice> {
pub fn from_bit_slice<T: Type, Depth: Size>(
array_type: ArrayType<T, Depth>,
bit_slice: &'a BitSlice,
) -> Self {
let array_type = array_type.as_dyn_array();
assert_eq!(bit_slice.len(), array_type.type_properties().bit_width);
Self {
array_type,
get_element: bit_slice,
}
}
}
impl<GetElement: DebugMemoryDataGetElement> fmt::Debug for DebugMemoryData<GetElement> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.array_type.len() == 0 {
return f.write_str("[]");
}
writeln!(f, "[\n // len = {:#x}", self.array_type.len())?;
for element_index in 0..self.array_type.len() {
let element = crate::util::BitSliceWriteWithBase(
self.get_element.get_element(element_index, self.array_type),
);
writeln!(f, " [{element_index:#x}]: {element:#x},")?;
}
f.write_str("]")
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod deduce_resets;
pub mod simplify_enums;
pub mod simplify_memories;
pub mod visit;

File diff suppressed because it is too large Load diff

View file

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

View file

@ -4,21 +4,20 @@ 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},
module::{
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtWire,
transform::visit::{Fold, Folder},
Block, Module, NameId, NameIdGen, ScopedNameId, Stmt, StmtConnect, StmtWire,
},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::MakeMutSlice,
util::{HashMap, MakeMutSlice},
wire::Wire,
};
use bitvec::{slice::BitSlice, vec::BitVec};
use hashbrown::HashMap;
use std::{
convert::Infallible,
fmt::Write,
@ -62,6 +61,7 @@ enum MemSplit {
Bundle {
fields: Rc<[MemSplit]>,
},
PhantomConst,
Single {
output_mem: Option<Mem>,
element_type: SingleType,
@ -76,6 +76,7 @@ impl MemSplit {
fn mark_changed_element_type(self) -> Self {
match self {
MemSplit::Bundle { fields: _ } => self,
MemSplit::PhantomConst => self,
MemSplit::Single {
output_mem,
element_type,
@ -97,6 +98,7 @@ impl MemSplit {
.map(|field| Self::new(field.ty).mark_changed_element_type())
.collect(),
},
CanonicalType::PhantomConst(_) => MemSplit::PhantomConst,
CanonicalType::Array(ty) => {
let element = MemSplit::new(ty.element());
if let Self::Single {
@ -192,6 +194,7 @@ impl MemSplit {
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"),
}
}
}
@ -321,6 +324,9 @@ impl SplitMemState<'_, '_> {
Expr::field(Expr::<Bundle>::from_canonical(e), &field.name)
},
|initial_value_element| {
let Some(field_offset) = field_offset.only_bit_width() else {
todo!("memory containing sim-only values");
};
&initial_value_element[field_offset..][..field_ty_bit_width]
},
);
@ -339,6 +345,7 @@ impl SplitMemState<'_, '_> {
self.split_state_stack.pop();
}
}
MemSplit::PhantomConst => {}
MemSplit::Single {
output_mem,
element_type: single_type,
@ -417,7 +424,6 @@ impl SplitMemState<'_, '_> {
struct ModuleState {
output_module: Option<Interned<Module<Bundle>>>,
name_id_gen: NameIdGen,
memories: HashMap<ScopedNameId, MemState>,
}
@ -524,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 =
@ -539,7 +545,12 @@ impl ModuleState {
};
loop {
match input_element_type {
CanonicalType::Bundle(_) => unreachable!("bundle types are always split"),
CanonicalType::Bundle(_) => {
unreachable!("bundle types are always split")
}
CanonicalType::PhantomConst(_) => {
unreachable!("PhantomConst are always removed")
}
CanonicalType::Enum(_)
if input_array_types
.first()
@ -569,7 +580,7 @@ impl ModuleState {
port_wmask.map(Expr::from_canonical),
connect_read_enum,
connect_write_enum,
connect_write_enum,
connect_write,
),
CanonicalType::Array(array_type) => {
input_array_types.push(array_type);
@ -613,6 +624,7 @@ impl ModuleState {
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
CanonicalType::DynSimOnly(_) => todo!("memory containing sim-only values"),
}
break;
}
@ -626,10 +638,10 @@ impl ModuleState {
mem_name_path: &str,
split_state: &SplitState<'_>,
) -> Mem {
let mem_name = self.name_id_gen.gen(Intern::intern_owned(format!(
"{}{mem_name_path}",
input_mem.scoped_name().1 .0
)));
let mem_name = NameId(
Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1.0)),
Id::new(),
);
let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name);
let output_element_type = match single_type {
SingleType::UInt(ty) => ty.canonical(),
@ -744,7 +756,8 @@ impl ModuleState {
..
}
| MemSplit::Bundle { .. }
| MemSplit::Array { .. } => {
| MemSplit::Array { .. }
| MemSplit::PhantomConst => {
let mut replacement_ports = Vec::with_capacity(input_mem.ports().len());
let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len());
let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len());
@ -753,9 +766,10 @@ impl ModuleState {
let port_ty = port.ty();
let NameId(mem_name, _) = input_mem.scoped_name().1;
let port_name = port.port_name();
let wire_name = self
.name_id_gen
.gen(Intern::intern_owned(format!("{mem_name}_{port_name}")));
let wire_name = NameId(
Intern::intern_owned(format!("{mem_name}_{port_name}")),
Id::new(),
);
let wire = Wire::new_unchecked(
ScopedNameId(input_mem.scoped_name().0, wire_name),
port.source_location(),
@ -766,7 +780,7 @@ impl ModuleState {
output_stmts.push(
StmtWire {
annotations: Default::default(),
wire: canonical_wire.clone(),
wire: canonical_wire,
}
.into(),
);
@ -887,8 +901,7 @@ impl Folder for State {
module,
ModuleState {
output_module: None,
name_id_gen: NameIdGen::for_module(*module),
memories: HashMap::new(),
memories: HashMap::default(),
},
);
let mut this = PushedState::push_module(self, module);

View file

@ -2,32 +2,40 @@
// See Notices.txt for copyright information
#![allow(clippy::multiple_bound_locations)]
use crate::{
annotations::{Annotation, CustomFirrtlAnnotation, TargetedAnnotation},
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::ArrayType,
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},
expr::{
ops,
Expr, ExprEnum, ValueType, ops,
target::{
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
TargetPathDynArrayElement, TargetPathElement,
},
Expr, ExprEnum,
},
formal::FormalKind,
int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue},
intern::{Intern, Interned},
memory::{Mem, MemPort, PortKind, PortName, PortType, ReadUnderWrite},
module::{
AnnotatedModuleIO, Block, BlockId, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Instance, Module, ModuleBody, ModuleIO, NameId,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtIf, StmtInstance,
StmtMatch, StmtReg, StmtWire,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf,
StmtInstance, StmtMatch, StmtReg, StmtWire,
},
phantom_const::PhantomConst,
reg::Reg,
reset::{AsyncReset, Reset, SyncReset},
reset::{AsyncReset, Reset, ResetType, SyncReset},
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

@ -0,0 +1,556 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, HdlPartialEqImpl, HdlPartialOrdImpl, ToExpr, ValueType},
int::Bool,
intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize},
sim::value::{SimValue, ToSimValue, ToSimValueWithType},
source_location::SourceLocation,
ty::{
CanonicalType, OpaqueSimValueSlice, OpaqueSimValueWriter, OpaqueSimValueWritten,
StaticType, Type, TypeProperties, impl_match_variant_as_self,
serde_impls::{SerdeCanonicalType, SerdePhantomConst},
},
};
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{DeserializeOwned, Error},
};
use std::{
any::Any,
borrow::Cow,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
ops::Index,
};
#[derive(Clone)]
pub struct PhantomConstCanonicalValue {
parsed: serde_json::Value,
serialized: Interned<str>,
}
impl PhantomConstCanonicalValue {
pub fn from_json_value(parsed: serde_json::Value) -> Self {
let serialized = Intern::intern_owned(
serde_json::to_string(&parsed)
.expect("conversion from json value to text shouldn't fail"),
);
Self { parsed, serialized }
}
pub fn as_json_value(&self) -> &serde_json::Value {
&self.parsed
}
pub fn as_str(&self) -> Interned<str> {
self.serialized
}
}
impl fmt::Debug for PhantomConstCanonicalValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.serialized)
}
}
impl fmt::Display for PhantomConstCanonicalValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.serialized)
}
}
impl PartialEq for PhantomConstCanonicalValue {
fn eq(&self, other: &Self) -> bool {
self.serialized == other.serialized
}
}
impl Eq for PhantomConstCanonicalValue {}
impl Hash for PhantomConstCanonicalValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.serialized.hash(state);
}
}
impl Serialize for PhantomConstCanonicalValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.parsed.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PhantomConstCanonicalValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(Self::from_json_value(serde_json::Value::deserialize(
deserializer,
)?))
}
}
pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug {
fn deserialize_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
where
D: serde::Deserializer<'de>;
}
impl<T> PhantomConstValue for T
where
T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug,
Interned<T>: DeserializeOwned,
{
fn deserialize_value<'de, D>(deserializer: D) -> Result<Interned<Self>, D::Error>
where
D: serde::Deserializer<'de>,
{
<Interned<T> as Deserialize<'de>>::deserialize(deserializer)
}
}
/// Wrapper type that allows any Rust value to be smuggled as a HDL [`Type`].
/// This only works for values that can be [serialized][Serialize] to and [deserialized][Deserialize] from [JSON][serde_json].
pub struct PhantomConst<T: ?Sized + PhantomConstValue = PhantomConstCanonicalValue> {
value: LazyInterned<T>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct PhantomConstWithoutGenerics;
#[allow(non_upper_case_globals)]
pub const PhantomConst: PhantomConstWithoutGenerics = PhantomConstWithoutGenerics;
impl<T: Type + PhantomConstValue> Index<T> for PhantomConstWithoutGenerics {
type Output = PhantomConst<T>;
fn index(&self, value: T) -> &Self::Output {
Interned::into_inner(PhantomConst::new(&value).intern_sized())
}
}
impl<T: ?Sized + PhantomConstValue> fmt::Debug for PhantomConst<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PhantomConst").field(&self.get()).finish()
}
}
impl<T: ?Sized + PhantomConstValue> Clone for PhantomConst<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized + PhantomConstValue> Copy for PhantomConst<T> {}
impl<T: ?Sized + PhantomConstValue> PartialEq for PhantomConst<T> {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
impl<T: ?Sized + PhantomConstValue> Eq for PhantomConst<T> {}
impl<T: ?Sized + PhantomConstValue> Hash for PhantomConst<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}
struct PhantomConstCanonicalMemoize<T: ?Sized, const IS_FROM_CANONICAL: bool>(PhantomData<T>);
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Copy
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Clone
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn clone(&self) -> Self {
*self
}
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Eq
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> PartialEq
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T: ?Sized, const IS_FROM_CANONICAL: bool> Hash
for PhantomConstCanonicalMemoize<T, IS_FROM_CANONICAL>
{
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, false> {
type Input = Interned<T>;
type InputOwned = Interned<T>;
type Output = Interned<PhantomConstCanonicalValue>;
fn inner(self, input: &Self::Input) -> Self::Output {
Intern::intern_sized(PhantomConstCanonicalValue::from_json_value(
serde_json::to_value(input)
.expect("serialization failed when constructing a canonical PhantomConst"),
))
}
}
impl<T: ?Sized + PhantomConstValue> Memoize for PhantomConstCanonicalMemoize<T, true> {
type Input = Interned<PhantomConstCanonicalValue>;
type InputOwned = Interned<PhantomConstCanonicalValue>;
type Output = Interned<T>;
fn inner(self, input: &Self::Input) -> Self::Output {
PhantomConstValue::deserialize_value(input.as_json_value())
.expect("deserialization failed ")
}
}
impl<T: ?Sized + PhantomConstValue> PhantomConst<T> {
pub fn new_interned(value: Interned<T>) -> Self {
Self {
value: LazyInterned::Interned(value),
}
}
pub fn new_sized(value: T) -> Self
where
T: Clone,
{
Self::new_interned(value.intern_sized())
}
pub fn new(value: &T) -> Self {
Self::new_interned(value.intern())
}
pub fn new_deref<U: Intern + std::ops::Deref<Target = T>>(value: U) -> Self
where
T: ToOwned<Owned = U>,
{
Self::new_interned(value.intern_deref())
}
pub const fn new_const<V: Default + Into<Interned<T>>>() -> Self {
Self {
value: LazyInterned::new_const::<V>(),
}
}
pub const fn new_const_default() -> Self
where
Interned<T>: Default,
{
Self::new_const::<Interned<T>>()
}
pub fn get(self) -> Interned<T> {
self.value.interned()
}
pub fn type_properties(self) -> TypeProperties {
<()>::TYPE_PROPERTIES
}
pub fn can_connect(self, other: Self) -> bool {
self == other
}
pub fn canonical_phantom_const(self) -> PhantomConst {
if let Some(&retval) = <dyn Any>::downcast_ref::<PhantomConst>(&self) {
return retval;
}
<PhantomConst>::new_interned(
PhantomConstCanonicalMemoize::<T, false>(PhantomData).get_owned(self.get()),
)
}
pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self {
if let Some(&retval) = <dyn Any>::downcast_ref::<Self>(&canonical_type) {
return retval;
}
Self::new_interned(
PhantomConstCanonicalMemoize::<T, true>(PhantomData).get_owned(canonical_type.get()),
)
}
}
impl<T: ?Sized + PhantomConstValue> Type for PhantomConst<T> {
type BaseType = PhantomConst;
type MaskType = ();
type SimValue = PhantomConst<T>;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
()
}
fn canonical(&self) -> CanonicalType {
CanonicalType::PhantomConst(self.canonical_phantom_const())
}
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::PhantomConst(phantom_const) = canonical_type else {
panic!("expected PhantomConst");
};
Self::from_canonical_phantom_const(phantom_const)
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert!(opaque.is_empty());
*self
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert!(opaque.is_empty());
assert_eq!(*value, *self);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(*value, *self);
writer.fill_cloned_from_slice(OpaqueSimValueSlice::empty())
}
}
impl<T: ?Sized + PhantomConstValue> Default for PhantomConst<T>
where
Interned<T>: Default,
{
fn default() -> Self {
Self::TYPE
}
}
impl<T: ?Sized + PhantomConstValue> StaticType for PhantomConst<T>
where
Interned<T>: Default,
{
const TYPE: Self = Self::new_const_default();
const MASK_TYPE: Self::MaskType = ();
const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;
}
type SerdeType<T> = SerdeCanonicalType<CanonicalType, SerdePhantomConst<Interned<T>>>;
impl<T: ?Sized + PhantomConstValue> Serialize for PhantomConst<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SerdeType::<T>::PhantomConst(SerdePhantomConst(self.get())).serialize(serializer)
}
}
impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match SerdeType::<T>::deserialize(deserializer)? {
SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => {
Ok(Self::new_interned(value))
}
ty => Err(Error::invalid_value(
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
&"a PhantomConst",
)),
}
}
}
impl<T: ?Sized + PhantomConstValue> HdlPartialEqImpl<Self> for PhantomConst<T> {
#[track_caller]
fn cmp_value_eq(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_expr_eq(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
#[track_caller]
fn cmp_expr_ne(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> HdlPartialOrdImpl<Self> for PhantomConst<T> {
#[track_caller]
fn cmp_value_lt(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
false
}
#[track_caller]
fn cmp_value_le(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_value_gt(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
false
}
#[track_caller]
fn cmp_value_ge(
lhs: Self,
_lhs_value: Cow<'_, Self::SimValue>,
rhs: Self,
_rhs_value: Cow<'_, <Self as Type>::SimValue>,
) -> bool {
assert_eq!(lhs, rhs);
true
}
#[track_caller]
fn cmp_expr_lt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
#[track_caller]
fn cmp_expr_le(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
#[track_caller]
fn cmp_expr_gt(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
false.to_expr()
}
#[track_caller]
fn cmp_expr_ge(lhs: Expr<Self>, rhs: Expr<Self>) -> Expr<Bool> {
assert_eq!(lhs.ty(), rhs.ty());
true.to_expr()
}
}
impl<T: ?Sized + PhantomConstValue> ToSimValue for PhantomConst<T> {
fn to_sim_value(&self) -> SimValue<Self::Type> {
SimValue::from_value(*self, *self)
}
}
impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<PhantomConst<T>> for PhantomConst<T> {
fn to_sim_value_with_type(&self, ty: PhantomConst<T>) -> SimValue<PhantomConst<T>> {
SimValue::from_value(ty, *self)
}
}
impl<T: ?Sized + PhantomConstValue> ToSimValueWithType<CanonicalType> for PhantomConst<T> {
fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue<CanonicalType> {
SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self))
}
}
mod sealed {
pub trait Sealed<T: ?Sized> {}
}
pub trait PhantomConstGet<T: ?Sized + PhantomConstValue>: sealed::Sealed<T> {
fn get(&self) -> Interned<T>;
}
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
sealed::Sealed<T> for This
{
}
impl<T: ?Sized + PhantomConstValue, This: ?Sized + std::ops::Deref<Target: PhantomConstGet<T>>>
PhantomConstGet<T> for This
{
fn get(&self) -> Interned<T> {
This::Target::get(&**self)
}
}
macro_rules! impl_phantom_const_get {
(
impl PhantomConstGet<$T:ident> for $ty:ty {
fn $get:ident(&$get_self:ident) -> _ $get_body:block
}
) => {
impl<$T: ?Sized + PhantomConstValue> sealed::Sealed<$T> for $ty {}
impl<$T: ?Sized + PhantomConstValue> PhantomConstGet<$T> for $ty {
fn $get(&$get_self) -> Interned<$T> $get_body
}
};
}
impl_phantom_const_get! {
impl PhantomConstGet<T> for PhantomConst<T> {
fn get(&self) -> _ {
PhantomConst::get(*self)
}
}
}
impl_phantom_const_get! {
impl PhantomConstGet<T> for Expr<PhantomConst<T>> {
fn get(&self) -> _ {
PhantomConst::get(self.ty())
}
}
}
#[doc(hidden)]
pub trait ReturnSelfUnchanged<T: ?Sized> {
type Type: ?Sized;
}
impl<This: ?Sized, T: ?Sized> ReturnSelfUnchanged<T> for This {
type Type = This;
}
#[doc(hidden)]
pub fn type_alias_phantom_const_get_helper<T: ?Sized + PhantomConstValue, R: Intern + Clone>(
param: impl PhantomConstGet<T>,
get: impl FnOnce(Interned<T>) -> R,
) -> &'static R {
Interned::into_inner(get(param.get()).intern_sized())
}

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

@ -1,22 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub use crate::{
annotations::Annotation,
__,
annotations::{
BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
},
array::{Array, ArrayType},
cli::Cli,
build::{BuildCli, JobParams, RunBuild},
bundle::Bundle,
clock::{Clock, ClockDomain, ToClock},
enum_::{HdlNone, HdlOption, HdlSome},
expr::{CastBitsTo, CastTo, CastToBits, Expr, ReduceBits, ToExpr},
enum_::{Enum, HdlNone, HdlOption, HdlSome},
expr::{
CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr,
ReduceBits, ToExpr, ValueType, repeat,
},
formal::{
MakeFormalExpr, all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset,
hdl_assert, hdl_assert_with_enable, hdl_assume, hdl_assume_with_enable, hdl_cover,
hdl_cover_with_enable,
},
hdl, hdl_module,
int::{Bool, DynSize, IntCmp, KnownSize, SInt, SIntType, Size, UInt, UIntType},
int::{Bool, DynSize, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue},
memory::{Mem, MemBuilder, ReadUnderWrite},
module::{
annotate, connect, connect_any, instance, memory, memory_array, memory_with_init,
reg_builder, wire, Instance, Module, ModuleBuilder,
Instance, Module, ModuleBuilder, annotate, connect, connect_any, incomplete_wire, instance,
memory, memory_array, memory_with_init, reg_builder, wire,
},
phantom_const::{PhantomConst, PhantomConstGet},
platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
sim::{
ExternModuleSimulationState, Simulation,
time::{SimDuration, SimInstant},
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
},
source_location::SourceLocation,
testing::{FormalMode, assert_formal},
ty::{AsMask, CanonicalType, Type},
util::{ConstUsize, GenericConstUsize},
wire::Wire,
__,
};
pub use bitvec::{slice::BitSlice, vec::BitVec};

View file

@ -2,24 +2,34 @@
// 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},
source_location::SourceLocation,
ty::{CanonicalType, Type},
};
use std::fmt;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Reg<T: Type> {
pub struct Reg<T: Type, R: ResetType = Reset> {
name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
clock_domain: Expr<ClockDomain<R>>,
init: Option<Expr<T>>,
}
impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
impl<T: Type, R: ResetType> ValueType for Reg<T, R> {
type Type = T;
type ValueCategory = ValueCategoryExpr;
fn ty(&self) -> Self::Type {
self.ty
}
}
impl<T: Type, R: ResetType> fmt::Debug for Reg<T, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
name,
@ -37,8 +47,8 @@ impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
}
}
impl<T: Type> Reg<T> {
pub fn canonical(&self) -> Reg<CanonicalType> {
impl<T: Type, R: ResetType> Reg<T, R> {
pub fn canonical(&self) -> Reg<CanonicalType, R> {
let Self {
name,
source_location,
@ -59,7 +69,7 @@ impl<T: Type> Reg<T> {
scoped_name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
clock_domain: Expr<ClockDomain<R>>,
init: Option<Expr<T>>,
) -> Self {
assert!(
@ -67,7 +77,7 @@ impl<T: Type> Reg<T> {
"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,
@ -77,9 +87,6 @@ impl<T: Type> Reg<T> {
init,
}
}
pub fn ty(&self) -> T {
self.ty
}
pub fn source_location(&self) -> SourceLocation {
self.source_location
}
@ -98,7 +105,7 @@ impl<T: Type> Reg<T> {
pub fn scoped_name(&self) -> ScopedNameId {
self.name
}
pub fn clock_domain(&self) -> Expr<ClockDomain> {
pub fn clock_domain(&self) -> Expr<ClockDomain<R>> {
self.clock_domain
}
pub fn init(&self) -> Option<Expr<T>> {

View file

@ -1,26 +1,57 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
int::Bool,
clock::Clock,
expr::{CastToImpl, Expr, ValueType},
int::{Bool, SInt, SIntValue, UInt, UIntValue},
sim::value::SimValue,
source_location::SourceLocation,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
ty::{
CanonicalType, OpaqueSimValueSize, OpaqueSimValueSlice, OpaqueSimValueWriter,
OpaqueSimValueWritten, StaticType, Type, TypeProperties, impl_match_variant_as_self,
},
util::ConstUsize,
};
use bitvec::{bits, order::Lsb0};
mod sealed {
pub trait ResetTypeSealed {}
}
pub trait ResetType: StaticType<MaskType = Bool> + sealed::ResetTypeSealed {}
pub trait ResetType:
StaticType<MaskType = Bool>
+ sealed::ResetTypeSealed
+ CastToImpl<Bool, ValueOutput = bool>
+ CastToImpl<Reset, ValueOutput = SimValue<Reset>>
+ CastToImpl<SyncReset, ValueOutput = SimValue<SyncReset>>
+ CastToImpl<AsyncReset, ValueOutput = SimValue<AsyncReset>>
+ CastToImpl<Clock, ValueOutput = SimValue<Clock>>
+ CastToImpl<UInt<1>, ValueOutput = UIntValue<ConstUsize<1>>>
+ CastToImpl<SInt<1>, ValueOutput = SIntValue<ConstUsize<1>>>
+ CastToImpl<UInt, ValueOutput = UIntValue>
+ CastToImpl<SInt, ValueOutput = SIntValue>
{
fn dispatch<D: ResetTypeDispatch>(input: D::Input<Self>, dispatch: D) -> D::Output<Self>;
}
pub trait ResetTypeDispatch: Sized {
type Input<T: ResetType>;
type Output<T: ResetType>;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset>;
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset>;
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset>;
}
macro_rules! reset_type {
($name:ident, $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal) => {
($name:ident, $(#[$impl_trait:ident])? $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal, $dispatch_fn:ident) => {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct $name;
impl Type for $name {
type BaseType = $name;
type MaskType = Bool;
type SimValue = bool;
impl_match_variant_as_self!();
@ -42,6 +73,31 @@ macro_rules! reset_type {
};
retval
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
opaque.bits()[0]
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert_eq!(opaque.size(), OpaqueSimValueSize::from_bit_width(1));
*value = opaque.bits()[0];
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(writer.size(), OpaqueSimValueSize::from_bit_width(1));
writer.fill_cloned_from_slice(OpaqueSimValueSlice::from_bitslice(
[bits![0], bits![1]][*value as usize],
))
}
}
impl $name {
@ -61,60 +117,76 @@ macro_rules! reset_type {
is_storable: false,
is_castable_from_bits: $is_castable_from_bits,
bit_width: 1,
sim_only_values_len: 0,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
impl sealed::ResetTypeSealed for $name {}
impl ResetType for $name {}
impl ResetType for $name {
fn dispatch<D: ResetTypeDispatch>(
input: D::Input<Self>,
dispatch: D,
) -> D::Output<Self> {
dispatch.$dispatch_fn(input)
}
}
pub trait $Trait {
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 for Expr<$name> {
fn $trait_fn(&self) -> Expr<$name> {
$($impl_trait $Trait for Expr<$name> {
type Output = Expr<$name>;
fn $trait_fn(&self) -> Self::Output {
*self
}
}
})?
};
}
reset_type!(AsyncReset, ToAsyncReset::to_async_reset, true);
reset_type!(SyncReset, ToSyncReset::to_sync_reset, true);
reset_type!(AsyncReset, #[impl] ToAsyncReset::to_async_reset, true, async_reset);
reset_type!(SyncReset, #[impl] ToSyncReset::to_sync_reset, true, sync_reset);
reset_type!(
Reset,
ToReset::to_reset,
false // Reset is not castable from bits because we don't know if it's async or sync
false, // Reset is not castable from bits because we don't know if it's async or sync
reset
);
impl ToSyncReset for bool {
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)
}
}

4285
crates/fayalite/src/sim.rs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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,397 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::{
fmt,
ops::{Add, AddAssign, Sub, SubAssign},
time::Duration,
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SimInstant {
time_since_start: SimDuration,
}
impl SimInstant {
pub const fn checked_add(self, duration: SimDuration) -> Option<Self> {
let Some(time_since_start) = self.time_since_start.checked_add(duration) else {
return None;
};
Some(SimInstant { time_since_start })
}
pub const fn checked_duration_since(self, earlier: Self) -> Option<SimDuration> {
self.time_since_start.checked_sub(earlier.time_since_start)
}
pub const fn checked_sub(self, duration: SimDuration) -> Option<Self> {
let Some(time_since_start) = self.time_since_start.checked_sub(duration) else {
return None;
};
Some(SimInstant { time_since_start })
}
#[track_caller]
pub const fn duration_since(self, earlier: Self) -> SimDuration {
let Some(retval) = self.checked_duration_since(earlier) else {
panic!(
"tried to compute the duration since a later time -- durations can't be negative"
);
};
retval
}
pub const fn saturating_duration_since(self, earlier: Self) -> SimDuration {
let Some(retval) = self.checked_duration_since(earlier) else {
return SimDuration::ZERO;
};
retval
}
}
impl Add<SimDuration> for SimInstant {
type Output = SimInstant;
#[track_caller]
fn add(mut self, rhs: SimDuration) -> Self::Output {
self += rhs;
self
}
}
impl AddAssign<SimDuration> for SimInstant {
#[track_caller]
fn add_assign(&mut self, rhs: SimDuration) {
self.time_since_start += rhs;
}
}
impl Add<SimInstant> for SimDuration {
type Output = SimInstant;
#[track_caller]
fn add(self, rhs: SimInstant) -> Self::Output {
rhs.add(self)
}
}
impl Sub for SimInstant {
type Output = SimDuration;
#[track_caller]
fn sub(self, rhs: SimInstant) -> Self::Output {
self.duration_since(rhs)
}
}
impl Sub<SimDuration> for SimInstant {
type Output = SimInstant;
#[track_caller]
fn sub(self, rhs: SimDuration) -> Self::Output {
let Some(retval) = self.checked_sub(rhs) else {
panic!("SimInstant underflow");
};
retval
}
}
impl SubAssign<SimDuration> for SimInstant {
#[track_caller]
fn sub_assign(&mut self, rhs: SimDuration) {
*self = *self - rhs;
}
}
impl SimInstant {
pub const START: SimInstant = SimInstant {
time_since_start: SimDuration::ZERO,
};
}
impl fmt::Debug for SimInstant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.time_since_start.fmt(f)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SimDuration {
attos: u128,
}
impl AddAssign for SimDuration {
#[track_caller]
fn add_assign(&mut self, rhs: SimDuration) {
*self = *self + rhs;
}
}
impl Add for SimDuration {
type Output = SimDuration;
#[track_caller]
fn add(self, rhs: SimDuration) -> Self::Output {
SimDuration {
attos: self
.attos
.checked_add(rhs.attos)
.expect("overflow adding durations"),
}
}
}
impl Sub for SimDuration {
type Output = Self;
#[track_caller]
fn sub(self, rhs: Self) -> Self::Output {
SimDuration {
attos: self
.attos
.checked_add(rhs.attos)
.expect("underflow subtracting durations -- durations can't be negative"),
}
}
}
impl SubAssign for SimDuration {
#[track_caller]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct SimDurationParts {
pub attos: u16,
pub femtos: u16,
pub picos: u16,
pub nanos: u16,
pub micros: u16,
pub millis: u16,
pub secs: u128,
}
macro_rules! impl_duration_units {
(
$(
#[unit_const = $UNIT:ident, from_units = $from_units:ident, as_units = $as_units:ident, units = $units:ident, suffix = $suffix:literal]
const $log10_units_per_sec:ident: u32 = $log10_units_per_sec_value:expr;
)*
) => {
impl SimDuration {
$(
const $log10_units_per_sec: u32 = $log10_units_per_sec_value;
pub const fn $from_units($units: u128) -> Self {
Self::from_units_helper::<{ Self::$log10_units_per_sec }>($units)
}
pub const fn $as_units(self) -> u128 {
self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }
}
)*
pub const fn to_parts(mut self) -> SimDurationParts {
$(
let $units = self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
self.attos %= const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
)*
SimDurationParts {
$($units: $units as _,)*
}
}
pub const fn from_parts_checked(parts: SimDurationParts) -> Option<Self> {
let attos = 0u128;
$(
let Some(product) = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }.checked_mul(parts.$units as u128) else {
return None;
};
let Some(attos) = attos.checked_add(product) else {
return None;
};
)*
Some(Self {
attos,
})
}
}
impl fmt::Debug for SimDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ilog10_attos = match self.attos.checked_ilog10() {
Some(v) => v,
None => Self::LOG10_ATTOS_PER_SEC,
};
let (suffix, int, fraction, fraction_digits) =
match Self::LOG10_ATTOS_PER_SEC.saturating_sub(ilog10_attos) {
$(
..=Self::$log10_units_per_sec => {
let divisor = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
(
$suffix,
self.attos / divisor,
self.attos % divisor,
(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) as usize,
)
},
)*
_ => unreachable!(),
};
write!(f, "{int}")?;
if fraction != 0 {
write!(f, ".{fraction:0fraction_digits$}")?;
}
write!(f, " {suffix}")
}
}
#[cfg(test)]
#[test]
fn test_duration_debug() {
$(
assert_eq!(
format!("{:?}", SimDuration::$from_units(123)),
concat!("123 ", $suffix)
);
assert_eq!(
format!("{:?}", SimDuration::$from_units(1)),
concat!("1 ", $suffix),
);
let mut v = SimDuration::$from_units(1);
if v.attos < 1 << 53 {
v.attos += 1;
assert_eq!(
format!("{v:?}"),
format!("{} {}", v.attos as f64 / 10.0f64.powf((SimDuration::LOG10_ATTOS_PER_SEC - SimDuration::$log10_units_per_sec) as f64), $suffix),
"1 {} + 1 as == {} as", $suffix, v.attos,
);
}
)*
}
};
}
impl_duration_units! {
#[unit_const = SECOND, from_units = from_secs, as_units = as_secs, units = secs, suffix = "s"]
const LOG10_SECS_PER_SEC: u32 = 0;
#[unit_const = MILLISECOND, from_units = from_millis, as_units = as_millis, units = millis, suffix = "ms"]
const LOG10_MILLIS_PER_SEC: u32 = 3;
#[unit_const = MICROSECOND, from_units = from_micros, as_units = as_micros, units = micros, suffix = "μs"]
const LOG10_MICROS_PER_SEC: u32 = 6;
#[unit_const = NANOSECOND, from_units = from_nanos, as_units = as_nanos, units = nanos, suffix = "ns"]
const LOG10_NANOS_PER_SEC: u32 = 9;
#[unit_const = PICOSECOND, from_units = from_picos, as_units = as_picos, units = picos, suffix = "ps"]
const LOG10_PICOS_PER_SEC: u32 = 12;
#[unit_const = FEMTOSECOND, from_units = from_femtos, as_units = as_femtos, units = femtos, suffix = "fs"]
const LOG10_FEMTOS_PER_SEC: u32 = 15;
#[unit_const = ATTOSECOND, from_units = from_attos, as_units = as_attos, units = attos, suffix = "as"]
const LOG10_ATTOS_PER_SEC: u32 = 18;
}
impl SimDuration {
const fn from_units_helper<const UNITS_PER_SEC: u32>(units: u128) -> Self {
let Some(attos) =
units.checked_mul(const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - UNITS_PER_SEC) })
else {
panic!("duration too big");
};
Self { attos }
}
pub const ZERO: SimDuration = SimDuration::from_secs(0);
pub const fn from_parts(parts: SimDurationParts) -> Self {
match Self::from_parts_checked(parts) {
Some(v) => v,
None => panic!("duration too big"),
}
}
pub const fn abs_diff(self, other: Self) -> Self {
Self {
attos: self.attos.abs_diff(other.attos),
}
}
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
let Some(attos) = self.attos.checked_add(rhs.attos) else {
return None;
};
Some(Self { attos })
}
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
let Some(attos) = self.attos.checked_sub(rhs.attos) else {
return None;
};
Some(Self { attos })
}
pub const fn is_zero(self) -> bool {
self.attos == 0
}
pub const fn saturating_add(self, rhs: Self) -> Self {
Self {
attos: self.attos.saturating_add(rhs.attos),
}
}
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self {
attos: self.attos.saturating_sub(rhs.attos),
}
}
pub const fn checked_ilog10(self) -> Option<i32> {
let Some(ilog10_attos) = self.attos.checked_ilog10() else {
return None;
};
Some(ilog10_attos as i32 - Self::LOG10_ATTOS_PER_SEC as i32)
}
#[track_caller]
pub const fn ilog10(self) -> i32 {
let Some(retval) = self.checked_ilog10() else {
panic!("tried to take the ilog10 of 0");
};
retval
}
pub const fn checked_pow10(log10: i32, underflow_is_zero: bool) -> Option<Self> {
let Some(log10) = Self::LOG10_ATTOS_PER_SEC.checked_add_signed(log10) else {
return if log10 < 0 && underflow_is_zero {
Some(Self::ZERO)
} else {
None
};
};
let Some(attos) = 10u128.checked_pow(log10) else {
return None;
};
Some(Self { attos })
}
#[track_caller]
pub const fn pow10(log10: i32) -> Self {
let Some(retval) = Self::checked_pow10(log10, true) else {
panic!("pow10 overflowed");
};
retval
}
pub const fn is_power_of_ten(self) -> bool {
const TEN: u128 = 10;
const NUMBER_OF_POWERS_OF_TEN: usize = {
let mut n = 0;
while let Some(_) = TEN.checked_pow(n as u32) {
n += 1;
}
n
};
const POWERS_OF_TEN: [u128; NUMBER_OF_POWERS_OF_TEN] = {
let mut retval = [0; NUMBER_OF_POWERS_OF_TEN];
let mut i = 0;
while i < NUMBER_OF_POWERS_OF_TEN {
retval[i] = TEN.pow(i as u32);
i += 1;
}
retval
};
let mut i = 0;
while i < NUMBER_OF_POWERS_OF_TEN {
if self.attos == POWERS_OF_TEN[i] {
return true;
}
i += 1;
}
false
}
}
impl From<Duration> for SimDuration {
fn from(duration: Duration) -> Self {
Self::from_nanos(duration.as_nanos())
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,327 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! `unsafe` parts of [`DynSimOnlyValue`]
use crate::expr::{ValueType, value_category::ValueCategoryValue};
use serde::{Serialize, de::DeserializeOwned};
use std::{
any::{self, TypeId},
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
mem::ManuallyDrop,
rc::Rc,
};
pub trait SimOnlyValueTrait:
'static + Eq + Hash + fmt::Debug + Serialize + DeserializeOwned + Clone + Default
{
}
impl<T: 'static + Eq + Hash + fmt::Debug + Serialize + DeserializeOwned + Clone + Default>
SimOnlyValueTrait for T
{
}
/// Safety: `type_id_dyn` must return `TypeId::of::<T>()` where `Self = SimOnly<T>`
unsafe trait DynSimOnlyTrait: 'static + Send + Sync {
fn type_id_dyn(&self) -> TypeId;
fn type_name(&self) -> &'static str;
fn default_value(&self) -> Rc<dyn DynSimOnlyValueTrait>;
fn deserialize_from_json_string(
&self,
json_str: &str,
) -> serde_json::Result<Rc<dyn DynSimOnlyValueTrait>>;
}
/// Safety: `type_id_dyn` is implemented correctly
unsafe impl<T: SimOnlyValueTrait> DynSimOnlyTrait for SimOnly<T> {
fn type_id_dyn(&self) -> TypeId {
TypeId::of::<T>()
}
fn type_name(&self) -> &'static str {
any::type_name::<T>()
}
fn default_value(&self) -> Rc<dyn DynSimOnlyValueTrait> {
Rc::new(T::default())
}
fn deserialize_from_json_string(
&self,
json_str: &str,
) -> serde_json::Result<Rc<dyn DynSimOnlyValueTrait>> {
Ok(Rc::<T>::new(serde_json::from_str(json_str)?))
}
}
/// Safety:
/// * `type_id_dyn()` must return `TypeId::of::<Self>()`.
/// * `ty().type_id()` must return `TypeId::of::<Self>()`.
unsafe trait DynSimOnlyValueTrait: 'static + fmt::Debug {
fn type_id_dyn(&self) -> TypeId;
fn ty(&self) -> DynSimOnly;
fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool;
fn serialize_to_json_string(&self) -> serde_json::Result<String>;
fn hash_dyn(&self, state: &mut dyn Hasher);
}
impl dyn DynSimOnlyValueTrait {
fn is<T: SimOnlyValueTrait>(&self) -> bool {
Self::type_id_dyn(self) == TypeId::of::<T>()
}
fn downcast_ref<T: SimOnlyValueTrait>(&self) -> Option<&T> {
if Self::is::<T>(self) {
// Safety: checked that `Self` is really `T`
Some(unsafe { &*(self as *const Self as *const T) })
} else {
None
}
}
fn downcast_rc<T: SimOnlyValueTrait>(self: Rc<Self>) -> Result<Rc<T>, Rc<Self>> {
if Self::is::<T>(&*self) {
// Safety: checked that `Self` is really `T`
Ok(unsafe { Rc::from_raw(Rc::into_raw(self) as *const T) })
} else {
Err(self)
}
}
}
/// Safety:
/// * `type_id_dyn()` returns `TypeId::of::<Self>()`.
/// * `ty().type_id()` returns `TypeId::of::<Self>()`.
unsafe impl<T: SimOnlyValueTrait> DynSimOnlyValueTrait for T {
fn type_id_dyn(&self) -> TypeId {
TypeId::of::<T>()
}
fn ty(&self) -> DynSimOnly {
DynSimOnly::of::<T>()
}
fn eq_dyn(&self, other: &dyn DynSimOnlyValueTrait) -> bool {
other.downcast_ref::<T>().is_some_and(|other| self == other)
}
fn serialize_to_json_string(&self) -> serde_json::Result<String> {
serde_json::to_string(self)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
}
#[derive(Copy, Clone)]
pub struct DynSimOnly {
ty: &'static dyn DynSimOnlyTrait,
}
impl DynSimOnly {
pub const fn of<T: SimOnlyValueTrait>() -> Self {
Self {
ty: &const { SimOnly::<T>::new() },
}
}
pub fn type_id(self) -> TypeId {
self.ty.type_id_dyn()
}
pub fn type_name(self) -> &'static str {
self.ty.type_name()
}
pub fn is<T: SimOnlyValueTrait>(self) -> bool {
self.type_id() == TypeId::of::<T>()
}
pub fn downcast<T: SimOnlyValueTrait>(self) -> Option<SimOnly<T>> {
self.is::<T>().then_some(SimOnly::default())
}
pub fn deserialize_from_json_string(
self,
json_str: &str,
) -> serde_json::Result<DynSimOnlyValue> {
self.ty
.deserialize_from_json_string(json_str)
.map(DynSimOnlyValue)
}
pub fn default_value(self) -> DynSimOnlyValue {
DynSimOnlyValue(self.ty.default_value())
}
}
impl PartialEq for DynSimOnly {
fn eq(&self, other: &Self) -> bool {
Self::type_id(*self) == Self::type_id(*other)
}
}
impl Eq for DynSimOnly {}
impl Hash for DynSimOnly {
fn hash<H: Hasher>(&self, state: &mut H) {
Self::type_id(*self).hash(state);
}
}
impl fmt::Debug for DynSimOnly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SimOnly<{}>", self.ty.type_name())
}
}
impl<T: SimOnlyValueTrait> From<SimOnly<T>> for DynSimOnly {
fn from(value: SimOnly<T>) -> Self {
let SimOnly(PhantomData) = value;
Self::of::<T>()
}
}
/// the [`Type`][Type] for a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL
///
/// [Type]: crate::ty::Type
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct SimOnly<T: SimOnlyValueTrait>(PhantomData<fn(T) -> T>);
impl<T: SimOnlyValueTrait> fmt::Debug for SimOnly<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
DynSimOnly::of::<T>().fmt(f)
}
}
impl<T: SimOnlyValueTrait> SimOnly<T> {
pub const fn new() -> Self {
Self(PhantomData)
}
}
impl<T: SimOnlyValueTrait> Copy for SimOnly<T> {}
impl<T: SimOnlyValueTrait> Default for SimOnly<T> {
fn default() -> Self {
Self::new()
}
}
/// a value that can only be used in a Fayalite simulation, it can't be converted to FIRRTL
#[derive(Clone, Eq, Hash, Default, Ord)]
pub struct SimOnlyValue<T: SimOnlyValueTrait>(Rc<T>);
impl<T: SimOnlyValueTrait + PartialEq<U>, U: SimOnlyValueTrait> PartialEq<SimOnlyValue<U>>
for SimOnlyValue<T>
{
fn eq(&self, other: &SimOnlyValue<U>) -> bool {
<T as PartialEq<U>>::eq(self, other)
}
}
impl<T: SimOnlyValueTrait + PartialOrd<U>, U: SimOnlyValueTrait> PartialOrd<SimOnlyValue<U>>
for SimOnlyValue<T>
{
fn partial_cmp(&self, other: &SimOnlyValue<U>) -> Option<std::cmp::Ordering> {
<T as PartialOrd<U>>::partial_cmp(self, other)
}
}
impl<T: SimOnlyValueTrait> SimOnlyValue<T> {
pub fn with_dyn_ref<F: FnOnce(&DynSimOnlyValue) -> R, R>(&self, f: F) -> R {
// Safety: creating a copied `Rc<T>` is safe as long as the copy isn't dropped and isn't changed
// to point somewhere else, `f` can't change `dyn_ref` because it's only given a shared reference.
let dyn_ref =
unsafe { ManuallyDrop::new(DynSimOnlyValue(Rc::<T>::from_raw(Rc::as_ptr(&self.0)))) };
f(&dyn_ref)
}
pub fn from_rc(v: Rc<T>) -> Self {
Self(v)
}
pub fn new(v: T) -> Self {
Self(Rc::new(v))
}
pub fn into_inner(this: Self) -> Rc<T> {
this.0
}
pub fn inner_mut(this: &mut Self) -> &mut Rc<T> {
&mut this.0
}
pub fn inner(this: &Self) -> &Rc<T> {
&this.0
}
pub fn into_dyn(this: Self) -> DynSimOnlyValue {
DynSimOnlyValue::from(this)
}
}
impl<T: SimOnlyValueTrait> std::ops::Deref for SimOnlyValue<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: SimOnlyValueTrait> std::ops::DerefMut for SimOnlyValue<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
Rc::make_mut(&mut self.0)
}
}
#[derive(Clone)]
pub struct DynSimOnlyValue(Rc<dyn DynSimOnlyValueTrait>);
impl fmt::Debug for DynSimOnlyValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<dyn DynSimOnlyValueTrait as fmt::Debug>::fmt(&*self.0, f)
}
}
impl PartialEq for DynSimOnlyValue {
fn eq(&self, other: &Self) -> bool {
DynSimOnlyValueTrait::eq_dyn(&*self.0, &*other.0)
}
}
impl Eq for DynSimOnlyValue {}
impl Hash for DynSimOnlyValue {
fn hash<H: Hasher>(&self, state: &mut H) {
DynSimOnlyValueTrait::hash_dyn(&*self.0, state);
}
}
impl<T: SimOnlyValueTrait> From<SimOnlyValue<T>> for DynSimOnlyValue {
fn from(value: SimOnlyValue<T>) -> Self {
Self(value.0)
}
}
impl ValueType for DynSimOnlyValue {
type Type = DynSimOnly;
type ValueCategory = ValueCategoryValue;
fn ty(&self) -> Self::Type {
self.0.ty()
}
}
impl DynSimOnlyValue {
pub fn type_id(&self) -> TypeId {
self.0.type_id_dyn()
}
pub fn is<T: SimOnlyValueTrait>(&self) -> bool {
self.0.is::<T>()
}
pub fn downcast<T: SimOnlyValueTrait>(self) -> Result<SimOnlyValue<T>, DynSimOnlyValue> {
match <dyn DynSimOnlyValueTrait>::downcast_rc(self.0) {
Ok(v) => Ok(SimOnlyValue(v)),
Err(v) => Err(Self(v)),
}
}
pub fn downcast_ref<T: SimOnlyValueTrait>(&self) -> Option<&T> {
<dyn DynSimOnlyValueTrait>::downcast_ref(&*self.0)
}
pub fn serialize_to_json_string(&self) -> serde_json::Result<String> {
self.0.serialize_to_json_string()
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,8 @@
// See Notices.txt for copyright information
use crate::{
intern::{Intern, Interned},
util::DebugAsDisplay,
util::{DebugAsDisplay, HashMap},
};
use hashbrown::HashMap;
use std::{cell::RefCell, fmt, num::NonZeroUsize, panic, path::Path};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -97,7 +96,7 @@ impl NormalizeFilesForTestsState {
fn new() -> Self {
Self {
test_position: panic::Location::caller(),
file_pattern_matches: HashMap::new(),
file_pattern_matches: HashMap::default(),
}
}
}
@ -143,7 +142,7 @@ impl From<&'_ panic::Location<'_>> for SourceLocation {
map.entry_ref(file)
.or_insert_with(|| NormalizedFileForTestState {
file_name_id: NonZeroUsize::new(len + 1).unwrap(),
positions_map: HashMap::new(),
positions_map: HashMap::default(),
});
file_str = m.generate_file_name(file_state.file_name_id);
file = &file_str;

View file

@ -0,0 +1,224 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
build::{
BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams,
NoArgs, RunBuild,
external::{ExternalCommandArgs, ExternalCommandJobKind},
firrtl::{FirrtlArgs, FirrtlJobKind},
formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind},
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
},
bundle::BundleType,
firrtl::ExportOptions,
module::Module,
util::HashMap,
};
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Write},
path::{Path, PathBuf},
process::Command,
sync::{Mutex, OnceLock},
};
#[derive(
clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize,
)]
#[non_exhaustive]
pub enum FormalMode {
#[default]
BMC,
Prove,
Live,
Cover,
}
impl FormalMode {
pub fn as_str(self) -> &'static str {
match self {
FormalMode::BMC => "bmc",
FormalMode::Prove => "prove",
FormalMode::Live => "live",
FormalMode::Cover => "cover",
}
}
}
impl fmt::Display for FormalMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Deserialize)]
struct CargoMetadata {
target_directory: String,
}
fn get_cargo_target_dir() -> &'static Path {
static RETVAL: OnceLock<PathBuf> = OnceLock::new();
RETVAL.get_or_init(|| {
let output = Command::new(
std::env::var_os("CARGO")
.as_deref()
.unwrap_or("cargo".as_ref()),
)
.arg("metadata")
.output()
.expect("can't run `cargo metadata`");
if !output.status.success() {
panic!(
"can't run `cargo metadata`:\n{}\nexited with status: {}",
String::from_utf8_lossy(&output.stderr),
output.status
);
}
let CargoMetadata { target_directory } =
serde_json::from_slice(&output.stdout).expect("can't parse output of `cargo metadata`");
PathBuf::from(target_directory)
})
}
#[track_caller]
fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf {
static DIRS: Mutex<Option<HashMap<String, u64>>> = Mutex::new(None);
let test_name = test_name.to_string();
// don't use line/column numbers since that constantly changes as you edit tests
let file = std::panic::Location::caller().file();
// simple reproducible hash
let simple_hash = file.bytes().chain(test_name.bytes()).fold(
((file.len() as u32) << 16).wrapping_add(test_name.len() as u32),
|mut h, b| {
h = h.wrapping_mul(0xaa0d184b);
h ^= h.rotate_right(5);
h ^= h.rotate_right(13);
h.wrapping_add(b as u32)
},
);
let mut dir = String::with_capacity(64);
for ch in Path::new(file)
.file_stem()
.unwrap_or_default()
.to_str()
.unwrap()
.chars()
.chain(['-'])
.chain(test_name.chars())
{
dir.push(match ch {
ch if ch.is_alphanumeric() => ch,
'_' | '-' | '+' | '.' | ',' | ' ' => ch,
_ => '_',
});
}
write!(dir, "-{simple_hash:08x}").unwrap();
let index = *DIRS
.lock()
.unwrap()
.get_or_insert_with(HashMap::default)
.entry_ref(&dir)
.and_modify(|v| *v += 1)
.or_insert(0);
write!(dir, "-{index}").unwrap();
get_cargo_target_dir()
.join("fayalite_assert_formal")
.join(dir)
}
fn make_assert_formal_args(
test_name: &dyn std::fmt::Display,
formal_mode: FormalMode,
formal_depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) -> eyre::Result<JobArgsAndDependencies<ExternalCommandJobKind<Formal>>> {
let args = JobKindAndArgs {
kind: BaseJobKind,
args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None),
};
let dependencies = JobArgsAndDependencies {
args,
dependencies: (),
};
let args = JobKindAndArgs {
kind: FirrtlJobKind,
args: FirrtlArgs { export_options },
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::resolve_program_path(
None,
UnadjustedVerilogArgs {
firtool_extra_args: vec![],
verilog_dialect: None,
verilog_debug: true,
},
)?,
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: VerilogJobKind,
args: VerilogJobArgs {},
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: WriteSbyFileJobKind,
args: FormalArgs {
sby_extra_args: vec![],
formal_mode,
formal_depth,
formal_solver: solver.unwrap_or(FormalArgs::DEFAULT_SOLVER).into(),
smtbmc_extra_args: vec![],
},
};
let dependencies = JobArgsAndDependencies { args, dependencies };
let args = JobKindAndArgs {
kind: ExternalCommandJobKind::new(),
args: ExternalCommandArgs::resolve_program_path(None, FormalAdditionalArgs {})?,
};
Ok(JobArgsAndDependencies { args, dependencies })
}
pub fn try_assert_formal<M: AsRef<Module<T>>, T: BundleType>(
test_name: impl std::fmt::Display,
module: M,
formal_mode: FormalMode,
formal_depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) -> eyre::Result<()> {
const APP_NAME: &'static str = "fayalite::testing::assert_formal";
make_assert_formal_args(
&test_name,
formal_mode,
formal_depth,
solver,
export_options,
)?
.run_without_platform(
|NoArgs {}| Ok(JobParams::new(module)),
&GlobalParams::new(None, APP_NAME),
)
}
#[track_caller]
pub fn assert_formal<M: AsRef<Module<T>>, T: BundleType>(
test_name: impl std::fmt::Display,
module: M,
formal_mode: FormalMode,
formal_depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) {
try_assert_formal(
test_name,
module,
formal_mode,
formal_depth,
solver,
export_options,
)
.expect("testing::assert_formal() failed");
}

View file

@ -7,12 +7,27 @@ use crate::{
clock::Clock,
enum_::Enum,
expr::Expr,
int::{Bool, SInt, UInt},
int::{Bool, SInt, UInt, UIntValue},
intern::{Intern, Interned},
phantom_const::PhantomConst,
reset::{AsyncReset, Reset, SyncReset},
sim::value::{DynSimOnly, DynSimOnlyValue, SimValue, ToSimValueWithType},
source_location::SourceLocation,
util::{ConstUsize, slice_range, try_slice_range},
};
use std::{fmt, hash::Hash, iter::FusedIterator, ops::Index};
use bitvec::{slice::BitSlice, vec::BitVec};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
use std::{
fmt,
hash::Hash,
iter::{FusedIterator, Sum},
marker::PhantomData,
mem,
ops::{Add, AddAssign, Bound, Index, Mul, MulAssign, Range, Sub, SubAssign},
sync::Arc,
};
pub(crate) mod serde_impls;
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
#[non_exhaustive]
@ -21,6 +36,23 @@ pub struct TypeProperties {
pub is_storable: bool,
pub is_castable_from_bits: bool,
pub bit_width: usize,
pub sim_only_values_len: usize,
}
impl TypeProperties {
pub const fn size(self) -> OpaqueSimValueSize {
let Self {
is_passive: _,
is_storable: _,
is_castable_from_bits: _,
bit_width,
sim_only_values_len,
} = self;
OpaqueSimValueSize {
bit_width,
sim_only_values_len,
}
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
@ -35,6 +67,8 @@ pub enum CanonicalType {
SyncReset(SyncReset),
Reset(Reset),
Clock(Clock),
PhantomConst(PhantomConst),
DynSimOnly(DynSimOnly),
}
impl fmt::Debug for CanonicalType {
@ -50,10 +84,30 @@ impl fmt::Debug for CanonicalType {
Self::SyncReset(v) => v.fmt(f),
Self::Reset(v) => v.fmt(f),
Self::Clock(v) => v.fmt(f),
Self::PhantomConst(v) => v.fmt(f),
Self::DynSimOnly(v) => v.fmt(f),
}
}
}
impl Serialize for CanonicalType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serde_impls::SerdeCanonicalType::from(*self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for CanonicalType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(serde_impls::SerdeCanonicalType::deserialize(deserializer)?.into())
}
}
impl CanonicalType {
pub fn type_properties(self) -> TypeProperties {
match self {
@ -67,6 +121,8 @@ impl CanonicalType {
CanonicalType::SyncReset(v) => v.type_properties(),
CanonicalType::Reset(v) => v.type_properties(),
CanonicalType::Clock(v) => v.type_properties(),
CanonicalType::PhantomConst(v) => v.type_properties(),
CanonicalType::DynSimOnly(v) => v.type_properties(),
}
}
pub fn is_passive(self) -> bool {
@ -81,6 +137,12 @@ impl CanonicalType {
pub fn bit_width(self) -> usize {
self.type_properties().bit_width
}
pub fn sim_only_values_len(self) -> usize {
self.type_properties().sim_only_values_len
}
pub fn size(self) -> OpaqueSimValueSize {
self.type_properties().size()
}
pub fn can_connect(self, rhs: Self) -> bool {
match self {
CanonicalType::UInt(lhs) => {
@ -143,8 +205,23 @@ impl CanonicalType {
};
lhs.can_connect(rhs)
}
CanonicalType::PhantomConst(lhs) => {
let CanonicalType::PhantomConst(rhs) = rhs else {
return false;
};
lhs.can_connect(rhs)
}
CanonicalType::DynSimOnly(lhs) => {
let CanonicalType::DynSimOnly(rhs) = rhs else {
return false;
};
lhs.can_connect(rhs)
}
}
}
pub(crate) fn as_serde_unexpected_str(self) -> &'static str {
serde_impls::SerdeCanonicalType::from(self).as_serde_unexpected_str()
}
}
pub trait MatchVariantAndInactiveScope: Sized {
@ -166,7 +243,7 @@ impl<T: 'static + Send + Sync> MatchVariantAndInactiveScope for MatchVariantWith
}
pub trait FillInDefaultedGenerics {
type Type: Type;
type Type;
fn fill_in_defaulted_generics(self) -> Self::Type;
}
@ -178,6 +255,22 @@ impl<T: Type> FillInDefaultedGenerics for T {
}
}
impl FillInDefaultedGenerics for usize {
type Type = usize;
fn fill_in_defaulted_generics(self) -> Self::Type {
self
}
}
impl<const V: usize> FillInDefaultedGenerics for ConstUsize<V> {
type Type = ConstUsize<V>;
fn fill_in_defaulted_generics(self) -> Self::Type {
self
}
}
mod sealed {
pub trait TypeOrDefaultSealed {}
pub trait BaseTypeSealed {}
@ -195,6 +288,34 @@ macro_rules! impl_base_type {
};
}
macro_rules! impl_base_type_serde {
($name:ident, $expected:literal) => {
impl Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.canonical().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match CanonicalType::deserialize(deserializer)? {
CanonicalType::$name(retval) => Ok(retval),
ty => Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Other(ty.as_serde_unexpected_str()),
&$expected,
)),
}
}
}
};
}
impl_base_type!(UInt);
impl_base_type!(SInt);
impl_base_type!(Bool);
@ -205,12 +326,24 @@ impl_base_type!(AsyncReset);
impl_base_type!(SyncReset);
impl_base_type!(Reset);
impl_base_type!(Clock);
impl_base_type!(PhantomConst);
impl_base_type!(DynSimOnly);
impl_base_type_serde!(Bool, "a Bool");
impl_base_type_serde!(Enum, "an Enum");
impl_base_type_serde!(Bundle, "a Bundle");
impl_base_type_serde!(AsyncReset, "an AsyncReset");
impl_base_type_serde!(SyncReset, "a SyncReset");
impl_base_type_serde!(Reset, "a Reset");
impl_base_type_serde!(Clock, "a Clock");
impl sealed::BaseTypeSealed for CanonicalType {}
impl BaseType for CanonicalType {}
pub trait TypeOrDefault<D: Type>: sealed::TypeOrDefaultSealed {
pub trait TypeOrDefault<D: Type>:
sealed::TypeOrDefaultSealed + Copy + Eq + Hash + fmt::Debug
{
type Type: Type;
fn get<F: FnOnce() -> D>(self, default: F) -> Self::Type;
}
@ -238,26 +371,48 @@ pub trait Type:
{
type BaseType: BaseType;
type MaskType: Type<MaskType = Self::MaskType>;
type SimValue: fmt::Debug + Clone + 'static + ToSimValueWithType<Self>;
type MatchVariant: 'static + Send + Sync;
type MatchActiveScope;
type MatchVariantAndInactiveScope: MatchVariantAndInactiveScope<
MatchVariant = Self::MatchVariant,
MatchActiveScope = Self::MatchActiveScope,
>;
MatchVariant = Self::MatchVariant,
MatchActiveScope = Self::MatchActiveScope,
>;
type MatchVariantsIter: Iterator<Item = Self::MatchVariantAndInactiveScope>
+ ExactSizeIterator
+ FusedIterator
+ DoubleEndedIterator;
#[track_caller]
fn match_variants(this: Expr<Self>, source_location: SourceLocation)
-> Self::MatchVariantsIter;
-> Self::MatchVariantsIter;
fn mask_type(&self) -> Self::MaskType;
fn canonical(&self) -> CanonicalType;
fn from_canonical(canonical_type: CanonicalType) -> Self;
fn source_location() -> SourceLocation;
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue;
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
);
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w>;
}
pub trait BaseType: Type<BaseType = Self> + sealed::BaseTypeSealed + Into<CanonicalType> {}
pub trait BaseType:
Type<
BaseType = Self,
MaskType: Serialize + DeserializeOwned,
SimValue: Serialize + DeserializeOwned,
> + sealed::BaseTypeSealed
+ Into<CanonicalType>
+ Serialize
+ DeserializeOwned
{
}
macro_rules! impl_match_variant_as_self {
() => {
@ -284,6 +439,7 @@ pub trait TypeWithDeref: Type {
impl Type for CanonicalType {
type BaseType = CanonicalType;
type MaskType = CanonicalType;
type SimValue = OpaqueSimValue;
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
match self {
@ -297,6 +453,8 @@ impl Type for CanonicalType {
CanonicalType::SyncReset(v) => v.mask_type().canonical(),
CanonicalType::Reset(v) => v.mask_type().canonical(),
CanonicalType::Clock(v) => v.mask_type().canonical(),
CanonicalType::PhantomConst(v) => v.mask_type().canonical(),
CanonicalType::DynSimOnly(v) => v.mask_type().canonical(),
}
}
fn canonical(&self) -> CanonicalType {
@ -308,9 +466,636 @@ impl Type for CanonicalType {
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue {
assert_eq!(self.type_properties().size(), opaque.size());
opaque.to_owned()
}
fn sim_value_clone_from_opaque(
&self,
value: &mut Self::SimValue,
opaque: OpaqueSimValueSlice<'_>,
) {
assert_eq!(self.type_properties().size(), opaque.size());
assert_eq!(value.size(), opaque.size());
value.clone_from_slice(opaque);
}
fn sim_value_to_opaque<'w>(
&self,
value: &Self::SimValue,
writer: OpaqueSimValueWriter<'w>,
) -> OpaqueSimValueWritten<'w> {
assert_eq!(self.type_properties().size(), writer.size());
assert_eq!(value.size(), writer.size());
writer.fill_cloned_from_slice(value.as_slice())
}
}
pub trait StaticType: Type {
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub struct OpaqueSimValueSizeRange {
pub bit_width: Range<usize>,
pub sim_only_values_len: Range<usize>,
}
impl OpaqueSimValueSizeRange {
pub fn start(&self) -> OpaqueSimValueSize {
OpaqueSimValueSize {
bit_width: self.bit_width.start,
sim_only_values_len: self.sim_only_values_len.start,
}
}
pub fn end(&self) -> OpaqueSimValueSize {
OpaqueSimValueSize {
bit_width: self.bit_width.end,
sim_only_values_len: self.sim_only_values_len.end,
}
}
pub fn is_empty(&self) -> bool {
let Self {
bit_width,
sim_only_values_len,
} = self;
bit_width.is_empty() && sim_only_values_len.is_empty()
}
}
impl From<Range<OpaqueSimValueSize>> for OpaqueSimValueSizeRange {
fn from(value: Range<OpaqueSimValueSize>) -> Self {
Self {
bit_width: value.start.bit_width..value.end.bit_width,
sim_only_values_len: value.start.sim_only_values_len..value.end.sim_only_values_len,
}
}
}
impl From<OpaqueSimValueSizeRange> for Range<OpaqueSimValueSize> {
fn from(value: OpaqueSimValueSizeRange) -> Self {
value.start()..value.end()
}
}
pub trait OpaqueSimValueSizeRangeBounds {
fn start_bound(&self) -> Bound<OpaqueSimValueSize>;
fn end_bound(&self) -> Bound<OpaqueSimValueSize>;
}
impl OpaqueSimValueSizeRangeBounds for OpaqueSimValueSizeRange {
fn start_bound(&self) -> Bound<OpaqueSimValueSize> {
Bound::Included(self.start())
}
fn end_bound(&self) -> Bound<OpaqueSimValueSize> {
Bound::Excluded(self.end())
}
}
impl<T: ?Sized + std::ops::RangeBounds<OpaqueSimValueSize>> OpaqueSimValueSizeRangeBounds for T {
fn start_bound(&self) -> Bound<OpaqueSimValueSize> {
std::ops::RangeBounds::start_bound(self).cloned()
}
fn end_bound(&self) -> Bound<OpaqueSimValueSize> {
std::ops::RangeBounds::end_bound(self).cloned()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub struct OpaqueSimValueSize {
pub bit_width: usize,
pub sim_only_values_len: usize,
}
impl OpaqueSimValueSize {
pub const fn from_bit_width(bit_width: usize) -> Self {
Self::from_bit_width_and_sim_only_values_len(bit_width, 0)
}
pub const fn from_bit_width_and_sim_only_values_len(
bit_width: usize,
sim_only_values_len: usize,
) -> Self {
Self {
bit_width,
sim_only_values_len,
}
}
pub const fn only_bit_width(self) -> Option<usize> {
if let Self {
bit_width,
sim_only_values_len: 0,
} = self
{
Some(bit_width)
} else {
None
}
}
pub const fn empty() -> Self {
Self {
bit_width: 0,
sim_only_values_len: 0,
}
}
pub const fn is_empty(self) -> bool {
let Self {
bit_width,
sim_only_values_len,
} = self;
bit_width == 0 && sim_only_values_len == 0
}
pub const fn checked_mul(self, factor: usize) -> Option<Self> {
let Some(bit_width) = self.bit_width.checked_mul(factor) else {
return None;
};
let Some(sim_only_values_len) = self.sim_only_values_len.checked_mul(factor) else {
return None;
};
Some(Self {
bit_width,
sim_only_values_len,
})
}
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
let Some(bit_width) = self.bit_width.checked_add(rhs.bit_width) else {
return None;
};
let Some(sim_only_values_len) = self
.sim_only_values_len
.checked_add(rhs.sim_only_values_len)
else {
return None;
};
Some(Self {
bit_width,
sim_only_values_len,
})
}
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
let Some(bit_width) = self.bit_width.checked_sub(rhs.bit_width) else {
return None;
};
let Some(sim_only_values_len) = self
.sim_only_values_len
.checked_sub(rhs.sim_only_values_len)
else {
return None;
};
Some(Self {
bit_width,
sim_only_values_len,
})
}
pub fn try_slice_range<R: OpaqueSimValueSizeRangeBounds>(
self,
range: R,
) -> Option<OpaqueSimValueSizeRange> {
let start = range.start_bound();
let end = range.end_bound();
let bit_width = try_slice_range(
(start.map(|v| v.bit_width), end.map(|v| v.bit_width)),
self.bit_width,
)?;
let sim_only_values_len = try_slice_range(
(
start.map(|v| v.sim_only_values_len),
end.map(|v| v.sim_only_values_len),
),
self.sim_only_values_len,
)?;
Some(OpaqueSimValueSizeRange {
bit_width,
sim_only_values_len,
})
}
pub fn slice_range<R: OpaqueSimValueSizeRangeBounds>(
self,
range: R,
) -> OpaqueSimValueSizeRange {
self.try_slice_range(range).expect("range out of bounds")
}
}
impl Mul<usize> for OpaqueSimValueSize {
type Output = OpaqueSimValueSize;
fn mul(self, rhs: usize) -> Self::Output {
self.checked_mul(rhs).expect("multiplication overflowed")
}
}
impl Mul<OpaqueSimValueSize> for usize {
type Output = OpaqueSimValueSize;
fn mul(self, rhs: OpaqueSimValueSize) -> Self::Output {
rhs.checked_mul(self).expect("multiplication overflowed")
}
}
impl Add for OpaqueSimValueSize {
type Output = OpaqueSimValueSize;
fn add(self, rhs: OpaqueSimValueSize) -> Self::Output {
rhs.checked_add(self).expect("addition overflowed")
}
}
impl Sub for OpaqueSimValueSize {
type Output = OpaqueSimValueSize;
fn sub(self, rhs: OpaqueSimValueSize) -> Self::Output {
rhs.checked_sub(self).expect("subtraction underflowed")
}
}
impl MulAssign<usize> for OpaqueSimValueSize {
fn mul_assign(&mut self, rhs: usize) {
*self = *self * rhs;
}
}
impl AddAssign for OpaqueSimValueSize {
fn add_assign(&mut self, rhs: OpaqueSimValueSize) {
*self = *self + rhs;
}
}
impl SubAssign for OpaqueSimValueSize {
fn sub_assign(&mut self, rhs: OpaqueSimValueSize) {
*self = *self - rhs;
}
}
impl Sum for OpaqueSimValueSize {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(OpaqueSimValueSize::empty(), Add::add)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct OpaqueSimValue {
bits: UIntValue,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
sim_only_values: Vec<DynSimOnlyValue>,
}
impl OpaqueSimValue {
pub fn empty() -> Self {
Self {
bits: UIntValue::new(Default::default()),
sim_only_values: Vec::new(),
}
}
pub fn with_capacity(capacity: OpaqueSimValueSize) -> Self {
Self {
bits: UIntValue::new(Arc::new(BitVec::with_capacity(capacity.bit_width))),
sim_only_values: Vec::with_capacity(capacity.sim_only_values_len),
}
}
pub fn size(&self) -> OpaqueSimValueSize {
OpaqueSimValueSize {
bit_width: self.bits.width(),
sim_only_values_len: self.sim_only_values.len(),
}
}
pub fn is_empty(&self) -> bool {
self.size().is_empty()
}
pub fn bit_width(&self) -> usize {
self.bits.width()
}
pub fn bits(&self) -> &UIntValue {
&self.bits
}
pub fn bits_mut(&mut self) -> &mut UIntValue {
&mut self.bits
}
pub fn into_bits(self) -> UIntValue {
self.bits
}
pub fn from_bits(bits: UIntValue) -> Self {
Self {
bits,
sim_only_values: Vec::new(),
}
}
pub fn from_bitslice(v: &BitSlice) -> Self {
Self::from_bitslice_and_sim_only_values(v, Vec::new())
}
pub fn from_bitslice_and_sim_only_values(
bits: &BitSlice,
sim_only_values: Vec<DynSimOnlyValue>,
) -> Self {
Self {
bits: UIntValue::new(Arc::new(bits.to_bitvec())),
sim_only_values,
}
}
pub fn from_bits_and_sim_only_values(
bits: UIntValue,
sim_only_values: Vec<DynSimOnlyValue>,
) -> Self {
Self {
bits,
sim_only_values,
}
}
pub fn into_parts(self) -> (UIntValue, Vec<DynSimOnlyValue>) {
let Self {
bits,
sim_only_values,
} = self;
(bits, sim_only_values)
}
pub fn parts_mut(&mut self) -> (&mut UIntValue, &mut Vec<DynSimOnlyValue>) {
let Self {
bits,
sim_only_values,
} = self;
(bits, sim_only_values)
}
pub fn sim_only_values(&self) -> &[DynSimOnlyValue] {
&self.sim_only_values
}
pub fn sim_only_values_mut(&mut self) -> &mut Vec<DynSimOnlyValue> {
&mut self.sim_only_values
}
pub fn as_slice(&self) -> OpaqueSimValueSlice<'_> {
OpaqueSimValueSlice {
bits: self.bits.bits(),
sim_only_values: &self.sim_only_values,
}
}
pub fn slice<R: OpaqueSimValueSizeRangeBounds>(&self, range: R) -> OpaqueSimValueSlice<'_> {
self.as_slice().slice(range)
}
pub fn rewrite_with<F>(&mut self, target_size: OpaqueSimValueSize, f: F)
where
F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand
{
OpaqueSimValueWriter::rewrite_with(target_size, self, f);
}
pub fn clone_from_slice(&mut self, slice: OpaqueSimValueSlice<'_>) {
let OpaqueSimValueSlice {
bits,
sim_only_values,
} = slice;
self.bits.bits_mut().copy_from_bitslice(bits);
self.sim_only_values.clone_from_slice(sim_only_values);
}
pub fn extend_from_slice(&mut self, slice: OpaqueSimValueSlice<'_>) {
let OpaqueSimValueSlice {
bits,
sim_only_values,
} = slice;
self.bits.bitvec_mut().extend_from_bitslice(bits);
self.sim_only_values.extend_from_slice(sim_only_values);
}
}
impl<'a> Extend<OpaqueSimValueSlice<'a>> for OpaqueSimValue {
fn extend<T: IntoIterator<Item = OpaqueSimValueSlice<'a>>>(&mut self, iter: T) {
let Self {
bits,
sim_only_values,
} = self;
let bits = bits.bitvec_mut();
for slice in iter {
bits.extend_from_bitslice(slice.bits);
sim_only_values.extend_from_slice(slice.sim_only_values);
}
}
}
impl Extend<OpaqueSimValue> for OpaqueSimValue {
fn extend<T: IntoIterator<Item = OpaqueSimValue>>(&mut self, iter: T) {
let Self {
bits,
sim_only_values,
} = self;
let bits = bits.bitvec_mut();
for value in iter {
bits.extend_from_bitslice(value.bits().bits());
sim_only_values.extend_from_slice(value.sim_only_values());
}
}
}
impl<T: Type<SimValue = OpaqueSimValue>> ToSimValueWithType<T> for OpaqueSimValue {
fn to_sim_value_with_type(&self, ty: T) -> SimValue<T> {
SimValue::from_value(ty, self.clone())
}
fn into_sim_value_with_type(self, ty: T) -> SimValue<T> {
SimValue::from_value(ty, self)
}
}
#[derive(Copy, Clone, Debug)]
pub struct OpaqueSimValueSlice<'a> {
bits: &'a BitSlice,
sim_only_values: &'a [DynSimOnlyValue],
}
impl<'a> Default for OpaqueSimValueSlice<'a> {
fn default() -> Self {
Self::empty()
}
}
impl<'a> OpaqueSimValueSlice<'a> {
pub fn from_parts(bits: &'a BitSlice, sim_only_values: &'a [DynSimOnlyValue]) -> Self {
Self {
bits,
sim_only_values,
}
}
pub fn from_bitslice(bits: &'a BitSlice) -> Self {
Self::from_parts(bits, &[])
}
pub fn empty() -> Self {
Self {
bits: BitSlice::empty(),
sim_only_values: &[],
}
}
pub fn size(self) -> OpaqueSimValueSize {
OpaqueSimValueSize {
bit_width: self.bit_width(),
sim_only_values_len: self.sim_only_values_len(),
}
}
pub fn is_empty(self) -> bool {
self.size().is_empty()
}
pub fn bit_width(self) -> usize {
self.bits.len()
}
pub fn bits(self) -> &'a BitSlice {
self.bits
}
pub fn sim_only_values(self) -> &'a [DynSimOnlyValue] {
self.sim_only_values
}
pub fn sim_only_values_len(self) -> usize {
self.sim_only_values.len()
}
pub fn to_owned(self) -> OpaqueSimValue {
OpaqueSimValue::from_bitslice_and_sim_only_values(self.bits, self.sim_only_values.to_vec())
}
pub fn slice<R: OpaqueSimValueSizeRangeBounds>(self, range: R) -> OpaqueSimValueSlice<'a> {
let start = range.start_bound();
let end = range.end_bound();
let bits_range = slice_range(
(start.map(|v| v.bit_width), end.map(|v| v.bit_width)),
self.bit_width(),
);
let sim_only_values_range = slice_range(
(start.map(|v| v.bit_width), end.map(|v| v.bit_width)),
self.sim_only_values_len(),
);
Self {
bits: &self.bits[bits_range],
sim_only_values: &self.sim_only_values[sim_only_values_range],
}
}
pub fn split_at(self, index: OpaqueSimValueSize) -> (Self, Self) {
let bits = self.bits.split_at(index.bit_width);
let sim_only_values = self.sim_only_values.split_at(index.sim_only_values_len);
(
Self {
bits: bits.0,
sim_only_values: sim_only_values.0,
},
Self {
bits: bits.1,
sim_only_values: sim_only_values.1,
},
)
}
}
#[derive(Debug)]
pub struct OpaqueSimValueWriter<'a> {
bits: &'a mut BitSlice,
sim_only_values: &'a mut Vec<DynSimOnlyValue>,
sim_only_values_range: std::ops::Range<usize>,
}
#[derive(Debug)]
pub struct OpaqueSimValueWritten<'a> {
_phantom: PhantomData<&'a ()>,
}
impl<'a> OpaqueSimValueWriter<'a> {
pub fn sim_only_values_range(&self) -> std::ops::Range<usize> {
self.sim_only_values_range.clone()
}
pub fn rewrite_with<F>(target_size: OpaqueSimValueSize, value: &mut OpaqueSimValue, f: F)
where
F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand
{
let OpaqueSimValueWritten {
_phantom: PhantomData,
} = f(OpaqueSimValueWriter::rewrite_helper(target_size, value));
}
pub(crate) fn rewrite_helper(
target_size: OpaqueSimValueSize,
value: &'a mut OpaqueSimValue,
) -> Self {
let (bits, sim_only_values) = value.parts_mut();
let OpaqueSimValueSize {
bit_width,
sim_only_values_len,
} = target_size;
let bits = bits.bitvec_mut();
bits.resize(bit_width, false);
sim_only_values.truncate(sim_only_values_len);
sim_only_values.reserve_exact(sim_only_values_len - sim_only_values.len());
Self {
bits,
sim_only_values,
sim_only_values_range: 0..sim_only_values_len,
}
}
pub fn size(&self) -> OpaqueSimValueSize {
OpaqueSimValueSize {
bit_width: self.bit_width(),
sim_only_values_len: self.sim_only_values_len(),
}
}
pub fn bit_width(&self) -> usize {
self.bits.len()
}
pub fn sim_only_values_len(&self) -> usize {
self.sim_only_values_range.len()
}
pub fn is_empty(&self) -> bool {
self.size().is_empty()
}
pub fn fill_cloned_from_slice(
self,
slice: OpaqueSimValueSlice<'_>,
) -> OpaqueSimValueWritten<'a> {
assert_eq!(self.size(), slice.size());
let Self {
bits,
sim_only_values,
sim_only_values_range,
} = self;
bits.copy_from_bitslice(slice.bits);
let (clone_from_src, clone_src) = slice.sim_only_values.split_at(
(sim_only_values.len() - sim_only_values_range.start).min(slice.sim_only_values.len()),
);
sim_only_values[sim_only_values_range.start..][..clone_from_src.len()]
.clone_from_slice(clone_from_src);
sim_only_values.extend_from_slice(clone_src);
OpaqueSimValueWritten {
_phantom: PhantomData,
}
}
pub fn fill_with_bits_with<F: FnOnce(&mut BitSlice)>(self, f: F) -> OpaqueSimValueWritten<'a> {
assert!(self.size().only_bit_width().is_some());
let Self {
bits,
sim_only_values,
sim_only_values_range,
} = self;
f(bits);
assert_eq!(sim_only_values.len(), sim_only_values_range.end);
OpaqueSimValueWritten {
_phantom: PhantomData,
}
}
pub fn fill_with_zeros(self) -> OpaqueSimValueWritten<'a> {
assert!(
self.size().only_bit_width().is_some(),
"can't fill things other than bits with zeros",
);
self.fill_with_bits_with(|bits| bits.fill(false))
}
pub fn fill_prefix_with<F>(&mut self, prefix_size: OpaqueSimValueSize, f: F)
where
F: for<'b> FnOnce(OpaqueSimValueWriter<'b>) -> OpaqueSimValueWritten<'b>, // 'b is used as a brand
{
let OpaqueSimValueSize {
bit_width,
sim_only_values_len,
} = prefix_size;
assert!(bit_width <= self.bit_width());
assert!(sim_only_values_len <= self.sim_only_values_len());
let next_start = self.sim_only_values_range.start + sim_only_values_len;
let OpaqueSimValueWritten {
_phantom: PhantomData,
} = f(OpaqueSimValueWriter {
bits: &mut self.bits[..bit_width],
sim_only_values: self.sim_only_values,
sim_only_values_range: self.sim_only_values_range.start..next_start,
});
assert!(self.sim_only_values.len() >= next_start);
self.bits = &mut mem::take(&mut self.bits)[bit_width..];
self.sim_only_values_range.start = next_start;
}
}
pub trait StaticType: Type + Default {
const TYPE: Self;
const MASK_TYPE: Self::MaskType;
const TYPE_PROPERTIES: TypeProperties;
@ -328,6 +1113,6 @@ impl<T: Type> Index<T> for AsMaskWithoutGenerics {
type Output = T::MaskType;
fn index(&self, ty: T) -> &Self::Output {
Interned::<_>::into_inner(Intern::intern_sized(ty.mask_type()))
Interned::into_inner(Intern::intern_sized(ty.mask_type()))
}
}

View file

@ -0,0 +1,135 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
array::Array,
bundle::{Bundle, BundleType},
clock::Clock,
enum_::{Enum, EnumType},
int::{Bool, SInt, UInt},
intern::Interned,
phantom_const::{PhantomConstCanonicalValue, PhantomConstValue},
prelude::PhantomConst,
reset::{AsyncReset, Reset, SyncReset},
sim::value::DynSimOnly,
ty::{BaseType, CanonicalType},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub(crate) struct SerdePhantomConst<T>(pub T);
impl<T: ?Sized + PhantomConstValue> Serialize for SerdePhantomConst<Interned<T>> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for SerdePhantomConst<Interned<T>> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize_value(deserializer).map(Self)
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "CanonicalType")]
pub(crate) enum SerdeCanonicalType<
ArrayElement = CanonicalType,
ThePhantomConst = SerdePhantomConst<Interned<PhantomConstCanonicalValue>>,
> {
UInt {
width: usize,
},
SInt {
width: usize,
},
Bool,
Array {
element: ArrayElement,
len: usize,
},
Enum {
variants: Interned<[crate::enum_::EnumVariant]>,
},
Bundle {
fields: Interned<[crate::bundle::BundleField]>,
},
AsyncReset,
SyncReset,
Reset,
Clock,
PhantomConst(ThePhantomConst),
DynSimOnly(DynSimOnly),
}
impl<ArrayElement, PhantomConstInner> SerdeCanonicalType<ArrayElement, PhantomConstInner> {
pub(crate) fn as_serde_unexpected_str(&self) -> &'static str {
match self {
Self::UInt { .. } => "a UInt",
Self::SInt { .. } => "a SInt",
Self::Bool => "a Bool",
Self::Array { .. } => "an Array",
Self::Enum { .. } => "an Enum",
Self::Bundle { .. } => "a Bundle",
Self::AsyncReset => "an AsyncReset",
Self::SyncReset => "a SyncReset",
Self::Reset => "a Reset",
Self::Clock => "a Clock",
Self::PhantomConst(_) => "a PhantomConst",
Self::DynSimOnly(_) => "a SimOnlyValue",
}
}
}
impl<T: BaseType> From<T> for SerdeCanonicalType {
fn from(ty: T) -> Self {
let ty: CanonicalType = ty.into();
match ty {
CanonicalType::UInt(ty) => Self::UInt { width: ty.width() },
CanonicalType::SInt(ty) => Self::SInt { width: ty.width() },
CanonicalType::Bool(Bool {}) => Self::Bool,
CanonicalType::Array(ty) => Self::Array {
element: ty.element(),
len: ty.len(),
},
CanonicalType::Enum(ty) => Self::Enum {
variants: ty.variants(),
},
CanonicalType::Bundle(ty) => Self::Bundle {
fields: ty.fields(),
},
CanonicalType::AsyncReset(AsyncReset {}) => Self::AsyncReset,
CanonicalType::SyncReset(SyncReset {}) => Self::SyncReset,
CanonicalType::Reset(Reset {}) => Self::Reset,
CanonicalType::Clock(Clock {}) => Self::Clock,
CanonicalType::PhantomConst(ty) => Self::PhantomConst(SerdePhantomConst(ty.get())),
CanonicalType::DynSimOnly(ty) => Self::DynSimOnly(ty),
}
}
}
impl From<SerdeCanonicalType> for CanonicalType {
fn from(ty: SerdeCanonicalType) -> Self {
match ty {
SerdeCanonicalType::UInt { width } => Self::UInt(UInt::new(width)),
SerdeCanonicalType::SInt { width } => Self::SInt(SInt::new(width)),
SerdeCanonicalType::Bool => Self::Bool(Bool),
SerdeCanonicalType::Array { element, len } => Self::Array(Array::new(element, len)),
SerdeCanonicalType::Enum { variants } => Self::Enum(Enum::new(variants)),
SerdeCanonicalType::Bundle { fields } => Self::Bundle(Bundle::new(fields)),
SerdeCanonicalType::AsyncReset => Self::AsyncReset(AsyncReset),
SerdeCanonicalType::SyncReset => Self::SyncReset(SyncReset),
SerdeCanonicalType::Reset => Self::Reset(Reset),
SerdeCanonicalType::Clock => Self::Clock(Clock),
SerdeCanonicalType::PhantomConst(value) => {
Self::PhantomConst(PhantomConst::new_interned(value.0))
}
SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value),
}
}
}

View file

@ -1,11 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub(crate) mod alternating_cell;
mod const_bool;
mod const_cmp;
mod const_usize;
mod misc;
mod scoped_ref;
pub(crate) mod streaming_read_utf8;
mod test_hasher;
// allow easily switching the hasher crate-wide for testing
#[cfg(feature = "unstable-test-hasher")]
pub type DefaultBuildHasher = test_hasher::DefaultBuildHasher;
#[cfg(not(feature = "unstable-test-hasher"))]
pub(crate) type DefaultBuildHasher = hashbrown::DefaultHashBuilder;
pub(crate) type HashMap<K, V> = hashbrown::HashMap<K, V, DefaultBuildHasher>;
pub(crate) type HashSet<T> = hashbrown::HashSet<T, DefaultBuildHasher>;
#[doc(inline)]
pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool};
@ -23,5 +35,14 @@ pub use scoped_ref::ScopedRef;
#[doc(inline)]
pub use misc::{
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter,
SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest,
SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix,
os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty,
serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range,
};
pub(crate) use misc::{InternedStrCompareAsStr, chain, copy_le_bytes_to_bitslice};
pub mod job_server;
pub mod prefix_sum;
pub mod ready_valid;

View file

@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::util::DebugAsDisplay;
use std::{
cell::{Cell, UnsafeCell},
fmt,
};
pub(crate) trait AlternatingCellMethods {
fn unique_to_shared(&mut self);
fn shared_to_unique(&mut self);
}
#[derive(Copy, Clone, Debug)]
enum State {
Unique,
Shared,
Locked,
}
pub(crate) struct AlternatingCell<T: ?Sized> {
state: Cell<State>,
value: UnsafeCell<T>,
}
impl<T: ?Sized + fmt::Debug + AlternatingCellMethods> fmt::Debug for AlternatingCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("AlternatingCell")
.field(
self.try_share()
.as_ref()
.map(|v| -> &dyn fmt::Debug { v })
.unwrap_or(&DebugAsDisplay("<...>")),
)
.finish()
}
}
impl<T: ?Sized> AlternatingCell<T> {
pub(crate) const fn new_shared(value: T) -> Self
where
T: Sized,
{
Self {
state: Cell::new(State::Shared),
value: UnsafeCell::new(value),
}
}
pub(crate) const fn new_unique(value: T) -> Self
where
T: Sized,
{
Self {
state: Cell::new(State::Unique),
value: UnsafeCell::new(value),
}
}
pub(crate) fn is_unique(&self) -> bool {
matches!(self.state.get(), State::Unique)
}
pub(crate) fn is_shared(&self) -> bool {
matches!(self.state.get(), State::Shared)
}
pub(crate) fn into_inner(self) -> T
where
T: Sized,
{
self.value.into_inner()
}
pub(crate) fn try_share(&self) -> Option<&T>
where
T: AlternatingCellMethods,
{
match self.state.get() {
State::Shared => {}
State::Unique => {
struct Locked<'a>(&'a Cell<State>);
impl Drop for Locked<'_> {
fn drop(&mut self) {
self.0.set(State::Shared);
}
}
self.state.set(State::Locked);
let lock = Locked(&self.state);
// Safety: state is Locked, so nothing else will
// access value while calling unique_to_shared.
unsafe { &mut *self.value.get() }.unique_to_shared();
drop(lock);
}
State::Locked => return None,
}
// Safety: state is Shared so nothing will create any mutable
// references until the returned reference's lifetime expires.
Some(unsafe { &*self.value.get() })
}
#[track_caller]
pub(crate) fn share(&self) -> &T
where
T: AlternatingCellMethods,
{
let Some(retval) = self.try_share() else {
panic!("`share` called recursively");
};
retval
}
pub(crate) fn unique(&mut self) -> &mut T
where
T: AlternatingCellMethods,
{
match self.state.get() {
State::Shared => {
self.state.set(State::Unique);
self.value.get_mut().shared_to_unique();
}
State::Unique => {}
State::Locked => unreachable!(),
}
self.value.get_mut()
}
}

View file

@ -1,5 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{DeserializeOwned, Error, Unexpected},
};
use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr};
mod sealed {
@ -9,7 +13,17 @@ mod sealed {
/// # Safety
/// the only implementation is `ConstBool<Self::VALUE>`
pub unsafe trait GenericConstBool:
sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync
sealed::Sealed
+ Copy
+ Ord
+ Hash
+ Default
+ Debug
+ 'static
+ Send
+ Sync
+ Serialize
+ DeserializeOwned
{
const VALUE: bool;
}
@ -30,6 +44,32 @@ unsafe impl<const VALUE: bool> GenericConstBool for ConstBool<VALUE> {
const VALUE: bool = VALUE;
}
impl<const VALUE: bool> Serialize for ConstBool<VALUE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
VALUE.serialize(serializer)
}
}
impl<'de, const VALUE: bool> Deserialize<'de> for ConstBool<VALUE> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = bool::deserialize(deserializer)?;
if value == VALUE {
Ok(ConstBool)
} else {
Err(D::Error::invalid_value(
Unexpected::Bool(value),
&if VALUE { "true" } else { "false" },
))
}
}
}
pub trait ConstBoolDispatchTag {
type Type<Select: GenericConstBool>;
}

View file

@ -1,5 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{DeserializeOwned, Error, Unexpected},
};
use std::{fmt::Debug, hash::Hash};
mod sealed {
@ -8,7 +12,17 @@ mod sealed {
/// the only implementation is `ConstUsize<Self::VALUE>`
pub trait GenericConstUsize:
sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync
sealed::Sealed
+ Copy
+ Ord
+ Hash
+ Default
+ Debug
+ 'static
+ Send
+ Sync
+ Serialize
+ DeserializeOwned
{
const VALUE: usize;
}
@ -27,3 +41,29 @@ impl<const VALUE: usize> sealed::Sealed for ConstUsize<VALUE> {}
impl<const VALUE: usize> GenericConstUsize for ConstUsize<VALUE> {
const VALUE: usize = VALUE;
}
impl<const VALUE: usize> Serialize for ConstUsize<VALUE> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
VALUE.serialize(serializer)
}
}
impl<'de, const VALUE: usize> Deserialize<'de> for ConstUsize<VALUE> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = usize::deserialize(deserializer)?;
if value == VALUE {
Ok(ConstUsize)
} else {
Err(D::Error::invalid_value(
Unexpected::Unsigned(value as u64),
&&*VALUE.to_string(),
))
}
}
}

View file

@ -0,0 +1,157 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use ctor::{ctor, dtor};
use jobslot::Client;
use std::{
ffi::OsString,
io, mem,
num::NonZeroUsize,
sync::{Mutex, MutexGuard},
};
#[ctor]
static CLIENT: Mutex<Option<Option<Client>>> = unsafe { Mutex::new(Some(Client::from_env())) };
#[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;
}
_ => {}
}
}
}
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,
}
static STATE: Mutex<State> = Mutex::new(State {
obtained_count: 0,
waiting_count: 0,
});
#[derive(Debug)]
pub struct AcquiredJob {
client: Client,
}
impl AcquiredJob {
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();
state.waiting_count -= 1;
state
}
}
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> {
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 &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
}
}
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

@ -3,7 +3,11 @@
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},
};
@ -94,9 +98,15 @@ pub fn interned_bit(v: bool) -> Interned<BitSlice> {
RETVAL.get_or_init(|| [bits![0; 1].intern(), bits![1; 1].intern()])[v as usize]
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone)]
pub struct BitSliceWriteWithBase<'a>(pub &'a BitSlice);
impl<'a> Debug for BitSliceWriteWithBase<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:#x}")
}
}
impl BitSliceWriteWithBase<'_> {
fn fmt_with_base<const BITS_PER_DIGIT: usize, const UPPER_CASE: bool>(
self,
@ -155,3 +165,490 @@ impl fmt::UpperHex for BitSliceWriteWithBase<'_> {
self.fmt_with_base::<4, true>(f)
}
}
#[derive(Clone, Default)]
pub struct RcWriter(Rc<Cell<Vec<u8>>>);
impl Debug for RcWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.borrow_impl(|buf| {
f.debug_tuple("RcWriter")
.field(&DebugAsDisplay(format_args!("b\"{}\"", buf.escape_ascii())))
.finish()
})
}
}
impl RcWriter {
fn borrow_impl<R>(&self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
let buf = Cell::take(&self.0);
struct PutBackOnDrop<'a> {
buf: Vec<u8>,
this: &'a RcWriter,
}
impl Drop for PutBackOnDrop<'_> {
fn drop(&mut self) {
self.this.0.set(std::mem::take(&mut self.buf));
}
}
let mut buf = PutBackOnDrop { buf, this: self };
f(&mut buf.buf)
}
pub fn borrow<R>(&mut self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
self.borrow_impl(f)
}
pub fn take(&mut self) -> Vec<u8> {
Cell::take(&self.0)
}
}
impl std::io::Write for RcWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.borrow(|v| v.extend_from_slice(buf));
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
macro_rules! chain {
() => {
std::iter::empty()
};
($first:expr $(, $rest:expr)* $(,)?) => {
{
let retval = IntoIterator::into_iter($first);
$(let retval = Iterator::chain(retval, $rest);)*
retval
}
};
}
pub(crate) use chain;
pub fn try_slice_range<R: RangeBounds<usize>>(range: R, size: usize) -> Option<Range<usize>> {
let start = match range.start_bound() {
Bound::Included(start) => *start,
Bound::Excluded(start) => start.checked_add(1)?,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(end) => end.checked_add(1)?,
Bound::Excluded(end) => *end,
Bound::Unbounded => size,
};
(start <= end && end <= size).then_some(start..end)
}
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
}
}
pub(crate) fn copy_le_bytes_to_bitslice(
dest: &mut BitSlice<usize, Lsb0>,
bytes: &[u8],
msb_fill: bool,
) {
let (chunks, remainder) = bytes.as_chunks();
let mut filled_to = 0;
for (i, chunk) in chunks.iter().enumerate() {
if let Some(start_bit_index) = i.checked_mul(usize::BITS as usize)
&& start_bit_index < dest.len()
{
let end_bit_index = start_bit_index
.saturating_add(usize::BITS as usize)
.min(dest.len());
let bit_len = end_bit_index - start_bit_index;
let chunk = usize::from_le_bytes(*chunk);
dest[start_bit_index..end_bit_index].copy_from_bitslice(&chunk.view_bits()[..bit_len]);
filled_to = end_bit_index;
} else {
break;
}
}
if !remainder.is_empty() {
if let Some(start_bit_index) = chunks.len().checked_mul(usize::BITS as usize)
&& start_bit_index < dest.len()
{
let end_bit_index = start_bit_index
.saturating_add(usize::BITS as usize)
.min(dest.len());
let bit_len = end_bit_index - start_bit_index;
let mut chunk = [if msb_fill { !0 } else { 0 }; _];
chunk[..remainder.len()].copy_from_slice(remainder);
let chunk = usize::from_le_bytes(chunk);
dest[start_bit_index..end_bit_index].copy_from_bitslice(&chunk.view_bits()[..bit_len]);
filled_to = end_bit_index;
}
}
dest[filled_to..].fill(msb_fill);
}

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