mirror of
https://github.com/verilator/verilator.git
synced 2026-10-02 16:13:34 +02:00
Compare commits
54
Commits
ba508c00d2
..
v5.048
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0aa828c21 | ||
|
|
42ea9fda17 | ||
|
|
00211c290c | ||
|
|
5c989025c7 | ||
|
|
d1c0184916 | ||
|
|
07d91fb253 | ||
|
|
555d7cc043 | ||
|
|
b8d59ed7a8 | ||
|
|
b1d3d63e9f | ||
|
|
2cd9b8df66 | ||
|
|
64b91023b1 | ||
|
|
121fd3f613 | ||
|
|
ba3937734f | ||
|
|
7e0f25b41b | ||
|
|
27e7fdc9eb | ||
|
|
ae642facf5 | ||
|
|
3149f43372 | ||
|
|
cfebe805c9 | ||
|
|
46a6884df6 | ||
|
|
72bbccb543 | ||
|
|
c1d1b333ac | ||
|
|
5064a5ee65 | ||
|
|
6945f04531 | ||
|
|
51eb5346c8 | ||
|
|
e4da16caf0 | ||
|
|
a680919edc | ||
|
|
41ddf4d9b6 | ||
|
|
5cddbd7fda | ||
|
|
496665800d | ||
|
|
59fe8066ff | ||
|
|
15163d1e39 | ||
|
|
686594b4ab | ||
|
|
a1a8b9624c | ||
|
|
280cff06f3 | ||
|
|
c60d8f002f | ||
|
|
bca5839d46 | ||
|
|
2b9d006097 | ||
|
|
2ef979a39c | ||
|
|
23ea3d7f11 | ||
|
|
94e3f387a7 | ||
|
|
cd30c22d1c | ||
|
|
81f6cdc32c | ||
|
|
23ca23b7b5 | ||
|
|
f3c63d017a | ||
|
|
ffd2c5c69e | ||
|
|
93d7d9c417 | ||
|
|
935b2564eb | ||
|
|
e82bd52fa3 | ||
|
|
60b41c7822 | ||
|
|
97454a1bc5 | ||
|
|
f7a349c5a7 | ||
|
|
29a93fe5bc | ||
|
|
707dcea914 | ||
|
|
0537999b1d |
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
WAVETOOLS_VERSION=v0.1.2
|
||||
+1
-1
@@ -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])
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
|
||||
.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
.. _Environment:
|
||||
.. _environment:
|
||||
|
||||
Environment
|
||||
===========
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -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}, \
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, ...)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user