Compare commits

...
Author SHA1 Message Date
Wilson Snyder 5ed3b46dba No need to revert verilatedos.h 2026-09-15 13:40:10 -04:00
Wilson Snyder 6da2657b56 Revert "Fix wide enum names (#8344)"
This reverts commit c65f312d6e.
2026-09-15 12:22:58 -04:00
Geza Lore 0f3a84643b CI: Fix pr-notification deletion 2026-09-15 17:01:45 +01:00
Marco Bartoli 253f51f1dc Fix %p format on class members (#8360) 2026-09-15 09:43:12 -04:00
Mike Murunov 270c528afd Fix missing implicit net for bufif/notif/nmos/pmos data input (#8353) (#8354) 2026-09-14 17:31:51 -04:00
Marco Bartoli c65f312d6e Fix wide enum names (#8344) 2026-09-14 16:29:42 -04:00
Dragon-Git 377865e2a0 Fix class-qualified self reference to a typedef in a parameterized class (#8348) (#8349) 2026-09-14 10:54:15 -04:00
Artur Bieniek 6a57cf10f4 Obtimize NBA assertions by keeping all writers before readers (#8326)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-14 10:29:27 -04:00
CatalpaEel 96e6bf02c4 Optimize wide four-state constant operations in V3Number (#8333) 2026-09-14 10:12:30 -04:00
Marco Bartoli 0bad0faca6 Fix ignoring cross_auto_bin_max instead of hard error (#8340) 2026-09-13 14:59:42 -04:00
Wilson Snyder 547eb6a836 Remove deprecated --structs-packed. 2026-09-13 14:54:34 -04:00
Wilson Snyder e172e94b30 Fix pattern type matching against nested aggregates (#7304).
Fixes #7304.
2026-09-13 14:48:07 -04:00
Wilson Snyder e871c3657e Commentary: Changes update 2026-09-13 14:00:43 -04:00
Wilson Snyder 3200946bb9 Tests: Improve t_string_to_bit 2026-09-13 13:41:53 -04:00
Marco Bartoli fb8bcdc0df Fix format of signed enums (#8338) 2026-09-13 13:34:10 -04:00
Marco Bartoli 47ead4e89d Support logic and set operations on binsof (#8306) 2026-09-13 12:59:10 -04:00
Kristof Marien 298922d887 Fix parameterized class nested in a parameterized interface (#8328) (#8329) 2026-09-12 15:31:10 -04:00
Wilson Snyder 4fedb791ed Add -fno-dead-members and optimize dead class member functions. (#8330) 2026-09-12 15:22:42 -04:00
Wilson Snyder 3bf0018354 Optimize away __Vconfigure if empty function 2026-09-12 14:36:45 -04:00
Wilson Snyder d523500b9b Optimize UVM reporting functions as branch-unlikely 2026-09-12 13:54:15 -04:00
Wilson Snyder a8a50bd8a0 Commentary 2026-09-12 12:22:14 -04:00
Wilson Snyder c7b1151f13 Fix unlinked VlProcess error 2026-09-12 11:00:29 -04:00
Wilson Snyder 73957647cc Add IEEE reference to some warnings 2026-09-12 09:06:09 -04:00
Wilson Snyder d048b62c13 Fix marking inherited virtual functions (part 2) 2026-09-12 08:55:45 -04:00
Wilson Snyder c8a75e9b4d Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-12 08:38:51 -04:00
Wilson Snyder 012b647833 Fix marking inherited virtual functions 2026-09-12 08:32:53 -04:00
Wilson Snyder 005b33faa1 Commentary: Changes update 2026-09-12 08:21:01 -04:00
Todd Strader 0c574618d0 Support VPI interface references (#8081) 2026-09-12 05:38:20 -04:00
Marco Bartoli 4aeb3dbc5a Fix wide typed pattern formatting primitives (#8325) 2026-09-11 18:11:14 -04:00
Wilson Snyder bd32c0e4c1 Internals: Refactor V3Dead ftask removal to use graph (#8322) 2026-09-11 06:52:25 -04:00
Wilson Snyder 72523c3be1 Tests: Fix coverage.dat in wrong directory 2026-09-10 21:21:04 -04:00
Andrii 2421c7b73b Fix property and sequence line coverage (#8314)
Signed-off-by: Andrii Andrieiev <[email protected]>
2026-09-10 18:00:24 -04:00
Nick Brereton dada720bca Internals: Inline VerilatedContext::assertCtlGet (#8320) 2026-09-10 15:48:58 -04:00
Artur Bieniek b06bd46dc9 Avoid duplicate lookup in NFA property recursion guard (#8321)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 20:46:54 +02:00
Artur Bieniek 5b24226b3b Avoid unnecessary per-vertex allocations in NFA lowering (#8319)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 20:21:37 +02:00
Artur Bieniek 09fd15af7b Narrow NBA shadow copies for partial writes (#8266)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 11:38:46 -04:00
Kamil Danecki ee792e235a Support rand dynamic arrays used inside with clause of randomize (#8246)
Signed-off-by: Kamil Danecki <[email protected]>
Signed-off-by: Igor Zaworski <[email protected]>
2026-09-10 10:40:30 -04:00
Artur Bieniek 557c21a337 Support multiplicity in SVA shapes that use NFA rings (#8267)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-10 10:39:35 -04:00
Wilson Snyder 24692ad3ef Internals: Change user1-4 storage to uint64 (#8317) 2026-09-10 06:45:54 -04:00
Wilson Snyder 3c8630744d Internals: V3Dead refactor and test update. No functional change. 2026-09-10 06:43:47 -04:00
Wilson Snyder 0dd45238a7 Internals: Update verilog_format flags for recent Verible 2026-09-10 06:39:43 -04:00
Geza Lore 1d2781091c Internals: Color densely in V3Graph::weaklyConnected, and return the count
No algorithm depends on this, so can be changed, later improvement will.
No functional change.
2026-09-09 21:11:23 +01:00
Geza Lore 5bf74e34b1 Internals: Move SplitReorderBaseVisitor next to SplitVisitor
Pure code movement to minimize change in subsequent patch.
2026-09-09 13:02:42 +01:00
Wilson Snyder 599f8b8e35 Tests: Reformat some tests to mostly verilog-format standard. No test functional change. 2026-09-08 21:26:11 -04:00
Geza Lore da39cea1bd Tests: Add -fno-exceptions to t_verilated_all (#8312)
Fixes #6771
2026-09-09 00:33:36 +01:00
Drew Risinger 3e874fb4d1 Fix CMake to call Verilator perl wrapper (#8269) (#8270) (#8311)
This reimplements commit c1c19494c4.
2026-09-08 18:29:09 -04:00
Geza Lore e4f210eb38 Optimize Dfg algorithms with an open addressing hash table (#8307)
This patch introduces V3HashTable.h, which defines an open addressing,
linear probing hash table. The table implement the public V3HashSet and
V3HashMap templates, which are generic containers. The benefit of this
over std::unordered_map and std::unordered_set is far better memory
locality during lookup. (The STL containers use chaining and require a
new heap allocation for every insertion, similarly probing involves
pointer chasing on collisions).

The new data structure is use in V3DfgCache, and V3DfgCse and yields a
significant speed improvement of those passes on large designs.
2026-09-08 23:21:13 +01:00
Wilson Snyder 5bb0e9b216 Commentary: Changes update 2026-09-08 18:09:39 -04:00
Wilson Snyder 5ce1abc2b4 Tests: Fix cleaning t_a6_examples (#8270 comment) 2026-09-08 18:07:12 -04:00
Geza Lore b835308a31 Optimize AstNodeDType::skipRefp to not track the type chain (#8310) 2026-09-08 18:00:08 -04:00
Geza Lore fa30985cda Internals: Exclude UDEBUGONLY from branch coverage 2026-09-08 21:49:10 +01:00
Kornel Uriasz f8c296ce2e Support unique constraint inside std::randomize (#8241)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-08 11:08:34 -04:00
Kornel Uriasz 47b9fba5e5 Fix array-size constraint on arrays inside base cass (#8219)
Signed-off-by: Kornel Uriasz <[email protected]>
2026-09-08 09:11:02 -04:00
Martijn Wobbes d70d1e5cd0 Fix DECLFILENAME warning on nested declarations (#8302) 2026-09-08 07:15:37 -04:00
Artur Bieniek 76df8b4a7e Fix multidimensional packed array type handling (#8235)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-07 14:04:17 -04:00
Matthew Ballance 048d18a84f Add covergroup runtime registry (#8134) 2026-09-07 14:02:03 -04:00
Artur Bieniek cd2475fbd4 Support non-overlapped property implications correctly (#8268)
Signed-off-by: Artur Bieniek <[email protected]>
2026-09-07 13:50:13 -04:00
Igor Zaworski 9c6126b834 [#99994] Fix of detecting non-inlined awaits in V3Delayed.cpp (#7982)
Signed-off-by: Igor Zaworski <[email protected]>
2026-09-07 13:46:30 -04:00
dependabot[bot] 2bffeec8b9 CI: Bump the everything group with 2 updates (#8304) 2026-09-07 10:58:37 -04:00
Marco Frank 63bcfff80a Fix array slice crash when used as a bare value (#5132) (#8262) 2026-09-06 20:57:44 -04:00
Marco Bartoli 9da900a5e2 Add initial support for solo binsof (#8298) 2026-09-06 19:51:31 -04:00
Geza Lore bbbb9c3e7c Optimize away netlist teardown when about to exit (#8294) 2026-09-06 18:08:34 -04:00
Geza Lore 849cc5aeca Internals: Move wrapTop before V3Coverage (#8291) 2026-09-06 17:34:40 -04:00
github action 8213c0ea98 Apply 'make format' [ci skip] 2026-09-06 21:19:40 +00:00
Geza Lore 62bec1bffc Internals: Use auto in UASSERT_SELFTEST (#8299) 2026-09-06 17:16:08 -04:00
Marco Bartoli 0b824f160f Fix escape strings in pattern formatting (#8297) 2026-09-06 13:22:03 -04:00
Nick Brereton 4d1f208cf0 Fix assignment pattern default fill of an array of unpacked structs (#8289) 2026-09-06 11:44:44 -04:00
Geza Lore 7cf8c5cca6 Optimize more selects in DFG (#8292) 2026-09-05 19:57:26 -04:00
Kamil Danecki 73708c55c3 Support nested array and associative array element member access in constraint 1/4 - pre-cleanup (#8237)
Signed-off-by: Kamil Danecki <[email protected]>
2026-09-05 15:33:24 -04:00
Sumanth KadiyalaandSumanth Kadiyala 3990376c57 Internals: resolved incorrect array bounds guarding (#8224)
Co-authored-by: Sumanth Kadiyala <[email protected]>
2026-09-05 15:33:06 -04:00
Geza Lore d04647f459 Fix library method argument references (#8252)
Each VCMethod now carries a signature describing the access required of
the references passed as arguments to the call. 'r' if the argument is
read, 'w' if it is fully assigned so the old value does not matter, 'm'
if it is modified (or only conditionally assigned), with a trailing '+'
repeating the preceding entry for all remaining arguments.  Signatures
are validated at compile time, and V3Broken checks the arguments of
every AstCMethodHard against them.

The incorrect references this found and that are easy to fix are
repaired in this patch. "TODO" marks method that are currently broken or
not yet fit the scheme, these will be fixed in follow up patches.

Also renames SCHED_COMMIT and SCHED_ENQUEUE to NBA_COMMIT and
NBA_ENQUEUE, and remove unused methods.
2026-09-05 19:48:07 +01:00
Matthew Ballance 338c6c2885 Fix covergroup-internal references multi-threaded ordering (#7779) (#8255) 2026-09-05 14:08:03 -04:00
Aditya Shevade a6e21e0eb7 Fix phased solve...before diversity (#8189) (#8194) 2026-09-05 14:06:57 -04:00
Geza Lore f2fe2b6f2e Optimize Dfg synthesis of simple continuous assignments (#8286) 2026-09-05 13:07:53 -04:00
Geza Lore bcf613c4fe Optimize redundant memoization in DFG CSE (#8285) 2026-09-05 12:13:11 -04:00
Geza Lore b907a1a75d Optimize DFG vertex iteration callbacks (#8284) 2026-09-05 12:12:46 -04:00
Geza Lore 87108ff943 Optimize redundant vertex cache lookups in DFG peephole (#8283) 2026-09-05 11:13:30 -04:00
Geza Lore fa59f8f302 Optimize DFG synthesis symbol table ordering (#8282) 2026-09-05 11:13:09 -04:00
Wilson Snyder 35c1a244a8 Internals: Introduce Ast{class}::Super and cleanup. No functional change intended (but possible if was wrong base used earlier) 2026-09-05 10:45:39 -04:00
Wilson Snyder 3493e409b8 Internals: Sort Ast{class} functions in src/V3AstNodes.cpp, and enforce. No functional change. 2026-09-05 10:38:18 -04:00
Wilson Snyder 4f557a4b0c Internals: Reorganize non-Ast{class} functions in src/V3AstNodes.cpp. No functional change. 2026-09-05 10:32:27 -04:00
Wilson Snyder 13c60bfede Commentary: Changes update 2026-09-05 10:10:10 -04:00
Wilson Snyder d6441c9cd3 Change JSON dumps to suppress empty strings, etc 2026-09-05 10:09:25 -04:00
Wilson Snyder 32695cf8be Tests: Enforce Ast*::dump/dumpJson member dumps 2026-09-05 10:06:14 -04:00
Wilson Snyder 5ab54f3c01 Commentary. (Restore CONTRIBUTORS from earlier rev) 2026-09-05 10:05:47 -04:00
Wilson Snyder c1c19494c4 Revert 81fafe237e: Fix CMake to call Verilator perl wrapper (#8269 revert) (#8270 revert) 2026-09-05 09:59:16 -04:00
Wilson Snyder e7b30e214e Internals: Add missing classes to dumps(), enforce with new test. No simulation change. (#8293) 2026-09-05 09:50:13 -04:00
Drew Risinger 81fafe237e Fix CMake to call Verilator perl wrapper (#8269) (#8270) 2026-09-05 09:47:15 -04:00
Wilson Snyder 80de081af0 devel release 2026-09-05 08:45:33 -04:00
421 changed files with 20722 additions and 8270 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ jobs:
type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }}
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
+2 -2
View File
@@ -69,11 +69,11 @@ jobs:
- name: Deploy to GitHub Pages
id: deploy-1
continue-on-error: true
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
- name: Deploy to GitHub Pages (retry)
id: deploy-2
if: steps.deploy-1.outcome == 'failure'
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
notify:
name: Notify
+1 -1
View File
@@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
project(
Verilator
VERSION 5.052
VERSION 5.053
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)
+50 -3
View File
@@ -10,6 +10,53 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.053 devel
==========================
**Other:**
* Add covergroup runtime registry (#8134). [Matthew Ballance]
* Add initial support for solo `binsof` (#8298). [Marco Bartoli]
* Add `-fno-dead-members` and optimize dead class member functions (#8830).
* Remove deprecated `--structs-packed`.
* Change JSON dumps to suppress empty strings, etc.
* Support VPI interface references (#8081). [Todd Strader]
* Support nested array and associative array element member access in constraint 1/4 - pre-cleanup (#8237). [Kamil Danecki, Antmicro Ltd.]
* Support unique constraint inside std::randomize (#8241). [Kornel Uriasz, Antmicro Ltd.]
* Support rand dynamic arrays used inside `with` clause of randomize (#8246). [Kamil Danecki, Antmicro Ltd.]
* Support multiplicity in SVA shapes that use NFA rings (#8267). [Artur Bieniek, Antmicro Ltd.]
* Support non-overlapped property implications correctly (#8268). [Artur Bieniek, Antmicro Ltd.]
* Support logic and set operations on binsof (#8306). [Marco Bartoli]
* Optimize NBA shadow copies for partial writes (#8266). [Artur Bieniek, Antmicro Ltd.]
* Optimize DFG synthesis symbol table ordering (#8282). [Geza Lore, Testorrent USA, Inc.]
* Optimize redundant vertex cache lookups in DFG peephole (#8283). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG vertex iteration callbacks (#8284). [Geza Lore, Testorrent USA, Inc.]
* Optimize redundant memoization in DFG CSE (#8285). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG synthesis of simple continuous assignments (#8286). [Geza Lore, Testorrent USA, Inc.]
* Optimize more selects in DFG (#8292). [Geza Lore, Testorrent USA, Inc.]
* Optimize netlist teardown when about to exit (#8294). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG algorithms with an open addressing hash table (#8307). [Geza Lore, Testorrent USA, Inc.]
* Optimize data-type type chain recursion (#8310). [Geza Lore, Testorrent USA, Inc.]
* Optimize unnecessary per-vertex allocations in NFA lowering (#8319). [Artur Bieniek, Antmicro Ltd.]
* Optimize duplicate lookup in NFA property recursion guard (#8321). [Artur Bieniek, Antmicro Ltd.]
* Fix array slice crash when used as a bare value (#5132) (#8262). [Marco Frank]
* Fix pattern type matching against nested aggregates (#7304).
* Fix covergroup-internal references multi-threaded ordering (#7779) (#8255). [Matthew Ballance]
* Fix detecting non-inlined awaits in V3Delayed.cpp (#7982). [Igor Zaworski, Antmicro Ltd.]
* Fix phased `solve...before` diversity (#8189) (#8194). [Aditya Shevade]
* Fix array-size constraint on arrays inside base class (#8219). [Kornel Uriasz, Antmicro Ltd.]
* Fix multidimensional packed array type handling (#8235). [Artur Bieniek, Antmicro Ltd.]
* Fix library method argument references (#8252). [Geza Lore, Testorrent USA, Inc.]
* Fix CMake to call Verilator Perl wrapper (#8269) (#8270) (#8311). [Drew Risinger]
* Fix assignment pattern default fill of an array of unpacked structs (#8289). [Nick Brereton]
* Fix escape strings in pattern formatting (#8297). [Marco Bartoli]
* Fix DECLFILENAME warning on nested declarations (#8302). [Martijn Wobbes]
* Fix property and sequence line coverage (#8314). [Andrii, Antmicro Ltd.]
* Fix wide typed pattern formatting primitives (#8325). [Marco Bartoli]
* Fix parameterized class nested in a parameterized interface (#8328) (#8329). [Kristof Marien]
* Fix format of signed enums (#8338). [Marco Bartoli]
Verilator 5.052 2026-09-05
==========================
@@ -1855,7 +1902,7 @@ Verilator 5.016 2023-09-16
* Optimize Verilator executable size by refactoring error reporting routines (#4446). [Anthony Donlon]
* Optimize Verilation runtime pointers and graphs (#4396) (#4397) (#4398). [Krzysztof Bieganski, Antmicro Ltd]
* Optimize preparations towards multithreaded Verilation (#4291) (#4463) (#4476) (#4477) (#4479). [Kamil Rakoczy, Antmicro Ltd]
* Fix Windows filename format, etc (#3873) (#4421). [Anthony Donlon].
* Fix Windows filename format, etc (#3873) (#4421). [Anthony Donlon]
* Fix t_dist_cppstyle Perl performance issue (#4085). [Srinivasan Venkataramanan]
* Fix using type in parameterized classes without #() (#4281) (#4440). [Anthony Donlon]
* Fix false INFINITELOOP on forever..mailbox.get() (#4323). [Srinivasan Venkataramanan]
@@ -3640,7 +3687,7 @@ Verilator 3.880 2015-12-19
* Fix model restore crash. (#1013) [Jason McMullan]
* Fix arrayed instances to unpacked of same size. (#1015) [Varun Koyyalagunta]
* Fix slices of unpacked arrays with non-zero LSBs.
* Fix ternary operation with unpacked array. (#1017) [Varun Koyyalagunta].
* Fix ternary operation with unpacked array. (#1017) [Varun Koyyalagunta]
Verilator 3.878 2015-11-01
@@ -3730,7 +3777,7 @@ Verilator 3.870 2015-02-12
* Suppress COMBDLY when inside always_latch. (#864) [Iztok Jeras]
* Support cast operator with expression size. (#865) [Iztok Jeras]
* Add warning on slice selection out of bounds. (#875) [Cong Van Nguyen].
* Add warning on slice selection out of bounds. (#875) [Cong Van Nguyen]
* Fix member select error broke in 3.868. (#867) [Iztok Jeras]
* Fix $sccanf from string. (#866) [David Pierce]
* Fix VM_PARALLEL_BUILDS broke in 3.868. (#870) [Hiroki Honda]
+1
View File
@@ -747,6 +747,7 @@ FASTCOV_OPT += BROKEN_RTN
FASTCOV_OPT += NUM_ASSERT
FASTCOV_OPT += SELF_CHECK
FASTCOV_OPT += UASSERT
FASTCOV_OPT += UDEBUGONLY
FASTCOV_OPT += UINFO
FASTCOV_OPT += assert
FASTCOV_OPT += 'if (VL_UNCOVERABLE'
+1 -1
View File
@@ -158,7 +158,7 @@ NEXTHIST_TEMPLATE
# 'pr-notification' for a single run, as the artifacts endpoint lists
# artifacts across all run attempts, and a re-run uploads a new one while
# keeping the previous attempt's artifact.
ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts" --jq '.artifacts[] | select(.name == "pr-notification") | .id')
ARTIFACT_IDS=$(gh api "repos/{owner}/{repo}/actions/runs/${RUN_ID}/artifacts?name=pr-notification" --jq '.artifacts[].id')
# Delete them all, so we only notify once
for ARTIFACT_ID in ${ARTIFACT_IDS}; do
+1 -1
View File
@@ -12,7 +12,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.052 2026-09-05],
AC_INIT([Verilator],[5.053 devel],
[https://verilator.org],
[verilator],[https://verilator.org])
+6
View File
@@ -46,6 +46,7 @@ BRDR LIFE
Brian Li
Cameron Kirk
Cameron Waite
CatalpaEel
Chih-Mao Chen
Chris Bachhuber
Chris Randall
@@ -74,6 +75,7 @@ Dominick Grochowina
Don Williamson
Dragon-Git
Drew Ranck
Drew Risinger
Drew Taussig
Driss Hafdi
Edgar E. Iglesias
@@ -182,6 +184,7 @@ Keith Colbert
Kevin Kiningham
Kevin Nygaard
Kornel Uriasz
Kristof Marien
Kritik Bhimani
Krzysztof Bieganski
Krzysztof Boronski
@@ -205,11 +208,13 @@ Maciej Sobkowski
Marcel Chang
Marco Bartoli
Marco Brambilla
Marco Frank
Marco Widmer
Mariusz Glebocki
Markus Krause
Marlon James
Marshal Qiao
Martijn Wobbes
Martin Schmidt
Martin Stadler
Mateusz Gancarz
@@ -224,6 +229,7 @@ Michael Rogenmoser
Michal Czyz
Michaël Lefebvre
Miguel Perez Andrade
Mike Murunov
Mike Popoloski
Miodrag Milanović
Mladen Slijepcevic
-8
View File
@@ -18,14 +18,6 @@ C++14 compiler support
the Ubuntu LTS versions of GCC and clang use C++20 by default, estimated
May 2028.)
`--structs-packed` option
The :vlopt:`--structs-packed` option was introduced when Verilator was
first implementing unpacked structs. That feature has been stable now
for multiple years, so :vlopt:`--structs-packed` should no longer be
used. Thus :vlopt:`--structs-packed` will change to a no-operation flag
and the related :option:`UNPACKED` warning will never be issued no
sooner than September 2026.
tcmalloc support
Verilator currently supports the default malloc, tcmalloc, or jemalloc.
As jemalloc has better performance, support for tcmalloc may be removed
+6 -5
View File
@@ -706,6 +706,8 @@ Summary:
.. option:: -fno-dead-cells
.. option:: -fno-dead-methods
.. option:: -fno-dedup
.. option:: -fno-dfg
@@ -1790,12 +1792,11 @@ Summary:
.. option:: --structs-packed
Deprecated; discontinue use of this option.
Removed in 5.054.
Converts all unpacked structures to packed structures, and issues an
:option:`UNPACKED` warning. Specifying this option allows for backward
compatibility with versions before Verilator 5.006, when Verilator would
always pack unpacked structures.
Converted all unpacked structures to packed structures, Specifying this
option allowed for backward compatibility with versions before Verilator
5.006, when Verilator would always pack unpacked structures.
.. option:: -sv
+1 -1
View File
@@ -174,7 +174,7 @@ once, after ``configure``:
# Install dependencies
sudo apt install python3-pip
# Create Python virutal environment in .venv:
# Create Python virtual environment in .venv:
make venv
# Or alternatively, to put it somewhere else:
+7 -7
View File
@@ -592,6 +592,10 @@ List Of Warnings
``covergroup``, ``coverpoint``, and coverage options, and the
construct was ignored.
This includes crosses whose normal-bin Cartesian product exceeds
``2**32 - 1`` tuples. The limit is checked during Verilation for both
automatic and explicit cross bins.
Disabling the :option:`UNSUPPORTED` error also disables this warning.
Ignoring this warning may make Verilator ignore lint checking on the
@@ -2499,14 +2503,10 @@ List Of Warnings
.. option:: UNPACKED
Warns that unpacked structs and unions are not supported because
:vlopt:`--structs-packed` was used, or by up through version 5.004.
Historical, never issued since version 5.004.
Ignoring this warning will make Verilator treat the structure as packed,
which may make Verilator simulations differ from other simulators. This
downgrading may also result in what would typically be a legal unpacked
struct/array inside an unpacked struct/array becoming an illegal
unpacked struct/array inside a packed struct/array.
Warned that unpacked structs and unions were not supported, or disabled
by the since-removed `--structs-packed` option.
.. option:: UNSATCONSTR
+3
View File
@@ -627,6 +627,7 @@ benchmarking
biguint
binToOneHot
bindir
binsof
biops
bisonpre
bitOpTree
@@ -947,6 +948,7 @@ makefile
makefiles
malloc
manpages
memoization
metacomment
metacomments
miree
@@ -1182,6 +1184,7 @@ systemc
taskify
tcmalloc
tcmalloc
teardown
tenghtt
testbench
testbenches
+109 -37
View File
@@ -1178,8 +1178,12 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
} else if (formatAttr == VL_VFORMATATTR_STRING) {
thingp = va_arg(ap, std::string*);
if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override
} else if (formatAttr == VL_VFORMATATTR_ENUM) {
} else if (formatAttr == VL_VFORMATATTR_ENUM
|| formatAttr == VL_VFORMATATTR_ENUM_SIGNED) {
// Always <= VL_QUADSIZE; emit uses non-ENUM format for wider enums
const int numericAttr = formatAttr == VL_VFORMATATTR_ENUM_SIGNED
? VL_VFORMATATTR_SIGNED
: VL_VFORMATATTR_UNSIGNED;
lbits = va_arg(ap, int);
ld = VL_VA_ARG_Q_(ap, lbits);
strwide.resize(2);
@@ -1192,6 +1196,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
enump = va_arg(ap, std::string*);
if (enump && !enump->empty()) {
formatAttr = (fmt == 'p') ? VL_VFORMATATTR_COMPLEX : VL_VFORMATATTR_STRING;
if (fmt == 'd') formatAttr = numericAttr;
thingp = const_cast<std::string*>(enump);
} else if (fmt == 'p' && widthSet && width == 0) {
output += "'h";
@@ -1201,7 +1206,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
if (fmt == 'p') width = 0;
widthSet = true;
fmt = 'd';
formatAttr = VL_VFORMATATTR_UNSIGNED;
formatAttr = numericAttr;
}
if (widthSet && width == 0) {
while (lsb && !VL_BITISSET_W(lwp, lsb)) --lsb;
@@ -1282,7 +1287,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
output += t_tmp;
} else if (formatAttr == VL_VFORMATATTR_STRING) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += '"' + *strp + '"';
output += VL_TO_STRING(*strp);
} else if (formatAttr == VL_VFORMATATTR_COMPLEX) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += *strp;
@@ -2488,6 +2493,30 @@ std::string VL_TO_STRING(QData lhs) {
std::string VL_TO_STRING(double lhs) {
return VL_SFORMATF_N_NX("%g", 1, VL_VFORMATATTR_DOUBLE, lhs);
}
std::string VL_TO_STRING(const std::string& obj) VL_PURE {
std::string out{"\""};
out.reserve(obj.size() + 2);
for (const unsigned char ch : obj) {
switch (ch) {
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
default:
if (std::isprint(ch)) {
out += static_cast<char>(ch);
} else {
out += '\\';
out += static_cast<char>('0' + ((ch >> 6) & 3));
out += static_cast<char>('0' + ((ch >> 3) & 7));
out += static_cast<char>('0' + (ch & 7));
}
break;
}
}
return out + '"';
}
std::string VL_TO_STRING_W(int words, const WDataInP obj) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, words * VL_EDATASIZE, obj);
}
@@ -3102,15 +3131,6 @@ bool VerilatedContext::assertOnGet(VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE {
return assertCtlGet(VerilatedAssertCtlQuery::ASSERT_CTL_ON, type, directive);
}
uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE {
// Place the directive bits at each selected assertion type's 3-bit group.
uint32_t mask = 0;
for (int i = 0; i < std::numeric_limits<VerilatedAssertType_t>::digits; ++i) {
if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH);
}
return mask;
}
void VerilatedContext::assertOnSet(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE {
if (assertCtlsLocked()) return;
@@ -3178,26 +3198,6 @@ void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t typ
.c_str());
}
}
uint32_t
VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE {
const uint32_t mask = assertOnMask(type, directive);
if (!mask) return 0;
switch (query) { // LCOV_EXCL_BR_LINE
case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_KILL:
assert(mask && (mask & (mask - 1)) == 0);
return m_s.m_assertKill[VL_CLOG2_I(mask)];
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS:
return (m_s.m_assertPassOnVacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS:
return (m_s.m_assertPassOnNonvacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0;
default: // LCOV_EXCL_START
VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query");
VL_UNREACHABLE;
} // LCOV_EXCL_STOP
}
void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
m_s.m_calcUnusedSigs = flag;
@@ -3745,11 +3745,17 @@ void VerilatedContext::statsPrintSummary() VL_MT_UNSAFE {
// VerilatedContext:: Methods - scopes
void VerilatedContext::scopesDump() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_impdatap->m_nameMutex};
VL_PRINTF_MT(" scopesDump:\n");
for (const auto& i : m_impdatap->m_nameMap) {
const VerilatedScope* const scopep = i.second;
scopep->scopeDump();
{
const VerilatedLockGuard lock{m_impdatap->m_nameMutex};
VL_PRINTF_MT(" scopesDump:\n");
for (const auto& i : m_impdatap->m_nameMap) {
const VerilatedScope* const scopep = i.second;
scopep->scopeDump();
}
}
{
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
for (const auto& i : m_impdatap->m_ifaceRefMap) i.second.ifaceRefDump();
}
VL_PRINTF_MT("\n");
}
@@ -3779,6 +3785,31 @@ const VerilatedScopeNameMap* VerilatedContext::scopeNameMap() VL_MT_SAFE {
return &(impp()->m_impdatap->m_nameMap);
}
void VerilatedContextImp::ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE {
// Slow ok - called once/interface-reference at construction
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
m_impdatap->m_ifaceRefMap.emplace(ifaceRef.fullname(), ifaceRef);
}
void VerilatedContextImp::ifaceRefErase(const std::string& fullname,
const VerilatedScope* scopep) VL_MT_SAFE {
// Slow ok - called once/interface-reference at destruction
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
const auto it = m_impdatap->m_ifaceRefMap.find(fullname);
// Models sharing an instance name collide on the key; only erase our own,
// so tearing one down leaves another's live reference registered
if (it != m_impdatap->m_ifaceRefMap.end() && it->second.scopep() == scopep) {
m_impdatap->m_ifaceRefMap.erase(it);
}
}
const VerilatedIfaceRef*
VerilatedContext::ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT {
// Thread safe only assuming this is called only after model construction completed
const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex};
const auto& it = m_impdatap->m_ifaceRefMap.find(namep);
if (VL_UNLIKELY(it == m_impdatap->m_ifaceRefMap.end())) return nullptr;
return &it->second;
}
//======================================================================
// VerilatedContext:: Methods - trace
@@ -4312,6 +4343,41 @@ void VerilatedScope::scopesConstructFromTable(const VlScopeTableEntry* entp, siz
}
}
// Prefix with the model instance name, as VerilatedScope's constructor does
static std::string vl_ifaceRefFullname(const VerilatedSyms* symsp, const char* suffixp) {
const char* const prefixp = symsp->name();
std::string out{prefixp};
if (*prefixp && *suffixp) out += '.';
out += suffixp;
return out;
}
void VerilatedScope::ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE {
// Use the model's own context; at destruction threadContextp() may be another's
VerilatedContextImp* const impp = symsp->_vm_contextp__->impp();
uint8_t* const base = reinterpret_cast<uint8_t*>(symsp);
for (size_t i = 0; i < n; ++i) {
const VlIfaceRefTableEntry& e = entp[i];
const VerilatedScope* const scopep
= *reinterpret_cast<VerilatedScope**>(base + e.ptrOffset);
impp->ifaceRefInsert(
VerilatedIfaceRef{scopep, e.namep, vl_ifaceRefFullname(symsp, e.suffixp), e.modportp});
}
}
void VerilatedScope::ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n,
const VerilatedSyms* symsp) VL_MT_UNSAFE {
VerilatedContextImp* const impp = symsp->_vm_contextp__->impp();
uint8_t* const base = reinterpret_cast<uint8_t*>(const_cast<VerilatedSyms*>(symsp));
for (size_t i = 0; i < n; ++i) {
const VlIfaceRefTableEntry& e = entp[i];
const VerilatedScope* const scopep
= *reinterpret_cast<VerilatedScope**>(base + e.ptrOffset);
impp->ifaceRefErase(vl_ifaceRefFullname(symsp, e.suffixp), scopep);
}
}
VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam,
VerilatedVarType vltype, int vlflags, int udims,
uint32_t entSize...) VL_MT_UNSAFE {
@@ -4457,6 +4523,12 @@ void VerilatedScope::scopeDump() const {
}
}
void VerilatedIfaceRef::ifaceRefDump() const VL_MT_SAFE_POSTINIT {
VL_PRINTF_MT(" IFACEREF %p: %s -> %s", this, fullname(), scopep()->name());
if (hasModport()) VL_PRINTF_MT(".%s", modport());
VL_PRINTF_MT("\n");
}
void VerilatedHierarchy::add(const VerilatedScope* fromp, const VerilatedScope* top) {
VerilatedImp::hierarchyAdd(fromp, top);
}
+89 -6
View File
@@ -100,6 +100,9 @@ class VerilatedFstC;
class VerilatedFstSc;
class VerilatedScope;
class VerilatedScopeNameMap;
class VerilatedIfaceRef;
class VerilatedIfaceRefMap;
struct VlIfaceRefTableEntry;
template <typename, typename>
class VerilatedTrace;
class VerilatedTraceBaseC;
@@ -109,6 +112,7 @@ class VerilatedVarNameMap;
class VerilatedVcd;
class VerilatedVcdC;
class VerilatedVcdSc;
class VlCovRegistry;
//=========================================================================
// Basic types
@@ -479,8 +483,8 @@ private:
= ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits<VerilatedAssertType_t>::digits
+ 1;
// Build the assertion-control bit mask for the given assertion x directive types.
static uint32_t assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE;
static inline uint32_t assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE;
static constexpr size_t ASSERT_CONTROL_SLOT_COUNT = ASSERT_ON_WIDTH - 1;
// No termination request has stamped m_finishPendingTime yet
static constexpr uint64_t TIME_UNSET = ~0ULL;
@@ -590,6 +594,9 @@ protected:
std::unique_ptr<VerilatedVirtualBase> m_executionProfiler;
// Coverage access
std::unique_ptr<VerilatedVirtualBase> m_coveragep; // Pointer for coveragep()
// Covergroup type/instance nodes. Covergroup data is always collected,
// independent of whether coverage data is recorded (--coverage).
std::unique_ptr<VerilatedVirtualBase> m_covergroupsp; // Pointer for covergroupRegistryp()
// File I/O
// Not serialized
@@ -639,8 +646,8 @@ public:
VerilatedAssertDirectiveType_t directives) VL_MT_SAFE;
/// Get assertion-control runtime state. Boolean queries return 0/1, Kill returns
/// the generation count.
uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE;
inline uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE;
/// Return if calculating of unused signals (for traces)
bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; }
/// Enable calculation of unused signals (for traces)
@@ -659,6 +666,8 @@ public:
/// Return VerilatedCovContext, allocate if needed
/// Note if get unresolved reference then likely forgot to link verilated_cov.cpp
VerilatedCovContext* coveragep() VL_MT_SAFE;
/// Returns VlCovRegistry. Allocated on-demand
VlCovRegistry* covergroupRegistryp() VL_MT_SAFE;
/// Return debug level
static inline int debug() VL_MT_SAFE; /// Set debug level
/// Debug is currently global, but for forward compatibility have a per-context method
@@ -868,6 +877,9 @@ public:
const VerilatedScope* scopeFind(const char* namep) const VL_MT_SAFE;
const VerilatedScopeNameMap* scopeNameMap() VL_MT_SAFE;
// Internal: Find interface reference by fully qualified path
const VerilatedIfaceRef* ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT;
// Internal: Serialization setup
static constexpr size_t serialized1Size() VL_PURE { return sizeof(m_s); }
void* serialized1Ptr() VL_MT_UNSAFE { return &m_s; }
@@ -897,6 +909,32 @@ public: // But for internal use only
virtual const char* name() const = 0;
};
// An interface reference port, and the concrete interface it is connected to.
// Used for VPI; references are not scopes, so are not in VerilatedScopeNameMap.
class VerilatedIfaceRef final {
const VerilatedScope* m_scopep = nullptr; // Concrete interface referred to
const char* m_namep = ""; // Name of the reference port
// Fully qualified path; owned, as the instance name prefix is set at construction
std::string m_fullname;
const char* m_modportp = ""; // Modport name, or "" if none
public:
VerilatedIfaceRef() = default;
VerilatedIfaceRef(const VerilatedScope* scopep, const char* namep, const std::string& fullname,
const char* modportp)
: m_scopep{scopep}
, m_namep{namep}
, m_fullname{fullname}
, m_modportp{modportp} {}
~VerilatedIfaceRef() = default;
// ACCESSORS
const VerilatedScope* scopep() const VL_MT_SAFE_POSTINIT { return m_scopep; }
const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; }
const char* fullname() const VL_MT_SAFE_POSTINIT { return m_fullname.c_str(); }
const char* modport() const VL_MT_SAFE_POSTINIT { return m_modportp; }
bool hasModport() const VL_MT_SAFE_POSTINIT { return m_modportp[0] != '\0'; }
void ifaceRefDump() const VL_MT_SAFE_POSTINIT;
};
//===========================================================================
// Verilator scope information class
// Used for internal VPI implementation, and introspection into scopes
@@ -908,8 +946,9 @@ public:
enum Type : uint8_t {
SCOPE_MODULE,
SCOPE_OTHER,
SCOPE_PACKAGE
}; // Type of a scope, currently only module and package are interesting
SCOPE_PACKAGE,
SCOPE_INTERFACE
}; // Type of a scope, currently only module, package and interface are interesting
private:
// Fastpath:
VerilatedSyms* const m_symsp; // Symbol table
@@ -942,6 +981,10 @@ public: // But internals only - called from verilated modules, VerilatedSyms
void varsInsertFromTable(const VlVarTableEntry* entp, size_t n, void* basep) VL_MT_UNSAFE;
static void scopesConstructFromTable(const VlScopeTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE;
static void ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n,
VerilatedSyms* symsp) VL_MT_UNSAFE;
static void ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n,
const VerilatedSyms* symsp) VL_MT_UNSAFE;
// ACCESSORS
const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; }
const char* identifier() const VL_MT_SAFE_POSTINIT { return m_identifierp; }
@@ -958,6 +1001,15 @@ public: // But internals only - called from verilated modules, VerilatedSyms
VerilatedContext* contextp() const { return m_symsp->_vm_contextp__; }
};
// One interface reference, consumed by VerilatedScope::ifaceRefsInsertFromTable()
struct VlIfaceRefTableEntry final {
uint32_t ptrOffset; // offsetof of the referred-to __Vscopep_* member within the Syms object
const char* namep; // Name of the reference port
// Path within the model; as VlScopeTableEntry::namep, instance name prepended at construction
const char* suffixp;
const char* modportp; // Modport name, or "" if none
};
// One scope, consumed by VerilatedScope::scopesConstructFromTable(); replaces
// per-scope 'new VerilatedScope{...}' statements, which compiles faster at scale.
struct VlScopeTableEntry final {
@@ -1276,5 +1328,36 @@ void VerilatedContext::timeprecision(int value) VL_MT_SAFE {
#endif
}
// Defined here, not in-class: VL_CLOG2_I / VL_FATAL_MT (verilated_funcs.h) are not yet in scope
uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types,
VerilatedAssertDirectiveType_t directives) VL_PURE {
// Place the directive bits at each selected assertion type's 3-bit group.
uint32_t mask = 0;
for (int i = 0; i < std::numeric_limits<VerilatedAssertType_t>::digits; ++i) {
if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH);
}
return mask;
}
uint32_t
VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type,
VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE {
const uint32_t mask = assertOnMask(type, directive);
if (!mask) return 0;
switch (query) { // LCOV_EXCL_BR_LINE
case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_KILL:
assert(mask && (mask & (mask - 1)) == 0);
return m_s.m_assertKill[VL_CLOG2_I(mask)];
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS:
return (m_s.m_assertPassOnVacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS:
return (m_s.m_assertPassOnNonvacuous & mask) != 0;
case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0;
default: // LCOV_EXCL_START
VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query");
VL_UNREACHABLE;
} // LCOV_EXCL_STOP
}
#undef VERILATOR_VERILATED_H_INTERNAL_
#endif // Guard
+396 -38
View File
@@ -27,12 +27,13 @@
// This file is compiled whenever covergroups are used, with or without
// "verilator --coverage" (see V3Global::verilatedCppFiles). Bin counts are
// members of the covergroup objects themselves, so sampling, bin naming, and
// coverage queries such as get_inst_coverage() all work with no coverage
// database present. VL_COVER_INSERT does not copy a count; it hands the
// database the address of a counter to read at write time. Only that
// publication step needs verilated_cov.cpp, which is compiled solely under
// --coverage, so only the registerBins() bodies are gated on VM_COVERAGE.
// owned by the covergroup instance nodes in the VerilatedContext's registry, so
// sampling, bin naming, and coverage queries such as get_inst_coverage() all
// work with no coverage database present. VL_COVER_INSERT does not copy a
// count; it hands the database the address of a counter the registry owns and
// reads it at write time. Only that publication step needs the database, so
// only the registerBins() bodies -- and this include -- are gated on
// VM_COVERAGE.
#if VM_COVERAGE
#include "verilated_cov.h"
#endif
@@ -114,79 +115,436 @@ void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cp
m_file = file;
m_line = line;
m_col = col;
m_dims = dims;
m_cps.assign(cps, cps + dims);
m_cpBinCounts.resize(dims);
assert(dims == m_dims);
// Accumulate in 64 bits so the overflow check itself cannot overflow.
uint64_t product = 1;
uint64_t product = m_numAutoBins ? 1 : 0;
for (uint32_t d = 0; d < dims; ++d) {
m_cpBinCounts[d] = cps[d]->normalBinCount();
product *= m_cpBinCounts[d];
m_dimensionsp[d] = {cps[d], nullptr, cps[d]->normalBinCount(), 1};
product *= m_dimensionsp[d].bins;
if (VL_UNLIKELY(product > UINT32_MAX)) { // LCOV_EXCL_START
VL_FATAL_MT(file, line, "", "Cross has too many auto bins to represent");
} // LCOV_EXCL_STOP
}
m_numAutoBins = static_cast<uint32_t>(product);
assert(product == m_numAutoBins);
// stride[d] = product of the Normal bin counts of all dimensions after d.
// Counts down with an offset so the unsigned index never wraps below zero.
m_stride.assign(dims, 1);
for (uint32_t d = dims; d > 1; --d) m_stride[d - 2] = m_stride[d - 1] * m_cpBinCounts[d - 1];
m_flatCounts.assign(m_numAutoBins, 0);
for (uint32_t d = dims; d > 1; --d) {
m_dimensionsp[d - 2].stride = m_dimensionsp[d - 1].stride * m_dimensionsp[d - 1].bins;
}
}
void VlCoverCross::iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx) {
const uint32_t hits = cps[dim]->hitCount();
const uint32_t* const list = cps[dim]->hitList();
void VlCoverCross::addBin(std::initializer_list<uint64_t> selection, const char* namep,
const char* filep, int line, int col) {
if (!m_numAutoBins) return; // An empty product creates no cross bin.
Explicit& data = *m_explicitp;
const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0);
assert(selection.size() == words);
assert(data.numBins < data.bins.size());
uint64_t* const selectionp = data.selectionp + static_cast<uint64_t>(data.numBins) * words;
std::copy(selection.begin(), selection.end(), selectionp);
Bin& bin = data.bins[data.numBins++];
bin.selectionp = selectionp;
bin.namep = namep;
bin.filep = filep;
bin.line = line;
bin.col = col;
uint32_t word = 0;
for (const uint64_t bits : selection) { data.wordsp[word++].autoExcluded |= bits; }
}
void VlCoverCross::finalizeBins() {
if (!hasExplicitBins()) return;
Explicit& data = *m_explicitp;
assert(data.numBins == data.bins.size());
uint32_t autoIdx = 0;
for (uint32_t flat = 0; flat < m_numAutoBins; ++flat) {
if (!(data.wordsp[flat / 64].autoExcluded & (uint64_t{1} << (flat % 64)))) {
assert(autoIdx < data.autoBins.size());
data.autoBins[autoIdx++] = flat;
}
}
const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0);
assert(autoIdx == data.autoBins.size());
data.minBinWords = words;
uint64_t pos = 0;
const uint32_t* const indicesp = data.binWords.begin();
for (Bin& bin : data.bins) {
const uint64_t begin = pos;
for (uint32_t word = 0; word < words; ++word) {
if (bin.selectionp[word]) {
assert(pos < data.binWords.size());
data.binWords[pos++] = word;
}
}
bin.wordIndicesp = indicesp ? indicesp + begin : nullptr;
bin.numWords = static_cast<uint32_t>(pos - begin);
data.minBinWords = std::min(data.minBinWords, bin.numWords);
}
assert(pos == data.binWords.size());
}
template <bool T_Explicit, bool T_RecordHits>
void VlCoverCross::iterateProduct(uint32_t dim, uint32_t baseIdx) {
const VlCoverpoint* const cpp = m_dimensionsp[dim].cpp;
const uint32_t hits = cpp->hitCount();
const uint32_t* const list = m_dimensionsp[dim].hitsp;
const bool last = (dim == m_dims - 1);
const uint32_t stride = m_stride[dim];
const uint32_t stride = m_dimensionsp[dim].stride;
for (uint32_t hit = 0; hit < hits; ++hit) {
const uint32_t idx = baseIdx + list[hit] * stride;
if (last) {
incrementTuple(idx);
if (T_Explicit) {
incrementTuple<T_RecordHits>(idx);
} else {
incrementAuto(idx);
}
} else {
iterateProduct(cps, dim + 1, idx);
iterateProduct<T_Explicit, T_RecordHits>(dim + 1, idx);
}
}
}
void VlCoverCross::sample(VlCoverpoint* const* cps) {
// Fast path: if any dimension had no Normal-bin hit, the cross cannot hit.
for (uint32_t d = 0; d < m_dims; ++d) {
if (cps[d]->hitCount() == 0) return;
template <bool T_ApplyIffs>
void VlCoverCross::sampleSingleTuple(uint32_t idx, const bool* binIffs) {
Explicit& data = *m_explicitp;
const uint32_t word = idx / 64;
const uint64_t bit = uint64_t{1} << (idx % 64);
if (!(data.wordsp[word].autoExcluded & bit)) {
incrementAuto(idx);
return;
}
for (Bin& bin : data.bins) {
if (T_ApplyIffs && !*binIffs++) continue;
if (bin.selectionp[word] & bit) {
if (bin.count++ == 0) ++m_numCovered;
}
}
iterateProduct(cps, 0, 0);
}
std::string VlCoverCross::binName(uint32_t flat) const {
template <bool T_ApplyIffs, uint32_t T_Touched, bool T_Dense>
void VlCoverCross::sampleBins(const bool* binIffs) {
struct HitWord final {
uint32_t index;
uint64_t bits;
};
Explicit& data = *m_explicitp;
const uint64_t bins = data.numBins;
const uint64_t touched = T_Touched ? T_Touched : data.numTouchedWords;
const Word* const wordsp = data.wordsp;
std::array<HitWord, T_Touched> cached{};
for (uint32_t i = 0; i < T_Touched; ++i) {
const uint32_t word = wordsp[i].touchedWord;
cached[i] = {word, wordsp[word].hitBits};
}
for (uint64_t binIdx = 0; binIdx < bins; ++binIdx) {
if (T_ApplyIffs && !*binIffs++) continue;
Bin& bin = data.bins[binIdx];
bool matched = false;
if (T_Touched == 1) {
matched = (bin.selectionp[cached[0].index] & cached[0].bits) != 0;
} else if (T_Dense || bin.numWords >= touched) {
for (uint64_t i = 0; i < touched; ++i) {
const uint32_t word = T_Touched ? cached[i].index : wordsp[i].touchedWord;
const uint64_t hits = T_Touched ? cached[i].bits : wordsp[word].hitBits;
if (bin.selectionp[word] & hits) {
matched = true;
break;
}
}
} else {
for (uint32_t pos = 0; pos < bin.numWords; ++pos) {
const uint32_t word = bin.wordIndicesp[pos];
if (bin.selectionp[word] & wordsp[word].hitBits) {
matched = true;
break;
}
}
}
if (matched && bin.count++ == 0) ++m_numCovered;
}
for (uint32_t i = 0; i < data.numTouchedWords; ++i) {
data.wordsp[wordsp[i].touchedWord].hitBits = 0;
}
data.numTouchedWords = 0;
}
template <bool T_ApplyIffs, bool T_Dense>
void VlCoverCross::sampleHitWords(const bool* binIffs) {
switch (m_explicitp->numTouchedWords) {
case 1: sampleBins<T_ApplyIffs, 1, T_Dense>(binIffs); break;
case 2: sampleBins<T_ApplyIffs, 2, T_Dense>(binIffs); break;
case 3: sampleBins<T_ApplyIffs, 3, T_Dense>(binIffs); break;
default: sampleBins<T_ApplyIffs, 0, T_Dense>(binIffs); break;
}
}
void VlCoverCross::sample(const bool* binIffs) {
// Fast path: if any dimension had no Normal-bin hit, the cross cannot hit.
bool single = true;
for (uint32_t d = 0; d < m_dims; ++d) {
const uint32_t hits = m_dimensionsp[d].cpp->hitCount();
if (hits == 0) return;
single &= hits == 1;
}
if (single) {
uint32_t idx = 0;
for (uint32_t d = 0; d < m_dims; ++d) {
idx += m_dimensionsp[d].cpp->hitList()[0] * m_dimensionsp[d].stride;
}
if (hasExplicitBins()) {
if (binIffs) {
sampleSingleTuple<true>(idx, binIffs);
} else {
sampleSingleTuple<false>(idx, nullptr);
}
} else {
incrementAuto(idx);
}
return;
}
bool enabled = true;
if (hasExplicitBins() && binIffs && !binIffs[0]) {
const bool* const endp = binIffs + m_explicitp->bins.size();
enabled = std::find(binIffs + 1, endp, true) != endp;
if (!enabled && m_explicitp->autoBins.empty()) return;
}
for (uint32_t d = 0; d < m_dims; ++d) {
m_dimensionsp[d].hitsp = m_dimensionsp[d].cpp->hitList();
}
if (!hasExplicitBins()) {
iterateProduct<false>(0, 0);
return;
}
if (!enabled) {
iterateProduct<true, false>(0, 0);
return;
}
iterateProduct<true>(0, 0);
if (m_explicitp->numTouchedWords) {
const bool dense = m_explicitp->minBinWords >= m_explicitp->numTouchedWords;
if (binIffs) {
if (dense) {
sampleHitWords<true, true>(binIffs);
} else {
sampleHitWords<true, false>(binIffs);
}
} else {
if (dense) {
sampleHitWords<false, true>(nullptr);
} else {
sampleHitWords<false, false>(nullptr);
}
}
}
}
std::string VlCoverCross::binName(uint32_t i) const {
if (hasExplicitBins()) {
if (i < m_explicitp->bins.size()) return m_explicitp->bins[i].namep;
i -= static_cast<uint32_t>(m_explicitp->bins.size());
}
return autoBinName(autoIndex(i));
}
std::string VlCoverCross::autoBinName(uint32_t flat) const {
// Built on demand by concatenating each coverpoint's own bin name.
std::string name;
for (uint32_t d = 0; d < m_dims; ++d) {
const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d];
const Dimension& dimension = m_dimensionsp[d];
const uint32_t crossIdx = (flat / dimension.stride) % dimension.bins;
if (d > 0) name += "_x_";
name += m_cps[d]->normalBinName(crossIdx);
name += dimension.cpp->normalBinName(crossIdx);
}
return name;
}
#if VM_COVERAGE
void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page) {
// Register every auto cross bin (zero-count bins included), so the report
// shows the full Cartesian product of cross bins. Names are built on the fly.
const std::string lineStr = std::to_string(m_line);
const std::string colStr = std::to_string(m_col);
for (uint32_t flat = 0; flat < binCount(); ++flat) {
const std::string bin = binName(flat); // "b1_x_b2_x_..."
const uint32_t explicitCount
= hasExplicitBins() ? static_cast<uint32_t>(m_explicitp->bins.size()) : 0;
// Use the same indexed names for registration and the runtime read interface.
for (uint32_t i = 0; i < binCount(); ++i) {
const std::string bin = binName(i);
const std::string full = m_hier + "." + bin;
if (i < explicitCount) {
Bin& userBin = m_explicitp->bins[i];
const std::string binLineStr = std::to_string(userBin.line);
const std::string binColStr = std::to_string(userBin.col);
VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, "filename",
userBin.filep, "lineno", binLineStr.c_str(), "column",
binColStr.c_str(), "bin", bin.c_str(), "cross", "1");
continue;
}
const uint32_t flat = autoIndex(i - explicitCount);
// cross_bins metadata: the same components joined by ',' (not read by the report)
std::string crossBins;
for (uint32_t d = 0; d < m_dims; ++d) {
const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d];
const Dimension& dimension = m_dimensionsp[d];
const uint32_t crossIdx = (flat / dimension.stride) % dimension.bins;
if (d > 0) crossBins += ",";
crossBins += m_cps[d]->normalBinName(crossIdx);
crossBins += dimension.cpp->normalBinName(crossIdx);
}
const std::string full = m_hier + "." + bin;
VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCounts[flat], "page", page, "filename",
VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCountsp[flat], "page", page, "filename",
m_file, "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin",
bin.c_str(), "cross", "1", "cross_bins", crossBins.c_str());
}
}
#endif // VM_COVERAGE
//=============================================================================
// VlCovergroupType / VlCovRegistry
VlCovergroupInst* VlCovergroupType::newInstance() {
VlCovergroupInst* const instp = new VlCovergroupInst{this, m_nextInstId++};
m_insts.emplace_back(instp);
#if !VM_COVERAGE
instp->m_slot = static_cast<uint32_t>(m_insts.size() - 1);
#endif
++m_createdInsts;
return instp;
}
void VlCovergroupType::foldResidue(const VlCovergroupInst* instp) {
double covered = 0.0;
double total = 0.0;
instp->coverageParts(covered, total);
// Nothing coverable: excluded from both sums, so it moves neither the mean
// nor the denominator. Never-sampled is different: it has bins, none hit,
// and folds as 0%.
if (total == 0.0) return;
// TODO(P5): IEEE 1800-2023 19.5 defines covergroup coverage as the weighted
// mean of the per-item ratios, not the ratio of the summed parts. This
// matches what the generated get_inst_coverage() computes today, so that a
// live instance and the same instance one delta after death never disagree.
m_retired.sumCoverage += 100.0 * covered / total;
++m_retired.count;
}
// Runs when the last handle to instp drops, possibly after ~VlCovRegistry, on a
// type teardown leaked to keep this valid (see ~VlCovRegistry). That late case
// needs no special handling: the leaked type is self-consistent.
void VlCovergroupType::retire(VlCovergroupInst* instp) {
foldResidue(instp); // Before unlink: reads instp's items, freed below
#if VM_COVERAGE
// registerBins() gave the coverage database raw &m_counts[i], read at
// write() time. Keep the node alive, marked dead so it counts as neither
// live nor residue. Freeing here needs the coverage-writer rework.
instp->m_retained = true;
#else
// Move out first, so the node destructs at end of scope with m_insts
// already consistent rather than mid-swap.
const uint32_t slot = instp->m_slot;
const std::unique_ptr<VlCovergroupInst> dying = std::move(m_insts[slot]);
if (slot != m_insts.size() - 1) {
m_insts[slot] = std::move(m_insts.back());
m_insts[slot]->m_slot = slot; // Moved node's slot is now stale
}
m_insts.pop_back();
#endif
}
uint32_t VlCovergroupType::liveInstanceCount() const {
uint32_t live = 0;
// Under VM_COVERAGE m_insts also holds retained (dead) nodes; otherwise
// retained() is never set and this equals m_insts.size().
for (const auto& instp : m_insts) {
if (!instp->retained()) ++live;
}
return live;
}
bool VlCovergroupType::anyAttached() const {
for (const auto& instp : m_insts) {
if (instp->m_attachCount > 0) return true;
}
return false;
}
double VlCovergroupType::retiredCoverage() const {
if (m_retired.count == 0) return -1.0;
return m_retired.sumCoverage / static_cast<double>(m_retired.count);
}
// Defined here, not in verilated.cpp, so that the registry costs nothing in a model with no
// covergroups: this file is linked only when covergroups are used (or --coverage is on).
// Mirrors VerilatedContext::coveragep(), which lives in verilated_cov.cpp for the same reason.
VlCovRegistry* VerilatedContext::covergroupRegistryp() VL_MT_SAFE {
static VerilatedMutex s_mutex;
// cppcheck-suppress identicalInnerCondition
if (VL_UNLIKELY(!m_covergroupsp)) {
const VerilatedLockGuard lock{s_mutex};
// cppcheck-suppress identicalInnerCondition
if (VL_LIKELY(!m_covergroupsp)) { // LCOV_EXCL_LINE // Not redundant, prevents race
m_covergroupsp.reset(new VlCovRegistry{});
}
}
return static_cast<VlCovRegistry*>(m_covergroupsp.get());
}
VlCovergroupInst* VlCovRegistry::newCovergroupInst(const char* typeName) {
VlCovergroupType*& typep = m_byName[typeName];
if (!typep) { // First instance of this type
m_types.emplace_back(new VlCovergroupType{});
typep = m_types.back().get();
}
return typep->newInstance();
}
// A covergroup object can outlive the registry: models must be destroyed before
// their context, and a user who gets that backwards drops covergroup handles
// after ~VerilatedContext. Those handle destructors call attachDec(), which
// reads the instance node and its type -- so freeing the nodes here is itself
// what would make the wrong ordering a use-after-free, and a "retirement
// disarmed" flag could not help. Instead, leak any type that still has an
// attached node, keeping the type, its nodes and their items valid; the late
// retire() then frees the nodes itself, so only the type object leaks.
VlCovRegistry::~VlCovRegistry() {
for (auto& typep : m_types) {
// Normally nothing is still attached; if something is, the model
// outlived its context and those handles still reach this type.
if (VL_UNLIKELY(typep->anyAttached())) {
VlCovergroupType* const leakedp = typep.release();
static_cast<void>(leakedp); // Deliberate leak
}
}
}
VlCovergroupType* VlCovRegistry::findType(const char* typeName) const {
const auto it = m_byName.find(typeName);
return it == m_byName.end() ? nullptr : it->second;
}
uint32_t VlCovRegistry::liveInstanceCount() const {
uint32_t total = 0;
for (const auto& typep : m_types) total += typep->liveInstanceCount();
return total;
}
uint32_t VlCovRegistry::createdInstanceCount() const {
uint32_t total = 0;
for (const auto& typep : m_types) total += typep->createdInstanceCount();
return total;
}
uint32_t VlCovRegistry::liveInstanceCount(const char* typeName) const {
const VlCovergroupType* const typep = findType(typeName);
return typep ? typep->liveInstanceCount() : 0;
}
uint32_t VlCovRegistry::createdInstanceCount(const char* typeName) const {
const VlCovergroupType* const typep = findType(typeName);
return typep ? typep->createdInstanceCount() : 0;
}
uint32_t VlCovRegistry::retiredInstanceCount(const char* typeName) const {
const VlCovergroupType* const typep = findType(typeName);
return typep ? typep->retiredInstanceCount() : 0;
}
double VlCovRegistry::retiredCoverage(const char* typeName) const {
const VlCovergroupType* const typep = findType(typeName);
return typep ? typep->retiredCoverage() : -1.0;
}
+388 -18
View File
@@ -32,10 +32,15 @@
#include "verilatedos.h"
#include "verilated.h"
#include "verilated_cov_model.h"
#include <array>
#include <cstdint>
#include <initializer_list>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
class VerilatedCovContext;
@@ -202,12 +207,65 @@ public:
//=============================================================================
// VlCoverCross
/// Per-instance auto cross runtime. Holds flat uint32_t[] storage over the
/// Per-instance cross runtime. Holds flat uint32_t[] storage over the
/// Cartesian product of the feeding coverpoints' Normal bins. Each sample()
/// walks the coverpoint hit lists (O(hits), not O(product)). Bin names are
/// built on demand from the coverpoints, so no per-bin name is stored.
/// walks only hit tuples, not the entire product. Bin names are
/// built on demand for automatic bins; explicit bins select sets of tuples
/// and replace the corresponding automatic cross bins. Explicit selections
/// are intersected with hit-tuple words once per sample.
/// VlCoverCrossT owns the fixed arrays. This shared core does not allocate bin
/// storage, and its borrowed storage pointers remain valid for the instance.
class VlCoverCross final : public VlCoverpointIf {
class VlCoverCross VL_NOT_FINAL : public VlCoverpointIf {
protected:
struct Dimension final {
VlCoverpoint* cpp; // Feeding coverpoint
const uint32_t* hitsp; // Hit list cached for Cartesian traversal
uint32_t bins; // Normal bin count
uint32_t stride; // Flat-index stride
};
struct Bin final {
const uint64_t* selectionp; // Slice of the fixed selection storage
const char* namep; // Explicit bin name
const char* filep; // Bin declaration file
int line; // Bin declaration line
int col; // Bin declaration column
uint32_t count = 0; // Samples matching the selection and guard
uint32_t numWords = 0; // Number of nonzero selection-word indices
const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices
};
struct Word final {
uint64_t autoExcluded = 0; // Tuples replaced by explicit bins
uint64_t hitBits = 0; // Selected hit tuples, cleared after each sample
uint32_t touchedWord = 0; // Flat word ID, stored by touched-list position
};
template <typename T>
class View final {
T* m_beginp;
T* m_endp;
public:
View(T* datap, uint64_t size)
: m_beginp{datap}
, m_endp{datap ? datap + size : nullptr} {}
T& operator[](uint64_t i) const { return m_beginp[i]; }
uint64_t size() const { return m_beginp == m_endp ? 0 : m_endp - m_beginp; }
bool empty() const { return m_beginp == m_endp; }
T* begin() const { return m_beginp; }
T* end() const { return m_endp; }
};
struct Explicit final {
View<Bin> bins; // Explicit bins in declaration order
Word* wordsp; // Masks use flat word indices; touchedWord uses a dense prefix
View<uint32_t> autoBins; // Retained flat indices
View<uint32_t> binWords; // Nonzero selection words, grouped by bin
uint64_t* selectionp; // [bins.size() * ceil(m_numAutoBins / 64)]
uint32_t numBins = 0; // Bins configured by addBin()
uint32_t minBinWords = 0; // Minimum nonzero-word count across explicit bins
uint32_t numTouchedWords = 0; // Active prefix of wordsp[].touchedWord
};
private:
// MEMBERS
std::string m_hier; // "covergroup.cross"
const char* m_file = nullptr; // Cross declaration file (registration metadata)
@@ -217,41 +275,353 @@ class VlCoverCross final : public VlCoverpointIf {
// Cross bin indexes are unsigned, like the coverpoint bin indexes they are
// built from. init() fatals if the product would exceed UINT32_MAX, so every
// index computed here provably fits. That bound is far beyond anything
// storable anyway: m_flatCounts alone would need 16GB.
// storable anyway: m_flatCountsp alone would need 16GB.
uint32_t m_numAutoBins = 0; // Product of per-dim Normal bin counts
uint32_t m_numCovered = 0; // Distinct bins hit >= 1 (maintained incrementally)
std::vector<uint32_t> m_cpBinCounts; // [m_dims] Normal bin count per dimension
std::vector<uint32_t> m_stride; // [m_dims] Flat-index stride per dimension
std::vector<uint32_t> m_flatCounts; // [m_numAutoBins] Per-bin hit counts
std::vector<VlCoverpoint*> m_cps; // Feeding coverpoints (the only name source)
Dimension* m_dimensionsp = nullptr; // [m_dims], owned by VlCoverCrossT
uint32_t* m_flatCountsp = nullptr; // [m_numAutoBins] Per-bin hit counts
Explicit* m_explicitp = nullptr; // Absent for automatic-only crosses
// PRIVATE METHODS
void iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx);
bool hasExplicitBins() const { return m_explicitp != nullptr; }
template <bool T_Explicit, bool T_RecordHits = true>
void iterateProduct(uint32_t dim, uint32_t baseIdx);
void incrementAuto(uint32_t idx) {
if (m_flatCountsp[idx]++ == 0) ++m_numCovered;
}
template <bool T_RecordHits>
void incrementTuple(uint32_t idx) {
if (m_flatCounts[idx]++ == 0) ++m_numCovered;
Explicit& data = *m_explicitp;
const uint32_t wordIdx = idx / 64;
Word& word = data.wordsp[wordIdx];
if ((word.autoExcluded >> (idx % 64)) & 1U) {
if (T_RecordHits) {
if (!word.hitBits) { data.wordsp[data.numTouchedWords++].touchedWord = wordIdx; }
word.hitBits |= uint64_t{1} << (idx % 64);
}
// Explicit selections consume automatic tuples independently of iff.
return;
}
incrementAuto(idx);
}
template <bool T_ApplyIffs>
void sampleSingleTuple(uint32_t idx, const bool* binIffs);
template <bool T_ApplyIffs, uint32_t T_Touched, bool T_Dense>
void sampleBins(const bool* binIffs);
template <bool T_ApplyIffs, bool T_Dense>
void sampleHitWords(const bool* binIffs);
uint32_t autoIndex(uint32_t i) const {
return hasExplicitBins() ? m_explicitp->autoBins[i] : i;
}
std::string autoBinName(uint32_t flat) const;
protected:
// CONSTRUCTORS
VlCoverCross(uint32_t dims, uint32_t tuples)
: m_dims{dims}
, m_numAutoBins{tuples} {}
void bindStorage(Dimension* dimensionsp, uint32_t* countsp, Explicit* explicitp = nullptr) {
m_dimensionsp = dimensionsp;
m_flatCountsp = countsp;
m_explicitp = explicitp;
}
public:
// CONSTRUCTORS
VlCoverCross() = default;
VL_UNCOPYABLE(VlCoverCross);
// METHODS
// ---- configuration (from generated constructor, after coverpoints init'd) ----
void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file,
int line, int col);
/// Add a cross bin using a verilation-time bitmap of selected Normal-bin tuples.
void addBin(std::initializer_list<uint64_t> selection, const char* namep, const char* filep,
int line, int col);
/// Retain only automatic cross bins not selected by any explicit bin.
void finalizeBins();
void registerBins(VerilatedCovContext* covcontextp, const char* page);
// ---- hot path (from generated sample(), after all coverpoints sampled) ----
void sample(VlCoverpoint* const* cps);
/// Sample automatic and explicit bins, optionally applying per-bin iff guards.
/// Reads the feeding coverpoints saved by init(), so the caller passes no coverpoints.
void sample(const bool* binIffs = nullptr);
// ---- VlCoverpointIf ----
// A cross is a coverpoint whose bins are the auto cross bins (all Normal).
uint32_t binCount() const override { return m_numAutoBins; }
std::string binName(uint32_t flat) const override;
// Explicit bins precede retained automatic bins; all are Normal bins.
uint32_t binCount() const override {
return hasExplicitBins()
? static_cast<uint32_t>(m_explicitp->bins.size() + m_explicitp->autoBins.size())
: m_numAutoBins;
}
std::string binName(uint32_t i) const override;
void coverageParts(double& covered, double& total) const override {
covered = m_numCovered;
total = m_numAutoBins;
total = binCount();
}
};
//=============================================================================
// VlCoverCrossT
/// Cross storage with verilation-time dimensions and bin capacities. All bin
/// data stays at the registry-owned object's address; no per-buffer allocations
/// or per-shape copies of the sampling algorithm are needed.
template <uint32_t Dims, uint32_t Tuples, uint32_t Bins, uint32_t AutoBins, uint64_t BinWords>
class VlCoverCrossT final : public VlCoverCross {
static constexpr uint32_t WORDS = Tuples / 64 + (Tuples % 64 != 0);
static_assert(Bins > 0, "Explicit cross storage requires bins");
std::array<Dimension, Dims> m_dimensions;
std::array<uint32_t, Tuples> m_counts{};
std::array<Bin, Bins> m_bins;
std::array<Word, WORDS> m_words{};
std::array<uint32_t, AutoBins> m_autoBins;
std::array<uint32_t, BinWords> m_binWords;
std::array<uint64_t, static_cast<uint64_t>(Bins) * WORDS> m_selections;
Explicit m_explicit;
public:
VlCoverCrossT()
: VlCoverCross{Dims, Tuples}
, m_explicit{{m_bins.data(), Bins},
m_words.data(),
{m_autoBins.data(), AutoBins},
{m_binWords.data(), BinWords},
m_selections.data()} {
bindStorage(m_dimensions.data(), m_counts.data(), &m_explicit);
}
};
/// Automatic-only crosses omit every explicit-bin array and its bookkeeping.
template <uint32_t Dims, uint32_t Tuples>
class VlCoverCrossT<Dims, Tuples, 0, 0, 0> final : public VlCoverCross {
std::array<Dimension, Dims> m_dimensions;
std::array<uint32_t, Tuples> m_counts{};
public:
VlCoverCrossT()
: VlCoverCross{Dims, Tuples} {
bindStorage(m_dimensions.data(), m_counts.data());
}
};
class VlCovergroupType;
//=============================================================================
// VlCovergroupInst
/// One covergroup instance: owns the coverpoint/cross runtimes created by one
/// SV 'new'. The generated class holds borrowed pointers to them, so the bins
/// outlive the SV object -- the coverage database registers raw count pointers
/// and reads them at write() time, long after the object may have been freed.
///
/// Attach-counted: every VlCovInstHandle bound here holds one count, and the
/// node is retired (see VlCovergroupType::retire) when the last one drops.
class VlCovergroupInst final {
// MEMBERS
// Coverpoint and cross runtimes of this instance; creation == declaration order
std::vector<std::unique_ptr<VlCoverpointIf>> m_items;
VlCovergroupType* const m_typep; // Owning type; outlives this node
const uint32_t m_instId; // Stable identity across churn; NOT the slot
#if !VM_COVERAGE
// Only retire()'s free path uses this; under VM_COVERAGE the node is never
// unlinked, so the slot would be dead. VlCovergroupType sets it.
uint32_t m_slot = 0; // Index into m_typep->m_insts; unlink-by-swap rewrites
#endif
uint32_t m_attachCount = 1; // SV handles bound here; 1 from construction
bool m_retained = false; // VM_COVERAGE: dead, but kept for registered count pointers
// Reads m_items to fold the residue; owns m_slot and m_retained.
friend class VlCovergroupType;
public:
// CONSTRUCTORS
VlCovergroupInst(VlCovergroupType* typep, uint32_t instId)
: m_typep{typep}
, m_instId{instId} {}
VL_UNCOPYABLE(VlCovergroupInst);
// METHODS
// ---- construction (from the generated covergroup constructor) ----
template <uint32_t MaxHits>
VlCoverpointT<MaxHits>* addCoverpoint() {
VlCoverpointT<MaxHits>* const cpp = new VlCoverpointT<MaxHits>{};
m_items.emplace_back(cpp);
return cpp; // borrowed by the generated class
}
template <uint32_t Dims, uint32_t Tuples, uint32_t Bins, uint32_t AutoBins, uint64_t BinWords>
VlCoverCrossT<Dims, Tuples, Bins, AutoBins, BinWords>* addCross() {
auto* const cxp = new VlCoverCrossT<Dims, Tuples, Bins, AutoBins, BinWords>{};
m_items.emplace_back(cxp);
return cxp; // borrowed by the generated class
}
// ---- attach counting (from VlCovInstHandle) ----
void attachInc() { ++m_attachCount; }
// Drops one handle; true if it was the last and the caller must retire the
// node. Retiring is the caller's job because VlCovergroupType is incomplete
// here, and because it frees 'this'.
bool attachDec() { return --m_attachCount == 0; }
// ---- introspection ----
VlCovergroupType* typep() const { return m_typep; }
uint32_t instId() const { return m_instId; }
// True once retired but kept alive because the coverage database holds raw
// pointers into this node's bin counts (VM_COVERAGE); see retire().
bool retained() const { return m_retained; }
// Sum of the instance's items' covered/total bin counts. Matches what the
// generated get_inst_coverage() computes; see foldResidue().
void coverageParts(double& covered, double& total) const {
covered = 0.0;
total = 0.0;
for (const auto& itemp : m_items) {
double c = 0.0;
double t = 0.0;
itemp->coverageParts(c, t);
covered += c;
total += t;
}
}
};
//=============================================================================
// VlCovRetiredAvg
/// Per-type residue: what survives an instance's death. Fixed size, so it does
/// not grow with churn. Weight is 1 everywhere until option.weight is plumbed.
struct VlCovRetiredAvg final {
uint64_t count = 0; // Retired instances that contributed (nonzero denominator)
double sumCoverage = 0.0; // Sigma of per-instance coverage, each in 0..100
};
//=============================================================================
// VlCovergroupType
/// One covergroup type: owns its live instances, in creation order, plus the
/// residue of the ones that have died.
class VlCovergroupType final {
// MEMBERS
// Live nodes, and -- under VM_COVERAGE -- retired-but-retained ones. Slot
// order is creation order only until the first unlink-by-swap.
std::vector<std::unique_ptr<VlCovergroupInst>> m_insts;
uint32_t m_createdInsts = 0; // Instances ever created; never decremented
uint32_t m_nextInstId = 0; // Monotonic; slots are reused, ids never are
VlCovRetiredAvg m_retired; // Contribution of every instance that has died
// PRIVATE METHODS
// Harvest instp's contribution into m_retired. Must run before instp is
// unlinked: it reads the instance's items.
void foldResidue(const VlCovergroupInst* instp);
public:
// CONSTRUCTORS
VlCovergroupType() = default;
VL_UNCOPYABLE(VlCovergroupType);
// METHODS
VlCovergroupInst* newInstance();
// Called when the last handle to instp drops. Folds the residue, then
// unlinks and frees the node -- except under VM_COVERAGE, where the coverage
// database still holds raw pointers into it and it is only marked retained.
void retire(VlCovergroupInst* instp);
// True if any node here still has an SV handle bound to it, and so can be
// retired again after the registry is destroyed. See ~VlCovRegistry.
bool anyAttached() const;
// ---- introspection ----
// Test and debug only; generated code never calls these, and SV reaches them
// only via explicit $c. They let a regression test pin node accumulation
// (otherwise visible only as memory growth) and the residue fold.
//
// Instance nodes still reachable from SV. Under VM_COVERAGE this is smaller
// than m_insts.size(), which also holds retained (dead) nodes.
uint32_t liveInstanceCount() const;
// Instances ever created, live or not. Wraps after 4G instances, which no
// introspection use cares about.
uint32_t createdInstanceCount() const { return m_createdInsts; }
// Instances that have died and contributed to the residue.
uint32_t retiredInstanceCount() const { return static_cast<uint32_t>(m_retired.count); }
// Mean coverage over the retired instances only, in 0..100; -1.0 if none.
double retiredCoverage() const;
};
//=============================================================================
// VlCovRegistry
/// Every covergroup type and instance in one VerilatedContext. Owned by the
/// VerilatedContext (not by the coverage database, which is only linked under
/// --coverage and is a *consumer* of this data), reached through
/// VerilatedContext::covergroupRegistryp().
class VlCovRegistry final : public VerilatedVirtualBase {
// MEMBERS
std::vector<std::unique_ptr<VlCovergroupType>> m_types; // Creation order
std::unordered_map<std::string, VlCovergroupType*> m_byName; // Lookup, borrowed
// PRIVATE METHODS
VlCovergroupType* findType(const char* typeName) const; // nullptr if unknown
public:
// CONSTRUCTORS
VlCovRegistry() = default;
~VlCovRegistry() override;
VL_UNCOPYABLE(VlCovRegistry);
// METHODS
// Find-or-create the type node, then add an instance to it. typeName is the
// generated covergroup class name, already --protect-ids obfuscated, and is
// the same string that keys the coverage database's hier/page.
VlCovergroupInst* newCovergroupInst(const char* typeName);
// ---- introspection (see VlCovergroupType) ----
// typeName is the obfuscated generated name, so a test using these under
// --protect-ids must pass the obfuscated string; the no-argument form does not.
uint32_t liveInstanceCount() const; // Summed over every type
uint32_t createdInstanceCount() const; // Summed over every type
uint32_t liveInstanceCount(const char* typeName) const; // 0 if type unknown
uint32_t createdInstanceCount(const char* typeName) const; // 0 if type unknown
uint32_t retiredInstanceCount(const char* typeName) const; // 0 if type unknown
double retiredCoverage(const char* typeName) const; // -1.0 if type unknown or none
};
//=============================================================================
// VlCovInstHandle
/// The generated covergroup class's link to its instance node. Attach-counting:
/// the registry owns the node, but the handles are what keep it reachable, and
/// the last one to go retires it.
///
/// Must stay copyable: every generated clone() copy-constructs. A copy shares
/// the node, and so the bin counts -- pre-existing covergroup-copy aliasing.
/// Attach counting makes that lifetime-safe, not correct.
class VlCovInstHandle final {
// MEMBERS
VlCovergroupInst* m_p = nullptr; // Attach-counted; the registry owns the node
// PRIVATE METHODS
// Drop one attach count, retiring the node if that was the last handle.
// Nothing may touch instp afterwards: retire() may have freed it.
static void release(VlCovergroupInst* instp) {
if (VL_UNCOVERABLE(!instp)) return; // Never attach()ed; codegen always does
if (instp->attachDec()) instp->typep()->retire(instp);
}
public:
// CONSTRUCTORS
VlCovInstHandle() = default;
VlCovInstHandle(const VlCovInstHandle& o)
: m_p{o.m_p} {
if (VL_UNCOVERABLE(!m_p)) return; // Unbound source; see release above
m_p->attachInc();
}
// Deleted, not implemented: nothing generates an assignment, and the
// implicit one would copy m_p raw -- no attachInc, no release.
VlCovInstHandle& operator=(const VlCovInstHandle&) = delete;
~VlCovInstHandle() { release(m_p); }
// METHODS
// Bind to a freshly created node, taking over the attach count of 1 it was
// created with. Called once, from the generated covergroup constructor.
void attach(VlCovergroupInst* p) { m_p = p; }
VlCovergroupInst* p() const { return m_p; }
};
#endif // Guard
+1 -1
View File
@@ -303,7 +303,7 @@ public:
}
template <typename T>
T readIndex(T origVal, int index) const {
T readIndex(const T origVal, int index) const {
if (m_entries.empty()) return origVal;
T result = origVal;
+2 -2
View File
@@ -1438,8 +1438,8 @@ inline void _vl_insert_WI(WDataOutP iowp, IData ld, int hbit, int lbit, int rbit
const int nbitsonright = VL_EDATASIZE - loffset; // bits that end up in lword
iowp[lword] = (iowp[lword] & ~linsmask) | ((lde << loffset) & linsmask);
// Prevent unsafe write where lword was final writable location and hword is
// out-of-bounds.
if (VL_LIKELY(!(hword == rword && roffset == 0))) {
// out-of-bounds. rbits==0 means the caller guarantees bounds.
if (VL_LIKELY(!(rbits && hword >= VL_WORDS_I(rbits)))) {
iowp[hword]
= (iowp[hword] & ~hinsmask) | ((lde >> nbitsonright) & (hinsmask & cleanmask));
}
+9
View File
@@ -207,6 +207,11 @@ protected:
// Used by scopeInsert, scopeFind, scopeErase, scopeNameMap
mutable VerilatedMutex m_nameMutex; // Protect m_nameMap
VerilatedScopeNameMap m_nameMap VL_GUARDED_BY(m_nameMutex);
// Map of <interface_reference_name, interface reference>
// Used by ifaceRefInsert, ifaceRefFind, ifaceRefErase, ifaceRefMap
mutable VerilatedMutex m_ifaceRefMutex; // Protect m_ifaceRefMap
VerilatedIfaceRefMap m_ifaceRefMap VL_GUARDED_BY(m_ifaceRefMutex);
};
//======================================================================
@@ -270,6 +275,10 @@ public: // But only for verilated*.cpp
void scopeInsert(const VerilatedScope* scopep) VL_MT_SAFE;
void scopeErase(const VerilatedScope* scopep) VL_MT_SAFE;
// METHODS - interface references - INTERNAL only for verilated*.cpp
void ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE;
void ifaceRefErase(const std::string& fullname, const VerilatedScope* scopep) VL_MT_SAFE;
// METHODS - file IO - INTERNAL only for verilated*.cpp
IData fdNewMcd(const char* filenamep) VL_MT_SAFE_EXCLUDES(m_fdMutex) {
+38 -8
View File
@@ -674,10 +674,38 @@ void VlRandomVar::set(const std::string& idx, const std::string& val) const {
}
}
void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1);
void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits,
const std::vector<std::string>* layerVarsp) {
// layerVarsp scopes sampling to the current phase's own layer, so a
// phased solve's diversity constraint can't be built entirely out of a
// later phase's (still-unsolved) variable instead of this one's.
std::vector<const VlRandomVar*>& vars = m_randomConstraintVars;
vars.clear();
int varBits = 0;
for (const auto& var : m_vars) varBits += var.second->totalWidth();
if (layerVarsp) {
for (const auto& name : *layerVarsp) {
const auto it = m_vars.find(name);
// buildSolveLayers() only ever adds a name after confirming
// it's already in m_vars.
assert(it != m_vars.end());
vars.push_back(it->second.get());
varBits += it->second->totalWidth();
}
} else {
for (const auto& var : m_vars) {
vars.push_back(var.second.get());
varBits += var.second->totalWidth();
}
}
if (varBits == 0) {
// Nothing to sample (e.g. a still-unsized queue/dynamic array, in a
// layer or the whole class) -- tautology instead of the degenerate
// empty-operand expression the loop below would otherwise build.
os << "(= #b1 #b1)";
return;
}
const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1);
os << "(= #b";
for (int i = bits - 1; i >= 0; i--) os << (VL_BITISSET_I(hash, i) ? '1' : '0');
if (bits > 1) os << " (concat";
@@ -685,11 +713,12 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) {
IData varBitsLeft = varBits;
IData varBitsWant = (varBits + 1) / 2;
if (varBits > 2) os << " (bvxor";
for (const auto& var : m_vars) {
for (int j = 0; j < var.second->totalWidth(); j++, varBitsLeft--) {
for (const auto& varp : vars) { // LCOV_EXCL_BR_LINE - reservoir-sampling below
// always forces its last pick before exhausting vars
for (int j = 0; j < varp->totalWidth(); j++, varBitsLeft--) {
const bool doEmit = (VL_RANDOM_RNG_I(rngr) % varBitsLeft) < varBitsWant;
if (doEmit) {
var.second->emitExtract(os, j);
varp->emitExtract(os, j);
if (--varBitsWant == 0) break;
}
}
@@ -1590,7 +1619,7 @@ void VlRandomizer::clearAll() {
m_randcConstraintHash = 0;
}
void VlRandomizer::markRandc(const char* name) { m_randcVarNames.insert(name); }
void VlRandomizer::markRandc(const std::string& name) { m_randcVarNames.insert(name); }
void VlRandomizer::solveBefore(const std::string& beforeName, const std::string& afterName) {
m_solveBefore.emplace_back(beforeName, afterName);
@@ -1748,8 +1777,9 @@ bool VlRandomizer::solvePhaseValues(VlSolverSession& sess, VlRNG& rngr,
// Try diversity: add random constraint, re-check. If sat, get
// updated (more diverse) values. If unsat, keep baseline values.
// Scoped to this layer's own vars -- see randomConstraint's comment.
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN, &layerVars);
os << ")\n";
os << "(check-sat)\n";
if (sess.readStatus() == VlSolverStatus::SAT) {
+8 -5
View File
@@ -259,6 +259,8 @@ class VlRandomizer VL_NOT_FINAL {
m_constraints_line; // fileline content of the constraint for unsat constraints
std::vector<std::string> m_softConstraints; // Soft constraints
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// Scratch buffer for randomConstraint(), reused across calls
std::vector<const VlRandomVar*> m_randomConstraintVars;
std::set<std::string> m_disabledVars; // Variables with rand_mode off (skip write-back)
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
@@ -279,7 +281,8 @@ class VlRandomizer VL_NOT_FINAL {
bool hasFrozenVar() const; // true if any var is currently rand_mode(0)-frozen
// PRIVATE METHODS
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits,
const std::vector<std::string>* layerVarsp = nullptr);
// Fetch the model and write it into the registered variables.
bool applyModel(VlSolverSession& sess);
bool parseModel(std::istream& is, size_t requested);
@@ -466,16 +469,16 @@ public:
// Mark a variable as rand_mode-disabled: solver keeps it in m_vars
// (so constraints still reference it) but skips write-back after solving.
void set_var_disabled(const char* name) { m_disabledVars.insert(name); }
void set_var_disabled(const std::string& name) { m_disabledVars.insert(name); }
// Clear disabled state for a variable
void clear_var_disabled(const char* name) { m_disabledVars.erase(name); }
void clear_var_disabled(const std::string& name) { m_disabledVars.erase(name); }
// --- write_var to register variables ---
// Register scalar variable (non-struct, basic type)
template <typename T>
typename std::enable_if<!VlContainsCustomStruct<T>::value && !IsVlUnpacked<T>::value,
void>::type
write_var(T& var, int width, const char* name, int dimension,
write_var(T& var, int width, const std::string& name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
if (m_vars.find(name) != m_vars.end()) return;
// TODO: make_unique once VlRandomizer is per-instance not per-ref
@@ -765,7 +768,7 @@ public:
void disable_soft(const std::string& varName);
void clearConstraints();
void clearAll(); // Clear both constraints and variables
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
void markRandc(const std::string& name); // Mark variable as randc for cyclic tracking
void solveBefore(const std::string& beforeName,
const std::string& afterName); // Register solve-before ordering
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
+9
View File
@@ -72,4 +72,13 @@ public:
~VerilatedHierarchyMap() = default;
};
// Map of sorted interface reference names to the concrete interface they refer to
// Keyed by value, as the full name is built at construction
// This is a class instead of typedef/using to allow forward declaration in verilated.h
class VerilatedIfaceRefMap final : public std::map<std::string, VerilatedIfaceRef, std::less<>> {
public:
VerilatedIfaceRefMap() = default;
~VerilatedIfaceRefMap() = default;
};
#endif // Guard
+9 -1
View File
@@ -231,7 +231,7 @@ extern std::string VL_TO_STRING(SData lhs);
extern std::string VL_TO_STRING(IData lhs);
extern std::string VL_TO_STRING(QData lhs);
extern std::string VL_TO_STRING(double lhs);
inline std::string VL_TO_STRING(const std::string& obj) { return "\"" + obj + "\""; }
extern std::string VL_TO_STRING(const std::string& obj) VL_PURE;
template <std::size_t N_Words>
inline std::string VL_TO_STRING(const VlWide<N_Words>& obj) {
return VL_TO_STRING_W(N_Words, obj);
@@ -1468,6 +1468,14 @@ public:
constexpr std::size_t size() const { return N_Depth; }
// Runtime slice v[loIdx +: N_Out], loIdx being an index into m_storage
template <std::size_t N_Out>
VlUnpacked<T_Value, N_Out> slice(int32_t loIdx) const {
VlUnpacked<T_Value, N_Out> out;
for (std::size_t i = 0; i < N_Out; ++i) out.m_storage[i] = m_storage[loIdx + i];
return out;
}
void fill(const T_Value& value) {
std::fill(std::begin(m_storage), std::end(m_storage), value);
}
+144 -2
View File
@@ -363,11 +363,13 @@ protected:
public:
explicit VerilatedVpioScope(const VerilatedScope* scopep)
: m_scopep{scopep} {
m_fullname = m_scopep->name();
if (std::strncmp(m_fullname, "TOP.", 4) == 0) m_fullname += 4;
m_fullname = vpiFullnamep(m_scopep->name());
m_name = m_scopep->identifier();
m_defname = m_scopep->defname();
}
static const char* vpiFullnamep(const char* namep) VL_PURE {
return (std::strncmp(namep, "TOP.", 4) == 0) ? namep + 4 : namep;
}
~VerilatedVpioScope() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioScope* castp(vpiHandle h) {
@@ -721,6 +723,17 @@ public:
uint32_t type() const override { return vpiModule; }
};
class VerilatedVpioInterface final : public VerilatedVpioScope {
public:
explicit VerilatedVpioInterface(const VerilatedScope* scopep)
: VerilatedVpioScope{scopep} {}
// cppcheck-suppress duplInheritedMember
static VerilatedVpioInterface* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioInterface*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiInterface; }
};
class VerilatedVpioModuleIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
@@ -778,6 +791,8 @@ public:
return (new VerilatedVpioScope{modp})->castVpiHandle();
} else if (itype == VerilatedScope::SCOPE_MODULE) {
return (new VerilatedVpioModule{modp})->castVpiHandle();
} else if (itype == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{modp})->castVpiHandle();
}
}
}
@@ -803,6 +818,90 @@ public:
uint32_t type() const override { return vpiPackage; }
};
class VerilatedVpioModport final : public VerilatedVpio {
const VerilatedScope* const m_scopep; // Interface the modport is within
const char* const m_name; // Modport name
const std::string m_fullname; // Interface full name + "." + modport name
public:
VerilatedVpioModport(const VerilatedScope* scopep, const char* namep)
: m_scopep{scopep}
, m_name{namep}
, m_fullname{std::string{VerilatedVpioScope::vpiFullnamep(scopep->name())} + "." + namep} {
}
~VerilatedVpioModport() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioModport* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioModport*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiModport; }
const VerilatedScope* scopep() const { return m_scopep; }
const char* name() const override { return m_name; }
const char* fullname() const override { return m_fullname.c_str(); }
// IEEE 1800-2023 37.15
const char* defname() const override { return m_name; }
};
class VerilatedVpioIfaceRef final : public VerilatedVpio {
// Held by value, as a handle may outlive the model that registered it
const VerilatedIfaceRef m_ifaceRef;
public:
explicit VerilatedVpioIfaceRef(const VerilatedIfaceRef& ifaceRef)
: m_ifaceRef{ifaceRef} {}
~VerilatedVpioIfaceRef() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioIfaceRef* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioIfaceRef*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiRefObj; }
const VerilatedIfaceRef* ifaceRefp() const { return &m_ifaceRef; }
const char* name() const override { return m_ifaceRef.name(); }
const char* fullname() const override {
return VerilatedVpioScope::vpiFullnamep(m_ifaceRef.fullname());
}
// IEEE 1800-2023 37.15: modport name, else the interface definition name
const char* defname() const override {
return m_ifaceRef.hasModport() ? m_ifaceRef.modport() : m_ifaceRef.scopep()->defname();
}
vpiHandle actual() const {
if (m_ifaceRef.hasModport()) {
return (new VerilatedVpioModport{m_ifaceRef.scopep(), m_ifaceRef.modport()})
->castVpiHandle();
}
return (new VerilatedVpioInterface{m_ifaceRef.scopep()})->castVpiHandle();
}
};
class VerilatedVpioInterfaceIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
public:
explicit VerilatedVpioInterfaceIter(const std::vector<const VerilatedScope*>& vec)
: m_vec{&vec} {
m_it = m_vec->begin();
}
~VerilatedVpioInterfaceIter() override = default;
// cppcheck-suppress duplInheritedMember
static VerilatedVpioInterfaceIter* castp(vpiHandle h) {
return dynamic_cast<VerilatedVpioInterfaceIter*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t type() const override { return vpiIterator; }
vpiHandle dovpi_scan() override {
while (true) {
if (m_it == m_vec->end()) {
delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
return nullptr;
}
const VerilatedScope* const scopep = *m_it++;
if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{scopep})->castVpiHandle();
}
}
}
};
class VerilatedVpioInstanceIter final : public VerilatedVpio {
const std::vector<const VerilatedScope*>* m_vec;
std::vector<const VerilatedScope*>::const_iterator m_it;
@@ -1999,6 +2098,9 @@ const char* VerilatedVpiError::strFromVpiMethod(PLI_INT32 vpiVal) VL_PURE {
"vpiStmt"
};
// clang-format on
// SystemVerilog relations are numbered far above the Verilog ones
if (vpiVal == vpiActual) return "vpiActual";
if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return strFromVpiObjType(vpiVal);
if (vpiVal > vpiStmt || vpiVal < vpiCondition) return "*undefined*";
return names[vpiVal - vpiCondition];
}
@@ -2402,6 +2504,8 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE {
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiCondition);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiStmt);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiActual);
SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiInterface);
SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbValueChange);
SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbAtEndOfSimTime);
@@ -2771,6 +2875,10 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope);
const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope);
// Not scopes, so no name resolves relative to them; must not fall through to
// the unprefixed lookup below, which would resolve from the top level
if (VerilatedVpioIfaceRef::castp(scope) || VerilatedVpioModport::castp(scope)) return nullptr;
if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) {
scopeAndName.erase(0, std::strlen("$root."));
} else if (voScopep) {
@@ -2798,8 +2906,15 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
if (scopep->type() == VerilatedScope::SCOPE_PACKAGE) {
return (new VerilatedVpioPackage{scopep})->castVpiHandle();
}
if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) {
return (new VerilatedVpioInterface{scopep})->castVpiHandle();
}
return (new VerilatedVpioScope{scopep})->castVpiHandle();
}
if (const VerilatedIfaceRef* const ifaceRefp
= Verilated::threadContextp()->ifaceRefFind(scopeAndName.c_str())) {
return (new VerilatedVpioIfaceRef{*ifaceRefp})->castVpiHandle();
}
std::string basename = scopeAndName;
std::string scopename;
std::string::size_type prevpos = std::string::npos;
@@ -2959,6 +3074,24 @@ vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) {
const int32_t val = vop->index().back();
return (new VerilatedVpioConst{val})->castVpiHandle();
}
case vpiActual: {
if (const VerilatedVpioIfaceRef* const vop = VerilatedVpioIfaceRef::castp(object)) {
return vop->actual();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
case vpiInterface: {
if (const VerilatedVpioModport* const vop = VerilatedVpioModport::castp(object)) {
return (new VerilatedVpioInterface{vop->scopep()})->castVpiHandle();
}
VL_VPI_WARNING_(__FILE__, __LINE__,
"%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned",
__func__, object, VerilatedVpiError::strFromVpiMethod(type));
return nullptr;
}
case vpiScope: {
const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
if (VL_UNLIKELY(!vop)) return nullptr;
@@ -3025,6 +3158,15 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) {
if (it == map->end()) return nullptr;
return ((new VerilatedVpioModuleIter{it->second})->castVpiHandle());
}
case vpiInterface: {
// IEEE 1800-2023 37.5: interfaces are a one-to-many of a module
const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
const VerilatedScope* const modp = vop ? vop->scopep() : nullptr;
const auto it = vlstd::as_const(map)->find(const_cast<VerilatedScope*>(modp));
if (it == map->end()) return nullptr;
return ((new VerilatedVpioInterfaceIter{it->second})->castVpiHandle());
}
case vpiInternalScope: {
const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
+11 -1
View File
@@ -318,6 +318,15 @@
# define VL_CONSTEXPR_CXX17
#endif
//=========================================================================
// C++-2020
#if __cplusplus >= 202002L
# define VL_NO_UNIQUE_ADDRESS_CXX20 [[no_unique_address]]
#else
# define VL_NO_UNIQUE_ADDRESS_CXX20
#endif
//=========================================================================
// Optimization
@@ -452,7 +461,8 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/VlWide/etc) Signed number; for %d showing sign
#define VL_VFORMATATTR_COMPLEX '!' // (std::string*); for non-POD; e.g. struct, requires %p typically
#define VL_VFORMATATTR_DOUBLE 'D' // (double); promote %p to %f
#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData, std::string* name); <= 64 bit enum with runtime %p/%s
#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData/const EData*, STRING, std::string* name)
#define VL_VFORMATATTR_ENUM_SIGNED 'F' // Same arguments as ENUM, with a signed numeric value
#define VL_VFORMATATTR_SCOPE 'M' // (char* name, char* scope); for scopes
#define VL_VFORMATATTR_STRING 'S' // (char* name, char* scope); for scopes // (std::string*); for %p/%s
#define VL_VFORMATATTR_TIMEUNIT 'T' // (int timeunit); timeunits passed from V3Emit to runtime
+3 -1
View File
@@ -25,8 +25,10 @@ def message_section(msg: str) -> int:
return 50
if re.match(r'^Support', msg, flags=re.IGNORECASE):
return 60
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
if re.match(r'^Optimize', msg, flags=re.IGNORECASE):
return 70
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
return 80
if re.match(r'^(Internals|CI|Tests)', msg, flags=re.IGNORECASE):
return -1
if re.match(r'^Bump.* from .* to .*', msg, flags=re.IGNORECASE): # dependabot
+5 -2
View File
@@ -12,20 +12,23 @@
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: CC0-1.0
# These flags tested against verible-v0.0-4080-ga0a8d8eb
# These flags tested against verible-v0.0-4192-g682a1d50
verible-verilog-format \
--inplace \
--wrap_end_else_clauses \
\
--assignment_statement_alignment=flush-left \
--case_items_alignment=flush-left \
--class_member_variable_alignment=flush-left \
--distribution_items_alignment=flush-left \
--enum_assignment_statement_alignment=flush-left \
--expand_coverpoints \
--formal_parameters_alignment=flush-left \
--module_net_variable_alignment=flush-left \
--named_parameter_alignment=flush-left \
--named_port_alignment=flush-left \
--parameter_declaration_alignment=flush-left \
--port_declarations_alignment=flush-left \
--struct_union_members_alignment=flush-left \
--wrap_end_else_clauses \
$*
+3
View File
@@ -114,6 +114,7 @@ set(HEADERS
V3GraphPathChecker.h
V3GraphStream.h
V3Hash.h
V3HashTable.h
V3Hasher.h
V3HierBlock.h
V3Inline.h
@@ -292,6 +293,7 @@ set(COMMON_SOURCES
V3GraphPathChecker.cpp
V3GraphTest.cpp
V3Hash.cpp
V3HashTable.cpp
V3Hasher.cpp
V3HierBlock.cpp
V3Inline.cpp
@@ -345,6 +347,7 @@ set(COMMON_SOURCES
V3Sampled.cpp
V3Sched.cpp
V3SchedAcyclic.cpp
V3SchedCovergroup.cpp
V3SchedPartition.cpp
V3SchedReplicate.cpp
V3SchedTiming.cpp
+2
View File
@@ -196,6 +196,7 @@ RAW_OBJS = \
V3GraphPathChecker.o \
V3GraphTest.o \
V3Hash.o \
V3HashTable.o \
V3OptionParser.o \
V3Os.o \
V3ParseGrammar.o \
@@ -330,6 +331,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Sampled.o \
V3Sched.o \
V3SchedAcyclic.o \
V3SchedCovergroup.o \
V3SchedPartition.o \
V3SchedReplicate.o \
V3SchedTiming.o \
+4 -4
View File
@@ -433,20 +433,20 @@ class AssertVisitor final : public VNVisitor {
if (!m_monitorNumVarp) {
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
nodep->findUInt64DType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp);
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorNumVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
AstVarRef* newMonitorOffVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorOffVarp) {
m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff",
nodep->findBitDType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp);
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorOffVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType,
+367 -133
View File
@@ -53,13 +53,18 @@ class SvaStateVertex;
// Per-vertex algorithm data, stored via V3GraphVertex::userp() during lowering
struct SvaVertexData final {
AstVar* stateVarp = nullptr; // NBA state register for this vertex
AstVar* delayRingVarp = nullptr; // Bitset ring buffer
AstVar* delayRingVarp = nullptr; // Packed occupancy bits or per-slot match counts
AstVar* delayRingHeadVarp
= nullptr; // Cached outgoing slot; isolates readers from in-place writes
AstVar* delayRingIdxVarp = nullptr; // Next slot written in delayRingVarp
AstVar* delayRingLiveCountVarp = nullptr; // Number of set bits in delayRingVarp
AstVar* delayRingLiveCountVarp = nullptr; // Number of threads in delayRingVarp
AstVar* delayRingWrappedVarp = nullptr; // All slots written since the last clear
AstVar* doneLVarp = nullptr; // SAnd LHS done-latch
AstVar* doneRVarp = nullptr; // SAnd RHS done-latch
AstNodeExpr* stateSigp = nullptr; // Combinational state signal; OWNED during lowering
AstNodeExpr* stateCountSigp = nullptr; // Cover-sequence match count; OWNED during lowering
AstNodeExpr* stateCountOverflowSigp = nullptr; // Count overflow; OWNED during lowering
bool delayRingCounts = false; // Ring slots hold cover-sequence match counts
bool needsReg = false; // True if vertex has incoming clocked edge
};
@@ -71,12 +76,11 @@ public:
bool m_isMatch = false;
// OWNED throughout-guard condition clones; IEEE 1800-2023 16.9.9
std::vector<AstNodeExpr*> m_throughoutConds;
// Nonzero for a bitset ring-buffer vertex for ## delays.
// Nonzero for a ring-buffer vertex for ## delays.
bool m_isFixedDelayRing = false;
unsigned m_delayRingSize = 0; // Number of ring slots. Range: max-min+1.
AstNodeExpr* m_delayRingClearCondp = nullptr; // local RHS for pure-boolean range
AstNodeExpr* m_delayRingAdvanceCondp = nullptr; // Advance only when this condition holds
SvaStateVertex* m_matchCountRingp = nullptr; // Ring supplying this checked match's count
bool m_replayAbortReject = false; // Compressed repetition needs per-thread abort replay
// OWNED; enclosing-abort fire condition clearing state or suppressing guard rejection
AstNodeExpr* m_abortClearp = nullptr;
@@ -120,7 +124,7 @@ public:
if (m_delayRingSize) {
name += "\\n";
name += m_isFixedDelayRing ? "fixed chain " : "range chain ";
name += cvtToStr(m_delayRingSize) + " bits";
name += cvtToStr(m_delayRingSize) + " slots";
}
return name;
}
@@ -587,11 +591,18 @@ class SvaNfaBuilder final {
bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) {
const unsigned minDelay = getConstUInt(delayp->lhsp());
if (delayp->isUnbounded()) {
// `##[M:$]`: wait M cycles, then self-loop waiting for the match
// condition. Unbounded = liveness, so no reject.
// `##[M:$]`: wait M cycles, then retain every matured attempt in a
// one-slot ring while also exposing the first eligible tick.
currentp = addDelayChain(currentp, minDelay, flp);
guardedEdge(currentp, currentp, flp);
currentp->m_isUnbounded = true;
SvaStateVertex* const waitRingp = addDelayChain(currentp, 1, flp, false);
// The ring's live count is updated in NBA, so this link retains the previous
// tick's live attempts alongside new arrivals for the next tick.
guardedLink(waitRingp, waitRingp, flp);
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
guardedLink(currentp, mergeVtxp, flp);
guardedLink(waitRingp, mergeVtxp, flp);
currentp = mergeVtxp;
currentp->m_isUnbounded = true; // Liveness, so no reject.
m_inUnboundedScope = true;
return true;
}
@@ -608,8 +619,8 @@ class SvaNfaBuilder final {
// blowup is possible.
constexpr unsigned kChainLimit = 256;
// IEEE 1800-2023 16.14.3: only a small bounded range before a plain
// boolean enumerates every end-of-match below. The counter FSM drops
// overlapping ends and the nested-sequence merge collapses them, so
// boolean enumerates every end-of-match below. The large-range ring uses
// first-match clearing and the nested-sequence merge collapses ends, so
// reject those for a cover sequence rather than under-count.
if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) {
warnEndpointUnsupported(flp, "this ranged cycle delay");
@@ -846,7 +857,6 @@ class SvaNfaBuilder final {
nextVtxp->m_replayAbortReject = true;
SvaStateVertex* const checkVtxp = scopedCreateVertex();
guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp);
checkVtxp->m_matchCountRingp = nextVtxp;
guardedLink(checkVtxp, mergeVtxp, flp);
if (m_isCoverSeq) consMidSources.push_back(checkVtxp);
}
@@ -919,8 +929,8 @@ class SvaNfaBuilder final {
const bool hasMax = repp->maxCountp() != nullptr;
const unsigned maxN = hasMax ? getConstUInt(repp->maxCountp()) : minN;
if (m_isCoverSeq) {
// Several matches may wait across false cycles, but the ring stores only one bit for
// them, so a cover sequence action block could run too few times.
// Several attempts may coalesce in the one-bit wait vertex before reaching the ring,
// so a cover sequence action block could run too few times.
warnEndpointUnsupported(flp, "a goto repetition");
return BuildResult::failWithError();
}
@@ -968,12 +978,6 @@ class SvaNfaBuilder final {
return {mergeVtxp, nullptr, {}};
}
// Free a dropped sub-result condition that is not linked into the AST
// (abort folds synthesize unparented finalCondp trees).
static void freeUnlinkedCondp(AstNodeExpr* condp) {
if (condp && !condp->backp()) VL_DO_DANGLING(condp->deleteTree(), condp);
}
// Build merge vertex for SOr / LogOr: both branches feed into one vertex.
BuildResult buildOrMerge(AstNodeExpr* lhsp, AstNodeExpr* rhsp, SvaStateVertex* entryVtxp,
FileLine* flp) {
@@ -989,24 +993,35 @@ class SvaNfaBuilder final {
// end reaches the merge vertex below, so reject sequence operands rather
// than under-count. Plain boolean disjunction has one end per cycle and
// is handled by the OR-fold.
if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) {
const bool booleanOnly = lhs.termVertexp == entryVtxp && lhs.finalCondp
&& rhs.termVertexp == entryVtxp && rhs.finalCondp;
if (m_isCoverSeq && !booleanOnly) {
warnEndpointUnsupported(flp, "a sequence operand of 'or'");
freeUnlinkedCondp(lhs.finalCondp);
freeUnlinkedCondp(rhs.finalCondp);
return BuildResult::failWithError();
}
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
if (lhs.finalCondp) {
guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)),
if (booleanOnly) {
UASSERT_OBJ(lhs.finalCondp && rhs.finalCondp, lhsp,
"Single-cycle SOr operands must have finalCondp");
guardedLink(entryVtxp, mergeVtxp,
new AstLogOr{flp, sampled(lhs.finalCondp->cloneTreePure(false)),
sampled(rhs.finalCondp->cloneTreePure(false))},
flp);
} else {
guardedLink(lhs.termVertexp, mergeVtxp, flp);
}
if (rhs.finalCondp) {
guardedLink(rhs.termVertexp, mergeVtxp, sampled(rhs.finalCondp->cloneTreePure(false)),
flp);
} else {
guardedLink(rhs.termVertexp, mergeVtxp, flp);
if (lhs.finalCondp) {
guardedLink(lhs.termVertexp, mergeVtxp,
sampled(lhs.finalCondp->cloneTreePure(false)), flp);
} else {
guardedLink(lhs.termVertexp, mergeVtxp, flp);
}
if (rhs.finalCondp) {
guardedLink(rhs.termVertexp, mergeVtxp,
sampled(rhs.finalCondp->cloneTreePure(false)), flp);
} else {
guardedLink(rhs.termVertexp, mergeVtxp, flp);
}
}
freeUnlinkedCondp(lhs.finalCondp);
freeUnlinkedCondp(rhs.finalCondp);
@@ -1585,6 +1600,12 @@ class SvaNfaBuilder final {
}
public:
// Free a dropped sub-result condition that is not linked into the AST
// (abort folds synthesize unparented finalCondp trees).
static void freeUnlinkedCondp(AstNodeExpr* condp) {
if (condp && !condp->backp()) VL_DO_DANGLING(condp->deleteTree(), condp);
}
SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames,
bool isCoverSeq = false, bool isSeqEvent = false, bool isCover = false)
: m_graph{graph}
@@ -1782,17 +1803,44 @@ class SvaNfaLowering final {
if (!ap) return bp;
return new AstLogOr{flp, ap, bp};
}
static AstNodeExpr* addThreadFailCountp(FileLine* const flp,
AstNodeExpr* const totalThreadFailCountp,
AstNodeExpr* const contributionp,
AstNodeExpr* const enablep = nullptr) {
static AstNodeExpr* addCountp(FileLine* const flp, AstNodeExpr* const totalCountp,
AstNodeExpr* const contributionp,
AstNodeExpr* const enablep = nullptr) {
// contribution = enable ? contribution : 0;
AstNodeExpr* const enabledContributionp
= enablep ? new AstCond{flp, enablep, contributionp,
newTypedConstp(flp, contributionp->dtypep(), 0)}
: contributionp;
if (!totalThreadFailCountp) return enabledContributionp;
return new AstAdd{flp, totalThreadFailCountp, enabledContributionp};
if (!totalCountp) return enabledContributionp;
return new AstAdd{flp, totalCountp, enabledContributionp};
}
static AstNodeExpr* gateCheckedCountp(FileLine* const flp, AstNodeExpr* const countp,
AstNodeExpr*& overflowp, AstNodeExpr* const enablep) {
if (overflowp) {
overflowp = new AstLogAnd{flp, enablep->cloneTreePure(false), overflowp};
}
return addCountp(flp, nullptr, countp, enablep);
}
static AstNodeExpr* addCheckedCountp(FileLine* const flp, AstNodeExpr* const totalCountp,
AstNodeExpr* const contributionp, AstNodeExpr*& overflowp,
AstNodeExpr* const contributionOverflowp = nullptr) {
if (contributionOverflowp) { overflowp = orExprs(flp, overflowp, contributionOverflowp); }
if (!totalCountp) return contributionp;
AstNodeExpr* const oldTotalp = totalCountp->cloneTreePure(false);
AstNodeExpr* const resultp = new AstAdd{flp, totalCountp, contributionp};
overflowp
= orExprs(flp, overflowp, new AstLt{flp, resultp->cloneTreePure(false), oldTotalp});
return resultp;
}
AstIf* newCountOverflowWarning(FileLine* const flp, AstNodeExpr* const condp) const {
AstDisplay* const dispp = new AstDisplay{
flp, VDisplayType::DT_WARNING,
"Cover sequence match count overflowed 32-bit storage; results may be incorrect.",
nullptr, nullptr};
dispp->fmtp()->timeunit(m_modp->timeunit());
AstIf* const ifp = new AstIf{flp, condp, dispp};
ifp->branchPred(VBranchPred::BP_UNLIKELY);
return ifp;
}
static AstNodeExpr* killActive(LowerCtx& c) {
return new AstNeq{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::READ},
@@ -1803,47 +1851,57 @@ class SvaNfaLowering final {
if (!exprp) return nullptr;
return new AstLogAnd{c.flp, exprp, notKillActive(c)};
}
static AstNodeExpr* nextRingIndex(FileLine* flp, AstVar* idxp, uint32_t size) {
static AstNodeExpr* nextRingIndex(FileLine* flp, AstNodeExpr* idxExprp, uint32_t size) {
const auto u32Const = [flp](uint32_t value) {
return new AstConst{flp, AstConst::WidthedValue{}, 32, value};
};
UASSERT_OBJ(size > 0, idxp, "Ring size must be positive");
if (size == 1) return u32Const(0);
UASSERT_OBJ(size > 0, idxExprp, "Ring size must be positive");
if (size == 1) {
idxExprp->deleteTree();
return u32Const(0);
}
// idx == size - 1 ? 0 : idx + 1
AstAdd* const addp = new AstAdd{flp, new AstVarRef{flp, idxp, VAccess::READ}, u32Const(1)};
addp->dtypeFrom(idxp);
AstCond* const condp = new AstCond{
flp, new AstEq{flp, new AstVarRef{flp, idxp, VAccess::READ}, u32Const(size - 1)},
u32Const(0), addp};
condp->dtypeFrom(idxp);
AstAdd* const addp = new AstAdd{flp, idxExprp->cloneTreePure(false), u32Const(1)};
addp->dtypeFrom(idxExprp);
AstCond* const condp
= new AstCond{flp, new AstEq{flp, idxExprp, u32Const(size - 1)}, u32Const(0), addp};
condp->dtypeFrom(idxExprp);
return condp;
}
static AstNodeExpr* delayRingBit(FileLine* flp, AstVar* ringp, AstNodeExpr* idxExprp,
VAccess access = VAccess::READ) {
static AstNodeExpr* delayRingSlot(FileLine* flp, SvaStateVertex* vtxp, AstNodeExpr* idxExprp,
VAccess access = VAccess::READ) {
// ring[idx]
return new AstSel{flp, new AstVarRef{flp, ringp, access}, idxExprp, 1};
AstVar* const ringp = vtxp->datap()->delayRingVarp;
AstVarRef* const refp = new AstVarRef{flp, ringp, access};
if (vtxp->datap()->delayRingCounts) return new AstArraySel{flp, refp, idxExprp};
return new AstSel{flp, refp, idxExprp, 1};
}
static AstNodeExpr* delayRingHeadCount(FileLine* flp, SvaStateVertex* vtxp) {
AstNodeExpr* const headp
= new AstVarRef{flp, vtxp->datap()->delayRingHeadVarp, VAccess::READ};
if (vtxp->datap()->delayRingCounts) return headp;
return new AstExtend{flp, headp, vtxp->datap()->delayRingLiveCountVarp->dtypep()->width()};
}
static AstNodeExpr* delayRingAtLastIndex(FileLine* const flp, AstVar* const idxp,
const uint32_t size) {
return new AstEq{flp, new AstVarRef{flp, idxp, VAccess::READ},
new AstConst{flp, AstConst::WidthedValue{}, 32, size - 1}};
}
static AstNodeExpr* delayRingOutput(FileLine* const flp, SvaStateVertex* const vtxp) {
static AstNodeExpr* delayRingOutputCount(FileLine* const flp, SvaStateVertex* const vtxp) {
AstVar* const idxp = vtxp->datap()->delayRingIdxVarp;
const uint32_t size = vtxp->m_delayRingSize;
AstNodeExpr* const outgoingIdxp = vtxp->m_isFixedDelayRing
? new AstVarRef{flp, idxp, VAccess::READ}
: nextRingIndex(flp, idxp, size);
AstNodeExpr* outgoingValidp
= new AstVarRef{flp, vtxp->datap()->delayRingWrappedVarp, VAccess::READ};
if (!vtxp->m_isFixedDelayRing) {
outgoingValidp
= new AstLogOr{flp, outgoingValidp, delayRingAtLastIndex(flp, idxp, size)};
}
return new AstLogAnd{flp, outgoingValidp,
delayRingBit(flp, vtxp->datap()->delayRingVarp, outgoingIdxp)};
AstVar* const liveCountVarp = vtxp->datap()->delayRingLiveCountVarp;
return new AstCond{flp, outgoingValidp, delayRingHeadCount(flp, vtxp),
newTypedConstp(flp, liveCountVarp->dtypep(), 0)};
}
static AstNodeExpr* delayRingHasLiveBitsp(FileLine* const flp, AstVar* const liveCountVarp) {
static AstNodeExpr* delayRingHasLiveThreadsp(FileLine* const flp,
AstVar* const liveCountVarp) {
// active = live_count != 0;
return new AstNeq{flp, new AstVarRef{flp, liveCountVarp, VAccess::READ},
newTypedConstp(flp, liveCountVarp->dtypep(), 0)};
@@ -1853,14 +1911,14 @@ class SvaNfaLowering final {
struct SignalSet final {
AstNodeExpr* terminalActivep = nullptr; // OR of all successful terminal matches
AstNodeExpr* matchCountp = nullptr; // NFA paths completing the sequence this tick
AstNodeExpr* matchCountOverflowp = nullptr; // Match count overflow this tick
AstNodeExpr* rejectBasep = nullptr; // Reject when a terminal match fails
AstNodeExpr* requiredStepRejectp = nullptr; // Per-source reject from rejectOnFail Links
AstNodeExpr* throughoutRejectp = nullptr; // Reject when a throughout guard drops
AstNodeExpr* threadFailCountp = nullptr; // Number of threads rejected on this tick
};
// Phase 2/2b/2c: Emit NBA state-update always blocks for registered vertices,
// delay rings, and SAnd combiner done-latches.
// Phase 2/2b/2c: Emit NBA register updates and in-place delay-ring writes.
// Phase 2: State register NBA always block. Each clocked-edge target
// latches the OR of its incoming contributions.
void emitStateRegisterNba(LowerCtx& c) {
@@ -1912,18 +1970,18 @@ class SvaNfaLowering final {
new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), bodyp});
}
// Phase 2b: Bitset ring-buffer delay always block.
void emitDelayRingNba(LowerCtx& c) {
// Phase 2b: In-place ring storage and registered ring metadata.
void emitDelayRingUpdates(LowerCtx& c, const bool countMatches) {
for (int ri = 0; ri < c.N; ++ri) {
SvaStateVertex* const vtxp = c.vtx[ri];
if (!vtxp->datap()->delayRingVarp) continue;
AstVar* const ringp = vtxp->datap()->delayRingVarp;
AstVar* const idxp = vtxp->datap()->delayRingIdxVarp;
AstVar* const liveCountVarp = vtxp->datap()->delayRingLiveCountVarp;
AstVar* const wrappedp = vtxp->datap()->delayRingWrappedVarp;
const uint32_t size = static_cast<uint32_t>(vtxp->m_delayRingSize);
AstNodeExpr* incomingp = nullptr;
AstNodeExpr* incomingOverflowp = nullptr;
for (const V3GraphEdge& edger : vtxp->inEdges()) {
++m_statDelayRingEdgeVisits;
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
@@ -1932,33 +1990,83 @@ class SvaNfaLowering final {
const int fi = tedger.fromVtxp()->color();
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp, c.vtx[fi],
"Delay-ring incoming source missing stateSig");
AstNodeExpr* contribp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
contribp = andCond(c.flp, contribp, tedger.m_condp);
UASSERT_OBJ(!countMatches || c.vtx[fi]->datap()->stateCountSigp, c.vtx[fi],
"Delay-ring incoming source missing stateCountSig");
// IEEE 1800-2023 16.14.1/16.14.3: assertion alternatives belong to one
// evaluation; only cover sequence counts each end-of-match.
AstNodeExpr* contribp
= countMatches
? c.vtx[fi]->datap()->stateCountSigp->cloneTreePure(false)
: new AstExtend{c.flp,
c.vtx[fi]->datap()->stateSigp->cloneTreePure(false),
m_u32DTypep->width()};
AstNodeExpr* contribOverflowp
= countMatches && c.vtx[fi]->datap()->stateCountOverflowSigp
? c.vtx[fi]->datap()->stateCountOverflowSigp->cloneTreePure(false)
: nullptr;
if (tedger.m_condp) {
AstNodeExpr* const enablep = tedger.m_condp->cloneTreePure(false);
contribp = countMatches
? gateCheckedCountp(c.flp, contribp, contribOverflowp, enablep)
: addCountp(c.flp, nullptr, contribp, enablep);
}
if (c.disableExprp) {
AstNodeExpr* const notDisp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
contribp = new AstLogAnd{c.flp, contribp, notDisp};
contribp = countMatches
? gateCheckedCountp(c.flp, contribp, contribOverflowp, notDisp)
: addCountp(c.flp, nullptr, contribp, notDisp);
}
incomingp = orExprs(c.flp, incomingp, contribp);
incomingp = countMatches ? addCheckedCountp(c.flp, incomingp, contribp,
incomingOverflowp, contribOverflowp)
: addCountp(c.flp, incomingp, contribp);
}
UASSERT_OBJ(incomingp, vtxp, "Delay ring has no incoming edge");
// ring[idx] <= incoming;
AstAssignDly* const writeIncomingp = new AstAssignDly{
const bool storesCounts = vtxp->datap()->delayRingCounts;
AstNodeExpr* const incomingCountp
= storesCounts
? incomingp->cloneTreePure(false)
: new AstExtend{c.flp,
new AstNeq{c.flp, incomingp->cloneTreePure(false),
newTypedConstp(c.flp, incomingp->dtypep(), 0)},
m_u32DTypep->width()};
AstNodeExpr* const storedIncomingp
= storesCounts ? incomingp
: new AstNeq{c.flp, incomingp,
newTypedConstp(c.flp, incomingp->dtypep(), 0)};
// Only this block accesses the storage. Other processes read the cached head, so
// the ring can be updated in place without an NBA shadow copy.
AstAssign* const writeIncomingp = new AstAssign{
c.flp,
delayRingBit(c.flp, ringp, new AstVarRef{c.flp, idxp, VAccess::READ},
VAccess::WRITE),
incomingp};
delayRingSlot(c.flp, vtxp, new AstVarRef{c.flp, idxp, VAccess::READ},
VAccess::WRITE),
storedIncomingp};
AstNode* updateBodyp = writeIncomingp;
// live_count <= live_count + incoming_bit - outgoing_bit;
const int liveCountWidth = liveCountVarp->dtypep()->width();
AstNodeExpr* const incomingIncrementp
= new AstExtend{c.flp, incomingp->cloneTreePure(false), liveCountWidth};
AstNodeExpr* const outgoingp = delayRingOutput(c.flp, vtxp);
AstSub* const nextLiveCountp
= new AstSub{c.flp,
new AstAdd{c.flp, new AstVarRef{c.flp, liveCountVarp, VAccess::READ},
incomingIncrementp},
new AstExtend{c.flp, outgoingp, liveCountWidth}};
// live_count <= live_count - outgoing_count + incoming_count;
AstNodeExpr* const outgoingp = delayRingOutputCount(c.flp, vtxp);
AstNodeExpr* const afterOutgoingp
= new AstSub{c.flp, new AstVarRef{c.flp, liveCountVarp, VAccess::READ}, outgoingp};
AstNodeExpr* const oldAfterOutgoingp = afterOutgoingp->cloneTreePure(false);
AstAdd* const nextLiveCountp = new AstAdd{c.flp, afterOutgoingp, incomingCountp};
if (storesCounts) {
AstNodeExpr* const liveOverflowp
= new AstLt{c.flp, nextLiveCountp->cloneTreePure(false), oldAfterOutgoingp};
AstNodeExpr* const overflowp = orExprs(c.flp, incomingOverflowp, liveOverflowp);
updateBodyp = newCountOverflowWarning(c.flp, overflowp);
updateBodyp->addNext(writeIncomingp);
} else {
VL_DO_DANGLING(oldAfterOutgoingp->deleteTree(), oldAfterOutgoingp);
}
// Cache the outgoing slot for the next index after the write. Reading after
// writing also handles one-slot rings and two-slot range rings without alias cases.
AstNodeExpr* nextHeadIdxp
= nextRingIndex(c.flp, new AstVarRef{c.flp, idxp, VAccess::READ}, size);
if (!vtxp->m_isFixedDelayRing) {
nextHeadIdxp = nextRingIndex(c.flp, nextHeadIdxp, size);
}
updateBodyp->addNext(new AstAssignDly{
c.flp, new AstVarRef{c.flp, vtxp->datap()->delayRingHeadVarp, VAccess::WRITE},
delayRingSlot(c.flp, vtxp, nextHeadIdxp)});
updateBodyp->addNext(new AstAssignDly{
c.flp, new AstVarRef{c.flp, liveCountVarp, VAccess::WRITE}, nextLiveCountp});
// wrapped <= wrapped || idx == size - 1;
@@ -1967,9 +2075,9 @@ class SvaNfaLowering final {
new AstLogOr{c.flp, new AstVarRef{c.flp, wrappedp, VAccess::READ},
delayRingAtLastIndex(c.flp, idxp, size)}});
// idx <= next_idx;
updateBodyp->addNext(new AstAssignDly{c.flp,
new AstVarRef{c.flp, idxp, VAccess::WRITE},
nextRingIndex(c.flp, idxp, size)});
updateBodyp->addNext(new AstAssignDly{
c.flp, new AstVarRef{c.flp, idxp, VAccess::WRITE},
nextRingIndex(c.flp, new AstVarRef{c.flp, idxp, VAccess::READ}, size)});
if (vtxp->m_delayRingAdvanceCondp) {
updateBodyp = new AstIf{
c.flp, sampled(vtxp->m_delayRingAdvanceCondp->cloneTreePure(false)),
@@ -2098,26 +2206,32 @@ class SvaNfaLowering final {
srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)};
}
if (needMatchCount) {
AstNodeExpr* contributionp = nullptr;
SvaStateVertex* const countRingp = tedgep->fromVtxp()->m_matchCountRingp;
if (countRingp) {
AstVar* const liveCountVarp
= c.vtx[countRingp->color()]->datap()->delayRingLiveCountVarp;
contributionp = new AstCond{c.flp, srcSigp->cloneTreePure(false),
new AstVarRef{c.flp, liveCountVarp, VAccess::READ},
newTypedConstp(c.flp, liveCountVarp->dtypep(), 0)};
} else {
contributionp = new AstExtend{c.flp, srcSigp->cloneTreePure(false),
m_u32DTypep->width()};
UASSERT_OBJ(c.vtx[fi]->datap()->stateCountSigp, tedgep->fromVtxp(),
"Terminal-link source missing stateCountSig");
AstNodeExpr* contributionp
= c.vtx[fi]->datap()->stateCountSigp->cloneTreePure(false);
AstNodeExpr* contributionOverflowp
= c.vtx[fi]->datap()->stateCountOverflowSigp
? c.vtx[fi]->datap()->stateCountOverflowSigp->cloneTreePure(false)
: nullptr;
if (tedgep->m_condp) {
contributionp = gateCheckedCountp(c.flp, contributionp, contributionOverflowp,
tedgep->m_condp->cloneTreePure(false));
}
sigs.matchCountp = addThreadFailCountp(c.flp, sigs.matchCountp, contributionp);
if (snapshotOkp) {
contributionp = gateCheckedCountp(c.flp, contributionp, contributionOverflowp,
snapshotOkp->cloneTreePure(false));
}
sigs.matchCountp
= addCheckedCountp(c.flp, sigs.matchCountp, contributionp,
sigs.matchCountOverflowp, contributionOverflowp);
}
if (tedgep->fromVtxp()->m_delayRingSize && !tedgep->fromVtxp()->m_isFixedDelayRing) {
sigs.terminalActivep
= orExprs(c.flp, sigs.terminalActivep, srcSigp->cloneTreePure(false));
// reject |= ring[next_idx] && final_condition;
AstNodeExpr* expireContribp = delayRingOutput(c.flp, tedgep->fromVtxp());
// reject |= (wrapped || idx == size - 1) && head && final_condition;
AstNodeExpr* expireContribp = delayRingOutputCount(c.flp, tedgep->fromVtxp());
expireContribp = andCond(c.flp, expireContribp, tedgep->m_condp);
if (snapshotOkp) {
expireContribp
@@ -2147,7 +2261,7 @@ class SvaNfaLowering final {
? static_cast<AstNodeExpr*>(
new AstVarRef{c.flp, delayRingLiveCountVarp, VAccess::READ})
: new AstExtend{c.flp, stateExprp->cloneTreePure(false), m_u32DTypep->width()};
return addThreadFailCountp(c.flp, nullptr, activeThreadCountp, enablep);
return addCountp(c.flp, nullptr, activeThreadCountp, enablep);
}
// Phase 3b: Throughout-drop and ring-wide abort rejection.
@@ -2161,7 +2275,7 @@ class SvaNfaLowering final {
stateExprp = new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::READ};
} else if (c.vtx[i]->datap()->delayRingVarp && c.vtx[i]->m_isFixedDelayRing) {
stateExprp
= delayRingHasLiveBitsp(c.flp, c.vtx[i]->datap()->delayRingLiveCountVarp);
= delayRingHasLiveThreadsp(c.flp, c.vtx[i]->datap()->delayRingLiveCountVarp);
} else {
UASSERT_OBJ(c.vtx[i]->datap()->stateSigp, c.vtx[i],
"Throughout-conds vertex missing state representation");
@@ -2196,8 +2310,7 @@ class SvaNfaLowering final {
AstNodeExpr* const contributionp
= newThroughoutThreadFailCountp(c, c.vtx[i]->datap()->delayRingLiveCountVarp,
stateExprp, rejectCondp->cloneTreePure(false));
sigs.threadFailCountp
= addThreadFailCountp(c.flp, sigs.threadFailCountp, contributionp);
sigs.threadFailCountp = addCountp(c.flp, sigs.threadFailCountp, contributionp);
if (c.vtx[i]->m_abortRejectp && c.vtx[i]->m_delayRingAdvanceCondp) {
// An advancing ring thread also occupies its same-tick match vertex in the
// unrolled NFA, so abort rejection must replay both fail actions.
@@ -2208,7 +2321,7 @@ class SvaNfaLowering final {
c, c.vtx[i]->datap()->delayRingLiveCountVarp, stateExprp,
abortAndAdvancep);
sigs.threadFailCountp
= addThreadFailCountp(c.flp, sigs.threadFailCountp, matchContributionp);
= addCountp(c.flp, sigs.threadFailCountp, matchContributionp);
}
}
sigs.throughoutRejectp = orExprs(c.flp, sigs.throughoutRejectp,
@@ -2257,7 +2370,7 @@ class SvaNfaLowering final {
AstNodeExpr* const rawFailp = new AstLogAnd{c.flp, srcSigp, notCondp};
if (needThreadFailCount) {
// thread_fail_count += fail;
sigs.threadFailCountp = addThreadFailCountp(
sigs.threadFailCountp = addCountp(
c.flp, sigs.threadFailCountp,
new AstExtend{c.flp, rawFailp->cloneTreePure(false), m_u32DTypep->width()});
}
@@ -2268,15 +2381,16 @@ class SvaNfaLowering final {
computeThroughoutReject(c, sigs, needThroughoutThreadFailCount);
if (sigs.threadFailCountp) {
sigs.threadFailCountp
= addThreadFailCountp(c.flp, nullptr, sigs.threadFailCountp, notKillActive(c));
= addCountp(c.flp, nullptr, sigs.threadFailCountp, notKillActive(c));
}
if (sigs.matchCountp) {
if (c.matchCondp) {
sigs.matchCountp = addThreadFailCountp(
c.flp, nullptr, sigs.matchCountp, sampled(c.matchCondp->cloneTreePure(false)));
sigs.matchCountp
= gateCheckedCountp(c.flp, sigs.matchCountp, sigs.matchCountOverflowp,
sampled(c.matchCondp->cloneTreePure(false)));
}
sigs.matchCountp
= addThreadFailCountp(c.flp, nullptr, sigs.matchCountp, notKillActive(c));
sigs.matchCountp = gateCheckedCountp(c.flp, sigs.matchCountp, sigs.matchCountOverflowp,
notKillActive(c));
}
sigs.terminalActivep = gateNotKill(c, sigs.terminalActivep);
sigs.rejectBasep = gateNotKill(c, sigs.rejectBasep);
@@ -2288,6 +2402,10 @@ class SvaNfaLowering final {
for (int i = 0; i < c.N; ++i) {
AstNodeExpr*& sigp = c.vtx[i]->datap()->stateSigp;
if (sigp) VL_DO_DANGLING(sigp->deleteTree(), sigp);
AstNodeExpr*& countp = c.vtx[i]->datap()->stateCountSigp;
if (countp) VL_DO_DANGLING(countp->deleteTree(), countp);
AstNodeExpr*& overflowp = c.vtx[i]->datap()->stateCountOverflowSigp;
if (overflowp) VL_DO_DANGLING(overflowp->deleteTree(), overflowp);
}
// Disable iff gating (IEEE 1800-2023 16.12). The edge counter misses a
// continuously-true disable, so gate on the current level value too.
@@ -2312,8 +2430,8 @@ class SvaNfaLowering final {
sigs.requiredStepRejectp = new AstLogAnd{c.flp, sigs.requiredStepRejectp, notDisp};
}
if (sigs.matchCountp) {
sigs.matchCountp = addThreadFailCountp(
c.flp, nullptr, sigs.matchCountp,
sigs.matchCountp = gateCheckedCountp(
c.flp, sigs.matchCountp, sigs.matchCountOverflowp,
new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)});
}
}
@@ -2323,24 +2441,42 @@ class SvaNfaLowering final {
snapshotOkp = nullptr;
}
if (sigs.matchCountOverflowp) {
m_modp->addStmtsp(
new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false),
newCountOverflowWarning(c.flp, sigs.matchCountOverflowp)});
sigs.matchCountOverflowp = nullptr;
}
return sigs;
}
// Phase 1: Resolve combinational Links via fixed-point propagation.
void resolveLinks(LowerCtx& c, AstNodeExpr* triggerExprp) {
// datap() was freshly allocated in lower() -- all stateSigp start null.
void resolveLinks(LowerCtx& c, AstNodeExpr* triggerExprp, const bool countMatches) {
// datap() was freshly allocated in lower() -- all state signals start null.
c.vtx[c.startIdx]->datap()->stateSigp = triggerExprp->cloneTreePure(false);
c.vtx[c.startIdx]->datap()->stateCountSigp
= new AstExtend{c.flp, triggerExprp->cloneTreePure(false), m_u32DTypep->width()};
for (int i = 0; i < c.N; ++i) {
if (c.vtx[i]->datap()->stateVarp) {
c.vtx[i]->datap()->stateSigp
= new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::READ};
c.vtx[i]->datap()->stateCountSigp = new AstExtend{
c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::READ},
m_u32DTypep->width()};
} else if (c.vtx[i]->datap()->delayRingVarp) {
if (c.vtx[i]->m_isFixedDelayRing) {
// state = ring[idx];
c.vtx[i]->datap()->stateSigp = delayRingOutput(c.flp, c.vtx[i]);
} else {
// state_count = wrapped ? head : 0; state = state_count != 0;
AstNodeExpr* const countp = delayRingOutputCount(c.flp, c.vtx[i]);
c.vtx[i]->datap()->stateSigp
= delayRingHasLiveBitsp(c.flp, c.vtx[i]->datap()->delayRingLiveCountVarp);
= new AstNeq{c.flp, countp->cloneTreePure(false),
newTypedConstp(c.flp, m_u32DTypep, 0)};
c.vtx[i]->datap()->stateCountSigp = countp;
} else {
c.vtx[i]->datap()->stateSigp = delayRingHasLiveThreadsp(
c.flp, c.vtx[i]->datap()->delayRingLiveCountVarp);
c.vtx[i]->datap()->stateCountSigp = new AstVarRef{
c.flp, c.vtx[i]->datap()->delayRingLiveCountVarp, VAccess::READ};
}
}
}
@@ -2375,6 +2511,13 @@ class SvaNfaLowering final {
c.vtx[i]->datap()->stateSigp);
}
c.vtx[i]->datap()->stateSigp = new AstLogAnd{c.flp, bothp, oneNowp};
if (c.vtx[i]->datap()->stateCountSigp) {
VL_DO_DANGLING(c.vtx[i]->datap()->stateCountSigp->deleteTree(),
c.vtx[i]->datap()->stateCountSigp);
}
c.vtx[i]->datap()->stateCountSigp
= new AstExtend{c.flp, c.vtx[i]->datap()->stateSigp->cloneTreePure(false),
m_u32DTypep->width()};
}
for (int ti = 0; ti < c.N; ++ti) {
@@ -2384,19 +2527,53 @@ class SvaNfaLowering final {
continue;
}
AstNodeExpr* nextStatep = nullptr;
AstNodeExpr* nextStateCountp = nullptr;
AstNodeExpr* nextStateCountOverflowp = nullptr;
for (const V3GraphEdge& er : c.vtx[ti]->inEdges()) {
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
const int fi = te.fromVtxp()->color();
if (!c.vtx[fi]->datap()->stateSigp) continue;
UASSERT_OBJ(c.vtx[fi]->datap()->stateCountSigp, c.vtx[fi],
"Link source missing stateCountSig");
AstNodeExpr* const contributionp = andCond(
c.flp, c.vtx[fi]->datap()->stateSigp->cloneTreePure(false), te.m_condp);
nextStatep = orExprs(c.flp, nextStatep, contributionp);
AstNodeExpr* countContributionp
= c.vtx[fi]->datap()->stateCountSigp->cloneTreePure(false);
AstNodeExpr* countContributionOverflowp
= countMatches && c.vtx[fi]->datap()->stateCountOverflowSigp
? c.vtx[fi]->datap()->stateCountOverflowSigp->cloneTreePure(false)
: nullptr;
if (te.m_condp) {
AstNodeExpr* const enablep = te.m_condp->cloneTreePure(false);
countContributionp
= countMatches
? gateCheckedCountp(c.flp, countContributionp,
countContributionOverflowp, enablep)
: addCountp(c.flp, nullptr, countContributionp, enablep);
}
nextStateCountp
= countMatches
? addCheckedCountp(c.flp, nextStateCountp, countContributionp,
nextStateCountOverflowp,
countContributionOverflowp)
: addCountp(c.flp, nextStateCountp, countContributionp);
}
if (c.vtx[ti]->datap()->stateSigp) {
VL_DO_DANGLING(c.vtx[ti]->datap()->stateSigp->deleteTree(),
c.vtx[ti]->datap()->stateSigp);
}
c.vtx[ti]->datap()->stateSigp = nextStatep;
if (c.vtx[ti]->datap()->stateCountSigp) {
VL_DO_DANGLING(c.vtx[ti]->datap()->stateCountSigp->deleteTree(),
c.vtx[ti]->datap()->stateCountSigp);
}
c.vtx[ti]->datap()->stateCountSigp = nextStateCountp;
if (c.vtx[ti]->datap()->stateCountOverflowSigp) {
VL_DO_DANGLING(c.vtx[ti]->datap()->stateCountOverflowSigp->deleteTree(),
c.vtx[ti]->datap()->stateCountOverflowSigp);
}
c.vtx[ti]->datap()->stateCountOverflowSigp = nextStateCountOverflowp;
}
}
}
@@ -2532,11 +2709,8 @@ public:
const std::vector<const SvaTransEdge*> edges = graph.allEdges();
// Allocate per-vertex lowering data (stored via V3GraphVertex::userp()).
std::vector<std::unique_ptr<SvaVertexData>> vertexData(N);
for (int i = 0; i < N; ++i) {
vertexData[i] = std::make_unique<SvaVertexData>();
vtx[i]->userp(vertexData[i].get());
}
std::vector<SvaVertexData> vertexData(N);
for (int i = 0; i < N; ++i) vtx[i]->userp(&vertexData[i]);
// Identify registered vertices (targets of clocked edges).
for (int i = 0; i < N; ++i) {
@@ -2550,6 +2724,40 @@ public:
}
}
// A packed range ring can emit several live endpoints without needing per-slot counts.
// Widen only a ring whose input can already carry multiplicity.
std::vector<bool> stateMayHaveMultiplicity(N);
std::vector<bool> ringNeedsMultiplicity(N);
std::vector<int> multiplicityWork;
const bool countMatches = coverp && coverp->isCoverSeq() && !isSeqEvent;
if (countMatches) {
for (int i = 0; i < N; ++i) {
const bool multipleInputs = vtx[i]->inEdges().size() > 1;
const bool isRing = vtx[i]->m_delayRingSize;
ringNeedsMultiplicity[i] = isRing && multipleInputs;
const bool startsMultiplicity = isRing
? !vtx[i]->m_isFixedDelayRing
: multipleInputs && !vtx[i]->datap()->needsReg;
if (startsMultiplicity) {
stateMayHaveMultiplicity[i] = true;
multiplicityWork.push_back(i);
}
}
for (size_t wi = 0; wi < multiplicityWork.size(); ++wi) {
for (const V3GraphEdge& edger : vtx[multiplicityWork[wi]]->outEdges()) {
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
const int ti = tedger.toVtxp()->color();
const bool isRing = vtx[ti]->m_delayRingSize;
if (isRing) ringNeedsMultiplicity[ti] = true;
// Match and reject sinks have no outgoing edges, so they stop naturally.
const bool stopsMultiplicity = !isRing && vtx[ti]->datap()->needsReg;
if (stateMayHaveMultiplicity[ti] || stopsMultiplicity) { continue; }
stateMayHaveMultiplicity[ti] = true;
multiplicityWork.push_back(ti);
}
}
}
AstVar* const killVarp
= new AstVar{flp, VVarType::MODULETEMP, baseName + "__kill", m_u32DTypep};
killVarp->lifetime(VLifetime::STATIC_EXPLICIT);
@@ -2571,14 +2779,31 @@ public:
}
if (vtx[i]->m_delayRingSize) {
const std::string base = baseName + "__d" + std::to_string(i);
// bit [size-1:0] ring;
AstNodeDType* const ringDTypep = m_modp->findBitDType(
vtx[i]->m_delayRingSize, vtx[i]->m_delayRingSize, VSigning::UNSIGNED);
const bool storesCounts = countMatches && ringNeedsMultiplicity[i];
AstNodeDType* ringDTypep = nullptr;
if (storesCounts) {
// int unsigned ring [size];
ringDTypep = new AstUnpackArrayDType{
flp, m_u32DTypep,
new AstRange{flp, static_cast<int>(vtx[i]->m_delayRingSize - 1), 0}};
v3Global.rootp()->typeTablep()->addTypesp(ringDTypep);
} else {
// bit [size-1:0] ring;
ringDTypep = m_modp->findBitDType(vtx[i]->m_delayRingSize,
vtx[i]->m_delayRingSize, VSigning::UNSIGNED);
}
AstVar* const ringp
= new AstVar{flp, VVarType::MODULETEMP, base + "_ring", ringDTypep};
ringp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(ringp);
vtx[i]->datap()->delayRingVarp = ringp;
vtx[i]->datap()->delayRingCounts = storesCounts;
AstVar* const headp
= new AstVar{flp, VVarType::MODULETEMP, base + "_head",
storesCounts ? m_u32DTypep : m_modp->findBitDType()};
headp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(headp);
vtx[i]->datap()->delayRingHeadVarp = headp;
// int unsigned idx;
AstVar* const idxp
= new AstVar{flp, VVarType::MODULETEMP, base + "_idx", m_u32DTypep};
@@ -2617,18 +2842,18 @@ public:
AstNodeExpr* const triggerExprp
= isSeqEvent ? new AstConst{flp, AstConst::BitTrue{}}
: assertOnCond(flp, assertp->userType(), assertp->directive());
resolveLinks(c, triggerExprp);
resolveLinks(c, triggerExprp, countMatches);
VL_DO_DANGLING(triggerExprp->deleteTree(), triggerExprp);
// Phase 2/2b/2c: Emit NBA state-update, delay-ring, and SAnd done-latch logic.
// Phase 2/2b/2c: Emit NBA register updates and in-place delay-ring writes.
emitStateRegisterNba(c);
emitDelayRingNba(c);
emitDelayRingUpdates(c, countMatches);
emitAndCombinerDoneLatchNba(c);
emitKillAckNba(c);
// Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig).
const SignalSet sigs = computeSignals(
c, needThreadFailCount, needThroughoutThreadFailCount, coverp && coverp->isCoverSeq());
const SignalSet sigs
= computeSignals(c, needThreadFailCount, needThroughoutThreadFailCount, countMatches);
// Strong s_always[m:n] end-of-simulation liveness: if any in-window state
// is still set at $finish, the universal-quantifier window never completed
@@ -2643,7 +2868,8 @@ public:
if (vtx[i]->datap()->stateVarp) {
pendingExprp = new AstVarRef{flp, vtx[i]->datap()->stateVarp, VAccess::READ};
} else if (vtx[i]->m_strongAlwaysRing) {
pendingExprp = delayRingHasLiveBitsp(flp, vtx[i]->datap()->delayRingLiveCountVarp);
pendingExprp
= delayRingHasLiveThreadsp(flp, vtx[i]->datap()->delayRingLiveCountVarp);
} else {
continue;
}
@@ -2665,7 +2891,7 @@ public:
m_modp->addStmtsp(new AstFinal{flp, new AstIf{flp, condp, firep}});
}
// Clear userp on every vertex before vertexData unique_ptrs are destroyed.
// Clear userp on every vertex before vertexData is destroyed.
for (int i = 0; i < N; ++i) vtx[i]->userp(nullptr);
return sigs;
}
@@ -2738,11 +2964,10 @@ class AssertNfaVisitor final : public VNVisitor {
// Recursion guard: IEEE 1800-2023 16.12.1 forbids recursive properties.
// V3Width emits "Recursive property call" for direct recursion before this
// pass runs; this catches any nested-inlining cycle that slips past.
if (m_inliningProps.count(propyp)) {
if (!m_inliningProps.emplace(propyp).second) {
funcrefp->v3error("Illegal recursive property reference"); // LCOV_EXCL_LINE
return; // LCOV_EXCL_LINE
}
m_inliningProps.insert(propyp);
struct Guard final {
std::set<const AstProperty*>& setr;
const AstProperty* keyp;
@@ -3281,6 +3506,7 @@ class AssertNfaVisitor final : public VNVisitor {
// A sequence event control is not an assertion directive; no default
// disable iff, no assertion control
const bool isSeqEvent = coverp && coverp->isSeqEvent();
const bool countMatches = isCoverSeq && !isSeqEvent;
// Inherit module defaults (IEEE 14.12, 16.15) when assertion has none.
if (!propp->sensesp() && m_defaultClockingp) {
propp->sensesp(m_defaultClockingp->sensesp()->cloneTree(true));
@@ -3327,6 +3553,14 @@ class AssertNfaVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
return;
}
if (countMatches && !v3Global.opt.coverageUser()) {
SvaNfaBuilder::freeUnlinkedCondp(result.finalCondp);
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
AstNode* const innerPropp = propp->propp();
innerPropp->replaceWith(new AstConst{flp, AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp);
return;
}
// Build succeeded. Now create snapshot mechanism for disable iff if needed.
// Done here (not before build) so failed builds don't pollute the AST.
@@ -3351,7 +3585,7 @@ class AssertNfaVisitor final : public VNVisitor {
AstNodeExpr* matchExprp = nullptr;
AstNodeExpr* outputExprp = m_loweringp->assembleResult(
assertp, negated, result.finalCondp, signals, needMatch ? &matchExprp : nullptr);
if (isCoverSeq) {
if (countMatches) {
UASSERT_OBJ(signals.matchCountp, coverp, "Cover sequence missing match count");
VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp);
propp->matchCountp(signals.matchCountp);
+1
View File
@@ -414,6 +414,7 @@ private:
flp, new AstVarRef{flp, queueVarp, VAccess::READWRITE}, VCMethod::DYN_POP,
new AstTime{nodep->fileline(), m_modp->timeunit()}};
popp->addPinsp(skewp->unlinkFrBack());
refp->access(VAccess::READWRITE); // Only conditionally assigned
popp->addPinsp(refp);
popp->dtypeSetVoid();
m_clockingp->addNextHere(
+1 -1
View File
@@ -66,7 +66,7 @@ VCMethod VCMethod::arrayMethod(const string& name) {
std::string VNUser::dumpStr(std::string (*fmtAddrp)(const void*)) const {
#ifdef VL_USER_TYPE_CHECKS
if (const int* const uip = std::get_if<int>(&m_u)) return "#"s + cvtToStr(*uip);
if (const uint64_t* const uip = std::get_if<uint64_t>(&m_u)) return "#"s + cvtToStr(*uip);
if (void* const* const upp = std::get_if<void*>(&m_u)) return fmtAddrp(*upp);
return "";
#else
+38 -39
View File
@@ -151,11 +151,11 @@ class VNUser final {
#ifdef VL_USER_TYPE_CHECKS
// monostate is an unwritten / cleared slot. It can be read as either form
// and yields nullptr/0.
std::variant<std::monostate, int, void*> m_u;
std::variant<std::monostate, uint64_t, void*> m_u;
#else
union {
void* up;
int ui;
uint64_t uq;
} m_u;
#endif
@@ -164,7 +164,7 @@ public:
VNUser() = default;
// non-explicit:
// cppcheck-suppress noExplicitConstructor
VNUser(int i) {
VNUser(uint64_t i) {
// VNUser{0} represents the monostate
if (i) m_u = i;
}
@@ -178,22 +178,22 @@ public:
typename std::enable_if<std::is_pointer<T>::value, T>::type to() const VL_MT_SAFE {
if (std::holds_alternative<std::monostate>(m_u)) return nullptr;
void* const* const upp = std::get_if<void*>(&m_u);
UASSERT_STATIC(upp, "AstNode user() slot written as int, read as pointer");
UASSERT_STATIC(upp, "AstNode user() slot written as uint64_t, read as pointer");
return reinterpret_cast<T>(*upp);
}
int toInt() const {
uint64_t toUQuad() const {
if (std::holds_alternative<std::monostate>(m_u)) return 0;
const int* const uip = std::get_if<int>(&m_u);
UASSERT_STATIC(uip, "AstNode user() slot written as pointer, read as int");
const uint64_t* const uip = std::get_if<uint64_t>(&m_u);
UASSERT_STATIC(uip, "AstNode user() slot written as pointer, read as uint64_t");
return *uip;
}
#else
VNUser() = default;
// non-explicit:
// cppcheck-suppress noExplicitConstructor
VNUser(int i) {
VNUser(uint64_t i) {
m_u.up = nullptr;
m_u.ui = i;
m_u.uq = i;
}
explicit VNUser(void* p) { m_u.up = p; }
~VNUser() = default;
@@ -202,7 +202,7 @@ public:
typename std::enable_if<std::is_pointer<T>::value, T>::type to() const VL_MT_SAFE {
return reinterpret_cast<T>(m_u.up);
}
int toInt() const { return m_u.ui; }
uint64_t toUQuad() const { return m_u.uq; }
#endif
VSymEnt* toSymEnt() const { return to<VSymEnt*>(); }
AstNode* toNodep() const VL_MT_SAFE { return to<AstNode*>(); }
@@ -492,14 +492,14 @@ class AstNode VL_NOT_FINAL {
// This member ordering both allows 64 bit alignment and puts associated data together
// (under VL_USER_TYPE_CHECKS a VNUser is larger than 64 bits, so this packing no
// longer holds; that build trades node size for catching int/pointer confusion)
VNUser m_user1u{0}; // Contains any information the user iteration routine wants
VNUser m_user1u{nullptr}; // Contains any information the user iteration routine wants
uint32_t m_user1Cnt = 0; // Mark of when userp was set
uint32_t m_user2Cnt = 0; // Mark of when userp was set
VNUser m_user2u{0}; // Contains any information the user iteration routine wants
VNUser m_user3u{0}; // Contains any information the user iteration routine wants
VNUser m_user2u{nullptr}; // Contains any information the user iteration routine wants
VNUser m_user3u{nullptr}; // Contains any information the user iteration routine wants
uint32_t m_user3Cnt = 0; // Mark of when userp was set
uint32_t m_user4Cnt = 0; // Mark of when userp was set
VNUser m_user4u{0}; // Contains any information the user iteration routine wants
VNUser m_user4u{nullptr}; // Contains any information the user iteration routine wants
// METHODS
void op1p(AstNode* nodep) {
@@ -708,61 +708,61 @@ public:
VNUser user1u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser1InUse::s_userBusy, "user1p used without AstUserInUse");
return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{nullptr});
}
AstNode* user1p() const VL_MT_STABLE { return user1u().toNodep(); }
void user1u(const VNUser& user) { m_user1u = user; m_user1Cnt = VNUser1InUse::s_userCntGbl; }
void user1p(void* userp) { user1u(VNUser{userp}); }
void user1(int val) { user1u(VNUser{val}); }
int user1() const { return user1u().toInt(); }
int user1Inc(int val = 1) { const int v = user1(); user1(v + val); return v; }
int user1Or(int val) { const int v = user1(); user1(v | val); return v; }
int user1SetOnce() { const int v = user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
void user1(uint64_t val) { user1u(VNUser{val}); }
uint64_t user1() const { return user1u().toUQuad(); }
uint64_t user1Inc(uint64_t val = 1) { const uint64_t v = user1(); user1(v + val); return v; }
uint64_t user1Or(uint64_t val) { const uint64_t v = user1(); user1(v | val); return v; }
uint64_t user1SetOnce() { const uint64_t v = user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc()
static void user1ClearTree() { VNUser1InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user2u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser2InUse::s_userBusy, "user2p used without AstUserInUse");
return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{nullptr});
}
AstNode* user2p() const VL_MT_STABLE { return user2u().toNodep(); }
void user2u(const VNUser& user) { m_user2u = user; m_user2Cnt = VNUser2InUse::s_userCntGbl; }
void user2p(void* userp) { user2u(VNUser{userp}); }
void user2(int val) { user2u(VNUser{val}); }
int user2() const { return user2u().toInt(); }
int user2Inc(int val = 1) { const int v = user2(); user2(v + val); return v; }
int user2Or(int val) { const int v = user2(); user2(v | val); return v; }
int user2SetOnce() { const int v = user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
void user2(uint64_t val) { user2u(VNUser{val}); }
uint64_t user2() const { return user2u().toUQuad(); }
uint64_t user2Inc(uint64_t val = 1) { const uint64_t v = user2(); user2(v + val); return v; }
uint64_t user2Or(uint64_t val) { const uint64_t v = user2(); user2(v | val); return v; }
uint64_t user2SetOnce() { const uint64_t v = user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc()
static void user2ClearTree() { VNUser2InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user3u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser3InUse::s_userBusy, "user3p used without AstUserInUse");
return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{nullptr});
}
AstNode* user3p() const VL_MT_STABLE { return user3u().toNodep(); }
void user3u(const VNUser& user) { m_user3u = user; m_user3Cnt = VNUser3InUse::s_userCntGbl; }
void user3p(void* userp) { user3u(VNUser{userp}); }
void user3(int val) { user3u(VNUser{val}); }
int user3() const { return user3u().toInt(); }
int user3Inc(int val = 1) { const int v = user3(); user3(v + val); return v; }
int user3Or(int val) { const int v = user3(); user3(v | val); return v; }
int user3SetOnce() { const int v = user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
void user3(uint64_t val) { user3u(VNUser{val}); }
uint64_t user3() const { return user3u().toUQuad(); }
uint64_t user3Inc(uint64_t val = 1) { const uint64_t v = user3(); user3(v + val); return v; }
uint64_t user3Or(uint64_t val) { const uint64_t v = user3(); user3(v | val); return v; }
uint64_t user3SetOnce() { const uint64_t v = user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc()
static void user3ClearTree() { VNUser3InUse::clear(); } // Clear userp()'s across the entire tree
VNUser user4u() const VL_MT_STABLE {
// Slows things down measurably, so disabled by default
//UASSERT_STATIC(VNUser4InUse::s_userBusy, "user4p used without AstUserInUse");
return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{nullptr});
}
AstNode* user4p() const VL_MT_STABLE { return user4u().toNodep(); }
void user4u(const VNUser& user) { m_user4u = user; m_user4Cnt = VNUser4InUse::s_userCntGbl; }
void user4p(void* userp) { user4u(VNUser{userp}); }
void user4(int val) { user4u(VNUser{val}); }
int user4() const { return user4u().toInt(); }
int user4Or(int val) { const int v = user4(); user4(v | val); return v; }
int user4Inc(int val = 1) { const int v = user4(); user4(v + val); return v; }
int user4SetOnce() { const int v = user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
void user4(uint64_t val) { user4u(VNUser{val}); }
uint64_t user4() const { return user4u().toUQuad(); }
uint64_t user4Or(uint64_t val) { const uint64_t v = user4(); user4(v | val); return v; }
uint64_t user4Inc(uint64_t val = 1) { const uint64_t v = user4(); user4(v + val); return v; }
uint64_t user4SetOnce() { const uint64_t v = user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc()
static void user4ClearTree() { VNUser4InUse::clear(); } // Clear userp()'s across the entire tree
// clang-format on
@@ -1035,7 +1035,6 @@ protected:
AstNode* iterateSubtreeReturnEdits(VNVisitor& v);
static void dumpJsonNum(std::ostream& os, const std::string& name, int64_t val);
static void dumpJsonBool(std::ostream& os, const std::string& name, bool val);
static void dumpJsonBoolIf(std::ostream& os, const std::string& name, bool val);
static void dumpJsonStr(std::ostream& os, const std::string& name, const std::string& val);
static void dumpJsonPtr(std::ostream& os, const std::string& name, const AstNode* const valp);
+198 -126
View File
@@ -509,6 +509,7 @@ public:
PROCESS_REFERENCE,
RANDOM_GENERATOR,
RANDOM_STDGENERATOR,
COVERGROUP_INSTHANDLE,
// Unsigned and two state; fundamental types
UINT32,
UINT64,
@@ -544,6 +545,7 @@ public:
"VlProcessRef",
"VlRandomizer",
"VlStdRandomizer",
"VlCovInstHandle",
"IData",
"QData",
"LOGIC_IMPLICIT",
@@ -576,6 +578,7 @@ public:
"%E-proc-ref",
"%E-rand-gen",
"%E-stdrand-gen",
"%E-cg-insthandle",
"IData",
"QData",
"%E-logic-implct",
@@ -620,6 +623,7 @@ public:
case PROCESS_REFERENCE: return 0; // opaque
case RANDOM_GENERATOR: return 0; // opaque
case RANDOM_STDGENERATOR: return 0; // opaque
case COVERGROUP_INSTHANDLE: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
default: return 0;
@@ -660,8 +664,8 @@ public:
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER
|| m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == PROCESS_REFERENCE
|| m_e == RANDOM_GENERATOR || m_e == RANDOM_STDGENERATOR || m_e == DOUBLE
|| m_e == UNTYPED);
|| m_e == RANDOM_GENERATOR || m_e == RANDOM_STDGENERATOR
|| m_e == COVERGROUP_INSTHANDLE || m_e == DOUBLE || m_e == UNTYPED);
}
bool isCHandle() const VL_MT_SAFE { return m_e == CHANDLE; }
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
@@ -716,6 +720,7 @@ public:
/* PROCESS_REFERENCE: */ "", // Should not be traced
/* RANDOM_GENERATOR: */ "", // Should not be traced
/* RANDOM_STD_GENERATOR: */ "", // Should not be traced
/* COVERGROUP_INSTHANDLE: */ "", // Should not be traced
/* UINT32: */ "BIT",
/* UINT64: */ "BIT",
/* LOGIC_IMPLICIT: */ "", // Should not be traced
@@ -752,6 +757,7 @@ public:
static const char* const names[] = {"FALSE", "TRUE", "UNK"};
return names[m_e];
}
bool isKnown() const { return m_e != BU_UNKNOWN; }
bool trueKnown() const { return m_e == BU_TRUE; }
void setTrueOrFalse(bool flag) { m_e = flag ? BU_TRUE : BU_FALSE; }
};
@@ -795,6 +801,10 @@ public:
static const char* const names[] = {"", "VL_LIKELY", "VL_UNLIKELY"};
return names[m_e];
}
const char* asciiShort() const {
static const char* const names[] = {"", "L", "!L"};
return names[m_e];
}
};
constexpr bool operator==(const VBranchPred& lhs, const VBranchPred& rhs) {
return lhs.m_e == rhs.m_e;
@@ -807,132 +817,154 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) {
// ######################################################################
// C++ methods invoked on runtime library data types via AstCMethodHard
// C++ methods invoked on runtime library data types via AstCMethodHard.
// The argument descriptor gives the access direction of each argument:
// - 'r' read by the call
// - 'w' written by the call (Use only if unconditionally and wholly written,
// that is: a preceding write can be removed. Otherwise use 'm'.)
// - 'm' read and written (modified) by the call
// - '+' repeats the entry before it for all remaining arguments, must be last
// - "" if the method takes no arguments
// - "TODO" if not yet checked due to existing issues
// clang-format off
#define FOR_EACH_CMETHOD(macro) \
/* id, method, pure */ \
macro(_NONE, "_none", false) \
macro(ARRAY_AND, "and", true) \
macro(ARRAY_AT, "at", true) \
macro(ARRAY_AT_BACK, "atBack", true) \
macro(ARRAY_AT_WRITE, "atWrite", true) \
macro(ARRAY_FIND, "find", true) \
macro(ARRAY_FIND_FIRST, "find_first", true) \
macro(ARRAY_FIND_FIRST_INDEX, "find_first_index", true) \
macro(ARRAY_FIND_INDEX, "find_index", true) \
macro(ARRAY_FIND_LAST, "find_last", true) \
macro(ARRAY_FIND_LAST_INDEX, "find_last_index", true) \
macro(ARRAY_FIRST, "first", false) \
macro(ARRAY_INSIDE, "inside", true) \
macro(ARRAY_LAST, "last", false) \
macro(ARRAY_MAP, "map", true) \
macro(ARRAY_MAX, "max", true) \
macro(ARRAY_MIN, "min", true) \
macro(ARRAY_NEXT, "next", false) \
macro(ARRAY_OR, "or", true) \
macro(ARRAY_POP_BACK, "pop_back", false) \
macro(ARRAY_POP_FRONT, "pop_front", false) \
macro(ARRAY_PREV, "prev", false) \
macro(ARRAY_PRODUCT, "product", true) \
macro(ARRAY_PUSH_BACK, "push_back", false) \
macro(ARRAY_PUSH_FRONT, "push_front", false) \
macro(ARRAY_REVERSE, "reverse", false) \
macro(ARRAY_RSORT, "rsort", false) \
macro(ARRAY_R_AND, "r_and", true) \
macro(ARRAY_R_OR, "r_or", true) \
macro(ARRAY_R_PRODUCT, "r_product", true) \
macro(ARRAY_R_SUM, "r_sum", true) \
macro(ARRAY_R_XOR, "r_xor", true) \
macro(ARRAY_SHUFFLE, "shuffle", false) \
macro(ARRAY_SORT, "sort", false) \
macro(ARRAY_SUM, "sum", true) \
macro(ARRAY_UNIQUE, "unique", true) \
macro(ARRAY_UNIQUE_INDEX, "unique_index", true) \
macro(ARRAY_XOR, "xor", true) \
macro(ASSOC_CLEAR, "clear", false) \
macro(ASSOC_ERASE, "erase", false) \
macro(ASSOC_EXISTS, "exists", true) \
macro(ASSOC_FIRST, "first", false) \
macro(ASSOC_NEXT, "next", false) \
macro(ASSOC_SIZE, "size", true) \
macro(CLASS_SET_RANDMODE, "set_randmode", false) \
macro(DYN_AT_WRITE_APPEND, "atWriteAppend", false) \
macro(DYN_AT_WRITE_APPEND_BACK, "atWriteAppendBack", false) \
macro(DYN_CLEAR, "clear", false) \
macro(DYN_ERASE, "erase", false) \
macro(DYN_INSERT, "insert", false) \
macro(DYN_POP, "pop", false) \
macro(DYN_POP_FRONT, "pop_front", false) \
macro(DYN_PUSH, "push", false) \
macro(DYN_PUSH_FRONT, "push_front", false) \
macro(DYN_RENEW, "renew", false) \
macro(DYN_RENEW_COPY, "renew_copy", false) \
macro(DYN_RESIZE, "resize", false) \
macro(DYN_SIZE, "size", true) \
macro(DYN_SLICE, "slice", true) \
macro(DYN_SLICE_ASSIGN, "sliceAssign", false) \
macro(DYN_SLICE_ASSIGN_BACK_BACK, "sliceAssignBackBack", false) \
macro(DYN_SLICE_ASSIGN_FRONT_BACK, "sliceAssignFrontBack", false) \
macro(DYN_SLICE_BACK_BACK, "sliceBackBack", true) \
macro(DYN_SLICE_FRONT_BACK, "sliceFrontBack", true) \
macro(EVENT_CLEAR_FIRED, "clearFired", false) \
macro(EVENT_CLEAR_TRIGGERED, "clearTriggered", false) \
macro(EVENT_FIRE, "fire", false) \
macro(EVENT_IS_FIRED, "isFired", true) \
macro(EVENT_IS_TRIGGERED, "isTriggered", true) \
macro(FORCE_ADD, "addForce", false) \
macro(FORCE_READ, "read", true) \
macro(FORCE_READ_INDEX, "readIndex", true) \
macro(FORCE_READ_SEL, "readSel", true) \
macro(FORCE_RELEASE, "release", false) \
macro(FORCE_TOUCH, "touch", false) \
macro(FORK_DONE, "done", false) \
macro(FORK_INIT, "init", false) \
macro(FORK_JOIN, "join", false) \
macro(FORK_ON_KILL, "onKill", false) \
macro(RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false) \
macro(RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false) \
macro(RANDOMIZER_CLEARALL, "clearAll", false) \
macro(RANDOMIZER_DISABLE_SOFT, "disable_soft", false) \
macro(RANDOMIZER_HARD, "hard", false) \
macro(RANDOMIZER_SOFT, "soft", false) \
macro(RANDOMIZER_UNIQUE, "rand_unique", false) \
macro(RANDOMIZER_MARK_RANDC, "markRandc", false) \
macro(RANDOMIZER_SOLVE_BEFORE, "solveBefore", false) \
macro(RANDOMIZER_PIN_VAR, "pin_var", false) \
macro(RANDOMIZER_WRITE_VAR, "write_var", false) \
macro(RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false) \
macro(RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false) \
macro(RANDOMIZER_MARK_VAR_STATIC, "mark_var_static", false) \
macro(RANDOMIZER_SET_STATIC_RANDMODE, "set_static_randmode", false) \
macro(RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true) \
macro(RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false) \
macro(SCHED_ANY_TRIGGERED, "anyTriggered", false) \
macro(SCHED_AWAITING_CURRENT_TIME, "awaitingCurrentTime", true) \
macro(SCHED_AWAITING_ZERO_DELAY, "awaitingZeroDelay", true) \
macro(SCHED_READY, "ready", false) \
macro(SCHED_COMMIT, "commit", false) \
macro(SCHED_MOVE_TO_RESUME_QUEUE, "moveToResumeQueue", false) \
macro(SCHED_DELAY, "delay", false) \
macro(SCHED_DO_POST_UPDATES, "doPostUpdates", false) \
macro(SCHED_ENQUEUE, "enqueue", false) \
macro(SCHED_EVALUATE, "evaluate", false) \
macro(SCHED_EVALUATION, "evaluation", false) \
macro(SCHED_POST_UPDATE, "postUpdate", false) \
macro(SCHED_RESUME, "resume", false) \
macro(SCHED_RESUME_ZERO_DELAY, "resumeZeroDelay", false) \
macro(SCHED_RESUMPTION, "resumption", false) \
macro(SCHED_TRIGGER, "trigger", false) \
macro(SCHED_WAIT_FOREVER, "waitForever", false) \
macro(UNPACKED_ASSIGN, "assign", false) \
macro(UNPACKED_FILL, "fill", false) \
macro(UNPACKED_NEQ, "neq", true)
/* id, method, pure, args */ \
macro(_NONE, "_none", false, "") \
macro(ARRAY_AT, "at", PURE, "r") \
macro(ARRAY_AT_BACK, "atBack", PURE, "r") \
macro(ARRAY_AT_WRITE, "atWrite", PURE, "r") \
macro(ARRAY_FIND, "find", PURE, "") \
macro(ARRAY_FIND_FIRST, "find_first", PURE, "") \
macro(ARRAY_FIND_FIRST_INDEX, "find_first_index", PURE, "") \
macro(ARRAY_FIND_INDEX, "find_index", PURE, "") \
macro(ARRAY_FIND_LAST, "find_last", PURE, "") \
macro(ARRAY_FIND_LAST_INDEX, "find_last_index", PURE, "") \
macro(ARRAY_FIRST, "first", false, "m") \
macro(ARRAY_INSIDE, "inside", PURE, "r") \
macro(ARRAY_LAST, "last", false, "m") \
macro(ARRAY_MAP, "map", PURE, "") \
macro(ARRAY_MAX, "max", PURE, "") \
macro(ARRAY_MIN, "min", PURE, "") \
macro(ARRAY_NEXT, "next", false, "m") \
macro(ARRAY_POP_BACK, "pop_back", false, "") \
macro(ARRAY_POP_FRONT, "pop_front", false, "") \
macro(ARRAY_PREV, "prev", false, "m") \
macro(ARRAY_PUSH_BACK, "push_back", false, "r") \
macro(ARRAY_PUSH_FRONT, "push_front", false, "r") \
macro(ARRAY_REVERSE, "reverse", false, "") \
macro(ARRAY_RSORT, "rsort", false, "") \
macro(ARRAY_R_AND, "r_and", PURE, "") \
macro(ARRAY_R_OR, "r_or", PURE, "") \
macro(ARRAY_R_PRODUCT, "r_product", PURE, "") \
macro(ARRAY_R_SUM, "r_sum", PURE, "") \
macro(ARRAY_R_XOR, "r_xor", PURE, "") \
macro(ARRAY_SHUFFLE, "shuffle", false, "") \
macro(ARRAY_SLICE, "slice", PURE, "r") \
macro(ARRAY_SORT, "sort", false, "") \
macro(ARRAY_UNIQUE, "unique", PURE, "") \
macro(ARRAY_UNIQUE_INDEX, "unique_index", PURE, "") \
macro(ASSOC_CLEAR, "clear", false, "") \
macro(ASSOC_ERASE, "erase", false, "r") \
macro(ASSOC_EXISTS, "exists", PURE, "r") \
macro(ASSOC_FIRST, "first", false, "m") \
macro(ASSOC_NEXT, "next", false, "m") \
macro(ASSOC_SIZE, "size", PURE, "") \
macro(CLASS_SET_RANDMODE, "set_randmode", false, "r") \
macro(COVERGROUP_ADD_ARRAY_NAMER, "addArrayNamer", false, "r+") \
macro(COVERGROUP_ADD_BIN, "addBin", false, "r+") \
macro(COVERGROUP_ADD_COVERPOINT, "addCoverpoint", false, "") \
macro(COVERGROUP_ADD_CROSS, "addCross", false, "") \
macro(COVERGROUP_ADD_SINGLE_NAMER, "addSingleNamer", false, "r+") \
macro(COVERGROUP_ATTACH, "attach", false, "r") \
macro(COVERGROUP_CLEAR_HIT_LIST, "clearHitList", false, "") \
macro(COVERGROUP_COVERAGE_PARTS, "coverageParts", false, "TODO") \
macro(COVERGROUP_FINALIZE_BINS, "finalizeBins", false, "") \
macro(COVERGROUP_INCREMENT_BIN, "incrementBin", false, "r") \
macro(COVERGROUP_INIT, "init", false, "r+") \
macro(COVERGROUP_INST_P, "p", PURE, "") \
macro(COVERGROUP_RECORD_HIT, "recordHit", false, "r") \
macro(COVERGROUP_REGISTER_BINS, "registerBins", false, "rr") \
macro(COVERGROUP_SAMPLE, "sample", false, "") \
macro(COVERGROUP_SAMPLE_IFFS, "sample", false, "r") \
macro(DYN_AT_WRITE_APPEND, "atWriteAppend", false, "r") \
macro(DYN_AT_WRITE_APPEND_BACK, "atWriteAppendBack", false, "r") \
macro(DYN_CLEAR, "clear", false, "") \
macro(DYN_ERASE, "erase", false, "r") \
macro(DYN_INSERT, "insert", false, "rr") \
macro(DYN_POP, "pop", false, "rrm") \
macro(DYN_POP_FRONT, "pop_front", false, "") \
macro(DYN_PUSH, "push", false, "rr") \
macro(DYN_PUSH_FRONT, "push_front", false, "r") \
macro(DYN_RENEW, "renew", false, "r") \
macro(DYN_RENEW_COPY, "renew_copy", false, "rr") \
macro(DYN_RESIZE, "resize", false, "TODO") \
macro(DYN_SIZE, "size", PURE, "") \
macro(DYN_SLICE, "slice", PURE, "rr") \
macro(DYN_SLICE_ASSIGN, "sliceAssign", false, "rrr") \
macro(DYN_SLICE_ASSIGN_BACK_BACK, "sliceAssignBackBack", false, "rrr") \
macro(DYN_SLICE_ASSIGN_FRONT_BACK, "sliceAssignFrontBack", false, "rrr") \
macro(DYN_SLICE_BACK_BACK, "sliceBackBack", PURE, "rr") \
macro(DYN_SLICE_FRONT_BACK, "sliceFrontBack", PURE, "rr") \
macro(EVENT_CLEAR_FIRED, "clearFired", false, "") \
macro(EVENT_CLEAR_TRIGGERED, "clearTriggered", false, "") \
macro(EVENT_FIRE, "fire", false, "") \
macro(EVENT_IS_FIRED, "isFired", PURE, "") \
macro(EVENT_IS_TRIGGERED, "isTriggered", PURE, "") \
macro(FORCE_ADD, "addForce", false, "rrrr+") \
macro(FORCE_READ, "read", PURE, "r") \
macro(FORCE_READ_INDEX, "readIndex", PURE, "rr") \
macro(FORCE_READ_SEL, "readSel", PURE, "TODO") \
macro(FORCE_RELEASE, "release", false, "rr+") \
macro(FORCE_TOUCH, "touch", false, "") \
macro(FORK_DONE, "done", false, "rr") \
macro(FORK_INIT, "init", false, "rr") \
macro(FORK_JOIN, "join", false, "rrr") \
macro(FORK_ON_KILL, "onKill", false, "r") \
macro(NBA_COMMIT, "commit", false, "w") \
macro(NBA_ENQUEUE, "enqueue", false, "r+") \
macro(RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false, "mr") \
macro(RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false, "") \
macro(RANDOMIZER_CLEARALL, "clearAll", false, "") \
macro(RANDOMIZER_DISABLE_SOFT, "disable_soft", false, "r") \
macro(RANDOMIZER_HARD, "hard", false, "r+") \
macro(RANDOMIZER_SOFT, "soft", false, "rrrr") \
macro(RANDOMIZER_UNIQUE, "rand_unique", false, "r") \
macro(RANDOMIZER_MARK_RANDC, "markRandc", false, "r") \
macro(RANDOMIZER_SOLVE_BEFORE, "solveBefore", false, "rr") \
macro(RANDOMIZER_PIN_VAR, "pin_var", false, "rrr") \
macro(RANDOMIZER_WRITE_VAR, "write_var", false, "TODO") \
macro(RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false, "r") \
macro(RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false, "r") \
macro(RANDOMIZER_MARK_VAR_STATIC, "mark_var_static", false, "r") \
macro(RANDOMIZER_SET_STATIC_RANDMODE, "set_static_randmode", false, "r") \
macro(RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", PURE, "") \
macro(RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false, "r") \
macro(SCHED_ANY_TRIGGERED, "anyTriggered", false, "r") \
macro(SCHED_AWAITING_CURRENT_TIME, "awaitingCurrentTime", PURE, "") \
macro(SCHED_AWAITING_ZERO_DELAY, "awaitingZeroDelay", PURE, "") \
macro(SCHED_READY, "ready", false, "r") \
macro(SCHED_MOVE_TO_RESUME_QUEUE, "moveToResumeQueue", false, "r") \
macro(SCHED_DELAY, "delay", false, "rrrr") \
macro(SCHED_DO_POST_UPDATES, "doPostUpdates", false, "") \
macro(SCHED_EVALUATE, "evaluate", false, "") \
macro(SCHED_EVALUATION, "evaluation", false, "rrrr") \
macro(SCHED_POST_UPDATE, "postUpdate", false, "rrrr") \
macro(SCHED_RESUME, "resume", false, "TODO") \
macro(SCHED_RESUME_ZERO_DELAY, "resumeZeroDelay", false, "") \
macro(SCHED_RESUMPTION, "resumption", false, "rrrr") \
macro(SCHED_TRIGGER, "trigger", false, "rrrrr") \
macro(SCHED_WAIT_FOREVER, "waitForever", false, "rrr") \
macro(UNPACKED_ASSIGN, "assign", false, "r") \
macro(UNPACKED_FILL, "fill", false, "r") \
macro(UNPACKED_NEQ, "neq", PURE, "r")
// clang-format on
class VCMethod final {
static constexpr bool PURE = true; // For macro expansion of 'pure' field only
public:
enum en : uint8_t {
#define VL_CMETHOD_ID(id, method, pure) id,
#define VL_CMETHOD_ID(id, method, pure, args) id,
FOR_EACH_CMETHOD(VL_CMETHOD_ID)
#undef VL_CMETHOD_ID
_ENUM_MAX // Leave last
@@ -948,7 +980,7 @@ public:
constexpr operator en() const { return m_e; }
const char* ascii() const VL_PURE {
static const char* const values[] = {
#define VL_CMETHOD_NAME(id, method, pure) method,
#define VL_CMETHOD_NAME(id, method, pure, args) method,
FOR_EACH_CMETHOD(VL_CMETHOD_NAME)
#undef VL_CMETHOD_NAME
"_ENUM_MAX" //
@@ -957,15 +989,39 @@ public:
}
bool isPure() const VL_PURE {
static const bool values[] = {
#define VL_CMETHOD_PURE(id, method, pure) pure,
#define VL_CMETHOD_PURE(id, method, pure, args) pure,
FOR_EACH_CMETHOD(VL_CMETHOD_PURE)
#undef VL_CMETHOD_PURE
false //
};
return values[m_e];
}
const char* args() const VL_PURE {
static const char* const values[] = {
#define VL_CMETHOD_ARGS(id, method, pure, args) args,
FOR_EACH_CMETHOD(VL_CMETHOD_ARGS)
#undef VL_CMETHOD_ARGS
"" //
};
return values[m_e];
}
// Return array method for given name
static VCMethod arrayMethod(const string& name);
// Validate the arguments descriptor
static constexpr bool validateArgsDescriptor(const char* descrp) {
// Is "TODO"
if (descrp[0] == 'T' && descrp[1] == 'O' && descrp[2] == 'D' && descrp[3] == 'O'
&& !descrp[4]) {
return true;
}
// Is a sequence of 'r'/'w'/'m' with an optional trailing '+'
for (const char* cp = descrp; *cp; ++cp) {
if (*cp == '+') return cp != descrp && !cp[1];
if (*cp != 'r' && *cp != 'w' && *cp != 'm') return false;
}
return true;
}
};
constexpr bool operator==(const VCMethod& lhs, const VCMethod& rhs) { return lhs.m_e == rhs.m_e; }
constexpr bool operator==(const VCMethod& lhs, VCMethod::en rhs) { return lhs.m_e == rhs; }
@@ -974,6 +1030,13 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
return os << rhs.ascii();
}
// Static assert all argument descriptors are well formed
#define VL_CMETHOD_ARGS_CHECK(id, method, pure, args) \
static_assert(VCMethod::validateArgsDescriptor(args), \
"Malformed argument descriptor for " #id);
FOR_EACH_CMETHOD(VL_CMETHOD_ARGS_CHECK)
#undef VL_CMETHOD_ARGS_CHECK
#undef FOR_EACH_CMETHOD
// ######################################################################
@@ -1039,6 +1102,11 @@ public:
explicit VCaseType(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
const char* ascii() const VL_PURE {
static const char* const names[]
= {"CASE", "CASEX", "CASEZ", "CASEINSIDE", "CASEMATCHES", "RANDSEQUENCE"};
return names[m_e];
}
};
constexpr bool operator==(const VCaseType& lhs, const VCaseType& rhs) {
return lhs.m_e == rhs.m_e;
@@ -1153,6 +1221,8 @@ public:
MERGE_INSTANCES,
DISTRIBUTE_FIRST,
REAL_INTERVAL,
// Legacy option.* accepted for compatibility
CROSS_AUTO_BIN_MAX,
// sentinel - should never appear after parse-time validation
UNKNOWN
};
@@ -1176,6 +1246,7 @@ public:
"merge_instances",
"distribute_first",
"real_interval",
"cross_auto_bin_max",
"unknown"};
return names[m_e];
}
@@ -1726,13 +1797,14 @@ public:
int hiMaxSelect() const {
return (lo() < 0 ? hi() - lo() : hi());
} // Maximum value a [] select may index
void dump(std::ostream& str) const {
string ascii() const {
if (ranged()) {
str << "[" << left() << ":" << right() << "]";
return "["s + std::to_string(left()) + ":" + std::to_string(right()) + "]";
} else {
str << "[norg]";
return "[norg]";
}
}
void dump(std::ostream& str) const { str << ascii(); }
};
inline std::ostream& operator<<(std::ostream& os, const VNumRange& rhs) {
rhs.dump(os);
+84 -4
View File
@@ -34,10 +34,14 @@ class AstNodeDType VL_NOT_FINAL : public AstNode {
// Ideally width() would migrate to BasicDType as that's where it makes sense,
// but it's currently so prevalent in the code we leave it here.
// Note the below members are included in AstTypeTable::Key lookups
// dist-ast-dump-suppress // Part of dumpSmall
int m_width = 0; // (also in AstTypeTable::Key) Bit width of operation
int m_widthMin
= 0; // (also in AstTypeTable::Key) If unsized, bitwidth of minimum implementation
// (also in AstTypeTable::Key) If unsized, bitwidth of minimum implementation
// dist-ast-dump-suppress // Part of dumpSmall
int m_widthMin = 0;
// dist-ast-dump-suppress // Part of dumpSmall
VSigning m_numeric; // (also in AstTypeTable::Key) Node is signed
// dist-ast-dump-suppress // Part of dumpSmall
bool m_generic = false; // Simple globally referenced type, don't garbage collect
// Unique number assigned to each dtype during creation for IEEE matching
static int s_uniqueNum;
@@ -405,6 +409,7 @@ class AstBasicDType final : public AstNodeDType {
// @astgen op1 := rangep : Optional[AstRange] // Range of variable
struct Members final {
VBasicDTypeKwd m_keyword; // (also in VBasicTypeKey) What keyword created basic type
// dist-ast-dump-suppress // Part of dumpSmall
VNumRange m_nrange; // (also in VBasicTypeKey) Numeric msb/lsb (if non-opaque keyword)
bool operator==(const Members& rhs) const {
return rhs.m_keyword == m_keyword && rhs.m_nrange == m_nrange;
@@ -493,6 +498,9 @@ public:
bool isStdRandomGenerator() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::RANDOM_STDGENERATOR;
}
bool isCovergroupInstHandle() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::COVERGROUP_INSTHANDLE;
}
bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
bool isString() const VL_MT_STABLE { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }
@@ -682,6 +690,78 @@ public:
int widthTotalBytes() const override { return 1; }
bool isCompound() const override { return false; }
};
class AstCoverCrossDType final : public AstNodeDType {
// Borrowed pointer to VlCoverCrossT<dimensions, tuples, bins, autoBins, binWords>.
const uint32_t m_dimensions;
const uint32_t m_tuples;
const uint32_t m_bins;
const uint32_t m_autoBins;
const uint64_t m_binWords;
public:
AstCoverCrossDType(FileLine* fl, uint32_t dimensions, uint32_t tuples, uint32_t bins,
uint32_t autoBins, uint64_t binWords)
: ASTGEN_SUPER_CoverCrossDType(fl)
, m_dimensions{dimensions}
, m_tuples{tuples}
, m_bins{bins}
, m_autoBins{autoBins}
, m_binWords{binWords} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverCrossDType;
const char* broken() const override {
BROKEN_RTN(m_dimensions == 0);
return nullptr;
}
bool sameNode(const AstNode* samep) const override {
const AstCoverCrossDType* const sp = VN_DBG_AS(samep, CoverCrossDType);
return dimensions() == sp->dimensions() && tuples() == sp->tuples() && bins() == sp->bins()
&& autoBins() == sp->autoBins() && binWords() == sp->binWords();
}
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
uint32_t dimensions() const { return m_dimensions; }
uint32_t tuples() const { return m_tuples; }
uint32_t bins() const { return m_bins; }
uint32_t autoBins() const { return m_autoBins; }
uint64_t binWords() const { return m_binWords; }
string cppTemplateArgs() const;
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
int widthTotalBytes() const override { return sizeof(void*); }
bool isCompound() const override { return true; }
};
class AstCoverpointDType final : public AstNodeDType {
// Borrowed pointer to a covergroup coverpoint runtime, 'VlCoverpointT<hitBound>*'.
// Follows pattern of AstQueueDType in capturing the compile-time max bin overlap
// template argument.
uint32_t m_hitBound; // VlCoverpointT<> template argument; hit list size, >= 1
public:
AstCoverpointDType(FileLine* fl, uint32_t hitBound)
: ASTGEN_SUPER_CoverpointDType(fl)
, m_hitBound{hitBound} {
dtypep(this);
}
ASTGEN_MEMBERS_AstCoverpointDType;
const char* broken() const override {
BROKEN_RTN(m_hitBound < 1);
return nullptr;
}
// V3Covergroup interns these one-per-hitBound into the type table, so identity is
// equality; there is never a second node with the same bound to compare against.
bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; }
void dumpSmall(std::ostream& str) const override;
// ACCESSORS
uint32_t hitBound() const { return m_hitBound; }
// METHODS
AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; }
int widthAlignBytes() const override { return sizeof(void*); }
int widthTotalBytes() const override { return sizeof(void*); }
bool isCompound() const override { return true; }
};
class AstDefImplicitDType final : public AstNodeDType {
// For parsing enum/struct/unions that are declared with a variable rather than typedef
// This allows "var enum {...} a,b" to share the enum definition for both variables
@@ -804,6 +884,7 @@ public:
private:
string m_name; // Name from upper typedef, if any
const int m_uniqueNum;
// dist-ast-dump-suppress // Skip dumping cache
TableMap m_tableMap; // Created table for V3Width only to remove duplicates
public:
@@ -859,6 +940,7 @@ public:
class AstIfaceGenericDType final : public AstNodeDType {
// Generic interface that will be replaced with AstIfaceRefDType
FileLine* m_modportFileline; // Where modport token was
// dist-ast-dump-suppress // Part of name()
string m_modportName; // "" = no modport
public:
explicit AstIfaceGenericDType(FileLine* fl)
@@ -872,8 +954,6 @@ public:
dtypep(this);
}
ASTGEN_MEMBERS_AstIfaceGenericDType;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
void dumpSmall(std::ostream& str) const override;
bool hasDType() const override VL_MT_SAFE { return true; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
+26 -6
View File
@@ -66,7 +66,15 @@ public:
bool isOpaque() const { return VN_IS(this, CvtPackString); }
// True for SVA multi-cycle sequence nodes (SExpr, SConsRep, etc.)
virtual bool isMultiCycleSva() const { return false; }
// TODO: consolidate cLValueTargetp, isLValue, baseFromp
// If the expression is a valid C++ LValue, return the target reference, else nullptr
// This always returns either AstVarRef, AstMemberSel, or nullptr
AstNodeExpr* cLValueTargetp();
// TODO: this actually means it's a write or RW, not that it's an LValue
bool isLValue() const;
// Return base var (or const) nodep dereferences
AstNode* baseFromp(bool overMembers);
// Wrap This expression into an AstStmtExpr to denote it occurs in statement position
inline AstStmtExpr* makeStmt();
@@ -189,6 +197,7 @@ class AstNodeCCall VL_NOT_FINAL : public AstNodeExpr {
// @astgen op2 := argsp : List[AstNodeExpr] // Note: op1 used by some sub-types only
//
// @astgen ptr := m_funcp : AstCFunc // Function being called
// dist-ast-dump-suppress // Too verbose
string m_argTypes;
bool m_superReference = false; // Called with super reference
@@ -216,7 +225,7 @@ public:
void funcp(AstCFunc* funcp) { m_funcp = funcp; }
string argTypes() const { return m_argTypes; }
void argTypes(const string& str) { m_argTypes = str; }
bool isUnlikely() const override { return m_funcp && m_funcp->isUnlikely(); }
string emitVerilog() final override { V3ERROR_NA_RETURN(""); }
string emitC() final override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const final override { return true; }
@@ -271,6 +280,7 @@ public:
bool superReference() const { return m_superReference; }
void superReference(bool flag) { m_superReference = flag; }
bool isPure() override;
bool isUnlikely() const override { return m_taskp && m_taskp->isUnlikely(); }
bool sameNode(const AstNode* samep) const override {
const AstNodeFTaskRef* const asamep = VN_DBG_AS(samep, NodeFTaskRef);
return taskp() == asamep->taskp() //
@@ -556,6 +566,7 @@ class AstWith final : public AstNode {
private:
// 'with (identifier_list) {...}' restricted form (IEEE 1800-2023 18.7).
bool m_restricted = false;
// dist-ast-dump-suppress // V3LinkDot temporary use only
bool m_validated = false; // identifier_list typo / unused checks already run
std::set<std::string> m_restrictedNames;
@@ -750,6 +761,8 @@ public:
setPurity();
}
ASTGEN_MEMBERS_AstCMethodHard;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return method().ascii(); }
bool sameNode(const AstNode* samep) const override {
const AstCMethodHard* const asamep = VN_DBG_AS(samep, CMethodHard);
@@ -1469,6 +1482,8 @@ public:
}
ASTGEN_MEMBERS_AstExprStmt;
// METHODS
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
@@ -1746,7 +1761,7 @@ public:
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstImplication final : public AstNodeExpr {
// Implication |-> |=> (IEEE 1800-2023 16.12.6) and followed-by #-# #=#
// Implication |-> |=> (IEEE 1800-2023 16.12.7) and followed-by #-# #=#
// (IEEE 1800-2023 16.12.9). Antecedent-miss is vacuous-pass for implication
// and non-vacuous-fail for followed-by, hence the separate flag.
// @astgen op1 := lhsp : AstNodeExpr
@@ -1774,7 +1789,7 @@ public:
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool isMultiCycleSva() const override { return m_isFollowedBy; }
bool isMultiCycleSva() const override { return m_isFollowedBy || !m_isOverlapped; }
bool isOverlapped() const { return m_isOverlapped; }
bool isFollowedBy() const { return m_isFollowedBy; }
};
@@ -1793,6 +1808,7 @@ public:
using KeyItemMap = std::map<uint64_t, AstInitItem*>;
private:
// dist-ast-dump-suppress // Dumped using dumpInitList
KeyItemMap m_map; // Node value for each array index
// METHODS
void dumpInitList(std::ostream& str) const;
@@ -1866,6 +1882,8 @@ public:
, m_name{name}
, m_index{index} {}
ASTGEN_MEMBERS_AstLambdaArgRef;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
string emitVerilog() override { return name(); }
string emitC() override { V3ERROR_NA_RETURN(""); }
@@ -3035,6 +3053,8 @@ public:
addConstraintsp(constraintsp);
}
ASTGEN_MEMBERS_AstWithParse;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* /*samep*/) const override { return true; }
bool restricted() const { return m_restricted; }
void restricted(bool flag) { m_restricted = flag; }
@@ -4911,9 +4931,6 @@ public:
bool isPredictOptimizable() const override { return true; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
int instrCount() const override { return widthInstrs(); }
// Special operators
// Return base var (or const) nodep dereferences
static AstNode* baseFromp(AstNode* nodep, bool overMembers);
};
class AstAssocSel final : public AstNodeSel {
void init(const AstNode* fromp) {
@@ -5166,6 +5183,8 @@ public:
"not coded to create after dtypes resolved");
}
ASTGEN_MEMBERS_AstSelBit;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
VAccess access() const { return m_access; }
void access(const VAccess& flag) { m_access = flag; }
};
@@ -5430,6 +5449,7 @@ public:
enum FmtType : int { ATOI = 10, ATOHEX = 16, ATOOCT = 8, ATOBIN = 2, ATOREAL = -1 };
private:
// dist-ast-dump-suppress // Part of name()
const FmtType m_fmtType; // Operation type
public:
AstAtoN(FileLine* fl, AstNodeExpr* lhsp, FmtType fmtType)
+105 -18
View File
@@ -94,6 +94,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
// @astgen op4 := scopeNamep : Optional[AstScopeName]
string m_name; // Name of task
string m_cname; // Name of task if DPI import
// dist-ast-dump-suppress // Not dumped due to verbosity
string m_dpiCDecl; // Custom DPI-C function declaration
string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8)
uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees
@@ -112,6 +113,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode {
bool m_isHideLocal : 1; // Verilog local
bool m_isHideProtected : 1; // Verilog protected
bool m_dpiPure : 1; // DPI import pure (vs. virtual pure)
bool m_keepAlive : 1; // Disable dead function elimination
bool m_pureVirtual : 1; // Pure virtual
bool m_recursive : 1; // Recursive or part of recursion
bool m_static : 1; // Static method in class
@@ -144,6 +146,7 @@ protected:
, m_isHideLocal{false}
, m_isHideProtected{false}
, m_dpiPure{false}
, m_keepAlive{false}
, m_pureVirtual{false}
, m_recursive{false}
, m_static{false}
@@ -211,6 +214,8 @@ public:
void isHideProtected(bool flag) { m_isHideProtected = flag; }
bool dpiPure() const { return m_dpiPure; }
void dpiPure(bool flag) { m_dpiPure = flag; }
bool keepAlive() const { return m_keepAlive; }
void keepAlive(bool flag) { m_keepAlive = flag; }
bool pureVirtual() const { return m_pureVirtual; }
void pureVirtual(bool flag) { m_pureVirtual = flag; }
bool recursive() const { return m_recursive; }
@@ -293,8 +298,8 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
// @astgen op2 := stmtsp : List[AstNode]
string m_name; // Name of the module
const string m_origName; // Name of the module, ignoring name() changes, for dot lookup
// dist-ast-dump-suppress // For some user errors messages only, visible where used
string m_someInstanceName; // Hierarchical name of some arbitrary instance of this module.
// Used for user messages only.
string m_libname; // Work library
int m_depth = 0; // 1=top module, 2=cell off top, shared things low, for -depth options
int m_level = 0; // 1=top module, 2=cell off top, shared things have high number
@@ -312,7 +317,6 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
bool m_hasParameterList : 1; // Has #() for parameter declaration
bool m_hierBlock : 1; // Hierarchical Block marked by HIER_BLOCK pragma
bool m_hierParams : 1; // Block containing params for parameterized hier blocks
bool m_internal : 1; // Internally created
bool m_recursive : 1; // Recursive module
bool m_recursiveClone : 1; // If recursive, what module it clones, otherwise nullptr
bool m_parameterizedTemplate : 1; // True when at least one specialized clone exists;
@@ -334,7 +338,6 @@ protected:
, m_hasParameterList{false}
, m_hierBlock{false}
, m_hierParams{false}
, m_internal{false}
, m_recursive{false}
, m_recursiveClone{false}
, m_parameterizedTemplate{false}
@@ -378,8 +381,6 @@ public:
void hierBlock(bool flag) { m_hierBlock = flag; }
bool hierParams() const { return m_hierParams; }
void hierParams(bool flag) { m_hierParams = flag; }
bool internal() const { return m_internal; }
void internal(bool flag) { m_internal = flag; }
bool recursive() const { return m_recursive; }
void recursive(bool flag) { m_recursive = flag; }
void recursiveClone(bool flag) { m_recursiveClone = flag; }
@@ -543,6 +544,7 @@ class AstCFunc final : public AstNode {
bool m_dpiImportWrapper : 1; // Wrapper for invoking DPI import prototype from generated code
bool m_needProcess : 1; // Needs access to VlProcess of the caller
bool m_recursive : 1; // Recursive or part of recursion
bool m_unlikely : 1; // Unlikely to get called (though still optimize unlike slow())
bool m_noLife : 1; // Disable V3Life on this function - has multiple calls, and reads Syms
// state
bool m_isCovergroupSample : 1; // Automatic covergroup sample() function
@@ -575,6 +577,7 @@ public:
m_dpiImportPrototype = false;
m_dpiImportWrapper = false;
m_recursive = false;
m_unlikely = false;
m_noLife = false;
m_isCovergroupSample = false;
m_cost = v3Global.opt.instrCountDpi(); // As proxy for unknown general DPI cost
@@ -653,6 +656,8 @@ public:
bool isCoroutine() const { return m_rtnType == "VlCoroutine"; }
void recursive(bool flag) { m_recursive = flag; }
bool recursive() const { return m_recursive; }
void unlikely(bool flag) { m_unlikely = flag; }
bool isUnlikely() const override { return m_unlikely; } // Note virtual override
void noLife(bool flag) { m_noLife = flag; }
bool noLife() const { return m_noLife; }
bool isCovergroupSample() const { return m_isCovergroupSample; }
@@ -687,7 +692,7 @@ class AstCell final : public AstNode {
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
// @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains
// via nextp()
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing/VPI
//
// @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
FileLine* m_modNameFileline; // Where module the cell instances token was
@@ -889,6 +894,8 @@ public:
}
}
ASTGEN_MEMBERS_AstClockingItem;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VDirection direction() const { return m_direction; }
AstClockingItem* outputp() const { return m_outputp; }
void outputp(AstClockingItem* outputp) { m_outputp = outputp; }
@@ -907,11 +914,11 @@ public:
, m_libname{libname}
, m_configname{cellname} {}
ASTGEN_MEMBERS_AstConfig;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
std::string name() const override VL_MT_STABLE { return m_libname + "." + m_configname; }
std::string libname() const VL_MT_STABLE { return m_libname; }
std::string configname() const VL_MT_STABLE { return m_configname; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
};
class AstConfigCell final : public AstNode {
// Parents: CONFIGRULE
@@ -1098,6 +1105,70 @@ public:
bool isArray() const { return m_isArray; }
void isArray(bool flag) { m_isArray = flag; }
};
class AstCoverBinsof final : public AstNode {
// A binsof selection of a coverpoint or one of its named bins
// @astgen op1 := pointp : AstCoverpointRef
// @astgen op2 := rangesp : List[AstNode] // Optional intersect value ranges
string m_name; // Selected bin name, or empty for all bins of the coverpoint
const bool m_isNegated; // Complement the selection within the cross product
public:
AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp, bool isNegated = false,
AstNode* rangesp = nullptr)
: ASTGEN_SUPER_CoverBinsof(fl)
, m_isNegated{isNegated} {
this->pointp(pointp);
addRangesp(rangesp);
}
ASTGEN_MEMBERS_AstCoverBinsof;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; }
void name(const string& name) override { m_name = name; }
bool isNegated() const { return m_isNegated; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
const AstCoverBinsof* const asamep = VN_DBG_AS(samep, CoverBinsof);
return m_name == asamep->m_name && m_isNegated == asamep->m_isNegated;
} // LCOV_EXCL_STOP
};
class AstCoverCrossBin final : public AstNode {
// A named cross bin and its selection expression
// @astgen op1 := selectp : Optional[AstNode] // Null for unsupported selections
// @astgen op2 := iffp : Optional[AstNodeExpr]
const string m_name; // Declared cross bin name
public:
AstCoverCrossBin(FileLine* fl, const string& name, AstNode* selectp, AstNodeExpr* iffp)
: ASTGEN_SUPER_CoverCrossBin(fl)
, m_name{name} {
this->selectp(selectp);
this->iffp(iffp);
}
ASTGEN_MEMBERS_AstCoverCrossBin;
string name() const override VL_MT_STABLE { return m_name; }
};
class AstCoverCrossSelect final : public AstNode {
// Intersection or union of two cross-bin selections
// @astgen op1 := lhsp : Optional[AstNode] // Null for an unsupported selection
// @astgen op2 := rhsp : Optional[AstNode] // Null for an unsupported selection
const bool m_isOr; // Union (||), rather than intersection (&&)
public:
AstCoverCrossSelect(FileLine* fl, AstNode* lhsp, AstNode* rhsp, bool isOr)
: ASTGEN_SUPER_CoverCrossSelect(fl)
, m_isOr{isOr} {
this->lhsp(lhsp);
this->rhsp(rhsp);
}
ASTGEN_MEMBERS_AstCoverCrossSelect;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool isOr() const { return m_isOr; }
string verilogKwd() const override { return isOr() ? "||" : "&&"; }
bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START
return m_isOr == VN_DBG_AS(samep, CoverCrossSelect)->m_isOr;
} // LCOV_EXCL_STOP
};
class AstCoverOption final : public AstNode {
// Coverage-option assignment
// @astgen op1 := valuep : AstNodeExpr
@@ -1258,6 +1329,8 @@ public:
BROKEN_RTN(!fmtp());
return nullptr;
}
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
string verilogKwd() const override { return "$"s + string{displayType().ascii()}; }
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
@@ -1320,13 +1393,21 @@ public:
};
class AstIntfRef final : public AstNode {
// An interface reference
string m_name; // Name of the reference
string m_name; // Hierarchical path of the reference
string m_baseName; // Final component of m_name, i.e. the reference port name
string m_modportName; // "" = no modport, else name of the modport referenced
public:
AstIntfRef(FileLine* fl, const string& name)
AstIntfRef(FileLine* fl, const string& name, const string& baseName, const string& modportName)
: ASTGEN_SUPER_IntfRef(fl)
, m_name{name} {}
string name() const override VL_MT_STABLE { return m_name; }
, m_name{name}
, m_baseName{baseName}
, m_modportName{modportName} {}
ASTGEN_MEMBERS_AstIntfRef;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
string name() const override VL_MT_STABLE { return m_name; }
string baseName() const { return m_baseName; }
string modportName() const { return m_modportName; }
};
class AstLibrary final : public AstNode {
// Parents: NETLIST
@@ -1487,7 +1568,7 @@ public:
void astConstOrigParamName(const AstConst* nodep, const string& name);
void astConstOrigParamNameErase(const AstConst* nodep);
AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; }
AstPackage* dollarUnitPkgAddp();
void dollarUnitPkgp(AstPackage* const packagep) { m_dollarUnitPkgp = packagep; }
AstCFunc* evalFuncp(VEval eval) const { return m_evalFuncps[eval]; }
void evalFuncp(VEval eval, AstCFunc* funcp) { m_evalFuncps[eval] = funcp; }
AstCFunc* dumpTriggersFuncp(VEval eval) const { return m_dumpTriggersFuncps[eval]; }
@@ -1915,8 +1996,11 @@ public:
class AstTextBlock final : public AstNode {
// Text block emitted into output, with some arbitrary nodes interspersed
// @astgen op1 := nodesp : List[AstNode] // Nodes to print
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_prefix; // Prefix to print before first element in 'nodesp'
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_separator; // Separator to print between each element in 'nodesp'
// dist-ast-dump-suppress // Omitting text blocks due to verbosity
const std::string m_suffix; // Suffix to pring after last element in 'nodesp'
public:
explicit AstTextBlock(FileLine* fl, //
@@ -1965,6 +2049,7 @@ class AstTypeTable final : public AstNode {
AstBasicDType* m_basicps[VBasicDTypeKwd::_ENUM_MAX]{};
//
using DetailedMap = std::map<VBasicTypeKey, AstBasicDType*>;
// dist-ast-dump-suppress // Link to other nodes
DetailedMap m_detailedMap;
public:
@@ -2662,7 +2747,7 @@ public:
const string& fsmTag() const { return m_fsmTag; }
bool sameNode(const AstNode* samep) const override {
const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl);
return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov();
return Super::sameNode(samep) && linescov() == asamep->linescov();
}
};
class AstCoverToggleDecl final : public AstNodeCoverDecl {
@@ -2684,7 +2769,7 @@ public:
const VNumRange& range() const { return m_range; }
bool sameNode(const AstNode* samep) const override {
const AstCoverToggleDecl* const asamep = VN_DBG_AS(samep, CoverToggleDecl);
return AstNodeCoverDecl::sameNode(samep) && range() == asamep->range();
return Super::sameNode(samep) && range() == asamep->range();
}
};
@@ -2802,8 +2887,8 @@ public:
class AstCoverCross final : public AstNodeFuncCovItem {
// @astgen op1 := itemsp : List[AstCoverpointRef]
// @astgen op2 := optionsp : List[AstCoverOption] // post-LinkParse only
// @astgen op3 := rawBodyp : List[AstNode] // Parse: raw cross_body items;
// // post-LinkParse: empty
// @astgen op3 := binsp : List[AstNode] // Parse: mixed cross bins/options;
// // post-LinkParse: AstCoverCrossBin only
// @astgen op4 := iffp : Optional[AstNodeExpr] // Conditional sampling guard
public:
AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp,
@@ -3031,9 +3116,11 @@ public:
AstIface(FileLine* fl, const string& name, const string& libname)
: ASTGEN_SUPER_Iface(fl, name, libname) {}
ASTGEN_MEMBERS_AstIface;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string verilogKwd() const override { return "interface"; }
// Interfaces have `timescale applicability but lots of code seems to
// get false warnings if we enable this
string verilogKwd() const override { return "interface"; }
bool timescaleMatters() const override { return false; }
bool hasVirtualRef() const { return m_hasVirtualRef; }
void setHasVirtualRef() { m_hasVirtualRef = true; }
+12 -11
View File
@@ -413,16 +413,16 @@ class AstCase final : public AstNodeStmt {
// @astgen op1 := exprp : AstNodeExpr // Condition (scurtinee) expression
// @astgen op2 := itemsp : List[AstCaseItem]
// @astgen op3 := notParallelp : List[AstNode] // assertion code for non-full case's
VCaseType m_casex; // 0=case, 1=casex, 2=casez
VCaseType m_caseType; // 0=case, 1=casex, 2=casez
bool m_fullPragma = false; // Synthesis full_case
bool m_parallelPragma = false; // Synthesis parallel_case
bool m_uniquePragma = false; // unique case
bool m_unique0Pragma = false; // unique0 case
bool m_priorityPragma = false; // priority case
public:
AstCase(FileLine* fl, VCaseType casex, AstNodeExpr* exprp, AstCaseItem* itemsp)
AstCase(FileLine* fl, VCaseType caseType, AstNodeExpr* exprp, AstCaseItem* itemsp)
: ASTGEN_SUPER_Case(fl)
, m_casex{casex} {
, m_caseType{caseType} {
this->exprp(exprp);
addItemsp(itemsp);
}
@@ -432,15 +432,15 @@ public:
int instrCount() const override { return INSTR_COUNT_BRANCH; }
string verilogKwd() const override { return casez() ? "casez" : casex() ? "casex" : "case"; }
bool sameNode(const AstNode* samep) const override {
return m_casex == VN_DBG_AS(samep, Case)->m_casex;
return m_caseType == VN_DBG_AS(samep, Case)->m_caseType;
}
bool casex() const { return m_casex == VCaseType::CT_CASEX; }
bool casez() const { return m_casex == VCaseType::CT_CASEZ; }
bool caseInside() const { return m_casex == VCaseType::CT_CASEINSIDE; }
bool caseMatches() const { return m_casex == VCaseType::CT_CASEMATCHES; }
bool caseSimple() const { return m_casex == VCaseType::CT_CASE; }
void caseInsideSet() { m_casex = VCaseType::CT_CASEINSIDE; }
void caseMatchesSet() { m_casex = VCaseType::CT_CASEMATCHES; }
bool casex() const { return m_caseType == VCaseType::CT_CASEX; }
bool casez() const { return m_caseType == VCaseType::CT_CASEZ; }
bool caseInside() const { return m_caseType == VCaseType::CT_CASEINSIDE; }
bool caseMatches() const { return m_caseType == VCaseType::CT_CASEMATCHES; }
bool caseSimple() const { return m_caseType == VCaseType::CT_CASE; }
void caseInsideSet() { m_caseType = VCaseType::CT_CASEINSIDE; }
void caseMatchesSet() { m_caseType = VCaseType::CT_CASEMATCHES; }
bool fullPragma() const { return m_fullPragma; }
void fullPragma(bool flag) { m_fullPragma = flag; }
bool parallelPragma() const { return m_parallelPragma; }
@@ -1340,6 +1340,7 @@ class AstTraceDecl final : public AstNodeStmt {
const VVarType m_varType; // Type of variable (for localparam vs. param)
const VDirection m_declDirection; // Declared direction input/output etc
const bool m_inDtypeFunc; // Trace decl inside type init function
// dist-ast-dump-suppress // Not stable and of low value
int m_codeInc{0}; // Code increment for type
public:
AstTraceDecl(FileLine* fl, const string& showname,
+3559 -3483
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -227,6 +227,52 @@ private:
}
return false;
}
static void checkArgRefs(AstNodeExpr* nodep, const char* descrp, AstNodeExpr* argsp) {
if (!std::strcmp(descrp, "TODO")) return; // Skip if not yet checked
// Check each argument
const char* dp = descrp;
for (AstNodeExpr* argp = argsp; argp; argp = VN_AS(argp->nextp(), NodeExpr)) {
if (argp->fileline()->erroringOn()) return; // Intentionally skip all checks
const AstNodeExpr* const lvalp = argp->cLValueTargetp();
const VAccess access = [&]() -> VAccess {
if (const AstVarRef* const varrefp = VN_CAST(lvalp, VarRef)) {
return varrefp->access();
}
if (const AstMemberSel* const memberselp = VN_CAST(lvalp, MemberSel)) {
return memberselp->access();
}
UASSERT_OBJ(!lvalp, argp, "Unknown LValue expression");
// Not an LValue, so it's read-only
return VAccess::READ;
}();
if (dp[0] == '+') --dp; // Repeats the entry before it
switch (dp[0]) {
case 'r':
UASSERT_OBJ(access.isReadOnly(), argp,
"Input argument of library call is not a read-only expression");
break;
case 'w':
UASSERT_OBJ(lvalp, argp, //
"Output argument of library call is not a valid C++ LValue");
UASSERT_OBJ(access.isWriteOnly(), argp,
"Output argument of library call is not write-only");
break;
case 'm':
UASSERT_OBJ(lvalp, argp, //
"Inout argument of library call is not a valid C++ LValue");
UASSERT_OBJ(access.isRW(), argp,
"Inout argument of library call is not read-write");
break;
default:
UASSERT_OBJ(dp[0], argp, "Unexpected trailing arguments to library call");
break; // LCOV_EXCL_LINE
}
++dp;
}
UASSERT_OBJ(!dp[0] || dp[0] == '+', nodep, "Insufficient arguments to library call");
}
// VISITORS
void visit(AstNodeAssign* nodep) override {
processEnter(nodep);
@@ -291,6 +337,7 @@ private:
void visit(AstCMethodHard* nodep) override {
++m_nCalls;
processAndIterate(nodep);
checkArgRefs(nodep, nodep->method().args(), nodep->pinsp());
}
void visit(AstNodeFTaskRef* nodep) override {
++m_nCalls;
+16 -12
View File
@@ -18,7 +18,8 @@
// Each module:
// For each expression, if it requires a clean operand,
// and the operand is dirty, insert a CLEAN node.
// Resize operands (but not variables or variable selects) to C++ 32/64/wide types.
// Resize operands (but not variables, variable selects, or formatting metadata)
// to C++ 32/64/wide types.
// Copy all width() values to widthMin() so RANGE, etc can still see orig widths
//
//*************************************************************************
@@ -84,6 +85,7 @@ class CleanVisitor final : public VNVisitor {
|| VN_IS(nodep, ConsPackMember) //
|| VN_IS(nodep, NodeDType) // Don't want to change variable widths!
|| VN_IS(nodep, NodeSel) // Array selects should reflect variable widths
|| VN_IS(nodep, SFormatArg) // Retain the logical argument type, not storage width
|| VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType) // Or arrays
|| VN_IS(nodep->dtypep()->skipRefp(), WildcardArrayDType)
|| VN_IS(nodep->dtypep()->skipRefp(), DynArrayDType)
@@ -91,6 +93,8 @@ class CleanVisitor final : public VNVisitor {
|| VN_IS(nodep->dtypep()->skipRefp(), QueueDType)
|| VN_IS(nodep->dtypep()->skipRefp(), StreamDType)
|| VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverCrossDType)
|| VN_IS(nodep->dtypep()->skipRefp(), CoverpointDType)
|| VN_IS(nodep->dtypep()->skipRefp(), VoidDType)) {
} else {
const AstNodeUOrStructDType* const dtypep
@@ -138,12 +142,12 @@ class CleanVisitor final : public VNVisitor {
computeCppWidth(nodep);
if (!isClean(nodep)) insertClean(nodep);
}
void ensureCleanAndNext(AstNodeExpr* nodep) {
void ensureCleanAndNext(AstNode* nodep) {
// Editing list, careful looping!
for (AstNodeExpr* exprp = nodep; exprp;) {
AstNodeExpr* const nextp = VN_AS(exprp->nextp(), NodeExpr);
ensureClean(exprp);
exprp = nextp;
for (AstNode* argp = nodep; argp;) {
AstNode* const nextp = argp->nextp();
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
argp = nextp;
}
}
@@ -247,9 +251,7 @@ class CleanVisitor final : public VNVisitor {
setClean(nodep, false);
// We always clean, as we don't trust those pesky users.
if (!VN_IS(nodep->backp(), And)) insertClean(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstTraceDecl* nodep) override {} // Nothing to do here
void visit(AstTraceInc* nodep) override {
@@ -285,11 +287,13 @@ class CleanVisitor final : public VNVisitor {
ensureCleanAndNext(nodep->exprsp());
setClean(nodep, true); // generates a string, so not relevant
}
void visit(AstCStmt* nodep) override {
iterateChildren(nodep);
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstCStmtUser* nodep) override {
iterateChildren(nodep);
for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) {
if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp);
}
ensureCleanAndNext(nodep->nodesp());
}
void visit(AstNodeCCall* nodep) override {
iterateChildren(nodep);
+10
View File
@@ -41,6 +41,15 @@ public:
if (itFoundPair.second) m_keys.push_back(key);
return itFoundPair.second;
}
template <typename T>
void insert(T begin, const T end) {
static_assert(std::is_same<typename std::iterator_traits<T>::value_type, T_Key>::value,
"T must be an iterator with value type T_Key");
while (begin != end) {
if (m_keySet.insert(*begin).second) m_keys.push_back(*begin);
++begin;
}
}
void clear() {
m_keys.clear();
m_keySet.clear();
@@ -48,6 +57,7 @@ public:
// ACCESSORS
bool empty() const { return m_keys.empty(); }
size_t size() const { return m_keys.size(); }
bool exists(const T_Key& key) const { return m_keySet.find(key) != m_keySet.end(); }
// ITERATORS
+30 -30
View File
@@ -491,41 +491,41 @@ public:
IgnIndices results;
int nextChange = 0;
tree.find(0, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 0);
UASSERT_SELFTEST(const int, nextChange, 10);
UASSERT_SELFTEST(results.size(), 0);
UASSERT_SELFTEST(nextChange, 10);
tree.find(10, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 0);
UASSERT_SELFTEST(const int, results[1], 3);
UASSERT_SELFTEST(const int, nextChange, 11);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 0);
UASSERT_SELFTEST(results[1], 3);
UASSERT_SELFTEST(nextChange, 11);
tree.find(11, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 1);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, nextChange, 15); // Center, or would be 20
UASSERT_SELFTEST(results.size(), 1);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(nextChange, 15); // Center, or would be 20
tree.find(20, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 3);
UASSERT_SELFTEST(const int, results[0], 1);
UASSERT_SELFTEST(const int, results[1], 3);
UASSERT_SELFTEST(const int, results[2], 4);
UASSERT_SELFTEST(const int, nextChange, 21);
UASSERT_SELFTEST(results.size(), 3);
UASSERT_SELFTEST(results[0], 1);
UASSERT_SELFTEST(results[1], 3);
UASSERT_SELFTEST(results[2], 4);
UASSERT_SELFTEST(nextChange, 21);
tree.find(21, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 25); // Center, or would be 30
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 25); // Center, or would be 30
tree.find(30, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 3);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 31);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 3);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 31);
tree.find(40, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 2);
UASSERT_SELFTEST(const int, results[0], 2);
UASSERT_SELFTEST(const int, results[1], 4);
UASSERT_SELFTEST(const int, nextChange, 41);
UASSERT_SELFTEST(results.size(), 2);
UASSERT_SELFTEST(results[0], 2);
UASSERT_SELFTEST(results[1], 4);
UASSERT_SELFTEST(nextChange, 41);
tree.find(41, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 0);
UASSERT_SELFTEST(const int, nextChange, std::numeric_limits<int>::max());
UASSERT_SELFTEST(results.size(), 0);
UASSERT_SELFTEST(nextChange, std::numeric_limits<int>::max());
//
points = {{0, 0}};
for (const auto& it : points) {
@@ -536,8 +536,8 @@ public:
tree.build(data);
//
tree.find(50, results, nextChange);
UASSERT_SELFTEST(const size_t, results.size(), 1);
UASSERT_SELFTEST(const int, results[0], 5);
UASSERT_SELFTEST(results.size(), 1);
UASSERT_SELFTEST(results[0], 5);
}
};
+11 -1
View File
@@ -283,7 +283,7 @@ class CoverageVisitor final : public VNVisitor {
VL_RESTORER_COPY(m_funcTemps);
createHandle(nodep);
m_modp = nodep;
m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops
m_state.m_inModOff = nodep->isTop(); // Already made top shell, no coverage for it
if (!origModp) {
// No blocks cross (non-nested) modules, so save some memory
m_varnames.clear();
@@ -1114,6 +1114,16 @@ class CoverageVisitor final : public VNVisitor {
}
// VISITORS - BOTH
void visit(AstProperty* nodep) override {
VL_RESTORER(m_state);
m_state.m_on = false;
iterateChildren(nodep);
}
void visit(AstSequence* nodep) override {
VL_RESTORER(m_state);
m_state.m_on = false;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
iterateChildren(nodep);
lineTrack(nodep);
+849 -92
View File
File diff suppressed because it is too large Load Diff
+285 -24
View File
@@ -37,6 +37,7 @@
#include "V3Dead.h"
#include "V3Graph.h"
#include "V3Stats.h"
#include <queue>
@@ -44,6 +45,199 @@
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Dead tracking graph
class DeadVertex final : public V3GraphVertex {
VL_RTTI_IMPL(DeadVertex, V3GraphVertex)
AstNode* const m_nodep; // Node that created vertex
uint64_t m_workPos = 0; // Position on DeadWorkList, 0 = not on list
bool m_removable = false; // Subject to dead removal
bool m_isVirtual = false; // Virtual node, indirection
public:
DeadVertex(V3Graph* graphp, AstNode* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~DeadVertex() override = default;
string dotShape() const override {
return isVirtual() ? "diamond" : removable() ? "rectangle" : "ellipse";
}
AstNode* nodep() const VL_MT_STABLE { return m_nodep; }
string name() const override VL_MT_STABLE {
return (isVirtual() ? "[VIRT] "
: removable() ? "[R] "s
: "[NR] ")
+ nodep()->typeName() + ' ' + cvtToHex(nodep()) + ' ' + nodep()->name();
}
bool removable() const { return m_removable; }
void removable(bool flag) { m_removable = flag; }
bool isVirtual() const { return m_isVirtual; }
void isVirtual(bool flag) { m_isVirtual = flag; }
uint64_t workPos() const { return m_workPos; }
void workPos(uint64_t value) { m_workPos = value; }
};
// Work list which keeps ordering of elements (so stable), adds always to
// the end of the queue, and allows arbitrary removal
class DeadWorkList final {
// MEMBERS
static uint64_t s_sequence; // Sequence number, inc each push
std::map<uint64_t, DeadVertex*> m_works; // Work list by sequence
public:
// METHODS
bool empty() const { return m_works.empty(); }
void push(DeadVertex* vtxp) {
if (vtxp->workPos()) return; // Already on list
const uint64_t id = ++s_sequence;
vtxp->workPos(id);
m_works.emplace(id, vtxp);
UINFO(9, "Worklist.push " << vtxp);
}
void erase(DeadVertex* vtxp) {
const uint64_t id = vtxp->workPos();
if (!id) return;
UINFO(9, "Worklist.erase " << vtxp);
const auto it = m_works.find(id);
UASSERT_OBJ(it != m_works.end(), vtxp->nodep(),
"vertex thought to be on work list but not");
m_works.erase(it);
vtxp->workPos(0);
}
DeadVertex* getPopFront() {
UDEBUGONLY(UASSERT(!empty(), "Front invalid to call on empty list"););
const auto it = m_works.begin();
DeadVertex* const vtxp = it->second;
m_works.erase(it);
vtxp->workPos(0);
UINFO(9, "Worklist.getPopFront " << vtxp);
return vtxp;
}
};
uint64_t DeadWorkList::s_sequence = 0;
class DeadGraph final : public V3Graph {
// NODE STATE
// AstNodeFTask::user2p() -> DeadVertex* for this node
// See const VNUser2InUse m_inuser2; inside DeadVisitor
// MEMBERS
DeadWorkList m_funcs; // Functions eligble for deletion
// Each virtual vertex by the ftask name()
std::unordered_map<std::string, DeadVertex*> m_virtualVtxsp;
void newEdge(DeadVertex* fromp, DeadVertex* top) {
new V3GraphEdge{this, fromp, top, 1, false};
if (top->removable()) m_funcs.erase(top); // Now has an input edge
}
void pushWorkMaybe(DeadVertex* vtxp, bool allowSize1) {
if (vtxp->removable()) {
if (VN_IS(vtxp->nodep(), NodeFTask)
&& (vtxp->inEmpty() || (allowSize1 && vtxp->inSize1())))
m_funcs.push(vtxp);
}
}
public:
// METHODS
DeadGraph() = default;
~DeadGraph() override = default;
DeadVertex* findNewVertex(AstNode* nodep) {
DeadVertex* vtxp = nodep->user2u().to<DeadVertex*>();
if (!vtxp) {
vtxp = new DeadVertex{this, nodep};
nodep->user2p(vtxp);
pushWorkMaybe(vtxp, false);
}
UASSERT_OBJ(vtxp->nodep() == nodep, nodep, "Vertex points at different node");
return vtxp;
}
DeadVertex* findVirtualVertex(AstNode* nodep) {
const auto it = m_virtualVtxsp.find(nodep->name());
DeadVertex* vtxp;
if (it != m_virtualVtxsp.end()) {
vtxp = it->second;
} else {
vtxp = new DeadVertex{this, nodep};
vtxp->removable(true);
vtxp->isVirtual(true);
pushWorkMaybe(vtxp, false);
m_virtualVtxsp.emplace(nodep->name(), vtxp);
}
return vtxp;
}
void findNewRemovableVertex(AstNode* nodep, bool removable) {
DeadVertex* const vtxp = findNewVertex(nodep);
// Wasn't removable before (due to earlier insert), make removable now
if (removable && !vtxp->removable()) {
vtxp->removable(true);
pushWorkMaybe(vtxp, false);
}
}
void deleteNodeVertex(AstNode* nodep) {
if (DeadVertex* const vtxp = nodep->user2u().to<DeadVertex*>()) deleteVertex(vtxp);
}
void deleteVertex(DeadVertex* vtxp) {
UINFO(9, "Delete vertex " << vtxp->name());
// Mark all about-to-empty downstream vertices onto worklist
for (const V3GraphEdge& oedge : vtxp->outEdges()) {
DeadVertex* const toVtxp = static_cast<DeadVertex*>(oedge.top());
pushWorkMaybe(toVtxp, true); // size1 ok as about to delete below
}
// Node shouldn't be looking at user2p later as node being deleting, but in case
if (!vtxp->isVirtual()) vtxp->nodep()->user2p(nullptr);
if (vtxp->removable()) m_funcs.erase(vtxp);
VL_DO_DANGLING(vtxp->unlinkDelete(this), vtxp);
}
// This only tracks usage dependancy, not "containership",
// When all needs disappear the related node is eligble for deletion
void needs(AstNode* nodep, AstNode* parentp) {
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
UINFO(9, "Edge node " << nodep << " -> " << parentp);
DeadVertex* const parentVtxp = findNewVertex(parentp);
DeadVertex* const nodeVtxp = findNewVertex(nodep);
UINFO(9, "Edge need " << parentVtxp << " -> " << nodeVtxp);
newEdge(parentVtxp, nodeVtxp);
}
void needsVirtual(AstNodeFTask* nodep, AstNode* parentp) {
// Virtual call can be to any function in the call hierarchy.
// For simplicity rather than tracking possible multiple base
// classes (due to 'implements' classes there can be more than
// one), we simply assume all virtual functions of the same name
// can call any other virtual function of the same name
// Track via an intermediate node.
// All calling parents' verticies -> Virtual Vertex -> all ftasks verticies
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
DeadVertex* const parentVtxp = findNewVertex(parentp);
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
UINFO(9, "Edge needVirtual " << parentVtxp << " -> " << virtualVtxp);
newEdge(parentVtxp, virtualVtxp);
}
void funcVirtual(AstNodeFTask* nodep) {
// Virtual Vertex -> all ftasks verticies
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
DeadVertex* const nodeVtxp = findNewVertex(nodep);
UINFO(9, "Edge funcvirtual " << virtualVtxp << " -> " << nodeVtxp);
newEdge(virtualVtxp, nodeVtxp);
}
bool funcsEmpty() const { return m_funcs.empty(); }
AstNode* funcsGetPopFront() {
DeadVertex* const vtxp = m_funcs.getPopFront();
UASSERT_OBJ(vtxp->inEmpty(), vtxp->nodep(), "Non-empty node on work list");
UASSERT_OBJ(vtxp->removable(), vtxp->nodep(), "Non-removable node on work list");
if (vtxp->isVirtual()) {
// Emptied (no inbound edge) virtual wrapper; all
// destinations verticies are now unused too (e.g. all virtual
// functions of this name may be deleted)
UINFO(9, "Removing virtual " << vtxp);
deleteVertex(vtxp);
return nullptr; // Caller will search again
}
AstNode* const nodep = vtxp->nodep();
return nodep;
}
};
//######################################################################
// Dead state, as a visitor of each AstNode
@@ -54,8 +248,8 @@ class DeadVisitor final : public VNVisitor {
// AstVar::user1() -> int. Count of number of references
// AstVarScope::user1() -> int. Count of number of references
// AstNodeDType::user1() -> int. Count of number of references
// AstNodeFTask::user1() -> int. Count of number of references (via AstNodeFTaskRefs)
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2; // For usage information see DeadGraph
// TYPES
using AssignMap = std::multimap<AstVarScope*, AstNodeAssign*>;
@@ -64,6 +258,9 @@ class DeadVisitor final : public VNVisitor {
const bool m_elimUserVars; // Allow removal of user's vars
const bool m_elimDTypes; // Allow removal of DTypes
const bool m_elimCells; // Allow removal of Cells
DeadGraph m_graph; // Tracking graph
// List of all encountered to avoid another loop through tree
std::vector<AstVar*> m_varsp;
std::vector<AstNode*> m_dtypeElimsp; // Data types might eliminate
@@ -73,7 +270,6 @@ class DeadVisitor final : public VNVisitor {
std::vector<AstCell*> m_cellsp;
std::vector<AstClass*> m_classesp;
std::vector<AstTypedef*> m_typedefsp;
std::queue<AstNodeFTask*> m_tasksp; // All the tasks that could be removed if not called
AssignMap m_assignMap; // List of all simple assignments for each variable
bool m_sideEffect = false; // Side effects discovered in assign RHS
@@ -82,14 +278,19 @@ class DeadVisitor final : public VNVisitor {
AstNodeDType* m_curDTypep = nullptr; // Current NodeDType
AstNodeModule* m_modp = nullptr; // Current module
AstForeachHeader* m_foreachHeaderp = nullptr; // Current foreach header
AstNode* m_containingFTaskRefp = nullptr; // Parent of ftaskref (e.g. task/module)
// STATE - Statistic tracking
VDouble0 m_statFTasksDeadified;
VDouble0 m_statFTasksDemoted;
VDouble0 m_statFTasksMDeadified;
VDouble0 m_statFTasksNMDeadified;
VDouble0 m_statFTasksVirtDeadified;
// METHODS
void deleting(AstNode* nodep) {
UINFO(9, " deleting " << nodep);
m_graph.deleteNodeVertex(nodep);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
@@ -118,12 +319,22 @@ class DeadVisitor final : public VNVisitor {
if (AstNode* const subnodep = nodep->virtRefDTypep()) subnodep->user1Inc();
if (AstNode* const subnodep = nodep->virtRefDType2p()) subnodep->user1Inc();
}
void needsTask(AstNodeFTask* taskp, AstNode* containerp) {
if (!taskp) return; // Unlinked
if (taskp->isVirtual()) {
m_graph.needsVirtual(taskp, containerp);
} else {
m_graph.needs(taskp, containerp);
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (m_modp) m_modp->user1Inc(); // e.g. Class under Package
VL_RESTORER(m_modp);
m_modp = nodep;
VL_RESTORER(m_containingFTaskRefp);
m_containingFTaskRefp = nodep;
if (nodep->dead()) return;
if (nodep->modPublic()) m_modp->user1Inc();
iterateChildren(nodep);
@@ -175,7 +386,7 @@ class DeadVisitor final : public VNVisitor {
iterateChildren(nodep);
if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true;
checkAll(nodep);
if (nodep->taskp()) nodep->taskp()->user1Inc();
needsTask(nodep->taskp(), m_containingFTaskRefp);
if (nodep->classOrPackagep()) {
if (m_elimCells) {
nodep->classOrPackagep(nullptr);
@@ -187,7 +398,7 @@ class DeadVisitor final : public VNVisitor {
void visit(AstModportFTaskRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (nodep->ftaskp()) nodep->ftaskp()->user1Inc();
needsTask(nodep->ftaskp(), m_containingFTaskRefp);
}
void visit(AstRefDType* nodep) override {
iterateChildren(nodep);
@@ -327,13 +538,19 @@ class DeadVisitor final : public VNVisitor {
if (assignInAssign) m_sideEffect = true; // Parent assign shouldn't optimize
}
void visit(AstNodeFTask* nodep) override {
const bool removable = !(nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()
|| nodep->keepAlive() || nodep->isConstructor()
|| (!v3Global.opt.fDeadMethods() && nodep->classMethod()));
m_graph.findNewRemovableVertex(nodep, removable);
//
VL_RESTORER(m_containingFTaskRefp);
m_containingFTaskRefp = nodep;
iterateChildren(nodep);
checkAll(nodep);
if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) {
if (nodep->isVirtual()) m_graph.funcVirtual(nodep);
if (!removable) {
if (m_modp && !m_modp->dead() && !m_modp->verilatorLib())
m_modp->user1Inc(); // Keep container
} else {
m_tasksp.push(nodep);
}
if (nodep->classOrPackagep()) {
if (m_elimCells) {
@@ -374,19 +591,41 @@ class DeadVisitor final : public VNVisitor {
}
void deadCheckTasks() {
while (!m_tasksp.empty()) {
AstNodeFTask* taskp = m_tasksp.front();
m_tasksp.pop();
if (taskp->user1() == 0 && !taskp->classMethod()) {
taskp->foreach([this](AstNodeFTaskRef* ftaskrefp) {
AstNodeFTask* task2p = ftaskrefp->taskp();
if (!task2p) return;
task2p->user1Inc(-1);
if (task2p->user1() == 0) m_tasksp.push(task2p);
});
taskp->user1(-1); // we don't want to try deleting twice
deleting(taskp);
++m_statFTasksDeadified;
while (!m_graph.funcsEmpty()) {
AstNode* const nodep = m_graph.funcsGetPopFront();
if (!nodep) continue;
UINFO(9, "Dead " << nodep);
if (AstNodeFTask* const taskp = VN_CAST(nodep, NodeFTask)) {
if (taskp->isVirtual()) {
++m_statFTasksVirtDeadified;
} else if (taskp->classMethod()) {
++m_statFTasksMDeadified;
} else {
++m_statFTasksNMDeadified;
}
}
deleting(nodep);
}
}
void deadCheckDemote() {
for (V3GraphVertex& gvtx : m_graph.vertices()) {
DeadVertex* const vtxp = gvtx.cast<DeadVertex>();
// A isVirtual vertex with single out means there's only one target virtual function
// that can be virtually called, so can make it non-virtual for faster execution
// (UVM benefits from this)
if (!vtxp->outSize1()) continue;
if (!vtxp->isVirtual()) continue;
for (V3GraphEdge& edge : vtxp->outEdges()) { // Always a single one
AstNode* const nodep = edge.top()->as<DeadVertex>()->nodep();
AstNodeFTask* const funcp = VN_AS(nodep, NodeFTask);
UASSERT_OBJ(funcp->isVirtual(), funcp,
"Only virtual ftasks should be under DeadVirtualVertex");
if (v3Global.opt.fDeadMethods()) {
funcp->isVirtual(false);
UINFO(9, "Demote to non-virtual " << funcp);
++m_statFTasksDemoted;
}
}
}
}
@@ -400,7 +639,10 @@ class DeadVisitor final : public VNVisitor {
AstNodeModule* nextmodp;
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; modp = nextmodp) {
nextmodp = VN_AS(modp->nextp(), NodeModule);
if (modp->dead() || (!modp->isTop() && modp->user1() == 0 && !modp->internal())) {
// Keep $unit until m_elimCells stages. Note v3Global.opt.serializeOnly()
// won't reach this stage, and will always have an empty $unit. That's ok.
const bool keep = !m_elimCells && modp == v3Global.rootp()->dollarUnitPkgp();
if (modp->dead() || (!modp->isTop() && modp->user1() == 0 && !keep)) {
// > 2 because L1 is the wrapper, L2 is the top user module
UINFO(4, " Dead module " << modp);
// And its children may now be killable too; correct counts
@@ -410,6 +652,9 @@ class DeadVisitor final : public VNVisitor {
cellp->modp()->user1Inc(-1);
});
}
if (modp == v3Global.rootp()->dollarUnitPkgp()) {
v3Global.rootp()->dollarUnitPkgp(nullptr);
}
deleting(modp);
retry = true;
}
@@ -566,9 +811,11 @@ public:
bool elimCells, bool elimTopIfaces, bool elimTasks)
: m_elimUserVars{elimUserVars}
, m_elimDTypes{elimDTypes}
, m_elimCells{elimCells} {
, m_elimCells{elimCells}
, m_containingFTaskRefp{nodep} {
// Prepare to remove some datatypes
nodep->typeTablep()->clearCache();
// Operate on whole netlist
iterate(nodep);
@@ -582,6 +829,11 @@ public:
if (itr.first->user1()) itr.second->user1Inc();
}
// Simplify redundant edges (e.g. function calls another function many times)
m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 9 || debug() >= 9)
m_graph.dumpDotFilePrefixed("dead_graph", false);
if (elimTasks) deadCheckTasks();
deadCheckTypedefs();
deadCheckVar();
@@ -594,13 +846,22 @@ public:
if (!elimTopIfaces) preserveTopIfaces(nodep);
deadCheckMod();
// After deleting as much as can, demote some virtual functions
if (elimTasks) deadCheckDemote();
// We may have removed some datatypes, cleanup
nodep->typeTablep()->repairCache();
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
nodep->constPoolp()->rebuildVarScopesAndCache();
}
~DeadVisitor() override {
V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified);
V3Stats::addStatSum("Optimizations, FTasks, virtual-to-nonvirtual demotion",
m_statFTasksDemoted);
V3Stats::addStatSum("Optimizations, FTasks, deadified, methods", m_statFTasksMDeadified);
V3Stats::addStatSum("Optimizations, FTasks, deadified, non-methods",
m_statFTasksNMDeadified);
V3Stats::addStatSum("Optimizations, FTasks, deadified, virtual",
m_statFTasksVirtDeadified);
};
};
+100 -8
View File
@@ -118,6 +118,7 @@
#include "V3Delayed.h"
#include "V3AstUserAllocator.h"
#include "V3ClassGraph.h"
#include "V3Const.h"
#include "V3Stats.h"
@@ -250,6 +251,7 @@ class DelayedVisitor final : public VNVisitor {
// AstVar::user1() -> bool. Set true if already issued MULTIDRIVEN warning
// AstVarRef::user1() -> bool. Set true if target of NBA
// AstAssignDly::user1() -> bool. Set true if already visited
// AstCFunc::user1() -> AstUser1Allocator. See `m_cfuncsCache` below
// AstAssignDly::user2p() -> AstVarScope*: Scope this AstAssignDelay is under
// AstNodeModule::user1p() -> std::unorded_map<std::string, AstVar*> temp map via m_varMap
// AstScope::user1() -> int: Temporary counter for this scope
@@ -259,6 +261,24 @@ class DelayedVisitor final : public VNVisitor {
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
struct CFuncCache final {
VInsertionSet<AstSenTree*> m_timingDomains; // What shall be added to m_timingDomains
std::set<AstCFunc*>
m_includes; // CFuncs whose CFuncCache shall be included into this - this is used to
// break cycles: A->B->A (instead of visiting A while it is still begin
// visited B just marks that it includes A)
enum State : uint8_t {
UNINITIALIZED = 0, // Not initialized members are empty
VISITING, // Visiting - needed for breaking recursion
INITIALIZED, // Members contains correct values
} m_state // Current state of Cache
= UNINITIALIZED;
};
// Caches what should be added to m_timingDomains because of calls to the AstCFunc (with
// recursive check of other AstCFuncs called from inside)
AstUser1Allocator<AstCFunc, CFuncCache> m_cfuncsCache;
AstUser1Allocator<AstNodeModule, std::unordered_map<std::string, AstVar*>> m_varMap;
AstUser1Allocator<AstVarScope, VarScopeInfo> m_vscpInfo;
AstUser3Allocator<AstVarScope, std::vector<WriteReference>> m_writeRefs;
@@ -266,6 +286,10 @@ class DelayedVisitor final : public VNVisitor {
// STATE - across all visitors
VInsertionSet<AstSenTree*> m_timingDomains; // Timing resume domains
const std::unique_ptr<V3ClassGraph>
m_classGraphp; // class graph to get possibly called functions from a virtual call
std::vector<const AstCFunc*> m_callStack; // Current callstack of AstCFuncs
// STATE - for current visit position (use VL_RESTORER)
AstActive* m_activep = nullptr; // Current activate
const AstCFunc* m_cfuncp = nullptr; // Current public C Function
@@ -293,6 +317,7 @@ class DelayedVisitor final : public VNVisitor {
VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial
VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables
VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA
VDouble0 m_nonInlinedCAwaitsWithSenTree; // Count uses of not inlined co_awaits
// METHODS
@@ -563,6 +588,17 @@ class DelayedVisitor final : public VNVisitor {
return ss.str();
}
void addCFuncCachedValues(const AstCFunc* const cfuncp,
std::unordered_set<const AstCFunc*>& visited) {
if (!visited.insert(cfuncp).second) return;
CFuncCache& value = m_cfuncsCache(cfuncp);
m_timingDomains.insert(value.m_timingDomains.begin(), value.m_timingDomains.end());
m_nonInlinedCAwaitsWithSenTree += value.m_timingDomains.size();
for (const AstCFunc* const includedp : value.m_includes) {
addCFuncCachedValues(includedp, visited);
}
}
// Create a temporary variable in the given 'scopep', with the given 'name', and with 'dtypep'
// type, with the bits selected by 'sLsbp'/'sWidthp' set to 'valuep', other bits set to zero.
// Insert new statements before 'insertp'.
@@ -865,8 +901,9 @@ class DelayedVisitor final : public VNVisitor {
activep->addStmtsp(postp);
// Add the commit
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, queueVscp, VAccess::READWRITE}, VCMethod::SCHED_COMMIT};
flp, new AstVarRef{flp, queueVscp, VAccess::READWRITE}, VCMethod::NBA_COMMIT};
callp->dtypeSetVoid();
// TODO: this is a partial update, so must be READWRITE, but that breaks scheduling
callp->addPinsp(new AstVarRef{flp, vscp, VAccess::WRITE});
postp->addStmtsp(callp->makeStmt());
}
@@ -975,7 +1012,7 @@ class DelayedVisitor final : public VNVisitor {
// Enqueue the update at the site of the original NBA
AstCMethodHard* const callp = new AstCMethodHard{
flp, new AstVarRef{flp, vscpInfo.valueQueueKit().vscp, VAccess::READWRITE},
VCMethod::SCHED_ENQUEUE};
VCMethod::NBA_ENQUEUE};
callp->dtypeSetVoid();
callp->addPinsp(valuep);
if (partial) callp->addPinsp(maskp);
@@ -1007,6 +1044,43 @@ class DelayedVisitor final : public VNVisitor {
return procedurep && procedurep->sentreep();
}
// Visit AstCFunc from a AstNodeCCall - this is made into a separate quasi-visitor because
// AstCFunc that is not called from the code (e.g.: DPI exports) does not need to be visited
// this way. Also, not visiting such AstCFuncs allows to avoid caching results for them which
// this function does - which could lead to excessive memory usage
void visitCalledCFunc(AstCFunc* const nodep) {
CFuncCache& value = m_cfuncsCache(nodep);
switch (value.m_state) {
case CFuncCache::UNINITIALIZED: {
// Save current state
VL_RESTORER_CLEAR(m_timingDomains);
// Visit
value.m_state = CFuncCache::VISITING;
m_callStack.push_back(nodep);
{
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
m_callStack.pop_back();
value.m_state = CFuncCache::INITIALIZED;
// Save a cache
std::swap(m_timingDomains, value.m_timingDomains);
} break;
case CFuncCache::VISITING: {
for (size_t i = m_callStack.size() - 1; m_callStack.at(i) != nodep; --i) {
m_cfuncsCache(m_callStack[i]).m_includes.insert(nodep);
}
return; // Break recursion
}
case CFuncCache::INITIALIZED: break;
}
std::unordered_set<const AstCFunc*> visited;
addCFuncCachedValues(nodep, visited);
}
// VISITORS
void visit(AstNetlist* nodep) override {
iterateChildren(nodep);
@@ -1103,11 +1177,6 @@ class DelayedVisitor final : public VNVisitor {
m_scopep = nodep;
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
void visit(AstActive* nodep) override {
UASSERT_OBJ(!m_activep, nodep, "Should not nest");
VL_RESTORER(m_activep);
@@ -1182,6 +1251,7 @@ class DelayedVisitor final : public VNVisitor {
}
void visit(AstCAwait* nodep) override {
if (nodep->sentreep()) m_timingDomains.insert(nodep->sentreep());
iterateChildren(nodep);
}
void visit(AstFireEvent* nodep) override {
UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent");
@@ -1337,6 +1407,24 @@ class DelayedVisitor final : public VNVisitor {
m_inLoop = true;
iterateChildren(nodep);
}
void visit(AstNodeCCall* const nodep) override {
iterateChildren(nodep);
// We need to visit bodies of non-inlined functions
const auto& cfuncps = m_classGraphp->getCallPossibleCFuncs(nodep);
if (cfuncps.empty()) {
visitCalledCFunc(nodep->funcp());
} else {
for (AstCFunc* const cfuncp : cfuncps) visitCalledCFunc(cfuncp);
}
}
void visit(AstCFunc* const nodep) override {
const auto& value = m_cfuncsCache(nodep);
// Check whether it was already visited by visitCalledCFunc()
if (value.m_state != CFuncCache::UNINITIALIZED) return;
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildren(nodep);
}
// Pre/Post logic are created here and their content need no further changes, so ignore.
void visit(AstAlwaysPre*) override {}
@@ -1347,7 +1435,10 @@ class DelayedVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit DelayedVisitor(AstNetlist* nodep) { iterate(nodep); }
explicit DelayedVisitor(AstNetlist* nodep)
: m_classGraphp{V3ClassGraph::build(nodep)} {
iterate(nodep);
}
~DelayedVisitor() override {
V3Stats::addStat("NBA, variables using ShadowVar scheme", m_nSchemeShadowVar);
V3Stats::addStat("NBA, variables using ShadowVarMasked scheme", m_nSchemeShadowVarMasked);
@@ -1358,6 +1449,7 @@ public:
m_nSchemeValueQueuesPartial);
V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags);
V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA);
V3Stats::addStat("Non-inlined co_awaits with SenTree", m_nonInlinedCAwaitsWithSenTree);
}
};
+41
View File
@@ -829,6 +829,47 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
delete this;
}
//------------------------------------------------------------------------------
// DfgVertexVar
std::pair<DfgVertex*, uint32_t> DfgVertexVar::driverOfRange(uint32_t lo, uint32_t size) {
DfgVertex* const srcp = this->srcp();
// Not driven at all
if (!srcp) return {nullptr, 0};
// If volatile, can have other drivers
if (isVolatile()) return {nullptr, 0};
// Don't inline CReset
if (srcp->is<DfgCReset>()) return {nullptr, 0};
// If not driven via a splice, then it is driven whole, at the same offsets
DfgVertexSplice* const splicep = srcp->cast<DfgVertexSplice>();
if (!splicep) return {srcp, lo};
// Find the driver that covers the whole searched range, if there is a single one
const uint32_t hi = lo + size - 1;
DfgVertex* driverp = nullptr;
uint32_t driverLo = 0;
bool useDefault = defaultp();
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLo) {
const uint32_t dHi = dLo + src.size() - 1;
// Note whether it overlaps the searched range, so the default cannot be used
if (dLo <= hi && lo <= dHi) useDefault = false;
// If it does not cover the whole searched range, move on
if (lo < dLo || dHi < hi) return false;
// Save the driver that covers the whole searched range
driverp = &src;
driverLo = dLo;
return true;
});
// If a single driver covers the searched range, it is the one
if (driverp) return {driverp, lo - driverLo};
// Otherwise the default driver is responsible for it, if nothing else overlaps it
if (useDefault) return {defaultp(), lo};
// Not driven by a single vertex
return {nullptr, 0};
}
//######################################################################
// Renders the canonical pattern S-expression for a single DfgVertex
+20 -5
View File
@@ -49,6 +49,7 @@
#include <new>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#ifndef VL_NOT_FINAL
@@ -247,7 +248,10 @@ public:
// Calls given function 'f' for each source vertex of this vertex. If 'f'
// returns true, further sources are not iterated and this method returns
// true itself. Unconnected source edges are not iterated.
bool foreachSource(std::function<bool(DfgVertex&)> f) {
template <typename T_Callable>
bool foreachSource(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
for (const std::unique_ptr<DfgEdge>& edgep : m_inputps) {
if (DfgVertex* const srcp = edgep->srcp()) {
if (f(*srcp)) return true;
@@ -259,9 +263,13 @@ public:
// Calls given function 'f' for each source vertex of this vertex. If 'f'
// returns true, further sources are not iterated and this method returns
// true itself. Unconnected source edges are not iterated.
bool foreachSource(std::function<bool(const DfgVertex&)> f) const {
template <typename T_Callable>
bool foreachSource(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
for (const std::unique_ptr<DfgEdge>& edgep : m_inputps) {
if (DfgVertex* const srcp = edgep->srcp()) {
if (const DfgVertex* const srcp = edgep->srcp()) {
if (f(*srcp)) return true;
}
}
@@ -272,7 +280,10 @@ public:
// returns true, further sinks are not iterated and this method returns
// true itself. Unlinking/deleting the given sink during iteration is safe,
// but not other sinks of this vertex.
bool foreachSink(std::function<bool(DfgVertex&)> f) {
template <typename T_Callable>
bool foreachSink(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
for (const DfgEdge* const edgep : m_sinks.unlinkable()) {
if (f(*edgep->dstp())) return true;
}
@@ -282,7 +293,11 @@ public:
// Calls given function 'f' for each sink vertex of this vertex. If 'f'
// returns true, further sinks are not iterated and this method returns
// true itself.
bool foreachSink(std::function<bool(const DfgVertex&)> f) const {
template <typename T_Callable>
bool foreachSink(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
for (const DfgEdge& edge : m_sinks) {
if (f(*edge.dstp())) return true;
}
+139 -182
View File
@@ -25,8 +25,11 @@
#ifndef VERILATOR_V3DFGCACHE_H_
#define VERILATOR_V3DFGCACHE_H_
#include "verilatedos.h"
#include "V3Dfg.h"
#include "V3DfgDataType.h"
#include "V3HashTable.h"
#include <type_traits>
@@ -52,137 +55,107 @@ struct V3DfgCacheType<Vertex, CacheBase, VertexBase, Cache, Pairs...> final {
class V3DfgCache final {
// TYPES
class KeySel final {
const DfgDataType& m_dtype;
const DfgVertex* const m_fromp;
const uint32_t m_lsb;
// Hashing and comparison of the cached vertices. Each takes either a vertex, or the
// parts a vertex would be created from, so a lookup needs no vertex and no key object.
public:
KeySel(const DfgDataType& dtype, DfgVertex* fromp, uint32_t lsb)
: m_dtype{dtype}
, m_fromp{fromp}
, m_lsb{lsb} {}
explicit KeySel(const DfgSel* vtxp)
: m_dtype{vtxp->dtype()}
, m_fromp{vtxp->fromp()}
, m_lsb{vtxp->lsb()} {}
struct Hash final {
size_t operator()(const KeySel& key) const {
// cppcheck-suppress unreadVariable // cppcheck bug
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_fromp);
hash += key.m_lsb;
return hash.value();
}
};
struct Equal final {
bool operator()(const KeySel& a, const KeySel& b) const {
return a.m_lsb == b.m_lsb && a.m_dtype == b.m_dtype
&& vertexEqual(a.m_fromp, b.m_fromp);
}
};
// DfgSel
struct HashSel final {
size_t operator()(const DfgSel* vtxp) const {
return operator()(vtxp->dtype(), vtxp->fromp(), vtxp->lsb());
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* fromp, uint32_t lsb) const {
// cppcheck-suppress unreadVariable // cppcheck bug
V3Hash hash = dtype.hash();
hash += vertexHash(fromp);
hash += lsb;
return hash.value();
}
};
struct EqualSel final {
bool operator()(const DfgSel* ap, const DfgSel* bp) const {
return operator()(ap, bp->dtype(), bp->fromp(), bp->lsb());
}
bool operator()(const DfgSel* vtxp, const DfgDataType& dtype, const DfgVertex* fromp,
uint32_t lsb) const {
return vtxp->lsb() == lsb && vtxp->dtype() == dtype
&& vertexEqual(vtxp->fromp(), fromp);
}
};
class KeyUnary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
public:
// cppcheck-suppress noExplicitConstructor
KeyUnary(const DfgDataType& dtype, DfgVertex* source0p)
: m_dtype{dtype}
, m_source0p{source0p} {}
explicit KeyUnary(const DfgVertexUnary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)} {}
struct Hash final {
size_t operator()(const KeyUnary& key) const { //
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyUnary& a, const KeyUnary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p);
}
};
// DfgVertexUnary
struct HashUnary final {
size_t operator()(const DfgVertexUnary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
return hash.value();
}
};
struct EqualUnary final {
bool operator()(const DfgVertexUnary* ap, const DfgVertexUnary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0));
}
bool operator()(const DfgVertexUnary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p);
}
};
class KeyBinary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
const DfgVertex* const m_source1p;
public:
KeyBinary(const DfgDataType& dtype, DfgVertex* source0p, DfgVertex* source1p)
: m_dtype{dtype}
, m_source0p{source0p}
, m_source1p{source1p} {}
explicit KeyBinary(const DfgVertexBinary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)}
, m_source1p{vtxp->inputp(1)} {}
struct Hash final {
size_t operator()(const KeyBinary& key) const {
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
hash += vertexHash(key.m_source1p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyBinary& a, const KeyBinary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p)
&& vertexEqual(a.m_source1p, b.m_source1p);
}
};
// DfgVertexBinary
struct HashBinary final {
size_t operator()(const DfgVertexBinary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p,
const DfgVertex* source1p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
hash += vertexHash(source1p);
return hash.value();
}
};
struct EqualBinary final {
bool operator()(const DfgVertexBinary* ap, const DfgVertexBinary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0), bp->inputp(1));
}
bool operator()(const DfgVertexBinary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p, const DfgVertex* source1p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p)
&& vertexEqual(vtxp->inputp(1), source1p);
}
};
class KeyTernary final {
const DfgDataType& m_dtype;
const DfgVertex* const m_source0p;
const DfgVertex* const m_source1p;
const DfgVertex* const m_source2p;
public:
KeyTernary(const DfgDataType& dtype, DfgVertex* source0p, DfgVertex* source1p,
DfgVertex* source2p)
: m_dtype{dtype}
, m_source0p{source0p}
, m_source1p{source1p}
, m_source2p{source2p} {}
explicit KeyTernary(const DfgVertexTernary* vtxp)
: m_dtype{vtxp->dtype()}
, m_source0p{vtxp->inputp(0)}
, m_source1p{vtxp->inputp(1)}
, m_source2p{vtxp->inputp(2)} {}
struct Hash final {
size_t operator()(const KeyTernary& key) const {
V3Hash hash = key.m_dtype.hash();
hash += vertexHash(key.m_source0p);
hash += vertexHash(key.m_source1p);
hash += vertexHash(key.m_source2p);
return hash.value();
}
};
struct Equal final {
bool operator()(const KeyTernary& a, const KeyTernary& b) const {
return a.m_dtype == b.m_dtype && vertexEqual(a.m_source0p, b.m_source0p)
&& vertexEqual(a.m_source1p, b.m_source1p)
&& vertexEqual(a.m_source2p, b.m_source2p);
}
};
// DfgVertexTernary
struct HashTernary final {
size_t operator()(const DfgVertexTernary* vtxp) const {
return operator()(vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1), vtxp->inputp(2));
}
size_t operator()(const DfgDataType& dtype, const DfgVertex* source0p,
const DfgVertex* source1p, const DfgVertex* source2p) const {
V3Hash hash = dtype.hash();
hash += vertexHash(source0p);
hash += vertexHash(source1p);
hash += vertexHash(source2p);
return hash.value();
}
};
struct EqualTernary final {
bool operator()(const DfgVertexTernary* ap, const DfgVertexTernary* bp) const {
return operator()(ap, bp->dtype(), bp->inputp(0), bp->inputp(1), bp->inputp(2));
}
bool operator()(const DfgVertexTernary* vtxp, const DfgDataType& dtype,
const DfgVertex* source0p, const DfgVertex* source1p,
const DfgVertex* source2p) const {
return vtxp->dtype() == dtype && vertexEqual(vtxp->inputp(0), source0p)
&& vertexEqual(vtxp->inputp(1), source1p)
&& vertexEqual(vtxp->inputp(2), source2p);
}
};
// Base class of vertex caches
class CacheBase VL_NOT_FINAL {
protected:
// These set the operands of a new vertex
@@ -210,86 +183,68 @@ class V3DfgCache final {
public:
// CacheBase does not cache anything
virtual DfgVertex* cache(DfgVertex*) { return nullptr; }
virtual void invalidate(const DfgVertex*) {}
virtual void invalidate(DfgVertex*) {}
};
template <typename T_Key, typename T_Vertex>
template <typename T_Vertex, typename T_Hash, typename T_Equal>
class Cache final : public CacheBase {
static_assert(std::is_base_of<DfgVertex, T_Vertex>::value, "T_Vertex must be a DfgVertex");
// TYPES
using Hash = typename T_Key::Hash;
using Equal = typename T_Key::Equal;
using Map = std::unordered_map<T_Key, T_Vertex*, Hash, Equal>;
// STATE
Map m_map;
// METHODS
// These return a reference to the mapped entry, inserting a nullptr if not yet exists
template <typename... T_Args>
T_Vertex*& entry(T_Args&&... args) {
const T_Key key{std::forward<T_Args>(args)...};
return m_map[key];
}
template <typename... T_Args>
typename Map::iterator find(T_Args&&... args) {
const T_Key key{std::forward<T_Args>(args)...};
return m_map.find(key);
}
V3HashSet<T_Vertex*, T_Hash, T_Equal> m_set;
public:
// Add an existing vertex to the cache. If an equivalent exists,
// it is returned and the cache is not updated.
// Add an existing vertex to the cache. If an equivalent but different vertex exists,
// it is returned and the cache is not updated. Returns nullptr if the vertex is inserted.
DfgVertex* cache(DfgVertex* vtxp) override {
UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type");
T_Vertex*& entrypr = entry(static_cast<const T_Vertex*>(vtxp));
if (entrypr && entrypr != vtxp) return entrypr;
entrypr = static_cast<T_Vertex*>(vtxp);
return nullptr;
UDEBUGONLY(UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type"););
T_Vertex* const typedp = static_cast<T_Vertex*>(vtxp);
T_Vertex* const cachedp = *m_set.insert(typedp).first;
return cachedp != vtxp ? cachedp : nullptr;
}
// Remove an existing vertex from the cache, if it is the cached vertex, otherwise no-op
void invalidate(const DfgVertex* vtxp) override {
UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type");
const auto it = find(static_cast<const T_Vertex*>(vtxp));
if (it != m_map.end() && it->second == vtxp) m_map.erase(it);
void invalidate(DfgVertex* vtxp) override {
UDEBUGONLY(UASSERT_OBJ(vtxp->is<T_Vertex>(), vtxp, "Vertex is wrong type"););
T_Vertex* const typedp = static_cast<T_Vertex*>(vtxp);
const auto it = m_set.find(typedp);
if (it != m_set.end() && *it == typedp) m_set.erase(it);
}
// Get vertex with given operands, return nullptr if not in cache
template <typename Vertex, typename... Operands>
Vertex* get(const DfgDataType& dtype, Operands... operands) {
const auto it = find(dtype, operands...);
return it != m_map.end() ? static_cast<Vertex*>(it->second) : nullptr;
const auto it = m_set.find(dtype, operands...);
return it != m_set.end() ? static_cast<Vertex*>(*it) : nullptr;
}
// Get or create (and insert) vertex with given operands
// Get vertex with given operands, if does not exist, create it
template <typename Vertex, typename... Operands>
Vertex* getOrCreate(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype,
Operands... operands) {
T_Vertex*& entryr = entry(dtype, operands...);
if (!entryr) {
T_Vertex* const newp = new Vertex{dfg, flp, dtype};
const auto pair = m_set.insertLazy(dtype, operands..., [&]() -> T_Vertex* {
Vertex* const newp = new Vertex{dfg, flp, dtype};
setOperands(newp, operands...);
entryr = newp;
}
return static_cast<Vertex*>(entryr);
return newp;
});
T_Vertex* const vtxp = *pair.first;
UDEBUGONLY(UASSERT_OBJ(vtxp->template is<Vertex>(), vtxp, "Vertex is wrong type"););
return static_cast<Vertex*>(vtxp);
}
};
// Map from Vertex type to cache type
// clang-format off
template <typename Vertex>
using CacheType =
typename V3DfgCacheType<Vertex, CacheBase, //
DfgSel, Cache<KeySel, DfgSel>, //
DfgVertexUnary, Cache<KeyUnary, DfgVertexUnary>, //
DfgVertexBinary, Cache<KeyBinary, DfgVertexBinary>, //
DfgVertexTernary, Cache<KeyTernary, DfgVertexTernary> //
>::Type;
using CacheType = typename V3DfgCacheType<Vertex, CacheBase,
DfgSel, /* -> */ Cache<DfgSel, HashSel, EqualSel>,
DfgVertexUnary, /* -> */ Cache<DfgVertexUnary, HashUnary, EqualUnary>,
DfgVertexBinary, /* -> */ Cache<DfgVertexBinary, HashBinary, EqualBinary>,
DfgVertexTernary, /* -> */ Cache<DfgVertexTernary, HashTernary, EqualTernary>
>::Type;
// clang-format on
// STATE
DfgGraph& m_dfg; // The DfgGraph we are caching the vertices of
// The per type caches
// The per type caches
#define VERTEX_CACHE_DECLARE_CACHE(t) CacheType<t> m_cache##t;
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE)
#undef VERTEX_CACHE_DECLARE_CACHE
@@ -328,21 +283,23 @@ class V3DfgCache final {
}
public:
// Note: the cache starts out empty. If the caller wants existing vertices
// to be found, it must add them itself by calling 'cache' on each.
explicit V3DfgCache(DfgGraph& dfg)
: m_dfg{dfg} {
// Initialize the type to cache lookup table
// Initialize the type to cache lookup table
#define VERTEX_CACHE_DECLARE_CACHE_PTR(t) m_vtxType2Cachep[t::dfgType()] = &m_cache##t;
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE_PTR)
FOREACH_DFG_VERTEX_TYPE(VERTEX_CACHE_DECLARE_CACHE_PTR)
#undef VERTEX_CACHE_DECLARE_CACHE_PTR
}
// Add all operation vertices to the cache
for (DfgVertex& vtx : m_dfg.opVertices()) cache(&vtx);
// Add an existing vertex to the cache. If an equivalent (but different) already exists,
// it is returned and the cache is not updated.
DfgVertex
* cache(DfgVertex * vtxp) {
return m_vtxType2Cachep[vtxp->type()]->cache(vtxp);
}
// Add an existing vertex to the cache. If an equivalent (but different) already exists,
// it is returned and the cache is not updated.
DfgVertex* cache(DfgVertex* vtxp) { return m_vtxType2Cachep[vtxp->type()]->cache(vtxp); }
// Remove an exiting vertex, it is the cached vertex.
void invalidate(DfgVertex* vtxp) { m_vtxType2Cachep[vtxp->type()]->invalidate(vtxp); }
+4 -6
View File
@@ -241,17 +241,15 @@ class V3DfgRemoveSelectsContext final : public V3DfgSubContext {
public:
// STATE
VDouble0 m_removedFullWidth; // Number of full width selects removed
VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver
VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver
VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver
VDouble0 m_replacedWithSelFromDriver; // Number of selects replaced with sel from a driver
VDouble0 m_replacedWithWholeDriver; // Number of selects replaced with a whole driver
private:
V3DfgRemoveSelectsContext()
: V3DfgSubContext{"RemoveSelects"} {}
~V3DfgRemoveSelectsContext() {
addStat("full width selects removed", m_removedFullWidth);
addStat("replaced with sel from full driver", m_replacedWithSelFromFull);
addStat("replaced with sel from partial driver", m_replacedWithSelFromPart);
addStat("replaced with partial driver", m_replacedWithPart);
addStat("replaced with sel from driver", m_replacedWithSelFromDriver);
addStat("replaced with whole driver", m_replacedWithWholeDriver);
}
};
class V3DfgRemoveUnobservableContext final : public V3DfgSubContext {
+139 -114
View File
@@ -18,30 +18,43 @@
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include "V3HashTable.h"
VL_DEFINE_DEBUG_FUNCTIONS;
class V3DfgCse final {
// TYPES
using VertexPair = std::pair<const DfgVertex*, const DfgVertex*>;
struct VertexPairHash final {
size_t operator()(const VertexPair& pair) const {
V3Hash hash;
hash += pair.first;
hash += pair.second;
return hash.value();
}
};
// Hash functor for V3HashSet - depends on vertex and all its inputs
class DfgCseHash final {
// STATE
// The graph being processed
DfgGraph& m_dfg;
// Cache for vertex hashes
DfgUserMap<V3Hash> m_hashCache = m_dfg.makeUserMap<V3Hash>();
// Cache for vertex equality
std::unordered_map<VertexPair, uint8_t, VertexPairHash> m_equivalentCache;
mutable DfgUserMap<V3Hash> m_cache; // Cache for vertex hashes
public:
// CONSTRUCTOR
explicit DfgCseHash(DfgGraph& dfg)
: m_cache{dfg.makeUserMap<V3Hash>()} {
// Pre-hash variables, these are all unique, so just set their hash to a unique value
uint32_t fixedHash = 0;
for (const DfgVertexVar& vtx : dfg.varVertices()) m_cache[vtx] = V3Hash{++fixedHash};
// Pre-hash Ast references, these are all unique like variables
for (const DfgVertexAst& vtx : dfg.astVertices()) m_cache[vtx] = V3Hash{++fixedHash};
// Pre-hash CReset and Prev vertices, these are all unique
for (const DfgVertex& vtx : dfg.opVertices()) {
if (vtx.is<DfgCReset>() || vtx.is<DfgPrev>()) m_cache[vtx] = V3Hash{++fixedHash};
}
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
// expressions using the same constants to be combined, so we do need to hash equal
// constants to equal values.
++fixedHash;
for (const DfgConst& vtx : dfg.constVertices()) {
const V3Hash hash = vtx.num().toHash() + fixedHash;
// Technically possible for a hash to be zero, 'vertexSelfHash' assumes it isn't
m_cache[vtx] = VL_LIKELY(hash.value()) ? hash : V3Hash{1};
}
}
// METHODS
size_t operator()(DfgVertex* vtxp) const { return vertexHash(*vtxp).value(); }
private:
// Returns hash of vertex dependent on information internal to the vertex
static V3Hash vertexSelfHash(const DfgVertex& vtx) {
switch (vtx.type()) {
@@ -135,29 +148,53 @@ class V3DfgCse final {
VL_UNREACHABLE;
}
// Returns hash of vertex dependent on and all its input
V3Hash vertexHash(DfgVertex& vtx) {
V3Hash& result = m_hashCache[vtx];
// Returns hash of vertex dependent on itself and all its inputs - memoized
V3Hash vertexHash(DfgVertex& vtx) const {
V3Hash& result = m_cache[vtx];
// Technically possible for a hash to be zero, but rare, so assume 0 means uninitialized
if (!result.value()) {
V3Hash hash{vertexSelfHash(vtx)};
// Variables are defined by themselves, so there is no need to hash them further
// (especially the sources). This enables sound hashing of graphs circular only through
// variables, which we rely on.
if (!vtx.is<DfgVertexVar>()) {
hash += vtx.type();
hash += vtx.size();
vtx.foreachSource([&](DfgVertex& src) {
hash += vertexHash(src);
return false;
});
}
hash += vtx.type();
hash += vtx.size();
vtx.foreachSource([&](DfgVertex& src) {
hash += vertexHash(src); // Graph is acyclic, so this terminates
return false;
});
result = hash;
}
return result;
}
};
// Equal functor for V3HashSet - depends on vertex and all its inputs
class DfgCseEqual final {
// TYPES
using VertexPair = std::pair<const DfgVertex*, const DfgVertex*>;
struct VertexPairHash final {
size_t operator()(const VertexPair& pair) const {
V3Hash hash;
hash += pair.first;
hash += pair.second;
return hash.value();
}
};
// STATE
mutable V3HashMap<VertexPair, bool, VertexPairHash> m_cache; // Cache for vertex equality
mutable std::vector<uint32_t> m_driverLo; // Low indices of drivers
const size_t m_size; // Size of the graph
public:
// CONSTRUCTORS
explicit DfgCseEqual(const DfgGraph& dfg)
: m_size{dfg.size()} {}
// METHODS
bool operator()(DfgVertex* ap, DfgVertex* bp) const { return vertexEquivalent(*ap, *bp); }
private:
// Compare 'a' and 'b' for equivalence based on their internal information only
bool vertexSelfEquivalent(const DfgVertex& a, const DfgVertex& b) {
bool vertexSelfEquivalent(const DfgVertex& a, const DfgVertex& b) const {
// Note: 'a' and 'b' are of the same Vertex type, data type, and have
// the same number of inputs with matching types. This is established
// by 'vertexEquivalent'.
@@ -187,16 +224,17 @@ class V3DfgCse final {
case VDfgType::SplicePacked: {
const DfgVertexSplice* const ap = a.as<DfgVertexSplice>();
// Gather indices of drivers of 'a'
std::vector<uint32_t> aLo;
aLo.reserve(ap->nInputs());
m_driverLo.clear();
m_driverLo.reserve(ap->nInputs());
ap->foreachDriver([&](const DfgVertex&, uint32_t lo) {
aLo.push_back(lo);
m_driverLo.push_back(lo);
return false;
});
// Compare indices of drivers of 'b'
uint32_t* aLop = aLo.data();
return !b.as<DfgVertexSplice>()->foreachDriver(
[&](const DfgVertex&, uint32_t lo) { return *aLop++ != lo; });
// Compare indices of drivers of 'b', equal if all match
uint32_t* aLop = m_driverLo.data();
return !b.as<DfgVertexSplice>()->foreachDriver([&](const DfgVertex&, uint32_t lo) { //
return *aLop++ != lo;
});
}
// Vertices with no internal information
@@ -262,8 +300,20 @@ class V3DfgCse final {
VL_UNREACHABLE;
}
// Compares the sources of 'a' and 'b' for equivalence
bool sourcesEquivalent(const DfgVertex& a, const DfgVertex& b) const {
for (size_t i = 0; i < a.nInputs(); ++i) {
const DfgVertex* const ap = a.inputp(i);
const DfgVertex* const bp = b.inputp(i);
if (!ap && !bp) continue;
if (!ap || !bp) return false;
if (!vertexEquivalent(*ap, *bp)) return false; // Graph is acyclic, so this terminates
}
return true;
}
// Compares 'a' and 'b' for equivalence
bool vertexEquivalent(const DfgVertex& a, const DfgVertex& b) {
bool vertexEquivalent(const DfgVertex& a, const DfgVertex& b) const {
// If same vertex, then equal
if (&a == &b) return true;
@@ -279,82 +329,57 @@ class V3DfgCse final {
// Check vertex specifics
if (!vertexSelfEquivalent(a, b)) return false;
// Check sources
// A given pair can only be reached more than once if one of the
// vertices has multiple sinks, or if there was a hash collision.
// Collisions are rare, so only memoize the result if it can actually
// be looked up again through multiple paths.
if (!a.hasMultipleSinks() && !b.hasMultipleSinks()) return sourcesEquivalent(a, b);
// Need to compare the source vertices, check memo
const VertexPair key = (&a < &b) ? std::make_pair(&a, &b) : std::make_pair(&b, &a);
// The recursive invocation can cause a re-hash but that will not invalidate references
uint8_t& result = m_equivalentCache[key];
if (!result) {
const bool equal = [&]() {
for (size_t i = 0; i < a.nInputs(); ++i) {
const DfgVertex* const ap = a.inputp(i);
const DfgVertex* const bp = b.inputp(i);
if (!ap && !bp) continue;
if (!ap || !bp) return false;
if (!vertexEquivalent(*ap, *bp)) return false;
}
return true;
}();
result = (static_cast<uint8_t>(equal) << 1) | 1;
}
return result >> 1;
}
const auto it = m_cache.find(key);
if (it != m_cache.end()) return it->second;
V3DfgCse(DfgGraph& dfg, V3DfgCseContext& ctx)
: m_dfg{dfg} {
std::unordered_map<V3Hash, std::vector<DfgVertex*>> verticesWithEqualHashes;
verticesWithEqualHashes.reserve(dfg.size());
// Not memoized yet, so compute and memoize, reserve table on first insert
const bool equal = sourcesEquivalent(a, b);
if (VL_UNLIKELY(m_cache.empty())) m_cache.reserve(m_size / 4);
m_cache.insert({key, equal});
// Pre-hash variables, these are all unique, so just set their hash to a unique value
uint32_t varHash = 0;
for (const DfgVertexVar& vtx : dfg.varVertices()) m_hashCache[vtx] = V3Hash{++varHash};
// Pre-hash Ast references, these are all unique like variables
for (const DfgVertexAst& vtx : dfg.astVertices()) m_hashCache[vtx] = V3Hash{++varHash};
// Pre-hash CReset and Prev vertices, these are all unique
for (const DfgVertex& vtx : dfg.opVertices()) {
if (vtx.is<DfgCReset>() || vtx.is<DfgPrev>()) m_hashCache[vtx] = V3Hash{++varHash};
}
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
// expressions using the same constants to be combined, so we do need to hash equal
// constants to equal values.
for (DfgConst* const vtxp : dfg.constVertices().unlinkable()) {
// Delete unused constants while we are at it.
if (!vtxp->hasSinks()) {
VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
continue;
}
m_hashCache[vtxp] = vtxp->num().toHash() + varHash;
}
// Combine operation vertices
for (DfgVertex* const vtxp : dfg.opVertices().unlinkable()) {
// Delete unused nodes while we are at it.
if (!vtxp->hasSinks()) {
vtxp->unlinkDelete(dfg);
continue;
}
std::vector<DfgVertex*>& vec = verticesWithEqualHashes[vertexHash(*vtxp)];
bool replaced = false;
for (DfgVertex* const candidatep : vec) {
if (vertexEquivalent(*candidatep, *vtxp)) {
++ctx.m_eliminated;
vtxp->replaceWith(candidatep);
VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
replaced = true;
break;
}
}
if (replaced) continue;
vec.push_back(vtxp);
}
}
public:
static void apply(DfgGraph& dfg, V3DfgCseContext& ctx) {
{ V3DfgCse{dfg, ctx}; }
// Prune unused nodes
V3DfgPasses::removeUnused(dfg);
// The predicate result
return equal;
}
};
void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { V3DfgCse::apply(dfg, ctx); }
// Combine equivalent operation vertices
void dfgCseCombineEquivalent(DfgGraph& dfg, V3DfgCseContext& ctx) {
// Delete unused constants, so the pre-hashing below need not consider them
for (DfgConst* const vtxp : dfg.constVertices().unlinkable()) {
if (!vtxp->hasSinks()) VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
}
// Set of unique vertices. This set does all the work identifying equivalent vertices.
V3HashSet<DfgVertex*, DfgCseHash, DfgCseEqual> uniqueVtxps{DfgCseHash{dfg}, DfgCseEqual{dfg}};
// There is at most one entry per vertex
uniqueVtxps.reserve(dfg.size());
// Combine operation vertices
for (DfgVertex* const vtxp : dfg.opVertices().unlinkable()) {
// Delete unused nodes while we are at it.
if (!vtxp->hasSinks()) {
vtxp->unlinkDelete(dfg);
continue;
}
// Insert the vertex into the set, if an equivalent is found, replace the vertex with it
const auto pair = uniqueVtxps.insert(vtxp);
if (!pair.second) {
++ctx.m_eliminated;
vtxp->replaceWith(*pair.first);
VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
}
}
}
void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
dfgCseCombineEquivalent(dfg, ctx);
V3DfgPasses::removeUnused(dfg);
}
+11 -34
View File
@@ -131,58 +131,35 @@ void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) {
if (selp->fromp()->dtype() == dtype) {
++ctx.m_removedFullWidth;
selp->replaceWith(selp->fromp());
VL_DO_DANGLING(selp->unlinkDelete(dfg), selp);
continue;
}
// Push selects through synthesis temporaries only
DfgVarPacked* const varp = selp->fromp()->cast<DfgVarPacked>();
if (!varp || !varp->tmpForp()) continue;
DfgVertex* const srcp = varp->srcp();
if (!srcp) continue;
// Don't inline CReset
if (srcp->is<DfgCReset>()) continue;
const uint32_t lsb = selp->lsb();
const uint32_t msb = lsb + selp->width() - 1;
// If driven whole, select from the driver
if (!srcp->is<DfgSplicePacked>()) {
++ctx.m_replacedWithSelFromFull;
DfgSel* const newSelp = new DfgSel{dfg, flp, dtype};
newSelp->lsb(lsb);
newSelp->fromp(srcp);
selp->replaceWith(newSelp);
continue;
}
// Otherwise attemt to select from the partial driver
DfgSplicePacked* const splicep = srcp->as<DfgSplicePacked>();
DfgVertex* driverp = nullptr;
uint32_t driverLsb = 0;
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) {
const uint32_t dMsb = dLsb + src.width() - 1;
// If it does not cover the whole searched bit range, move on
if (lsb < dLsb || dMsb < msb) return false;
// Save the driver
driverp = &src;
driverLsb = dLsb;
return true;
});
// Find the driver of this range
const auto pair = varp->driverOfRange(selp->lsb(), selp->width());
DfgVertex* const driverp = pair.first;
const uint32_t driverLsb = pair.second;
if (!driverp) continue;
// If partial driver is the whole thing we are looking for, just replace with the driver
// If partial driver is the whole thing we are looking for, just replace with that
if (driverp->dtype() == dtype) {
++ctx.m_replacedWithPart;
++ctx.m_replacedWithWholeDriver;
selp->replaceWith(driverp);
VL_DO_DANGLING(selp->unlinkDelete(dfg), selp);
continue;
}
// Otherwise create a new select from the partial driver
++ctx.m_replacedWithSelFromPart;
++ctx.m_replacedWithSelFromDriver;
DfgSel* const newSelp = new DfgSel{dfg, flp, dtype};
newSelp->lsb(lsb - driverLsb);
newSelp->lsb(driverLsb);
newSelp->fromp(driverp);
selp->replaceWith(newSelp);
VL_DO_DANGLING(selp->unlinkDelete(dfg), selp);
}
}
+69 -27
View File
@@ -195,6 +195,7 @@ class V3DfgPeephole final : public DfgVisitor {
size_t m_iterListIndex = 0; // Position of this vertx m_iterList (0 means not in list)
size_t m_generation = 0; // Generation number of this vertex - for uniqueness check
size_t m_id = 0; // Unique vertex ID (0 means unassigned) - for sorting
bool m_isCachedVertex = false; // This vertex is the vertex cached for its operation
};
// STATE
@@ -226,6 +227,41 @@ class V3DfgPeephole final : public DfgVisitor {
return true;
}
// Add vertex to the cache. If an equivalent (but different) vertex is
// already cached, it is returned and the cache is not updated.
DfgVertex* cacheVertex(DfgVertex* vtxp) {
DfgVertex* const equivp = m_cache.cache(vtxp);
UASSERT_OBJ(!m_vInfo[vtxp].m_isCachedVertex || !equivp, vtxp,
"Vertex marked 'm_isCachedVertex' has a cached equivalent");
m_vInfo[vtxp].m_isCachedVertex = !equivp;
return equivp;
}
// Remove vertex from the cache (no-op if it is not the cached vertex)
void invalidateVertex(DfgVertex* vtxp) {
m_cache.invalidate(vtxp);
m_vInfo[vtxp].m_isCachedVertex = false;
}
// Find the vertex of the given type with the given operands in the
// cache, or create a new one and add it to the cache.
template <typename Vertex, typename... Operands>
Vertex* getOrCreateVertex(FileLine* flp, const DfgDataType& dtype, Operands... operands) {
Vertex* const vtxp = m_cache.getOrCreate<Vertex, Operands...>(flp, dtype, operands...);
m_vInfo[vtxp].m_isCachedVertex = true;
return vtxp;
}
// Find the vertex of the given type with the given operands in the
// cache, or nullptr if there is no such vertex.
template <typename Vertex, typename... Operands>
Vertex* getVertex(const DfgDataType& dtype, Operands... operands) {
Vertex* const vtxp = m_cache.get<Vertex>(dtype, operands...);
UASSERT_OBJ(!vtxp || m_vInfo[vtxp].m_isCachedVertex, vtxp,
"Cached vertex not marked 'm_isCachedVertex'");
return vtxp;
}
void incrementGeneration() {
++m_currentGeneration;
// TODO: could sweep on overflow
@@ -270,7 +306,7 @@ class V3DfgPeephole final : public DfgVisitor {
UASSERT_OBJ(!varp || !varp->hasPrev(), vtxp, "Deleting variable consumed via DfgPrev");
// Invalidate cache entry
m_cache.invalidate(vtxp);
invalidateVertex(vtxp);
// It might be in the iter list, remove it
removeFromIterList(vtxp);
@@ -344,14 +380,14 @@ class V3DfgPeephole final : public DfgVisitor {
// Remove sinks of the original vertex from the cache - their inputs are changing
m_vtxp->foreachSink([&](DfgVertex& dst) {
m_cache.invalidate(&dst);
invalidateVertex(&dst);
return false;
});
// Replace vertex with the replacement
m_vtxp->replaceWith(resp);
// Re-cache all sinks of the replacement
resp->foreachSink([&](DfgVertex& dst) {
m_cache.cache(&dst);
cacheVertex(&dst);
return false;
});
@@ -390,7 +426,7 @@ class V3DfgPeephole final : public DfgVisitor {
template <typename Vertex, typename... Operands>
Vertex* make(FileLine* flp, const DfgDataType& dtype, Operands... operands) {
// Find or create an equivalent vertex
Vertex* const vtxp = m_cache.getOrCreate<Vertex, Operands...>(flp, dtype, operands...);
Vertex* const vtxp = getOrCreateVertex<Vertex, Operands...>(flp, dtype, operands...);
// Sanity check
UASSERT_OBJ(vtxp->dtype() == dtype, vtxp, "Vertex dtype mismatch");
if (VL_UNLIKELY(v3Global.opt.debugCheck())) vtxp->typeCheck(m_dfg);
@@ -594,7 +630,7 @@ class V3DfgPeephole final : public DfgVisitor {
// '(a OP (b OP c))' -> '(a OP b) OP c'
if (Vertex* const existingp
= m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) {
= getVertex<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) {
UASSERT_OBJ(existingp->hasSinks(), vtxp, "Existing vertex should be used");
if (existingp != rhsp) {
APPLYING(REUSE_ASSOC_BINARY_LHS_WITH_LHS_OF_RHS) {
@@ -607,7 +643,7 @@ class V3DfgPeephole final : public DfgVisitor {
// '(a OP (b OP c))' -> '(a OP c) OP b' iff also commutative
if VL_CONSTEXPR_CXX17 (IsCommutative<Vertex>::value) {
if (Vertex* const existingp
= m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rrVtxp), lhsp, rrVtxp)) {
= getVertex<Vertex>(resultDType<Vertex>(lhsp, rrVtxp), lhsp, rrVtxp)) {
UASSERT_OBJ(existingp->hasSinks(), vtxp, "Existing vertex should be used");
if (existingp != rhsp) {
APPLYING(REUSE_ASSOC_BINARY_LHS_WITH_RHS_OF_RHS) {
@@ -1159,24 +1195,24 @@ class V3DfgPeephole final : public DfgVisitor {
// Sel from a partial variable (including narrowed vertex)
if (DfgVarPacked* const varp = fromp->cast<DfgVarPacked>()) {
if (varp->srcp() && !varp->isVolatile() && !varp->srcp()->is<DfgCReset>()) {
// Must be a splice, otherwise it would have been inlined
DfgSplicePacked* splicep = varp->srcp()->as<DfgSplicePacked>();
DfgVertex* driverp = nullptr;
uint32_t driverLsb = 0;
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) {
const uint32_t dMsb = dLsb + src.width() - 1;
// If it does not cover the whole searched bit range, move on
if (lsb < dLsb || dMsb < msb) return false;
// Save the driver
driverp = &src;
driverLsb = dLsb;
return true;
});
if (driverp) {
// Find the driver of this range
const auto pair = varp->driverOfRange(lsb, width);
DfgVertex* const driverp = pair.first;
const uint32_t driverLsb = pair.second;
UASSERT_OBJ(
!driverp || driverp != varp->srcp(), varp,
"'varp' should be partially driven, otherwise should have been inlined");
if (driverp) {
if (driverp == varp->defaultp()) {
APPLYING(PUSH_SEL_THROUGH_DEFAULT) {
fromp = driverp;
lsb = driverLsb;
continue;
}
} else {
APPLYING(PUSH_SEL_THROUGH_SPLICE) {
fromp = driverp;
lsb -= driverLsb;
lsb = driverLsb;
continue;
}
}
@@ -3127,6 +3163,9 @@ class V3DfgPeephole final : public DfgVisitor {
// Assign vertex IDs
m_dfg.forEachVertex([&](DfgVertex& vtx) { m_vInfo[vtx].m_id = ++m_lastId; });
// Add all operation vertices to the cache
for (DfgVertex& vtx : m_dfg.opVertices()) cacheVertex(&vtx);
// Initialize the work list and iter list. They can't get bigger than
// m_dfg.size(), but new vertices are created in the loop, so over alloacte
m_workList.reserve(m_dfg.size() * 2);
@@ -3168,11 +3207,14 @@ class V3DfgPeephole final : public DfgVisitor {
// Unsued vertices should have been removed immediately
UASSERT_OBJ(m_vtxp->hasSinks(), m_vtxp, "Operation vertex should have sinks");
// Check if an equivalent vertex exists, if so replace this vertex with it
if (DfgVertex* const sampep = m_cache.cache(m_vtxp)) {
APPLYING(REPLACE_WITH_EQUIVALENT) {
replace(sampep);
continue;
// Check if an equivalent vertex exists, if so replace this vertex with it.
// Can skip the lookup if the vertex is known to be the cached one
if (!m_vInfo[m_vtxp].m_isCachedVertex) {
if (DfgVertex* const sampep = cacheVertex(m_vtxp)) {
APPLYING(REPLACE_WITH_EQUIVALENT) {
replace(sampep);
continue;
}
}
}
+1
View File
@@ -66,6 +66,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_DEFAULT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REP) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \
+32 -30
View File
@@ -268,7 +268,7 @@ class AstToDfgConverter final : public VNVisitor {
};
// Simplify the LHS, to get rid of things like SEL(CONCAT(_, _), _)
lhsp = VN_AS(V3Const::constifyExpensiveEdit(lhsp), NodeExpr);
if (!VN_IS(lhsp, VarRef)) lhsp = VN_AS(V3Const::constifyExpensiveEdit(lhsp), NodeExpr);
// Assigning compound expressions to a concatenated LHS requires a temporary
// to avoid multiple use of the expression
@@ -587,8 +587,11 @@ class AstToDfgSynthesize final {
// SymTab must be ordered in order to yield stable results
struct AstVarScopeComparator final {
bool operator()(const AstVarScope* lhs, const AstVarScope* rhs) const {
return lhs->name() < rhs->name();
static int s_vscpIdCounter; // Counter for lazily allocating the unique AstVarScope IDs
bool operator()(AstVarScope* lhs, AstVarScope* rhs) const {
if (!lhs->user4()) lhs->user4(++s_vscpIdCounter);
if (!rhs->user4()) rhs->user4(++s_vscpIdCounter);
return lhs->user4() < rhs->user4();
}
};
using SymTab = std::map<AstVarScope*, DfgVertexVar*, AstVarScopeComparator>;
@@ -1590,11 +1593,6 @@ class AstToDfgSynthesize final {
bool synthesizeAssignW(AstAssignW* nodep) {
++m_ctx.m_synt.inputAssign;
// Construct an equivalent AstAssign
AstNodeExpr* const lhsp = nodep->lhsp()->cloneTree(false);
AstNodeExpr* const rhsp = nodep->rhsp()->cloneTree(false);
AstAssign* const assignp = new AstAssign{nodep->fileline(), lhsp, rhsp};
// The input and output symbol tables
SymTab iSymTab;
SymTab oSymTab;
@@ -1604,10 +1602,8 @@ class AstToDfgSynthesize final {
// Synthesize as if it was in a single CfgBlock CFG
DfgVertex* condp = nullptr;
const bool success = synthesizeBasicBlock(oSymTab, condp, {assignp}, iSymTab);
const bool success = synthesizeBasicBlock(oSymTab, condp, {nodep}, iSymTab);
UASSERT_OBJ(!condp, nodep, "Conditional AstAssignW ???");
// Delete auxiliary AstAssign
VL_DO_DANGLING(assignp->deleteTree(), assignp);
if (!success) return false;
// Check exernal writes are observed correctly
@@ -1790,28 +1786,32 @@ class AstToDfgSynthesize final {
//-------------------------------------------------------------------
UINFO(5, "Step 1: Attempting to synthesize each of the selected DfgLogic");
for (DfgVertex& vtx : m_dfg.opVertices()) {
DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) continue;
{
// AstVarScope::user4() -> int: unique ID for 'AstVarScopeComparator'
const VNUser4InUse user4InUse;
for (DfgVertex& vtx : m_dfg.opVertices()) {
DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) continue;
// We should only have DfgLogic remaining that was selected for synthesis
UASSERT_OBJ(logicp->selectedForSynthesis(), logicp, "Unselected DfgLogic remains");
// We should only have DfgLogic remaining that was selected for synthesis
UASSERT_OBJ(logicp->selectedForSynthesis(), logicp, "Unselected DfgLogic remains");
// Debug aid
const auto debugCallback = [&]() -> void {
// This is the breaking logic
m_debugLogicp = logicp;
// Dump it
UINFOTREE(0, logicp->nodep(), "Problematic DfgLogic: " << logicp, " ");
V3EmitV::debugVerilogForTree(logicp->nodep(), std::cout);
debugDump("synth-lastok");
};
if (VL_UNLIKELY(s_dfgSynthDebugBisect.stop(debugCallback))) break;
// Debug aid
const auto debugCallback = [&]() -> void {
// This is the breaking logic
m_debugLogicp = logicp;
// Dump it
UINFOTREE(0, logicp->nodep(), "Problematic DfgLogic: " << logicp, " ");
V3EmitV::debugVerilogForTree(logicp->nodep(), std::cout);
debugDump("synth-lastok");
};
if (VL_UNLIKELY(s_dfgSynthDebugBisect.stop(debugCallback))) break;
// Synthesize it, if failed, enqueue for reversion
if (!synthesize(*logicp)) {
logicp->setNonSynthesizable();
m_toRevert.push_front(*logicp);
// Synthesize it, if failed, enqueue for reversion
if (!synthesize(*logicp)) {
logicp->setNonSynthesizable();
m_toRevert.push_front(*logicp);
}
}
}
debugDump("synth-converted");
@@ -1966,6 +1966,8 @@ public:
}
};
int AstToDfgSynthesize::AstVarScopeComparator::s_vscpIdCounter = 0;
// Decide which DfgLogic to attempt to synthesize
static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
// If we are told to synthesize everything, we will do so ...
+29 -5
View File
@@ -72,8 +72,8 @@ protected:
public:
~DfgVertexVar() {
// Decrement reference count
UASSERT_OBJ(m_vscp->user1() >= 0x40, m_vscp, "Reference count underflow");
m_vscp->user1(m_vscp->user1() - 0x40);
UASSERT_OBJ((m_vscp->user1() >> 6) >= 0, m_vscp, "Reference count underflow");
}
ASTGEN_MEMBERS_DfgVertexVar;
@@ -84,6 +84,11 @@ public:
DfgVertex* defaultp() const { return inputp(1); }
void defaultp(DfgVertex* vtxp) { inputp(1, vtxp); }
// Return the vertex and the offset into the vertex driving the given range [lo, lo + size - 1]
// of this variable, iff it is driven by a single vertex. Returns nullptr if undriven, or the
// range is driven by multiple vertices in parts.
std::pair<DfgVertex*, uint32_t> driverOfRange(uint32_t lo, uint32_t size);
std::string srcName(size_t idx) const override final { return idx ? "defaultp" : "srcp"; }
// The Ast variable this vertex representess
@@ -485,28 +490,47 @@ public:
return vtxp;
}
bool foreachDriver(std::function<bool(DfgVertex&, uint32_t, FileLine*)> f) {
template <typename T_Callable,
std::enable_if_t<vlstd::is_invocable_r<bool, T_Callable, DfgVertex&, uint32_t,
FileLine*>::value, //
int>
= 0>
bool foreachDriver(T_Callable&& f) {
const size_t n = nInputs();
for (size_t i = 0; i < n; ++i) {
if (f(*inputp(i), m_driverData[i].m_lo, m_driverData[i].m_flp)) return true;
}
return false;
}
bool foreachDriver(std::function<bool(const DfgVertex&, uint32_t, FileLine*)> f) const {
template <typename T_Callable,
std::enable_if_t<vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&, uint32_t,
FileLine*>::value,
int>
= 0>
bool foreachDriver(T_Callable&& f) const {
const size_t n = nInputs();
for (size_t i = 0; i < n; ++i) {
if (f(*inputp(i), m_driverData[i].m_lo, m_driverData[i].m_flp)) return true;
}
return false;
}
bool foreachDriver(std::function<bool(DfgVertex&, uint32_t)> f) {
template <
typename T_Callable,
std::enable_if_t<vlstd::is_invocable_r<bool, T_Callable, DfgVertex&, uint32_t>::value, //
int>
= 0>
bool foreachDriver(T_Callable&& f) {
const size_t n = nInputs();
for (size_t i = 0; i < n; ++i) {
if (f(*inputp(i), m_driverData[i].m_lo)) return true;
}
return false;
}
bool foreachDriver(std::function<bool(const DfgVertex&, uint32_t)> f) const {
template <typename T_Callable,
std::enable_if_t<
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&, uint32_t>::value, int>
= 0>
bool foreachDriver(T_Callable&& f) const {
const size_t n = nInputs();
for (size_t i = 0; i < n; ++i) {
if (f(*inputp(i), m_driverData[i].m_lo)) return true;
+5
View File
@@ -541,6 +541,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
depth + 1, suffix + ".atDefault()", nullptr);
} else if (VN_IS(dtypep, CDType)) {
return ""; // Constructor does it
} else if (VN_IS(dtypep, CoverCrossDType) || VN_IS(dtypep, CoverpointDType)) {
return ""; // Covergroup constructor creates the runtime and assigns the pointer
} else if (const AstClassRefDType* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
return adtypep->rawPointer() ? varNameProtected + suffix + " = nullptr;\n" : "";
} else if (VN_IS(dtypep, IfaceRefDType)) {
@@ -594,6 +596,9 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
return "";
} else if (basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator())) {
return "";
} else if (basicp && basicp->isCovergroupInstHandle()) {
// The handle's own constructor deals with it.
return "";
} else if (basicp && (basicp->isEvent())) {
return "VlAssignableEvent{};\n";
} else if (basicp) {
+15
View File
@@ -808,6 +808,21 @@ public:
if (nodep->method() == VCMethod::FORCE_READ_SEL) {
emitIQW(nodep);
if (nodep->isWide()) puts("<" + cvtToStr(nodep->dtypep()->widthWords()) + ">");
} else if (nodep->method() == VCMethod::ARRAY_SLICE) {
// VlUnpacked::slice<N_Out>(loIdx) - N_Out is the (fixed) result array size
const AstUnpackArrayDType* const adtypep
= VN_AS(nodep->dtypep()->skipRefp(), UnpackArrayDType);
puts("<" + cvtToStr(adtypep->elementsConst()) + ">");
} else if (nodep->method() == VCMethod::COVERGROUP_ADD_COVERPOINT) {
// The hit-list bound is a template argument of the returned VlCoverpointT<>, and the
// node's own dtype is that type, so it is the one source of truth for both.
const AstCoverpointDType* const cpdtypep
= VN_AS(nodep->dtypep()->skipRefp(), CoverpointDType);
puts("<" + cvtToStr(cpdtypep->hitBound()) + ">");
} else if (nodep->method() == VCMethod::COVERGROUP_ADD_CROSS) {
const AstCoverCrossDType* const cxdtypep
= VN_AS(nodep->dtypep()->skipRefp(), CoverCrossDType);
puts("<" + cxdtypep->cppTemplateArgs() + ">");
}
puts("(");
bool comma = false;
+4 -2
View File
@@ -78,7 +78,7 @@ class EmitCHeader final : public EmitCConstInit {
const auto emitCurrentList = [this, &first, &varList, &lastAnon]() {
if (varList.empty()) return;
decorateFirst(first, "\n// DESIGN SPECIFIC STATE\n");
decorateFirst(first, "\n// DESIGN-SPECIFIC STATE\n");
if (lastAnon) { // Output as anons
const int anonMembers = varList.size();
@@ -209,7 +209,9 @@ class EmitCHeader final : public EmitCConstInit {
if (!VN_IS(modp, Class)) {
decorateFirst(first, section);
puts("void " + protect("__Vconfigure") + "(bool first);\n");
if (v3Global.opt.coverage()) {
puts("void " + protect("__Vconfigure") + "(bool first);\n");
}
} else {
decorateFirst(first, section);
const std::string name = V3OutFormatter::quoteNameControls(
+3 -3
View File
@@ -163,12 +163,12 @@ class EmitCImp final : public EmitCFunc {
"(" + modName + "* vlSelf, bool first);");
}
puts("\nvoid " + modName + "::" + protect("__Vconfigure") + "(bool first) {\n");
puts("(void)first; // Prevent unused variable warning\n");
if (v3Global.opt.coverage()) {
puts("\nvoid " + modName + "::" + protect("__Vconfigure") + "(bool first) {\n");
puts("(void)first; // Prevent unused variable warning\n");
puts(modName + "__" + protect("_configure_coverage") + "(this, first);\n");
puts("}\n");
}
puts("}\n");
}
void emitCoverageImp() {
// Rather than putting out VL_COVER_INSERT calls directly, we do it via this
+105 -11
View File
@@ -61,6 +61,18 @@ class EmitCSyms final : EmitCBaseVisitorConst {
, m_timeunit{timeunit}
, m_type{type} {}
};
struct IfaceRefData final {
const AstScope* const m_scopep; // Concrete interface scope referred to
const std::string m_suffix; // Path relative to the model instance
const std::string m_name; // Name of the reference port
const std::string m_modportName; // "" = no modport
IfaceRefData(const AstScope* scopep, const std::string& suffix, const std::string& name,
const std::string& modportName)
: m_scopep{scopep}
, m_suffix{suffix}
, m_name{name}
, m_modportName{modportName} {}
};
struct ScopeFuncData final {
const AstScopeName* const m_scopep;
const AstCFunc* const m_cfuncp;
@@ -102,6 +114,7 @@ class EmitCSyms final : EmitCBaseVisitorConst {
ScopeNames m_scopeNames; // Each unique AstScopeName. Dpi scopes added later
ScopeNames m_dpiScopeNames; // Each unique AstScopeName for DPI export
ScopeNames m_vpiScopeCandidates; // All scopes for VPI
std::vector<IfaceRefData> m_ifaceRefs; // Each interface reference, for VPI
// The actual hierarchy of scopes
std::map<const std::string, std::vector<std::string>> m_vpiScopeHierarchy;
int m_coverBins = 0; // Global coverage bin number for non-object helper functions
@@ -117,6 +130,8 @@ class EmitCSyms final : EmitCBaseVisitorConst {
// Single VlScopeTableEntry[] table for all scopes, built in getSymCtorStmts()
std::string m_scopeTableName;
std::vector<std::string> m_scopeTableRows;
std::string m_ifaceRefTableName;
std::vector<std::string> m_ifaceRefTableRows;
// METHODS
void emitSymHdr();
@@ -689,6 +704,54 @@ class EmitCSyms final : EmitCBaseVisitorConst {
}
}
void collectIfaceRefs(const AstScope* nodep) {
const AstCell* const cellp = nodep->aboveCellp();
// Exclude classes inside interfaces; the cell's modp is the interface, not the Class
if (!cellp || !VN_IS(cellp->modp(), Iface) || !VN_IS(nodep->modp(), Iface)) return;
// vpiName() to match the scope table these are looked up alongside. Inlining
// flattens the hierarchy but leaves the inlined levels in the scope name, which
// therefore carries the full enclosing path.
const std::string path = AstNode::vpiName(nodep->name());
const std::string instName = AstNode::vpiName(cellp->origName());
UASSERT_OBJ(path.length() > instName.length() && VString::endsWith(path, instName), nodep,
"Interface scope name " << path << " does not end with instance name "
<< instName);
const std::string parentPath = path.substr(0, path.length() - instName.length());
for (AstIntfRef* intfRefp = cellp->intfRefsp(); intfRefp;
intfRefp = VN_AS(intfRefp->nextp(), IntfRef)) {
const std::string refName = AstNode::vpiName(intfRefp->name());
// Assume only references under the same parent scope reference the
// same interface. Same limitation as the trace path in V3TraceDecl.
if (!VString::startsWith(refName, parentPath)) continue;
m_ifaceRefs.emplace_back(nodep, refName, AstNode::vpiName(intfRefp->baseName()),
intfRefp->modportName());
}
}
void buildIfaceRefTable() {
if (m_ifaceRefs.empty()) return;
const std::string symClass = symClassName();
for (const IfaceRefData& ird : m_ifaceRefs) {
const std::string scopeSym = scopeSymString(ird.m_scopep->name());
// Only reference scopes that actually made it into the scope table
if (m_scopeNames.find(scopeSym) == m_scopeNames.end()) continue;
std::string row
= "{offsetof(" + symClass + ", " + protect("__Vscopep_" + scopeSym) + "), \"";
row += V3OutFormatter::quoteNameControls(VIdProtect::protectWordsIf(ird.m_name, true));
row += "\", \"";
row += V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(ird.m_suffix, true));
row += "\", \"";
row += V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(ird.m_modportName, true));
row += "\"}";
m_ifaceRefTableRows.emplace_back(std::move(row));
}
if (!m_ifaceRefTableRows.empty()) m_ifaceRefTableName = symClass + "__VpiIfaceRefTable";
}
void buildVpiHierarchy() {
for (const auto& itpair : m_scopeNames) {
const std::string symName = itpair.second.m_symName;
@@ -778,6 +841,7 @@ class EmitCSyms final : EmitCBaseVisitorConst {
if (v3Global.opt.vpi() && !nodep->isTop()) {
const std::string type = VN_IS(nodep->modp(), Package) ? "SCOPE_PACKAGE" //
: VN_IS(nodep->modp(), Iface) ? "SCOPE_INTERFACE" //
: "SCOPE_MODULE";
const int timeunit = m_modp->timeunit().powerOfTen();
m_vpiScopeCandidates.emplace( //
@@ -786,6 +850,7 @@ class EmitCSyms final : EmitCBaseVisitorConst {
std::forward_as_tuple(nodep, scopeSymString(nodep->name()),
AstNode::vpiName(nodep->shortName()),
nodep->modp()->origName(), timeunit, type));
collectIfaceRefs(nodep);
}
iterateChildrenConst(nodep);
}
@@ -1046,19 +1111,22 @@ void EmitCSyms::emitSymImpPreamble() {
// So split ctor sub-functions in other translation units can reference
// the VPI variable tables defined below.
if (!m_varTables.empty() || !m_scopeTableRows.empty()) {
if (!m_varTables.empty() || !m_scopeTableRows.empty() || !m_ifaceRefTableRows.empty()) {
for (const auto& kv : m_varTables) {
puts("extern const VlVarTableEntry " + kv.first + "[];\n");
}
if (!m_scopeTableRows.empty()) {
puts("extern const VlScopeTableEntry " + m_scopeTableName + "[];\n");
}
if (!m_ifaceRefTableRows.empty()) {
puts("extern const VlIfaceRefTableEntry " + m_ifaceRefTableName + "[];\n");
}
puts("\n");
}
}
void EmitCSyms::emitVarTables() {
if (m_varTables.empty() && m_scopeTableRows.empty()) return;
if (m_varTables.empty() && m_scopeTableRows.empty() && m_ifaceRefTableRows.empty()) return;
puts("\n// VPI VARIABLE/SCOPE TABLES\n");
// offsetof on the (non-standard-layout) generated module/Syms classes is well
// defined on all supported compilers but warns; suppress just here.
@@ -1084,6 +1152,15 @@ void EmitCSyms::emitVarTables() {
}
puts("};\n");
}
if (!m_ifaceRefTableRows.empty()) {
puts("extern const VlIfaceRefTableEntry " + m_ifaceRefTableName + "[] = {\n");
for (const std::string& row : m_ifaceRefTableRows) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(row);
ofp()->putsNoTracking(",\n");
}
puts("};\n");
}
puts("#if defined(__GNUC__)\n");
puts("# pragma GCC diagnostic pop\n");
puts("#endif\n");
@@ -1200,15 +1277,17 @@ std::vector<std::string> EmitCSyms::getSymCtorStmts() {
add(stmt);
}
add("// Setup each module's pointer back to symbol table (for public functions)");
for (const ScopeModPair& i : m_scopes) {
const AstScope* const scopep = i.first;
AstNodeModule* const modp = i.second;
// first is used by AstCoverDecl's call to __vlCoverInsert
const bool first = !modp->user1();
modp->user1(true);
add(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "."
+ protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");");
if (v3Global.opt.coverage()) {
add("// Setup each module's pointer back to symbol table (for public functions)");
for (const ScopeModPair& i : m_scopes) {
const AstScope* const scopep = i.first;
AstNodeModule* const modp = i.second;
// first is used by AstCoverDecl's call to __vlCoverInsert
const bool first = !modp->user1();
modp->user1(true);
add(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "."
+ protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");");
}
}
// Every scope has the same construction shape, so all fold into one table with no
@@ -1237,6 +1316,14 @@ std::vector<std::string> EmitCSyms::getSymCtorStmts() {
+ std::to_string(m_scopeNames.size()) + ", this);");
}
// After the scopes above, as each row points at an already-built VerilatedScope
buildIfaceRefTable();
if (!m_ifaceRefTableRows.empty()) {
add("// Setup interface references");
add("VerilatedScope::ifaceRefsInsertFromTable(" + m_ifaceRefTableName + ", "
+ std::to_string(m_ifaceRefTableRows.size()) + ", this);");
}
emitScopeHier(stmts, false);
if (v3Global.dpi()) {
@@ -1374,6 +1461,13 @@ std::vector<std::string> EmitCSyms::getSymDtorStmts() {
add("_vm_pgoProfiler.write(\"" + topClassName()
+ "\", _vm_contextp__->profVltFilename());");
}
// Before the scopes below, as each row names a scope being torn down
if (!m_ifaceRefTableRows.empty()) {
add("// Tear down interface references");
add("VerilatedScope::ifaceRefsEraseFromTable(" + m_ifaceRefTableName + ", "
+ std::to_string(m_ifaceRefTableRows.size()) + ", this);");
}
add("// Tear down scopes");
for (const auto& itpair : m_scopeNames) {
const ScopeData& sd = itpair.second;
+40 -2
View File
@@ -354,7 +354,38 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts(";\n");
}
void visit(AstCoverpointRef* nodep) override { putfs(nodep, nodep->name()); }
void visit(AstCoverBinsof* nodep) override {
putfs(nodep, nodep->isNegated() ? "!binsof(" : "binsof(");
iterateConst(nodep->pointp());
if (!nodep->name().empty()) puts("." + nodep->name());
puts(")");
if (nodep->rangesp()) {
puts(" intersect {");
iterateAndCommaConstNull(nodep->rangesp());
puts("}");
}
}
void visit(AstCoverCrossBin* nodep) override {
putfs(nodep, "bins " + nodep->name() + " = ");
iterateConstNull(nodep->selectp());
if (nodep->iffp()) {
puts(" iff (");
iterateConst(nodep->iffp());
puts(")");
}
puts(";\n");
}
void visit(AstCoverCrossSelect* nodep) override {
putfs(nodep, "(");
iterateConstNull(nodep->lhsp());
putbs(" " + nodep->verilogKwd() + " ");
iterateConstNull(nodep->rhsp());
puts(")");
}
void visit(AstCoverpointRef* nodep) override {
putfs(nodep, nodep->name());
iterateConstNull(nodep->exprp());
}
void visit(AstCoverCross* nodep) override {
putfs(nodep, nodep->name() + ": cross ");
for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) {
@@ -366,7 +397,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
iterateConst(nodep->iffp());
puts(")");
}
puts(";\n");
if (nodep->binsp()) {
puts(" {\n");
iterateAndNextConstNull(nodep->binsp());
puts("}\n");
} else {
puts(";\n");
}
}
void visit(AstCoverTransSet* nodep) override {
puts("(");
@@ -1207,6 +1244,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
}
void visit(AstConst* nodep) override { putfs(nodep, nodep->num().ascii(m_prefixed, true)); }
void visit(AstUnbounded* nodep) override { emitVerilogFormat(nodep, nodep->emitVerilog()); }
// Just iterate
void visit(AstTopScope* nodep) override { iterateChildrenConst(nodep); }
+1 -1
View File
@@ -266,7 +266,7 @@ void V3ErrorGuarded::v3errorEndGuts(const std::ostringstream& sstr, const string
}
if (debug()) {
execErrorExitCb();
V3Stats::statsFinalAll(v3Global.rootp());
V3Stats::statsStageAll(v3Global.rootp(), "Final");
V3Stats::statsReport();
}
vlAbortOrExit();
+3 -4
View File
@@ -783,11 +783,10 @@ void v3errorEndFatal(std::ostringstream& sstr)
} \
} while (false)
/// Check self test values for expected value. Safe from side-effects.
// Type argument can be removed when go to C++11 (use auto).
#define UASSERT_SELFTEST(Type, got, exp) \
#define UASSERT_SELFTEST(got, exp) \
do { \
Type g = (got); \
Type e = (exp); \
const auto g = (got); \
const decltype(g) e{exp}; \
UASSERT(g == e, "Self-test failed '" #got "==" #exp "'" \
" got=" \
<< g << " expected=" << e); \
+72 -72
View File
@@ -596,76 +596,76 @@ public:
10}; // Sandbag denom
const std::vector<ThreadSchedule> scheduled = packer.pack(graph);
UASSERT_SELFTEST(const size_t, scheduled.size(), 3);
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads.size(), threads);
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads[0].size(), 2);
UASSERT_SELFTEST(scheduled.size(), 3);
UASSERT_SELFTEST(scheduled[0].m_threads.size(), threads);
UASSERT_SELFTEST(scheduled[0].m_threads[0].size(), 2);
for (size_t i = 1; i < scheduled[0].m_threads.size(); ++i)
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads[i].size(), 0);
UASSERT_SELFTEST(scheduled[0].m_threads[i].size(), 0);
UASSERT_SELFTEST(const ExecMTask*, scheduled[0].m_threads[0][0], t0);
UASSERT_SELFTEST(const ExecMTask*, scheduled[0].m_threads[0][1], t1);
UASSERT_SELFTEST(scheduled[0].m_threads[0][0], t0);
UASSERT_SELFTEST(scheduled[0].m_threads[0][1], t1);
UASSERT_SELFTEST(const size_t, scheduled[1].m_threads.size(), hierThreads / 3);
UASSERT_SELFTEST(const ExecMTask*, scheduled[1].m_threads[0][0], t2);
UASSERT_SELFTEST(const ExecMTask*, scheduled[1].m_threads[0][1], t3);
UASSERT_SELFTEST(const ExecMTask*, scheduled[1].m_threads[1][0], t4);
UASSERT_SELFTEST(scheduled[1].m_threads.size(), hierThreads / 3);
UASSERT_SELFTEST(scheduled[1].m_threads[0][0], t2);
UASSERT_SELFTEST(scheduled[1].m_threads[0][1], t3);
UASSERT_SELFTEST(scheduled[1].m_threads[1][0], t4);
UASSERT_SELFTEST(const size_t, scheduled[2].m_threads.size(), threads);
UASSERT_SELFTEST(const ExecMTask*, scheduled[2].m_threads[0][0], t5);
UASSERT_SELFTEST(const ExecMTask*, scheduled[2].m_threads[1][0], t6);
UASSERT_SELFTEST(scheduled[2].m_threads.size(), threads);
UASSERT_SELFTEST(scheduled[2].m_threads[0][0], t5);
UASSERT_SELFTEST(scheduled[2].m_threads[1][0], t6);
UASSERT_SELFTEST(const size_t, ThreadSchedule::s_mtaskState.size(), 7);
UASSERT_SELFTEST(ThreadSchedule::s_mtaskState.size(), 7);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t0), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t1), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t2), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t3), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t4), 1);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t5), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t6), 1);
UASSERT_SELFTEST(ThreadSchedule::threadId(t0), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t1), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t2), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t3), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t4), 1);
UASSERT_SELFTEST(ThreadSchedule::threadId(t5), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t6), 1);
// On its native thread, we see the actual end time for t0:
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t0, 0), 1000);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t0, 0), 1000);
// On the other thread, we see a sandbagged end time which does not
// exceed the t1 end time:
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t0, 1), 1099);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t0, 1), 1099);
// Actual end time on native thread:
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t1, 0), 1100);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t1, 0), 1100);
// Sandbagged end time seen on thread 1. Note it does not compound
// with t0's sandbagged time; compounding caused trouble in
// practice.
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t1, 1), 1130);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t1, 1), 1130);
// Wide task scheduling
// Task does not depend on previous or future schedules
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t2, 0), 0);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[2], t2, 0), 0);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t2, 0), 0);
UASSERT_SELFTEST(packer.completionTime(scheduled[2], t2, 0), 0);
// We allow sandbagging for hierarchical children tasks, this does not affect
// wide task scheduling. When the next schedule is created it doesn't matter
// anyway.
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 0), 1200);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 1), 1230);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 2), 1230);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 3), 1230);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 4), 1230);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t2, 5), 1230);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 0), 1200);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 1), 1230);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 2), 1230);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 3), 1230);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 4), 1230);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t2, 5), 1230);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 0), 1300);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 1), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 2), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 3), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 4), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t3, 5), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 0), 1300);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 1), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 2), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 3), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 4), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t3, 5), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 0), 1360);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 1), 1330);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 2), 1360);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 3), 1360);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 4), 1360);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t4, 5), 1360);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 0), 1360);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 1), 1330);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 2), 1360);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 3), 1360);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 4), 1360);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t4, 5), 1360);
for (V3GraphVertex& vtx : graph.vertices()) vtx.as<ExecMTask>()->funcp()->deleteTree();
VL_DO_DANGLING(execGraphp->deleteTree(), execGraphp);
@@ -698,29 +698,29 @@ public:
10}; // Sandbag denom
const std::vector<ThreadSchedule> scheduled = packer.pack(graph);
UASSERT_SELFTEST(const size_t, scheduled.size(), 2);
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads.size(), hierThreads / 2);
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads[0].size(), 1);
UASSERT_SELFTEST(scheduled.size(), 2);
UASSERT_SELFTEST(scheduled[0].m_threads.size(), hierThreads / 2);
UASSERT_SELFTEST(scheduled[0].m_threads[0].size(), 1);
for (size_t i = 1; i < scheduled[0].m_threads.size(); ++i)
UASSERT_SELFTEST(const size_t, scheduled[0].m_threads[i].size(), 0);
UASSERT_SELFTEST(scheduled[0].m_threads[i].size(), 0);
UASSERT_SELFTEST(const ExecMTask*, scheduled[0].m_threads[0][0], t0);
UASSERT_SELFTEST(scheduled[0].m_threads[0][0], t0);
UASSERT_SELFTEST(const size_t, scheduled[1].m_threads.size(), threads);
UASSERT_SELFTEST(const size_t, scheduled[1].m_threads[0].size(), 1);
UASSERT_SELFTEST(scheduled[1].m_threads.size(), threads);
UASSERT_SELFTEST(scheduled[1].m_threads[0].size(), 1);
for (size_t i = 1; i < scheduled[1].m_threads.size(); ++i)
UASSERT_SELFTEST(const size_t, scheduled[1].m_threads[i].size(), 0);
UASSERT_SELFTEST(const ExecMTask*, scheduled[1].m_threads[0][0], t1);
UASSERT_SELFTEST(scheduled[1].m_threads[i].size(), 0);
UASSERT_SELFTEST(scheduled[1].m_threads[0][0], t1);
UASSERT_SELFTEST(const size_t, ThreadSchedule::s_mtaskState.size(), 2);
UASSERT_SELFTEST(ThreadSchedule::s_mtaskState.size(), 2);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t0), 0);
UASSERT_SELFTEST(const uint32_t, ThreadSchedule::threadId(t1), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t0), 0);
UASSERT_SELFTEST(ThreadSchedule::threadId(t1), 0);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[0], t0, 0), 1000);
UASSERT_SELFTEST(packer.completionTime(scheduled[0], t0, 0), 1000);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t1, 0), 1100);
UASSERT_SELFTEST(const uint32_t, packer.completionTime(scheduled[1], t1, 1), 1130);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t1, 0), 1100);
UASSERT_SELFTEST(packer.completionTime(scheduled[1], t1, 1), 1130);
for (V3GraphVertex& vtx : graph.vertices()) vtx.as<ExecMTask>()->funcp()->deleteTree();
VL_DO_DANGLING(execGraphp->deleteTree(), execGraphp);
@@ -1200,12 +1200,12 @@ void selfTest() {
{2, {20, 0}}, // Note no profile
{3, {30, 3000}}});
normalizeCosts(costs);
UASSERT_SELFTEST(const uint64_t, costs[1].first, 1000);
UASSERT_SELFTEST(const uint64_t, costs[1].second, 1000);
UASSERT_SELFTEST(const uint64_t, costs[2].first, 2000);
UASSERT_SELFTEST(const uint64_t, costs[2].second, 0);
UASSERT_SELFTEST(const uint64_t, costs[3].first, 3000);
UASSERT_SELFTEST(const uint64_t, costs[3].second, 3000);
UASSERT_SELFTEST(costs[1].first, 1000);
UASSERT_SELFTEST(costs[1].second, 1000);
UASSERT_SELFTEST(costs[2].first, 2000);
UASSERT_SELFTEST(costs[2].second, 0);
UASSERT_SELFTEST(costs[3].first, 3000);
UASSERT_SELFTEST(costs[3].second, 3000);
}
{ // Test that very large profile data properly scales
Costs costs({// id est prof
@@ -1213,12 +1213,12 @@ void selfTest() {
{2, {20, 200000000000}},
{3, {30, 1}}}); // Make sure doesn't underflow
normalizeCosts(costs);
UASSERT_SELFTEST(const uint64_t, costs[1].first, 2500000);
UASSERT_SELFTEST(const uint64_t, costs[1].second, 5000000);
UASSERT_SELFTEST(const uint64_t, costs[2].first, 5000000);
UASSERT_SELFTEST(const uint64_t, costs[2].second, 10000000);
UASSERT_SELFTEST(const uint64_t, costs[3].first, 7500000);
UASSERT_SELFTEST(const uint64_t, costs[3].second, 1);
UASSERT_SELFTEST(costs[1].first, 2500000);
UASSERT_SELFTEST(costs[1].second, 5000000);
UASSERT_SELFTEST(costs[2].first, 5000000);
UASSERT_SELFTEST(costs[2].second, 10000000);
UASSERT_SELFTEST(costs[3].first, 7500000);
UASSERT_SELFTEST(costs[3].second, 1);
}
PackThreads::selfTest();
+12 -8
View File
@@ -268,7 +268,7 @@ public:
}
static AstVarRef* getOneVarRef(AstNodeExpr* forceStmtp) {
AstNode* const basep = AstArraySel::baseFromp(forceStmtp, true);
AstNode* const basep = forceStmtp->baseFromp(true);
if (AstSampled* sampledp = VN_CAST(basep, Sampled))
if (AstNodeExpr* exprp = VN_CAST(sampledp->exprp(), NodeExpr))
return getOneVarRef(exprp);
@@ -822,8 +822,9 @@ public:
AstNodeExpr* createForceReadExpression(const VarForceInfo& varInfo,
AstVarRef* originalRefp) const {
FileLine* const flp = originalRefp->fileline();
return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ,
originalRefp->cloneTreePure(false), originalRefp->varp(),
AstVarRef* const refp = originalRefp->cloneTreePure(false);
refp->access(VAccess::READ);
return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ, refp, refp->varp(),
nullptr);
}
@@ -831,8 +832,11 @@ public:
AstNodeExpr* originalExprp,
AstNodeExpr* indexExprp) const {
FileLine* const flp = originalExprp->fileline();
return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ_INDEX,
originalExprp->cloneTreePure(false), originalExprp, indexExprp);
AstNodeExpr* const exprp = originalExprp->cloneTreePure(false);
// Must be an LValue to a static variable
VN_AS(exprp->cLValueTargetp(), VarRef)->access(VAccess::READ);
return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ_INDEX, exprp, originalExprp,
indexExprp);
}
static AstNodeExpr* rebuildSelPath(AstNodeExpr* pathp, AstNodeExpr* baseExprp) {
@@ -1315,7 +1319,7 @@ class ForceReplaceVisitor final : public VNVisitor {
m_stmtp = nodep;
iterate(nodep->lhsp());
iterate(nodep->rhsp());
if (AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(nodep->lhsp(), true), VarRef)) {
if (AstVarRef* const lhsp = VN_CAST(nodep->lhsp()->baseFromp(true), VarRef)) {
if (AstNode* const updatep
= m_state.createRhsUpdatesForWrite(nodep->fileline(), lhsp->varp())) {
nodep->addNextHere(updatep);
@@ -1394,7 +1398,7 @@ class ForceReplaceVisitor final : public VNVisitor {
}
}
AstNode* const basep = AstArraySel::baseFromp(nodep, true);
AstNode* const basep = nodep->baseFromp(true);
AstVarRef* const baseRefp = VN_CAST(basep, VarRef);
if (!baseRefp) {
iterateChildren(nodep);
@@ -1492,7 +1496,7 @@ class ForceReplaceVisitor final : public VNVisitor {
// Handle the whole opaque path at its outermost node so we can assign one stable
// synthetic force-path index to the full selection/member chain.
AstNodeExpr* const exprp = VN_AS(nodep, NodeExpr);
AstNode* const basep = AstArraySel::baseFromp(exprp, true);
AstNode* const basep = exprp->baseFromp(true);
AstVarRef* const baseRefp = VN_CAST(basep, VarRef);
if (baseRefp) {
AstVar* const varp = baseRefp->varp();
+2 -2
View File
@@ -267,7 +267,7 @@ private:
class DynScopeVisitor final : public VNVisitor {
// NODE STATE
// AstVar::user1() -> int. timing-control fork nesting level of that variable
// AstVar::user1() -> uint64_t. timing-control fork nesting level of that variable
// AstVarRef::user2() -> bool. Node is a class handle reference. The handle gets
// modified in the context of this reference.
// AstAssignDly::user2() -> bool. Already visited
@@ -281,7 +281,7 @@ class DynScopeVisitor final : public VNVisitor {
std::deque<AstNode*> m_frameOrder; // Ordered list of frames (for determinism)
std::map<AstNode*, ForkDynScopeFrame*> m_frames; // Map nodes to related DynScopeFrames
VMemberMap m_memberMap; // Class member look-up
int m_forkDepth = 0; // Number of asynchronous forks we are currently under
uint64_t m_forkDepth = 0; // Number of asynchronous forks we are currently under
bool m_afterTimingControl = false; // A timing control might've be executed in the current
// process
size_t m_id = 0; // Unique ID for a frame
+4 -4
View File
@@ -994,7 +994,7 @@ class FsmDetectVisitor final : public VNVisitor {
AstVarScope*& fromVscp) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef);
AstVarRef* const lhsp = VN_CAST(assp->lhsp()->baseFromp(true), VarRef);
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
if (!rhsp || !lhsp) return nullptr;
stateVscp = lhsp->varScopep();
@@ -1008,7 +1008,7 @@ class FsmDetectVisitor final : public VNVisitor {
FsmStateValue& resetValue) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef);
AstVarRef* const lhsp = VN_CAST(assp->lhsp()->baseFromp(true), VarRef);
AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond);
if (!rhsp || !lhsp) return nullptr;
if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) {
@@ -1033,7 +1033,7 @@ class FsmDetectVisitor final : public VNVisitor {
FsmStateValue& value) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef);
AstVarRef* const lhsp = VN_CAST(assp->lhsp()->baseFromp(true), VarRef);
UASSERT_OBJ(lhsp, assp,
"direct constant state assignment lhs should be normalized to a VarRef");
if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr;
@@ -1220,7 +1220,7 @@ class FsmDetectVisitor final : public VNVisitor {
AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef);
AstNodeExpr* valuep = eqp->rhsp();
if (!vrefp) {
vrefp = VN_CAST(AstArraySel::baseFromp(eqp->rhsp(), true), VarRef);
vrefp = VN_CAST(eqp->rhsp()->baseFromp(true), VarRef);
if (!vrefp) { return false; }
valuep = eqp->lhsp();
}
-5
View File
@@ -198,11 +198,6 @@ void V3Graph::userClearEdges() {
}
}
void V3Graph::clearColors() {
// Reset colors
for (V3GraphVertex& vertex : vertices()) vertex.color(0);
}
//======================================================================
// Dumping
+2 -4
View File
@@ -364,12 +364,10 @@ public:
// METHODS - ALGORITHMS
/// Clears color
void clearColors() VL_MT_DISABLED;
/// Assign same color to all vertices in the same weakly connected component
/// Thus different color if there's no edges between the two subgraphs
void weaklyConnected(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Colors are assigned densely, as 0 .. n-1, and 'n' is returned
uint32_t weaklyConnected(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Assign same color to all vertices that are strongly connected
/// Thus different color if there's no directional circuit within the subgraphs.
+15 -7
View File
@@ -27,6 +27,7 @@
#include "V3Stats.h"
#include <algorithm>
#include <limits>
#include <list>
#include <map>
#include <numeric>
@@ -135,21 +136,24 @@ void V3Graph::removeTransitiveEdges() { GraphAlgRemoveTransitiveEdges{this}.go()
// Changes color()
class GraphAlgWeakly final : GraphAlg<> {
// Sentinel color, meaning not colored yet. Colors themselves are 0 .. m_numColors-1.
static constexpr uint32_t UNCOLORED = std::numeric_limits<uint32_t>::max();
uint32_t m_numColors = 0; // Number of colors assigned
void main() {
// Initialize state
m_graphp->clearColors();
// Color graph
uint32_t currentColor = 0;
for (V3GraphVertex& vertex : m_graphp->vertices()) vertex.color(UNCOLORED);
// Color graph, without gaps
for (V3GraphVertex& vertex : m_graphp->vertices()) {
currentColor++;
vertexIterate(&vertex, currentColor);
if (vertex.color() == UNCOLORED) vertexIterate(&vertex, m_numColors++);
}
}
void vertexIterate(V3GraphVertex* vertexp, uint32_t currentColor) {
// Assign new color to each unvisited node
// then visit each of its edges, giving them the same color
if (vertexp->color()) return; // Already colored it
if (vertexp->color() != UNCOLORED) return; // Already colored it
vertexp->color(currentColor);
for (V3GraphEdge& edge : vertexp->outEdges()) {
if (followEdge(&edge)) vertexIterate(edge.top(), currentColor);
@@ -165,9 +169,13 @@ public:
main();
}
~GraphAlgWeakly() = default;
uint32_t numColors() const { return m_numColors; }
};
void V3Graph::weaklyConnected(V3EdgeFuncP edgeFuncp) { GraphAlgWeakly{this, edgeFuncp}; }
uint32_t V3Graph::weaklyConnected(V3EdgeFuncP edgeFuncp) {
return GraphAlgWeakly{this, edgeFuncp}.numColors();
}
//######################################################################
//######################################################################
+1144
View File
File diff suppressed because it is too large Load Diff
+472
View File
@@ -0,0 +1,472 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Open addressing hash set and hash map
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// An open addressing, linear probing hash table, with backward shift deletion.
// Usable as V3HashSet or V3HashMap. The benefit of these over
// std::unordered_set and std::unordered_map is far better memory locality
// during lookup, and fewer dynamic memory allocations (which also means less
// heap fragmentation). Consider using these if profiling shows that the
// unordered STL collections contribute a significant cost to an algorithm.
//
// Four types tell the table what it holds: the entry, a hash, an equality, and
// a key extractor yielding the lookup key of an entry. The extractor is what
// lets one table serve both roles: a set's entry is its own key, a map's is a
// pair keyed by its first. The hash and equality hence only ever see keys,
// never entries. V3HashSet and V3HashMap at the bottom of this file derive
// from the table, pairing it with the extractor that suits each.
//
// Those two work on keys via Hash and Equal functors as in std::unordered_set
// or std::unordered_map, but lookup is always heterogeneous, with no
// is_transparent to opt in like in the STL, and a lookup key can be spelled as
// several arguments, being the parts a key is made of. An entry can hence be
// looked up without one at hand, as when it is only created on a miss. The
// functors must provide call operators as const members, for the key of an
// entry and for every lookup key spelling used:
//
// size_t Hash::operator()(const T_Key&) const
// size_t Hash::operator()(<lookup keys>...) const
// bool Equal::operator()(const T_Key&, const T_Key&) const
// bool Equal::operator()(const T_Key&, <lookup keys>...) const
//
// with equal keys hashing equal, as usual, and consistently across the
// spellings.
//
// As only entries are stored, a slot is just a hash and an entry, so probing
// touches few cache lines. The table is doubled when an insertion would take
// it over the maximum load factor, or sized up front with 'reserve', to keep
// the probe runs short.
//
// Entries are referred to by iterators, as in the STL containers, but unlike
// STL containers, the mapped value in a V3HashMap is not mutable through an
// iterator. Iterators and entry addresses stay valid until the table grows or
// an entry is erased; either invalidates all of them.
//
// Erasure uses backward shift deletion: entries following the hole are moved
// back over it where their probe run ran through it (no tombstones).
//
//*************************************************************************
#ifndef VERILATOR_V3HASHTABLE_H_
#define VERILATOR_V3HASHTABLE_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include "V3StdFuture.h"
#include <functional>
#include <memory>
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
namespace V3HashTableInternals {
constexpr size_t MIN_CAPACITY = 16; // Smallest table allocated
constexpr size_t LOAD_FACTOR_NUM = 3; // Numerator of the maximum load factor
constexpr size_t LOAD_FACTOR_DEN = 4; // Denominator of the maximum load factor
// Key extractor for a table whose entries are their own keys, that is, a set
template <typename T_Key>
struct V3HashTableKeyIsEntry final {
using Key = T_Key; // What it yields, so the table need not deduce it
const T_Key& operator()(const T_Key& entry) const { return entry; }
};
// Key extractor for a table whose entries are pairs keyed by the first, that is, a map
template <typename T_Key, typename T_Val>
struct V3HashTableKeyIsFirst final {
using Key = T_Key; // What it yields, so the table need not deduce it
const T_Key& operator()(const std::pair<T_Key, T_Val>& entry) const { return entry.first; }
};
void selfTest();
} // namespace V3HashTableInternals
// V3HashTable, see the file header
// T_Entry The entries (STL calls this value_type)
// T_Hash Hashes a lookup key
// T_Equal Compares a key to a lookup key
// T_KeyOf Yields the key of an entry
template <typename T_Entry, typename T_Hash, typename T_Equal, typename T_KeyOf>
class V3HashTable VL_NOT_FINAL {
public:
// TYPES
using Entry = T_Entry; // What is stored
using Key = typename T_KeyOf::Key; // What entries are looked up by
private:
// TYPES
// Holds if the hash accepts a lookup key spelled as the given arguments
template <typename... T_Args>
using ValidHash = vlstd::is_invocable_r<size_t, const T_Hash&, const T_Args&...>;
// Holds if the equality accepts a key and such a lookup key
template <typename... T_Args>
using ValidEqual = vlstd::is_invocable_r<bool, const T_Equal&, const Key&, const T_Args&...>;
// The Key must itself be a valid lookup key, as every lookup ends in comparing one
// against a stored entry. Asserted separately, so the failure names the functor.
static_assert(ValidHash<Key>::value, "The 'Hash' functor must accept the 'Key'");
static_assert(ValidEqual<Key>::value, "The 'Equal' functor must accept two 'Key's");
// A table slot
struct Slot final {
// The entry comes first, so it starts the slot whatever its alignment.
// It is a union so it is alive only while the slot is occupied.
union {
Entry m_entry;
};
size_t m_hash = 0; // Hash of the entry, or zero when the slot is free
Slot() {} // Leaves 'm_entry' uninitialized, as the slot is free
~Slot() {
if (!isFree()) destroy();
}
Slot(const Slot&) = delete;
Slot(Slot&&) = delete;
const Slot& operator=(const Slot&) = delete;
Slot& operator=(Slot&& that) {
UDEBUGONLY(UASSERT(this != &that, "Moving a slot onto itself"););
UDEBUGONLY(UASSERT(!that.isFree(), "Moving from a free slot"););
UDEBUGONLY(UASSERT(isFree(), "Moving into an occupied slot"););
new (&m_entry) Entry{std::move(that.m_entry)};
m_hash = that.m_hash;
that.destroy();
return *this;
}
bool isFree() const { return !m_hash; }
// Construct the entry of this free slot from the given entry
void construct(size_t hash, Entry&& entry) {
UDEBUGONLY(UASSERT(isFree(), "Constructing the entry of an occupied slot"););
new (&m_entry) Entry{std::move(entry)};
m_hash = hash;
}
// Destroy the entry of this occupied slot, leaving it free
void destroy() {
UDEBUGONLY(UASSERT(!isFree(), "Destroying the entry of a free slot"););
m_entry.~Entry();
m_hash = 0;
}
};
public:
// Iterator over the entries, see the file header on invalidation
class iterator final {
friend class V3HashTable;
Slot* m_slotp = nullptr; // The slot iterated, or the end of the table
Slot* m_endp = nullptr; // One past the last slot
iterator(Slot* slotp, Slot* endp)
: m_slotp{slotp}
, m_endp{endp} {}
public:
iterator() = default;
// As opposed to the STL, this always returns a const reference so the
// collection is not mutable through an iterator alone. This is
// required because entries must be movable, hence can't be const, but
// the key of a map must not be modified.
const Entry& operator*() const { return m_slotp->m_entry; }
const Entry* operator->() const { return &m_slotp->m_entry; }
// Pre-increment, skipping the free slots
iterator& operator++() {
while (++m_slotp != m_endp && m_slotp->isFree()) {}
return *this;
}
bool operator==(const iterator& that) const { return m_slotp == that.m_slotp; }
bool operator!=(const iterator& that) const { return m_slotp != that.m_slotp; }
};
private:
// STATE
std::unique_ptr<Slot[]> m_table; // The table, null when unallocated
size_t m_capacity = 0; // Number of slots in the table, a power of two, or zero
size_t m_size = 0; // Number of occupied slots
VL_NO_UNIQUE_ADDRESS_CXX20 T_Hash m_hash; // Hashes a lookup key
VL_NO_UNIQUE_ADDRESS_CXX20 T_Equal m_equal; // Compares a key to a lookup key
VL_NO_UNIQUE_ADDRESS_CXX20 T_KeyOf m_keyOf; // Yields the lookup key of an entry
// METHODS
// The hash of the given entry or lookup key, as stored in a slot
template <typename... T_Args>
size_t hashOf(const T_Args&... args) const {
// A free slot is one with a zero hash, so force the high bit into every hash.
constexpr size_t USED_BIT = size_t{1} << (sizeof(size_t) * 8 - 1);
return static_cast<size_t>(m_hash(args...)) | USED_BIT;
}
// Index of the free slot the given hash probes to. There must always be one.
size_t freeSlot(size_t hash) const {
const size_t mask = m_capacity - 1;
size_t i = hash & mask;
while (!m_table[i].isFree()) i = (i + 1) & mask;
return i;
}
// Resize to the given number of slots, which must fit all entries
void resize(size_t count) {
UDEBUGONLY(UASSERT(count && !(count & (count - 1)), "Capacity not a power of 2"););
const std::unique_ptr<Slot[]> oldTable{std::move(m_table)};
const size_t oldCapacity = m_capacity;
m_table = std::make_unique<Slot[]>(count);
m_capacity = count;
// Reinsert the entries. 'freeSlot' appends to the probe run of each, so the runs
// come out contiguous whatever order this visits the old slots in.
for (size_t i = 0; i < oldCapacity; ++i) {
Slot& slot = oldTable[i];
if (!slot.isFree()) m_table[freeSlot(slot.m_hash)] = std::move(slot);
}
}
// Index of the slot holding the entry equal to the given key, or of the free slot its
// probe sequence ends at. The table must not be empty.
template <typename... T_Args>
size_t probe(size_t hash, const T_Args&... args) const {
UDEBUGONLY(UASSERT(m_table, "Table must be allocated"););
const size_t mask = m_capacity - 1;
size_t i = hash & mask;
while (!m_table[i].isFree()) {
const Slot& slot = m_table[i];
if (slot.m_hash == hash && m_equal(m_keyOf(slot.m_entry), args...)) break;
i = (i + 1) & mask;
}
return i;
}
// Implementation of 'insertLazy' below. 'all' holds the key arguments, followed by
// the callable that creates the entry, so 'N_Key' indexes the key.
template <size_t... N_Key, typename T_All>
std::pair<iterator, bool> insertLazyImpl(std::index_sequence<N_Key...>, T_All&& all) {
static_assert(ValidHash<std::tuple_element_t<N_Key, T_All>...>::value,
"The 'Hash' functor does not accept a lookup key spelled like this");
static_assert(ValidEqual<std::tuple_element_t<N_Key, T_All>...>::value,
"The 'Equal' functor does not accept a lookup key spelled like this");
const size_t hash = hashOf(std::get<N_Key>(all)...);
// Allocate on the first insertion
if (VL_UNLIKELY(!m_capacity)) resize(V3HashTableInternals::MIN_CAPACITY);
// Find the slot for the entry
Slot* slotp = m_table.get() + probe(hash, std::get<N_Key>(all)...);
// If occupied, it's the equivalent, and we are done
if (!slotp->isFree()) return {iterator{slotp, m_table.get() + m_capacity}, false};
// Table is growing
++m_size;
// Increase if necessary by load factor
if (VL_UNLIKELY(m_size * V3HashTableInternals::LOAD_FACTOR_DEN
> m_capacity * V3HashTableInternals::LOAD_FACTOR_NUM)) {
resize(m_capacity * 2);
slotp = m_table.get() + freeSlot(hash);
}
// Construct the entry via the user provided callable (last item in 'all')
slotp->construct(hash, std::get<sizeof...(N_Key)>(all)());
// The key of the created entry must both hash and compare as the key looked up
#ifdef VL_DEBUG
const Key& key = m_keyOf(slotp->m_entry);
UASSERT(hashOf(key) == hash,
"Created entry does not hash as the key it was looked up with");
UASSERT(m_equal(key, std::get<N_Key>(all)...),
"Created entry does not match the key it was looked up with");
#endif
// Return newly create entry
return {iterator{slotp, m_table.get() + m_capacity}, true};
}
protected:
// CONSTRUCTORS
V3HashTable() = default;
V3HashTable(T_Hash hash, T_Equal equal)
: m_hash{std::move(hash)}
, m_equal{std::move(equal)} {}
~V3HashTable() = default;
VL_UNCOPYABLE(V3HashTable);
// Movable, as the table is just a pointer. The source is left empty rather than
// merely unspecified, so it remains a usable, empty table.
V3HashTable(V3HashTable&& that)
: m_table{std::move(that.m_table)}
, m_capacity{that.m_capacity}
, m_size{that.m_size}
, m_hash{std::move(that.m_hash)}
, m_equal{std::move(that.m_equal)}
, m_keyOf{std::move(that.m_keyOf)} {
that.m_capacity = 0;
that.m_size = 0;
}
V3HashTable& operator=(V3HashTable&& that) {
m_table = std::move(that.m_table); // Frees the table this held, if any
m_capacity = that.m_capacity;
m_size = that.m_size;
m_hash = std::move(that.m_hash);
m_equal = std::move(that.m_equal);
m_keyOf = std::move(that.m_keyOf);
that.m_capacity = 0;
that.m_size = 0;
return *this;
}
public:
// METHODS
size_t size() const { return m_size; }
bool empty() const { return !m_size; }
iterator begin() const {
Slot* const endp = m_table.get() + m_capacity;
Slot* slotp = m_table.get();
while (slotp != endp && slotp->isFree()) ++slotp;
return iterator{slotp, endp};
}
iterator end() const {
Slot* const endp = m_table.get() + m_capacity;
return iterator{endp, endp};
}
// Make room for the given number of entries, so inserting that many will not resize
void reserve(size_t count) {
size_t capacity = V3HashTableInternals::MIN_CAPACITY;
while (capacity * V3HashTableInternals::LOAD_FACTOR_NUM
< count * V3HashTableInternals::LOAD_FACTOR_DEN)
capacity *= 2;
if (capacity > m_capacity) resize(capacity);
}
// Return iterator to the entry equal to the given key, or 'end()' if there
// is none. The key is whatever T_Hash and T_Equal accept, spelled as any
// number of arguments. Same as STL containers.
template <typename... T_Args>
iterator find(const T_Args&... args) const {
static_assert(ValidHash<T_Args...>::value,
"The 'Hash' functor does not accept a lookup key spelled like this");
static_assert(ValidEqual<T_Args...>::value,
"The 'Equal' functor does not accept a lookup key spelled like this");
if (!m_size) return end(); // Nothing to find, and this also covers there being no table
Slot* const slotp = m_table.get() + probe(hashOf(args...), args...);
return slotp->isFree() ? end() : iterator{slotp, m_table.get() + m_capacity};
}
// Add the given entry, unless an equal one is in the table already. Return
// iterator to the entry and true if insertion happened. Same as STL containers.
std::pair<iterator, bool> insert(const Entry& entry) {
static_assert(std::is_copy_constructible<Entry>::value,
"'Entry' must be copy constructible to use 'insert'");
return insertLazy(m_keyOf(entry), [&entry]() -> Entry { return entry; });
}
// As 'insert', but the entry is only made when needed: all but the last argument spell
// the key, and the last is a callable to create the entry on a miss. Note the created entry
// must hash and compare equal to the key, and the call must not touch the container, as this
// holds the slot the entry will go in.
template <typename... T_Args>
std::pair<iterator, bool> insertLazy(T_Args&&... args) {
static_assert(sizeof...(T_Args) >= 2,
"'insertLazy' needs a lookup key, then a callable to create the entry");
using Callable = std::tuple_element_t<sizeof...(T_Args) - 1, std::tuple<T_Args...>>;
static_assert(vlstd::is_invocable_r<Entry, Callable>::value,
"The last argument of 'insertLazy' must be a callable that takes no "
"arguments and returns an 'Entry'");
return insertLazyImpl(std::make_index_sequence<sizeof...(T_Args) - 1>{},
std::forward_as_tuple(std::forward<T_Args>(args)...));
}
// Whether an entry equal to the given key is in the table. The key is spelled as for 'find'.
template <typename... T_Args>
bool contains(const T_Args&... args) const {
return find(args...) != end();
}
// Remove the entry equal to the given key, and return whether there was one.
template <typename... T_Args>
bool erase(const T_Args&... args) {
const iterator it = find(args...);
if (it == end()) return false;
erase(it);
return true;
}
// Remove the entry the given iterator refers to, which must not be 'end()'. Note that
// unlike STL erase this returns nothing, as every iterator is invalidated on deletion.
void erase(iterator it) {
UDEBUGONLY(UASSERT(it != end() && !it.m_slotp->isFree(), "Erasing a bad iterator"););
const size_t mask = m_capacity - 1;
size_t i = static_cast<size_t>(it.m_slotp - m_table.get());
// Destroy the entry
m_table[i].destroy();
// The entry is gone, so slot 'i' is now a hole
--m_size;
// Backward shift deletion: move back the entries whose probing the hole breaks
size_t j = i;
while (true) {
j = (j + 1) & mask;
Slot& slot = m_table[j];
if (slot.isFree()) break;
// Move back if its home position does not lie in the cyclic range (i, j]
if (((j - (slot.m_hash & mask)) & mask) >= ((j - i) & mask)) {
m_table[i] = std::move(slot); // Frees 'slot', which is then the hole
i = j;
}
}
}
};
template <typename T_Key, typename T_Hash = std::hash<T_Key>,
typename T_Equal = std::equal_to<T_Key>>
class V3HashSet final : public V3HashTable<T_Key, T_Hash, T_Equal,
V3HashTableInternals::V3HashTableKeyIsEntry<T_Key>> {
using Super
= V3HashTable<T_Key, T_Hash, T_Equal, V3HashTableInternals::V3HashTableKeyIsEntry<T_Key>>;
// Entries are only ever moved. Note 'insert' additionally needs copy construction.
static_assert(std::is_move_constructible<T_Key>::value, "'T_Key' must be move constructible");
static_assert(std::is_destructible<T_Key>::value, "'T_Key' must be destructible");
public:
// CONSTRUCTORS
V3HashSet() = default;
V3HashSet(T_Hash hash, T_Equal equal)
: Super{std::move(hash), std::move(equal)} {}
};
template <typename T_Key, typename T_Val, typename T_Hash = std::hash<T_Key>,
typename T_Equal = std::equal_to<T_Key>>
class V3HashMap final
: public V3HashTable<std::pair<T_Key, T_Val>, T_Hash, T_Equal,
V3HashTableInternals::V3HashTableKeyIsFirst<T_Key, T_Val>> {
using Super = V3HashTable<std::pair<T_Key, T_Val>, T_Hash, T_Equal,
V3HashTableInternals::V3HashTableKeyIsFirst<T_Key, T_Val>>;
// Entries are only ever moved. Note 'insert' additionally needs copy construction.
// Asserted separately, so the failure names the one at fault.
static_assert(std::is_move_constructible<T_Key>::value, "'T_Key' must be move constructible");
static_assert(std::is_destructible<T_Key>::value, "'T_Key' must be destructible");
static_assert(std::is_move_constructible<T_Val>::value, "'T_Val' must be move constructible");
static_assert(std::is_destructible<T_Val>::value, "'T_Val' must be destructible");
public:
// TYPES
using Value = T_Val; // What a key maps to
// CONSTRUCTORS
V3HashMap() = default;
V3HashMap(T_Hash hash, T_Equal equal)
: Super{std::move(hash), std::move(equal)} {}
};
#endif // Guard
+3 -3
View File
@@ -55,7 +55,7 @@ class HasherVisitor final : public VNVisitorConst {
std::function<void()>&& f) {
// See comments in visit(AstCFunc) about this breaking recursion
if (m_cacheInUser4 && nodep->user4()) {
return V3Hash{nodep->user4()};
return V3Hash{static_cast<uint32_t>(nodep->user4())};
} else {
VL_RESTORER(m_hash);
// Reset accumulator
@@ -585,13 +585,13 @@ public:
V3Hash V3Hasher::operator()(AstNode* nodep) const {
if (!nodep->user4()) HasherVisitor{nodep};
return V3Hash{nodep->user4()};
return V3Hash{static_cast<uint32_t>(nodep->user4())};
}
V3Hash V3Hasher::rehash(AstNode* nodep) const {
nodep->user4(0);
{ HasherVisitor{nodep}; }
return V3Hash{nodep->user4()};
return V3Hash{static_cast<uint32_t>(nodep->user4())};
}
V3Hash V3Hasher::uncachedHash(const AstNode* nodep) {
+1 -1
View File
@@ -32,7 +32,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
class InstrCountVisitor final : public VNVisitorConst {
// NODE STATE
// AstNode::user1() -> bool. Processed if assertNoDups
// AstNode::user2() -> int. Path cost + 1, 0 means don't dump
// AstNode::user2() -> uint64_t. Path cost + 1, 0 means don't dump
const VNUser2InUse m_inuser2;
// MEMBERS
+11 -3
View File
@@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Interface references for tracing
// DESCRIPTION: Verilator: Interface references for tracing and VPI
//
// Code available from: https://verilator.org
//
@@ -64,7 +64,12 @@ class InlineIntfRefVisitor final : public VNVisitor {
if ((cellp = VN_CAST(fromVarp->user1p(), Cell)) || (cellp = irdtp->cellp())) {
varp->user1p(cellp);
const string alias = m_scope + "__DOT__" + pinp->name();
cellp->addIntfRefsp(new AstIntfRef{pinp->fileline(), alias});
// Prefer the port's own dtype; the source may have no modport
const AstIfaceRefDType* const portIrdtp = VN_CAST(varp->dtypep(), IfaceRefDType);
const string modportName
= portIrdtp ? portIrdtp->modportName() : irdtp->modportName();
cellp->addIntfRefsp(
new AstIntfRef{pinp->fileline(), alias, pinp->name(), modportName});
}
}
@@ -92,7 +97,10 @@ class InlineIntfRefVisitor final : public VNVisitor {
string alias;
if (!m_scope.empty()) alias = m_scope + "__DOT__";
alias += varlp->name();
cellp->addIntfRefsp(new AstIntfRef{varlp->fileline(), alias});
const AstIfaceRefDType* const lirdtp = VN_CAST(varlp->dtypep(), IfaceRefDType);
const string modportName = lirdtp ? lirdtp->modportName() : "";
cellp->addIntfRefsp(
new AstIntfRef{varlp->fileline(), alias, varlp->origName(), modportName});
}
//--------------------
void visit(AstNodeExpr*) override {} // Accelerate
+1 -1
View File
@@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Interface references for tracing
// DESCRIPTION: Verilator: Interface references for tracing and VPI
//
// Code available from: https://verilator.org
//
+1 -1
View File
@@ -124,7 +124,7 @@ public:
class LifeBlock final {
// NODE STATE
// Cleared each AstIf:
// AstVarScope::user1() -> int. Used in combining to detect duplicates
// AstVarScope::user1() -> uint64_t. Used in combining to detect duplicates
// LIFE MAP
// For each basic block, we'll make a new map of what variables that if/else is changing
+44 -1
View File
@@ -56,6 +56,10 @@
//
// Constraint 3 should always hold with V3Delayed, will check assert it.
//
// If old-value reads prevent elimination, constant partial writes can still narrow both
// copies to the words containing those writes. Other shadow bits are never read. The
// assignments keep their original positions, so evaluation order and scheduling are unchanged.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
@@ -124,6 +128,7 @@ class LifePostDlyVisitor final : public VNVisitorConst {
const AstExecGraph* m_execGraphp = nullptr; // Current AstExecGraph being processed (or null)
const ExecMTask* m_execMTaskp = nullptr; // Current ExecMTask being processed (or null)
VDouble0 m_statAssnDel; // Statistic tracking
VDouble0 m_statWordsSaved; // Words removed from NBA shadow copies
// Maps from Varscope to all their reads and writes
using LocMap = std::unordered_map<const AstVarScope*, std::vector<Location<AstVarRef>>>;
LocMap m_reads; // VarScope read locations
@@ -134,6 +139,40 @@ class LifePostDlyVisitor final : public VNVisitorConst {
bool m_inEvalNba = false; // Traversing under the 'nba' region entry point
// METHODS
void narrowCopies(AstNodeAssign* postp, AstVarScope* dVscp,
const std::vector<Location<AstVarRef>>& writes) {
if (!dVscp->isWide()) return;
AstNodeAssign* const prep = VN_AS(writes[0].nodep()->backp(), NodeAssign);
UASSERT_OBJ(VN_AS(prep->lhsp(), VarRef)->varScopep()
== VN_AS(postp->rhsp(), VarRef)->varScopep()
&& VN_AS(prep->rhsp(), VarRef)->varScopep()
== VN_AS(postp->lhsp(), VarRef)->varScopep(),
prep, "NBA shadow pre/post assignments are not reverse copies");
int lsb = dVscp->width();
int end = 0;
for (size_t i = 1; i < writes.size(); ++i) {
const AstSel* const selp = VN_CAST(writes[i].nodep()->backp(), Sel);
if (!selp || !VN_IS(selp->lsbp(), Const)) return;
const int start = selp->lsbConst();
if (start > dVscp->width() - selp->width()) return;
lsb = std::min(lsb, start);
end = std::max(end, start + selp->width());
}
// Copy one word-aligned range enclosing all writes, including any gaps.
lsb = VL_BITWORD_E(lsb) * VL_EDATASIZE;
end = std::min(VL_WORDS_I(end) * VL_EDATASIZE, dVscp->width());
const int width = end - lsb;
if (width == dVscp->width()) return;
for (AstNodeAssign* const assignp : {prep, postp}) {
FileLine* const flp = assignp->fileline();
assignp->lhsp(new AstSel{flp, assignp->lhsp()->unlinkFrBack(), lsb, width});
assignp->rhsp(new AstSel{flp, assignp->rhsp()->unlinkFrBack(), lsb, width});
assignp->dtypeFrom(assignp->lhsp());
}
m_statWordsSaved += 2 * (dVscp->widthWords() - VL_WORDS_I(width));
}
void squashAssignposts() {
for (const Location<AstNodeAssign>& assign : m_assigns) {
AstVarScope* const dVscp = VN_AS(assign.nodep()->rhsp(), VarRef)->varScopep();
@@ -172,7 +211,10 @@ class LifePostDlyVisitor final : public VNVisitorConst {
}
return true;
}();
if (!qRdOK) continue;
if (!qRdOK) {
narrowCopies(assign.nodep(), dVscp, dWrites);
continue;
}
}
// Mark variable for replacement
@@ -308,6 +350,7 @@ public:
}
~LifePostDlyVisitor() override {
V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel);
V3Stats::addStat("Optimizations, Lifetime NBA copy words removed", m_statWordsSaved);
}
};
+6 -4
View File
@@ -236,6 +236,7 @@ class LinkCellsVisitor final : public VNVisitor {
const V3GraphVertex* m_topVertexp = nullptr; // Vertex of top module
std::unordered_set<string> m_declfnWarned; // Files we issued DECLFILENAME on
string m_origTopModuleName; // original name of the top module
int m_modDepth = 0; // Depth of the current module
// METHODS
V3GraphVertex* vertex(AstNodeModule* nodep) {
@@ -455,19 +456,20 @@ class LinkCellsVisitor final : public VNVisitor {
void visit(AstConstPool* nodep) override {}
void visit(AstNodeModule* nodep) override {
// Module: Pick up modnames, so we can resolve cells later
VL_RESTORER(m_modDepth);
VL_RESTORER(m_modp);
{
// For nested modules/classes, child below parent
if (m_modp) newEdge(vertex(m_modp), vertex(nodep), 1, false);
//
m_modp = nodep;
vertex(m_modp); // Need vertex to levelize even if no edges
// Need vertex to levelize even if no edges
vertex(m_modp);
++m_modDepth;
UINFO(4, "Link Module: " << nodep);
if (nodep->fileline()->filebasenameNoExt() != nodep->prettyName()
&& !v3Global.opt.isLibraryFile(nodep->fileline()->filename(), nodep->libname())
&& !VN_IS(nodep, NotFoundModule) && !nodep->recursiveClone()
&& !nodep->internal()) {
&& nodep != v3Global.rootp()->dollarUnitPkgp() && m_modDepth == 1) {
// We only complain once per file, otherwise library-like files
// have a huge mess of warnings
const auto itFoundPair = m_declfnWarned.insert(nodep->fileline()->filename());
+12 -4
View File
@@ -1216,7 +1216,14 @@ class LinkDotFindVisitor final : public VNVisitor {
// (sorted before this is called).
// This may not be the module with isTop() set, as early in the steps,
// wrapTop may have not been created yet.
if (!nodep->modulesp()) nodep->v3error("No top level module found");
// $unit always exists, so nothing else, and nothing in it, means nothing was given
AstNodeModule* const modulesp = nodep->modulesp();
UASSERT_OBJ(modulesp, nodep, "$unit should always be in the netlist");
if (!modulesp->nextp()) {
UASSERT_OBJ(modulesp == v3Global.rootp()->dollarUnitPkgp(), modulesp,
"Sole module should be $unit");
if (!modulesp->stmtsp()) nodep->v3error("No top level module found");
}
for (AstNodeModule* modp = nodep->modulesp(); modp && modp->isTop();
modp = VN_AS(modp->nextp(), NodeModule)) {
UINFO(8, "Top Module: " << modp);
@@ -1405,7 +1412,7 @@ class LinkDotFindVisitor final : public VNVisitor {
}
void visit(AstClassOrPackageRef* nodep) override { // FindVisitor::
if (!nodep->classOrPackageNodep() && nodep->name() == "$unit") {
nodep->classOrPackageNodep(v3Global.rootp()->dollarUnitPkgAddp());
nodep->classOrPackageNodep(v3Global.rootp()->dollarUnitPkgp());
}
iterateChildren(nodep);
}
@@ -2457,8 +2464,9 @@ class LinkDotParamVisitor final : public VNVisitor {
if (AstNode* const refp = nodep->op2p()) pinImplicitExprRecurse(refp);
if (AstNode* const refp = nodep->op3p()) pinImplicitExprRecurse(refp);
if (AstNode* const refp = nodep->op4p()) pinImplicitExprRecurse(refp);
if (AstNode* const refp = nodep->nextp()) pinImplicitExprRecurse(refp);
}
// Continue along a list (e.g. the terminals under AstImplicit), also after a reference
if (AstNode* const refp = nodep->nextp()) pinImplicitExprRecurse(refp);
}
// VISITORS
@@ -5572,7 +5580,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstNode* const attrp = nodep->attrp()->unlinkFrBack();
VL_DO_DANGLING(attrp->deleteTree(), attrp);
}
AstNode* const basefromp = AstArraySel::baseFromp(nodep, false);
AstNode* const basefromp = nodep->baseFromp(false);
if (VN_IS(basefromp, Replicate)) {
// From {...}[...] syntax in IEEE 2017
if (basefromp) UINFO(9, indent() << " Related node: " << basefromp);
+4 -2
View File
@@ -211,7 +211,7 @@ class LinkJumpVisitor final : public VNVisitor {
if (!processQueuep->lifetime().isStatic() || processQueuep->isTemp()) {
return new AstVarRef{fl, processQueuep, access};
}
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgp();
return new AstVarRef{fl, topPkgp, processQueuep, access};
}
static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) {
@@ -251,6 +251,7 @@ class LinkJumpVisitor final : public VNVisitor {
// Disable-by-name rewrites kill this detached task-body process, so mark it as process
// backed to ensure fork/join kill-accounting hooks are always emitted.
taskBodyp->setNeedProcess();
v3Global.setUsesTiming();
if (taskp->stmtsp()) taskBodyp->addStmtsp(taskp->stmtsp()->unlinkFrBackWithNext());
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};
@@ -275,7 +276,7 @@ class LinkJumpVisitor final : public VNVisitor {
AstNodeModule* const ownerp = findOwnerModulep(nodep);
if (VN_IS(ownerp, Package) || VN_IS(ownerp, Class)) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgp();
AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::VAR);
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
topPkgp->addStmtsp(processQueuep);
@@ -306,6 +307,7 @@ class LinkJumpVisitor final : public VNVisitor {
// Disable-by-name rewrites kill this detached block-body process, so mark it as process
// backed to ensure fork/join kill-accounting hooks are always emitted.
beginBodyp->setNeedProcess();
v3Global.setUsesTiming();
if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext());
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN};
+65 -6
View File
@@ -1228,6 +1228,7 @@ class LinkParseVisitor final : public VNVisitor {
addArgMemberCopies(funcp, sampleArgsp, false);
funcp->classMethod(true);
funcp->dtypep(funcp->findVoidDType());
funcp->keepAlive(true); // TODO create AstFuncRef and hold until findMethod("sample")
nodep->addMembersp(funcp);
}
@@ -1284,6 +1285,23 @@ class LinkParseVisitor final : public VNVisitor {
}
}
bool dropDeprecatedCoverageOption(AstCgOptionAssign* const nodep) {
if (!(nodep->optType() == VCoverOptionType::CROSS_AUTO_BIN_MAX)) return false;
cleanFileline(nodep);
nodep->v3warn(NONSTD, "Coverage option 'option."
<< nodep->optType().ascii()
<< "' is deprecated and ignored; it was removed from the "
"IEEE LRM because it was poorly defined.");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return true;
}
void visit(AstCgOptionAssign* nodep) override {
if (dropDeprecatedCoverageOption(nodep)) return;
cleanFileline(nodep);
iterateChildren(nodep);
}
void visit(AstCovergroup* nodep) override {
// AstCovergroup can only appear inside a module/class/package; never at root level.
UASSERT_OBJ(m_modp, nodep, "AstCovergroup not under module");
@@ -1400,6 +1418,7 @@ class LinkParseVisitor final : public VNVisitor {
for (AstNode *itemp = nodep->binsp(), *nextp; itemp; itemp = nextp) {
nextp = itemp->nextp();
if (AstCgOptionAssign* const optp = VN_CAST(itemp, CgOptionAssign)) {
if (dropDeprecatedCoverageOption(optp)) continue;
optp->unlinkFrBack();
if (optp->optType() == VCoverOptionType::AT_LEAST
|| optp->optType() == VCoverOptionType::AUTO_BIN_MAX) {
@@ -1415,16 +1434,56 @@ class LinkParseVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstCoverBinsof* nodep) override {
cleanFileline(nodep);
AstCoverpointRef* const refp = nodep->pointp();
const AstParseRef* pointp = VN_CAST(refp->exprp(), ParseRef);
const AstParseRef* binp = nullptr;
const AstDot* const dotp = VN_CAST(refp->exprp(), Dot);
if (dotp) {
pointp = VN_CAST(dotp->lhsp(), ParseRef);
binp = VN_CAST(dotp->rhsp(), ParseRef);
}
if (!pointp || (dotp && !binp)) {
nodep->v3warn(COVERIGN, "Unsupported: 'binsof' in coverage select expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
// These names belong to the coverage namespace, not the sampled variables.
if (binp) nodep->name(binp->name());
refp->replaceWith(new AstCoverpointRef{pointp->fileline(), pointp->name()});
VL_DO_DANGLING(pushDeletep(refp), refp);
iterateChildren(nodep);
}
void visit(AstCoverCrossBin* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (!nodep->selectp()) {
nodep->v3warn(COVERIGN, "Unsupported: explicit coverage cross bins");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
void visit(AstCoverCrossSelect* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
if (!nodep->lhsp()
|| !nodep->rhsp()) { // Due to earlier Unsupported errors dropping only one operand
// would silently change the selected set.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
void visit(AstCoverCross* nodep) override {
cleanFileline(nodep);
// Distribute the parse-time raw cross_body list (rawBodyp, op3) into the
// typed optionsp slot. The grammar produces AstCgOptionAssign nodes for
// option.* items; convert them to AstCoverOption exactly as visit(AstCoverpoint*)
// does. Other items (functions, unsupported bin selectors) are discarded.
for (AstNode *itemp = nodep->rawBodyp(), *nextp; itemp; itemp = nextp) {
// Move options out of the mixed parse-time body, leaving only cross bins.
for (AstNode *itemp = nodep->binsp(), *nextp; itemp; itemp = nextp) {
nextp = itemp->nextp();
itemp->unlinkFrBack();
if (VN_IS(itemp, CoverCrossBin)) continue;
AstCgOptionAssign* const optp = VN_AS(itemp, CgOptionAssign);
if (dropDeprecatedCoverageOption(optp)) continue;
itemp->unlinkFrBack();
const VCoverOptionType optType = optp->optType();
optp->v3warn(COVERIGN,
"Ignoring unsupported coverage cross option: " + optp->prettyNameQ());
+141 -84
View File
@@ -530,7 +530,8 @@ void V3Number::setBitX0(int bit) {
V3Number& V3Number::setMask(int nbits, int lsb) {
setZero();
for (int bit = lsb; bit < lsb + nbits; ++bit) setBit(bit, 1);
UASSERT(lsb >= 0, "Negative destination bit range");
fillBits(lsb, std::max(0, std::min(nbits, width() - lsb)), 1);
return *this;
}
@@ -754,7 +755,8 @@ string V3Number::displayed(FileLine* fl, const string& vformat,
(void)VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, "%g", n);
return tmp;
}
if (formatAttr.isString()) return '"' + toString() + '"';
if (formatAttr.isString())
return '"' + V3OutFormatter::quoteNameControls(toString()) + '"';
if (formatAttr.isComplex()) return toString();
return "%p";
}
@@ -1152,8 +1154,10 @@ uint8_t V3Number::dataByte(int byte) const {
bool V3Number::isAllZ() const VL_MT_SAFE {
if (isDouble() || isString()) return false;
for (int i = 0; i < width(); ++i) {
if (!bitIsZ(i)) return false;
for (int i = 0; i < words(); ++i) {
const ValueAndX v = m_data.num()[i];
const uint32_t mask = i == words() - 1 ? hiWordMask() : ~0U;
if (((~v.m_value & v.m_valueX) & mask) != mask) return false;
}
return true;
}
@@ -1214,16 +1218,24 @@ bool V3Number::isFourState() const VL_MT_SAFE {
}
bool V3Number::isAnyX() const VL_MT_SAFE {
if (isDouble() || isString()) return false;
for (int bit = 0; bit < width(); ++bit) {
if (bitIsX(bit)) return true;
for (int i = 0; i < words(); ++i) {
const ValueAndX v = m_data.num()[i];
if (v.m_value & v.m_valueX) return true;
}
return false;
}
bool V3Number::isAnyXZ() const {
if (isDouble() || isString()) return false;
for (int i = 0; i < words(); ++i) {
if (m_data.num()[i].m_valueX) return true;
}
return false;
}
bool V3Number::isAnyXZ() const { return isAnyX() || isAnyZ(); }
bool V3Number::isAnyZ() const VL_MT_SAFE {
if (isDouble() || isString()) return false;
for (int bit = 0; bit < width(); ++bit) {
if (bitIsZ(bit)) return true;
for (int i = 0; i < words(); ++i) {
const ValueAndX v = m_data.num()[i];
if (~v.m_value & v.m_valueX) return true;
}
return false;
}
@@ -1564,6 +1576,57 @@ V3Number& V3Number::opXor(const V3Number& lhs, const V3Number& rhs) {
return *this;
}
void V3Number::fillBits(int destLsb, int width, char value) {
UASSERT(destLsb >= 0 && width >= 0, "Negative bit range");
if (!width) return;
UASSERT(width <= this->width() - destLsb, "Destination bit range exceeds number width");
const uint32_t valueWord = value == '1' || value == 'x' || value == 1 || value == 3 ? ~0U : 0;
const uint32_t xWord = value == 'z' || value == 'x' || value == 2 || value == 3 ? ~0U : 0;
const int firstWord = VL_BITWORD_E(destLsb);
const int lastWord = VL_BITWORD_E(destLsb + width - 1);
for (int word = firstWord; word <= lastWord; ++word) {
const int wordLsb = word == firstWord ? VL_BITBIT_E(destLsb) : 0;
const int wordMsb = word == lastWord ? VL_BITBIT_E(destLsb + width - 1) : VL_SIZEBITS_E;
const uint32_t mask = VL_MASK_E(wordMsb - wordLsb + 1) << wordLsb;
ValueAndX& dest = m_data.num()[word];
dest.m_value = (dest.m_value & ~mask) | (valueWord & mask);
dest.m_valueX = (dest.m_valueX & ~mask) | (xWord & mask);
}
}
void V3Number::copyBits(int destLsb, const V3Number& source, int sourceLsb, int width) {
UASSERT(destLsb >= 0 && sourceLsb >= 0 && width >= 0, "Negative bit range");
if (!width) return;
UASSERT(width <= this->width() - destLsb, "Destination bit range exceeds number width");
UASSERT(width <= source.width() - sourceLsb, "Source bit range exceeds number width");
const int firstWord = VL_BITWORD_E(destLsb);
const int lastWord = VL_BITWORD_E(destLsb + width - 1);
for (int destWord = firstWord; destWord <= lastWord; ++destWord) {
const int destBit = std::max(destLsb, destWord * VL_EDATASIZE);
const int destOffset = VL_BITBIT_E(destBit);
const int sourceBit = sourceLsb + destBit - destLsb;
const int sourceWord = VL_BITWORD_E(sourceBit);
const int sourceOffset = VL_BITBIT_E(sourceBit);
const int copyWidth = std::min(width - (destBit - destLsb), VL_EDATASIZE - destOffset);
const uint32_t mask = VL_MASK_E(copyWidth) << destOffset;
const ValueAndX& sourceLo = source.m_data.num()[sourceWord];
uint32_t value = sourceLo.m_value >> sourceOffset;
uint32_t valueX = sourceLo.m_valueX >> sourceOffset;
if (sourceOffset && copyWidth > VL_EDATASIZE - sourceOffset) {
const ValueAndX& sourceHi = source.m_data.num()[sourceWord + 1];
value |= sourceHi.m_value << (VL_EDATASIZE - sourceOffset);
valueX |= sourceHi.m_valueX << (VL_EDATASIZE - sourceOffset);
}
ValueAndX& dest = m_data.num()[destWord];
dest.m_value = (dest.m_value & ~mask) | ((value << destOffset) & mask);
dest.m_valueX = (dest.m_valueX & ~mask) | ((valueX << destOffset) & mask);
}
}
V3Number& V3Number::opConcat(const V3Number& lhs, const V3Number& rhs) {
// Correct number of zero bits/width matters
NUM_ASSERT_OP_ARGS2(lhs, rhs);
@@ -1573,15 +1636,8 @@ V3Number& V3Number::opConcat(const V3Number& lhs, const V3Number& rhs) {
if (!lhs.sized() || !rhs.sized()) {
v3warn(WIDTHCONCAT, "Unsized numbers/parameters not allowed in concatenations.");
}
int obit = 0;
for (int bit = 0; bit < rhs.width(); ++bit) {
setBit(obit, rhs.bitIs(bit));
++obit;
}
for (int bit = 0; bit < lhs.width(); ++bit) {
setBit(obit, lhs.bitIs(bit));
++obit;
}
copyBits(0, rhs, 0, rhs.width());
copyBits(rhs.width(), lhs, 0, lhs.width());
return *this;
}
@@ -1615,12 +1671,11 @@ V3Number& V3Number::opRepl(const V3Number& lhs,
<< v3Global.opt.replicationLimit() << " is suspect: " << rhsval);
}
setZero();
int obit = 0;
for (unsigned times = 0; times < rhsval; ++times) {
for (int bit = 0; bit < lhs.width(); ++bit) {
setBit(obit, lhs.bitIs(bit));
++obit;
}
const uint64_t destLsb = uint64_t{times} * lhs.width();
if (destLsb >= static_cast<uint32_t>(width())) break;
copyBits(static_cast<int>(destLsb), lhs, 0,
std::min(lhs.width(), width() - static_cast<int>(destLsb)));
}
return *this;
}
@@ -1637,9 +1692,7 @@ V3Number& V3Number::opStreamL(const V3Number& lhs, const V3Number& rhs) {
const int ssize = std::min(rhs.toUInt(), static_cast<unsigned>(lhs.width()));
for (int istart = 0; istart < lhs.width(); istart += ssize) {
const int ostart = std::max(0, lhs.width() - ssize - istart);
for (int bit = 0; bit < ssize && bit < lhs.width() - istart; ++bit) {
setBit(ostart + bit, lhs.bitIs(istart + bit));
}
copyBits(ostart, lhs, istart, std::min(ssize, lhs.width() - istart));
}
return *this;
}
@@ -1973,12 +2026,11 @@ V3Number& V3Number::opShiftR(const V3Number& lhs, const V3Number& rhs) {
NUM_ASSERT_LOGIC_ARGS2(lhs, rhs);
if (rhs.isFourState()) return setAllBitsX();
setZero();
for (int bit = 32; bit < rhs.width(); ++bit) {
if (rhs.bitIs1(bit)) return *this; // shift of over 2^32 must be zero
}
if (rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32)) return *this;
const uint32_t rhsval = rhs.toUInt();
if (rhsval < static_cast<uint32_t>(lhs.width())) {
for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIs(bit + rhsval));
copyBits(0, lhs, static_cast<int>(rhsval),
std::min(width(), lhs.width() - static_cast<int>(rhsval)));
}
return *this;
}
@@ -1995,14 +2047,14 @@ V3Number& V3Number::opShiftRS(const V3Number& lhs, const V3Number& rhs, uint32_t
const bool overflow = rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32);
if (!overflow) {
const uint32_t rhsval = rhs.toUInt();
if (rhsval < static_cast<uint32_t>(lhs.width())) {
for (int bit = 0; bit < width(); ++bit) {
setBit(bit, lhs.bitIsExtend(bit + rhsval, lbits));
}
if (rhsval < lbits) {
const int copyWidth = std::min(width(), static_cast<int>(lbits - rhsval));
copyBits(0, lhs, static_cast<int>(rhsval), copyWidth);
fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1));
return *this;
}
}
for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIs(lbits - 1)); // '0/'1/'x/'z
fillBits(0, width(), lhs.bitIs(lbits - 1)); // '0/'1/'x/'z
return *this;
}
@@ -2012,12 +2064,11 @@ V3Number& V3Number::opShiftL(const V3Number& lhs, const V3Number& rhs) {
NUM_ASSERT_LOGIC_ARGS2(lhs, rhs);
if (rhs.isFourState()) return setAllBitsX();
setZero();
for (int bit = 32; bit < rhs.width(); ++bit) {
if (rhs.bitIs1(bit)) return *this; // shift of over 2^32 must be zero
}
if (rhs.width() > 32 && !rhs.isBitsZero(rhs.width() - 1, 32)) return *this;
const uint32_t rhsval = rhs.toUInt();
for (uint32_t bit = 0; bit < static_cast<uint32_t>(width()); ++bit) {
if (bit >= rhsval) setBit(bit, lhs.bitIs(bit - rhsval));
if (rhsval < static_cast<uint32_t>(width())) {
copyBits(static_cast<int>(rhsval), lhs, 0,
std::min(width() - static_cast<int>(rhsval), lhs.width()));
}
return *this;
}
@@ -2386,8 +2437,11 @@ V3Number& V3Number::opBufIf1(const V3Number& ens, const V3Number& if1s) {
// Sets all bits in range to the given value
V3Number& V3Number::opSetRange(uint32_t lsb, uint32_t width, char bitValue) {
const uint32_t msb = lsb + width - 1;
for (uint32_t i = lsb; i <= msb; ++i) setBit(i, bitValue);
if (lsb < static_cast<uint32_t>(this->width())) {
fillBits(static_cast<int>(lsb),
static_cast<int>(std::min(width, static_cast<uint32_t>(this->width()) - lsb)),
bitValue);
}
return *this;
}
@@ -2412,10 +2466,17 @@ V3Number& V3Number::opAssignNonXZ(const V3Number& lhs, bool ignoreXZ) {
setZero();
} else if (lhs.isDouble()) {
setDouble(lhs.toDouble());
} else if (!ignoreXZ) {
setZero();
copyBits(0, lhs, 0, std::min(width(), lhs.width()));
} else {
for (int bit = 0; bit < this->width(); ++bit) {
setBit(bit, ignoreXZ ? lhs.bitIs1(bit) : lhs.bitIs(bit));
for (int word = 0; word < words(); ++word) {
const uint32_t value = word < lhs.words() ? lhs.m_data.num()[word].m_value
& ~lhs.m_data.num()[word].m_valueX
: 0;
m_data.num()[word] = {value, 0};
}
opCleanThis();
}
}
return *this;
@@ -2442,10 +2503,9 @@ V3Number& V3Number::opExtendS(const V3Number& lhs, uint32_t lbits) {
NUM_ASSERT_OP_ARGS1(lhs);
NUM_ASSERT_LOGIC_ARGS1(lhs);
setZero();
for (int bit = 0; bit < width(); ++bit) {
const char extendWith = lhs.bitIsExtend(bit, lbits);
setBit(bit, extendWith);
}
const int copyWidth = std::min(width(), static_cast<int>(lbits));
copyBits(0, lhs, 0, copyWidth);
fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1));
return *this;
}
@@ -2454,7 +2514,9 @@ V3Number& V3Number::opExtendXZ(const V3Number& lhs, uint32_t lbits) {
NUM_ASSERT_OP_ARGS1(lhs);
NUM_ASSERT_LOGIC_ARGS1(lhs);
setZero();
for (int bit = 0; bit < width(); ++bit) setBit(bit, lhs.bitIsExtend(bit, lbits));
const int copyWidth = std::min(width(), static_cast<int>(lbits));
copyBits(0, lhs, 0, copyWidth);
fillBits(copyWidth, width() - copyWidth, lhs.bitIs(lbits - 1));
return *this;
}
@@ -2486,15 +2548,15 @@ V3Number& V3Number::opSel(const V3Number& lhs, uint32_t msbval, uint32_t lsbval)
NUM_ASSERT_OP_ARGS1(lhs);
NUM_ASSERT_LOGIC_ARGS1(lhs);
setZero();
int ibit = lsbval;
for (int bit = 0; bit < width(); ++bit) {
if (ibit >= 0 && ibit < lhs.width() && ibit <= static_cast<int>(msbval)) {
setBit(bit, lhs.bitIs(ibit));
} else {
setBitX0(bit);
}
++ibit;
int copyWidth = 0;
if (lsbval <= msbval && lsbval < static_cast<uint32_t>(lhs.width())) {
const uint64_t selectedWidth = uint64_t{msbval} - lsbval + 1;
copyWidth = static_cast<int>(
std::min({static_cast<uint64_t>(width()), static_cast<uint64_t>(lhs.width()) - lsbval,
selectedWidth}));
}
copyBits(0, lhs, copyWidth ? static_cast<int>(lsbval) : 0, copyWidth);
fillBits(copyWidth, width() - copyWidth, v3Global.constRemoveXs() ? 0 : 'x');
// UINFO(0, "RANGE " << lhs << " " << msb << " " << lsb << " = " << *this);
return *this;
}
@@ -2507,15 +2569,12 @@ V3Number& V3Number::opSelInto(const V3Number& lhs, int lsbval, int width) {
// this[lsbval+width-1 : lsbval] = lhs; Other bits of this are not affected
NUM_ASSERT_OP_ARGS1(lhs);
NUM_ASSERT_LOGIC_ARGS1(lhs);
int ibit = 0;
for (int bit = lsbval; bit < lsbval + width; ++bit) {
if (ibit >= 0 && ibit < lhs.width()) {
setBit(bit, lhs.bitIs(ibit));
} else {
setBitX0(bit);
}
++ibit;
}
UASSERT(lsbval >= 0, "Negative destination bit range");
const int copyWidth = std::max(0, std::min({width, lhs.width(), this->width() - lsbval}));
copyBits(lsbval, lhs, 0, copyWidth);
const int fillWidth
= std::max(0, std::min(width - copyWidth, this->width() - lsbval - copyWidth));
fillBits(lsbval + copyWidth, fillWidth, v3Global.constRemoveXs() ? 0 : 'x');
return *this;
}
@@ -2743,24 +2802,22 @@ void V3Number::selfTest() {
void V3Number::selfTestThis() {
// The self test has a "this" so UASSERT_SELFTEST/errorEndFatal works correctly
UASSERT_SELFTEST(const bool, V3Number::epsilonEqual(0, 0), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonEqual(1e19, 1e19), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonEqual(9, 0.0001), false);
UASSERT_SELFTEST(const bool,
V3Number::epsilonEqual(1, 1 + std::numeric_limits<double>::epsilon()), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonEqual(0.009, 0.00899999999999999931998839741709),
true);
UASSERT_SELFTEST(V3Number::epsilonEqual(0, 0), true);
UASSERT_SELFTEST(V3Number::epsilonEqual(1e19, 1e19), true);
UASSERT_SELFTEST(V3Number::epsilonEqual(9, 0.0001), false);
UASSERT_SELFTEST(V3Number::epsilonEqual(1, 1 + std::numeric_limits<double>::epsilon()), true);
UASSERT_SELFTEST(V3Number::epsilonEqual(0.009, 0.00899999999999999931998839741709), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(0), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(1), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(-1), true);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(1.0001), false);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(0.9999), false);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(-1.0001), false);
UASSERT_SELFTEST(const bool, V3Number::epsilonIntegral(-0.9999), false);
UASSERT_SELFTEST(V3Number::epsilonIntegral(0), true);
UASSERT_SELFTEST(V3Number::epsilonIntegral(1), true);
UASSERT_SELFTEST(V3Number::epsilonIntegral(-1), true);
UASSERT_SELFTEST(V3Number::epsilonIntegral(1.0001), false);
UASSERT_SELFTEST(V3Number::epsilonIntegral(0.9999), false);
UASSERT_SELFTEST(V3Number::epsilonIntegral(-1.0001), false);
UASSERT_SELFTEST(V3Number::epsilonIntegral(-0.9999), false);
UASSERT_SELFTEST(const int, log2b(0), 0);
UASSERT_SELFTEST(const int, log2b(1), 0);
UASSERT_SELFTEST(const int, log2b(0x40000000UL), 30);
UASSERT_SELFTEST(const int, log2bQuad(0x4000000000000000ULL), 62);
UASSERT_SELFTEST(log2b(0), 0);
UASSERT_SELFTEST(log2b(1), 0);
UASSERT_SELFTEST(log2b(0x40000000UL), 30);
UASSERT_SELFTEST(log2bQuad(0x4000000000000000ULL), 62);
}
+4 -1
View File
@@ -47,6 +47,7 @@ public:
COMPLEX = VL_VFORMATATTR_COMPLEX,
DOUBLE = VL_VFORMATATTR_DOUBLE,
ENUM = VL_VFORMATATTR_ENUM,
ENUM_SIGNED = VL_VFORMATATTR_ENUM_SIGNED,
SCOPE = VL_VFORMATATTR_SCOPE,
STRING = VL_VFORMATATTR_STRING,
TIMEUNIT = VL_VFORMATATTR_TIMEUNIT
@@ -63,7 +64,7 @@ public:
char ascii() const { return m_e; }
bool isComplex() const { return m_e == COMPLEX; }
bool isDouble() const { return m_e == DOUBLE; }
bool isEnum() const { return m_e == ENUM; }
bool isEnum() const { return m_e == ENUM || m_e == ENUM_SIGNED; }
bool isSigned() const { return m_e == SIGNED; }
bool isString() const { return m_e == STRING; }
bool isUnsigned() const { return m_e == UNSIGNED; }
@@ -396,6 +397,8 @@ class V3Number final {
m_data.m_autoExtend = true;
}
V3Number& setSingleBits(char value);
void fillBits(int destLsb, int width, char value);
void copyBits(int destLsb, const V3Number& source, int sourceLsb, int width);
V3Number& setString(const string& str) {
m_data.setString(str);
return *this;
+2 -4
View File
@@ -1476,6 +1476,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-fconst-eager", FOnOff, &m_fConstEager);
DECL_OPTION("-fdead-assigns", FOnOff, &m_fDeadAssigns);
DECL_OPTION("-fdead-cells", FOnOff, &m_fDeadCells);
DECL_OPTION("-fdead-methods", FOnOff, &m_fDeadMethods);
DECL_OPTION("-fdedup", FOnOff, &m_fDedupe);
DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { m_fDfg = flag; });
DECL_OPTION("-fdfg-break-cycles", CbFOnOff, [fl](bool) {
@@ -1773,10 +1774,6 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-std-package", OnOff, &m_stdPackage);
DECL_OPTION("-std-waiver", OnOff, &m_stdWaiver);
DECL_OPTION("-stop-fail", OnOff, &m_stopFail);
DECL_OPTION("-structs-packed", CbOnOff, [this, fl](bool flag) {
m_structsPacked = flag;
fl->v3warn(DEPRECATED, "Option --structs-packed is deprecated, avoid use");
}).undocumented();
DECL_OPTION("-sv", CbCall, [this]() { m_defaultLanguage = V3LangCode::L1800_2023; });
DECL_OPTION("-no-threads", CbCall, [this, fl]() {
@@ -2386,6 +2383,7 @@ void V3Options::optimize(int level) {
m_fDfg = flag;
m_fDeadAssigns = flag;
m_fDeadCells = flag;
m_fDeadMethods = flag;
m_fExpand = flag;
m_fGate = flag;
m_fInline = flag;
+7 -2
View File
@@ -48,6 +48,11 @@ public:
explicit VOptionBool(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
const char* ascii() const {
static const char* const names[]
= {"DEFAULT_FALSE", "DEFAULT_TRUE", "OPT_TRUE", "OPT_FALSE"};
return names[m_e];
}
bool isDefault() const { return m_e == OPT_DEFAULT_FALSE || m_e == OPT_DEFAULT_TRUE; }
bool isTrue() const { return m_e == OPT_TRUE || m_e == OPT_DEFAULT_TRUE; }
bool isSetTrue() const { return m_e == OPT_TRUE; }
@@ -289,7 +294,6 @@ private:
VOptionBool m_schedZeroDelay; // main switch: --sched-zero-delay
bool m_stdPackage = true; // main switch: --std-package
bool m_stdWaiver = true; // main switch: --std-waiver
bool m_structsPacked = false; // main switch: --structs-packed
bool m_systemC = false; // main switch: --sc: System C instead of simple C++
bool m_stats = false; // main switch: --stats
bool m_statsVars = false; // main switch: --stats-vars
@@ -407,6 +411,7 @@ private:
bool m_fDfgSynthesizeAll = false; // main switch: -fdfg-synthesize-all
bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns
bool m_fDeadCells; // main switch: -fno-dead-cells: remove dead cells
bool m_fDeadMethods; // main switch: -fno-dead-methods: remove dead methods
bool m_fExpand; // main switch: -fno-expand: expansion of C macros
bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros
bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros
@@ -504,7 +509,6 @@ public:
bool statsVars() const { return m_statsVars; }
bool stdPackage() const { return m_stdPackage; }
bool stdWaiver() const { return m_stdWaiver; }
bool structsPacked() const { return m_structsPacked; }
bool assertOn() const { return m_assert; } // assertOn as __FILE__ may be defined
bool assertCase() const { return m_assertCase; }
bool autoflush() const { return m_autoflush; }
@@ -748,6 +752,7 @@ public:
}
bool fDeadAssigns() const { return m_fDeadAssigns; }
bool fDeadCells() const { return m_fDeadCells; }
bool fDeadMethods() const { return m_fDeadMethods; }
bool fExpand() const { return m_fExpand; }
bool fFuncBalanceCat() const { return m_fFuncBalanceCat; }
bool fFuncSplitCat() const { return m_fFuncSplitCat; }
+2 -1
View File
@@ -102,13 +102,14 @@ void V3Order::orderOrderGraph(OrderGraph& graph, const std::string& tag) {
AstCFunc* V3Order::order(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& logic, //
const V3Order::TrigToSenMap& trigToSen,
const V3Sched::CovergroupRefBindings& cgRefBindings,
const string& tag, //
bool parallel, //
bool slow, //
const ExternalDomainsProvider& externalDomains) {
// Build the OrderGraph
const std::unique_ptr<OrderGraph> graph
= buildOrderGraph(netlistp, logic, trigToSen, parallel);
= buildOrderGraph(netlistp, logic, trigToSen, cgRefBindings, parallel);
// Order it
orderOrderGraph(*graph, tag);
// Assign sensitivity domains to combinational logic
+2
View File
@@ -32,6 +32,7 @@ class AstVarScope;
namespace V3Sched {
struct LogicByScope;
class CovergroupRefBindings;
}; // namespace V3Sched
//============================================================================
@@ -46,6 +47,7 @@ using TrigToSenMap = std::unordered_map<const AstSenTree*, const AstSenTree*>;
AstCFunc* order(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& logic, //
const TrigToSenMap& trigToSen, //
const V3Sched::CovergroupRefBindings& cgRefBindings, //
const string& tag, //
bool parallel, //
bool slow, //
+69 -20
View File
@@ -112,6 +112,11 @@ class OrderGraphBuilder final : public VNVisitor {
V3Sched::util::VarScopeSet m_forceReadEdgeIgnores;
const bool m_parallel; // Ordering for multi-threaded execution (record variable accesses)
// What covergroup reference formal arguments are bound to at construction
const V3Sched::CovergroupRefBindings& m_cgRefBindings;
// Bindings reachable from the covergroup sample() being walked, nullptr when not in one
const V3Sched::CovergroupRefBindings::Bindings* m_cgRefBoundps = nullptr;
// METHODS
void iterateLogic(AstNode* nodep) {
@@ -198,18 +203,36 @@ class OrderGraphBuilder final : public VNVisitor {
UASSERT_OBJ(m_logicVxp, nodep, "AstVarRef not under logic");
AstVarScope* const varscp = nodep->varScopep();
UASSERT_OBJ(varscp, nodep, "Var didn't get varscoped in V3Scope.cpp");
// Variable reference in logic. Add data dependency.
// Record the raw access for the multi-threaded data hazard fixer
if (m_parallel) {
uint8_t recorded = 0;
if (nodep->access().isWriteOrRW()) recorded |= VA_WRITE;
if (nodep->access().isReadOrRW()) recorded |= VA_READ;
UASSERT_OBJ(recorded, nodep, "Unknown variable access type");
// Accumulate access type, record the variable on first access only
if (!varscp->user4Or(recorded)) m_accessedVscps.push_back(varscp);
// Reading a covergroup 'ref' formal reads whatever it was bound to at construction.
// The formal itself is a pointer member fixed at construction, so it is not itself
// interesting to ordering.
const AstVar* const varp = nodep->varp();
if (m_cgRefBoundps && varp->covergroupRefMember()) {
// Covergroup params are considered const-ref
UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "covergroup ref argument is written");
for (AstVarScope* const boundp : *m_cgRefBoundps) {
accountVarAccess(boundp, VAccess::READ, nodep);
}
} else {
accountVarAccess(varscp, nodep->access(), nodep);
}
}
// Record the raw access for the multi-threaded data hazard fixer
void recordRawAccess(AstVarScope* varscp, const VAccess& access, AstNode* nodep) {
if (!m_parallel) return;
uint8_t recorded = 0;
if (access.isWriteOrRW()) recorded |= VA_WRITE;
if (access.isReadOrRW()) recorded |= VA_READ;
UASSERT_OBJ(recorded, nodep, "Unknown variable access type");
// Accumulate access type, record the variable on first access only
if (!varscp->user4Or(recorded)) m_accessedVscps.push_back(varscp);
}
// Add the graph edges, and record the raw access, for one access of one variable
void accountVarAccess(AstVarScope* varscp, const VAccess& access, AstNode* nodep) {
// Variable reference in logic. Add data dependency.
recordRawAccess(varscp, access, nodep);
// Check whether this variable was already generated/consumed in the same logic. We
// don't want to add extra edges if the logic has many usages of the same variable,
@@ -218,12 +241,11 @@ class OrderGraphBuilder final : public VNVisitor {
const bool prevCon = varscp->user2() & VU_CON;
// Compute whether the variable is produced (written) here
const bool gen
= !prevGen && nodep->access().isWriteOrRW() && !varscp->varp()->ignoreSchedWrite();
const bool gen = !prevGen && access.isWriteOrRW() && !varscp->varp()->ignoreSchedWrite();
// Compute whether the value is consumed (read) here
bool con = false;
if (!prevCon && nodep->access().isReadOrRW()) {
if (!prevCon && access.isReadOrRW()) {
con = true;
if (prevGen && !m_inClocked) {
// Dangerous assumption:
@@ -239,7 +261,11 @@ class OrderGraphBuilder final : public VNVisitor {
// latch?).
con = false;
}
if (!m_inClocked && m_forceReadEdgeIgnores.count(varscp)) con = false;
if (!m_inClocked) {
// Ignored reads and references from within covergroups do not
// add to the combinational sensitivity of the block
if (m_forceReadEdgeIgnores.count(varscp) || m_cgRefBoundps) con = false;
}
}
// Note: See V3OrderGraph.h about the roles of the various vertex types
@@ -323,7 +349,26 @@ class OrderGraphBuilder final : public VNVisitor {
}
}
}
void visit(AstCCall* nodep) override { iterateChildren(nodep); }
// A covergroup sample() is not inlined and may read design signals through cross-scope
// references held by the covergroup. This attributes those references to the calling block.
void visit(AstCMethodCall* nodep) override {
iterateChildren(nodep);
AstCFunc* const funcp = nodep->funcp();
if (!funcp->isCovergroupSample()) return;
// Since sample is a built-in, we never expect recursion.
UASSERT_OBJ(!m_cgRefBoundps, nodep, "Covergroup sample() calls another sample()");
VL_RESTORER(m_cgRefBoundps);
// Reference formals are bound per covergroup object. If the call handle matches
// one that we recorded, use that info. If the call handle isn't something we
// recorded (eg array-element construction), use the union of references across
// the covergroup type.
const AstVarScope* instp = nullptr;
if (const AstVarRef* const fromRefp = VN_CAST(nodep->fromp(), VarRef)) {
instp = fromRefp->varScopep();
}
m_cgRefBoundps = &m_cgRefBindings.forSample(instp, VN_AS(funcp->scopep()->modp(), Class));
iterateChildren(funcp);
}
//--- Logic akin to SystemVerilog Processes (AstNodeProcedure)
void visit(AstInitial* nodep) override { // LCOV_EXCL_START
@@ -384,9 +429,11 @@ class OrderGraphBuilder final : public VNVisitor {
// CONSTRUCTOR
OrderGraphBuilder(AstNetlist* /*nodep*/, const std::vector<V3Sched::LogicByScope*>& coll,
const V3Order::TrigToSenMap& trigToSen, bool parallel)
const V3Order::TrigToSenMap& trigToSen,
const V3Sched::CovergroupRefBindings& cgRefBindings, bool parallel)
: m_trigToSen{trigToSen}
, m_parallel{parallel} {
, m_parallel{parallel}
, m_cgRefBindings{cgRefBindings} {
// Build the graph
for (const V3Sched::LogicByScope* const lbsp : coll) {
for (const auto& pair : *lbsp) {
@@ -404,9 +451,10 @@ public:
static std::unique_ptr<OrderGraph> apply(AstNetlist* nodep,
const std::vector<V3Sched::LogicByScope*>& coll,
const V3Order::TrigToSenMap& trigToSen,
const V3Sched::CovergroupRefBindings& cgRefBindings,
bool parallel) {
return std::unique_ptr<OrderGraph>{
OrderGraphBuilder{nodep, coll, trigToSen, parallel}.m_graphp};
OrderGraphBuilder{nodep, coll, trigToSen, cgRefBindings, parallel}.m_graphp};
}
};
@@ -414,6 +462,7 @@ std::unique_ptr<OrderGraph>
V3Order::buildOrderGraph(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const V3Order::TrigToSenMap& trigToSen, //
const V3Sched::CovergroupRefBindings& cgRefBindings, //
bool parallel) {
return OrderGraphBuilder::apply(netlistp, coll, trigToSen, parallel);
return OrderGraphBuilder::apply(netlistp, coll, trigToSen, cgRefBindings, parallel);
}
+1
View File
@@ -43,6 +43,7 @@ namespace V3Order {
std::unique_ptr<OrderGraph> buildOrderGraph(AstNetlist* netlistp, //
const std::vector<V3Sched::LogicByScope*>& coll, //
const TrigToSenMap& trigToSen, //
const V3Sched::CovergroupRefBindings& cgRefBindings,
bool parallel);
void orderOrderGraph(OrderGraph& graph, const std::string& tag);
+37 -37
View File
@@ -522,42 +522,42 @@ int V3Os::system(const string& command) {
void V3Os::selfTest() {
#ifdef VL_DEBUG
UASSERT_SELFTEST(const string, filenameCleanup(""), "");
UASSERT_SELFTEST(const string, filenameCleanup("."), ".");
UASSERT_SELFTEST(const string, filenameCleanup(".."), "..");
UASSERT_SELFTEST(const string, filenameCleanup("/"), "/");
UASSERT_SELFTEST(const string, filenameCleanup("../"), "..");
UASSERT_SELFTEST(const string, filenameCleanup("//"), "/");
UASSERT_SELFTEST(const string, filenameCleanup("//."), "/.");
UASSERT_SELFTEST(const string, filenameCleanup("./"), ".");
UASSERT_SELFTEST(const string, filenameCleanup("././"), ".");
UASSERT_SELFTEST(const string, filenameCleanup(".///"), ".");
UASSERT_SELFTEST(const string, filenameCleanup("a"), "a");
UASSERT_SELFTEST(const string, filenameCleanup("a/"), "a");
UASSERT_SELFTEST(const string, filenameCleanup("a/b"), "a/b");
UASSERT_SELFTEST(const string, filenameCleanup("././//./a/b"), "a/b");
UASSERT_SELFTEST(const string, filenameCleanup(".//./a///"), "a");
UASSERT_SELFTEST(const string, filenameCleanup("///a/./b///."), "/a/./b/.");
UASSERT_SELFTEST(const string, filenameCleanup("aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(const string, filenameCleanup("./aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(const string, filenameCleanup("../aaa/bbb/ccc/"), "../aaa/bbb/ccc");
UASSERT_SELFTEST(const string, filenameDir("a.a/b.b/f.e"), "a.a/b.b");
UASSERT_SELFTEST(const string, filenameExt("a.a/b.b/f"), "");
UASSERT_SELFTEST(const string, filenameExt("a.a/b.b/f.e"), ".e");
UASSERT_SELFTEST(const string, filenameNonDirExt("a.a/b.b/f.e"), "f");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b", "/a/b"), ".");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b", "/a/b/c"), "..");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b", "/a/b/c/d"), "../..");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b/x", "/a/b/c/d"), "../../x");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b/x/y", "/"), "a/b/x/y");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b/x/y", "/a/b"), "x/y");
UASSERT_SELFTEST(const string, filenameRelativePath("/a/b/x/y", "/a/q"), "../b/x/y");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b", "a/b"), ".");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b", "a/b/c"), "..");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b", "a/b/c/d"), "../..");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b/x", "a/b/c/d"), "../../x");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b/x/y", ""), "a/b/x/y");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b/x/y", "a/b"), "x/y");
UASSERT_SELFTEST(const string, filenameRelativePath("a/b/x/y", "a/q"), "../b/x/y");
UASSERT_SELFTEST(filenameCleanup(""), "");
UASSERT_SELFTEST(filenameCleanup("."), ".");
UASSERT_SELFTEST(filenameCleanup(".."), "..");
UASSERT_SELFTEST(filenameCleanup("/"), "/");
UASSERT_SELFTEST(filenameCleanup("../"), "..");
UASSERT_SELFTEST(filenameCleanup("//"), "/");
UASSERT_SELFTEST(filenameCleanup("//."), "/.");
UASSERT_SELFTEST(filenameCleanup("./"), ".");
UASSERT_SELFTEST(filenameCleanup("././"), ".");
UASSERT_SELFTEST(filenameCleanup(".///"), ".");
UASSERT_SELFTEST(filenameCleanup("a"), "a");
UASSERT_SELFTEST(filenameCleanup("a/"), "a");
UASSERT_SELFTEST(filenameCleanup("a/b"), "a/b");
UASSERT_SELFTEST(filenameCleanup("././//./a/b"), "a/b");
UASSERT_SELFTEST(filenameCleanup(".//./a///"), "a");
UASSERT_SELFTEST(filenameCleanup("///a/./b///."), "/a/./b/.");
UASSERT_SELFTEST(filenameCleanup("aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(filenameCleanup("./aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(filenameCleanup("../aaa/bbb/ccc/"), "../aaa/bbb/ccc");
UASSERT_SELFTEST(filenameDir("a.a/b.b/f.e"), "a.a/b.b");
UASSERT_SELFTEST(filenameExt("a.a/b.b/f"), "");
UASSERT_SELFTEST(filenameExt("a.a/b.b/f.e"), ".e");
UASSERT_SELFTEST(filenameNonDirExt("a.a/b.b/f.e"), "f");
UASSERT_SELFTEST(filenameRelativePath("/a/b", "/a/b"), ".");
UASSERT_SELFTEST(filenameRelativePath("/a/b", "/a/b/c"), "..");
UASSERT_SELFTEST(filenameRelativePath("/a/b", "/a/b/c/d"), "../..");
UASSERT_SELFTEST(filenameRelativePath("/a/b/x", "/a/b/c/d"), "../../x");
UASSERT_SELFTEST(filenameRelativePath("/a/b/x/y", "/"), "a/b/x/y");
UASSERT_SELFTEST(filenameRelativePath("/a/b/x/y", "/a/b"), "x/y");
UASSERT_SELFTEST(filenameRelativePath("/a/b/x/y", "/a/q"), "../b/x/y");
UASSERT_SELFTEST(filenameRelativePath("a/b", "a/b"), ".");
UASSERT_SELFTEST(filenameRelativePath("a/b", "a/b/c"), "..");
UASSERT_SELFTEST(filenameRelativePath("a/b", "a/b/c/d"), "../..");
UASSERT_SELFTEST(filenameRelativePath("a/b/x", "a/b/c/d"), "../../x");
UASSERT_SELFTEST(filenameRelativePath("a/b/x/y", ""), "a/b/x/y");
UASSERT_SELFTEST(filenameRelativePath("a/b/x/y", "a/b"), "x/y");
UASSERT_SELFTEST(filenameRelativePath("a/b/x/y", "a/q"), "../b/x/y");
#endif
}
+58
View File
@@ -961,6 +961,12 @@ class ParamProcessor final {
} else {
newModp = srcModp->cloneTree(false);
}
// AstPin normally retains links to external module formals across cloning. For a cloned
// interface, relink pins whose formals were cloned with the interface while clonep() is
// still valid, so nested parameterized classes use the cloned interface parameters.
if (AstIface* const newIfacep = VN_CAST(newModp, Iface)) {
newIfacep->foreach([](AstPin* pinp) { pinp->cloneRelinkGen(); });
}
// Mark the source module as a parameterized template now that a specialized
// clone exists. This suppresses width/type errors on the unresolved template
@@ -2361,6 +2367,22 @@ class ParamClassRefDTypeRelinkVisitor final : public VNVisitor {
}
}
// A class's name before specialization (e.g. "holder" for "holder__Tz1").
static string classOrigName(const AstClass* classp) {
return classp->origName().empty() ? classp->name() : classp->origName();
}
// Find 'name' in classp or any base class (findTypedefInModule() searches
// only the class itself).
static AstTypedef* findTypedefWithBases(AstClass* classp, const string& name) {
for (AstClass* cp = classp; cp; cp = cp->extendsp() ? cp->extendsp()->classp() : nullptr) {
if (AstTypedef* const tdp = V3LinkDotIfaceCapture::findTypedefInModule(cp, name)) {
if (tdp->subDTypep()) return tdp;
}
}
return nullptr;
}
// Re-resolve REFDTYPE.typedefp/refDTypep using the containing module's
// own resolved typedef chain. The eager retargeting in deepCloneModule
// blindly retargets every captured entry to whichever sibling clone is
@@ -2371,10 +2393,46 @@ class ParamClassRefDTypeRelinkVisitor final : public VNVisitor {
// those local typedefs as ground truth.
void retargetRefDType(AstRefDType* refp) {
if (!m_ownerModp) return;
// IEEE 1800-2023 8.25.1: a bare class-qualified reference to the class
// being compiled means the current specialization, but V3LinkDot binds
// it to the default instance, so a typedef reached through it widens
// with the template's defaults (#8348). Rebind it to the
// specialization's own typedef.
if (AstClass* const ownerClassp = VN_CAST(m_ownerModp, Class)) {
if (!ownerClassp->hasGParam() && !refp->paramsp()) {
// What the reference resolves to: its typedef's owner if
// linked, else the class it is qualified by.
const AstClass* refClassp = nullptr;
if (AstTypedef* const tdp = refp->typedefp()) {
refClassp = VN_CAST(V3LinkDotIfaceCapture::findOwnerModule(tdp), Class);
} else if (const AstClassOrPackageRef* const classRefp
= VN_CAST(refp->classOrPackageOpp(), ClassOrPackageRef)) {
refClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class);
}
// Same originating class => bare self reference (another
// specialization would carry #(), rejected above).
if (refClassp && refClassp != ownerClassp
&& classOrigName(refClassp) == classOrigName(ownerClassp)) {
AstTypedef* const selfTdp = findTypedefWithBases(ownerClassp, refp->name());
if (selfTdp) {
UINFO(9, "post-param REFDTYPE self-reference retarget: "
<< refp << " from " << refClassp->name() << " to "
<< ownerClassp->name());
refp->typedefp(selfTdp);
refp->classOrPackagep(V3LinkDotIfaceCapture::findOwnerModule(selfTdp));
refp->refDTypep(selfTdp->subDTypep());
return;
}
}
}
}
AstTypedef* const oldTdp = refp->typedefp();
if (!oldTdp) return;
AstClass* const oldOwnerp = VN_CAST(V3LinkDotIfaceCapture::findOwnerModule(oldTdp), Class);
if (!oldOwnerp) return;
ensureOwnerMap();
if (m_origNameToClone.empty()) return;
const std::string origName
+1 -1
View File
@@ -291,7 +291,7 @@ public:
size_t flexPpInputToLex(char* buf, size_t max_size) { return ppInputToLex(buf, max_size); }
//==== Symbol tables
AstPackage* unitPackage(FileLine* /*fl*/) { return parsep()->rootp()->dollarUnitPkgAddp(); }
AstPackage* unitPackage(FileLine* /*fl*/) { return parsep()->rootp()->dollarUnitPkgp(); }
// CONSTRUCTORS
V3ParseImp(AstNetlist* rootp, VInFilter* filterp)
+1 -1
View File
@@ -159,7 +159,7 @@ class V3PreExpr final {
}
}
const bool got = parser.result();
UASSERT_SELFTEST(const bool, got, expect);
UASSERT_SELFTEST(got, expect);
}
// METHODS
+557 -465
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -293,7 +293,6 @@ class ReorderVisitor final : public VNVisitor {
for (AstNode* currp = nodep; currp; currp = currp->nextp()) {
ReorderLogicVertex* const vtxp = currp->user3u().to<ReorderLogicVertex*>();
const uint32_t color = vtxp->color();
UASSERT_OBJ(color, currp, "No node color assigned");
if (lastOfColor[color]) new ReorderStrictEdge{m_graphp, lastOfColor[color], vtxp};
lastOfColor[color] = vtxp;
}
@@ -321,7 +320,7 @@ class ReorderVisitor final : public VNVisitor {
// Is the current ordering OK?
bool leaveAlone = true;
int newOrder = 0; // New sequence number of assignment
uint64_t newOrder = 0; // New sequence number of assignment
for (const auto& item : rankMap) {
const AstNode* const nextp = item.second;
if (++newOrder != nextp->user4()) leaveAlone = false;

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