mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 18:13:50 +02:00
Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ed3b46dba | ||
|
|
6da2657b56 | ||
|
|
0f3a84643b | ||
|
|
253f51f1dc | ||
|
|
270c528afd | ||
|
|
c65f312d6e | ||
|
|
377865e2a0 | ||
|
|
6a57cf10f4 | ||
|
|
96e6bf02c4 | ||
|
|
0bad0faca6 | ||
|
|
547eb6a836 | ||
|
|
e172e94b30 | ||
|
|
e871c3657e | ||
|
|
3200946bb9 | ||
|
|
fb8bcdc0df | ||
|
|
47ead4e89d | ||
|
|
298922d887 | ||
|
|
4fedb791ed | ||
|
|
3bf0018354 | ||
|
|
d523500b9b | ||
|
|
a8a50bd8a0 | ||
|
|
c7b1151f13 | ||
|
|
73957647cc | ||
|
|
d048b62c13 | ||
|
|
c8a75e9b4d | ||
|
|
012b647833 | ||
|
|
005b33faa1 | ||
|
|
0c574618d0 | ||
|
|
4aeb3dbc5a | ||
|
|
bd32c0e4c1 | ||
|
|
72523c3be1 | ||
|
|
2421c7b73b | ||
|
|
dada720bca | ||
|
|
b06bd46dc9 | ||
|
|
5b24226b3b | ||
|
|
09fd15af7b | ||
|
|
ee792e235a | ||
|
|
557c21a337 | ||
|
|
24692ad3ef | ||
|
|
3c8630744d | ||
|
|
0dd45238a7 | ||
|
|
1d2781091c | ||
|
|
5bf74e34b1 | ||
|
|
599f8b8e35 | ||
|
|
da39cea1bd | ||
|
|
3e874fb4d1 | ||
|
|
e4f210eb38 | ||
|
|
5bb0e9b216 | ||
|
|
5ce1abc2b4 | ||
|
|
b835308a31 | ||
|
|
fa30985cda | ||
|
|
f8c296ce2e | ||
|
|
47b9fba5e5 | ||
|
|
d70d1e5cd0 | ||
|
|
76df8b4a7e | ||
|
|
048d18a84f | ||
|
|
cd2475fbd4 | ||
|
|
9c6126b834 | ||
|
|
2bffeec8b9 | ||
|
|
63bcfff80a | ||
|
|
9da900a5e2 | ||
|
|
bbbb9c3e7c | ||
|
|
849cc5aeca | ||
|
|
8213c0ea98 | ||
|
|
62bec1bffc | ||
|
|
0b824f160f | ||
|
|
4d1f208cf0 | ||
|
|
7cf8c5cca6 | ||
|
|
73708c55c3 | ||
|
|
3990376c57 | ||
|
|
d04647f459 | ||
|
|
338c6c2885 | ||
|
|
a6e21e0eb7 | ||
|
|
f2fe2b6f2e | ||
|
|
bcf613c4fe | ||
|
|
b907a1a75d | ||
|
|
87108ff943 | ||
|
|
fa59f8f302 | ||
|
|
35c1a244a8 | ||
|
|
3493e409b8 | ||
|
|
4f557a4b0c | ||
|
|
13c60bfede | ||
|
|
d6441c9cd3 | ||
|
|
32695cf8be | ||
|
|
5ab54f3c01 | ||
|
|
c1c19494c4 | ||
|
|
e7b30e214e | ||
|
|
81fafe237e | ||
|
|
80de081af0 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
$*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+285
-24
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -198,11 +198,6 @@ void V3Graph::userClearEdges() {
|
||||
}
|
||||
}
|
||||
|
||||
void V3Graph::clearColors() {
|
||||
// Reset colors
|
||||
for (V3GraphVertex& vertex : vertices()) vertex.color(0);
|
||||
}
|
||||
|
||||
//======================================================================
|
||||
// Dumping
|
||||
|
||||
|
||||
+2
-4
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -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
Reference in New Issue
Block a user