Compare commits

...
54 Commits
Author SHA1 Message Date
Wilson Snyder d0aa828c21 Version bump 2026-04-26 01:52:06 -04:00
Geza Lore 42ea9fda17 Commentary: Changes update 2026-04-25 17:56:03 +01:00
Wilson Snyder 00211c290c Commentary 2026-04-25 11:41:30 -04:00
Wilson Snyder 5c989025c7 Commentary: Changes update 2026-04-25 11:26:51 -04:00
github action d1c0184916 Apply 'make format' 2026-04-25 15:17:45 +00:00
M2kar 07d91fb253 Fix infinite recursion with VERILATOR_BIN (#7496) (#7497)
Fixes #7496.
2026-04-25 17:16:50 +02:00
Wilson Snyder 555d7cc043 Tests: Remove old .out files and related cleanups 2026-04-24 12:25:28 -04:00
Wilson Snyder b8d59ed7a8 Commentary: Changes update 2026-04-24 12:22:26 -04:00
em2machine b1d3d63e9f Fix for unction argument expects a class refetence (#7483) (#7489)
Fixes #7483.
2026-04-24 10:31:57 +02:00
Wilson Snyder 2cd9b8df66 Fix multiple inclusion of verilated_std.sv (#7478). 2026-04-23 15:32:02 -04:00
Wilson Snyder 64b91023b1 Commentary: Changes update 2026-04-23 12:47:22 -04:00
em2machine 121fd3f613 Fix module parameters not re-evaluated upon instantiation (#7463) (#7477) 2026-04-23 12:30:42 -04:00
Geza Lore ba3937734f Optimize mix of Concat/Extend assignments (#7479)
Generalize concatenation splitting/balancing in V3FuncOpt to also handle
AstExtend (which is a Concat with a 0 LHS).
2026-04-23 16:57:43 +01:00
Alex Zhou 7e0f25b41b Fix access to static variable inside function (#7474) (#7475)
Fixes #7474.
2026-04-23 11:17:14 -04:00
Yilou Wang 27e7fdc9eb Fix std::randomize inside {typedef array} internal error (#7481)
Fixes #7481.
2026-04-23 11:16:24 -04:00
Yilou Wang ae642facf5 Fix function-in-constraint internal error on bare port VarRef return (#7473) (#7480)
Fixes #7473.
2026-04-23 10:47:39 -04:00
Geza Lore 3149f43372 Internals: Add special Dfg vertex for replication (#7471) 2026-04-23 10:14:27 +01:00
Yilou Wangandgithub action cfebe805c9 Support implication operator with constraint_set (#7300) (#7448)
* Support implication operator with constraint_set

* improve coverage, achieve 100 line cov

* Address review: simplify addNext; tighten disable soft grammar to constraint_primary

* Apply 'make format'

* re-run

---------

Co-authored-by: github action <[email protected]>
2026-04-23 10:50:23 +02:00
github action 46a6884df6 Apply 'make format' 2026-04-23 07:14:49 +00:00
Yogish Sekhar 72bbccb543 Add Fsm coverage to print summary (#7462) 2026-04-23 09:13:45 +02:00
Wilson Snyder c1d1b333ac With -Wpendantic allow circular assigns 2026-04-23 00:50:12 -04:00
Wilson Snyder 5064a5ee65 Commentary: Changes update 2026-04-23 00:44:50 -04:00
Wilson Snyder 6945f04531 Internals: Use addNext. No functional change intended 2026-04-23 00:34:47 -04:00
Yilou Wang 51eb5346c8 Support sequence within operator (#7461) 2026-04-23 05:47:24 +02:00
github action e4da16caf0 Apply 'make format' 2026-04-22 19:20:00 +00:00
Yogish Sekhar a680919edc Support native FSM state and arc coverage (#7412) 2026-04-22 15:18:59 -04:00
github action 41ddf4d9b6 Apply 'make format' 2026-04-22 16:13:36 +00:00
Nikolay Puzanov 5cddbd7fda Fix tracing virtual interface member written from classes (#5044) (#7465)
Track AstMemberSel writes through virtual interface refs and connect them to matching interface-member VarScopes, so class-driven interface clocks get proper VCD activity updates.

Fixes #5044.
2026-04-22 12:09:30 -04:00
Geza Lore 496665800d Internals: Annotate AstNode classes to generate matching DfgVertex (#7469)
Explicitly annotate those AstNodeExpr subclasses that should have a
corresponding DfgVertex subclass generated by astgen. This avoids having
to tweak things in Dfg when adding new AstNode subclasses, which can
then be handled separately.
2026-04-22 16:35:07 +01:00
em2machine 59fe8066ff Support multidimensional arrays of interfaces (#6230) (#7451)
Fixes #6230
2026-04-22 13:42:10 +02:00
Todd Strader 15163d1e39 Tests: Switch VCD/FST compare to wavediff (#7426) 2026-04-21 13:53:53 -04:00
Geza Lore 686594b4ab Optimize temporary insertion in Dfg (#7459)
Insert temporaries for highest fanout vertices first. Omit them for
cheap comparisons.
2026-04-21 18:34:26 +01:00
Artur Bieniek a1a8b9624c Support IEEE-compliant force/release handling (#7391) 2026-04-21 11:54:42 -04:00
Yilou Wang 280cff06f3 Fix std::randomize inside corrupting class-member queue operand (#7449) (#7456)
Fixes #7449
2026-04-21 17:37:58 +02:00
github action c60d8f002f Apply 'make format' 2026-04-21 12:22:33 +00:00
Yogish Sekhar bca5839d46 Add printed summary to verilator_coverage (#7438) 2026-04-21 14:21:38 +02:00
Geza Lore 2b9d006097 Change Dfg pattern dumps to use --dump-dfg-patterns (#7455)
Dumping Dfg patterns can take a non-trivial amount of time, so do it
only with --dump-dfg-patterns, instead of with --stats.
Also further improve dumping format.
2026-04-21 12:07:19 +01:00
Wilson Snyder 2ef979a39c Fix dead removing packages with only DPI. 2026-04-21 00:34:20 -04:00
Wilson Snyder 23ea3d7f11 Commentary: Changes update 2026-04-21 00:33:40 -04:00
Wilson Snyder 94e3f387a7 Internals: With debug, show cwd 2026-04-20 14:06:01 -04:00
Geza Lore cd30c22d1c Optimize more Dfg patterns (#7452) 2026-04-20 18:28:11 +01:00
dependabot[bot] 81f6cdc32c CI: Bump actions/upload-pages-artifact from 4 to 5 in the everything group (#7453) 2026-04-20 19:16:37 +02:00
em2machine 23ca23b7b5 Fix for ariane/CVA6 false member call on object error (#7445) (#7450)
Fixes #7445.
2026-04-20 18:21:59 +02:00
Geza Lore f3c63d017a Optimize Sel patterns in DfgPeephole 2026-04-20 09:01:31 +01:00
Geza Lore ffd2c5c69e Optimize temporary insertion in DfgPeephole 2026-04-20 09:01:31 +01:00
Geza Lore 93d7d9c417 Optimize DfgDataType::hash() 2026-04-20 09:01:31 +01:00
Yilou Wang 935b2564eb Use NFA in SVA pass (V3AssertNfa: NFA-based multi-lcycle SVA evaluation engine) (#7430) 2026-04-20 07:43:18 +02:00
Geza Lore e82bd52fa3 Optimize arithmetic right shift (>>>) in DfgBreakCycles (#7447) 2026-04-19 20:28:17 +01:00
Geza Lore 60b41c7822 Optimize DfgBreakCycles IndependentBits analysis ordering (#7446)
Perform the data flow analysis based on reverse post-order numbering of
vertices. This speeds up convergence by ~10x. Partial fix for #7201.
2026-04-19 19:48:02 +01:00
Geza Lore 97454a1bc5 Remove multi-threaded FST tracing (#7443)
Remove parallel (using the FST library writer thread) and offloaded
(separate Verilator internal thread) tracing (only used by FST). These
are not compatible with #6992, and #5806 should yield better performance
in all cases.

Consequently mark '--trace-threads' and '--trace-fst-thread' options as
deprecated
2026-04-19 16:02:12 +01:00
Christian Hecken f7a349c5a7 Fix uvm_hdl_release_and_read not reading release value or checking for success (#7425) 2026-04-19 08:24:07 -04:00
Yilou Wang 29a93fe5bc Fix internal error on dist under implication operator in constraints (#7440) (#7442)
Fixes #7440
2026-04-19 08:03:19 +02:00
Wilson Snyder 707dcea914 Commentary/Tests: Describe PARAMNODEFAULT as top.
Fixes #7441.
2026-04-18 11:34:19 -04:00
Wilson Snyder 0537999b1d Commentary: Changes update 2026-04-18 11:27:54 -04:00
363 changed files with 13279 additions and 4885 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
ls -lsha
tree -L 3 pages
- name: Upload pages artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v5
with:
path: pages
+1 -1
View File
@@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
project(
Verilator
VERSION 5.047
VERSION 5.048
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)
+47 -18
View File
@@ -8,18 +8,36 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.047 devel
Verilator 5.048 2026-04-26
==========================
**Important:**
* Displaying Verilator generated FST files will require an updated version
of Surfer (built from 'main', or the future numbered release 0.7.0).
No GTKWave update is necessary (#7255).
* Support jemalloc as the default allocator on Linux (#7250). [Yangyu Chen]
For better performance, `apt install libjemalloc-dev` or equivalent before running ./configure.
**Other:**
* Add VPI callback support to `--main` (#7145).
* Add `--func-recursion-depth` option (#7175) (#7179).
* Add `+verilator+solver+file` for debugging constraint solver (#7242).
* Add `--coverage-fsm` for experimental FSM state and arc coverage (#7412). [Yogish Sekhar]
* Add printed summary to verilator_coverage (#7438) (#7462). [Yogish Sekhar]
* Deprecate `--structs-packed` (#7222).
* Remove DFG extract optimization pass (#7394). [Geza Lore, Testorrent USA, Inc.]
* Remove multi-threaded FST tracing (#7443). [Geza Lore, Testorrent USA, Inc.]
* Improve assignment-compatibility type check (#2843) (#5666) (#7052). [Pawel Kojma, Antmicro Ltd.]
* Improve error message when variable used as data type (#7318). [Ryszard Rozak, Antmicro Ltd.]
* Improve E_UNSUPPORTED warning messages (#7329). [Eunseo Song]
* Improve NFA-based multi-cycle SVA evaluation engine (#7430). [Yilou Wang]
* Change array tracing to dump left index to right index (#7205). [Geza Lore, Testorrent USA, Inc.]
* Change `--converge-limit` default to 10000 (#7209).
* Support inout inside SV interface (#3466) (#7134). [Nick Brereton]
* Support `##0` cycle delays (#4263) (#7298). [Yilou Wang]
* Support multidimensional arrays of interfaces (#6230) (#7451). [em2machine]
* Support array reduction methods with 'with' clause in constraints (#6455) (#6999). [Rahul Behl]
* Support constraint imperfect distributions (#6811) (#7168). [Yilou Wang]
* Support disable task by name (#6853) (#7136). [Nick Brereton]
@@ -39,14 +57,15 @@ Verilator 5.047 devel
* Support modport export/import task prototypes and out-of-block definitions (#7277). [Yilou Wang]
* Support very wide $display arguments (#7280). [Jakub Michalski]
* Support named sequence declarations and instances in assertions (#7283). [Yilou Wang]
* Support `##` delay on implication RHS in SVA properties (#7284). [Yilou Wang]
* Support `##`, `##[*]`, `##[+]`, `##[M:N]` cycle delays in assertions (#7284) (#7312) (#7377). [Yilou Wang]
* Support boolean and/or in sequence expressions (#7285). [Yilou Wang]
* Support property-local variables and sequence match items (#7286). [Yilou Wang]
* Support 'until' property (partial #7290) (#7399). [Ryszard Rozak, Antmicro Ltd.]
* Support 'until' and `until_with` property (#7290 partial) (#7399) (#7436). [Ryszard Rozak, Antmicro Ltd.]
* Support implication operator with constraint_set (#7300) (#7448). [Yilou Wang]
* Support array map() method (#7307) (#7316) (#7344). [Wei-Lun Chiu]
* Support MacOS address sanitizer memory limit (#7308). [Marco Bartoli]
* Support SVA goto repetition `[->N]` in concurrent assertions (#7310). [Yilou Wang]
* Support consecutive repetition `[\*N]` in SVA properties (#7311). [Yilou Wang]
* Support `##[*]`, `##[+]`, `##[M:N]`, `##[M:$]` cycle delays in assertions (#7312) (#7377). [Yilou Wang]
* Support rise/fall delays (#7368). [Artur Bieniek, Antmicro Ltd.]
* Support sequence intersect operator (#7374). [Yilou Wang]
* Support sequence 'throughout' operator (#7378). [Yilou Wang]
@@ -55,19 +74,10 @@ Verilator 5.047 devel
* Support nonconsecutive repetition [=N] in sequence expressions (#7397). [Yilou Wang]
* Support per-process RNG for process::srandom() and object seeding (#7408) (#7415) (#7408). [Yilou Wang]
* Support 2**n expressions in constraint randomization (#7422). [Yilou Wang]
* Add VPI callback support to --main (#7145).
* Add V3LiftExpr pass to lower impure expressions and calls (#7141) (#7164). [Geza Lore, Testorrent USA, Inc.]
* Add --func-recursion-depth CLI option (#7175) (#7179).
* Add `+verilator+solver+file` (#7242).
* Add MacOS support for address sanitizer memory limit (#7308). [Marco Bartoli]
* Deprecate `--structs-packed` (#7222).
* Improve assignment-compatibility type check (#2843) (#5666) (#7052). [Pawel Kojma, Antmicro Ltd.]
* Improve error message when variable used as data type (#7318). [Ryszard Rozak, Antmicro Ltd.]
* Improve E_UNSUPPORTED warning messages (#7329). [Eunseo Song]
* Change array tracing to dump left index to right index (#7205). [Geza Lore, Testorrent USA, Inc.]
* Change `--converge-limit` default to 10000 (#7209).
* Remove DFG extract optimization pass (#7394). [Geza Lore, Testorrent USA, Inc.]
* Support IEEE-compliant force/release handling (#7391). [Artur Bieniek, Antmicro Ltd.]
* Support sequence 'within' operator (#7461). [Yilou Wang]
* Optimize trace code for faster compiles on repeated types (#6707) (#6832). [Todd Strader]
* Optimize impure expressions and calls with new V3LiftExpr pass (#7141) (#7164). [Geza Lore, Testorrent USA, Inc.]
* Optimize size of trace declaration object code (#7150). [Szymon Gizler, Antmicro Ltd.]
* Optimize function call return value temporaries (#7152). [Geza Lore, Testorrent USA, Inc.]
* Optimize conditional merging across some impure statements (#7159). [Geza Lore, Testorrent USA, Inc.]
@@ -79,11 +89,18 @@ Verilator 5.047 devel
* Optimize more patterns in DfgPeephole (#7332). [Geza Lore, Testorrent USA, Inc.]
* Optimize read references in DFG (#7354). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG only once, after scoping (#7362). [Geza Lore, Testorrent USA, Inc.]
* Optimize more DFG peephole patterns (#7423). [Geza Lore, Testorrent USA, Inc.]
* Optimize more DFG peephole patterns (#7423) (#7452). [Geza Lore, Testorrent USA, Inc.]
* Optimize DfgBreakCycles IndependentBits analysis ordering (#7446). [Geza Lore, Testorrent USA, Inc.]
* Optimize select patterns in DfgPeephole. [Geza Lore, Testorrent USA, Inc.]
* Optimize temporary insertion in DfgPeephole. [Geza Lore, Testorrent USA, Inc.]
* Optimize arithmetic right shift (>>>) in DfgBreakCycles (#7447). [Geza Lore, Testorrent USA, Inc.]
* Optimize temporary insertion in DFG (#7459). [Geza Lore, Testorrent USA, Inc.]
* Optimize mix of Concat/Extend assignments (#7479). [Geza Lore, Testorrent USA, Inc.]
* Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier]
* Fix tracing virtual interface member written from classes (#5044) (#7465). [Nikolay Puzanov]
* Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang]
* Fix shift width mismatch in constraint solver SMT emission (#5420) (#7265). [Yilou Wang]
* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418). [em2machine]
* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418) (#7445) (#7450). [em2machine]
* Fix randomize size+element queue constraints (#5582) (#7225). [Rahul Behl, Testorrent USA, Inc.]
* Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev]
* Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton]
@@ -150,6 +167,18 @@ Verilator 5.047 devel
* Fix delete inside foreach skipping elements (#7407) (#7410)
* Fix std::randomize in parameterized-derived class (#7409) (#7416). [Yilou Wang]
* Fix virtual interface implied comparison with null (#7421). [Alex Solomatnikov]
* Fix uvm_hdl_release_and_read to release value and check success (#7425). [Christian Hecken, Heidelberg University]
* Fix inline constraint on array-indexed randomize target (#7431) (#7434). [Yilou Wang]
* Fix modification of members of object with const handle (#7433). [Kamil Danecki, Antmicro Ltd.]
* Fix `dist` under implication in constraints (#7440) (#7442). [Alex Solomatnikov] [Yilou Wang]
* Fix std::randomize `inside` corrupting class-member queue operand (#7449) (#7456). [Yilou Wang]
* Fix module parameters not re-evaluated upon instantiation (#7463) (#7477). [em2machine]
* Fix function-in-constraint internal error on bare port return (#7473) (#7480). [Yilou Wang]
* Fix access to static variable inside function (#7474) (#7475). [Alex Zhou]
* Fix multiple inclusion of verilated_std.sv (#7478). [Larry Doolittle]
* Fix std::randomize inside {typedef array} internal error (#7481). [Yilou Wang]
* Fix module parameters not re-evaluated upon instantiation (#7463) (#7477). [em2machine]
* Fix infinite recursion with VERILATOR_BIN (#7496) (#7497).
Verilator 5.046 2026-02-28
+1
View File
@@ -15,6 +15,7 @@ path = [
".style.yapf",
"CITATION.cff",
"CPPLINT.cfg",
"ci/docker/buildenv/wavetools.conf",
"docs/CONTRIBUTORS",
"docs/spelling.txt",
"docs/verilated.dox",
+17 -1
View File
@@ -27,6 +27,21 @@ use vars qw($Debug @Opt_Verilator_Sw);
autoflush STDOUT 1;
autoflush STDERR 1;
# Guard against unbounded recursive re-entry. Hierarchical / --build flows
# legitimately re-enter the wrapper a few levels deep (wrapper -> verilator_bin
# -> make -> wrapper -> ...); a misconfigured VERILATOR_BIN, VERILATOR_GDB or
# VERILATOR_VALGRIND that points back at this wrapper, however, recurses
# without bound and exhausts the OS PID table. Use VERILATOR_RUNNING as a
# depth counter and cap it well above any real nesting.
my $verilator_running_max = 16;
my $verilator_running_depth = ($ENV{VERILATOR_RUNNING} || 0) + 1;
if ($verilator_running_depth > $verilator_running_max) {
die "%Error: verilator: re-entered $verilator_running_depth levels deep"
. " via \$VERILATOR_RUNNING; check that VERILATOR_BIN, VERILATOR_GDB"
. " and VERILATOR_VALGRIND do not resolve back to this wrapper script.\n";
}
$ENV{VERILATOR_RUNNING} = $verilator_running_depth;
$Debug = 0;
my $opt_aslr;
my $opt_gdb;
@@ -358,6 +373,7 @@ detailed descriptions of these arguments.
--coverage Enable all coverage
--coverage-expr Enable expression coverage
--coverage-expr-max <value> Maximum permutations allowed for an expression
--coverage-fsm Enable FSM state/arc coverage
--coverage-line Enable line coverage
--coverage-max-width <width> Maximum array depth for coverage
--coverage-toggle Enable toggle coverage
@@ -379,6 +395,7 @@ detailed descriptions of these arguments.
--dump-<srcfile> Enable dumping everything in source file
--dump-defines Show preprocessor defines with -E
--dump-dfg Enable dumping DfgGraphs to .dot files
--dump-dfg-patterns Enable dumping Dfg pattern statistics
--dump-graph Enable dumping V3Graphs to .dot files
--dump-inputs Enable dumping preprocessed input files
--dump-tree Enable dumping Ast .tree files
@@ -526,7 +543,6 @@ detailed descriptions of these arguments.
--trace-params Enable tracing of parameters
--trace-saif Enable SAIF file creation
--trace-structs Enable tracing structure names
--trace-threads <threads> Enable FST waveform creation on separate threads
--no-trace-top Do not emit traces for signals in the top module generated by verilator
--trace-underscore Enable tracing of _signals
--trace-vcd Enable VCD waveform creation
+1
View File
@@ -175,6 +175,7 @@ L<https://verilator.org/guide/latest/exe_verilator_coverage.html>.
--annotate-points Annotates info from each coverage point.
--filter-type <regex> Keep only records of given coverage type.
--help Displays this message and version and exits.
--include-reset-arcs Include reset arcs in FSM arc summaries.
--rank Compute relative importance of tests.
--unlink With --write, unlink all inputs
--version Displays program version and exits.
+27 -7
View File
@@ -40,12 +40,32 @@ if [ "$CI_OS_NAME" = "linux" ]; then
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
fi
install-vcddiff() {
TMP_DIR="$(mktemp -d)"
git clone https://github.com/veripool/vcddiff "$TMP_DIR"
git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5
"$MAKE" -C "${TMP_DIR}"
sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin
install-wavediff() {
source ci/docker/buildenv/wavetools.conf
local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
local _platform
if [ "$CI_OS_NAME" = "linux" ]; then
_platform="linux-x86_64"
elif [ "$CI_OS_NAME" = "osx" ]; then
_platform="macos-arm64"
elif [ "$CI_OS_NAME" = "windows" ]; then
_platform="windows-x86_64"
else
echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping"
return 0
fi
local _tmpdir
_tmpdir=$(mktemp -d)
local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}"
if [ "$CI_OS_NAME" = "windows" ]; then
wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip"
unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}"
else
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
fi
sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff
rm -rf "${_tmpdir}"
}
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
@@ -120,7 +140,7 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
# Common installs
install-vcddiff
install-wavediff
# Workaround -fsanitize=address crash
sudo sysctl -w vm.mmap_rnd_bits=28
else
+8 -4
View File
@@ -53,10 +53,14 @@ RUN apt-get update \
WORKDIR /tmp
RUN git clone https://github.com/veripool/vcddiff.git && \
make -C vcddiff && \
cp -p vcddiff/vcddiff /usr/local/bin/vcddiff && \
rm -rf vcddiff
COPY wavetools.conf /tmp/wavetools.conf
ARG WGET_EXTRA_ARGS=
RUN . /tmp/wavetools.conf && \
wget -q ${WGET_EXTRA_ARGS} -O /tmp/wavetools.tar.gz \
"https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}/wavetools-${WAVETOOLS_VERSION}-linux-x86_64.tar.gz" && \
tar -xzf /tmp/wavetools.tar.gz -C /tmp && \
cp -p /tmp/wavetools-${WAVETOOLS_VERSION}-linux-x86_64/wavediff /usr/local/bin/wavediff && \
rm -rf /tmp/wavetools.tar.gz /tmp/wavetools-* /tmp/wavetools.conf
COPY build.sh /tmp/build.sh
+1
View File
@@ -0,0 +1 @@
WAVETOOLS_VERSION=v0.1.2
+1 -1
View File
@@ -12,7 +12,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.047 devel],
AC_INIT([Verilator],[5.048 2026-04-26],
[https://verilator.org],
[verilator],[https://verilator.org])
+3
View File
@@ -58,6 +58,7 @@ Drew Ranck
Drew Taussig
Driss Hafdi
Edgar E. Iglesias
Eric Mejdrich
Eric Müller
Eric Rippey
Eunseo Song
@@ -164,6 +165,7 @@ Liam Braun
Luca Colagrande
Ludwig Rogiers
Lukasz Dalek
M2kar
Maarten De Braekeleer
Maciej Sobkowski
Marcel Chang
@@ -302,3 +304,4 @@ em2machine
emmettifelts
Àlex Torregrosa
Ícaro Lima
Yogish Sekhar
+1 -1
View File
@@ -44,7 +44,7 @@ def setup(app):
# -- Project information
project = 'Verilator'
copyright = '2024 by Wilson Snyder, under LGPL-3.0 or Artistic-2.0'
copyright = '2026 by Wilson Snyder, under LGPL-3.0 or Artistic-2.0'
author = 'Wilson Snyder'
# The master toctree document.
+4 -4
View File
@@ -7,10 +7,10 @@ Copyright
The latest version of Verilator is available from https://verilator.org.
Copyright 2003-2026 by Wilson Snyder. Verilator is free software; you
can redistribute it and/or modify the Verilator internals under the terms
of either the GNU Lesser General Public License Version 3 or the Perl
Artistic License Version 2.0.
Copyright 2003-2026 by Wilson Snyder. Verilator is free software; you can
redistribute it and/or modify the Verilator internals under the terms of
either the GNU Lesser General Public License Version 3 or the Perl Artistic
License Version 2.0.
All Verilog and C++/SystemC code quoted within this documentation file is
released as Creative Commons Public Domain (CC0). Many example files and
+1 -1
View File
@@ -1,7 +1,7 @@
.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
.. _Environment:
.. _environment:
Environment
===========
+15 -2
View File
@@ -281,7 +281,8 @@ Summary:
.. option:: --coverage
Enables all forms of coverage, an alias for :vlopt:`--coverage-line`
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-user`.
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-fsm`
:vlopt:`--coverage-user`.
.. option:: --coverage-expr
@@ -293,6 +294,10 @@ Summary:
covered for a given expression. Defaults to 32. Increasing may slow
coverage simulations and make analyzing the results unwieldy.
.. option:: --coverage-fsm
Enables native FSM state and arc coverage. See :ref:`FSM Coverage`.
.. option:: --coverage-line
Enables basic block line coverage analysis. See :ref:`Line Coverage`.
@@ -478,6 +483,10 @@ Summary:
Rarely needed. Enable dumping DfgGraph .dot debug files with dumping
level 3.
.. option:: --dump-dfg-patterns
Rarely needed. Enable dumping DfgGraph pattern statistics.
.. option:: --dump-graph
Rarely needed. Enable dumping V3Graph .dot debug files with dumping
@@ -1825,7 +1834,7 @@ Summary:
.. option:: --trace-fst
Enable FST waveform tracing in the model. This overrides
:vlopt:`--trace`. See also :vlopt:`--trace-threads` option.
:vlopt:`--trace`.
.. option:: --trace-max-array <depth>
@@ -1859,6 +1868,10 @@ Summary:
.. option:: --trace-threads <threads>
Deprecated and has no effect.
In versions before 5.048:
Enable waveform tracing using separate threads. This is typically faster
in simulation runtime but uses more total compute. This option only
applies to :vlopt:`--trace-fst`. FST tracing can utilize at most
+9 -2
View File
@@ -129,13 +129,20 @@ verilator_coverage Arguments
.. option:: --filter-type <regex>
Skips records of coverage types that matches with <regex>
Possible values are `toggle`, `line`, `branch`, `expr`, `user` and
a wildcard with `\*` or `?`. The default value is `\*`.
Possible values are `toggle`, `line`, `branch`, `expr`, `user`,
`fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default
value is `\*`.
.. option:: --help
Displays a help summary, the program version, and exits.
.. option:: --include-reset-arcs
Includes FSM reset arcs in the printed summaries and annotated output.
By default, reset arcs are tracked but summarized separately from the
non-reset FSM arcs.
.. option:: --rank
Prints an experimental report listing the relative importance of each
+42
View File
@@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables
all forms of coverage:
- :ref:`User Coverage`
- :ref:`FSM Coverage`
- :ref:`Line Coverage`
- :ref:`Toggle Coverage`
@@ -208,6 +209,47 @@ point under the coverage name "DefaultClock":
DefaultClock: cover property (@(posedge clk) cyc==3);
.. _fsm coverage:
FSM Coverage
------------
With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can
instrument a conservative subset of single-process FSMs and report both
state coverage (`fsm_state`) and transition coverage (`fsm_arc`).
This feature is currently experimental and might change in subsequent
releases. In particular, the native FSM coverage extraction heuristics,
:vlopt:`--coverage-fsm`, and the Verilator-specific FSM metacomments below
should be treated as subject to change while the interface settles.
FSM extraction is intentionally narrow. The current implementation targets
clocked, enum-driven state machines that can be recovered directly from the
RTL. It does not claim broad support for two-process FSMs, one-hot
inference, helper-function next-state recovery, or deeply nested control
recovery.
The following metacomments may be attached to the state variable to steer
the extracted coverage model:
- ``/*verilator fsm_state*/`` forces the variable to be treated as
FSM state.
- ``/*verilator fsm_reset_arc*/`` marks reset transitions as
user-visible reset arcs instead of defaulting to a hidden reset-only
summary.
- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch
arcs that would otherwise be skipped by the conservative extractor.
Reset transitions are included in the collected data either way. By
default, :command:`verilator_coverage` summarizes reset-only arcs rather
than printing them alongside non-reset arcs. Use
:option:`verilator_coverage --include-reset-arcs` to include those arcs in
the printed summary and annotated output.
Annotated output produced by :command:`verilator_coverage --annotate` will
label FSM points with `fsm_state` and `fsm_arc`, and synthetic fallback
transitions with `SYNTHETIC DEFAULT ARC`.
.. _line coverage:
+9 -24
View File
@@ -262,23 +262,6 @@ construction is parallelized using the same number of threads as specified
with :vlopt:`--threads`, and is executed on the same thread pool as the
model.
The :vlopt:`--trace-threads` options can be used with :vlopt:`--trace-fst`
to offload FST tracing using multiple threads. If :vlopt:`--trace-threads`
is given without :vlopt:`--threads`, then :vlopt:`--trace-threads` will
imply :vlopt:`--threads 1 <--threads>`, i.e., the support libraries will be
thread safe.
With :vlopt:`--trace-threads 0 <--trace-threads>`, trace dumps are produced
on the main thread. This again gives the highest single-thread performance.
With :vlopt:`--trace-threads {N} <--trace-threads>`, where N is at least 1,
up to N additional threads will be created and managed by the trace files
(e.g., VerilatedFstC), to offload construction of the trace dump. The main
thread will be released to proceed with execution as soon as possible,
though some main thread blocking is still necessary while capturing the
trace. FST tracing can utilize up to 2 offload threads, so there is no use
of setting :vlopt:`--trace-threads` higher than 2 at the moment.
When running a multithreaded model, the default Linux task scheduler often
works against the model by assuming short-lived threads and thus it often
schedules threads using multiple hyperthreads within the same physical
@@ -286,15 +269,14 @@ core. If there is no affinity already set, on Linux only, Verilator
attempts to set thread-to-processor affinity in a reasonable way.
Some newer Linux kernels handle thread assignment well. If running
Verilator on such a system, automatic thread affinity may not be
beneficial and may even reduce performance. In this case, environment
variable :vlopt:`VERILATOR_NUMA_STRATEGY` may be set to ``none`` to
disable automatic thread affinity. For more information, refer to
Verilator on such a system, automatic thread affinity may not be beneficial
and may even reduce performance. In this case, environment variable
:vlopt:`VERILATOR_NUMA_STRATEGY` may be set to ``none`` to disable
automatic thread affinity. For more information, refer to
:ref:`Environment`.
For best performance, use the :command:`numactl` program to (when the
threading count fits) select unique physical cores on the same socket. The
same applies for :vlopt:`--trace-threads` as well.
threading count fits) select unique physical cores on the same socket.
As an example, if a model was Verilated with :vlopt:`--threads 4
<--threads>`, we consult:
@@ -521,7 +503,10 @@ include directories and link to the SystemC libraries.
.. describe:: TRACE_THREADS
Optional. Enable multithreaded FST trace; see :vlopt:`--trace-threads`.
Deprecated and has no effect.
In versions before 5.048: Optional. Enable multithreaded FST trace; see
:vlopt:`--trace-threads`.
.. describe:: TRACE_VCD
+21 -4
View File
@@ -306,7 +306,7 @@ List Of Warnings
else
array[address] <= data;
While this is supported in typical synthesizeable code (including the
While this is supported in typical synthesizable code (including the
example above), some complicated cases are not supported. Namely:
1. If the above loop is inside a suspendable process or fork statement.
@@ -837,6 +837,18 @@ List Of Warnings
with a newline."
.. option:: FSMMULTI
Warns that the same always block contains multiple enum-typed case
statements that look like FSM candidates for native FSM coverage when
:vlopt:`--coverage-fsm` or :vlopt:`--coverage` is enabled.
Verilator's FSM coverage instruments only the first such candidate in
source order. Split the FSMs into separate always blocks, or explicitly
annotate the intended state variables and restructure the RTL for full
coverage of such multiple state machines.
.. option:: FUNCTIMECTL
Error that a function contains a time-controlling statement or call of a
@@ -1524,7 +1536,12 @@ List Of Warnings
ANSI-style `#(...)` declarations. IEEE 1800-2023 6.20.1 requires this
error, but some simulators accept this syntax.
Faulty example:
Also issued with ANSI format where a parameter without default is
present in the top-level module, and as such Verilator cannot know how
to process that module. For such cases suggest adding a default so the
module can lint cleanly.
Faulty non-ANSI example:
.. include:: ../../docs/gen/ex_PARAMNODEFAULT_faulty.rst
@@ -1534,8 +1551,8 @@ List Of Warnings
To fix the issue, move to an ANSI-style declaration.
Suppressing this error will only suppress the IEEE-required check; it
will simulate correctly.
For the non-ANSI case, suppressing this error will only suppress the
IEEE-required check; it will simulate correctly.
.. option:: PINCONNECTEMPTY
+40 -37
View File
@@ -720,15 +720,15 @@ in that process. When ordering code using ``V3Order``, these triggers are
provided as external domains of these variables. This ensures that the
necessary combinational logic is triggered after a coroutine resumption.
Every call to a `VlTriggerScheduler`'s `trigger()` method is preempt by
a call to a proper `__VbeforeTrig` function which evaluates all the necessary
triggers so, the information about order of suspension/resumption is not lost.
The triggers necessary to evaluate are ones dependent on the same events
as the `trigger()` - e.g.: if `triggers()` awaits for event `a` or `b`, then
every trigger that depends on any of those shall be evaluated. If they wouldn't
be evaluated and next coroutine after resumption would fire the event `a` then
it is impossible to get to know whether await or fire on event `a` was called
first - which is necessary to know.
Every call to a `VlTriggerScheduler`'s `trigger()` method is preempt by a
call to a proper `__VbeforeTrig` function which evaluates all the necessary
triggers so, the information about order of suspension/resumption is not
lost. The triggers necessary to evaluate are ones dependent on the same
events as the `trigger()` - e.g.: if `triggers()` awaits for event `a` or
`b`, then every trigger that depends on any of those shall be evaluated. If
they wouldn't be evaluated and next coroutine after resumption would fire
the event `a` then it is impossible to get to know whether await or fire on
event `a` was called first - which is necessary to know.
There are two functions for managing timing logic called by ``_eval()``:
@@ -741,10 +741,10 @@ Thanks to this separation a coroutine:
* awaiting a trigger cannot be suspended and resumed in the same iteration
(``test_regress/t/t_timing_eval_act.v``) - which is necessary to make
Verilator more predictable; this is the reason for introduction of 3rd stage
in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream
logic will be evaluated before resumption (assuming that the coroutine wasn't
already triggered in previous iteration);
Verilator more predictable; this is the reason for introduction of 3rd
stage in `VlTriggerScheduler` and thanks to this it is guaranteed that
downstream logic will be evaluated before resumption (assuming that the
coroutine wasn't already triggered in previous iteration);
* cannot be resumed before it is suspended -
``test_regress/t/t_event_control_double_excessive.v``;
* firing cannot cannot be lost
@@ -1568,8 +1568,8 @@ For all tests to pass, you must install the following packages:
- SystemC to compile the SystemC outputs, see https://systemc.org
- vcddiff to find differences in VCD outputs. See the readme at
https://github.com/veripool/vcddiff
- wavediff to find differences in waveform outputs. See the readme at
https://github.com/hudson-trading/wavetools
- Cmake for build paths that use it.
@@ -2136,13 +2136,14 @@ backtrace. You will typically see a frame sequence something like:
Bisecting bad transformations
-----------------------------
If a bad transformation in the internals of Verilator causes a failure only at
runtime, it can be found fairly automatically by only applying the transform a
limited number of times, then performing a bisection search over the limit to
pinpoint the exact transformation that introduces the failure.
If a bad transformation in the internals of Verilator causes a failure only
at runtime, it can be found fairly automatically by only applying the
transform a limited number of times, then performing a bisection search
over the limit to pinpoint the exact transformation that introduces the
failure.
To facilitate this an instance of the ``V3DebugBisect`` class can be used in
conjunction with the ``verilator_bisect`` script.
To facilitate this an instance of the ``V3DebugBisect`` class can be used
in conjunction with the ``verilator_bisect`` script.
In the offending algorithm, create a static instance of ``V3DebugBisect``:
@@ -2150,30 +2151,31 @@ In the offending algorithm, create a static instance of ``V3DebugBisect``:
static V3DebugBisect s_debugBisect{"TransformName"};
Call the ``stop`` method before applying a transformation, and do not proceed
if it returns ``false``. Then use ``verilator_bisect`` to search an interval of
values. You need to provide an arbitrary discriminator command, this should run
Verilator, then any necessary checks (e.g.: simulation) to detect that the
failure is still present. It should exit with a non-zero status if the failure
is still present. The discriminator command can otherwise be arbitrarily
complex, the actual search limit is passed via environment variables. E.g.:
Call the ``stop`` method before applying a transformation, and do not
proceed if it returns ``false``. Then use ``verilator_bisect`` to search an
interval of values. You need to provide an arbitrary discriminator command,
this should run Verilator, then any necessary checks (e.g.: simulation) to
detect that the failure is still present. It should exit with a non-zero
status if the failure is still present. The discriminator command can
otherwise be arbitrarily complex, the actual search limit is passed via
environment variables. E.g.:
::
bin/verilator_bisect DfgPeephole 0 1000 test_regress/t/t_myothertest.py
bin/verilator_bisect DfgPeephole 0 1000 test_regress/t/t_myothertest.py
An additional command can be run before the discriminator command. E.g. this
will run RTLMeter, but first removes its working directory so the models are
recompiled on every step:
An additional command can be run before the discriminator command. E.g.
this will run RTLMeter, but first removes its working directory so the
models are recompiled on every step:
::
bin/verilator_bisect --pre "rm -rf work-bisect" DfgPeephole 0 10000000 \
rtlmeter run --cases "..." --workRoot=work-bisect
bin/verilator_bisect --pre "rm -rf work-bisect" DfgPeephole 0 10000000 \
rtlmeter run --cases "..." --workRoot=work-bisect
When the bisection ends, the first value that makes the discriminator command
fail is printed, which identifies the exact offending application of the
transform.
When the bisection ends, the first value that makes the discriminator
command fail is printed, which identifies the exact offending application
of the transform.
Adding a New Feature
====================
@@ -2695,6 +2697,7 @@ 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
.. |Logo| image:: https://www.veripool.org/img/verilator_256_200_min.png
+9
View File
@@ -333,6 +333,7 @@ Muhlestein
Multithreaded
Multithreading
Mykyta
NFA
NOUNOPTFLAT
NaN
Nalbantis
@@ -589,6 +590,7 @@ al
ala
alejandro
algrobman
allocator
andit
ar
architected
@@ -810,6 +812,7 @@ fs
fscanf
fseek
fsiegle
fsm
fst
fstrict
ftell
@@ -837,6 +840,7 @@ gotFinish
goto
gprof
gtkwave
hdl
hdr
hdzhangdoc
hh
@@ -983,6 +987,7 @@ onehot
ooo
oprofile
ortegon
output
oversized
oversubscription
parallelized
@@ -1071,12 +1076,14 @@ recrem
recurse
recurses
redeclaring
reentrant
regs
reloop
replaceShiftOp
reproducibility
resetall
respecified
reusability
rodata
rolloverSize
rr
@@ -1211,6 +1218,7 @@ upcasting
urandom
uselib
utimes
uvm
uwire
uwires
valgrind
@@ -1257,6 +1265,7 @@ vpm
vpp
warmup
wavealloca
wavediff
waveforms
whitespace
widthed
+20
View File
@@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7
C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24),
N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and C-string-valued metadata pairs.
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
A(7)) VL_MT_SAFE {
const std::string val2str = std::to_string(val2);
const std::string val3str = std::to_string(val3);
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
N(8), N(9), N(10), N(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and additional FSM metadata pairs.
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
A(7), A(8), A(9), A(10), A(11)) VL_MT_SAFE {
const std::string val2str = std::to_string(val2);
const std::string val3str = std::to_string(val3);
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
C(8), C(9), C(10), C(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for Verilator
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4),
const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE {
+7
View File
@@ -191,6 +191,13 @@ public:
void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12),
A(13), A(14), A(15), A(16), A(17), A(18), A(19), A(20), D(21), D(22), D(23),
D(24), D(25), D(26), D(27), D(28), D(29)) VL_MT_SAFE;
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and C-string-valued metadata pairs.
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7)) VL_MT_SAFE;
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and additional FSM metadata pairs.
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7), A(8), A(9),
A(10), A(11)) VL_MT_SAFE;
// Backward compatibility for Verilator
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5),
A(6), A(7)) VL_MT_SAFE;
+12
View File
@@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
// Bin attributes
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'")
VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'")
VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'")
VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'")
VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'")
@@ -49,6 +53,10 @@ VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'd
#define VL_CIK_COLUMN "n"
#define VL_CIK_COMMENT "o"
#define VL_CIK_FILENAME "f"
#define VL_CIK_FSM_FROM "Ff"
#define VL_CIK_FSM_TAG "Fg"
#define VL_CIK_FSM_TO "Ft"
#define VL_CIK_FSM_VAR "Fv"
#define VL_CIK_HIER "h"
#define VL_CIK_LINENO "l"
#define VL_CIK_LINESCOV "S"
@@ -70,6 +78,10 @@ public:
if (key == "column") return VL_CIK_COLUMN;
if (key == "comment") return VL_CIK_COMMENT;
if (key == "filename") return VL_CIK_FILENAME;
if (key == "fsm_from") return VL_CIK_FSM_FROM;
if (key == "fsm_tag") return VL_CIK_FSM_TAG;
if (key == "fsm_to") return VL_CIK_FSM_TO;
if (key == "fsm_var") return VL_CIK_FSM_VAR;
if (key == "hier") return VL_CIK_HIER;
if (key == "lineno") return VL_CIK_LINENO;
if (key == "linescov") return VL_CIK_LINESCOV;
+258
View File
@@ -0,0 +1,258 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Code available from: https://verilator.org
//
// Copyright 2026-2026 by Wilson Snyder. 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: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
///
/// \file
/// \brief Verilator: Runtime support for force/release statements
///
/// This file provides runtime data structures for efficient dynamic
/// resolution of force/release statements. A sorted list of active
/// forces is maintained that can be efficiently queried and modified
/// at runtime.
///
//*************************************************************************
#ifndef VERILATOR_VERILATED_FORCE_H_
#define VERILATOR_VERILATED_FORCE_H_
#include "verilatedos.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <vector>
template <typename T>
using VlForceBaseType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
// VlForceRead - Helper functions to read a forced value
//
// These functions combine original value with forced values based on
// VlForceVec entries.
// This achieves O(k) complexity where k = number of active forces.
template <typename T>
struct VlForceTypeInfo final {
using Type = VlForceBaseType<T>;
static constexpr bool bitwise
= std::is_integral<Type>::value || std::is_enum<Type>::value || VlIsVlWide<Type>::value;
static constexpr bool unpackedArray = false;
};
template <typename T>
struct VlForceArrayIndexer final {
static constexpr std::size_t size = 1;
static T& elem(T& value, std::size_t) { return value; }
};
template <typename T, std::size_t N>
struct VlForceArrayIndexer<VlUnpacked<T, N>> final {
static constexpr std::size_t size = N * VlForceArrayIndexer<T>::size;
static auto& elem(VlUnpacked<T, N>& array, std::size_t index) {
constexpr std::size_t subSize = VlForceArrayIndexer<T>::size;
return VlForceArrayIndexer<T>::elem(array[index / subSize], index % subSize);
}
};
template <typename T, std::size_t N>
struct VlForceTypeInfo<VlUnpacked<T, N>> final {
using Type = VlUnpacked<T, N>;
static constexpr bool bitwise = false;
static constexpr bool unpackedArray = true;
};
template <typename T, bool = std::is_enum<T>::value>
struct VlForceStorageTypeOf final {
using type = typename std::make_unsigned<T>::type;
};
template <typename T>
struct VlForceStorageTypeOf<T, true> final {
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
};
template <typename T>
using VlForceStorageType = typename VlForceStorageTypeOf<VlForceBaseType<T>>::type;
//=============================================================================
// VlForceVec - Vector of active force entries for a signal
//
// This class maintains a sorted vector of non-overlapping force entries.
// When a new force is added, it removes or trims existing entries that
// overlap with the new range.
//
// The generated code will:
// 1. Use addForce/release to update the active forces
// 2. Call a generated read function that iterates entries and evaluates RHS
class VlForceVec final {
private:
struct Entry final {
int m_lsb; // Inclusive lower bit
int m_msb; // Inclusive upper bit
int m_rhsLsb; // Destination index that maps to RHS index 0
const void* m_rhsDatap; // Pointer to RHS storage
bool operator<(const Entry& other) const { return m_msb < other.m_msb; }
};
std::vector<Entry> m_entries; // Sorted by msb, non-overlapping
std::vector<Entry>::iterator trimEntries(int lsb, int msb) {
auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
[](const Entry& e, int bit) { return e.m_msb < bit; });
while (it != m_entries.end() && it->m_lsb <= msb) {
if (it->m_lsb < lsb && it->m_msb > msb) {
const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap};
it->m_msb = lsb - 1;
return m_entries.insert(++it, right);
}
if (it->m_lsb < lsb) {
it->m_msb = lsb - 1;
++it;
continue;
}
if (it->m_msb > msb) {
it->m_lsb = msb + 1;
return it;
}
it = m_entries.erase(it);
}
return it;
}
static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) {
assert(width > 0 && width <= VL_QUADSIZE);
assert(rhsLsb >= 0);
const QData mask = static_cast<QData>(VL_MASK_Q(width));
const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1;
if (rhsWidth <= VL_QUADSIZE) {
const QData rhsVal = static_cast<QData>(*static_cast<const QData*>(entry.m_rhsDatap));
return (rhsVal >> rhsLsb) & mask;
}
const EData* const rhswp = static_cast<const EData*>(entry.m_rhsDatap);
return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask;
}
template <typename T>
static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) {
const T lowMask = static_cast<T>(VL_MASK_Q(width));
const T mask = static_cast<T>(lowMask << lsb);
const T rhsBits = static_cast<T>(
(static_cast<T>(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb);
return static_cast<T>((cur & ~mask) | (rhsBits & mask));
}
template <typename T>
static typename std::enable_if<VlIsVlWide<T>::value, T>::type applyEntry(T result,
const Entry& entry) {
EData* const reswp = result.data();
const int lword = VL_BITWORD_E(entry.m_lsb);
const int hword = VL_BITWORD_E(entry.m_msb);
for (int word = lword; word <= hword; ++word) {
const int wordLsb = word * VL_EDATASIZE;
const int segLsb = std::max(entry.m_lsb, wordLsb);
const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1);
const int segWidth = segMsb - segLsb + 1;
const int bitOffset = segLsb - wordLsb;
const int rhsLsb = segLsb - entry.m_rhsLsb;
reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
}
return result;
}
template <typename T>
static typename std::enable_if<!VlIsVlWide<T>::value && VlForceTypeInfo<T>::bitwise, T>::type
applyEntry(T result, const Entry& entry) {
using U = VlForceStorageType<T>;
const int width = entry.m_msb - entry.m_lsb + 1;
const int bits = static_cast<int>(sizeof(U) * 8);
const int rhsLsb = entry.m_lsb - entry.m_rhsLsb;
const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width);
if (width >= bits) return static_cast<T>(static_cast<U>(rhsChunk));
return static_cast<T>(
applyBits(static_cast<U>(result), entry, entry.m_lsb, width, rhsLsb));
}
template <typename T>
static typename std::enable_if<!VlForceTypeInfo<T>::bitwise, T>::type
applyEntry(T result, const Entry& entry) {
static_cast<void>(result);
return *static_cast<const VlForceBaseType<T>*>(entry.m_rhsDatap);
}
public:
VlForceVec() = default;
template <typename T>
T read(T val) const {
if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
// Handling the case of a nested flattened array using recursion
using ElemRef
= decltype(VlForceArrayIndexer<T>::elem(val, static_cast<std::size_t>(0)));
using Elem = VlForceBaseType<ElemRef>;
const int total = static_cast<int>(VlForceArrayIndexer<T>::size);
for (const auto& entry : m_entries) {
const Elem* const rhsBasep = static_cast<const Elem*>(entry.m_rhsDatap);
const int startIdx = entry.m_lsb;
const int endIdx = entry.m_msb;
for (int idx = startIdx; idx <= endIdx; idx++) {
const int rhsIndex = idx - entry.m_rhsLsb;
const std::size_t uidx = static_cast<std::size_t>(idx);
VlForceArrayIndexer<T>::elem(val, uidx) = rhsBasep[rhsIndex];
}
}
return val;
}
for (const auto& entry : m_entries) { val = applyEntry(val, entry); }
return val;
}
template <typename T>
T readIndex(T origVal, int index) const {
if (m_entries.empty()) return origVal;
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index,
[](const Entry& e, int idx) { return e.m_msb < idx; });
if (it != m_entries.end() && it->m_lsb <= index) {
const int rhsIndex = index - it->m_rhsLsb;
const T* const rhsBasep = static_cast<const T*>(it->m_rhsDatap);
return rhsBasep[rhsIndex];
}
return origVal;
}
void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) {
assert(lsb <= msb);
assert(rhsDatap);
assert(rhsLsb <= lsb);
auto it = trimEntries(lsb, msb);
m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap});
}
void release(int lsb, int msb) {
assert(lsb <= msb);
trimEntries(lsb, msb);
}
void touch() {}
};
#endif // guard
+2 -22
View File
@@ -353,29 +353,9 @@ void VerilatedFst::declDoubleArray(uint32_t code, const char* name, int dtypenum
//=============================================================================
// Get/commit trace buffer
VerilatedFst::Buffer* VerilatedFst::getTraceBuffer(uint32_t /*fidx*/) {
if (offload()) return new OffloadBuffer{*this};
return new Buffer{*this};
}
VerilatedFst::Buffer* VerilatedFst::getTraceBuffer(uint32_t /*fidx*/) { return new Buffer{*this}; }
void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) {
if (offload()) {
const OffloadBuffer* const offloadBufferp = static_cast<const OffloadBuffer*>(bufp);
if (offloadBufferp->m_offloadBufferWritep) {
m_offloadBufferWritep = offloadBufferp->m_offloadBufferWritep;
return; // Buffer will be deleted by the offload thread
}
}
delete bufp;
}
//=============================================================================
// Configure
void VerilatedFst::configure(const VerilatedTraceConfig& config) {
// If at least one model requests the FST writer thread, then use it
m_useFstWriterThread |= config.m_useFstWriterThread;
}
void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) { delete bufp; }
//=============================================================================
// VerilatedFstBuffer implementation
+1 -2
View File
@@ -87,7 +87,7 @@ protected:
void commitTraceBuffer(Buffer*) override;
// Configure sub-class
void configure(const VerilatedTraceConfig&) override;
void configure(const VerilatedTraceConfig&) override {}
public:
//=========================================================================
@@ -206,7 +206,6 @@ class VerilatedFstBuffer VL_NOT_FINAL {
friend VerilatedFst;
friend VerilatedFst::Super;
friend VerilatedFst::Buffer;
friend VerilatedFst::OffloadBuffer;
VerilatedFst& m_owner; // Trace file owning this buffer. Required by subclasses.
-1
View File
@@ -224,7 +224,6 @@ class VerilatedSaifBuffer VL_NOT_FINAL {
friend VerilatedSaif;
friend VerilatedSaif::Super;
friend VerilatedSaif::Buffer;
friend VerilatedSaif::OffloadBuffer;
VerilatedSaif& m_owner; // Trace file owning this buffer. Required by subclasses.
uint32_t m_fidx; // Index of target activity accumulator
+5
View File
@@ -26,6 +26,9 @@
// The following keywords from this file are hardcoded for detection in the parser:
// "mailbox", "process", "randomize", "semaphore", "std"
`ifndef VERILATOR_STD_SV_
`define VERILATOR_STD_SV_
// verilator lint_off DECLFILENAME
// verilator lint_off TIMESCALEMOD
// verilator lint_off UNUSEDSIGNAL
@@ -292,3 +295,5 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
} vl_cross_type_options_t;
endpackage
`endif // Guard
+2 -215
View File
@@ -43,8 +43,6 @@
class VlThreadPool;
template <typename T_Buffer>
class VerilatedTraceBuffer;
template <typename T_Buffer>
class VerilatedTraceOffloadBuffer;
//=============================================================================
// Common enumerations
@@ -98,76 +96,6 @@ enum class VerilatedTraceSigType : uint8_t {
TIME,
};
//=============================================================================
// Offloaded tracing
// A simple synchronized first in first out queue
template <typename T>
class VerilatedThreadQueue final { // LCOV_EXCL_LINE // lcov bug
private:
mutable VerilatedMutex m_mutex; // Protects m_queue
std::condition_variable_any m_cv;
std::deque<T> m_queue VL_GUARDED_BY(m_mutex);
public:
// Put an element at the back of the queue
void put(T value) VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
m_queue.push_back(value);
m_cv.notify_one();
}
// Put an element at the front of the queue
void put_front(T value) VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
m_queue.push_front(value);
m_cv.notify_one();
}
// Get an element from the front of the queue. Blocks if none available
T get() VL_MT_SAFE_EXCLUDES(m_mutex) {
VerilatedLockGuard lock{m_mutex};
m_cv.wait(m_mutex, [this]() VL_REQUIRES(m_mutex) { return !m_queue.empty(); });
assert(!m_queue.empty());
T value = m_queue.front();
m_queue.pop_front();
return value;
}
// Non blocking get
bool tryGet(T& result) VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lockGuard{m_mutex};
if (m_queue.empty()) return false;
result = m_queue.front();
m_queue.pop_front();
return true;
}
};
// Commands used by thread tracing. Anonymous enum in class, as we want
// it scoped, but we also want the automatic conversion to integer types.
class VerilatedTraceOffloadCommand final {
public:
// These must all fit in 4 bit at the moment, as the tracing routines
// pack parameters in the top bits.
enum : uint8_t {
CHG_BIT_0 = 0x0,
CHG_BIT_1 = 0x1,
CHG_CDATA = 0x2,
CHG_SDATA = 0x3,
CHG_IDATA = 0x4,
CHG_QDATA = 0x5,
CHG_WDATA = 0x6,
CHG_DOUBLE = 0x8,
CHG_EVENT = 0x9,
// TODO: full..
TIME_CHANGE = 0xc,
TRACE_BUFFER = 0xd,
END = 0xe, // End of buffer
SHUTDOWN = 0xf // Shutdown worker thread, also marks end of buffer
};
};
//=============================================================================
// VerilatedTraceConfig
@@ -175,13 +103,9 @@ public:
class VerilatedTraceConfig final {
public:
const bool m_useParallel; // Use parallel tracing
const bool m_useOffloading; // Offloading trace rendering
const bool m_useFstWriterThread; // Use the separate FST writer thread
VerilatedTraceConfig(bool useParallel, bool useOffloading, bool useFstWriterThread)
: m_useParallel{useParallel}
, m_useOffloading{useOffloading}
, m_useFstWriterThread{useFstWriterThread} {}
VerilatedTraceConfig(bool useParallel)
: m_useParallel{useParallel} {}
};
//=============================================================================
@@ -208,27 +132,23 @@ template <typename T_Trace, typename T_Buffer>
class VerilatedTrace VL_NOT_FINAL {
public:
using Buffer = VerilatedTraceBuffer<T_Buffer>;
using OffloadBuffer = VerilatedTraceOffloadBuffer<T_Buffer>;
//=========================================================================
// Generic tracing internals
using initCb_t = void (*)(void*, T_Trace*, uint32_t); // Type of init callbacks
using dumpCb_t = void (*)(void*, Buffer*); // Type of dump callbacks
using dumpOffloadCb_t = void (*)(void*, OffloadBuffer*); // Type of offload dump callbacks
using cleanupCb_t = void (*)(void*, T_Trace*); // Type of cleanup callbacks
private:
// Give the buffer (both base and derived) access to the private bits
friend T_Buffer;
friend Buffer;
friend OffloadBuffer;
struct CallbackRecord final {
union { // The callback
const initCb_t m_initCb;
const dumpCb_t m_dumpCb;
const dumpOffloadCb_t m_dumpOffloadCb;
const cleanupCb_t m_cleanupCb;
};
const uint32_t m_fidx; // The index of the tracing function
@@ -252,14 +172,6 @@ private:
, m_name{} // Don't care
, m_nTraceCodes{0} // Don't care
{}
CallbackRecord(dumpOffloadCb_t cb, uint32_t fidx, void* userp)
: m_dumpOffloadCb{cb}
, m_fidx{fidx}
, m_userp{userp}
, m_isLibInstance{false} // Don't care
, m_name{} // Don't care
, m_nTraceCodes{0} // Don't care
{}
CallbackRecord(cleanupCb_t cb, void* userp)
: m_cleanupCb{cb}
, m_fidx{0}
@@ -270,7 +182,6 @@ private:
{}
};
bool m_offload = false; // Use the offload thread
bool m_parallel = false; // Use parallel tracing
struct ParallelWorkerData final {
@@ -300,11 +211,8 @@ private:
std::vector<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp
std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing
std::vector<CallbackRecord> m_constCbs; // Routines to perform const dump
std::vector<CallbackRecord> m_constOffloadCbs; // Routines to perform offloaded const dump
std::vector<CallbackRecord> m_fullCbs; // Routines to perform full dump
std::vector<CallbackRecord> m_fullOffloadCbs; // Routines to perform offloaded full dump
std::vector<CallbackRecord> m_chgCbs; // Routines to perform incremental dump
std::vector<CallbackRecord> m_chgOffloadCbs; // Routines to perform offloaded incremental dump
std::vector<CallbackRecord> m_cleanupCbs; // Routines to call at the end of dump
bool m_constDump = true; // Whether a const dump is required on the next call to 'dump'
bool m_fullDump = true; // Whether a full dump is required on the next call to 'dump'
@@ -329,44 +237,13 @@ private:
T_Trace* self() { return static_cast<T_Trace*>(this); }
void runCallbacks(const std::vector<CallbackRecord>& cbVec);
void runOffloadedCallbacks(const std::vector<CallbackRecord>& cbVec);
// Flush any remaining data for this file
static void onFlush(void* selfp) VL_MT_UNSAFE_ONE;
// Close the file on termination
static void onExit(void* selfp) VL_MT_UNSAFE_ONE;
// Number of total offload buffers that have been allocated
uint32_t m_numOffloadBuffers = 0;
// Size of offload buffers
size_t m_offloadBufferSize = 0;
// Buffers handed to worker for processing
VerilatedThreadQueue<uint32_t*> m_offloadBuffersToWorker;
// Buffers returned from worker after processing
VerilatedThreadQueue<uint32_t*> m_offloadBuffersFromWorker;
protected:
// Write pointer into current buffer
uint32_t* m_offloadBufferWritep = nullptr;
// End of offload buffer
uint32_t* m_offloadBufferEndp = nullptr;
private:
// The offload worker thread itself
std::unique_ptr<std::thread> m_workerThread;
// Get a new offload buffer that can be populated. May block if none available
uint32_t* getOffloadBuffer();
// The function executed by the offload worker thread
void offloadWorkerThreadMain();
// Wait until given offload buffer is placed in m_offloadBuffersFromWorker
void waitForOffloadBuffer(const uint32_t* bufferp);
// Shut down and join worker, if it's running, otherwise do nothing
void shutdownOffloadWorker();
// CONSTRUCTORS
VL_UNCOPYABLE(VerilatedTrace);
@@ -395,7 +272,6 @@ protected:
void closeBase();
void flushBase();
bool offload() const { return m_offload; }
bool parallel() const { return m_parallel; }
// Return last ' ' separated word. Assumes string does not end in ' '.
@@ -453,11 +329,8 @@ public:
void addInitCb(initCb_t cb, void* userp, const std::string& name, bool isLibInstance,
uint32_t nTraceCodes) VL_MT_SAFE;
void addConstCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addConstCb(dumpOffloadCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addFullCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addFullCb(dumpOffloadCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addChgCb(dumpCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addChgCb(dumpOffloadCb_t cb, uint32_t fidx, void* userp) VL_MT_SAFE;
void addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE;
void initLib(const std::string& name) VL_MT_UNSAFE;
};
@@ -517,10 +390,6 @@ public:
void fullEvent(uint32_t* oldp, const VlEventBase* newvalp);
void fullEventTriggered(uint32_t* oldp);
// In non-offload mode, these are called directly by the trace callbacks,
// and are called chg*. In offload mode, they are called by the worker
// thread and are called chg*Impl
// Check previous dumped value of signal. If changed, then emit trace entry
VL_ATTR_ALWINLINE void chgBit(uint32_t* oldp, CData newval) {
const uint32_t diff = *oldp ^ newval;
@@ -563,86 +432,4 @@ public:
}
};
//=============================================================================
// VerilatedTraceOffloadBuffer
// T_Buffer is the format-specific base class of VerilatedTraceBuffer.
// The format-specific hot-path methods use duck-typing via T_Buffer for performance.
template <typename T_Buffer>
class VerilatedTraceOffloadBuffer final : public VerilatedTraceBuffer<T_Buffer> {
using typename VerilatedTraceBuffer<T_Buffer>::Trace;
friend Trace; // Give the trace file access to the private bits
uint32_t* m_offloadBufferWritep; // Write pointer into current buffer
uint32_t* const m_offloadBufferEndp; // End of offload buffer
explicit VerilatedTraceOffloadBuffer(Trace& owner);
~VerilatedTraceOffloadBuffer() override = default;
public:
//=========================================================================
// Hot path internal interface to Verilator generated code
// Offloaded tracing. Just dump everything in the offload buffer
void chgBit(uint32_t code, CData newval) {
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_BIT_0 | newval;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep += 2;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgCData(uint32_t code, CData newval, int bits) {
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_CDATA;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep[2] = newval;
m_offloadBufferWritep += 3;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgSData(uint32_t code, SData newval, int bits) {
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_SDATA;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep[2] = newval;
m_offloadBufferWritep += 3;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgIData(uint32_t code, IData newval, int bits) {
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_IDATA;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep[2] = newval;
m_offloadBufferWritep += 3;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgQData(uint32_t code, QData newval, int bits) {
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_QDATA;
m_offloadBufferWritep[1] = code;
*reinterpret_cast<QData*>(m_offloadBufferWritep + 2) = newval;
m_offloadBufferWritep += 4;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgWData(uint32_t code, const WData* newvalp, int bits) {
m_offloadBufferWritep[0] = (bits << 4) | VerilatedTraceOffloadCommand::CHG_WDATA;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep += 2;
for (int i = 0; i < (bits + 31) / 32; ++i) *m_offloadBufferWritep++ = newvalp[i];
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgDouble(uint32_t code, double newval) {
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_DOUBLE;
m_offloadBufferWritep[1] = code;
// cppcheck-suppress invalidPointerCast
*reinterpret_cast<double*>(m_offloadBufferWritep + 2) = newval;
m_offloadBufferWritep += 4;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
void chgEvent(uint32_t code, const VlEventBase* newvalp) {
if (newvalp->isTriggered()) chgEventTriggered(code);
}
void chgEventTriggered(uint32_t code) {
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_EVENT;
m_offloadBufferWritep[1] = code;
m_offloadBufferWritep += 2;
VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp););
}
};
#endif // guard
+7 -329
View File
@@ -29,13 +29,6 @@
#include "verilated_threads.h"
#include <list>
#if 0
# include <iostream>
# define VL_TRACE_OFFLOAD_DEBUG(msg) std::cout << "TRACE OFFLOAD THREAD: " << msg << "\n"
#else
# define VL_TRACE_OFFLOAD_DEBUG(msg)
#endif
// clang-format on
//=============================================================================
@@ -60,210 +53,14 @@ static double timescaleToDouble(const char* unitp) VL_PURE {
return value;
}
//=========================================================================
// Buffer management
template <>
uint32_t* VerilatedTrace<VL_SUB_T, VL_BUF_T>::getOffloadBuffer() {
uint32_t* bufferp;
// Some jitter is expected, so some number of alternative offload buffers are
// required, but don't allocate more than 8 buffers.
if (m_numOffloadBuffers < 8) {
// Allocate a new buffer if none is available
if (!m_offloadBuffersFromWorker.tryGet(bufferp)) {
++m_numOffloadBuffers;
// Note: over allocate a bit so pointer comparison is well defined
// if we overflow only by a small amount
bufferp = new uint32_t[m_offloadBufferSize + 16];
}
} else {
// Block until a buffer becomes available
bufferp = m_offloadBuffersFromWorker.get();
}
return bufferp;
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::waitForOffloadBuffer(const uint32_t* buffp) {
// Slow path code only called on flush/shutdown, so use a simple algorithm.
// Collect buffers from worker and stash them until we get the one we want.
std::deque<uint32_t*> stash;
do { stash.push_back(m_offloadBuffersFromWorker.get()); } while (stash.back() != buffp);
// Now put them back in the queue, in the original order.
while (!stash.empty()) {
m_offloadBuffersFromWorker.put_front(stash.back());
stash.pop_back();
}
}
//=========================================================================
// Worker thread
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain() {
bool shutdown = false;
do {
uint32_t* const bufferp = m_offloadBuffersToWorker.get();
VL_TRACE_OFFLOAD_DEBUG("");
VL_TRACE_OFFLOAD_DEBUG("Got buffer: " << bufferp);
const uint32_t* readp = bufferp;
std::unique_ptr<Buffer> traceBufp; // We own the passed tracebuffer
while (true) {
const uint32_t cmd = readp[0];
const uint32_t top = cmd >> 4;
// Always set this up, as it is almost always needed
uint32_t* const oldp = m_sigs_oldvalp + readp[1];
// Note this increment needs to be undone on commands which do not
// actually contain a code, but those are the rare cases.
readp += 2;
switch (cmd & 0xF) {
//===
// CHG_* commands
case VerilatedTraceOffloadCommand::CHG_BIT_0:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_0 " << top);
traceBufp->chgBit(oldp, 0);
continue;
case VerilatedTraceOffloadCommand::CHG_BIT_1:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_1 " << top);
traceBufp->chgBit(oldp, 1);
continue;
case VerilatedTraceOffloadCommand::CHG_CDATA:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_CDATA " << top);
// Bits stored in bottom byte of command
traceBufp->chgCData(oldp, *readp, top);
readp += 1;
continue;
case VerilatedTraceOffloadCommand::CHG_SDATA:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_SDATA " << top);
// Bits stored in bottom byte of command
traceBufp->chgSData(oldp, *readp, top);
readp += 1;
continue;
case VerilatedTraceOffloadCommand::CHG_IDATA:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_IDATA " << top);
// Bits stored in bottom byte of command
traceBufp->chgIData(oldp, *readp, top);
readp += 1;
continue;
case VerilatedTraceOffloadCommand::CHG_QDATA:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_QDATA " << top);
// Bits stored in bottom byte of command
traceBufp->chgQData(oldp, *reinterpret_cast<const QData*>(readp), top);
readp += 2;
continue;
case VerilatedTraceOffloadCommand::CHG_WDATA:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_WDATA " << top);
traceBufp->chgWData(oldp, readp, top);
readp += VL_WORDS_I(top);
continue;
case VerilatedTraceOffloadCommand::CHG_DOUBLE:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_DOUBLE " << top);
traceBufp->chgDouble(oldp, *reinterpret_cast<const double*>(readp));
readp += 2;
continue;
case VerilatedTraceOffloadCommand::CHG_EVENT:
VL_TRACE_OFFLOAD_DEBUG("Command CHG_EVENT " << top);
traceBufp->chgEventTriggered(oldp);
continue;
//===
// Rare commands
case VerilatedTraceOffloadCommand::TIME_CHANGE: {
VL_TRACE_OFFLOAD_DEBUG("Command TIME_CHANGE " << top);
readp -= 1; // No code in this command, undo increment
const uint64_t timeui
= static_cast<uint64_t>(*reinterpret_cast<const uint32_t*>(readp)) << 32ULL
| static_cast<uint64_t>(*reinterpret_cast<const uint32_t*>(readp + 1));
emitTimeChange(timeui);
readp += 2;
continue;
}
case VerilatedTraceOffloadCommand::TRACE_BUFFER:
VL_TRACE_OFFLOAD_DEBUG("Command TRACE_BUFFER " << top);
readp -= 1; // No code in this command, undo increment
traceBufp.reset(*reinterpret_cast<Buffer* const*>(readp));
readp += 2;
continue;
//===
// Commands ending this buffer
case VerilatedTraceOffloadCommand::END: //
VL_TRACE_OFFLOAD_DEBUG("Command END");
break;
case VerilatedTraceOffloadCommand::SHUTDOWN:
VL_TRACE_OFFLOAD_DEBUG("Command SHUTDOWN");
shutdown = true;
break;
//===
// Unknown command
default: { // LCOV_EXCL_START
VL_TRACE_OFFLOAD_DEBUG("Command UNKNOWN " << cmd);
VL_FATAL_MT(__FILE__, __LINE__, "", "Unknown trace command");
break;
} // LCOV_EXCL_STOP
}
// The above switch will execute 'continue' when necessary,
// so if we ever reach here, we are done with the buffer.
break;
}
VL_TRACE_OFFLOAD_DEBUG("Returning buffer");
// Return buffer
m_offloadBuffersFromWorker.put(bufferp);
} while (VL_LIKELY(!shutdown));
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::shutdownOffloadWorker() {
// If the worker thread is not running, done..
if (!m_workerThread) return;
// Hand an buffer with a shutdown command to the worker thread
uint32_t* const bufferp = getOffloadBuffer();
bufferp[0] = VerilatedTraceOffloadCommand::SHUTDOWN;
m_offloadBuffersToWorker.put(bufferp);
// Wait for it to return
waitForOffloadBuffer(bufferp);
// Join the thread and delete it
m_workerThread->join();
m_workerThread.reset(nullptr);
}
//=============================================================================
// Life cycle
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::closeBase() {
if (offload()) {
shutdownOffloadWorker();
while (m_numOffloadBuffers) {
delete[] m_offloadBuffersFromWorker.get();
--m_numOffloadBuffers;
}
}
}
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::closeBase() {}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::flushBase() {
if (offload()) {
// Hand an empty buffer to the worker thread
uint32_t* const bufferp = getOffloadBuffer();
*bufferp = VerilatedTraceOffloadCommand::END;
m_offloadBuffersToWorker.put(bufferp);
// Wait for it to be returned. As the processing is in-order,
// this ensures all previous buffers have been processed.
waitForOffloadBuffer(bufferp);
}
}
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::flushBase() {}
//=============================================================================
// Callbacks to run on global events
@@ -295,7 +92,6 @@ VerilatedTrace<VL_SUB_T, VL_BUF_T>::~VerilatedTrace() {
if (m_sigs_enabledp) VL_DO_CLEAR(delete[] m_sigs_enabledp, m_sigs_enabledp = nullptr);
Verilated::removeFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
Verilated::removeExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit, this);
if (offload()) closeBase();
}
//=========================================================================
@@ -353,19 +149,6 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
// Set callback so flush/abort will flush this file
Verilated::addFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
Verilated::addExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit, this);
if (offload()) {
// Compute offload buffer size. we need to be able to store a new value for
// each signal, which is 'nextCode()' entries after the init callbacks
// above have been run, plus up to 2 more words of metadata per signal,
// plus fixed overhead of 1 for a termination flag and 3 for a time stamp
// update and 2 for the buffer address.
m_offloadBufferSize = nextCode() + numSignals() * 2 + 6;
// Start the worker thread
m_workerThread.reset(
new std::thread{&VerilatedTrace<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain, this});
}
}
template <>
@@ -520,17 +303,6 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::runCallbacks(const std::vector<Callback
}
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::runOffloadedCallbacks(
const std::vector<CallbackRecord>& cbVec) {
// Fall back on sequential execution
for (const CallbackRecord& cbr : cbVec) {
Buffer* traceBufferp = getTraceBuffer(cbr.m_fidx);
cbr.m_dumpOffloadCb(cbr.m_userp, static_cast<OffloadBuffer*>(traceBufferp));
commitTraceBuffer(traceBufferp);
}
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::dump(uint64_t timeui) VL_MT_SAFE_EXCLUDES(m_mutex) {
// Not really VL_MT_SAFE but more VL_MT_UNSAFE_ONE.
@@ -556,72 +328,23 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD
}
uint32_t* bufferp = nullptr;
if (offload()) {
// Currently only incremental dumps run on the worker thread
if (VL_LIKELY(!m_fullDump)) {
// Get the offload buffer we are about to fill
bufferp = getOffloadBuffer();
m_offloadBufferWritep = bufferp;
m_offloadBufferEndp = bufferp + m_offloadBufferSize;
// Tell worker to update time point
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::TIME_CHANGE;
*reinterpret_cast<uint32_t*>(m_offloadBufferWritep + 1)
= static_cast<uint32_t>(timeui >> 32ULL);
*reinterpret_cast<uint32_t*>(m_offloadBufferWritep + 2)
= static_cast<uint32_t>(timeui);
m_offloadBufferWritep += 3;
} else {
// Update time point
flushBase();
emitTimeChange(timeui);
}
} else {
// Update time point
emitTimeChange(timeui);
}
// Update time point
emitTimeChange(timeui);
// Run the callbacks
if (VL_UNLIKELY(m_fullDump)) {
m_fullDump = false; // No more need for next dump to be full
if (offload()) {
runOffloadedCallbacks(m_fullOffloadCbs);
} else {
runCallbacks(m_fullCbs);
}
runCallbacks(m_fullCbs);
} else {
if (offload()) {
runOffloadedCallbacks(m_chgOffloadCbs);
} else {
runCallbacks(m_chgCbs);
}
runCallbacks(m_chgCbs);
}
if (VL_UNLIKELY(m_constDump)) {
m_constDump = false;
if (offload()) {
runOffloadedCallbacks(m_constOffloadCbs);
} else {
runCallbacks(m_constCbs);
}
runCallbacks(m_constCbs);
}
for (const CallbackRecord& cbr : m_cleanupCbs) cbr.m_cleanupCb(cbr.m_userp, self());
if (offload() && VL_LIKELY(bufferp)) {
// Mark end of the offload buffer we just filled
*m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::END;
// Assert no buffer overflow
assert(static_cast<size_t>(m_offloadBufferWritep - bufferp) <= m_offloadBufferSize);
// Reset our pointers as we are giving up the buffer
m_offloadBufferWritep = nullptr;
m_offloadBufferEndp = nullptr;
// Pass it to the worker thread
m_offloadBuffersToWorker.put(bufferp);
}
}
//=============================================================================
@@ -658,20 +381,9 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addModel(VerilatedModel* modelp)
const std::unique_ptr<VerilatedTraceConfig> configp = modelp->traceConfig();
// Configure trace base class
if (!firstModel) {
if (m_offload != configp->m_useOffloading) {
VL_FATAL_MT(__FILE__, __LINE__, "",
"Either all or no models using the same trace file must use offloading");
}
}
m_offload = configp->m_useOffloading;
// If at least one model requests parallel tracing, then use it
m_parallel |= configp->m_useParallel;
if (VL_UNCOVERABLE(m_parallel && m_offload)) { // LCOV_EXCL_START
VL_FATAL_MT(__FILE__, __LINE__, "", "Cannot use parallel tracing with offloading");
} // LCOV_EXCL_STOP
// Configure format-specific sub class
configure(*(configp.get()));
}
@@ -696,31 +408,16 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addConstCb(dumpCb_t cb, uint32_t fidx,
addCallbackRecord(m_constCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addConstCb(dumpOffloadCb_t cb, uint32_t fidx,
void* userp) VL_MT_SAFE {
addCallbackRecord(m_constOffloadCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpCb_t cb, uint32_t fidx,
void* userp) VL_MT_SAFE {
addCallbackRecord(m_fullCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpOffloadCb_t cb, uint32_t fidx,
void* userp) VL_MT_SAFE {
addCallbackRecord(m_fullOffloadCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addChgCb(dumpCb_t cb, uint32_t fidx,
void* userp) VL_MT_SAFE {
addCallbackRecord(m_chgCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addChgCb(dumpOffloadCb_t cb, uint32_t fidx,
void* userp) VL_MT_SAFE {
addCallbackRecord(m_chgOffloadCbs, CallbackRecord{cb, fidx, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE {
addCallbackRecord(m_cleanupCbs, CallbackRecord{cb, userp});
}
@@ -914,23 +611,4 @@ void VerilatedTraceBuffer<VL_BUF_T>::fullDouble(uint32_t* oldp, double newval) {
emitDouble(code, newval);
}
//=========================================================================
// VerilatedTraceOffloadBuffer
template <>
VerilatedTraceOffloadBuffer<VL_BUF_T>::VerilatedTraceOffloadBuffer(VL_SUB_T& owner)
: VerilatedTraceBuffer<VL_BUF_T>{owner}
, m_offloadBufferWritep{owner.m_offloadBufferWritep}
, m_offloadBufferEndp{owner.m_offloadBufferEndp} {
if (m_offloadBufferWritep) {
using This = VerilatedTraceBuffer<VL_BUF_T>*;
// Tack on the buffer address
static_assert(2 * sizeof(uint32_t) >= sizeof(This),
"This should be enough on all platforms");
*m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::TRACE_BUFFER;
*reinterpret_cast<This*>(m_offloadBufferWritep) = static_cast<This>(this);
m_offloadBufferWritep += 2;
}
}
#endif // VL_CPPCHECK
-1
View File
@@ -278,7 +278,6 @@ void VerilatedVcd::bufferResize(size_t minsize) {
}
void VerilatedVcd::bufferFlush() VL_MT_UNSAFE_ONE {
// This function can be called from the trace offload thread
// This function is on the flush() call path
// We add output data to m_writep.
// When it gets nearly full we dump it using this routine which calls write()
-1
View File
@@ -211,7 +211,6 @@ class VerilatedVcdBuffer VL_NOT_FINAL {
friend VerilatedVcd;
friend VerilatedVcd::Super;
friend VerilatedVcd::Buffer;
friend VerilatedVcd::OffloadBuffer;
VerilatedVcd& m_owner; // Trace file owning this buffer. Required by subclasses.
+1 -1
View File
@@ -3402,7 +3402,7 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_
vpi_put_value(object, const_cast<p_vpi_value>(&forcedValue), nullptr, vpiNoDelay);
}
return nullptr;
return object;
}
if (valuep->format == vpiVectorVal) {
+10 -4
View File
@@ -13,14 +13,20 @@ import re
def message_section(msg: str) -> int:
"""Return sorting-section number for given commit message"""
if re.match(r'^Support', msg, flags=re.IGNORECASE):
return 10
if re.match(r'^Add', msg, flags=re.IGNORECASE):
return 10
if re.match(r'^Deprecate', msg, flags=re.IGNORECASE):
return 20
if re.match(r'^Improve', msg, flags=re.IGNORECASE):
if re.match(r'^Remove', msg, flags=re.IGNORECASE):
return 30
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
if re.match(r'^Improve', msg, flags=re.IGNORECASE):
return 40
if re.match(r'^Change', msg, flags=re.IGNORECASE):
return 50
if re.match(r'^Support', msg, flags=re.IGNORECASE):
return 60
if re.match(r'^Fix', msg, flags=re.IGNORECASE):
return 70
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
+4 -2
View File
@@ -42,8 +42,8 @@ set(HEADERS
V3Active.h
V3ActiveTop.h
V3Assert.h
V3AssertNfa.h
V3AssertPre.h
V3AssertProp.h
V3Ast.h
V3AstAttr.h
V3AstInlines.h
@@ -100,6 +100,7 @@ set(HEADERS
V3File.h
V3FileLine.h
V3Force.h
V3FsmDetect.h
V3Fork.h
V3FuncOpt.h
V3FunctionTraits.h
@@ -213,8 +214,8 @@ set(COMMON_SOURCES
V3Active.cpp
V3ActiveTop.cpp
V3Assert.cpp
V3AssertNfa.cpp
V3AssertPre.cpp
V3AssertProp.cpp
V3Ast.cpp
V3AstNodes.cpp
V3Begin.cpp
@@ -277,6 +278,7 @@ set(COMMON_SOURCES
V3File.cpp
V3FileLine.cpp
V3Force.cpp
V3FsmDetect.cpp
V3Fork.cpp
V3FuncOpt.cpp
V3Gate.cpp
+2 -1
View File
@@ -232,8 +232,8 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Active.o \
V3ActiveTop.o \
V3Assert.o \
V3AssertNfa.o \
V3AssertPre.o \
V3AssertProp.o \
V3Begin.o \
V3Branch.o \
V3CCtors.o \
@@ -280,6 +280,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3ExecGraph.o \
V3Expand.o \
V3Force.o \
V3FsmDetect.o \
V3Fork.o \
V3Gate.o \
V3HierBlock.o \
+1888
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementation of assertion properties
// DESCRIPTION: Verilator: NFA-based multi-cycle SVA assertion evaluation
//
// Code available from: https://verilator.org
//
@@ -14,8 +14,8 @@
//
//*************************************************************************
#ifndef VERILATOR_V3ASSERTPROP_H_
#define VERILATOR_V3ASSERTPROP_H_
#ifndef VERILATOR_V3ASSERTNFA_H_
#define VERILATOR_V3ASSERTNFA_H_
#include "config_build.h"
#include "verilatedos.h"
@@ -24,9 +24,9 @@ class AstNetlist;
//============================================================================
class V3AssertProp final {
class V3AssertNfa final {
public:
static void assertPropAll(AstNetlist* nodep) VL_MT_DISABLED;
static void assertNfaAll(AstNetlist* nodep) VL_MT_DISABLED;
};
#endif // Guard
+15 -20
View File
@@ -422,8 +422,10 @@ private:
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
const AstConst* const constp = VN_CAST(valuep, Const);
if (!constp) {
nodep->v3error(
"Delay value is not an elaboration-time constant (IEEE 1800-2023 16.7)");
// V3AssertNfa handles non-const delays before this pass and
// replaces the property; this branch should never be reached.
nodep->v3fatalSrc("Non-constant cycle delay in assertion: "
"should have been caught by V3AssertNfa");
} else if (constp->isZero()) {
VL_DO_DANGLING(pushDeletep(valuep), valuep);
if (m_inSynchDrive) {
@@ -704,9 +706,11 @@ private:
}
void visit(AstSConsRep* nodep) override {
// IEEE 1800-2023 16.9.2 -- Lower standalone exact [*N] (N >= 2) via saturating counter.
// Range/unbounded forms and SExpr-contained forms are lowered by V3AssertProp.
// Range/unbounded forms and SExpr-contained forms are lowered by V3AssertNfa.
iterateChildren(nodep);
if (nodep->unbounded() || nodep->maxCountp()) return; // Handled by V3AssertProp
// V3AssertNfa handles unbounded/ranged forms upstream, so this fast-path
// is effectively unreachable when NFA is enabled.
if (nodep->unbounded() || nodep->maxCountp()) return; // LCOV_EXCL_LINE
const AstConst* const constp = VN_CAST(nodep->countp(), Const);
if (VL_UNLIKELY(!constp || constp->toSInt() < 1)) {
nodep->v3fatalSrc("Consecutive repetition count must be a positive constant"
@@ -1095,7 +1099,7 @@ private:
if (AstPExpr* const pexprp = VN_CAST(rhsp, PExpr)) {
// Implication with sequence expression on RHS (IEEE 1800-2023 16.11, 16.12.7).
// The PExpr was already lowered from the property expression by V3AssertProp.
// The PExpr was lowered from the property expression earlier in this pass.
// Wrap the PExpr body with the antecedent check so the sequence only
// starts when the antecedent holds.
AstNodeExpr* condp;
@@ -1212,13 +1216,15 @@ private:
iterate(nodep->propp());
}
void visit(AstPExpr* nodep) override {
if (m_pexprp && m_pexprp->user1()) {
// V3AssertNfa handles multi-cycle property expressions before this pass,
// so the following unsupported paths are defensive and typically unreached.
if (m_pexprp && m_pexprp->user1()) { // LCOV_EXCL_START
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Complex property expression inside 'until''");
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
} // LCOV_EXCL_STOP
if (AstLogNot* const notp = VN_CAST(nodep->backp(), LogNot)) {
notp->replaceWith(nodep->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(notp), notp);
@@ -1227,30 +1233,19 @@ private:
}
// Sequence expression as antecedent of implication is not yet supported
if (AstImplication* const implp = VN_CAST(nodep->backp(), Implication)) {
if (implp->lhsp() == nodep) {
if (implp->lhsp() == nodep) { // LCOV_EXCL_START
implp->v3warn(E_UNSUPPORTED,
"Unsupported: Implication with sequence expression as antecedent");
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
} // LCOV_EXCL_STOP
}
VL_RESTORER(m_pexprp);
VL_RESTORER(m_disableSeqIfp);
m_pexprp = nodep;
if (m_disablep) {
const AstSampled* sampledp;
if (m_disablep->exists([&sampledp](const AstSampled* const sp) {
sampledp = sp;
return true;
})) {
sampledp->v3warn(E_UNSUPPORTED,
"Unsupported: $sampled inside disabled condition of a sequence");
m_disablep = new AstConst{m_disablep->fileline(), AstConst::BitFalse{}};
// always a copy is used, so remove it now
pushDeletep(m_disablep);
}
FileLine* const flp = nodep->fileline();
// Add counter which counts times the condition turned true
AstVar* const disableCntp
-1446
View File
File diff suppressed because it is too large Load Diff
+17 -4
View File
@@ -311,6 +311,9 @@ public:
//
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
VAR_FSM_ARC_INCLUDE_COND, // V3LinkParse moves to AstVar::attrFsmArcInclCond
VAR_FSM_RESET_ARC, // V3LinkParse moves to AstVar::attrFsmResetArc
VAR_FSM_STATE, // V3LinkParse moves to AstVar::attrFsmState
VAR_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
@@ -336,10 +339,10 @@ public:
"ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID",
"FUNC_ARG_PROTO", "FUNC_RETURN_PROTO",
"TYPEID", "TYPENAME",
"VAR_BASE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC",
"VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW",
"VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT",
"VAR_SPLIT_VAR"
"VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
"VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT",
"VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS",
"VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
};
// clang-format on
return names[m_e];
@@ -809,6 +812,11 @@ public:
EVENT_FIRE,
EVENT_IS_FIRED,
EVENT_IS_TRIGGERED,
FORCE_ADD,
FORCE_READ,
FORCE_READ_INDEX,
FORCE_RELEASE,
FORCE_TOUCH,
FORK_DONE,
FORK_INIT,
FORK_JOIN,
@@ -955,6 +963,11 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{EVENT_FIRE, "fire", false}, \
{EVENT_IS_FIRED, "isFired", true}, \
{EVENT_IS_TRIGGERED, "isTriggered", true}, \
{FORCE_ADD, "addForce", false}, \
{FORCE_READ, "read", true}, \
{FORCE_READ_INDEX, "readIndex", true}, \
{FORCE_RELEASE, "release", false}, \
{FORCE_TOUCH, "touch", false}, \
{FORK_DONE, "done", false}, \
{FORK_INIT, "init", false}, \
{FORK_JOIN, "join", false}, \
+83
View File
@@ -64,6 +64,8 @@ public:
// Someday we will generically support data types on every expr node
// Until then isOpaque indicates we shouldn't constant optimize this node type
bool isOpaque() const { return VN_IS(this, CvtPackString); }
// True for SVA multi-cycle sequence nodes (SExpr, SConsRep, etc.)
virtual bool isMultiCycleSva() const { return false; }
bool isLValue() const;
// Wrap This expression into an AstStmtExpr to denote it occurs in statement position
@@ -2172,6 +2174,7 @@ public:
return m_unbounded == VN_DBG_AS(samep, SConsRep)->m_unbounded; // LCOV_EXCL_LINE
}
bool unbounded() const { return m_unbounded; }
bool isMultiCycleSva() const override { return true; }
};
class AstSExpr final : public AstNodeExpr {
// Sequence expression
@@ -2196,6 +2199,7 @@ public:
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
bool isMultiCycleSva() const override { return true; }
};
class AstSFormatArg final : public AstNodeExpr {
// Information for formatting each argument to AstSFormat,
@@ -2314,6 +2318,7 @@ public:
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
bool isMultiCycleSva() const override { return true; }
};
class AstSNonConsRep final : public AstNodeExpr {
// Nonconsecutive repetition: expr [= count]
@@ -2330,6 +2335,7 @@ public:
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
bool isMultiCycleSva() const override { return true; }
};
class AstSScanF final : public AstNodeExpr {
// @astgen op1 := exprsp : List[AstNodeExpr] // VarRefs for results
@@ -2883,6 +2889,7 @@ public:
};
class AstConcat final : public AstNodeBiop {
// If you're looking for {#{}}, see AstReplicate
// @astgen makeDfgVertex
public:
AstConcat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Concat(fl, lhsp, rhsp) {
@@ -2932,6 +2939,7 @@ public:
bool stringFlavor() const override { return true; }
};
class AstDiv final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Div(fl, lhsp, rhsp) {
@@ -2974,6 +2982,7 @@ public:
bool doubleFlavor() const override { return true; }
};
class AstDivS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_DivS(fl, lhsp, rhsp) {
@@ -2997,6 +3006,7 @@ public:
};
class AstEqWild final : public AstNodeBiop {
// Note wildcard operator rhs differs from lhs
// @astgen makeDfgVertex
public:
AstEqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) {
@@ -3103,6 +3113,7 @@ public:
bool sizeMattersRhs() const override { return false; }
};
class AstGt final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstGt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Gt(fl, lhsp, rhsp) {
@@ -3165,6 +3176,7 @@ public:
bool stringFlavor() const override { return true; }
};
class AstGtS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstGtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_GtS(fl, lhsp, rhsp) {
@@ -3186,6 +3198,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstGte final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstGte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Gte(fl, lhsp, rhsp) {
@@ -3248,6 +3261,7 @@ public:
bool stringFlavor() const override { return true; }
};
class AstGteS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstGteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_GteS(fl, lhsp, rhsp) {
@@ -3269,6 +3283,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstLogAnd final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLogAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) {
@@ -3290,6 +3305,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
};
class AstLogIf final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLogIf(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) {
@@ -3311,6 +3327,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
};
class AstLogOr final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLogOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) {
@@ -3332,6 +3349,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
};
class AstLt final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Lt(fl, lhsp, rhsp) {
@@ -3394,6 +3412,7 @@ public:
bool stringFlavor() const override { return true; }
};
class AstLtS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LtS(fl, lhsp, rhsp) {
@@ -3415,6 +3434,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstLte final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Lte(fl, lhsp, rhsp) {
@@ -3477,6 +3497,7 @@ public:
bool stringFlavor() const override { return true; }
};
class AstLteS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstLteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LteS(fl, lhsp, rhsp) {
@@ -3498,6 +3519,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstModDiv final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstModDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) {
@@ -3519,6 +3541,7 @@ public:
int instrCount() const override { return widthInstrs() * INSTR_COUNT_INT_DIV; }
};
class AstModDivS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstModDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) {
@@ -3541,6 +3564,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstNeqWild final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstNeqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) {
@@ -3560,6 +3584,7 @@ public:
bool sizeMattersRhs() const override { return false; }
};
class AstPow final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstPow(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Pow(fl, lhsp, rhsp) {
@@ -3600,6 +3625,7 @@ public:
bool doubleFlavor() const override { return true; }
};
class AstPowSS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstPowSS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) {
@@ -3621,6 +3647,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstPowSU final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstPowSU(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) {
@@ -3642,6 +3669,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstPowUS final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstPowUS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) {
@@ -3741,6 +3769,7 @@ public:
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
bool isMultiCycleSva() const override { return true; }
};
class AstSIntersect final : public AstNodeBiop {
// Sequence 'intersect' (IEEE 1800-2023 16.9.6): both operands match with equal length.
@@ -3765,6 +3794,7 @@ public:
bool sizeMattersRhs() const override { return false; }
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
// LCOV_EXCL_STOP
bool isMultiCycleSva() const override { return true; }
};
class AstSOr final : public AstNodeBiop {
// Sequence 'or' (IEEE 1800-2023 16.9.7): at least one operand sequence must match.
@@ -3787,6 +3817,7 @@ public:
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
bool isMultiCycleSva() const override { return true; }
};
class AstSThroughout final : public AstNodeBiop {
// expr throughout seq (IEEE 1800-2023 16.9.9)
@@ -3809,6 +3840,30 @@ public:
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
// LCOV_EXCL_STOP
bool isMultiCycleSva() const override { return true; }
};
class AstSWithin final : public AstNodeBiop {
// seq1 within seq2 (IEEE 1800-2023 16.9.10)
public:
AstSWithin(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_SWithin(fl, lhsp, rhsp) {
dtypeSetBit();
}
ASTGEN_MEMBERS_AstSWithin;
// LCOV_EXCL_START // Lowered in V3AssertNfa before these are called
void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override {
out.opLogAnd(lhs, rhs);
}
string emitVerilog() override { return "%k(%l %fwithin %r)"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
bool cleanLhs() const override { return true; }
bool cleanRhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
bool sizeMattersRhs() const override { return false; }
// LCOV_EXCL_STOP
bool isMultiCycleSva() const override { return true; }
};
class AstSel final : public AstNodeBiop {
// *Resolved* (tyep checked) multiple bit range extraction. Always const width
@@ -3867,6 +3922,7 @@ public:
void declElWidth(int flag) { m_declElWidth = flag; }
};
class AstShiftL final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstShiftL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) {
@@ -3910,6 +3966,7 @@ public:
bool sizeMattersRhs() const override { return false; }
};
class AstShiftR final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstShiftR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) {
@@ -3957,6 +4014,7 @@ public:
class AstShiftRS final : public AstNodeBiop {
// Shift right with sign extension, >>> operator
// Output data type's width determines which bit is used for sign extension
// @astgen makeDfgVertex
public:
AstShiftRS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftRS(fl, lhsp, rhsp) {
@@ -4003,6 +4061,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstSub final : public AstNodeBiop {
// @astgen makeDfgVertex
public:
AstSub(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Sub(fl, lhsp, rhsp) {
@@ -4068,6 +4127,7 @@ public:
// === AstNodeBiCom ===
class AstEq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public:
AstEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Eq(fl, lhsp, rhsp) {
@@ -4090,6 +4150,7 @@ public:
bool sizeMattersRhs() const override { return false; }
};
class AstEqCase final : public AstNodeBiCom {
// @astgen makeDfgVertex
public:
AstEqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) {
@@ -4170,6 +4231,7 @@ public:
int instrCount() const override { return INSTR_COUNT_STR; }
};
class AstLogEq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public:
AstLogEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) {
@@ -4191,6 +4253,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
};
class AstNeq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public:
AstNeq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Neq(fl, lhsp, rhsp) {
@@ -4212,6 +4275,7 @@ public:
bool sizeMattersRhs() const override { return false; }
};
class AstNeqCase final : public AstNodeBiCom {
// @astgen makeDfgVertex
public:
AstNeqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) {
@@ -4294,6 +4358,7 @@ public:
// === AstNodeBiComAsv ===
class AstAdd final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstAdd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Add(fl, lhsp, rhsp) {
@@ -4335,6 +4400,7 @@ public:
bool doubleFlavor() const override { return true; }
};
class AstAnd final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_And(fl, lhsp, rhsp) {
@@ -4356,6 +4422,7 @@ public:
const char* widthMismatch() const override VL_MT_STABLE;
};
class AstMul final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstMul(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Mul(fl, lhsp, rhsp) {
@@ -4398,6 +4465,7 @@ public:
bool doubleFlavor() const override { return true; }
};
class AstMulS final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstMulS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_MulS(fl, lhsp, rhsp) {
@@ -4421,6 +4489,7 @@ public:
bool signedFlavor() const override { return true; }
};
class AstOr final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Or(fl, lhsp, rhsp) {
@@ -4442,6 +4511,7 @@ public:
const char* widthMismatch() const override VL_MT_STABLE;
};
class AstXor final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public:
AstXor(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Xor(fl, lhsp, rhsp) {
@@ -4499,6 +4569,7 @@ public:
// === AstNodeSel ===
class AstArraySel final : public AstNodeSel {
// @astgen makeDfgVertex
void init(const AstNode* fromp) {
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) {
// Strip off array to find what array references
@@ -4611,6 +4682,7 @@ public:
// === AstNodeStream ===
class AstStreamL final : public AstNodeStream {
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
// @astgen makeDfgVertex
public:
AstStreamL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {}
@@ -4629,6 +4701,7 @@ public:
};
class AstStreamR final : public AstNodeStream {
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
// @astgen makeDfgVertex
public:
AstStreamR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {}
@@ -4917,6 +4990,7 @@ class AstCond final : public AstNodeTriop {
// @astgen alias op1 := condp
// @astgen alias op2 := thenp
// @astgen alias op3 := elsep
// @astgen makeDfgVertex
public:
AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep);
ASTGEN_MEMBERS_AstCond;
@@ -5265,6 +5339,7 @@ public:
};
class AstCountOnes final : public AstNodeUniop {
// Number of bits set in vector
// @astgen makeDfgVertex
public:
AstCountOnes(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_CountOnes(fl, lhsp) {}
@@ -5296,6 +5371,7 @@ public:
};
class AstExtend final : public AstNodeUniop {
// Expand a value into a wider entity by 0 extension. Width is implied from nodep->width()
// @astgen makeDfgVertex
public:
AstExtend(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Extend(fl, lhsp) {}
@@ -5319,6 +5395,7 @@ public:
};
class AstExtendS final : public AstNodeUniop {
// Expand a value into a wider entity by sign extension. Width is implied from nodep->width()
// @astgen makeDfgVertex
public:
AstExtendS(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_ExtendS(fl, lhsp) {}
@@ -5465,6 +5542,7 @@ public:
bool sizeMattersLhs() const override { return false; }
};
class AstLogNot final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstLogNot(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_LogNot(fl, lhsp) {
@@ -5496,6 +5574,7 @@ public:
bool sizeMattersLhs() const override { return false; }
};
class AstNegate final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstNegate(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Negate(fl, lhsp) {
@@ -5529,6 +5608,7 @@ public:
bool doubleFlavor() const override { return true; }
};
class AstNot final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstNot(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Not(fl, lhsp) {
@@ -5650,6 +5730,7 @@ public:
bool isSystemFunc() const override { return true; }
};
class AstRedAnd final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstRedAnd(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedAnd(fl, lhsp) {
@@ -5664,6 +5745,7 @@ public:
bool sizeMattersLhs() const override { return false; }
};
class AstRedOr final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstRedOr(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedOr(fl, lhsp) {
@@ -5678,6 +5760,7 @@ public:
bool sizeMattersLhs() const override { return false; }
};
class AstRedXor final : public AstNodeUniop {
// @astgen makeDfgVertex
public:
AstRedXor(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedXor(fl, lhsp) {
+30 -3
View File
@@ -656,7 +656,8 @@ class AstCell final : public AstNode {
// A instantiation cell or interface call (don't know which until link)
// @astgen op1 := pinsp : List[AstPin] // List of port assignments
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
// @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances
// @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 ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
@@ -680,7 +681,7 @@ public:
, m_trace{true} {
addPinsp(pinsp);
addParamsp(paramsp);
this->rangep(rangep);
addRangep(rangep);
}
ASTGEN_MEMBERS_AstCell;
// No cloneRelink, we presume cloneee's want the same module linkages
@@ -1933,6 +1934,9 @@ class AstVar final : public AstNode {
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
bool m_attrSFormat : 1; // User sformat attribute
bool m_attrSplitVar : 1; // declared with split_var metacomment
bool m_attrFsmState : 1; // declared with fsm_state metacomment
bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
bool m_fileDescr : 1; // File descriptor
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
bool m_isConst : 1; // Table contains constant data
@@ -1991,6 +1995,9 @@ class AstVar final : public AstNode {
m_attrIsolateAssign = false;
m_attrSFormat = false;
m_attrSplitVar = false;
m_attrFsmState = false;
m_attrFsmResetArc = false;
m_attrFsmArcInclCond = false;
m_fileDescr = false;
m_gotNansiType = false;
m_isConst = false;
@@ -2135,6 +2142,9 @@ public:
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
void attrSFormat(bool flag) { m_attrSFormat = flag; }
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
void attrFsmState(bool flag) { m_attrFsmState = flag; }
void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; }
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
void rand(const VRandAttr flag) { m_rand = flag; }
void usedParam(bool flag) { m_usedParam = flag; }
void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
@@ -2298,6 +2308,9 @@ public:
bool attrFileDescr() const { return m_fileDescr; }
bool attrSFormat() const { return m_attrSFormat; }
bool attrSplitVar() const { return m_attrSplitVar; }
bool attrFsmState() const { return m_attrFsmState; }
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
AstIface* sensIfacep() const { return m_sensIfacep; }
VRandAttr rand() const { return m_rand; }
@@ -2383,12 +2396,22 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl {
// Coverage analysis point declaration
// Used for other than toggle types of coverage
string m_linescov;
string m_fsmVar;
string m_fsmFrom;
string m_fsmTo;
string m_fsmTag;
int m_offset; // Offset column numbers to uniq-ify IFs
public:
AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment,
const string& linescov, int offset)
const string& linescov, int offset, const string& fsmVar = "",
const string& fsmFrom = "", const string& fsmTo = "",
const string& fsmTag = "")
: ASTGEN_SUPER_CoverOtherDecl(fl, page, comment)
, m_linescov{linescov}
, m_fsmVar{fsmVar}
, m_fsmFrom{fsmFrom}
, m_fsmTo{fsmTo}
, m_fsmTag{fsmTag}
, m_offset{offset} {}
ASTGEN_MEMBERS_AstCoverOtherDecl;
void dump(std::ostream& str) const override;
@@ -2396,6 +2419,10 @@ public:
int offset() const { return m_offset; }
int size() const override { return 1; }
const string& linescov() const { return m_linescov; }
const string& fsmVar() const { return m_fsmVar; }
const string& fsmFrom() const { return m_fsmFrom; }
const string& fsmTo() const { return m_fsmTo; }
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();
+14
View File
@@ -3004,6 +3004,9 @@ void AstVar::dump(std::ostream& str) const {
if (processQueue()) str << " [PROCQ]";
if (sampled()) str << " [SAMPLED]";
if (attrIsolateAssign()) str << " [aISO]";
if (attrFsmState()) str << " [aFSMSTATE]";
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
if (attrFileDescr()) str << " [aFD]";
if (isFuncReturn()) {
str << " [FUNCRTN]";
@@ -3036,6 +3039,9 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, processQueue);
dumpJsonBoolFuncIf(str, sampled);
dumpJsonBoolFuncIf(str, attrIsolateAssign);
dumpJsonBoolFuncIf(str, attrFsmState);
dumpJsonBoolFuncIf(str, attrFsmResetArc);
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
dumpJsonBoolFuncIf(str, attrFileDescr);
dumpJsonBoolFuncIf(str, isDpiOpenArray);
dumpJsonBoolFuncIf(str, isFuncReturn);
@@ -3283,10 +3289,18 @@ void AstNodeCoverDecl::dumpJson(std::ostream& str) const {
void AstCoverOtherDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str);
if (!linescov().empty()) str << " lc=" << linescov();
if (!fsmVar().empty()) str << " fv=" << fsmVar();
if (!fsmFrom().empty()) str << " ff=" << fsmFrom();
if (!fsmTo().empty()) str << " ft=" << fsmTo();
if (!fsmTag().empty()) str << " fg=" << fsmTag();
}
void AstCoverOtherDecl::dumpJson(std::ostream& str) const {
this->AstNodeCoverDecl::dumpJson(str);
dumpJsonStrFunc(str, linescov);
dumpJsonStrFunc(str, fsmVar);
dumpJsonStrFunc(str, fsmFrom);
dumpJsonStrFunc(str, fsmTo);
dumpJsonStrFunc(str, fsmTag);
}
void AstCoverToggleDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str);
+3 -1
View File
@@ -2229,7 +2229,9 @@ class ConstVisitor final : public VNVisitor {
&& !VN_IS(nodep, AssignDly)) {
// X = X. Quite pointless, though X <= X may override another earlier assignment
if (VN_IS(nodep, AssignW)) {
nodep->v3error("Wire inputs its own output, creating circular logic (wire x=x)");
if (!v3Global.opt.pedantic())
nodep->v3error(
"Wire inputs its own output, creating circular logic (wire x=x)");
return false; // Don't delete the assign, or V3Gate will freak out
} else {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
+5 -1
View File
@@ -329,8 +329,12 @@ class DeadVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) {
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) {
nodep->classOrPackagep(nullptr);
+161 -15
View File
@@ -97,6 +97,11 @@ std::unique_ptr<DfgGraph> DfgGraph::clone() const {
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::Rep: {
DfgRep* const cp = new DfgRep{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
break;
}
case VDfgType::UnitArray: {
DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtype()};
vtxp2clonep.emplace(&vtx, cp);
@@ -672,7 +677,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
case VDfgType::Add:
case VDfgType::And:
case VDfgType::BufIf1:
case VDfgType::Div:
case VDfgType::DivS:
case VDfgType::ModDiv:
@@ -713,12 +717,10 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
return;
}
case VDfgType::Replicate: {
// TODO: model DfgReplicate without an explicit 'countp' which is always constant
const DfgReplicate& v = *as<DfgReplicate>();
case VDfgType::Rep: {
const DfgRep& v = *as<DfgRep>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.srcp()->isPacked(), "'srcp' should be same type");
CHECK(v.countp()->isPacked(), "'countp' should be Packed type");
CHECK(v.size() % v.srcp()->size() == 0, "Not a replicate");
return;
}
@@ -759,16 +761,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
return;
}
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout: {
// LCOV_EXCL_START // Lowered before DFG
UASSERT_OBJ(false, this, "SAnd/SIntersect/SOr/SThroughout should be removed before DFG");
return;
// LCOV_EXCL_STOP
}
case VDfgType::LogAnd:
case VDfgType::LogEq:
case VDfgType::LogIf:
@@ -936,6 +928,160 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
delete this;
}
class DfgPatternString final {
std::ostream& m_os;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Multiplicity and depth of vertices
std::map<const DfgVertex*, std::pair<uint32_t, uint32_t>> m_multiplicityAndDepth;
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) {
std::pair<uint32_t, uint32_t>& value = m_multiplicityAndDepth
.emplace(std::piecewise_construct, //
std::forward_as_tuple(&vtx), //
std::forward_as_tuple(0, depth))
.first->second;
value.first += 1;
value.second = std::max(value.second, depth);
if (!depth) return;
vtx.foreachSource([&](const DfgVertex& src) {
recordMultiplicityAndDepth(src, depth - 1);
return false;
});
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) {
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
m_os << "(CONST ZERO)";
} else if (constp->isOnes()) {
m_os << "(CONST ONES)";
} else {
m_os << "(CONST #" << internConst(*constp) << ')';
}
} else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) {
// Base case 2: vertex appearing multiple times
m_os << internVertex(vtx);
} else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) {
// Base case 3: vertex with no inputs (input variable)
m_os << '(' << vtx.typeName() << ')';
} else if (depth == 0) {
// Base case 4: deep vertex (apperaing only once)
m_os << "_";
} else {
// Recursively print an S-expression for the vertex
m_os << '(';
// Name
m_os << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
m_os << '@';
if (selp->lsb() == 0) {
m_os << '0';
} else {
m_os << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
m_os << ' ';
render(src, depth - 1, false);
return false;
});
// S-expression end
m_os << ')';
}
// Annotate type
m_os << ':';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(m_os);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
m_os << '1';
} else if (width <= VL_QUADSIZE) {
m_os << internWordWidth(width);
} else {
m_os << internWideWidth(width);
}
}
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) m_os << '*';
}
public:
DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth)
: m_os{os} {
recordMultiplicityAndDepth(vtx, depth);
render(vtx, depth, false);
using Pair = std::pair<std::string, const DfgVertex*>;
std::vector<Pair> vertices;
for (const auto& pair : m_multiplicityAndDepth) {
if (pair.second.first == 1) continue;
vertices.emplace_back(internVertex(*pair.first), pair.first);
}
std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) { //
return a.first < b.first;
});
for (const Pair& pair : vertices) {
m_os << " | " << pair.first << " is ";
render(*pair.second, m_multiplicityAndDepth.at(pair.second).second);
}
}
};
std::string DfgVertex::patternString(uint32_t depth) const {
std::ostringstream oss;
DfgPatternString{oss, *this, depth};
return oss.str();
}
//------------------------------------------------------------------------------
// DfgVisitor
+29 -12
View File
@@ -352,6 +352,9 @@ public:
// Human-readable name for source operand with given index for debugging
virtual std::string srcName(size_t idx) const = 0;
// S-expression inspired dump of vertex and operands for debugging
std::string patternString(uint32_t depth = 0) const;
};
// DfgVertex visitor
@@ -812,9 +815,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
bool DfgVertex::isCheaperThanLoad() const {
// Constants
if (is<DfgConst>()) return true;
// Variables
if (is<DfgVertexVar>()) return true;
// Array sels are just address computation
if (is<DfgArraySel>()) return true;
// Small constant select from variable
// Small select from variable
if (const DfgSel* const selp = cast<DfgSel>()) {
if (!selp->fromp()->is<DfgVarPacked>()) return false;
if (selp->fromp()->width() <= VL_QUADSIZE) return true;
@@ -825,20 +830,32 @@ bool DfgVertex::isCheaperThanLoad() const {
// Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
if (const DfgConcat* const catp = cast<DfgConcat>()) {
if (catp->width() > VL_QUADSIZE) return false;
const DfgConst* const lCatp = catp->lhsp()->cast<DfgConst>();
if (!lCatp) return false;
if (!lCatp->isZero()) return false;
const DfgConst* const lConstp = catp->lhsp()->cast<DfgConst>();
if (!lConstp || !lConstp->isZero()) return false;
return catp->rhsp()->isCheaperThanLoad();
}
// Reduction of a cheap vertex
if (const DfgRedOr* const redOrp = cast<DfgRedOr>()) {
return redOrp->srcp()->isCheaperThanLoad();
// Reduction of a narrow cheap vertex
if (is<DfgRedOr>() //
|| is<DfgRedAnd>() //
|| is<DfgRedXor>()) {
const DfgVertex* const srcp = as<DfgVertexUnary>()->srcp();
return srcp->width() <= VL_QUADSIZE && srcp->isCheaperThanLoad();
}
if (const DfgRedAnd* const redAndp = cast<DfgRedAnd>()) {
return redAndp->srcp()->isCheaperThanLoad();
}
if (const DfgRedXor* const redXorp = cast<DfgRedXor>()) {
return redXorp->srcp()->isCheaperThanLoad();
// Comparisons of a narrow cheap vertex with constant
if (is<DfgEq>() //
|| is<DfgNeq>() //
|| is<DfgLt>() //
|| is<DfgLte>() //
|| is<DfgGt>() //
|| is<DfgGte>() //
|| is<DfgLtS>() //
|| is<DfgLteS>() //
|| is<DfgGtS>() //
|| is<DfgGteS>()) {
const DfgVertexBinary* const binp = as<DfgVertexBinary>();
const DfgVertex* const lhsp = binp->inputp(0);
const DfgVertex* const rhsp = binp->inputp(1);
return lhsp->width() <= VL_QUADSIZE && lhsp->is<DfgConst>() && rhsp->isCheaperThanLoad();
}
// Otherwise probably not
return false;
+143 -33
View File
@@ -22,8 +22,8 @@
#include "V3Hash.h"
#include <algorithm>
#include <deque>
#include <fstream>
#include <queue>
#include <unordered_map>
#include <vector>
@@ -374,14 +374,13 @@ class TraceDriver final : public DfgVisitor {
SET_RESULT(resp);
}
void visit(DfgReplicate* vtxp) override {
void visit(DfgRep* vtxp) override {
DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
// If we need more bits than the source, then we need the whole source
if (m_msb - m_lsb + 1 > sWidth) {
DfgReplicate* const repp = make<DfgReplicate>(vtxp, vtxp->width());
DfgRep* const repp = make<DfgRep>(vtxp, vtxp->width());
repp->srcp(trace(srcp, sWidth - 1, 0));
repp->countp(vtxp->countp()); // Always a DfgConst
DfgSel* const resp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
resp->fromp(repp);
resp->lsb(m_lsb);
@@ -471,6 +470,44 @@ class TraceDriver final : public DfgVisitor {
void visit(DfgGt* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgGte* vtxp) override { SET_RESULT(traceCmp(vtxp)); }
void visit(DfgShiftRS* vtxp) override {
DfgVertex* const lhsp = vtxp->lhsp();
if (const DfgConst* const rConstp = vtxp->rhsp()->cast<DfgConst>()) {
const uint32_t shiftAmnt = rConstp->toU32();
// Width of lower half of result
const uint32_t lowerWidth = shiftAmnt > vtxp->width() ? 0 : vtxp->width() - shiftAmnt;
// If the traced bits are wholly in the input
if (lowerWidth > m_msb) {
SET_RESULT(trace(lhsp, m_msb + shiftAmnt, m_lsb + shiftAmnt));
return;
}
// If the traced bits are wholly in the extension
if (m_lsb >= lowerWidth) {
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(lhsp, lhsp->width() - 1, lhsp->width() - 1));
SET_RESULT(resp);
return;
}
// The traced bits span both sides
DfgExtendS* const resp = make<DfgExtendS>(vtxp, m_msb - m_lsb + 1);
resp->srcp(trace(lhsp, lowerWidth - 1 + shiftAmnt, m_lsb + shiftAmnt));
SET_RESULT(resp);
return;
}
DfgShiftRS* const shiftrsp = make<DfgShiftRS>(vtxp, vtxp->lhsp()->width() - m_lsb);
shiftrsp->rhsp(trace(vtxp->rhsp(), vtxp->rhsp()->width() - 1, 0));
shiftrsp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, m_lsb));
if (m_msb == vtxp->lhsp()->width() - 1) {
SET_RESULT(shiftrsp);
return;
}
DfgSel* const selp = make<DfgSel>(vtxp, m_msb - m_lsb + 1);
selp->fromp(shiftrsp);
selp->lsb(0);
SET_RESULT(selp);
}
void visit(DfgShiftR* vtxp) override {
DfgVertex* const lhsp = vtxp->lhsp();
if (const DfgConst* const rConstp = vtxp->rhsp()->cast<DfgConst>()) {
@@ -581,12 +618,19 @@ public:
};
class IndependentBits final : public DfgVisitor {
// TYPES
struct VertexState final {
size_t m_rpoNumber = 0; // Reverse Postorder Number of vertex
bool m_isOnWorkList = false; // Whether the vertex is on the work list
};
// STATE
const Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map
// Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC
std::unordered_map<const DfgVertex*, V3Number> m_vtxp2Mask;
std::deque<DfgVertex*> m_workList; // Work list for the traversal
std::unordered_map<DfgVertex*, bool> m_onWorkList; // Marks vertices on the work list
// Work list for the traversal (min-queue of vertex RPO numbers)
std::priority_queue<size_t, std::vector<size_t>, std::greater<size_t>> m_workQueue;
std::unordered_map<const DfgVertex*, VertexState> m_vtxp2State; // State of each vertex
#ifdef VL_DEBUG
std::ofstream m_lineCoverageFile; // Line coverage file, just for testing
@@ -712,8 +756,8 @@ class IndependentBits final : public DfgVisitor {
m.opSelInto(MASK(lhsp), rhsp->width(), lhsp->width());
}
void visit(DfgReplicate* vtxp) override {
const uint32_t count = vtxp->countp()->as<DfgConst>()->toU32();
void visit(DfgRep* vtxp) override {
const uint32_t count = vtxp->count();
const DfgVertex* const srcp = vtxp->srcp();
const uint32_t sWidth = srcp->width();
V3Number& vMask = MASK(vtxp);
@@ -816,6 +860,38 @@ class IndependentBits final : public DfgVisitor {
MASK(vtxp).setBit(0, independent ? '1' : '0');
}
void visit(DfgShiftRS* vtxp) override {
DfgVertex* const rhsp = vtxp->rhsp();
DfgVertex* const lhsp = vtxp->lhsp();
const uint32_t width = vtxp->width();
// Constant shift can be computed precisely
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
if (shiftAmount >= width) {
if (MASK(lhsp).bitIs0(width - 1)) {
MASK(vtxp).setAllBits0();
} else {
MASK(vtxp).setAllBits1();
}
} else {
V3Number& m = MASK(vtxp);
m.opShiftRS(MASK(lhsp), rConstp->num(), width);
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
return;
}
// Otherwise, as the shift amount is non-negative, all independent
// and consecutive top bits in the lhs yield an independent result
// if the shift amount is independent.
if (MASK(rhsp).isEqAllOnes()) {
V3Number& m = MASK(vtxp);
m = MASK(lhsp);
floodTowardsLsb(m);
}
}
void visit(DfgShiftR* vtxp) override {
DfgVertex* const rhsp = vtxp->rhsp();
DfgVertex* const lhsp = vtxp->lhsp();
@@ -824,10 +900,10 @@ class IndependentBits final : public DfgVisitor {
// Constant shift can be computed precisely
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
V3Number& m = MASK(vtxp);
if (shiftAmount >= width) {
m.setAllBits1();
MASK(vtxp).setAllBits1();
} else {
V3Number& m = MASK(vtxp);
m.opShiftR(MASK(lhsp), rConstp->num());
m.opSetRange(width - shiftAmount, shiftAmount, '1');
}
@@ -852,10 +928,10 @@ class IndependentBits final : public DfgVisitor {
// Constant shift can be computed precisely
if (DfgConst* const rConstp = rhsp->cast<DfgConst>()) {
const uint32_t shiftAmount = rConstp->toU32();
V3Number& m = MASK(vtxp);
if (shiftAmount >= width) {
m.setAllBits1();
MASK(vtxp).setAllBits1();
} else {
V3Number& m = MASK(vtxp);
m.opShiftL(MASK(lhsp), rConstp->num());
m.opSetRange(0, shiftAmount, '1');
}
@@ -891,16 +967,30 @@ class IndependentBits final : public DfgVisitor {
return false;
}
// Otherwise just enqueue it
bool& onWorkList = m_onWorkList[&sink];
if (!onWorkList) {
VertexState& state = m_vtxp2State.at(&sink);
if (!state.m_isOnWorkList) {
UINFO(9, "Enqueuing vertex " << &sink << " " << sink.typeName());
m_workList.emplace_back(&sink);
m_workQueue.push(state.m_rpoNumber);
}
onWorkList = true;
state.m_isOnWorkList = true;
return false;
});
};
void dfsPostOrder(std::unordered_set<DfgVertex*>& visited, //
std::vector<DfgVertex*>& postOrderEnumeration, //
DfgVertex& vtx) {
// Mark visited, skip if already visited
if (!visited.insert(&vtx).second) return;
// Visit un-visited successors
vtx.foreachSink([&](DfgVertex& snk) {
dfsPostOrder(visited, postOrderEnumeration, snk);
return false;
});
// Add to post order enumeration
postOrderEnumeration.emplace_back(&vtx);
};
IndependentBits(DfgGraph& dfg, const Vtx2Scc& vtx2Scc)
: m_vtx2Scc{vtx2Scc} {
@@ -913,31 +1003,51 @@ class IndependentBits final : public DfgVisitor {
}
#endif
// Compute Reverse-Postorder enumeration of vertices
std::vector<DfgVertex*> rpoEnumeration;
std::unordered_set<DfgVertex*> visited;
dfg.forEachVertex([&](DfgVertex& vtx) {
if (!vtx.nInputs()) dfsPostOrder(visited, rpoEnumeration, vtx);
});
dfg.forEachVertex([&](DfgVertex& vtx) { //
dfsPostOrder(visited, rpoEnumeration, vtx);
});
std::reverse(rpoEnumeration.begin(), rpoEnumeration.end());
UASSERT(rpoEnumeration.size() == dfg.size(), "Inconsistent vertex count");
// Initialzie VertexState for each vertex
for (size_t i = 0; i < rpoEnumeration.size(); ++i) {
VertexState& state = m_vtxp2State[rpoEnumeration[i]];
state.m_rpoNumber = i;
state.m_isOnWorkList = false;
}
// Set up the initial conditions:
// - For vertices not in an SCC, mark all bits as independent
// - For vertices in an SCC, dispatch to analyse at least once, as some vertices
// always assign constants bits, which are always independent (eg Extend/Shift)
// Enqueue sinks of all SCC vertices that have at least one independent bit
dfg.forEachVertex([&](DfgVertex& vtx) {
if (!handledDirectly(vtx)) return;
if (m_vtx2Scc.at(&vtx)) return;
mask(vtx).setAllBits1();
});
dfg.forEachVertex([&](DfgVertex& vtx) {
if (!handledDirectly(vtx)) return;
if (!m_vtx2Scc.at(&vtx)) return;
iterate(&vtx);
UINFO(9, "Initial independent bits of " << &vtx << " " << vtx.typeName() << " are: "
<< mask(vtx).displayed(vtx.fileline(), "%b"));
if (!mask(vtx).isEqZero()) enqueueSinks(vtx);
});
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (m_vtx2Scc.at(vtxp)) continue;
mask(*vtxp).setAllBits1();
}
for (DfgVertex* const vtxp : rpoEnumeration) {
if (!handledDirectly(*vtxp)) continue;
if (!m_vtx2Scc.at(vtxp)) continue;
iterate(vtxp);
UINFO(9, "Initial independent bits of "
<< vtxp << " " << vtxp->typeName()
<< " are: " << mask(*vtxp).displayed(vtxp->fileline(), "%b"));
if (!mask(*vtxp).isEqZero()) enqueueSinks(*vtxp);
}
// Propagate independent bits until no more changes are made
while (!m_workList.empty()) {
while (!m_workQueue.empty()) {
// Grab next item
DfgVertex* const currp = m_workList.front();
m_workList.pop_front();
m_onWorkList[currp] = false;
DfgVertex* const currp = rpoEnumeration[m_workQueue.top()];
m_workQueue.pop();
m_vtxp2State.at(currp).m_isOnWorkList = false;
// Should not enqueue vertices that are not in an SCC
UASSERT_OBJ(m_vtx2Scc.at(currp), currp, "Vertex should be in an SCC");
// Should only enqueue packed vertices
+2 -12
View File
@@ -78,7 +78,6 @@ class V3DfgCse final {
case VDfgType::Add:
case VDfgType::And:
case VDfgType::ArraySel:
case VDfgType::BufIf1:
case VDfgType::Concat:
case VDfgType::Cond:
case VDfgType::CountOnes:
@@ -119,16 +118,12 @@ class V3DfgCse final {
case VDfgType::RedAnd:
case VDfgType::RedOr:
case VDfgType::RedXor:
case VDfgType::Replicate:
case VDfgType::Rep:
case VDfgType::ShiftL:
case VDfgType::ShiftR:
case VDfgType::ShiftRS:
case VDfgType::StreamL:
case VDfgType::StreamR:
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout:
case VDfgType::Sub:
case VDfgType::Xor: return V3Hash{};
}
@@ -205,7 +200,6 @@ class V3DfgCse final {
case VDfgType::Add:
case VDfgType::And:
case VDfgType::ArraySel:
case VDfgType::BufIf1:
case VDfgType::Concat:
case VDfgType::Cond:
case VDfgType::CountOnes:
@@ -246,15 +240,11 @@ class V3DfgCse final {
case VDfgType::RedAnd:
case VDfgType::RedOr:
case VDfgType::RedXor:
case VDfgType::Replicate:
case VDfgType::Rep:
case VDfgType::ShiftL:
case VDfgType::ShiftR:
case VDfgType::ShiftRS:
case VDfgType::StreamL:
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout:
case VDfgType::StreamR:
case VDfgType::Sub:
case VDfgType::Xor: return true;
+2 -7
View File
@@ -125,6 +125,8 @@ public:
// Thanks to the interning, equality is identity
bool operator==(const DfgDataType& that) const { return this == &that; }
bool operator!=(const DfgDataType& that) const { return this != &that; }
// Similarly for hash
V3Hash hash() const { return V3Hash{this}; }
// Type of elements, for arrays only
const DfgDataType& elemDtype() const {
@@ -132,13 +134,6 @@ public:
return *m_elemDtypep;
}
V3Hash hash() const {
V3Hash hash{static_cast<uint32_t>(m_kind)};
hash += m_size;
if (m_elemDtypep) hash += m_elemDtypep->hash();
return hash;
}
//-----------------------------------------------------------------------
// Static factory and management functions
+6
View File
@@ -228,6 +228,12 @@ class DfgToAstVisitor final : DfgVisitor {
m_resultp = new AstConst{vtxp->fileline(), vtxp->num()};
}
void visit(DfgRep* vtxp) override {
FileLine* const flp = vtxp->fileline();
AstNodeExpr* const srcp = convertDfgVertexToAstNodeExpr(vtxp->srcp());
m_resultp = new AstReplicate{flp, srcp, vtxp->count()};
}
void visit(DfgSel* vtxp) override {
FileLine* const flp = vtxp->fileline();
AstNodeExpr* const fromp = convertDfgVertexToAstNodeExpr(vtxp->fromp());
+9 -121
View File
@@ -21,133 +21,27 @@
#include "V3DfgPasses.h"
#include "V3File.h"
#include <algorithm>
#include <map>
#include <unordered_map>
class V3DfgPatternStats final {
static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Maps from pattern to the number of times it appears, for each pattern depth
std::vector<std::unordered_map<std::string, size_t>> m_patterCounts{MAX_PATTERN_DEPTH + 1};
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) {
bool deep = depth == 0;
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
ss << "'0";
} else if (constp->isOnes()) {
ss << "'1";
} else {
ss << internConst(*constp);
}
} else if (depth == 0) {
// Base case 2: deep vertex
ss << "_";
} else {
// Recursively print an S-expression for the vertex
// S-expression begin
ss << '(';
// Name
ss << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
ss << '@';
if (selp->lsb() == 0) {
ss << '0';
} else {
ss << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
ss << ' ';
if (render(ss, src, depth - 1)) deep = true;
return false;
});
// S-expression end
ss << ')';
}
// Annotate identity
ss << ":" << internVertex(vtx);
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) ss << '*';
// Annotate type
ss << '/';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(ss);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
ss << '1';
} else if (width <= VL_QUADSIZE) {
ss << internWordWidth(width);
} else {
ss << internWideWidth(width);
}
}
// Done
return deep;
}
void dump(std::ostream& os) {
using Line = std::pair<std::string, size_t>;
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
os << "DFG patterns with depth " << i << '\n';
// Pick up pattern accumulators with given depth
const auto& patternCounts = m_patterCounts[i];
auto& patternCounts = m_patterCounts[i];
// Remove patterns also present at shallower depths
for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) {
for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first);
}
// Sort patterns, first by descending frequency, then lexically
std::vector<Line> lines;
@@ -173,8 +67,8 @@ public:
V3DfgPatternStats() = default;
~V3DfgPatternStats() {
// File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix()
+ "__stats_dfg_patterns.txt";
const std::string filename
= v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt";
// Open, write, close
const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)};
if (ofp->fail()) v3fatal("Can't write file: " << filename);
@@ -184,13 +78,7 @@ public:
void accumulate(const DfgGraph& dfg) {
dfg.forEachVertex([&](const DfgVertex& vtx) {
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
std::ostringstream ss;
if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1;
m_internedConsts.clear();
m_internedSelLsbs.clear();
m_internedWordWidths.clear();
m_internedWideWidths.clear();
m_internedVertices.clear();
m_patterCounts[i][vtx.patternString(i)] += 1;
}
});
}
+2 -2
View File
@@ -154,9 +154,9 @@ class DataflowOptimize final {
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
endOfStage("peephole", dfg, acyclicComps);
// Accumulate patterns for reporting
if (v3Global.opt.stats()) {
if (v3Global.opt.dumpDfgPatterns()) {
V3DfgPasses::dumpPatterns(acyclicComps);
endOfStage("patterns");
endOfStage("dumpPatterns");
}
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
endOfStage("pushDownSels", dfg, acyclicComps);
+305 -141
View File
@@ -130,7 +130,6 @@ template <> const DfgDataType& resultDType<DfgPow> (const DfgVertex* lhsp
template <> const DfgDataType& resultDType<DfgPowSS> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
template <> const DfgDataType& resultDType<DfgPowSU> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
template <> const DfgDataType& resultDType<DfgPowUS> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
// template <> const DfgDataType& resultDType<DfgReplicate> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return ; }
template <> const DfgDataType& resultDType<DfgShiftL> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
template <> const DfgDataType& resultDType<DfgShiftR> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
template <> const DfgDataType& resultDType<DfgShiftRS> (const DfgVertex* lhsp, const DfgVertex* rhsp) { return lhsp->dtype(); }
@@ -178,7 +177,6 @@ template <> void foldOp<DfgPow> (V3Number& out, const V3Number& lhs, cons
template <> void foldOp<DfgPowSS> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSS(lhs, rhs); }
template <> void foldOp<DfgPowSU> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSU(lhs, rhs); }
template <> void foldOp<DfgPowUS> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowUS(lhs, rhs); }
template <> void foldOp<DfgReplicate> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opRepl(lhs, rhs); }
template <> void foldOp<DfgShiftL> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftL(lhs, rhs); }
template <> void foldOp<DfgShiftR> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftR(lhs, rhs); }
template <> void foldOp<DfgShiftRS> (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftRS(lhs, rhs, lhs.width()); }
@@ -208,6 +206,9 @@ class V3DfgPeephole final : public DfgVisitor {
size_t m_currentGeneration = 0; // Current generation number
size_t m_lastId = 0; // Last unique vertex ID assigned
size_t m_nTemps = 0; // Number of temporary variables created
// Scope for transient temporariy variables cerated in this pass. They should all be
// eliminated wihtin this pass, so anything should be ok, pick the top scope as easy to find.
AstScope* const m_tmpScopep = v3Global.rootp()->topScopep()->scopep();
// STATIC STATE
static V3DebugBisect s_debugBisect; // Debug aid
@@ -404,6 +405,13 @@ class V3DfgPeephole final : public DfgVisitor {
return make<Vertex>(examplep->fileline(), examplep->dtype(), operands...);
}
// Replicate 'bitp' to 'vtxp->width()' bits
DfgVertex* replicate(DfgVertex* vtxp, DfgVertex* bitp) {
UASSERT_OBJ(bitp->dtype() == m_bitDType, vtxp, "Expected 1-bit vertex");
if (vtxp->dtype() == m_bitDType) return bitp;
return make<DfgRep>(vtxp, bitp);
}
// Check two vertex are the same, or the same constant value
static bool isSame(const DfgVertex* ap, const DfgVertex* bp) {
if (ap == bp) return true;
@@ -561,15 +569,25 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
// Attempt to reuse associative binary expressions if hey already exist, e.g.:
// '(a OP (b OP c))' -> '(a OP b) OP c', iff '(a OP b)' already exists, or
// '(a OP c) OP b' iff '(a OP c)' already exists and the vertex is commutative.
// Only do this is 'b OP c' has a single use and can subsequently be removed,
// otherwise there is no improvement.
if (rSamep && !rSamep->hasMultipleSinks()) {
DfgVertex* const rlVtxp = rSamep->lhsp();
DfgVertex* const rrVtxp = rSamep->rhsp();
if VL_CONSTEXPR_CXX17 (IsCommutative<Vertex>::value) {
if (!lhsp->hasMultipleSinks() && rlVtxp->hasMultipleSinks()) {
APPLYING(ROTATE_ASSOC_COMM_MULTIUSE) {
replace(make<Vertex>(vtxp, rlVtxp, make<Vertex>(vtxp, lhsp, rrVtxp)));
return true;
}
}
}
// Attempt to reuse associative binary expressions if hey already exist, e.g.:
// '(a OP (b OP c))' -> '(a OP b) OP c', iff '(a OP b)' already exists, or
// '(a OP c) OP b' iff '(a OP c)' already exists and the vertex is commutative.
// Only do this if 'b OP c' has a single use and can subsequently be removed,
// otherwise there is no improvement.
// '(a OP (b OP c))' -> '(a OP b) OP c'
if (Vertex* const existingp
= m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) {
@@ -700,10 +718,10 @@ class V3DfgPeephole final : public DfgVisitor {
tryPushBitwiseOpThroughConcat(Vertex* const vtxp, DfgConst* constp, DfgConcat* concatp) {
FileLine* const flp = vtxp->fileline();
// If at least one of the sides of the Concat constant, or width 1 (i.e.: can be
// further simplified), then push the Vertex past the Concat
if (concatp->lhsp()->is<DfgConst>() || concatp->rhsp()->is<DfgConst>() //
|| concatp->lhsp()->dtype() == m_bitDType || concatp->rhsp()->dtype() == m_bitDType) {
// If at least one of the sides of the Concat constant, then push Vertex past Concat
DfgConst* const catLConstp = concatp->lhsp()->cast<DfgConst>();
DfgConst* const catRConstp = concatp->rhsp()->cast<DfgConst>();
if (catLConstp || catRConstp) {
APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) {
const uint32_t width = concatp->width();
const DfgDataType& lDtype = concatp->lhsp()->dtype();
@@ -712,14 +730,30 @@ class V3DfgPeephole final : public DfgVisitor {
const uint32_t rWidth = rDtype.size();
// The new Lhs vertex
DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
Vertex* const newLhsp = make<Vertex>(flp, lDtype, newLhsConstp, concatp->lhsp());
DfgVertex* const newLhsp = [&]() -> DfgVertex* {
DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
if (catLConstp) {
V3Number num{constp->fileline(), static_cast<int>(lWidth), 0u};
num.opSel(constp->num(), width - 1, rWidth);
foldOp<Vertex>(newLhsConstp->num(), num, catLConstp->num());
return newLhsConstp;
}
newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
return make<Vertex>(flp, lDtype, newLhsConstp, concatp->lhsp());
}();
// The new Rhs vertex
DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
Vertex* const newRhsp = make<Vertex>(flp, rDtype, newRhsConstp, concatp->rhsp());
DfgVertex* const newRhsp = [&]() -> DfgVertex* {
DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
if (catRConstp) {
V3Number num{constp->fileline(), static_cast<int>(rWidth), 0u};
num.opSel(constp->num(), rWidth - 1, 0);
foldOp<Vertex>(newRhsConstp->num(), num, catRConstp->num());
return newRhsConstp;
}
newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
return make<Vertex>(flp, rDtype, newRhsConstp, concatp->rhsp());
}();
// Replace this vertex
replace(make<DfgConcat>(concatp, newLhsp, newRhsp));
@@ -794,6 +828,46 @@ class V3DfgPeephole final : public DfgVisitor {
return false;
}
template <typename Bitwise>
VL_ATTR_WARN_UNUSED_RESULT bool tryPushBitwiseOpThrougSel(Bitwise* const vtxp) {
DfgVertex* const lhsp = vtxp->lhsp();
DfgVertex* const rhsp = vtxp->rhsp();
if (DfgSel* const lSelp = lhsp->cast<DfgSel>()) {
DfgSel* rSelp = nullptr;
DfgVertex* extrap = nullptr;
if (DfgSel* const selp = rhsp->cast<DfgSel>()) {
rSelp = selp;
} else if (Bitwise* const bitwisep = rhsp->cast<Bitwise>()) {
if (DfgSel* const rlSelp = bitwisep->lhsp()->template cast<DfgSel>()) {
rSelp = rlSelp;
extrap = bitwisep->rhsp();
} else if (DfgSel* const rrSelp = bitwisep->rhsp()->template cast<DfgSel>()) {
rSelp = rrSelp;
extrap = bitwisep->lhsp();
}
}
if (rSelp) {
DfgVertex* const lFromp = lSelp->fromp();
DfgVertex* const rFromp = rSelp->fromp();
if (lFromp->dtype() == rFromp->dtype() && lFromp->width() <= VL_QUADSIZE //
&& lSelp->lsb() == rSelp->lsb()) {
APPLYING(PUSH_BITWISE_THROUGH_SEL) {
Bitwise* const bwp
= make<Bitwise>(vtxp->fileline(), lSelp->fromp()->dtype(),
lSelp->fromp(), rSelp->fromp());
DfgVertex* resp = make<DfgSel>(vtxp, bwp, lSelp->lsb());
if (extrap) resp = make<Bitwise>(vtxp, resp, extrap);
replace(resp);
return true;
}
}
}
}
return false;
}
template <typename Bitwise>
VL_ATTR_WARN_UNUSED_RESULT bool tryReplaceBitwiseWithReduction(Bitwise* vtxp) {
UASSERT_OBJ(vtxp->width() == 1, vtxp, "Width must be 1");
@@ -803,13 +877,15 @@ class V3DfgPeephole final : public DfgVisitor {
DfgVertex* const rhsp = vtxp->rhsp();
if (DfgSel* const lSelp = lhsp->template cast<DfgSel>()) {
DfgSel* rSelp = rhsp->template cast<DfgSel>();
DfgSel* rSelp = nullptr;
DfgVertex* extrap = nullptr;
if (!rSelp) {
if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) {
rSelp = rBitwisep->lhsp()->template cast<DfgSel>();
extrap = rBitwisep->rhsp();
}
if (DfgSel* const selp = rhsp->template cast<DfgSel>()) {
rSelp = selp;
} else if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) {
rSelp = rBitwisep->lhsp()->template cast<DfgSel>();
extrap = rBitwisep->rhsp();
} else if (Reduction* const rRedp = rhsp->template cast<Reduction>()) {
rSelp = rRedp->srcp()->template cast<DfgSel>();
}
if (rSelp) {
uint32_t lsb = 0;
@@ -1021,6 +1097,94 @@ class V3DfgPeephole final : public DfgVisitor {
return {nullptr, 0, 0};
}
// The following patterns all unwind a Sel throgh it's source and replace it with
// another single Sel. Doing this one at a time can take a long time with nested
// concatenations/selects/etc, so instead unwind as much as possible in one go.
std::pair<DfgVertex*, uint32_t> unwindSel(DfgVertex* fromp, uint32_t lsb,
const uint32_t width) {
while (true) {
const uint32_t msb = lsb + width - 1;
// Sel from Concat
if (DfgConcat* const concatp = fromp->cast<DfgConcat>()) {
DfgVertex* const lhsp = concatp->lhsp();
DfgVertex* const rhsp = concatp->rhsp();
if (msb < rhsp->width()) {
// If the select is entirely from rhs, then replace with sel from rhs
APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) {
fromp = rhsp;
continue;
}
} else if (lsb >= rhsp->width()) {
// If the select is entirely from the lhs, then replace with sel from lhs
APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) {
fromp = lhsp;
lsb -= rhsp->width();
continue;
}
}
}
if (DfgRep* const repp = fromp->cast<DfgRep>()) {
// If the Sel is wholly into the source of the Replicate, push the Sel through
// the Replicate and apply it directly to the source of the Replicate.
const uint32_t srcWidth = repp->srcp()->width();
if (width <= srcWidth) {
const uint32_t newLsb = lsb % srcWidth;
const uint32_t newMsb = newLsb + width - 1;
if (newMsb < srcWidth) {
APPLYING(PUSH_SEL_THROUGH_REP) {
fromp = repp->srcp();
lsb = newLsb;
continue;
}
}
}
}
// Sel from Sel
if (DfgSel* const selp = fromp->cast<DfgSel>()) {
APPLYING(REPLACE_SEL_FROM_SEL) {
// Select from the source of the source Sel with adjusted LSB
fromp = selp->fromp();
lsb += selp->lsb();
continue;
}
}
// Sel from a partial variable (including narrowed vertex)
if (DfgVarPacked* const varp = fromp->cast<DfgVarPacked>()) {
if (varp->srcp() && !varp->isVolatile()) {
// 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) {
APPLYING(PUSH_SEL_THROUGH_SPLICE) {
fromp = driverp;
lsb -= driverLsb;
continue;
}
}
}
}
// No patterns matched, stop
break;
}
return {fromp, lsb};
}
// VISIT methods
void visit(DfgVertex*) override {}
@@ -1148,38 +1312,12 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
// Sel from Concat
if (DfgConcat* const concatp = fromp->cast<DfgConcat>()) {
DfgVertex* const lhsp = concatp->lhsp();
DfgVertex* const rhsp = concatp->rhsp();
if (msb < rhsp->width()) {
// If the select is entirely from rhs, then replace with sel from rhs
APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { //
replace(make<DfgSel>(vtxp, rhsp, vtxp->lsb()));
return;
}
} else if (lsb >= rhsp->width()) {
// If the select is entirely from the lhs, then replace with sel from lhs
APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) {
replace(make<DfgSel>(vtxp, lhsp, lsb - rhsp->width()));
return;
}
}
}
if (DfgReplicate* const repp = fromp->cast<DfgReplicate>()) {
// If the Sel is wholly into the source of the Replicate, push the Sel through the
// Replicate and apply it directly to the source of the Replicate.
const uint32_t srcWidth = repp->srcp()->width();
if (width <= srcWidth) {
const uint32_t newLsb = lsb % srcWidth;
if (newLsb + width <= srcWidth) {
APPLYING(PUSH_SEL_THROUGH_REPLICATE) {
replace(make<DfgSel>(vtxp, repp->srcp(), newLsb));
return;
}
}
// Unwind through bit packing in one go
{
const auto res = unwindSel(fromp, lsb, width);
if (res.first != fromp || res.second != lsb) {
replace(make<DfgSel>(vtxp, res.first, res.second));
return;
}
}
@@ -1197,15 +1335,6 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
// Sel from Sel
if (DfgSel* const selp = fromp->cast<DfgSel>()) {
APPLYING(REPLACE_SEL_FROM_SEL) {
// Select from the source of the source Sel with adjusted LSB
replace(make<DfgSel>(vtxp, selp->fromp(), lsb + selp->lsb()));
return;
}
}
// Sel from Cond
if (DfgCond* const condp = fromp->cast<DfgCond>()) {
if (!condp->hasMultipleSinks()) {
@@ -1239,31 +1368,6 @@ 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()) {
// 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) {
APPLYING(PUSH_SEL_THROUGH_SPLICE) {
replace(make<DfgSel>(vtxp, driverp, lsb - driverLsb));
return;
}
}
}
}
}
void visit(DfgMux* const vtxp) override {
@@ -1349,18 +1453,27 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
if (DfgNot* const lhsNotp = lhsp->cast<DfgNot>()) {
if (tryPushBitwiseOpThrougSel(vtxp)) return;
{
DfgNot* const lNotp = lhsp->cast<DfgNot>();
DfgNot* const rNotp = rhsp->cast<DfgNot>();
// ~A & A is all zeroes
if (lhsNotp->srcp() == rhsp) {
if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
APPLYING(REPLACE_CONTRADICTORY_AND) {
replace(makeZero(flp, vtxp->width()));
return;
}
}
// ~A & (A & _) or ~A & (_ & A) is all zeroes
if (DfgAnd* const rhsAndp = rhsp->cast<DfgAnd>()) {
if (lhsNotp->srcp() == rhsAndp->lhsp() || lhsNotp->srcp() == rhsAndp->rhsp()) {
if (DfgAnd* const rSamep = rhsp->cast<DfgAnd>()) {
DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
DfgNot* const rrNotp = rSamep->rhsp()->cast<DfgNot>();
// ~A & (A & _) or ~A & (_ & A) is all zeroes
if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp()))
|| (lNotp && isSame(lNotp->srcp(), rSamep->rhsp()))
|| (rlNotp && isSame(lhsp, rlNotp->srcp()))
|| (rrNotp && isSame(lhsp, rrNotp->srcp()))) {
APPLYING(REPLACE_CONTRADICTORY_AND_3) {
replace(makeZero(flp, vtxp->width()));
return;
@@ -1374,10 +1487,10 @@ class V3DfgPeephole final : public DfgVisitor {
}
{
DfgReplicate* repp = lhsp->cast<DfgReplicate>();
DfgRep* repp = lhsp->cast<DfgRep>();
DfgCond* condp = rhsp->cast<DfgCond>();
if (!repp && !condp) {
repp = rhsp->cast<DfgReplicate>();
repp = rhsp->cast<DfgRep>();
condp = lhsp->cast<DfgCond>();
}
if (repp && condp && repp->srcp()->size() == 1 && !condp->hasMultipleSinks()
@@ -1463,24 +1576,29 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
if (DfgNot* const lhsNotp = lhsp->cast<DfgNot>()) {
if (tryPushBitwiseOpThrougSel(vtxp)) return;
{
DfgNot* const lNotp = lhsp->cast<DfgNot>();
DfgNot* const rNotp = rhsp->cast<DfgNot>();
// ~A | A is all ones
if (lhsNotp->srcp() == rhsp) {
if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
APPLYING(REPLACE_TAUTOLOGICAL_OR) {
DfgConst* const resp = makeZero(flp, vtxp->width());
resp->num().setAllBits1();
replace(resp);
replace(makeOnes(flp, vtxp->width()));
return;
}
}
// ~A | (A | _) or ~A | (_ | A) is all ones
if (DfgOr* const rhsOrp = rhsp->cast<DfgOr>()) {
if (lhsNotp->srcp() == rhsOrp->lhsp() || lhsNotp->srcp() == rhsOrp->rhsp()) {
if (DfgOr* const rSamep = rhsp->cast<DfgOr>()) {
DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
DfgNot* const rrNotp = rSamep->rhsp()->cast<DfgNot>();
// ~A | (A | _) or ~A | (_ | A) is all ones
if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp()))
|| (lNotp && isSame(lNotp->srcp(), rSamep->rhsp()))
|| (rlNotp && isSame(lhsp, rlNotp->srcp()))
|| (rrNotp && isSame(lhsp, rrNotp->srcp()))) {
APPLYING(REPLACE_TAUTOLOGICAL_OR_3) {
DfgConst* const resp = makeZero(flp, vtxp->width());
resp->num().setAllBits1();
replace(resp);
replace(makeOnes(flp, vtxp->width()));
return;
}
}
@@ -1525,6 +1643,8 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
if (tryPushBitwiseOpThrougSel(vtxp)) return;
if (vtxp->dtype() == m_bitDType) {
if (tryReplaceBitwiseWithReduction(vtxp)) return;
}
@@ -1847,10 +1967,9 @@ class V3DfgPeephole final : public DfgVisitor {
DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()};
m_vInfo[sp].m_id = ++m_lastId;
sp->addDriver(catp, lsb, flp);
DfgVertex::ScopeCache scopeCache;
AstScope* const scopep = vtxp->scopep(scopeCache, true);
const std::string name = m_dfg.makeUniqueName("PeepholeNarrow", m_nTemps++);
DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
DfgVertexVar* const varp
= m_dfg.makeNewVar(flp, name, vtxp->dtype(), m_tmpScopep);
varp->tmpForp(varp->vscp());
m_vInfo[varp].m_id = ++m_lastId;
varp->vscp()->varp()->isInternal(true);
@@ -1864,24 +1983,22 @@ class V3DfgPeephole final : public DfgVisitor {
// Convert various forms to REPLICATE
if (isSame(lhsp, rhsp)) {
APPLYING(REPLACE_CONCAT_SAME) {
replace(make<DfgReplicate>(vtxp, lhsp, makeI32(flp, 2)));
replace(make<DfgRep>(vtxp, lhsp));
return;
}
}
if (DfgReplicate* const lRepp = lhsp->cast<DfgReplicate>()) {
if (DfgRep* const lRepp = lhsp->cast<DfgRep>()) {
if (isSame(lRepp->srcp(), rhsp)) {
APPLYING(REPLACE_CONCAT_SAME_REP_ON_LHS) {
const uint32_t count = lRepp->countp()->as<DfgConst>()->toU32() + 1;
replace(make<DfgReplicate>(vtxp, rhsp, makeI32(flp, count)));
replace(make<DfgRep>(vtxp, rhsp));
return;
}
}
}
if (DfgReplicate* const rRepp = rhsp->cast<DfgReplicate>()) {
if (DfgRep* const rRepp = rhsp->cast<DfgRep>()) {
if (isSame(lhsp, rRepp->srcp())) {
APPLYING(REPLACE_CONCAT_SAME_REP_ON_RHS) {
const uint32_t count = rRepp->countp()->as<DfgConst>()->toU32() + 1;
replace(make<DfgReplicate>(vtxp, lhsp, makeI32(flp, count)));
replace(make<DfgRep>(vtxp, lhsp));
return;
}
}
@@ -2158,23 +2275,34 @@ class V3DfgPeephole final : public DfgVisitor {
if (binary(vtxp)) return;
}
void visit(DfgReplicate* const vtxp) override {
if (DfgConst* const sConstp = vtxp->srcp()->cast<DfgConst>()) {
DfgConst* const cConstp = vtxp->countp()->as<DfgConst>();
APPLYING(FOLD_REPLICATE) {
DfgConst* const resultp = makeZero(vtxp->fileline(), vtxp->width());
foldOp<DfgReplicate>(resultp->num(), sConstp->num(), cConstp->num());
void visit(DfgRep* const vtxp) override {
DfgVertex* const srcp = vtxp->srcp();
FileLine* const flp = vtxp->fileline();
if (DfgConst* const sConstp = srcp->cast<DfgConst>()) {
APPLYING(FOLD_REP) {
DfgConst* const resultp = makeZero(flp, vtxp->width());
resultp->num().opRepl(sConstp->num(), vtxp->count());
replace(resultp);
return;
}
}
if (vtxp->dtype() == vtxp->srcp()->dtype()) {
APPLYING(REMOVE_REPLICATE_ONCE) {
if (vtxp->dtype() == srcp->dtype()) {
APPLYING(REMOVE_REP_ONCE) {
replace(vtxp->srcp());
return;
}
}
if (DfgRep* const sRepp = srcp->cast<DfgRep>()) {
if (!sRepp->hasMultipleSinks() || vtxp->width() <= VL_QUADSIZE) {
APPLYING(REPLACE_REP_REP) {
replace(make<DfgRep>(vtxp, sRepp->srcp()));
return;
}
}
}
}
void visit(DfgShiftL* const vtxp) override {
@@ -2511,53 +2639,63 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (vtxp->dtype() == m_bitDType) {
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
replace(make<DfgAnd>(vtxp, make<DfgNot>(vtxp, condp), elsep));
return;
}
}
if (thenp == condp) { // a ? a : b becomes a | b
if (isSame(condp, thenp)) { // a ? a : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) {
replace(make<DfgOr>(vtxp, condp, elsep));
return;
}
}
if (elsep == condp) { // a ? b : a becomes a & b
if (isSame(condp, elsep)) { // a ? b : a becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) {
replace(make<DfgAnd>(vtxp, condp, thenp));
return;
}
}
}
if (vtxp->width() <= VL_QUADSIZE) {
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgAnd>(vtxp, maskp, elsep));
return;
}
}
if (isOnes(thenp)) { // a ? 1 : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) {
replace(make<DfgOr>(vtxp, condp, elsep));
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgOr>(vtxp, maskp, elsep));
return;
}
}
if (isZero(elsep)) { // a ? b : 0 becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) {
replace(make<DfgAnd>(vtxp, condp, thenp));
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgAnd>(vtxp, maskp, thenp));
return;
}
}
if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) {
replace(make<DfgOr>(vtxp, make<DfgNot>(vtxp, condp), thenp));
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgOr>(vtxp, maskp, thenp));
return;
}
}
if (DfgOr* const tOrp = thenp->cast<DfgOr>()) {
if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c)
APPLYING(REPLACE_COND_THEN_OR_LHS) {
DfgAnd* const andp = make<DfgAnd>(vtxp, condp, tOrp->rhsp());
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->rhsp());
replace(make<DfgOr>(vtxp, tOrp->lhsp(), andp));
return;
}
}
if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b)
APPLYING(REPLACE_COND_THEN_OR_RHS) {
DfgAnd* const andp = make<DfgAnd>(vtxp, condp, tOrp->lhsp());
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->lhsp());
replace(make<DfgOr>(vtxp, tOrp->rhsp(), andp));
return;
}
@@ -2590,6 +2728,33 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
}
if (!tConcatp->hasMultipleSinks()) {
if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast<DfgConcat>()) {
if (!tRCatp->hasMultipleSinks()) {
if (DfgSel* const tLSelp = tConcatp->lhsp()->cast<DfgSel>()) {
if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast<DfgSel>()) {
if (tLSelp->lsb() == tRCatp->width() //
&& tRRSelp->lsb() == 0 //
&& isSame(tLSelp->fromp(), elsep) //
&& isSame(tRRSelp->fromp(), elsep)) {
APPLYING(REPLACE_COND_INSERT) {
DfgVertex* const newTp = tRCatp->lhsp();
DfgVertex* const newEp = make<DfgSel>(
flp, newTp->dtype(), elsep, tRRSelp->width());
DfgCond* const newCp = make<DfgCond>(flp, newTp->dtype(),
condp, newTp, newEp);
replace(make<DfgConcat>(
vtxp, tLSelp,
make<DfgConcat>(tRCatp, newCp, tRRSelp)));
return;
}
}
}
}
}
}
}
}
if (isEqOne(thenp) && isZero(elsep)) {
@@ -2618,15 +2783,14 @@ class V3DfgPeephole final : public DfgVisitor {
if (isOnes(thenp) && isZero(elsep)) {
APPLYING(REPLACE_COND_CONST_ONES_ZERO) {
replace(make<DfgReplicate>(vtxp, condp, makeI32(flp, vtxp->width())));
replace(replicate(vtxp, condp));
return;
}
}
if (isZero(thenp) && isOnes(elsep)) {
APPLYING(REPLACE_COND_CONST_ZERO_ONES) {
replace(make<DfgReplicate>(vtxp, make<DfgNot>(condp, condp),
makeI32(flp, vtxp->width())));
replace(replicate(vtxp, make<DfgNot>(condp, condp)));
return;
}
}
+7 -3
View File
@@ -32,7 +32,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ONES) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ZERO) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_REPLICATE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_REP) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SELF_EQ) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SELF_GT) \
@@ -51,6 +51,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_SEL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMMUTATIVE_BINARY_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_COND_LHS) \
@@ -63,7 +64,7 @@
_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_NOT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REP) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SPLICE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SHIFTL_THROUGH_COND) \
@@ -83,7 +84,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REPLICATE_ONCE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REP_ONCE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SHIFTL_ZERO) \
@@ -110,6 +111,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \
@@ -142,6 +144,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REP_REP) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SHIFTL_CAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SHIFTL_OVER) \
@@ -160,6 +163,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REUSE_ASSOC_BINARY_LHS_WITH_LHS_OF_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REUSE_ASSOC_BINARY_LHS_WITH_RHS_OF_RHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, ROTATE_ASSOC_COMM_MULTIUSE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_SIDES_IN_BINARY)
+24 -13
View File
@@ -195,24 +195,35 @@ class DfgRegularize final {
// Scope cache for below
DfgVertex::ScopeCache scopeCache;
// Ensure intermediate values used multiple times are written to variables
// Build map from fanout to list of vertices with that fanout
std::vector<std::vector<DfgVertex*>> fanout2Vtxps;
for (DfgVertex& vtx : m_dfg.opVertices()) {
// LValue vertices feed into variables eventually and need no temporaries
if (vtx.is<DfgVertexSplice>()) continue;
if (vtx.is<DfgUnitArray>()) continue;
// Add to map
const uint32_t fanout = vtx.fanout();
if (!fanout) continue;
if (fanout >= fanout2Vtxps.size()) fanout2Vtxps.resize(2 * fanout);
fanout2Vtxps[fanout].push_back(&vtx);
}
if (fanout2Vtxps.empty()) return;
if (!needsTemporary(vtx, vtx)) continue;
// Need to create an intermediate variable
++m_ctx.m_temporariesIntroduced;
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
FileLine* const flp = vtx.fileline();
AstScope* const scopep = vtx.scopep(scopeCache);
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtx.dtype(), scopep);
++m_nTmps;
// Replace vertex with the variable, make it drive the variable
vtx.replaceWith(newp);
newp->srcp(&vtx);
// Ensure intermediate values used multiple times are written to variables
for (size_t fanout = fanout2Vtxps.size() - 1; fanout > 0; --fanout) {
for (DfgVertex* const vtxp : fanout2Vtxps[fanout]) {
if (!needsTemporary(*vtxp, *vtxp)) continue;
// Need to create an intermediate variable
++m_ctx.m_temporariesIntroduced;
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
FileLine* const flp = vtxp->fileline();
AstScope* const scopep = vtxp->scopep(scopeCache);
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
++m_nTmps;
// Replace vertex with the variable, make it drive the variable
vtxp->replaceWith(newp);
newp->srcp(vtxp);
}
}
}
+21
View File
@@ -384,6 +384,26 @@ class AstToDfgConverter final : public VNVisitor {
nodep->user2p(vtxp);
}
}
void visit(AstReplicate* nodep) override {
UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting");
UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
const DfgDataType* const dtypep = DfgDataType::fromAst(nodep->dtypep());
if (!dtypep) {
m_foundUnhandled = true;
++m_ctx.m_conv.nonRepDType;
return;
}
iterate(nodep->srcp());
if (m_foundUnhandled) return;
FileLine* const flp = nodep->fileline();
DfgRep* const vtxp = make<DfgRep>(flp, *dtypep);
vtxp->srcp(nodep->srcp()->user2u().to<DfgVertex*>());
nodep->user2p(vtxp);
}
void visit(AstSel* nodep) override {
UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting");
UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex");
@@ -426,6 +446,7 @@ class AstToDfgConverter final : public VNVisitor {
}
nodep->user2p(vtxp);
}
// The rest of the visit methods for expressions are generated by 'astgen'
#include "V3Dfg__gen_ast_to_dfg.h"
+11
View File
@@ -265,6 +265,17 @@ public:
std::string srcName(size_t) const override final { return ""; }
};
class DfgRep final : public DfgVertexUnary {
// AstReplicate is binary, but 'countp' is always constant. Represented in
// Dfg as a unary vertex, with the count determined by the data type,
// similar to Extend/ExtendS.
public:
DfgRep(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype)
: DfgVertexUnary{dfg, dfgType(), flp, dtype} {}
ASTGEN_MEMBERS_DfgRep;
uint32_t count() const { return width() / srcp()->width(); }
};
class DfgSel final : public DfgVertexUnary {
// AstSel is binary, but 'lsbp' is very often constant. As AstSel is fairly
// common, we special case as a DfgSel for the constant 'lsbp', and as
+8
View File
@@ -826,6 +826,14 @@ public:
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
puts(", ");
putsQuoted(nodep->linescov());
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
puts(");\n");
}
void visit(AstCoverToggleDecl* nodep) override {
+4 -1
View File
@@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit {
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
"linescovp);\n");
"linescovp,\n");
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
"fsmTagp);\n");
}
if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) {
@@ -663,6 +665,7 @@ class EmitCHeader final : public EmitCConstInit {
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");
std::set<string> cuse_set;
auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };
+11 -7
View File
@@ -180,7 +180,9 @@ class EmitCImp final : public EmitCFunc {
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
"linescovp) {\n");
"linescovp,\n");
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
"fsmTagp) {\n");
if (v3Global.opt.threads() > 1) {
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n");
@@ -198,10 +200,14 @@ class EmitCImp final : public EmitCFunc {
puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n");
puts("\"hier\",fullhier,");
puts("\"hier\",fullhier.c_str(),");
puts(" \"page\",pagep,");
puts(" \"comment\",commentp,");
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n");
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp,");
puts(" (fsmVarp[0] ? \"fsm_var\" : \"\"), fsmVarp,");
puts(" (fsmFromp[0] ? \"fsm_from\" : \"\"), fsmFromp,");
puts(" (fsmTop[0] ? \"fsm_to\" : \"\"), fsmTop,");
puts(" (fsmTagp[0] ? \"fsm_tag\" : \"\"), fsmTagp);\n");
puts("}\n");
}
if (v3Global.opt.coverageToggle()) {
@@ -237,7 +243,7 @@ class EmitCImp final : public EmitCFunc {
puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n");
puts("\"hier\",fullhier,");
puts("\"hier\",fullhier.c_str(),");
puts(" \"page\",pagep,");
puts(" \"comment\",commentWithIndex.c_str(),");
puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always
@@ -752,9 +758,7 @@ class EmitCTrace final : public EmitCFunc {
const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords());
const uint32_t code = nodep->declp()->code() + offset;
// Note: Both VTraceType::CHANGE and VTraceType::FULL use the 'full' methods
puts(v3Global.opt.useTraceOffload() && nodep->traceType() == VTraceType::CHANGE
? "(base+"
: "(oldp+");
puts("(oldp+");
puts(cvtToStr(code - nodep->baseCode()));
puts(",");
const VNumRange& arrayRange = nodep->declp()->arrayRange();
-2
View File
@@ -519,8 +519,6 @@ class EmitCModel final : public EmitCFunc {
+ "::traceConfig() const {\n");
puts("return std::unique_ptr<VerilatedTraceConfig>{new VerilatedTraceConfig{");
puts(v3Global.opt.useTraceParallel() ? "true" : "false");
puts(v3Global.opt.useTraceOffload() ? ", true" : ", false");
puts(v3Global.opt.useFstWriterThread() ? ", true" : ", false");
puts("}};\n");
puts("};\n");
}
+9 -8
View File
@@ -106,6 +106,7 @@ public:
ENUMITEMWIDTH, // Error: enum item width mismatch
ENUMVALUE, // Error: enum type needs explicit cast
EOFNEWLINE, // End-of-file missing newline
FSMMULTI, // Multiple FSM candidates in one always block
FUNCTIMECTL, // Functions cannot have timing/delay/wait
FUTURE, // Feature is under development and not yet supported
GENCLK, // Generated Clock. Historical, never issued.
@@ -223,14 +224,14 @@ public:
"BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST",
"CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN",
"CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL",
"FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN",
"IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH",
"INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN",
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP",
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT",
"PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL",
"PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI",
"FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH",
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH",
"LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN",
"MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT",
"PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
"PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
"SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",
+929 -576
View File
File diff suppressed because it is too large Load Diff
+788
View File
@@ -0,0 +1,788 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: FSM coverage detect pass
//
// 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: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// FSM COVERAGE DETECT:
// Walk clocked always blocks while the original FSM structure is still
// present, build a per-FSM V3Graph representation of the extracted
// states/transitions, then immediately lower that completed graph state
// into the final coverage declarations, previous-state tracking, and
// active blocks needed to implement FSM state and arc coverage in the
// generated model.
//
//*************************************************************************
#include "V3PchAstNoMT.h"
#include "V3FsmDetect.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include <cctype>
#include <map>
#include <memory>
#include <unordered_map>
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
// Captures one sensitivity-list entry so the lowering phase can later rebuild
// an active block with the same triggering event control.
struct FsmSenDesc final {
// Encoded edge kind copied from AstSenItem::edgeType() so lowering can
// rebuild the same trigger semantics on the synthesized coverage block.
VEdgeType::en edgeType = static_cast<VEdgeType::en>(0);
// Triggering signal in the saved scoped AST.
AstVarScope* varScopep = nullptr;
};
// Captures the simple reset predicate shape that survives to this pass after
// earlier normalization so reset arcs can be reconstructed during lowering.
struct FsmResetCondDesc final {
// Reset signal used by the FSM in the saved scoped AST.
AstVarScope* varScopep = nullptr;
};
class FsmGraph;
class FsmVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(FsmVertex, V3GraphVertex)
public:
enum class Kind : uint8_t { STATE, RESET_ANY, DEFAULT_ANY };
private:
Kind m_kind; // State vs synthetic ANY/default vertex role.
string m_label; // User-facing state or pseudo-state label.
int m_value = 0; // Encoded state value for real state vertices.
protected:
FsmVertex(V3Graph* graphp, Kind kind, string label, int value) VL_MT_DISABLED
: V3GraphVertex{graphp},
m_kind{kind},
m_label{label},
m_value{value} {}
~FsmVertex() override = default;
public:
Kind kind() const { return m_kind; }
bool isState() const { return m_kind == Kind::STATE; }
bool isResetAny() const { return m_kind == Kind::RESET_ANY; }
bool isDefaultAny() const { return m_kind == Kind::DEFAULT_ANY; }
const string& label() const { return m_label; }
int value() const { return m_value; }
string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); }
};
class FsmStateVertex final : public FsmVertex {
VL_RTTI_IMPL(FsmStateVertex, FsmVertex)
public:
FsmStateVertex(V3Graph* graphp, string label, int value) VL_MT_DISABLED
: FsmVertex{graphp, Kind::STATE, label, value} {}
~FsmStateVertex() override = default;
string dotColor() const override { return "lightblue"; }
string dotShape() const override { return "ellipse"; }
};
class FsmPseudoVertex final : public FsmVertex {
VL_RTTI_IMPL(FsmPseudoVertex, FsmVertex)
public:
FsmPseudoVertex(V3Graph* graphp, Kind kind, string label) VL_MT_DISABLED
: FsmVertex{graphp, kind, label, 0} {}
~FsmPseudoVertex() override = default;
string name() const override VL_MT_SAFE { return label(); }
string dotColor() const override { return isResetAny() ? "darkgreen" : "orange"; }
string dotShape() const override { return "diamond"; }
};
class FsmArcEdge final : public V3GraphEdge {
VL_RTTI_IMPL(FsmArcEdge, V3GraphEdge)
bool m_isReset = false; // Arc originates from the synthetic reset source.
bool m_isCond = false; // Arc came from a conditional next-state split.
bool m_isDefault = false; // Arc represents a case default source.
FileLine* m_flp = nullptr; // Source location for emitted coverage metadata.
public:
FsmArcEdge(V3Graph* graphp, FsmVertex* fromp, FsmStateVertex* top, bool isReset, bool isCond,
bool isDefault, FileLine* flp) VL_MT_DISABLED : V3GraphEdge{graphp, fromp, top, 1},
m_isReset{isReset},
m_isCond{isCond},
m_isDefault{isDefault},
m_flp{flp} {}
~FsmArcEdge() override = default;
bool isReset() const { return m_isReset; }
bool isCond() const { return m_isCond; }
bool isDefault() const { return m_isDefault; }
FileLine* fileline() const { return m_flp; }
string dotLabel() const override {
if (m_isReset) return "reset";
if (m_isDefault) return "default";
if (m_isCond) return "cond";
return "";
}
string dotColor() const override {
if (m_isReset) return "darkgreen";
if (m_isDefault) return "orange";
if (m_isCond) return "blue";
return "black";
}
};
// One graph per detected FSM. Graph-level metadata captures the non-graph
// context needed to lower states/arcs back into the AST after detection.
class FsmGraph final : public V3Graph {
AstScope* m_scopep = nullptr; // Owning scoped block for the detected FSM.
AstAlways* m_alwaysp = nullptr; // Original always block being instrumented.
string m_stateVarName; // Pretty state variable name for user-visible output.
string m_stateVarInternalName; // Internal state symbol name for dump tags.
AstVarScope* m_stateVarScopep = nullptr; // Scoped state variable being tracked.
std::vector<FsmSenDesc> m_senses; // Saved event controls for recreated active blocks.
FsmResetCondDesc m_resetCond; // Saved reset predicate shape, if one exists.
bool m_hasResetCond = false; // Whether the detected FSM had a reset branch.
bool m_resetInclude = false; // Whether reset arcs count toward coverage totals.
bool m_inclCond = false; // Whether conditional arcs should be kept explicitly.
FileLine* m_flp = nullptr; // Representative source location for declarations/arcs.
std::unordered_map<int, FsmStateVertex*> m_stateVertices; // Value to state-vertex map.
FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs.
FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults.
public:
FsmGraph() VL_MT_DISABLED
: m_resetVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::RESET_ANY, "ANY"}},
m_defaultVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::DEFAULT_ANY, "default"}} {}
AstScope* scopep() const { return m_scopep; }
void scopep(AstScope* scopep) { m_scopep = scopep; }
AstAlways* alwaysp() const { return m_alwaysp; }
void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; }
const string& stateVarName() const { return m_stateVarName; }
void stateVarName(const string& name) { m_stateVarName = name; }
const string& stateVarInternalName() const { return m_stateVarInternalName; }
void stateVarInternalName(const string& name) { m_stateVarInternalName = name; }
AstVarScope* stateVarScopep() const { return m_stateVarScopep; }
void stateVarScopep(AstVarScope* vscp) { m_stateVarScopep = vscp; }
const std::vector<FsmSenDesc>& senses() const { return m_senses; }
std::vector<FsmSenDesc>& senses() { return m_senses; }
const FsmResetCondDesc& resetCond() const { return m_resetCond; }
FsmResetCondDesc& resetCond() { return m_resetCond; }
bool hasResetCond() const { return m_hasResetCond; }
void hasResetCond(bool flag) { m_hasResetCond = flag; }
bool resetInclude() const { return m_resetInclude; }
void resetInclude(bool flag) { m_resetInclude = flag; }
bool inclCond() const { return m_inclCond; }
void inclCond(bool flag) { m_inclCond = flag; }
FileLine* fileline() const { return m_flp; }
void fileline(FileLine* flp) { m_flp = flp; }
FsmStateVertex* addStateVertex(string label, int value) VL_MT_DISABLED {
FsmStateVertex* const vertexp = new FsmStateVertex{this, label, value};
m_stateVertices.emplace(value, vertexp);
return vertexp;
}
FsmPseudoVertex* resetAnyVertex() VL_MT_DISABLED { return m_resetVertexp; }
FsmPseudoVertex* defaultAnyVertex() VL_MT_DISABLED { return m_defaultVertexp; }
FsmArcEdge* addArc(int fromValue, int toValue, bool isReset, bool isCond, bool isDefault,
FileLine* flp) VL_MT_DISABLED {
FsmStateVertex* const top = m_stateVertices.at(toValue);
FsmVertex* fromp = nullptr;
if (isReset) {
fromp = resetAnyVertex();
} else if (isDefault) {
fromp = defaultAnyVertex();
} else {
fromp = m_stateVertices.at(fromValue);
}
return new FsmArcEdge{this, fromp, top, isReset, isCond, isDefault, flp};
}
string name() const VL_MT_SAFE {
return "FSM "
+ (m_stateVarName.empty() ? (m_stateVarScopep ? m_stateVarScopep->name() : "")
: m_stateVarName);
}
string dumpTag(size_t index) const {
string tag = stateVarInternalName();
for (char& ch : tag) {
if (!std::isalnum(static_cast<unsigned char>(ch))) ch = '_';
}
return "fsm_" + cvtToStr(index) + "_" + tag;
}
};
struct DetectedFsm final {
std::unique_ptr<FsmGraph> graphp; // Extracted graph for one detected FSM candidate.
};
using DetectedFsmMap = std::map<string, DetectedFsm>;
// Local shared state between the two adjacent FSM coverage phases. Detection
// fills this with recovered FSM graphs; lowering consumes the completed graphs
// immediately afterward without needing any AST serialization bridge.
class FsmState final {
// All detected FSMs keyed by state varscope name. This is the only bridge
// between the adjacent detect and lower phases, so the second phase never
// needs to rediscover or serialize the extracted machine.
DetectedFsmMap m_fsms;
public:
DetectedFsmMap& fsms() { return m_fsms; }
const DetectedFsmMap& fsms() const { return m_fsms; }
};
// Detection runs while the original clocked/case structure is still intact and
// populates graph-backed FSM models without mutating the tree mid-traversal.
// This pass is intentionally conservative: for this PR we only lock down the
// small set of transition/selector forms that are already stable in the
// normalized AST we see here. The remaining reject branches are therefore
// mostly future-feature boundaries, not accidental dead code.
class FsmDetectVisitor final : public VNVisitor {
// STATE - for current visit position (use VL_RESTORER)
FsmState& m_state;
AstScope* m_scopep = nullptr;
// METHODS
// Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the
// underlying enum type before deciding whether a case is a candidate.
static AstNodeDType* unwrapEnumCandidate(AstNodeDType* dtypep) {
return dtypep->skipRefToEnump();
}
// Reset arcs are only modeled for the simple signal form that survives to
// this pass after earlier normalization.
static bool isSimpleResetCond(AstNodeExpr* condp) { return VN_IS(condp, VarRef); }
// Normalize the reset condition into a compact description so the lowering
// phase can regenerate the same predicate after detection. By the time
// this pass runs, active-low source forms such as "!rst_n" have already
// been canonicalized to a positive-condition if/else shape, so only a
// plain VarRef survives here.
static FsmResetCondDesc describeResetCond(AstNodeExpr* condp) {
FsmResetCondDesc desc;
if (AstVarRef* const vrefp = VN_CAST(condp, VarRef)) {
desc.varScopep = vrefp->varScopep();
}
return desc;
}
// Snapshot the original event control so the lowering phase can rebuild an
// active block with the same edge semantics.
static std::vector<FsmSenDesc> describeSenTree(AstSenTree* sentreep) {
std::vector<FsmSenDesc> senses;
for (AstSenItem* itemp = sentreep->sensesp(); itemp;
itemp = VN_AS(itemp->nextp(), SenItem)) {
AstNodeVarRef* const vrefp = itemp->varrefp();
if (!vrefp) continue;
FsmSenDesc desc;
desc.edgeType = itemp->edgeType().m_e;
desc.varScopep = vrefp->varScopep();
senses.push_back(desc);
}
return senses;
}
// Ignore existing coverage increments so FSM detection sees the user logic
// rather than other instrumentation already attached to the block.
static bool isIgnorableStmt(AstNode* nodep) { return VN_IS(nodep, CoverInc); }
// Conservative extractor: only treat a branch as simple when exactly one
// non-coverage statement remains after unwrapping. Richer multi-statement
// or control-flow forms are intentionally left for follow-on FSM-detection
// work instead of being partially inferred here.
static AstNode* singleMeaningfulStmt(AstNode* stmtp) {
AstNode* resultp = nullptr;
for (AstNode* nodep = stmtp; nodep; nodep = nodep->nextp()) {
if (isIgnorableStmt(nodep)) continue;
if (resultp) return nullptr;
resultp = nodep;
}
return resultp;
}
// Recognize the direct "state <= X" form that gives us an unambiguous arc
// target without needing deeper control-flow reasoning. Branches that fall
// out here represent currently unsupported next-state shapes rather than
// bugs in the implemented subset.
static AstNodeAssign* directStateAssign(AstNode* stmtp, AstVarScope* stateVscp) {
AstNode* const nodep = singleMeaningfulStmt(stmtp);
if (!nodep) return nullptr;
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
if (!vrefp || vrefp->varScopep() != stateVscp) return nullptr;
return assp;
}
// Prefer enum labels in reports; fall back to synthetic labels for forced
// non-enum FSMs so coverage points remain human-readable.
static string labelForValue(const std::unordered_map<int, string>& labels, int value) {
const std::unordered_map<int, string>::const_iterator it = labels.find(value);
return it == labels.end() ? ("S" + cvtToStr(value)) : it->second;
}
// The extractor only models constant-valued state transitions, and by the
// time detect runs those values have already been constant-folded.
static bool exprConstValue(AstNodeExpr* exprp, int& value) {
if (AstConst* const constp = VN_CAST(exprp, Const)) {
value = constp->toSInt();
return true;
}
return false;
}
// Enum-backed FSMs should only transition to values that were interned as
// known states. If a constant transition targets some other encoding, warn
// and skip FSM instrumentation for that edge rather than silently dropping
// it or turning optional coverage into a hard compile failure.
static bool validateKnownStateValue(AstNode* nodep,
const std::unordered_map<int, string>& labels, int value) {
if (labels.find(value) != labels.end()) return true;
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state transitions "
"that assign a constant not present in the declared enum");
return false;
}
// Extract supported case-item transitions in one place so the conservative
// policy for direct and ternary forms stays consistent. The false exits in
// this helper are deliberate subset boundaries: they document shapes we do
// not yet model in this PR and that future FSM-detection work may widen.
static bool emitCaseItemArcs(FsmGraph& graph, AstCaseItem* itemp, AstVarScope* stateVscp,
const std::unordered_map<int, string>& labels, bool inclCond) {
std::vector<std::pair<string, int>> froms;
if (itemp->isDefault()) {
if (!inclCond) return false;
froms.emplace_back("default", 0);
} else {
for (AstNodeExpr* condp = itemp->condsp(); condp;
condp = VN_CAST(condp->nextp(), NodeExpr)) {
int value = 0;
if (!exprConstValue(condp, value)) continue;
froms.emplace_back(labelForValue(labels, value), value);
}
if (froms.empty()) return false;
}
if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) {
int toValue = 0;
if (exprConstValue(assp->rhsp(), toValue)) {
if (!validateKnownStateValue(assp, labels, toValue)) return true;
for (const std::pair<string, int>& from : froms) {
graph.addArc(from.second, toValue, false, false, itemp->isDefault(),
assp->fileline());
}
return true;
}
if (AstCond* const condp = VN_CAST(assp->rhsp(), Cond)) {
int thenValue = 0;
int elseValue = 0;
const bool simpleCond = exprConstValue(condp->thenp(), thenValue)
&& exprConstValue(condp->elsep(), elseValue);
if (simpleCond || inclCond) {
if (!validateKnownStateValue(condp->thenp(), labels, thenValue)) return true;
if (!validateKnownStateValue(condp->elsep(), labels, elseValue)) return true;
for (const int branchValue : {thenValue, elseValue}) {
for (const std::pair<string, int>& from : froms) {
graph.addArc(from.second, branchValue, false, true, itemp->isDefault(),
assp->fileline());
}
}
return true;
}
}
}
return false;
}
// Reset transitions are described separately because they live in the reset
// branch outside the steady-state case statement.
static void addResetArcs(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp,
const std::unordered_map<int, string>& labels) {
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) {
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
int toValue = 0;
if (vrefp && vrefp->varScopep() == stateVscp
&& exprConstValue(assp->rhsp(), toValue)) {
if (!validateKnownStateValue(assp, labels, toValue)) continue;
graph.addArc(0, toValue, true, false, false, assp->fileline());
}
}
}
}
// Turn one candidate case statement into the graph representation that the
// later lowering phase will consume directly, while reviewers can still
// inspect the extracted machine via DOT dumps.
void processCase(AstCase* casep, AstNodeExpr* resetCondp, AstAlways* alwaysp) {
AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef);
if (!selp) return;
AstVarScope* const stateVscp = selp->varScopep();
AstVar* const stateVarp = selp->varp();
AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType);
if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType);
const bool forced = stateVarp->attrFsmState();
if (!enump && !forced) return;
std::vector<std::pair<string, int>> states;
std::unordered_map<int, string> labels;
if (enump) {
if (stateVscp->width() < 1 || stateVscp->width() > 32) {
casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state "
"variables wider than 32 bits");
return;
}
for (AstEnumItem* itemp = enump->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const AstConst* const constp = VN_AS(itemp->valuep(), Const);
const int value = constp->toSInt();
states.emplace_back(itemp->name(), value);
labels.emplace(value, itemp->name());
}
if (states.size() < 2) return;
} else {
const int width = stateVarp->width();
if (width < 1 || width >= 31) return;
const unsigned stateCount = 1U << width;
for (unsigned value = 0; value < stateCount; ++value) {
const string label = "S" + cvtToStr(value);
states.emplace_back(label, static_cast<int>(value));
labels.emplace(static_cast<int>(value), label);
}
}
DetectedFsm& entry = m_state.fsms()[stateVscp->name()];
if (!entry.graphp) {
entry.graphp.reset(new FsmGraph{});
entry.graphp->scopep(m_scopep);
entry.graphp->alwaysp(alwaysp);
entry.graphp->stateVarName(stateVscp->prettyName());
entry.graphp->stateVarInternalName(stateVarp->name());
entry.graphp->stateVarScopep(stateVscp);
entry.graphp->senses() = describeSenTree(alwaysp->sentreep());
entry.graphp->resetCond() = describeResetCond(resetCondp);
entry.graphp->hasResetCond(entry.graphp->resetCond().varScopep != nullptr);
entry.graphp->resetInclude(stateVarp->attrFsmResetArc());
entry.graphp->inclCond(stateVarp->attrFsmArcInclCond());
entry.graphp->fileline(casep->fileline());
for (const std::pair<string, int>& state : states) {
entry.graphp->addStateVertex(state.first, state.second);
}
}
for (AstCaseItem* itemp = casep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
emitCaseItemArcs(*entry.graphp, itemp, stateVscp, labels, entry.graphp->inclCond());
}
}
// Find the first supported FSM candidate in a clocked always block, warn on
// additional candidates, and attach reset arcs when present. Candidate
// filtering stays narrow on purpose: we prefer to skip ambiguous shapes now
// and expand detection in a later PR rather than over-infer coverage from
// forms we do not yet model confidently.
void processAlways(AstAlways* alwaysp) {
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasClocked()) return;
std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates;
AstNode* stmtsp = alwaysp->stmtsp();
AstIf* const firstIfp = VN_CAST(stmtsp, If);
if (firstIfp) {
if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) {
candidates.emplace_back(
casep, isSimpleResetCond(firstIfp->condp()) ? firstIfp->condp() : nullptr);
}
}
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
if (AstCase* const casep = VN_CAST(nodep, Case))
candidates.emplace_back(casep, nullptr);
}
if (candidates.empty()) return;
AstVarScope* firstVscp = nullptr;
for (const std::pair<AstCase*, AstNodeExpr*>& cand : candidates) {
AstVarRef* const selp = VN_CAST(cand.first->exprp(), VarRef);
AstVarScope* const vscp = selp ? selp->varScopep() : nullptr;
if (!vscp) continue;
if (!firstVscp) {
firstVscp = vscp;
processCase(cand.first, cand.second, alwaysp);
} else if (vscp != firstVscp) {
cand.first->v3warn(FSMMULTI,
"FSM coverage: multiple enum-typed case statements found in "
"the same always block. Only the first candidate will be "
"instrumented.");
} else {
cand.first->v3warn(COVERIGN,
"Ignoring unsupported: FSM coverage on multiple supported case "
"statements found in the same always block. Only the first "
"candidate will be instrumented.");
}
}
if (!(firstIfp && firstVscp)) return;
const DetectedFsmMap& fsms = m_state.fsms();
const DetectedFsmMap::const_iterator it = fsms.find(firstVscp->name());
if (it == fsms.end()) return;
FsmGraph* const graphp = it->second.graphp.get();
if (!graphp->hasResetCond()) return;
std::unordered_map<int, string> labels;
for (const V3GraphVertex& vtx : graphp->vertices()) {
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
if (!vertexp->isState()) continue;
labels.emplace(vertexp->value(), vertexp->label());
}
addResetArcs(*graphp, firstIfp->thensp(), firstVscp, labels);
}
// Track the current scope so each detected FSM records the module/scope
// where instrumentation must later be inserted.
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
}
// FSM extraction only cares about clocked always processes.
void visit(AstAlways* nodep) override { processAlways(nodep); }
// Continue the walk through the rest of the design hierarchy.
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
// Collect all FSM graphs into the shared local state before the lowering
// phase starts mutating the AST with coverage machinery.
FsmDetectVisitor(FsmState& state, AstNetlist* rootp)
: m_state{state} {
iterate(rootp);
}
};
// Lower the completed FSM graphs into the concrete coverage declarations,
// previous-state tracking, and pre/post-triggered instrumentation that the
// runtime uses to record state and transition coverage.
class FsmLowerVisitor final {
// STATE - across all visitors
const FsmState& m_state;
// METHODS
// Rebuild a state-typed constant using the tracked state variable
// width/sign so emitted comparisons match the original representation.
static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, int value) {
V3Number num{flp, vscp->width(), static_cast<uint32_t>(value)};
num.isSigned(vscp->dtypep()->isSigned());
return new AstConst{flp, num};
}
// Build guards incrementally without forcing callers to special-case the
// first predicate; this keeps emitted state/arc conditions readable.
static AstNodeExpr* andExpr(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) {
if (!lhsp) return rhsp;
return new AstLogAnd{flp, lhsp, rhsp};
}
static AstNodeExpr* buildResetCond(FileLine* flp, AstVarScope* resetVscp,
const FsmResetCondDesc&) {
return new AstVarRef{flp, resetVscp, VAccess::READ};
}
// Rebuild the original event control from the saved sense description so
// post-state coverage sampling runs on the same triggering edges.
static AstSenTree* buildSenTree(FileLine* flp, const std::vector<FsmSenDesc>& senses) {
AstSenTree* const sentreep = new AstSenTree{flp, nullptr};
for (const FsmSenDesc& sense : senses) {
AstSenItem* const senItemp
= new AstSenItem{flp, VEdgeType{sense.edgeType},
new AstVarRef{flp, sense.varScopep, VAccess::READ}};
sentreep->addSensesp(senItemp);
}
return sentreep;
}
// Lower one fully detected FSM graph into the concrete coverage machinery
// used by generated models: declarations, previous-state tracking, and the
// pre/post-triggered increment logic for states and arcs.
void buildOne(const FsmGraph& graph) {
AstAlways* const alwaysp = graph.alwaysp();
AstScope* const scopep = graph.scopep();
AstVarScope* const stateVscp = graph.stateVarScopep();
FileLine* const flp = graph.fileline();
AstNodeModule* const modp = scopep->modp();
AstNodeDType* const prevDTypep = scopep->findLogicDType(
stateVscp->width(), stateVscp->width(), stateVscp->dtypep()->numeric());
AstVarScope* const prevVscp
= scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep);
// The saved previous-state temp crosses the scheduler's pre/post split
// in the same way as Verilator's built-in NBA shadow variables, so keep
// both vars marked as post-life participants for stable MT ordering.
stateVscp->optimizeLifePost(true);
prevVscp->optimizeLifePost(true);
AstActive* const initActivep
= new AstActive{flp, "fsm-coverage-init",
new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
initActivep->senTreeStorep(initActivep->sentreep());
// Seed the previous-state temp during initialization so the first
// clock edge compares against a defined state value.
initActivep->addStmtsp(new AstInitialStatic{
flp, new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}}});
scopep->addBlocksp(initActivep);
AstAlwaysPost* const covPostp = new AstAlwaysPost{flp};
// Save the previous state as plain sequential logic at the front of
// the original always_ff body, then evaluate coverage in post logic
// after the delayed state update commits. This avoids a scheduler race
// between a separate AstAlwaysPre task and the real state commit.
AstNode* const bodysp = alwaysp->stmtsp()->unlinkFrBackWithNext();
alwaysp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}});
alwaysp->addStmtsp(bodysp);
for (const V3GraphVertex& vtx : graph.vertices()) {
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
if (!vertexp->isState()) continue;
const FsmStateVertex* const statep = vtx.as<FsmStateVertex>();
// State coverage fires when the FSM enters a state from any other
// value, so repeated self-holds do not count as new entries.
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{flp,
"v_fsm_state/" + modp->prettyName(),
graph.stateVarName() + "::" + statep->label(),
"",
0,
graph.stateVarName(),
"",
statep->label()};
declp->hier(scopep->prettyName());
modp->addStmtsp(declp);
AstNodeExpr* const guardp
= andExpr(flp,
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, statep->value())},
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, statep->value())});
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
}
for (const V3GraphVertex& vtx : graph.vertices()) {
const FsmVertex* const fromVertexp = vtx.as<FsmVertex>();
for (const V3GraphEdge& edge : fromVertexp->outEdges()) {
const FsmArcEdge* const arcp = edge.as<FsmArcEdge>();
const FsmStateVertex* const toStatep = arcp->top()->as<FsmStateVertex>();
// Arc coverage mirrors the extracted graph exactly, including
// reset and synthetic-default sources, so reports match the
// reviewer-visible graph dump and the user-visible annotation.
const string resetTag
= arcp->isReset() ? (graph.resetInclude() ? "[reset_include]" : "[reset]")
: "";
const string fsmTag = arcp->isReset()
? (graph.resetInclude() ? "reset_include" : "reset")
: arcp->isDefault() ? "default"
: "";
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{flp,
"v_fsm_arc/" + modp->prettyName(),
graph.stateVarName() + "::" + fromVertexp->label()
+ "->" + toStatep->label() + resetTag,
"",
0,
graph.stateVarName(),
fromVertexp->label(),
toStatep->label(),
fsmTag};
declp->hier(scopep->prettyName());
modp->addStmtsp(declp);
AstNodeExpr* guardp = nullptr;
if (fromVertexp->isResetAny()) {
// Reset arcs are modeled as pseudo-source edges in the
// graph, then reconstructed here into the original simple
// reset predicate combined with the destination state.
guardp = buildResetCond(flp, graph.resetCond().varScopep, graph.resetCond());
guardp = andExpr(flp, guardp,
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
} else if (fromVertexp->isDefaultAny()) {
// Synthetic default arcs mean "none of the explicit
// source states matched", so rebuild that as a conjunction
// of previous-state != known-state tests.
for (const V3GraphVertex& stateVtx : graph.vertices()) {
const FsmVertex* const stateVertexp = stateVtx.as<FsmVertex>();
if (!stateVertexp->isState()) continue;
guardp = andExpr(
flp, guardp,
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, stateVertexp->value())});
}
guardp = andExpr(flp, guardp,
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
} else {
guardp
= andExpr(flp,
new AstEq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, fromVertexp->value())},
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
}
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
}
}
AstSenTree* const sentreep = buildSenTree(flp, graph.senses());
AstActive* const activep = new AstActive{flp, "fsm-coverage", sentreep};
activep->senTreeStorep(sentreep);
scopep->addBlocksp(activep);
activep->addStmtsp(covPostp);
}
public:
// CONSTRUCTORS
// Lower every detected FSM graph from the shared local state into
// concrete coverage instrumentation while the saved scoped pointers are
// still valid in the same pass.
explicit FsmLowerVisitor(const FsmState& state)
: m_state{state} {
for (const std::pair<const string, DetectedFsm>& it : m_state.fsms()) {
buildOne(*it.second.graphp);
}
}
};
} // namespace
void V3FsmDetect::detect(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
FsmState state;
// Phase 1: recover each supported FSM into a complete graph while the
// original clocked/case structure is still easy to recognize.
FsmDetectVisitor detect{state, rootp};
if (dumpGraphLevel() >= 6) {
size_t index = 0;
for (const std::pair<const string, DetectedFsm>& it : state.fsms()) {
it.second.graphp->dumpDotFilePrefixed(it.second.graphp->dumpTag(index++));
}
}
// Phase 2: lower the completed in-memory graph state immediately, without
// crossing into another pass owner or serializing through AST placeholders.
{ FsmLowerVisitor lower{state}; }
V3Global::dumpCheckGlobalTree("fsm-detect", 0, dumpTreeEitherLevel() >= 3);
}
+33
View File
@@ -0,0 +1,33 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: FSM coverage detect pass
//
// 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: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3FSMDETECT_H_
#define VERILATOR_V3FSMDETECT_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
class V3FsmDetect final {
public:
// Detect FSMs while the original clocked/case structure is still visible,
// then immediately lower the recovered graphs into concrete coverage
// instrumentation as a second local phase in the same pass.
static void detect(AstNetlist* rootp) VL_MT_DISABLED;
};
#endif
+70 -23
View File
@@ -14,7 +14,7 @@
//
//*************************************************************************
//
// - Split assignments to wide locations with Concat on the RHS
// - Split assignments to wide locations with Concat/Extend on the RHS
// at word boundaries:
// foo = {l, r};
// becomes (recursively):
@@ -60,6 +60,12 @@ class BalanceConcatTree final {
gatherTermsRecursive(catp->lhsp(), terms);
return;
}
if (AstExtend* const extp = VN_CAST(exprp, Extend)) {
// Recursive case: gather sub terms, right to left
gatherTermsRecursive(extp->lhsp(), terms);
terms.emplace_back(extp);
return;
}
// Base case: different operation
terms.emplace_back(exprp);
@@ -68,10 +74,18 @@ class BalanceConcatTree final {
// Gather terms in the tree rooted at the given node.
// Results are right to left, that is, index 0 in the returned vector
// is the rightmost term, index size()-1 is the leftmost term.
static std::vector<AstNodeExpr*> gatherTerms(AstConcat* rootp) {
// If a term is an AstExtend, it represents the extension part only.
static std::vector<AstNodeExpr*> gatherTerms(AstNodeExpr* rootp) {
std::vector<AstNodeExpr*> terms;
gatherTermsRecursive(rootp->rhsp(), terms);
gatherTermsRecursive(rootp->lhsp(), terms);
if (AstConcat* const catp = VN_CAST(rootp, Concat)) {
gatherTermsRecursive(catp->rhsp(), terms);
gatherTermsRecursive(catp->lhsp(), terms);
} else if (AstExtend* const extp = VN_CAST(rootp, Extend)) {
gatherTermsRecursive(extp->lhsp(), terms);
terms.emplace_back(extp);
} else {
rootp->v3fatalSrc("Unexpected node type");
}
return terms;
}
@@ -108,7 +122,7 @@ class BalanceConcatTree final {
}
// Returns replacement node, or nullptr if no change
static AstConcat* balance(AstConcat* const rootp) {
static AstConcat* balance(AstNodeExpr* const rootp) {
UINFO(9, "balanceConcat " << rootp);
// Gather all input vertices of the tree
const std::vector<AstNodeExpr*> exprps = gatherTerms(rootp);
@@ -125,8 +139,15 @@ class BalanceConcatTree final {
terms[0].offset = 0;
terms[exprps.size()].exprp = nullptr;
for (size_t i = 0; i < exprps.size(); ++i) {
terms[i].exprp = exprps[i]->unlinkFrBack();
terms[i + 1].offset = terms[i].offset + exprps[i]->width();
AstNodeExpr* const exprp = [&]() -> AstNodeExpr* {
if (AstExtend* const extp = VN_CAST(exprps[i], Extend)) {
const int width = extp->width() - extp->lhsp()->width();
return new AstConst{extp->fileline(), AstConst::WidthedValue{}, width, 0};
}
return exprps[i]->cloneTreePure(false);
}();
terms[i].exprp = exprp;
terms[i + 1].offset = terms[i].offset + exprp->width();
}
// Round 1: try to create terms ending on VL_EDATASIZE boundaries.
@@ -166,7 +187,15 @@ class BalanceConcatTree final {
}
public:
static AstConcat* apply(AstConcat* rootp) { return balance(rootp); }
static AstNodeExpr* apply(AstNodeExpr* nodep) {
if (!v3Global.opt.fFuncBalanceCat()) return nullptr;
if (nodep->user1()) return nullptr; // Created by us, don't try to balance again
if (VN_IS(nodep->backp(), Concat)) return nullptr; // Not root of tree
if (VN_IS(nodep->backp(), Extend)) return nullptr; // Not root of tree
AstNodeExpr* const exprp = balance(nodep);
if (exprp) exprp->user1(true); // Must not attempt again.
return exprp;
}
};
struct FuncOptStats final {
@@ -230,9 +259,9 @@ class FuncOptVisitor final : public VNVisitor {
// Returns true if 'nodep' was deleted
bool splitConcat(AstNodeAssign* nodep) {
UINFO(9, "splitConcat " << nodep);
// Only care about concatenations on the right
AstConcat* const rhsp = VN_CAST(nodep->rhsp(), Concat);
if (!rhsp) return false;
AstNodeExpr* const rhsp = nodep->rhsp();
// Only care about concatenations an zero extend on the RHS
if (!VN_IS(rhsp, Concat) && !VN_IS(rhsp, Extend)) return false;
// Will need the LHS
AstNodeExpr* lhsp = nodep->lhsp();
UASSERT_OBJ(lhsp->width() == rhsp->width(), nodep, "Inconsistent assignment");
@@ -268,8 +297,19 @@ class FuncOptVisitor final : public VNVisitor {
UINFO(5, "splitConcat optimizing " << nodep);
++m_stats.m_concatSplits;
// The 2 parts and their offsets
AstNodeExpr* const rrp = rhsp->rhsp()->unlinkFrBack();
AstNodeExpr* const rlp = rhsp->lhsp()->unlinkFrBack();
AstNodeExpr* const rrp = [rhsp]() -> AstNodeExpr* {
if (AstConcat* const catp = VN_CAST(rhsp, Concat)) {
return catp->rhsp()->unlinkFrBack();
}
return VN_AS(rhsp, Extend)->lhsp()->unlinkFrBack();
}();
AstNodeExpr* const rlp = [rhsp, rrp]() -> AstNodeExpr* {
if (AstConcat* const catp = VN_CAST(rhsp, Concat)) {
return catp->lhsp()->unlinkFrBack();
}
const int lWidth = rhsp->width() - rrp->width();
return new AstConst{rhsp->fileline(), AstConst::WidthedValue{}, lWidth, 0};
}();
const int rLsb = lsb;
const int lLsb = lsb + rrp->width();
// Insert the 2 assignment right after the original. They will be visited next.
@@ -301,16 +341,23 @@ class FuncOptVisitor final : public VNVisitor {
}
void visit(AstConcat* nodep) override {
if (v3Global.opt.fFuncBalanceCat() && !nodep->user1() && !VN_IS(nodep->backp(), Concat)) {
if (AstConcat* const newp = BalanceConcatTree::apply(nodep)) {
UINFO(5, "balanceConcat optimizing " << nodep);
++m_stats.m_balancedConcats;
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
newp->user1(true); // Must not attempt again.
// Return here. The new node will be iterated next.
return;
}
if (AstNodeExpr* const newp = BalanceConcatTree::apply(nodep)) {
UINFO(5, "balanceConcat optimizing " << nodep);
++m_stats.m_balancedConcats;
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return; // The new node will be iterated next
}
iterateChildren(nodep);
}
void visit(AstExtend* nodep) override {
if (AstNodeExpr* const newp = BalanceConcatTree::apply(nodep)) {
UINFO(5, "balanceConcat optimizing " << nodep);
++m_stats.m_balancedConcats;
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return; // The new node will be iterated next
}
iterateChildren(nodep);
}
+3
View File
@@ -125,6 +125,7 @@ class V3Global final {
bool m_usesProbDist = false; // Uses $dist_*
bool m_usesStdPackage = false; // Design uses the std package
bool m_usesTiming = false; // Design uses timing constructs
bool m_usesForce = false; // Design uses force/release statements
bool m_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay)
bool m_hasForceableSignals = false; // Need to apply V3Force pass
bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted
@@ -205,6 +206,8 @@ public:
void setUsesZeroDelay() { m_usesZeroDelay = true; }
bool hasForceableSignals() const { return m_hasForceableSignals; }
void setHasForceableSignals() { m_hasForceableSignals = true; }
bool usesForce() const { return m_usesForce; }
void setUsesForce() { m_usesForce = true; }
bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; }
void setHasSystemCSections() { m_hasSystemCSections = true; }
V3HierGraph* hierGraphp() const { return m_hierGraphp; }
+1 -1
View File
@@ -40,7 +40,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// Changes user() and weight()
class GraphRemoveRedundant final : GraphAlg<> {
const bool m_sumWeights; ///< Sum, rather then maximize weights
const bool m_sumWeights; // Sum, rather then maximize weights
private:
void main() {
for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex);
+193 -53
View File
@@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor {
// Find all cells with arrays, and convert to non-arrayed
private:
// STATE
// Range for arrayed instantiations, nullptr for normal instantiations
const AstRange* m_cellRangep = nullptr;
int m_instSelNum = 0; // Current instantiation count 0..N-1
const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells
int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion
InstDeModVarVisitor m_deModVars; // State of variables for current cell module
// VISITORS
@@ -233,52 +232,72 @@ private:
m_deModVars.main(nodep->modp());
//
if (nodep->rangep()) {
m_cellRangep = nodep->rangep();
// Collect the full range chain (outer first).
std::vector<const AstRange*> rangesp;
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
rangesp.push_back(rp);
}
m_cellRangep = rangesp.front();
const int ndim = static_cast<int>(rangesp.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = rangesp[d]->elementsConst();
totalElems *= sizes[d];
}
AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var);
// cppcheck-suppress constVariablePointer
AstNodeDType* const ifaceVarDtp
= ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr;
const bool isIface
= ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType)
&& VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
&& !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
->isVirtual();
// Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType.
AstIfaceRefDType* origIfaceRefp = nullptr;
AstUnpackArrayDType* innermostArrp = nullptr;
for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) {
if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) {
innermostArrp = arrp;
dp = arrp->subDTypep()->skipRefp();
} else {
origIfaceRefp = VN_CAST(dp, IfaceRefDType);
break;
}
}
const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual();
// Make all of the required clones
for (int i = 0; i < m_cellRangep->elementsConst(); i++) {
m_instSelNum
= m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i;
const int instNum = m_cellRangep->loConst() + i;
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
// Unflatten n into a row-major cartesian index; outer dim most significant.
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
// Flat select number for 1D-compat pin expansion; ascending dims invert.
// Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx).
int flatSel = 0;
string suffix;
for (int d = 0; d < ndim; ++d) {
const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d];
flatSel = flatSel * sizes[d] + sel;
suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d])
+ "__KET__";
}
m_instSelNum = flatSel;
AstCell* const newp = nodep->cloneTree(false);
nodep->addNextHere(newp);
// Remove ranging and fix name
newp->rangep()->unlinkFrBack()->deleteTree();
// Somewhat illogically, we need to rename the original name of the cell too.
// as that is the name users expect for dotting
// The spec says we add [x], but that won't work in C...
newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__");
while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
newp->name(newp->name() + suffix);
newp->origName(newp->origName() + suffix);
UINFO(8, " CELL loop " << newp);
// If this AstCell is actually an interface instantiation, also clone the IfaceRef
// within the same parent module as the cell
// Interface instantiation: also clone the IfaceRef in the parent module.
if (isIface) {
AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType);
AstIfaceRefDType* const origIfaceRefp
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType);
origIfaceRefp->cellp(nullptr);
AstVar* const varNewp = ifaceVarp->cloneTree(false);
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
arrdtype->addNextHere(ifaceRefp);
innermostArrp->addNextHere(ifaceRefp);
ifaceRefp->cellp(newp);
ifaceRefp->cellName(newp->name());
varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum)
+ "__KET__");
varNewp->name(varNewp->name() + suffix);
varNewp->origName(varNewp->origName() + suffix);
varNewp->dtypep(ifaceRefp);
newp->addNextHere(varNewp);
if (debug() == 9) {
@@ -389,6 +408,105 @@ private:
}
} else {
AstVar* const pinVarp = nodep->modVarp();
// Multi-dim whole-array iface pin fanout: cartesian-product the port's
// nested UnpackArrayDType layers and emit one pin + per-element var per cell.
// For 1-dim falls through to the original code below.
std::vector<const AstUnpackArrayDType*> portArrs;
for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
portArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
if (portArrs.size() >= 2) {
AstIfaceRefDType* const portIrp
= VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType);
if (!portIrp || portIrp->isVirtual()) return;
const int ndim = static_cast<int>(portArrs.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = portArrs[d]->elementsConst();
totalElems *= sizes[d];
}
// V3Width should have already rejected non-VarRef and rank-mismatch pins;
// these are defensive internal-invariant guards.
const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef);
UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port");
std::vector<const AstUnpackArrayDType*> exprArrs;
for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
exprArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
UASSERT_OBJ(exprArrs.size() == static_cast<size_t>(ndim), nodep->exprp(),
"Multi-dim iface pin expression rank does not match port");
AstNode* prevp = nullptr;
AstNode* prevPinp = nullptr;
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
string portSuffix;
string exprSuffix;
for (int d = 0; d < ndim; ++d) {
portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d])
+ "__KET__";
exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d])
+ "__KET__";
}
const string varNewName = pinVarp->name() + portSuffix;
AstVar* varNewp = nullptr;
if (!pinVarp->backp()) {
varNewp = m_deModVars.find(varNewName);
} else {
portIrp->cellp(nullptr);
varNewp = pinVarp->cloneTree(false);
varNewp->name(varNewName);
varNewp->origName(varNewp->origName() + portSuffix);
varNewp->dtypep(portIrp);
m_deModVars.insert(varNewp);
if (!prevp) {
prevp = varNewp;
} else {
prevp->addNextHere(varNewp);
}
}
if (!varNewp) {
if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE
nodep->v3fatalSrc("Module dearray failed for "
<< AstNode::prettyNameQ(varNewName));
}
AstPin* const newp = nodep->cloneTree(false);
newp->modVarp(varNewp);
newp->name(newp->name() + portSuffix);
AstVarXRef* const newVarXRefp = new AstVarXRef{
nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE};
newVarXRefp->varp(newp->modVarp());
newp->exprp()->unlinkFrBack()->deleteTree();
newp->exprp(newVarXRefp);
if (!prevPinp) {
prevPinp = newp;
} else {
prevPinp->addNextHere(newp);
}
}
if (prevp) {
pinVarp->replaceWith(prevp);
pushDeletep(pinVarp);
}
nodep->replaceWith(prevPinp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
const AstUnpackArrayDType* const pinArrp
= VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType);
if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
@@ -472,28 +590,50 @@ private:
}
}
void visit(AstArraySel* nodep) override {
if (const AstUnpackArrayDType* const arrp
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
V3Const::constifyParamsEdit(nodep->bitp());
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
// If a parent is also an ArraySel into the same iface array, let it handle the chain.
if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return;
// Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index).
std::vector<AstArraySel*> sels;
AstNode* curp = nodep;
while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) {
sels.push_back(asp);
curp = asp->fromp();
}
const AstVarRef* const varrefp = VN_CAST(curp, VarRef);
if (!varrefp) return;
// Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType.
std::vector<AstUnpackArrayDType*> arrs; // outer dim first
AstNodeDType* dtp = varrefp->dtypep()->skipRefp();
while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) {
arrs.push_back(ap);
dtp = ap->subDTypep()->skipRefp();
}
if (arrs.empty() || sels.size() != arrs.size()) return;
AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType);
if (!irp || irp->isVirtual()) return;
// Constify bitps and collect indices in outer-dim-first order (sels is inner-first).
std::vector<int> indices(sels.size());
for (size_t i = 0; i < sels.size(); ++i) {
AstArraySel* const asp = sels[i];
V3Const::constifyParamsEdit(asp->bitp());
const AstConst* const constp = VN_CAST(asp->bitp(), Const);
if (!constp) {
nodep->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
asp->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
return;
}
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo());
const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
UASSERT_OBJ(varrefp, nodep, "No interface varref under array");
AstVarXRef* const newp = new AstVarXRef{
nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "",
VAccess::READ};
newp->dtypep(arrp->subDTypep());
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
indices[sels.size() - 1 - i] = constp->toSInt();
}
string indexStr;
for (size_t i = 0; i < indices.size(); ++i) {
indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__";
}
AstVarXRef* const newp
= new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ};
newp->dtypep(irp);
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstNodeAssign* nodep) override {
if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) {
+12 -5
View File
@@ -776,12 +776,19 @@ class LinkCellsVisitor final : public VNVisitor {
idtypep->cellp(nodep); // Only set when real parent cell known.
AstVar* varp;
if (nodep->rangep()) {
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst
AstNodeArrayDType* const arrp
= new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep,
nodep->rangep()->cloneTree(true)};
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst.
// Multi-dim arrays wrap one UnpackArrayDType per range, innermost first.
std::vector<AstRange*> rangesp;
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
rangesp.push_back(rp);
}
AstNodeDType* dtp = idtypep;
for (auto it = rangesp.rbegin(); it != rangesp.rend(); ++it) {
dtp = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, dtp,
(*it)->cloneTree(false)};
}
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, arrp};
VFlagChildDType{}, dtp};
} else {
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, idtypep};
+36 -13
View File
@@ -524,17 +524,19 @@ public:
void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE
m_ifaceVarSyms.push_back(symp);
}
// Iface for a raw or arrayed iface
// Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays.
static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) {
AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType);
if (!ifacerefp) {
while (nodep) {
if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp;
if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else {
return nullptr;
}
}
return ifacerefp;
return nullptr;
}
// Given a pin expression, resolve it to a live AstIface* (or nullptr).
// Handles both simple VarRef and dotted VarXRef pin connections.
@@ -770,10 +772,16 @@ public:
if (forPrearray()) {
// GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled,
// but presently should be just "foo".
// Likewise cell foo__[array] before we've expanded arrays is just foo
if ((pos = ident.rfind("__BRA__")) != string::npos) {
altIdent = ident.substr(0, pos);
// Likewise cell foo__[array] before we've expanded arrays is just foo.
// Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them
// all.
altIdent = ident;
while (VString::endsWith(altIdent, "__KET__")) {
const auto braPos = altIdent.rfind("__BRA__");
if (braPos == string::npos) break;
altIdent = altIdent.substr(0, braPos);
}
if (altIdent == ident) altIdent.clear();
}
UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname
<< " at se" << lookupSymp);
@@ -4323,6 +4331,13 @@ class LinkDotResolveVisitor final : public VNVisitor {
<< cellp->modp()->prettyNameQ());
}
}
} else if (allowScope && !allowFTask && VN_IS(foundp->nodep(), NodeFTask)) {
// Function/task used as scope for static variable access (IEEE std 1800-2017 6.21)
// Don't set m_dotText so the static variable resolves as a VarRef rather than
// VarXRef. This is so that V3Begin won't move it out of the function/task's scope.
ok = true;
m_ds.m_dotSymp = foundp;
m_ds.m_dotPos = DP_SCOPE;
} else if (allowFTask && VN_IS(foundp->nodep(), NodeFTask)) {
AstNodeFTaskRef* taskrefp;
if (VN_IS(foundp->nodep(), Task)) {
@@ -5316,10 +5331,18 @@ class LinkDotResolveVisitor final : public VNVisitor {
symIterateNull(nodep->attrp(), m_curSymp);
if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) {
AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack();
AstCellArrayRef* const newp
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) {
// Multi-dim iface array access: append this select to the existing chain
fromArrRefp->addSelp(exprp);
AstCellArrayRef* const movedp = fromArrRefp->unlinkFrBack();
nodep->replaceWith(movedp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
AstCellArrayRef* const newp
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
}
void visit(AstNodePreSel* nodep) override {
+12
View File
@@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor {
m_varp->attrSplitVar(true);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_ARC_INCLUDE_COND) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmArcInclCond(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_RESET_ARC) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmResetArc(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_STATE) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmState(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrScBigUint(true);
+10 -20
View File
@@ -1039,14 +1039,6 @@ void V3Options::notify() VL_MT_DISABLED {
if (m_timing.isDefault() && (v3Global.opt.jsonOnly() || v3Global.opt.lintOnly()))
v3Global.opt.m_timing.setTrueOrFalse(true);
if (trace()) {
// With --trace-vcd, --trace-threads is ignored
if (traceEnabledVcd()) m_traceThreads = 1;
}
UASSERT(!(useTraceParallel() && useTraceOffload()),
"Cannot use both parallel and offloaded tracing");
// Default split limits if not specified
if (m_outputSplitCFuncs < 0) m_outputSplitCFuncs = m_outputSplit;
if (m_outputSplitCTrace < 0) m_outputSplitCTrace = m_outputSplit;
@@ -1360,6 +1352,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-coverage", CbOnOff, [this](bool flag) { coverage(flag); });
DECL_OPTION("-coverage-expr", OnOff, &m_coverageExpr);
DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax);
DECL_OPTION("-coverage-fsm", OnOff, &m_coverageFsm);
DECL_OPTION("-coverage-line", OnOff, &m_coverageLine);
DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth);
DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle);
@@ -1822,22 +1815,16 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
m_traceEnabledFst = true;
addLdLibs("-lz");
});
DECL_OPTION("-trace-fst-thread", CbCall, [this, fl]() {
m_traceEnabledFst = true;
addLdLibs("-lz");
fl->v3warn(DEPRECATED, "Option --trace-fst-thread is deprecated. "
"Use --trace-fst with --trace-threads > 0.");
if (m_traceThreads == 0) m_traceThreads = 1;
DECL_OPTION("-trace-fst-thread", CbCall, [fl]() {
fl->v3warn(DEPRECATED, "Option '--trace-fst-thread' is deprecated and has no effect.");
}).undocumented();
DECL_OPTION("-trace-max-array", Set, &m_traceMaxArray);
DECL_OPTION("-trace-max-width", Set, &m_traceMaxWidth);
DECL_OPTION("-trace-params", OnOff, &m_traceParams);
DECL_OPTION("-trace-structs", OnOff, &m_traceStructs);
DECL_OPTION("-trace-threads", CbVal, [this, fl](const char* valp) {
m_trace = true;
m_traceThreads = std::atoi(valp);
if (m_traceThreads < 1) fl->v3fatal("--trace-threads must be >= 1: " << valp);
});
DECL_OPTION("-trace-threads", CbVal, [fl](const char*) {
fl->v3warn(DEPRECATED, "Option '--trace-threads' is deprecated and has no effect.");
}).undocumented();
DECL_OPTION("-no-trace-top", Set, &m_noTraceTop);
DECL_OPTION("-trace-underscore", OnOff, &m_traceUnderscore);
DECL_OPTION("-trace-vcd", CbCall, [this]() { m_traceEnabledVcd = true; });
@@ -2309,7 +2296,10 @@ void V3Options::setDebugMode(int level) {
if (!m_dumpLevel.count("tree")) m_dumpLevel["tree"] = 3; // Don't override if already set.
m_stats = true;
m_debugCheck = true;
if (level) cout << "Starting " << version() << "\n";
if (level) {
cout << "- Starting " << version() << "\n";
UINFO(1, "Current working directory (CWD) is " << V3Os::cwd());
}
}
unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE {
+15 -5
View File
@@ -226,6 +226,7 @@ private:
bool m_build = false; // main switch: --build
bool m_context = true; // main switch: --Wcontext
bool m_coverageExpr = false; // main switch: --coverage-expr
bool m_coverageFsm = false; // main switch: --coverage-fsm
bool m_coverageLine = false; // main switch: --coverage-block
bool m_coverageToggle = false; // main switch: --coverage-toggle
bool m_coverageUnderscore = false; // main switch: --coverage-underscore
@@ -351,7 +352,6 @@ private:
int m_traceDepth = 0; // main switch: --trace-depth
int m_traceMaxArray = 32; // main switch: --trace-max-array
int m_traceMaxWidth = 4096; // main switch: --trace-max-width
int m_traceThreads = 0; // main switch: --trace-threads
int m_unrollCount = 64; // main switch: --unroll-count
int m_unrollLimit = 16384; // main switch: --unroll-limit
int m_unrollStmts = 30000; // main switch: --unroll-stmts
@@ -448,7 +448,7 @@ private:
void optimize(int level);
void showVersion(bool verbose);
void coverage(bool flag) {
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageUser = flag;
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageFsm = m_coverageUser = flag;
}
static bool suffixed(const string& sw, const char* arg);
static string parseFileArg(const string& optdir, const string& relfilename);
@@ -509,9 +509,19 @@ public:
void buildDepBin(const string& flag) { m_buildDepBin = flag; }
bool context() const VL_MT_SAFE { return m_context; }
bool coverage() const VL_MT_SAFE {
// Any enabled coverage kind, including FSM coverage. Code generation
// and runtime support should generally query this accessor.
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser
|| m_coverageFsm;
}
bool coverageNonFsm() const VL_MT_SAFE {
// The broad line/toggle/expr/user coverage transforms use this
// accessor. FSM coverage shares the overall coverage umbrella, but its
// extraction still happens through a separate early-recognition path.
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser;
}
bool coverageExpr() const { return m_coverageExpr; }
bool coverageFsm() const { return m_coverageFsm; }
bool coverageLine() const { return m_coverageLine; }
bool coverageToggle() const { return m_coverageToggle; }
bool coverageUnderscore() const { return m_coverageUnderscore; }
@@ -535,6 +545,9 @@ public:
bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; }
bool dpiHdrOnly() const { return m_dpiHdrOnly; }
bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); }
bool dumpDfgPatterns() const {
return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns");
}
bool dumpTreeDot() const {
return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot");
}
@@ -634,12 +647,9 @@ public:
int traceDepth() const { return m_traceDepth; }
int traceMaxArray() const { return m_traceMaxArray; }
int traceMaxWidth() const { return m_traceMaxWidth; }
int traceThreads() const { return m_traceThreads; }
bool useTraceOffload() const { return trace() && traceEnabledFst() && traceThreads() > 1; }
bool useTraceParallel() const {
return trace() && traceEnabledVcd() && (threads() > 1 || hierChild() > 1);
}
bool useFstWriterThread() const { return traceThreads() && traceEnabledFst(); }
int unrollCount() const { return m_unrollCount; }
int unrollLimit() const { return m_unrollLimit; }
int unrollStmts() const { return m_unrollStmts; }
+117 -66
View File
@@ -475,6 +475,12 @@ class ParamProcessor final {
}
return nullptr;
}
// Peel all unpacked-array layers to reach the innermost subDTypep.
// Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType.
static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) {
while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp;
return nodep;
}
static bool isString(AstNodeDType* nodep) {
if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType))
return basicp->isString();
@@ -1400,19 +1406,23 @@ class ParamProcessor final {
AstConst* normedNamep = nullptr;
if (exprp && !exprp->num().isDouble() && !exprp->num().isString()) {
AstVar* cloneVarp = modvarp->cloneTree(false);
bool cloneVarpUnresolved = false;
if (AstNode* const oldValuep = cloneVarp->valuep()) {
oldValuep->unlinkFrBack();
VL_DO_DANGLING(oldValuep->deleteTree(), oldValuep);
}
cloneVarp->valuep(exprp->cloneTree(false));
if (AstNodeDType* const origDTypep = modvarp->subDTypep()) {
AstNodeDType* const dtypeClonep = origDTypep->cloneTree(false);
// Inline every param ref so widthing doesn't reach back into the template
// (#7411). Cycle detector for dependent parameters in the same module.
// Attach clone under cloneVarp so the root has a back pointer.
if (cloneVarp->childDTypep())
cloneVarp->childDTypep()->unlinkFrBack()->deleteTree();
cloneVarp->childDTypep(origDTypep->cloneTree(false));
cloneVarp->dtypep(nullptr);
// Inline param refs so widthing doesn't touch the template (#7411).
constexpr int maxSubstIters = 1000;
for (int it = 0; it < maxSubstIters; ++it) {
bool any = false;
dtypeClonep->foreach([&](AstVarRef* varrefp) {
cloneVarp->foreach([&](AstVarRef* varrefp) {
AstVar* const targetp = varrefp->varp();
AstNode* replacep = nullptr;
for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) {
@@ -1432,26 +1442,60 @@ class ParamProcessor final {
any = true;
}
});
// Replace RefDType to a ParamTypeDType with pin override
// or the paramtype's default so constify below does not
// reach into the template. Collect then replace in
// reverse so descendants aren't freed early.
std::vector<std::pair<AstRefDType*, AstNodeDType*>> toReplace;
cloneVarp->foreach([&](AstRefDType* refp) {
AstParamTypeDType* const ptdp
= VN_CAST(refp->refDTypep(), ParamTypeDType);
if (!ptdp) return;
AstPin* overridePinp = nullptr;
for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) {
if (pp->modPTypep() == ptdp) {
overridePinp = pp;
break;
}
}
AstNodeDType* const substp
= overridePinp ? VN_CAST(overridePinp->exprp(), NodeDType)
: ptdp->subDTypep();
if (substp) toReplace.emplace_back(refp, substp);
});
for (auto it = toReplace.rbegin(); it != toReplace.rend(); ++it) {
AstRefDType* const refp = it->first;
refp->replaceWith(it->second->cloneTree(false));
VL_DO_DANGLING(refp->deleteTree(), refp);
any = true;
}
if (!any) break;
}
// Bail if anything still points at the template.
dtypeClonep->foreach([&](AstVarRef* varrefp) {
cloneVarp->foreach([&](AstVarRef* varrefp) {
varrefp->v3fatalSrc(
"Unresolved VarRef '"
<< varrefp->prettyName() << "' in pin dtype clone. Pin: "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
});
if (cloneVarp->childDTypep())
cloneVarp->childDTypep()->unlinkFrBack()->deleteTree();
cloneVarp->childDTypep(dtypeClonep);
cloneVarp->dtypep(nullptr);
// Skip the widthing constify if any RefDType is unresolved.
cloneVarp->foreach([&](AstRefDType* refp) {
if (VN_IS(refp->refDTypep(), ParamTypeDType)) {
UINFO(5, " cellPinCleanup: skip normedNamep "
"(unresolved RefDType->ParamTypeDType) pin="
<< pinp->prettyNameQ());
cloneVarpUnresolved = true;
}
});
}
V3Const::constifyParamsEdit(cloneVarp);
if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) {
// Stamp the port's type on the const so equal values hash the same.
if (cloneVarp->dtypep()) widthedp->dtypep(cloneVarp->dtypep());
widthedp->unlinkFrBack();
normedNamep = widthedp;
if (!cloneVarpUnresolved) {
V3Const::constifyParamsEdit(cloneVarp);
if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) {
// Stamp the port's type so equal values hash the same.
if (cloneVarp->dtypep()) widthedp->dtypep(cloneVarp->dtypep());
widthedp->unlinkFrBack();
normedNamep = widthedp;
}
}
VL_DO_DANGLING(cloneVarp->deleteTree(), cloneVarp);
}
@@ -1589,38 +1633,32 @@ class ParamProcessor final {
const AstVar* const modvarp = pinp->modVarp();
if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue;
if (modvarp->isIfaceRef()) {
AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType);
if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) {
portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType);
}
AstIfaceRefDType* pinIrefp = nullptr;
// arraySubDTypeDeepp returns input unchanged if not an array.
AstIfaceRefDType* const portIrefp
= VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType);
const AstNode* const exprp = pinp->exprp();
const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef))
? VN_AS(exprp, NodeVarRef)->varp()
: nullptr;
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType);
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
&& VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) {
pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType);
} else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef)
&& VN_CAST(exprp->op1p(), VarRef)->varp()
&& VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()
&& arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep())
&& VN_CAST(
arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType)) {
pinIrefp
= VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType);
} else if (VN_IS(exprp, CellArrayRef)) {
// Interface array element selection (e.g., l1(l2.l1[0]) for nested iface
// array) The CellArrayRef is not yet fully linked to an interface type. Skip
// interface cleanup for this pin - V3LinkDot will resolve this later. Just
// continue to the next pin without error.
if (VN_IS(exprp, CellArrayRef)) {
// CellArrayRef not yet linked; V3LinkDot resolves this pin later.
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp);
continue;
}
AstIfaceRefDType* pinIrefp = nullptr;
// Pin is a VarRef to a var of (possibly arrayed) iface type.
if (const AstNodeVarRef* const vrp = VN_CAST(exprp, NodeVarRef)) {
if (vrp->varp()) {
pinIrefp
= VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType);
}
}
// Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array).
if (!pinIrefp && exprp) {
if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) {
if (vrp->varp()) {
pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()),
IfaceRefDType);
}
}
}
UINFO(9, " portIfaceRef " << portIrefp);
@@ -2813,33 +2851,46 @@ class ParamVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstCellArrayRef* nodep) override {
V3Const::constifyParamsEdit(nodep->selp());
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) {
const string index = AstNode::encodeNumber(constp->toSInt());
// For nested interface array ports, the node name may have a __Viftop suffix
// that doesn't exist in the original unlinked text. Try without the suffix.
const string viftopSuffix = "__Viftop";
const string baseName
= VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
: nodep->name();
const string replacestr = baseName + "__BRA__??__KET__";
const size_t pos = m_unlinkedTxt.find(replacestr);
// For interface port array element selections (e.g., l1(l2.l1[0])),
// the AstCellArrayRef may be visited outside of an AstUnlinkedRef context.
// In such cases, m_unlinkedTxt won't contain the expected pattern.
// Simply skip the replacement - the cell array ref will be resolved later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep);
// Multi-dim iface arrays chain multiple selps via nextp(); constify each in place.
for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) {
nxt = s->nextp(); // cache before constifyParamsEdit replaces s
V3Const::constifyParamsEdit(s);
}
std::vector<int> indices;
for (AstNode* s = nodep->selp(); s; s = s->nextp()) {
const AstConst* const constp = VN_CAST(s, Const);
if (!constp) {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< s->prettyNameQ());
return;
}
m_unlinkedTxt.replace(pos, replacestr.length(),
baseName + "__BRA__" + index + "__KET__");
} else {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< nodep->selp()->prettyNameQ());
indices.push_back(constp->toSInt());
}
// Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt.
const string viftopSuffix = "__Viftop";
const string baseName
= VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
: nodep->name();
const string placeholder = "__BRA__??__KET__";
size_t pos = m_unlinkedTxt.find(baseName + placeholder);
// An AstCellArrayRef for an iface-array pin expr (e.g. l1(l2.l1[0])) is visited
// outside AstUnlinkedRef, so m_unlinkedTxt has no placeholder; V3LinkDot resolves later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep);
return;
}
pos += baseName.length();
for (int idx : indices) {
const string replacement = "__BRA__" + AstNode::encodeNumber(idx) + "__KET__";
if (m_unlinkedTxt.compare(pos, placeholder.length(), placeholder) != 0) {
nodep->v3fatalSrc( // LCOV_EXCL_LINE
"Expected placeholder at position in multi-dim iface dotted reference");
return;
}
m_unlinkedTxt.replace(pos, placeholder.length(), replacement);
pos += replacement.length();
}
}
// Generate Statements
+11 -8
View File
@@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n
return assignp;
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list
AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list; preserves multi-dim chain via nextp().
for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) {
nextp = VN_AS(nodep->nextp(), NodeRange);
if (!VN_IS(nodep, Range)) {
@@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
if (nrangep && nrangep->nextp()) {
// Not supported by at least 2 of big 3
nrangep->nextp()->v3warn(E_UNSUPPORTED,
"Unsupported: Multidimensional instances/interfaces.");
nrangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return VN_CAST(nrangep, Range);
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
AstRange* const rangep = scrubRangeMulti(nrangep);
if (rangep && rangep->nextp()) {
// Gate primitives only support a single dimension
rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances.");
rangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return rangep;
}
AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED {
// SEL(PARSELVALUE, ...) -> SEL(fromp, ...)
+2 -1
View File
@@ -71,6 +71,7 @@ public:
return v3Global.opt.trace() && m_tracingParse && fl->tracingOn();
}
AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED;
AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED;
AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED;
AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep,
bool isPacked) VL_MT_DISABLED;
@@ -90,7 +91,7 @@ public:
singletonp()->m_instModule,
pinlistp,
(singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr),
singletonp()->scrubRange(rangelistp)};
singletonp()->scrubRangeMulti(rangelistp)};
nodep->trace(singletonp()->allTracingOn(fileline));
return nodep;
}
+4
View File
@@ -263,6 +263,10 @@ class PremitVisitor final : public VNVisitor {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCMethodHard* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCvtArrayToPacked* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
+1 -1
View File
@@ -221,7 +221,7 @@ class ProtectVisitor final : public VNVisitor {
+ "_protectlib_final(chandle handle__V);\n\n");
// Local variables
// Avoid tracing handle, as it is not a stable value, so breaks vcddiff
// Avoid tracing handle, as it is not a stable value, so breaks wavediff
// Likewise other internals aren't interesting to the user
txtp->add("// verilator tracing_off\n");
+160 -25
View File
@@ -964,7 +964,9 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNodeExpr* newSel(FileLine* fl, AstNodeExpr* arrayp, AstNodeExpr* idxp) {
// similar to V3WidthSel.cpp
AstNodeDType* const arrDtp = arrayp->unlinkFrBack()->dtypep();
// skipRefp() unwraps typedef wrappers (AstRefDType) so the dtype kind
// checks below recognize types spelled as `typedef <scalar> <name>[$]`.
AstNodeDType* const arrDtp = arrayp->unlinkFrBack()->dtypep()->skipRefp();
AstNodeExpr* selp = nullptr;
if (VN_IS(arrDtp, QueueDType) || VN_IS(arrDtp, DynArrayDType))
selp = new AstCMethodHard{fl, arrayp, VCMethod::ARRAY_AT, idxp};
@@ -2007,6 +2009,10 @@ class ConstraintExprVisitor final : public VNVisitor {
void visit(AstSFormatF* nodep) override {}
void visit(AstStmtExpr* nodep) override {}
void visit(AstCStmt* nodep) override {}
void visit(AstIf* nodep) override {
// Hoisted runtime-conditional disable_soft statements parked in the
// constraint-items list by the pre-pass; already fully lowered.
}
void visit(AstConstraintIf* nodep) override {
AstNodeExpr* newp = nullptr;
FileLine* const fl = nodep->fileline();
@@ -2192,28 +2198,13 @@ class ConstraintExprVisitor final : public VNVisitor {
}
void visit(AstConstraintExpr* nodep) override {
// IEEE 1800-2023 18.5.13: "disable soft" removes all soft constraints
// referencing the specified variable. Pass the variable name directly
// instead of going through SMT lowering.
// IEEE 1800-2023 18.5.13: 'disable soft' is a meta-level directive on
// the constraint graph, lowered to a void RANDOMIZER_DISABLE_SOFT
// call. Conditional occurrences (if / foreach / implication body)
// are rewritten by extractConditionalDisableSofts() before this
// visitor runs, so anything we see here is unconditional.
if (nodep->isDisableSoft()) {
// Extract variable name from expression (VarRef or MemberSel)
std::string varName;
if (const AstNodeVarRef* const vrefp = VN_CAST(nodep->exprp(), NodeVarRef)) {
varName = vrefp->name();
} else if (const AstMemberSel* const mselp = VN_CAST(nodep->exprp(), MemberSel)) {
varName = mselp->name();
} else {
nodep->v3fatalSrc("Unexpected expression type in disable soft");
return;
}
AstCMethodHard* const callp = new AstCMethodHard{
nodep->fileline(),
new AstVarRef{nodep->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
VAccess::READWRITE},
VCMethod::RANDOMIZER_DISABLE_SOFT,
new AstConst{nodep->fileline(), AstConst::String{}, varName}};
callp->dtypeSetVoid();
nodep->replaceWith(callp->makeStmt());
nodep->replaceWith(buildDisableSoftCallStmt(nodep));
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
@@ -2571,12 +2562,23 @@ class ConstraintExprVisitor final : public VNVisitor {
const V3TaskConnects tconnects
= V3Task::taskConnects(nodep, funcp->stmtsp(), nullptr, false);
// Clone return expression, substitute params with args
AstNodeExpr* const inlinedp = retExprp->cloneTreePure(false);
// Clone return expression, substitute params with args.
// Track the root via a local pointer: when the root itself is a VarRef to a port
// (e.g. body `F = d;`), the cloned root has no back pointer, so foreach's
// replaceWith would trip the "no back" assertion -- substitute the root pointer
// directly instead.
AstNodeExpr* inlinedp = retExprp->cloneTreePure(false);
for (const auto& tconnect : tconnects) {
const AstVar* const portp = tconnect.first;
AstArg* const argp = tconnect.second;
if (!argp || !argp->exprp()) continue;
if (AstVarRef* const rootRefp = VN_CAST(inlinedp, VarRef)) {
if (rootRefp->varp() == portp) {
inlinedp = argp->exprp()->cloneTreePure(false);
VL_DO_DANGLING(rootRefp->deleteTree(), rootRefp);
continue;
}
}
inlinedp->foreach([&](AstVarRef* refp) {
if (refp->varp() == portp) {
refp->replaceWith(argp->exprp()->cloneTreePure(false));
@@ -2603,6 +2605,93 @@ class ConstraintExprVisitor final : public VNVisitor {
"Visit function missing? Constraint function missing for node: " << nodep);
}
// Build the runtime statement that calls RANDOMIZER_DISABLE_SOFT on the
// variable referenced by the given AstConstraintExpr. Parser enforces
// constraint_primary operand (verilog.y, IEEE 1800-2023 18.5.13).
AstNode* buildDisableSoftCallStmt(AstConstraintExpr* nodep) {
std::string varName;
if (const AstNodeVarRef* const vrefp = VN_CAST(nodep->exprp(), NodeVarRef)) {
varName = vrefp->name();
} else if (const AstMemberSel* const mselp = VN_CAST(nodep->exprp(), MemberSel)) {
varName = mselp->name();
} else {
nodep->v3fatalSrc("Unexpected expression type in disable soft");
}
AstCMethodHard* const callp = new AstCMethodHard{
nodep->fileline(),
new AstVarRef{nodep->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
VAccess::READWRITE},
VCMethod::RANDOMIZER_DISABLE_SOFT,
new AstConst{nodep->fileline(), AstConst::String{}, varName}};
callp->dtypeSetVoid();
return callp->makeStmt();
}
// Pre-pass: for every AstConstraintExpr(isDisableSoft) nested inside an
// AstConstraintIf subtree (any depth), return a list of runtime AstIf
// statements `if (accumulated_cond) randomizer.disable_soft(var);` where
// accumulated_cond AND-folds the enclosing then/else branch conditions.
// The original ConstraintExpr is replaced with `1'b1` so editSingle()
// folds the surrounding boolean cleanly. Caller appends the returned
// list to the constraint-items chain; that chain becomes the setup task
// body, which runs before the solver on every randomize() call -- giving
// disable-soft real conditional semantics per IEEE 1800-2023 18.5.13.
AstNode* extractConditionalDisableSofts(AstNode* itemsp, AstNodeExpr* outerCondp) {
AstNode* hoistListp = nullptr;
for (AstNode *stmtp = itemsp, *nextp; stmtp; stmtp = nextp) {
nextp = stmtp->nextp();
if (AstConstraintIf* const ifp = VN_CAST(stmtp, ConstraintIf)) {
FileLine* const fl = ifp->fileline();
// then branch: accumulated = outer && this.cond
AstNodeExpr* const thenCondp
= outerCondp ? static_cast<AstNodeExpr*>(
new AstLogAnd{fl, outerCondp->cloneTreePure(false),
ifp->condp()->cloneTreePure(false)})
: ifp->condp()->cloneTreePure(false);
if (AstNode* const thenHoistp
= extractConditionalDisableSofts(ifp->thensp(), thenCondp)) {
hoistListp
= hoistListp ? AstNode::addNext(hoistListp, thenHoistp) : thenHoistp;
}
VL_DO_DANGLING(thenCondp->deleteTree(), thenCondp);
if (ifp->elsesp()) {
// else branch: accumulated = outer && !this.cond
AstNodeExpr* const notInnerp
= new AstLogNot{fl, ifp->condp()->cloneTreePure(false)};
AstNodeExpr* const elseCondp
= outerCondp ? static_cast<AstNodeExpr*>(new AstLogAnd{
fl, outerCondp->cloneTreePure(false), notInnerp})
: notInnerp;
if (AstNode* const elseHoistp
= extractConditionalDisableSofts(ifp->elsesp(), elseCondp)) {
hoistListp
= hoistListp ? AstNode::addNext(hoistListp, elseHoistp) : elseHoistp;
}
VL_DO_DANGLING(elseCondp->deleteTree(), elseCondp);
}
} else if (AstConstraintExpr* const exprp = VN_CAST(stmtp, ConstraintExpr)) {
if (exprp->isDisableSoft() && outerCondp) {
FileLine* const fl = exprp->fileline();
AstIf* const hoistIfp = new AstIf{fl, outerCondp->cloneTreePure(false),
buildDisableSoftCallStmt(exprp), nullptr};
hoistListp = hoistListp ? AstNode::addNext(hoistListp,
static_cast<AstNode*>(hoistIfp))
: static_cast<AstNode*>(hoistIfp);
// Replace in-tree disable-soft with a no-op boolean
// constraint so editSingle() folds cleanly.
AstConstraintExpr* const truep
= new AstConstraintExpr{fl, new AstConst{fl, AstConst::BitTrue{}}};
exprp->replaceWith(truep);
VL_DO_DANGLING(exprp->deleteTree(), exprp);
}
}
// foreach/unique bodies are left untouched: disable-soft inside
// a foreach would need per-iteration condition support, and
// unique cannot legally contain disable-soft.
}
return hoistListp;
}
public:
// CONSTRUCTORS
explicit ConstraintExprVisitor(AstClass* classp, VMemberMap& memberMap, AstNode* nodep,
@@ -2618,6 +2707,14 @@ public:
, m_memberMap{memberMap}
, m_writtenVars{writtenVars}
, m_sizeConstrainedArraysp{sizeConstrainedArraysp} {
// Pre-pass before SMT lowering: extract conditional disable-soft
// directives as runtime AstIf statements and append them to the
// constraint-items chain so they reach the setup task body. The SMT
// visitor skips these via visit(AstIf) above.
if (AstNode* const hoistListp
= nodep ? extractConditionalDisableSofts(nodep, nullptr) : nullptr) {
nodep->addNext(hoistListp);
}
iterateAndNextNull(nodep);
}
};
@@ -2760,7 +2857,9 @@ class CaptureVisitor final : public VNVisitor {
m_ignore.emplace(thisRefp);
AstMemberSel* const memberSelp
= new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()};
if (!m_targetp) memberSelp->user1(true);
// Propagate random-dependence marking; forcing it on read-only operands
// would route them through the solver write path.
if (!m_targetp && nodep->user1()) memberSelp->user1(true);
memberSelp->user2p(m_targetp);
nodep->replaceWith(memberSelp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
@@ -3998,6 +4097,23 @@ class RandomizeVisitor final : public VNVisitor {
}
}
// Rewrite a LogIf-of-Dist chain into nested AstConstraintIf. The outermost
// AstLogIf shell is left for the caller's AstConstraintExpr to free; inner
// shells are deleted here once their children are transplanted.
AstConstraintIf* liftLogIfChainToConstraintIf(AstLogIf* logIfp) {
FileLine* const fl = logIfp->fileline();
AstNodeExpr* const condp = logIfp->lhsp()->unlinkFrBack();
AstNodeExpr* const rhsp = logIfp->rhsp()->unlinkFrBack();
AstNode* thenBodyp;
if (AstLogIf* const innerLogIfp = VN_CAST(rhsp, LogIf)) {
thenBodyp = liftLogIfChainToConstraintIf(innerLogIfp);
VL_DO_DANGLING(pushDeletep(innerLogIfp), innerLogIfp);
} else {
thenBodyp = new AstConstraintExpr{fl, rhsp};
}
return new AstConstraintIf{fl, condp, thenBodyp, nullptr};
}
// Replace AstDist with weighted bucket selection via AstConstraintIf chain.
// Supports both constant and variable weight expressions.
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) {
@@ -4019,6 +4135,25 @@ class RandomizeVisitor final : public VNVisitor {
AstConstraintExpr* const constrExprp = VN_CAST(itemp, ConstraintExpr);
if (!constrExprp) continue;
// `cond -> x dist {...}` parses as ConstraintExpr(LogIf(cond, Dist)).
// This pass only scans ConstraintExpr/If/Foreach for Dist, so lift the
// LogIf chain into nested ConstraintIf first. Chains like
// `a -> b -> dist` nest accordingly.
if (AstLogIf* const topLogIfp = VN_CAST(constrExprp->exprp(), LogIf)) {
AstNode* chainEndp = topLogIfp->rhsp();
while (AstLogIf* const innerp = VN_CAST(chainEndp, LogIf)) {
chainEndp = innerp->rhsp();
}
if (VN_IS(chainEndp, Dist)) {
AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp);
constrExprp->replaceWith(liftedp);
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
lowerDistConstraints(taskp, liftedp->thensp());
continue;
}
}
AstDist* const distp = VN_CAST(constrExprp->exprp(), Dist);
if (!distp) continue;
+38 -17
View File
@@ -47,6 +47,7 @@
#include "V3UniqueNames.h"
#include <limits>
#include <map>
#include <set>
#include <unordered_map>
@@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor {
using ActCodeSet = std::set<uint32_t>;
// For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
// Candidate interface-member VarScopes keyed by (interface type, member name)
std::map<std::pair<const AstIface*, std::string>, std::vector<AstVarScope*>>
m_ifaceMemberVscps;
// METHODS
@@ -666,8 +670,7 @@ class TraceVisitor final : public VNVisitor {
// Add it to top scope
m_topScopep->addBlocksp(funcp);
const std::string bufArg
= v3Global.opt.traceClassBase()
+ "::" + (v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer") + "* bufp"
= v3Global.opt.traceClassBase() + "::Buffer* bufp"
+ (declp ? (", uint32_t offset, const " + declp->dtypep()->cType("", true, true)
+ " __VdtypeVar")
: "");
@@ -710,21 +713,11 @@ class TraceVisitor final : public VNVisitor {
+ (declp ? " + offset" : "") + ");\n"});
} else {
// Change dump sub function
if (v3Global.opt.useTraceOffload()) {
funcp->addStmtsp(new AstCStmt{flp, //
"const uint32_t base VL_ATTR_UNUSED = "
"vlSymsp->__Vm_baseCode + "
+ (declp ? " offset" : cvtToStr(baseCode))
+ ";\n"});
funcp->addStmtsp(
new AstCStmt{flp, "(void)bufp; // Prevent unused variable warning\n"});
} else {
funcp->addStmtsp(new AstCStmt{flp, //
"uint32_t* const oldp VL_ATTR_UNUSED = "
"bufp->oldp(vlSymsp->__Vm_baseCode + "
+ (declp ? " offset" : cvtToStr(baseCode))
+ ");\n"});
}
funcp->addStmtsp(new AstCStmt{flp, //
"uint32_t* const oldp VL_ATTR_UNUSED = "
"bufp->oldp(vlSymsp->__Vm_baseCode + "
+ (declp ? " offset" : cvtToStr(baseCode))
+ ");\n"});
}
if (!declp) {
// Add call to top function
@@ -1136,6 +1129,13 @@ class TraceVisitor final : public VNVisitor {
if (nodep->isTop()) m_topModp = nodep;
iterateChildren(nodep);
}
void visit(AstVarScope* nodep) override {
if (!m_finding) {
if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) {
m_ifaceMemberVscps[{ifacep, nodep->varp()->name()}].push_back(nodep);
}
}
}
void visit(AstStmtExpr* nodep) override {
if (!m_finding && !nodep->user2()) {
if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) {
@@ -1225,6 +1225,27 @@ class TraceVisitor final : public VNVisitor {
}
}
}
void visit(AstMemberSel* nodep) override {
if (m_cfuncp && m_finding && nodep->access().isWriteOrRW()) {
AstIfaceRefDType* const dtypep
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType);
if (dtypep && dtypep->isVirtual()) {
const auto it = m_ifaceMemberVscps.find({dtypep->ifacep(), nodep->varp()->name()});
if (it != m_ifaceMemberVscps.end()) {
V3GraphVertex* const funcVtxp = getCFuncVertexp(m_cfuncp);
for (AstVarScope* const vscp : it->second) {
V3GraphVertex* varVtxp = vscp->user1u().toGraphVertex();
if (!varVtxp) {
varVtxp = new TraceVarVertex{&m_graph, vscp};
vscp->user1p(varVtxp);
}
new V3GraphEdge{&m_graph, funcVtxp, varVtxp, 1};
}
}
}
}
iterateChildren(nodep);
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
+11 -3
View File
@@ -299,6 +299,7 @@ class WidthVisitor final : public VNVisitor {
void visit(AstSAnd* nodep) override { visit_log_and_or(nodep); }
void visit(AstSIntersect* nodep) override { visit_log_and_or(nodep); }
void visit(AstSOr* nodep) override { visit_log_and_or(nodep); }
void visit(AstSWithin* nodep) override { visit_log_and_or(nodep); }
void visit(AstLogEq* nodep) override {
// Conversion from real not in IEEE, but a fallout
visit_log_and_or(nodep);
@@ -7155,7 +7156,9 @@ class WidthVisitor final : public VNVisitor {
}
if (portp->isWritable()) V3LinkLValue::linkLValueSet(pinp);
if (!portp->basicp() || portp->basicp()->isOpaque()) {
checkClassAssign(nodep, "Function Argument", pinp, portDTypep);
// Output args: at return caller = callee, reverse direction.
checkClassAssign(nodep, "Function Argument", pinp, portDTypep,
portp->direction() == VDirection::OUTPUT);
userIterate(pinp, WidthVP{portDTypep, FINAL}.p());
} else {
iterateCheckAssign(nodep, "Function Argument", pinp, FINAL, portDTypep);
@@ -8511,14 +8514,19 @@ class WidthVisitor final : public VNVisitor {
}
}
void checkClassAssign(const AstNode* nodep, const char* side, AstNode* rhsp,
AstNodeDType* const lhsDTypep) {
AstNodeDType* const lhsDTypep, bool isOutputArg = false) {
UASSERT_OBJ(rhsp->dtypep(), rhsp, "Node has no type");
const AstNodeDType* const lhsRawDTypep = lhsDTypep->skipRefp();
const AstNodeDType* const rhsRawDTypep = rhsp->dtypep()->skipRefp();
if (const AstClassRefDType* const lhsClassRefp = VN_CAST(lhsRawDTypep, ClassRefDType)) {
if (const AstClassRefDType* const rhsClassRefp
= VN_CAST(rhsRawDTypep, ClassRefDType)) {
if (isBaseClassRecurse(lhsClassRefp->classp(), rhsClassRefp->classp())) return;
// Output arg swaps sides: caller actual = callee formal at return.
const AstClass* const dstp
= isOutputArg ? rhsClassRefp->classp() : lhsClassRefp->classp();
const AstClass* const srcp
= isOutputArg ? lhsClassRefp->classp() : rhsClassRefp->classp();
if (isBaseClassRecurse(dstp, srcp)) return;
} else if (rhsp->isNull()) {
return;
}
+20 -9
View File
@@ -19,8 +19,8 @@
#include "V3Active.h"
#include "V3ActiveTop.h"
#include "V3Assert.h"
#include "V3AssertNfa.h"
#include "V3AssertPre.h"
#include "V3AssertProp.h"
#include "V3Ast.h"
#include "V3Begin.h"
#include "V3Branch.h"
@@ -55,6 +55,7 @@
#include "V3File.h"
#include "V3Force.h"
#include "V3Fork.h"
#include "V3FsmDetect.h"
#include "V3FuncOpt.h"
#include "V3Gate.h"
#include "V3Global.h"
@@ -232,9 +233,11 @@ static void process() {
v3Global.vlExit(0);
}
// Coverage insertion
// Before we do dead code elimination and inlining, or we'll lose it.
if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp());
// Insert generic non-FSM coverage before dead code elimination and
// inlining, or those opportunities may be optimized away. FSM
// coverage is handled later in V3FsmDetect, after scoping has created
// the AST context needed to recover and lower FSMs reliably.
if (v3Global.opt.coverageNonFsm()) V3Coverage::coverage(v3Global.rootp());
// Resolve randsequence if they are used by the design
if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp());
@@ -252,8 +255,9 @@ static void process() {
// Assertion insertion
// After we've added block coverage, but before other nasty transforms
V3AssertProp::assertPropAll(v3Global.rootp());
//
V3AssertNfa::assertNfaAll(v3Global.rootp());
// V3AssertProp removed: NFA subsumes multi-cycle property lowering.
// Unsupported constructs fall through to V3AssertPre.
V3AssertPre::assertPreAll(v3Global.rootp());
//
V3Assert::assertAll(v3Global.rootp());
@@ -346,6 +350,12 @@ static void process() {
// No more AstAlias after linkDotScope
V3Scope::scopeAll(v3Global.rootp());
V3LinkDot::linkDotScope(v3Global.rootp());
// FSM coverage needs scopes, but should otherwise run as early as
// possible before later lowering rewrites user-visible clocked
// case structure. This entry point runs two adjacent phases:
// detect into local graph state, then lower that completed state
// into the concrete coverage machinery.
if (v3Global.opt.coverageFsm()) V3FsmDetect::detect(v3Global.rootp());
// Relocate classes (after linkDot)
V3Class::classAll(v3Global.rootp());
@@ -427,8 +437,9 @@ static void process() {
"This may cause ordering problems.");
}
// Combine COVERINCs with duplicate terms
if (v3Global.opt.coverage()) V3CoverageJoin::coverageJoin(v3Global.rootp());
// Combine generic COVERINCs with duplicate terms. FSM coverage is
// already lowered separately inside V3FsmDetect.
if (v3Global.opt.coverageNonFsm()) V3CoverageJoin::coverageJoin(v3Global.rootp());
// Remove unused vars
V3Const::constifyAll(v3Global.rootp());
@@ -446,7 +457,7 @@ static void process() {
}
// Create delayed assignments
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step.
V3Delayed::delayedAll(v3Global.rootp());
// Make Active's on the top level.
+5
View File
@@ -69,6 +69,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); });
DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); });
DECL_OPTION("-filter-type", Set, &m_filterType);
DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs);
DECL_OPTION("-rank", OnOff, &m_rank);
DECL_OPTION("-unlink", OnOff, &m_unlink);
DECL_OPTION("-V", CbCall, []() {
@@ -140,6 +141,10 @@ int main(int argc, char** argv) {
top.points().dump();
}
if (!top.opt.rank() && top.opt.writeFile().empty() && top.opt.writeInfoFile().empty()) {
top.printTypeSummary();
}
V3Error::abortIfWarnings();
if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut());
+2
View File
@@ -39,6 +39,7 @@ class VlcOptions final {
bool m_annotateAll = false; // main switch: --annotate-all
int m_annotateMin = 10; // main switch: --annotate-min I<count>
bool m_annotatePoints = false; // main switch: --annotate-points
bool m_includeResetArcs = false; // main switch: --include-reset-arcs
string m_filterType = "*"; // main switch: --filter-type
VlStringSet m_readFiles; // main switch: --read
bool m_rank = false; // main switch: --rank
@@ -67,6 +68,7 @@ public:
int annotateMin() const { return m_annotateMin; }
bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); }
bool annotatePoints() const { return m_annotatePoints; }
bool includeResetArcs() const { return m_includeResetArcs; }
bool rank() const { return m_rank; }
bool unlink() const { return m_unlink; }
string writeFile() const { return m_writeFile; }
+12
View File
@@ -70,6 +70,18 @@ public:
return keyExtract(VL_CIK_THRESH, m_name.c_str());
}
string linescov() const { return keyExtract(VL_CIK_LINESCOV, m_name.c_str()); }
bool isFsmState() const { return type() == "fsm_state"; }
bool isFsmArc() const { return type() == "fsm_arc"; }
// Arc-specific helpers are used after callers have already filtered to
// FSM arc points, so they do not repeat the type check here.
string fsmVarName() const { return keyExtract(VL_CIK_FSM_VAR, m_name.c_str()); }
string fsmFromState() const { return keyExtract(VL_CIK_FSM_FROM, m_name.c_str()); }
string fsmToState() const { return keyExtract(VL_CIK_FSM_TO, m_name.c_str()); }
string fsmTag() const { return keyExtract(VL_CIK_FSM_TAG, m_name.c_str()); }
bool isFsmResetInclude() const { return fsmTag() == "reset_include"; }
bool isFsmResetArc() const { return fsmTag() == "reset"; }
bool isFsmDefaultArc() const { return fsmTag() == "default"; }
bool fsmIsReset() const { return isFsmResetArc() || isFsmResetInclude(); }
int lineno() const {
const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str());
return std::atoi(lineStr.c_str());
+86 -7
View File
@@ -25,6 +25,8 @@
#include <algorithm>
#include <fstream>
#include <map>
#include <set>
#include <string>
#include <vector>
@@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) {
int branchesHit = 0;
for (auto& li : lines) {
VlcSourceCount& sc = li.second;
os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n";
const int num_branches = sc.points().size();
if (num_branches == 1) continue;
branchesFound += num_branches;
int point_num = 0;
uint64_t daCount = 0;
std::vector<const VlcPoint*> infoPoints;
for (const auto& point : sc.points()) {
if (point->isFsmArc()) continue;
daCount = std::max(daCount, point->count());
if (!point->isFsmState()) infoPoints.push_back(point);
}
os << "DA:" << sc.lineno() << "," << daCount << "\n";
if (infoPoints.size() <= 1) continue;
branchesFound += static_cast<int>(infoPoints.size());
int point_num = 0;
for (const VlcPoint* point : infoPoints) {
os << "BRDA:" << sc.lineno() << ",";
os << "0,";
os << point_num << ",";
@@ -269,8 +277,10 @@ void VlcTop::annotateCalcNeeded() {
}
}
const float pct = totCases ? (100 * totOk / totCases) : 0;
std::cout << "Total coverage (" << totOk << "/" << totCases << ") ";
std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\n";
std::cout << "Annotation Summary:\n";
std::cout << " lines with all attached points covered : ";
std::cout << std::fixed << std::setw(5) << std::setprecision(2) << pct << "% (" << totOk
<< "/" << totCases << ")\n";
if (totOk != totCases) cout << "See lines with '%00' in " << opt.annotateOut() << '\n';
}
@@ -326,6 +336,29 @@ void VlcTop::annotateOutputFiles(const string& dirname) {
if (opt.annotatePoints()) {
for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin());
}
bool printedFsmHeader = false;
for (const auto& pit : sc.points()) {
if (!pit->isFsmState() && !pit->isFsmArc()) continue;
if (!printedFsmHeader) {
os << " // [FSM coverage]\n";
printedFsmHeader = true;
}
os << (opt.countOk(pit->count()) ? " " : "%");
os << std::setfill('0') << std::setw(6) << pit->count() << " ";
if (pit->isFsmState()) {
os << "// [fsm_state " << pit->comment() << "]";
if (pit->count() == 0) os << " *** UNCOVERED ***";
os << "\n";
} else if (pit->isFsmDefaultArc()) {
os << "// [SYNTHETIC DEFAULT ARC: " << pit->comment() << "]\n";
} else {
os << "// [fsm_arc " << pit->comment() << "]";
if (pit->fsmIsReset() && !opt.includeResetArcs()) {
os << " [reset arc, excluded from %]";
}
os << "\n";
}
}
}
}
}
@@ -337,3 +370,49 @@ void VlcTop::annotate(const string& dirname) {
annotateCalcNeeded();
annotateOutputFiles(dirname);
}
void VlcTop::printTypeSummary() {
static const std::vector<std::string> orderedTypes
= {"line", "toggle", "branch", "expr", "fsm_state", "fsm_arc"};
std::map<std::string, std::pair<uint64_t, uint64_t>> tally;
for (const auto& i : m_points) {
const VlcPoint& pt = m_points.pointNumber(i.second);
const string type = pt.type().empty() ? "point" : pt.type();
auto& entry = tally[type];
if (pt.count() > 0) ++entry.first;
++entry.second;
}
if (tally.empty()) return;
std::set<std::string> printed;
size_t typeWidth = 0;
size_t countWidth = 0;
for (const string& type : orderedTypes) typeWidth = std::max(typeWidth, type.size());
countWidth = std::max(countWidth, cvtToStr(0).size());
for (const auto& it : tally) {
typeWidth = std::max(typeWidth, it.first.size());
countWidth = std::max(countWidth, cvtToStr(it.second.first).size());
countWidth = std::max(countWidth, cvtToStr(it.second.second).size());
}
std::cout << "Coverage Summary:\n";
for (const string& type : orderedTypes) {
const auto it = tally.find(type);
printed.insert(type);
const uint64_t hit = (it == tally.end()) ? 0 : it->second.first;
const uint64_t total = (it == tally.end()) ? 0 : it->second.second;
const double pct
= total ? (100.0 * static_cast<double>(hit) / static_cast<double>(total)) : 0.0;
std::cout << " " << std::left << std::setw(typeWidth) << type << " : " << std::right
<< std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth)
<< hit << "/" << std::setw(countWidth) << total << ")\n";
}
for (const auto& it : tally) {
if (printed.count(it.first)) continue;
const uint64_t hit = it.second.first;
const uint64_t total = it.second.second;
const double pct
= total ? (100.0 * static_cast<double>(hit) / static_cast<double>(total)) : 0.0;
std::cout << " " << std::left << std::setw(typeWidth) << it.first << " : " << std::right
<< std::fixed << std::setprecision(1) << pct << "% (" << std::setw(countWidth)
<< hit << "/" << std::setw(countWidth) << total << ")\n";
}
}
+1
View File
@@ -55,6 +55,7 @@ public:
// METHODS
void annotate(const string& dirname);
void printTypeSummary();
void readCoverage(const string& filename, bool nonfatal = false);
void writeCoverage(const string& filename);
void writeInfo(const string& filename);
+24 -123
View File
@@ -31,6 +31,7 @@ class Node:
self._arity = -1 # Arity of node
self._ops = {} # Operands of node
self._ptrs = [] # Pointer members of node (name, types)
self._makeDfgVertex = False # Create a corresponding DfgVertex sub-class
@property
def name(self):
@@ -68,6 +69,10 @@ class Node:
assert self.isCompleted
return self._ptrs
@property
def makeDfgVertex(self):
return self._makeDfgVertex
# Pre completion methods
def addSubClass(self, subClass):
assert not self.isCompleted
@@ -91,6 +96,9 @@ class Node:
name = re.sub(r'^m_', '', name)
self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals})
def setMakeDfgVertex(self):
self._makeDfgVertex = True
# Computes derived properties over entire class hierarchy.
# No more changes to the hierarchy are allowed once this was called
def complete(self, typeId=0, ordIdx=0):
@@ -609,9 +617,12 @@ def read_types(filename, Nodes, prefix):
if prefix != "Ast":
continue
match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line)
if match:
if match := re.match(r'^\s*//\s*@astgen\s+(.*)$', line):
decl = re.sub(r'//.*$', '', match.group(1))
if decl == "makeDfgVertex":
node.setMakeDfgVertex()
continue
what, sep, rest = partitionAndStrip(decl, ":=")
what = re.sub(r'\s+', ' ', what)
if not sep:
@@ -668,10 +679,12 @@ def read_types(filename, Nodes, prefix):
error(
lineno, "Malformed @astgen what (expecting 'op1'..'op4'," +
" 'alias op1'.., 'ptr'): " + what)
else:
line = re.sub(r'//.*$', '', line)
if re.match(r'.*[Oo]p[1-9].*', line):
error(lineno, "Use generated accessors to access op<N> operands")
continue
line = re.sub(r'//.*$', '', line)
if re.match(r'.*[Oo]p[1-9].*', line):
error(lineno, "Use generated accessors to access op<N> operands")
if re.match(r'^\s*Ast[A-Z][A-Za-z0-9_]+\s*\*(\s*const)?\s+m_[A-Za-z0-9_]+\s*;', line):
error(lineno, "Use '@astgen ptr' for Ast pointer members: " + line)
@@ -1407,116 +1420,6 @@ check_types(AstNodeList, "Ast", "Node")
# These are standalone so we don't need to parse the sources for this.
DfgVertices["Vertex"] = Node("Vertex", None)
# AstNodeExpr that are not representable in Dfg
DfgIgnored = (
# Floating point operations
"AcosD",
"AcoshD",
"AddD",
"AsinD",
"AsinhD",
"Atan2D",
"AtanD",
"AtanhD",
"BitsToRealD",
"CeilD",
"CosD",
"CoshD",
"DivD",
"EqD",
"ExpD",
"FloorD",
"GtD",
"GteD",
"HypotD",
"ISToRD",
"IToRD",
"Log10D",
"LogD",
"LtD",
"LteD",
"MulD",
"NegateD",
"NeqD",
"PowD",
"RealToBits",
"RToIRoundS",
"RToIS",
"SinD",
"SinhD",
"SqrtD",
"SubD",
"TanD",
"TanhD",
# String operations
"AtoN",
"CompareNN",
"ConcatN",
"CvtPackString",
"EqN",
"GetcN",
"GetcRefN",
"GteN",
"GtN",
"LenN",
"LteN",
"LtN",
"NeqN",
"NToI",
"PutcN",
"ReplicateN",
"SubstrN",
"ToLowerN",
"ToStringN",
"ToUpperN",
# Effectful
"PostAdd",
"PostSub",
"PreAdd",
"PreSub",
# Only used after DFG
"ShiftLOvr",
"ShiftROvr",
"ShiftRSOvr",
"WordSel",
# File operations
"FEof",
"FGetC",
"FGetS",
"FUngetC",
# Dynamic array operations
"AssocSel",
"IsUnbounded",
"WildcardSel",
# Type comparison
"EqT",
"NeqT",
# Distributions
"DistChiSquare",
"DistErlang",
"DistExponential",
"DistNormal",
"DistPoisson",
"DistT",
"DistUniform",
# Specials
"CastDynamic",
"CastWrap",
"CAwait",
"CCast",
"CLog2",
"IsUnknown",
"NullCheck",
"OneHot",
"OneHot0",
"ResizeLValue",
"Signed",
"SliceSel",
"TimeImport",
"Unsigned",
"URandomRange",
)
# Read DfgVertex definitions
for filename in Args.dfgdef:
read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg")
@@ -1527,12 +1430,8 @@ for node in AstNodeList:
if not node.isLeaf:
continue
# Ignore any explicitly defined vertex
if node.name in DfgVertices:
continue
# Ignore expressions types that DFG cannot handle
if node.name in DfgIgnored:
# Only create vertices for explcitly marked AstNode sub-types
if not node.makeDfgVertex:
continue
if node.isSubClassOf(AstNodes["NodeUniop"]):
@@ -1542,7 +1441,9 @@ for node in AstNodeList:
elif node.isSubClassOf(AstNodes["NodeTriop"]):
base = DfgVertices["VertexTernary"]
else:
continue
sys.exit(
"%Error: cannot create DfgVertex for node not derived from AstNodeUnary, AstNodeBinary, or AstNodeTernary: "
+ node.name)
vertex = Node(node.name, base)
DfgVertices[node.name] = vertex
+8
View File
@@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; }
"/*verilator sformat*/" { FL; return yVL_SFORMAT; }
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; }
/* Experimental Verilator-specific FSM coverage controls. These names were
* chosen to match the current extractor behavior, not a published synthesis
* or simulator pragma standard, so they may evolve as we settle on longer-
* term compatibility/aliasing.
*/
"/*verilator fsm_arc_include_cond*/" { FL; return yVL_FSM_ARC_INCL_COND; }
"/*verilator fsm_reset_arc*/" { FL; return yVL_FSM_RESET_ARC; }
"/*verilator fsm_state*/" { FL; return yVL_FSM_STATE; }
"/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
return yVL_TAG; }
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }
+44 -9
View File
@@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVL_SC_BV "/*verilator sc_bv*/"
%token<fl> yVL_SFORMAT "/*verilator sformat*/"
%token<fl> yVL_SPLIT_VAR "/*verilator split_var*/"
%token<fl> yVL_FSM_ARC_INCL_COND "/*verilator fsm_arc_include_cond*/"
%token<fl> yVL_FSM_RESET_ARC "/*verilator fsm_reset_arc*/"
%token<fl> yVL_FSM_STATE "/*verilator fsm_state*/"
%token<strp> yVL_TAG "/*verilator tag*/"
%token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/"
%token<fl> yVL_UNROLL_FULL "/*verilator unroll_full*/"
@@ -3123,6 +3126,9 @@ sigAttr<nodep>:
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
| yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; }
| yVL_FSM_ARC_INCL_COND { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_ARC_INCLUDE_COND}; }
| yVL_FSM_RESET_ARC { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_RESET_ARC}; }
| yVL_FSM_STATE { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_STATE}; }
;
rangeListE<nodeRangep>: // IEEE: [{packed_dimension}]
@@ -5079,11 +5085,11 @@ expr<nodeExprp>: // IEEE: part of expression/constant_expression/
| type_referenceEq yP_EQUAL type_referenceBoth { $$ = new AstEqT{$2, $1, $3}; }
| type_referenceEq yP_NOTEQUAL type_referenceBoth { $$ = new AstNeqT{$2, $1, $3}; }
// // IEEE: expr yP_MINUSGT expr (1800-2009)
// // Conflicts with constraint_expression:"expr yP_MINUSGT constraint_set"
// // To duplicating expr for constraints, just allow the more general form
// // Later Ast processing must ignore constraint terms where inappropriate
//UNSUP ~l~expr yP_MINUSGT constraint_set { $<fl>$ = $<fl>1; $$ = $1 + $2 + $3; }
//UNSUP remove line below
// // Full IEEE 1800-2023 18.7.2 "expr ->
// // constraint_set" is split: this rule handles
// // the bare-expression RHS as a boolean
// // (AstLogIf), constraint_expression has one
// // production per non-expression RHS shape.
| ~l~expr yP_MINUSGT ~r~expr { $$ = new AstLogIf{$2, $1, $3}; }
//
// // <= is special, as we need to disambiguate it with <= assignment
@@ -6879,7 +6885,7 @@ sexpr<nodeExprp>: // ==IEEE: sequence_expr (The name sexpr is important as reg
// // Below pexpr's are really sequence_expr, but avoid conflict
// // IEEE: sexpr yWITHIN sexpr
| ~p~sexpr yWITHIN sexpr
{ $$ = $1; BBUNSUP($2, "Unsupported: within (in sequence expression)"); DEL($3); }
{ $$ = new AstSWithin{$2, $1, $3}; }
// // Note concurrent_assertion had duplicate rule for below
//UNSUP clocking_event ~p~sexpr %prec prSEQ_CLOCKING { }
//
@@ -7904,8 +7910,37 @@ constraint_expression<nodep>: // ==IEEE: constraint_expression
| yUNIQUE '{' range_list '}' ';'
{ $$ = new AstConstraintUnique{$1, $3}; }
// // IEEE: expr yP_MINUSGT constraint_set
// // Conflicts with expr:"expr yP_MINUSGT expr"; rule moved there
//
// // The bare-expression case "expr -> expr ;"
// // arrives via the general expr rule above
// // (AstLogIf) and matches "expr ';'". Each
// // production below covers one constraint_set
// // shape that is not a plain expression and
// // builds an outer AstConstraintIf wrapping the
// // statement V3Randomize already knows how to
// // lower (with disable-soft hoisted by the
// // ConstraintExprVisitor pre-pass).
| expr yP_MINUSGT '{' constraint_expressionList '}'
{ $$ = new AstConstraintIf{$2, $1, $4, nullptr}; }
| expr yP_MINUSGT yIF '(' expr ')' constraint_set %prec prLOWER_THAN_ELSE
{ $$ = new AstConstraintIf{$2, $1,
new AstConstraintIf{$3, $5, $7, nullptr}, nullptr}; }
| expr yP_MINUSGT yIF '(' expr ')' constraint_set yELSE constraint_set
{ $$ = new AstConstraintIf{$2, $1,
new AstConstraintIf{$3, $5, $7, $9}, nullptr}; }
| expr yP_MINUSGT yFOREACH '(' idClassSelForeach ')' constraint_set
{ $$ = new AstConstraintIf{$2, $1,
new AstConstraintForeach{$3, $5, $7}, nullptr}; }
| expr yP_MINUSGT yUNIQUE '{' range_list '}' ';'
{ $$ = new AstConstraintIf{$2, $1,
new AstConstraintUnique{$3, $5}, nullptr}; }
| expr yP_MINUSGT ySOFT expr ';'
{ AstConstraintExpr* const innerp = new AstConstraintExpr{$3, $4};
innerp->isSoft(true);
$$ = new AstConstraintIf{$2, $1, innerp, nullptr}; }
| expr yP_MINUSGT yDISABLE ySOFT constraint_primary ';'
{ AstConstraintExpr* const innerp = new AstConstraintExpr{$3, $5};
innerp->isDisableSoft(true);
$$ = new AstConstraintIf{$2, $1, innerp, nullptr}; }
| yIF '(' expr ')' constraint_set %prec prLOWER_THAN_ELSE
{ $$ = new AstConstraintIf{$1, $3, $5, nullptr}; }
| yIF '(' expr ')' constraint_set yELSE constraint_set
@@ -7914,7 +7949,7 @@ constraint_expression<nodep>: // ==IEEE: constraint_expression
| yFOREACH '(' idClassSelForeach ')' constraint_set
{ $$ = new AstConstraintForeach{$1, $3, $5}; }
// // soft is 1800-2012
| yDISABLE ySOFT expr/*constraint_primary*/ ';'
| yDISABLE ySOFT constraint_primary ';'
{ AstConstraintExpr* const newp = new AstConstraintExpr{$1, $3};
newp->isDisableSoft(true);
$$ = newp; }
-4
View File
@@ -69,7 +69,6 @@ string(
getarg(TEST_VERILATOR_ARGS_NORM "-prefix" TEST_PREFIX)
getarg(TEST_VERILATOR_ARGS_NORM "-threads" TEST_THREADS)
getarg(TEST_VERILATOR_ARGS_NORM "-trace-threads" TEST_TRACE_THREADS)
# Strip unwanted args with 1 parameter
string(
@@ -111,9 +110,6 @@ endif()
if(TEST_THREADS)
list(APPEND verilate_ARGS THREADS ${TEST_THREADS})
endif()
if(TEST_TRACE_THREADS)
list(APPEND verilate_ARGS TRACE_THREADS ${TEST_TRACE_THREADS})
endif()
if(TEST_SYSTEMC)
list(APPEND verilate_ARGS SYSTEMC)
endif()
+13 -180
View File
@@ -7,7 +7,6 @@ import collections
import ctypes
import glob
import hashlib
import json
import logging
import multiprocessing
import os
@@ -1148,8 +1147,6 @@ class VlTest:
verilator_flags += ["--debug-partition"]
if param['threads'] >= 0:
verilator_flags += ["--threads", str(param['threads'])]
if param['vltmt'] and re.search(r'-trace-fst ', checkflags):
verilator_flags += ["--trace-threads 2"]
if param['make_main'] and param['verilator_make_gmake']:
verilator_flags += ["--exe"]
if param['make_main'] and param['verilator_make_gmake']:
@@ -2513,104 +2510,19 @@ class VlTest:
print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr)
shutil.copy(fn1, fn2)
def vcd_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
"""Test if two VCD files have logically-identical contents"""
# vcddiff to check transitions, if installed
cmd = "vcddiff --help"
out = test.run_capture(cmd, check=True)
cmd = 'vcddiff ' + fn1 + ' ' + fn2
out = test.run_capture(cmd, check=True)
if out != "":
cmd = 'vcddiff ' + fn2 + " " + fn1 # Reversed arguments
out = VtOs.run_capture(cmd, check=False)
if out != "":
print(out)
self.copy_if_golden(fn1, fn2)
self.error("VCD miscompares " + fn2 + " " + fn1)
# vcddiff doesn't check module and variable scope, so check that
# Also provides backup if vcddiff not installed
h1 = self._vcd_read(fn1)
h2 = self._vcd_read(fn2)
if ignore_attr:
h1 = {k: v for k, v in h1.items() if "$attr" not in v}
h2 = {k: v for k, v in h2.items() if "$attr" not in v}
a = json.dumps(h1, sort_keys=True, indent=1)
b = json.dumps(h2, sort_keys=True, indent=1)
if a != b:
def vcd_identical(self, fn1: str, fn2: str) -> None:
"""Test if two VCD/FST files have logically-identical contents"""
cmd = VtOs.getenv_def('WAVEDIFF', 'wavediff') + ' --epsilon 0.0000001 ' + fn1 + ' ' + fn2
proc = subprocess.run([cmd], capture_output=True, text=True, shell=True, check=False)
if proc.returncode:
print(proc.stderr)
print(proc.stdout)
self.copy_if_golden(fn1, fn2)
self.error("VCD hier miscompares " + fn1 + " " + fn2 + "\nGOT=" + a + "\nEXP=" + b +
"\n")
self.error("VCD miscompares " + fn1 + " " + fn2)
def fst2vcd(self, fn1: str, fn2: str) -> None:
cmd = "fst2vcd -h"
out = VtOs.run_capture(cmd, check=False)
if out == "" or not re.search(r'Usage:', out):
self.skip("No fst2vcd installed")
return
cmd = 'fst2vcd -e -f "' + fn1 + '" -o "' + fn2 + '"'
print("\t " + cmd + "\n") # Always print to help debug race cases
out = VtOs.run_capture(cmd, check=False)
print(out)
# Post-process file and fix up to match upcoming fst2vcd output
# also reindent for readability
# Slurp whole file
with open(fn2, 'r', encoding='latin-1') as fd:
lines = fd.readlines()
# Process line by line
new_lines = []
fixup_array_scope = False
indent = ""
for line in lines:
line = line.strip()
# Change "$attrbegin class" to "$attrbegin pack"
if match := re.match(r'^(\$attrbegin\s+)class(.*)', line):
line = indent + match.group(1) + "pack" + match.group(2)
# Check for "$attrbegin array"
elif re.search(r'^\$attrbegin\s+array', line):
line = indent + line
fixup_array_scope = True
# Check for "$scope"
elif match := re.match(r'(\$scope\s)(\S+)(.*)', line.lstrip('\r\n')):
if not indent:
indent = " "
# Fix up array scope
if (match.group(2) == "module") and fixup_array_scope:
line = indent + match.group(1) + "sv_array" + match.group(3)
fixup_array_scope = False
else:
line = indent + line
indent += " "
# Check for "$upscope"
elif re.search(r'^\$upscope', line):
indent = indent[0:-1]
line = indent + line
if len(indent) == 1:
indent = ""
# Just reindent
else:
line = indent + line
new_lines.append(line + "\n")
# Write back to file
with open(fn2, 'w', encoding='latin-1') as fd:
fd.writelines(new_lines)
def fst_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
def fst_identical(self, fn1: str, fn2: str) -> None:
"""Test if two FST files have logically-identical contents"""
if fn1.endswith(".fst"):
tmp = fn1 + ".vcd"
self.fst2vcd(fn1, tmp)
fn1 = tmp
if fn2.endswith(".fst"):
tmp = fn2 + ".vcd"
self.fst2vcd(fn2, tmp)
fn2 = tmp
self.vcd_identical(fn1, fn2, ignore_attr)
self.vcd_identical(fn1, fn2)
def saif_identical(self, fn1: str, fn2: str) -> None:
"""Test if two SAIF files have logically-identical contents"""
@@ -2623,96 +2535,17 @@ class VlTest:
self.copy_if_golden(fn1, fn2)
self.error("SAIF files miscompare")
def trace_identical(self, traceFn: str, goldenFn: str, ignore_attr: bool = False) -> None:
def trace_identical(self, traceFn: str, goldenFn: str) -> None:
match traceFn.rpartition(".")[-1]:
case "vcd":
self.vcd_identical(traceFn, goldenFn, ignore_attr)
self.vcd_identical(traceFn, goldenFn)
case "fst":
self.fst_identical(traceFn, goldenFn, ignore_attr)
self.fst_identical(traceFn, goldenFn)
case "saif":
self.saif_identical(traceFn, goldenFn)
case _:
self.error("Unknown trace file format " + traceFn)
@staticmethod
def _vcd_parse_header(filename: str, root_scope: 'str | None' = None) -> 'tuple[dict, dict]':
"""Parse VCD header into hierarchy data and signal-code mapping.
Returns (hier_data, var_codes) where:
hier_data: dict used by _vcd_read for hierarchy comparison
(scope paths -> scope description, var paths -> $var prefix,
attr paths -> $attrbegin text)
var_codes: dict mapping 'scope.signal' -> VCD code identifier
"""
hier_data: dict = {}
var_codes: dict = {}
hier_stack = [root_scope] if root_scope else []
attr: list = []
with open(filename, 'r', encoding='latin-1') as fh:
for line in fh:
m_scope = re.search(r'\$scope\s+(\S*)\s+(\S+)', line)
m_var = re.search(r'(\$var\s+\S+\s+\d+\s+)(\S+)\s+(.+)\s+\$end', line)
m_attr = re.search(r'(\$attrbegin .* \$end)', line)
if m_scope:
scope_type = m_scope.group(1)
name = m_scope.group(2)
hier_stack.append(name)
scope = '.'.join(hier_stack)
hier_data[scope] = scope_type + " " + name
if attr:
hier_data[scope + "#"] = " ".join(attr)
attr = []
elif m_var:
scope = '.'.join(hier_stack)
decl_prefix = m_var.group(1)
code = m_var.group(2)
var_name = m_var.group(3)
hier_data[scope + "." + var_name] = decl_prefix
if attr:
hier_data[scope + "." + var_name + "#"] = " ".join(attr)
attr = []
bare_name = var_name.split()[0]
var_codes[scope + "." + bare_name] = code
elif m_attr:
attr.append(m_attr.group(1))
elif re.search(r'\$enddefinitions', line):
break
n = len(re.findall(r'\$upscope', line))
if n:
for _i in range(0, n):
hier_stack.pop()
return hier_data, var_codes
def _vcd_read(self, filename: str) -> dict:
data, _ = self._vcd_parse_header(filename, root_scope="TOP")
return data
def vcd_extract_codes(self, filename: str) -> dict:
_, codes = self._vcd_parse_header(filename)
return codes
def vcd_check_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None:
code_a = codes.get(sig_a)
code_b = codes.get(sig_b)
if code_a is None:
self.error(f"Signal '{sig_a}' not found in VCD")
if code_b is None:
self.error(f"Signal '{sig_b}' not found in VCD")
if code_a != code_b:
self.error(f"Expected '{sig_a}' (code {code_a}) to alias "
f"'{sig_b}' (code {code_b})")
def vcd_check_not_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None:
code_a = codes.get(sig_a)
code_b = codes.get(sig_b)
if code_a is None:
self.error(f"Signal '{sig_a}' not found in VCD")
if code_b is None:
self.error(f"Signal '{sig_b}' not found in VCD")
if code_a == code_b:
self.error(f"Expected '{sig_a}' and '{sig_b}' to have different codes, "
f"both have code {code_a}")
def inline_checks(self) -> None:
covfn = self.coverage_filename
contents = self.file_contents(covfn)
@@ -1,4 +1,5 @@
%Error-UNSUPPORTED: t/t_assert_clock_event_unsup.v:24:5: Unsupported: Clock event before property call and in its body
: ... note: In instance 't'
24 | @(negedge clk) check(
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
+2 -2
View File
@@ -9,9 +9,9 @@
import vltest_bootstrap
test.scenarios('simulator')
test.scenarios('vlt') # UNOPTTHREADS in vltmt due to many small assertion states
test.compile(verilator_flags2=['--timing'])
test.compile(verilator_flags2=['--assert', '--timing'])
test.execute()
+25 -14
View File
@@ -52,7 +52,7 @@ module t (
else count_fail4 <= count_fail4 + 1;
// Test 5: a[*10000] large count
assert property (@(posedge clk) a [* 10000] |-> b)
assert property (@(posedge clk) a [* 100] |-> b)
else count_fail5 <= count_fail5 + 1;
// Test 6: a[*1:3] ##1 b -- bounded range in SExpr
@@ -63,11 +63,11 @@ module t (
assert property (@(posedge clk) a [+] ##1 b)
else count_fail7 <= count_fail7 + 1;
// Test 8: a[+] |-> b -- standalone [+] (same as a |-> b)
// Test 8: a[+] |-> b -- standalone [+]
assert property (@(posedge clk) a [+] |-> b)
else count_fail8 <= count_fail8 + 1;
// Test 9: a[*] |-> b -- standalone [*] (shortest non-vacuous match = a)
// Test 9: a[*] |-> b -- standalone [*]
assert property (@(posedge clk) a [*] |-> b)
else count_fail9 <= count_fail9 + 1;
@@ -79,6 +79,11 @@ module t (
assert property (@(posedge clk) a [*] ##1 b)
else count_fail11 <= count_fail11 + 1;
// Counter FSM with M>0: range > kChainLimit (256) forces counter vertex
// creation; min>0 exercises the Gte/active gating path in resolveLinks and
// emitNbaLogic. Cover-only so count_fail values above are undisturbed.
cover property (@(posedge clk) ##[10:300] b);
always @(posedge clk) begin
`ifdef TEST_VERBOSE
$write("[%0t] cyc==%0d crc=%x a=%b b=%b c=%b d=%b\n", $time, cyc, crc, a, b, c, d);
@@ -90,17 +95,23 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail1, 5);
`checkd(count_fail2, 25);
`checkd(count_fail3, 9);
`checkd(count_fail4, 74);
`checkd(count_fail5, 0);
`checkd(count_fail6, 65);
`checkd(count_fail7, 65);
`checkd(count_fail8, 20);
`checkd(count_fail9, 20);
`checkd(count_fail10, 73);
`checkd(count_fail11, 40);
`checkd(count_fail1, 5); // Questa: 5
`checkd(count_fail2, 25); // Questa: 25
`checkd(count_fail3, 9); // Questa: 9
`checkd(count_fail4, 49); // Questa: 49
`checkd(count_fail5, 0); // Questa: 0
// NFA merge-node range [*M:N] over-counts rejects (Questa: 51); match
// detection is correct, only reject counting is imprecise
`checkd(count_fail6, 59);
`checkd(count_fail7, 51); // Questa: 51
`checkd(count_fail8, 20); // Questa: 20
// IEEE 1800-2023 16.9.2 permits empty match of [*0]; NFA reports
// rejects on each tick while Questa suppresses (Questa: 20)
`checkd(count_fail9, 49);
`checkd(count_fail10, 59); // Questa: 59
// a[*] ##1 b: NFA treats unbounded [*] as liveness (no reject);
// Questa treats as definite antecedent (Questa: 29)
`checkd(count_fail11, 0);
$write("*-* All Finished *-*\n");
$finish;
end
@@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:13:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2)
: ... note: In instance 't'
13 | assert property (@(posedge clk) (a ##1 b) [* 2] |-> a);
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

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