Merge branch 'master' into fix/sva-within

This commit is contained in:
Yilou Wang 2026-04-22 22:26:11 +02:00 committed by GitHub
commit f831ea1962
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
197 changed files with 7562 additions and 1739 deletions

View File

@ -48,7 +48,7 @@ jobs:
ls -lsha ls -lsha
tree -L 3 pages tree -L 3 pages
- name: Upload pages artifact - name: Upload pages artifact
uses: actions/upload-pages-artifact@v4 uses: actions/upload-pages-artifact@v5
with: with:
path: pages path: pages

12
Changes
View File

@ -65,9 +65,11 @@ Verilator 5.047 devel
* Improve assignment-compatibility type check (#2843) (#5666) (#7052). [Pawel Kojma, Antmicro Ltd.] * 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 error message when variable used as data type (#7318). [Ryszard Rozak, Antmicro Ltd.]
* Improve E_UNSUPPORTED warning messages (#7329). [Eunseo Song] * 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 array tracing to dump left index to right index (#7205). [Geza Lore, Testorrent USA, Inc.]
* Change `--converge-limit` default to 10000 (#7209). * Change `--converge-limit` default to 10000 (#7209).
* Remove DFG extract optimization pass (#7394). [Geza Lore, Testorrent USA, Inc.] * Remove DFG extract optimization pass (#7394). [Geza Lore, Testorrent USA, Inc.]
* Remove multi-threaded FST tracing (#7443). [Geza Lore, Testorrent USA, Inc.]
* Optimize trace code for faster compiles on repeated types (#6707) (#6832). [Todd Strader] * Optimize trace code for faster compiles on repeated types (#6707) (#6832). [Todd Strader]
* Optimize size of trace declaration object code (#7150). [Szymon Gizler, Antmicro Ltd.] * 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 function call return value temporaries (#7152). [Geza Lore, Testorrent USA, Inc.]
@ -80,11 +82,15 @@ Verilator 5.047 devel
* Optimize more patterns in DfgPeephole (#7332). [Geza Lore, Testorrent USA, Inc.] * Optimize more patterns in DfgPeephole (#7332). [Geza Lore, Testorrent USA, Inc.]
* Optimize read references in DFG (#7354). [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 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.]
* Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier] * Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier]
* Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang] * Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang]
* Fix shift width mismatch in constraint solver SMT emission (#5420) (#7265). [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 randomize size+element queue constraints (#5582) (#7225). [Rahul Behl, Testorrent USA, Inc.]
* Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev] * Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev]
* Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton] * Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton]
@ -151,8 +157,10 @@ Verilator 5.047 devel
* Fix delete inside foreach skipping elements (#7407) (#7410) * Fix delete inside foreach skipping elements (#7407) (#7410)
* Fix std::randomize in parameterized-derived class (#7409) (#7416). [Yilou Wang] * Fix std::randomize in parameterized-derived class (#7409) (#7416). [Yilou Wang]
* Fix virtual interface implied comparison with null (#7421). [Alex Solomatnikov] * 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 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 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]
Verilator 5.046 2026-02-28 Verilator 5.046 2026-02-28

View File

@ -358,6 +358,7 @@ detailed descriptions of these arguments.
--coverage Enable all coverage --coverage Enable all coverage
--coverage-expr Enable expression coverage --coverage-expr Enable expression coverage
--coverage-expr-max <value> Maximum permutations allowed for an expression --coverage-expr-max <value> Maximum permutations allowed for an expression
--coverage-fsm Enable FSM state/arc coverage
--coverage-line Enable line coverage --coverage-line Enable line coverage
--coverage-max-width <width> Maximum array depth for coverage --coverage-max-width <width> Maximum array depth for coverage
--coverage-toggle Enable toggle coverage --coverage-toggle Enable toggle coverage
@ -379,6 +380,7 @@ detailed descriptions of these arguments.
--dump-<srcfile> Enable dumping everything in source file --dump-<srcfile> Enable dumping everything in source file
--dump-defines Show preprocessor defines with -E --dump-defines Show preprocessor defines with -E
--dump-dfg Enable dumping DfgGraphs to .dot files --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-graph Enable dumping V3Graphs to .dot files
--dump-inputs Enable dumping preprocessed input files --dump-inputs Enable dumping preprocessed input files
--dump-tree Enable dumping Ast .tree files --dump-tree Enable dumping Ast .tree files

View File

@ -175,6 +175,7 @@ L<https://verilator.org/guide/latest/exe_verilator_coverage.html>.
--annotate-points Annotates info from each coverage point. --annotate-points Annotates info from each coverage point.
--filter-type <regex> Keep only records of given coverage type. --filter-type <regex> Keep only records of given coverage type.
--help Displays this message and version and exits. --help Displays this message and version and exits.
--include-reset-arcs Include reset arcs in FSM arc summaries.
--rank Compute relative importance of tests. --rank Compute relative importance of tests.
--unlink With --write, unlink all inputs --unlink With --write, unlink all inputs
--version Displays program version and exits. --version Displays program version and exits.

View File

@ -40,12 +40,32 @@ if [ "$CI_OS_NAME" = "linux" ]; then
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
fi fi
install-vcddiff() { install-wavediff() {
TMP_DIR="$(mktemp -d)" source ci/docker/buildenv/wavetools.conf
git clone https://github.com/veripool/vcddiff "$TMP_DIR" local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5 local _platform
"$MAKE" -C "${TMP_DIR}" if [ "$CI_OS_NAME" = "linux" ]; then
sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin _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 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'" fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi fi
# Common installs # Common installs
install-vcddiff install-wavediff
# Workaround -fsanitize=address crash # Workaround -fsanitize=address crash
sudo sysctl -w vm.mmap_rnd_bits=28 sudo sysctl -w vm.mmap_rnd_bits=28
else else

View File

@ -53,10 +53,14 @@ RUN apt-get update \
WORKDIR /tmp WORKDIR /tmp
RUN git clone https://github.com/veripool/vcddiff.git && \ COPY wavetools.conf /tmp/wavetools.conf
make -C vcddiff && \ ARG WGET_EXTRA_ARGS=
cp -p vcddiff/vcddiff /usr/local/bin/vcddiff && \ RUN . /tmp/wavetools.conf && \
rm -rf vcddiff 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 COPY build.sh /tmp/build.sh

View File

@ -0,0 +1 @@
WAVETOOLS_VERSION=v0.1.2

View File

@ -58,6 +58,7 @@ Drew Ranck
Drew Taussig Drew Taussig
Driss Hafdi Driss Hafdi
Edgar E. Iglesias Edgar E. Iglesias
Eric Mejdrich
Eric Müller Eric Müller
Eric Rippey Eric Rippey
Eunseo Song Eunseo Song
@ -302,3 +303,4 @@ em2machine
emmettifelts emmettifelts
Àlex Torregrosa Àlex Torregrosa
Ícaro Lima Ícaro Lima
Yogish Sekhar

View File

@ -281,7 +281,8 @@ Summary:
.. option:: --coverage .. option:: --coverage
Enables all forms of coverage, an alias for :vlopt:`--coverage-line` 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 .. option:: --coverage-expr
@ -293,6 +294,10 @@ Summary:
covered for a given expression. Defaults to 32. Increasing may slow covered for a given expression. Defaults to 32. Increasing may slow
coverage simulations and make analyzing the results unwieldy. 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 .. option:: --coverage-line
Enables basic block line coverage analysis. See :ref:`Line Coverage`. 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 Rarely needed. Enable dumping DfgGraph .dot debug files with dumping
level 3. level 3.
.. option:: --dump-dfg-patterns
Rarely needed. Enable dumping DfgGraph pattern statistics.
.. option:: --dump-graph .. option:: --dump-graph
Rarely needed. Enable dumping V3Graph .dot debug files with dumping Rarely needed. Enable dumping V3Graph .dot debug files with dumping

View File

@ -129,13 +129,20 @@ verilator_coverage Arguments
.. option:: --filter-type <regex> .. option:: --filter-type <regex>
Skips records of coverage types that matches with <regex> Skips records of coverage types that matches with <regex>
Possible values are `toggle`, `line`, `branch`, `expr`, `user` and Possible values are `toggle`, `line`, `branch`, `expr`, `user`,
a wildcard with `\*` or `?`. The default value is `\*`. `fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default
value is `\*`.
.. option:: --help .. option:: --help
Displays a help summary, the program version, and exits. 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 .. option:: --rank
Prints an experimental report listing the relative importance of each Prints an experimental report listing the relative importance of each

View File

@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables
all forms of coverage: all forms of coverage:
- :ref:`User Coverage` - :ref:`User Coverage`
- :ref:`FSM Coverage`
- :ref:`Line Coverage` - :ref:`Line Coverage`
- :ref:`Toggle Coverage` - :ref:`Toggle Coverage`
@ -208,6 +209,47 @@ point under the coverage name "DefaultClock":
DefaultClock: cover property (@(posedge clk) cyc==3); 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: .. _line coverage:

View File

@ -505,7 +505,7 @@ include directories and link to the SystemC libraries.
Deprecated and has no effect. Deprecated and has no effect.
Before Verialtor 5.048: Optional. Enable multithreaded FST trace; see In versions before 5.048: Optional. Enable multithreaded FST trace; see
:vlopt:`--trace-threads`. :vlopt:`--trace-threads`.
.. describe:: TRACE_VCD .. describe:: TRACE_VCD

View File

@ -306,7 +306,7 @@ List Of Warnings
else else
array[address] <= data; 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: example above), some complicated cases are not supported. Namely:
1. If the above loop is inside a suspendable process or fork statement. 1. If the above loop is inside a suspendable process or fork statement.
@ -837,6 +837,18 @@ List Of Warnings
with a newline." 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 .. option:: FUNCTIMECTL
Error that a function contains a time-controlling statement or call of a Error that a function contains a time-controlling statement or call of a

View File

@ -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 - SystemC to compile the SystemC outputs, see https://systemc.org
- vcddiff to find differences in VCD outputs. See the readme at - wavediff to find differences in waveform outputs. See the readme at
https://github.com/veripool/vcddiff https://github.com/hudson-trading/wavetools
- Cmake for build paths that use it. - Cmake for build paths that use it.

View File

@ -2,6 +2,7 @@ ABCp
Aadi Aadi
Accellera Accellera
Aditya Aditya
allocator
Affe Affe
Aleksander Aleksander
Alexandre Alexandre
@ -333,6 +334,7 @@ Muhlestein
Multithreaded Multithreaded
Multithreading Multithreading
Mykyta Mykyta
NFA
NOUNOPTFLAT NOUNOPTFLAT
NaN NaN
Nalbantis Nalbantis
@ -361,6 +363,7 @@ Olofsson
Ondrej Ondrej
Oron Oron
Oyvind Oyvind
output
PLI PLI
Pakanati Pakanati
Palaniappan Palaniappan
@ -401,8 +404,10 @@ Ranjan
Rapp Rapp
Redhat Redhat
Reitan Reitan
reentrant
Renga Renga
Requin Requin
reusability
Riaz Riaz
Rodas Rodas
Rodionov Rodionov
@ -837,6 +842,7 @@ gotFinish
goto goto
gprof gprof
gtkwave gtkwave
hdl
hdr hdr
hdzhangdoc hdzhangdoc
hh hh
@ -1211,6 +1217,7 @@ upcasting
urandom urandom
uselib uselib
utimes utimes
uvm
uwire uwire
uwires uwires
valgrind valgrind
@ -1257,6 +1264,7 @@ vpm
vpp vpp
warmup warmup
wavealloca wavealloca
wavediff
waveforms waveforms
whitespace whitespace
widthed widthed

View File

@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7
C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24), 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)); 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 // Backward compatibility for Verilator
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), 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 { const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE {

View File

@ -191,6 +191,13 @@ public:
void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12), 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), 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; 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 // 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), 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; A(6), A(7)) VL_MT_SAFE;

View File

@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'") VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
// Bin attributes // Bin attributes
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'") 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':'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':'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'") 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_COLUMN "n"
#define VL_CIK_COMMENT "o" #define VL_CIK_COMMENT "o"
#define VL_CIK_FILENAME "f" #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_HIER "h"
#define VL_CIK_LINENO "l" #define VL_CIK_LINENO "l"
#define VL_CIK_LINESCOV "S" #define VL_CIK_LINESCOV "S"
@ -70,6 +78,10 @@ public:
if (key == "column") return VL_CIK_COLUMN; if (key == "column") return VL_CIK_COLUMN;
if (key == "comment") return VL_CIK_COMMENT; if (key == "comment") return VL_CIK_COMMENT;
if (key == "filename") return VL_CIK_FILENAME; 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 == "hier") return VL_CIK_HIER;
if (key == "lineno") return VL_CIK_LINENO; if (key == "lineno") return VL_CIK_LINENO;
if (key == "linescov") return VL_CIK_LINESCOV; if (key == "linescov") return VL_CIK_LINESCOV;

258
include/verilated_force.h Normal file
View File

@ -0,0 +1,258 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Code available from: https://verilator.org
//
// Copyright 2026-2026 by Wilson Snyder. This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
///
/// \file
/// \brief Verilator: Runtime support for force/release statements
///
/// This file provides runtime data structures for efficient dynamic
/// resolution of force/release statements. A sorted list of active
/// forces is maintained that can be efficiently queried and modified
/// at runtime.
///
//*************************************************************************
#ifndef VERILATOR_VERILATED_FORCE_H_
#define VERILATOR_VERILATED_FORCE_H_
#include "verilatedos.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <type_traits>
#include <vector>
template <typename T>
using VlForceBaseType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
// VlForceRead - Helper functions to read a forced value
//
// These functions combine original value with forced values based on
// VlForceVec entries.
// This achieves O(k) complexity where k = number of active forces.
template <typename T>
struct VlForceTypeInfo final {
using Type = VlForceBaseType<T>;
static constexpr bool bitwise
= std::is_integral<Type>::value || std::is_enum<Type>::value || VlIsVlWide<Type>::value;
static constexpr bool unpackedArray = false;
};
template <typename T>
struct VlForceArrayIndexer final {
static constexpr std::size_t size = 1;
static T& elem(T& value, std::size_t) { return value; }
};
template <typename T, std::size_t N>
struct VlForceArrayIndexer<VlUnpacked<T, N>> final {
static constexpr std::size_t size = N * VlForceArrayIndexer<T>::size;
static auto& elem(VlUnpacked<T, N>& array, std::size_t index) {
constexpr std::size_t subSize = VlForceArrayIndexer<T>::size;
return VlForceArrayIndexer<T>::elem(array[index / subSize], index % subSize);
}
};
template <typename T, std::size_t N>
struct VlForceTypeInfo<VlUnpacked<T, N>> final {
using Type = VlUnpacked<T, N>;
static constexpr bool bitwise = false;
static constexpr bool unpackedArray = true;
};
template <typename T, bool = std::is_enum<T>::value>
struct VlForceStorageTypeOf final {
using type = typename std::make_unsigned<T>::type;
};
template <typename T>
struct VlForceStorageTypeOf<T, true> final {
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
};
template <typename T>
using VlForceStorageType = typename VlForceStorageTypeOf<VlForceBaseType<T>>::type;
//=============================================================================
// VlForceVec - Vector of active force entries for a signal
//
// This class maintains a sorted vector of non-overlapping force entries.
// When a new force is added, it removes or trims existing entries that
// overlap with the new range.
//
// The generated code will:
// 1. Use addForce/release to update the active forces
// 2. Call a generated read function that iterates entries and evaluates RHS
class VlForceVec final {
private:
struct Entry final {
int m_lsb; // Inclusive lower bit
int m_msb; // Inclusive upper bit
int m_rhsLsb; // Destination index that maps to RHS index 0
const void* m_rhsDatap; // Pointer to RHS storage
bool operator<(const Entry& other) const { return m_msb < other.m_msb; }
};
std::vector<Entry> m_entries; // Sorted by msb, non-overlapping
std::vector<Entry>::iterator trimEntries(int lsb, int msb) {
auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
[](const Entry& e, int bit) { return e.m_msb < bit; });
while (it != m_entries.end() && it->m_lsb <= msb) {
if (it->m_lsb < lsb && it->m_msb > msb) {
const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap};
it->m_msb = lsb - 1;
return m_entries.insert(++it, right);
}
if (it->m_lsb < lsb) {
it->m_msb = lsb - 1;
++it;
continue;
}
if (it->m_msb > msb) {
it->m_lsb = msb + 1;
return it;
}
it = m_entries.erase(it);
}
return it;
}
static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) {
assert(width > 0 && width <= VL_QUADSIZE);
assert(rhsLsb >= 0);
const QData mask = static_cast<QData>(VL_MASK_Q(width));
const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1;
if (rhsWidth <= VL_QUADSIZE) {
const QData rhsVal = static_cast<QData>(*static_cast<const QData*>(entry.m_rhsDatap));
return (rhsVal >> rhsLsb) & mask;
}
const EData* const rhswp = static_cast<const EData*>(entry.m_rhsDatap);
return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask;
}
template <typename T>
static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) {
const T lowMask = static_cast<T>(VL_MASK_Q(width));
const T mask = static_cast<T>(lowMask << lsb);
const T rhsBits = static_cast<T>(
(static_cast<T>(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb);
return static_cast<T>((cur & ~mask) | (rhsBits & mask));
}
template <typename T>
static typename std::enable_if<VlIsVlWide<T>::value, T>::type applyEntry(T result,
const Entry& entry) {
EData* const reswp = result.data();
const int lword = VL_BITWORD_E(entry.m_lsb);
const int hword = VL_BITWORD_E(entry.m_msb);
for (int word = lword; word <= hword; ++word) {
const int wordLsb = word * VL_EDATASIZE;
const int segLsb = std::max(entry.m_lsb, wordLsb);
const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1);
const int segWidth = segMsb - segLsb + 1;
const int bitOffset = segLsb - wordLsb;
const int rhsLsb = segLsb - entry.m_rhsLsb;
reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
}
return result;
}
template <typename T>
static typename std::enable_if<!VlIsVlWide<T>::value && VlForceTypeInfo<T>::bitwise, T>::type
applyEntry(T result, const Entry& entry) {
using U = VlForceStorageType<T>;
const int width = entry.m_msb - entry.m_lsb + 1;
const int bits = static_cast<int>(sizeof(U) * 8);
const int rhsLsb = entry.m_lsb - entry.m_rhsLsb;
const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width);
if (width >= bits) return static_cast<T>(static_cast<U>(rhsChunk));
return static_cast<T>(
applyBits(static_cast<U>(result), entry, entry.m_lsb, width, rhsLsb));
}
template <typename T>
static typename std::enable_if<!VlForceTypeInfo<T>::bitwise, T>::type
applyEntry(T result, const Entry& entry) {
static_cast<void>(result);
return *static_cast<const VlForceBaseType<T>*>(entry.m_rhsDatap);
}
public:
VlForceVec() = default;
template <typename T>
T read(T val) const {
if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
// Handling the case of a nested flattened array using recursion
using ElemRef
= decltype(VlForceArrayIndexer<T>::elem(val, static_cast<std::size_t>(0)));
using Elem = VlForceBaseType<ElemRef>;
const int total = static_cast<int>(VlForceArrayIndexer<T>::size);
for (const auto& entry : m_entries) {
const Elem* const rhsBasep = static_cast<const Elem*>(entry.m_rhsDatap);
const int startIdx = entry.m_lsb;
const int endIdx = entry.m_msb;
for (int idx = startIdx; idx <= endIdx; idx++) {
const int rhsIndex = idx - entry.m_rhsLsb;
const std::size_t uidx = static_cast<std::size_t>(idx);
VlForceArrayIndexer<T>::elem(val, uidx) = rhsBasep[rhsIndex];
}
}
return val;
}
for (const auto& entry : m_entries) { val = applyEntry(val, entry); }
return val;
}
template <typename T>
T readIndex(T origVal, int index) const {
if (m_entries.empty()) return origVal;
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index,
[](const Entry& e, int idx) { return e.m_msb < idx; });
if (it != m_entries.end() && it->m_lsb <= index) {
const int rhsIndex = index - it->m_rhsLsb;
const T* const rhsBasep = static_cast<const T*>(it->m_rhsDatap);
return rhsBasep[rhsIndex];
}
return origVal;
}
void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) {
assert(lsb <= msb);
assert(rhsDatap);
assert(rhsLsb <= lsb);
auto it = trimEntries(lsb, msb);
m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap});
}
void release(int lsb, int msb) {
assert(lsb <= msb);
trimEntries(lsb, msb);
}
void touch() {}
};
#endif // guard

View File

@ -100,6 +100,7 @@ set(HEADERS
V3File.h V3File.h
V3FileLine.h V3FileLine.h
V3Force.h V3Force.h
V3FsmDetect.h
V3Fork.h V3Fork.h
V3FuncOpt.h V3FuncOpt.h
V3FunctionTraits.h V3FunctionTraits.h
@ -277,6 +278,7 @@ set(COMMON_SOURCES
V3File.cpp V3File.cpp
V3FileLine.cpp V3FileLine.cpp
V3Force.cpp V3Force.cpp
V3FsmDetect.cpp
V3Fork.cpp V3Fork.cpp
V3FuncOpt.cpp V3FuncOpt.cpp
V3Gate.cpp V3Gate.cpp

View File

@ -280,6 +280,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3ExecGraph.o \ V3ExecGraph.o \
V3Expand.o \ V3Expand.o \
V3Force.o \ V3Force.o \
V3FsmDetect.o \
V3Fork.o \ V3Fork.o \
V3Gate.o \ V3Gate.o \
V3HierBlock.o \ V3HierBlock.o \

View File

@ -311,6 +311,9 @@ public:
// //
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable 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_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
VAR_PUBLIC_FLAT, // 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", "ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID",
"FUNC_ARG_PROTO", "FUNC_RETURN_PROTO", "FUNC_ARG_PROTO", "FUNC_RETURN_PROTO",
"TYPEID", "TYPENAME", "TYPEID", "TYPENAME",
"VAR_BASE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
"VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT",
"VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS",
"VAR_SPLIT_VAR" "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
}; };
// clang-format on // clang-format on
return names[m_e]; return names[m_e];
@ -809,6 +812,11 @@ public:
EVENT_FIRE, EVENT_FIRE,
EVENT_IS_FIRED, EVENT_IS_FIRED,
EVENT_IS_TRIGGERED, EVENT_IS_TRIGGERED,
FORCE_ADD,
FORCE_READ,
FORCE_READ_INDEX,
FORCE_RELEASE,
FORCE_TOUCH,
FORK_DONE, FORK_DONE,
FORK_INIT, FORK_INIT,
FORK_JOIN, FORK_JOIN,
@ -955,6 +963,11 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{EVENT_FIRE, "fire", false}, \ {EVENT_FIRE, "fire", false}, \
{EVENT_IS_FIRED, "isFired", true}, \ {EVENT_IS_FIRED, "isFired", true}, \
{EVENT_IS_TRIGGERED, "isTriggered", 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_DONE, "done", false}, \
{FORK_INIT, "init", false}, \ {FORK_INIT, "init", false}, \
{FORK_JOIN, "join", false}, \ {FORK_JOIN, "join", false}, \

View File

@ -2889,6 +2889,7 @@ public:
}; };
class AstConcat final : public AstNodeBiop { class AstConcat final : public AstNodeBiop {
// If you're looking for {#{}}, see AstReplicate // If you're looking for {#{}}, see AstReplicate
// @astgen makeDfgVertex
public: public:
AstConcat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstConcat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Concat(fl, lhsp, rhsp) { : ASTGEN_SUPER_Concat(fl, lhsp, rhsp) {
@ -2938,6 +2939,7 @@ public:
bool stringFlavor() const override { return true; } bool stringFlavor() const override { return true; }
}; };
class AstDiv final : public AstNodeBiop { class AstDiv final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Div(fl, lhsp, rhsp) { : ASTGEN_SUPER_Div(fl, lhsp, rhsp) {
@ -2980,6 +2982,7 @@ public:
bool doubleFlavor() const override { return true; } bool doubleFlavor() const override { return true; }
}; };
class AstDivS final : public AstNodeBiop { class AstDivS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_DivS(fl, lhsp, rhsp) { : ASTGEN_SUPER_DivS(fl, lhsp, rhsp) {
@ -3003,6 +3006,7 @@ public:
}; };
class AstEqWild final : public AstNodeBiop { class AstEqWild final : public AstNodeBiop {
// Note wildcard operator rhs differs from lhs // Note wildcard operator rhs differs from lhs
// @astgen makeDfgVertex
public: public:
AstEqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstEqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) { : ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) {
@ -3109,6 +3113,7 @@ public:
bool sizeMattersRhs() const override { return false; } bool sizeMattersRhs() const override { return false; }
}; };
class AstGt final : public AstNodeBiop { class AstGt final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstGt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstGt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Gt(fl, lhsp, rhsp) { : ASTGEN_SUPER_Gt(fl, lhsp, rhsp) {
@ -3171,6 +3176,7 @@ public:
bool stringFlavor() const override { return true; } bool stringFlavor() const override { return true; }
}; };
class AstGtS final : public AstNodeBiop { class AstGtS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstGtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstGtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_GtS(fl, lhsp, rhsp) { : ASTGEN_SUPER_GtS(fl, lhsp, rhsp) {
@ -3192,6 +3198,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstGte final : public AstNodeBiop { class AstGte final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstGte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstGte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Gte(fl, lhsp, rhsp) { : ASTGEN_SUPER_Gte(fl, lhsp, rhsp) {
@ -3254,6 +3261,7 @@ public:
bool stringFlavor() const override { return true; } bool stringFlavor() const override { return true; }
}; };
class AstGteS final : public AstNodeBiop { class AstGteS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstGteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstGteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_GteS(fl, lhsp, rhsp) { : ASTGEN_SUPER_GteS(fl, lhsp, rhsp) {
@ -3275,6 +3283,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstLogAnd final : public AstNodeBiop { class AstLogAnd final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLogAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLogAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) { : ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) {
@ -3296,6 +3305,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
}; };
class AstLogIf final : public AstNodeBiop { class AstLogIf final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLogIf(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLogIf(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) { : ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) {
@ -3317,6 +3327,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
}; };
class AstLogOr final : public AstNodeBiop { class AstLogOr final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLogOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLogOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) { : ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) {
@ -3338,6 +3349,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
}; };
class AstLt final : public AstNodeBiop { class AstLt final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Lt(fl, lhsp, rhsp) { : ASTGEN_SUPER_Lt(fl, lhsp, rhsp) {
@ -3400,6 +3412,7 @@ public:
bool stringFlavor() const override { return true; } bool stringFlavor() const override { return true; }
}; };
class AstLtS final : public AstNodeBiop { class AstLtS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LtS(fl, lhsp, rhsp) { : ASTGEN_SUPER_LtS(fl, lhsp, rhsp) {
@ -3421,6 +3434,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstLte final : public AstNodeBiop { class AstLte final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Lte(fl, lhsp, rhsp) { : ASTGEN_SUPER_Lte(fl, lhsp, rhsp) {
@ -3483,6 +3497,7 @@ public:
bool stringFlavor() const override { return true; } bool stringFlavor() const override { return true; }
}; };
class AstLteS final : public AstNodeBiop { class AstLteS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstLteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LteS(fl, lhsp, rhsp) { : ASTGEN_SUPER_LteS(fl, lhsp, rhsp) {
@ -3504,6 +3519,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstModDiv final : public AstNodeBiop { class AstModDiv final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstModDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstModDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) { : ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) {
@ -3525,6 +3541,7 @@ public:
int instrCount() const override { return widthInstrs() * INSTR_COUNT_INT_DIV; } int instrCount() const override { return widthInstrs() * INSTR_COUNT_INT_DIV; }
}; };
class AstModDivS final : public AstNodeBiop { class AstModDivS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstModDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstModDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) { : ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) {
@ -3547,6 +3564,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstNeqWild final : public AstNodeBiop { class AstNeqWild final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstNeqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstNeqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) { : ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) {
@ -3566,6 +3584,7 @@ public:
bool sizeMattersRhs() const override { return false; } bool sizeMattersRhs() const override { return false; }
}; };
class AstPow final : public AstNodeBiop { class AstPow final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstPow(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstPow(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Pow(fl, lhsp, rhsp) { : ASTGEN_SUPER_Pow(fl, lhsp, rhsp) {
@ -3606,6 +3625,7 @@ public:
bool doubleFlavor() const override { return true; } bool doubleFlavor() const override { return true; }
}; };
class AstPowSS final : public AstNodeBiop { class AstPowSS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstPowSS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstPowSS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) { : ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) {
@ -3627,6 +3647,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstPowSU final : public AstNodeBiop { class AstPowSU final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstPowSU(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstPowSU(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) { : ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) {
@ -3648,6 +3669,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstPowUS final : public AstNodeBiop { class AstPowUS final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstPowUS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstPowUS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) { : ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) {
@ -3673,6 +3695,7 @@ class AstReplicate final : public AstNodeBiop {
// Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp() // Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp()
// @astgen alias op1 := srcp // @astgen alias op1 := srcp
// @astgen alias op2 := countp // @astgen alias op2 := countp
// @astgen makeDfgVertex
public: public:
AstReplicate(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstReplicate(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) { : ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) {
@ -3900,6 +3923,7 @@ public:
void declElWidth(int flag) { m_declElWidth = flag; } void declElWidth(int flag) { m_declElWidth = flag; }
}; };
class AstShiftL final : public AstNodeBiop { class AstShiftL final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstShiftL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) AstShiftL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) { : ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) {
@ -3943,6 +3967,7 @@ public:
bool sizeMattersRhs() const override { return false; } bool sizeMattersRhs() const override { return false; }
}; };
class AstShiftR final : public AstNodeBiop { class AstShiftR final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstShiftR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) AstShiftR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) { : ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) {
@ -3990,6 +4015,7 @@ public:
class AstShiftRS final : public AstNodeBiop { class AstShiftRS final : public AstNodeBiop {
// Shift right with sign extension, >>> operator // Shift right with sign extension, >>> operator
// Output data type's width determines which bit is used for sign extension // Output data type's width determines which bit is used for sign extension
// @astgen makeDfgVertex
public: public:
AstShiftRS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0) AstShiftRS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
: ASTGEN_SUPER_ShiftRS(fl, lhsp, rhsp) { : ASTGEN_SUPER_ShiftRS(fl, lhsp, rhsp) {
@ -4036,6 +4062,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstSub final : public AstNodeBiop { class AstSub final : public AstNodeBiop {
// @astgen makeDfgVertex
public: public:
AstSub(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstSub(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Sub(fl, lhsp, rhsp) { : ASTGEN_SUPER_Sub(fl, lhsp, rhsp) {
@ -4101,6 +4128,7 @@ public:
// === AstNodeBiCom === // === AstNodeBiCom ===
class AstEq final : public AstNodeBiCom { class AstEq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public: public:
AstEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Eq(fl, lhsp, rhsp) { : ASTGEN_SUPER_Eq(fl, lhsp, rhsp) {
@ -4123,6 +4151,7 @@ public:
bool sizeMattersRhs() const override { return false; } bool sizeMattersRhs() const override { return false; }
}; };
class AstEqCase final : public AstNodeBiCom { class AstEqCase final : public AstNodeBiCom {
// @astgen makeDfgVertex
public: public:
AstEqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstEqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) { : ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) {
@ -4203,6 +4232,7 @@ public:
int instrCount() const override { return INSTR_COUNT_STR; } int instrCount() const override { return INSTR_COUNT_STR; }
}; };
class AstLogEq final : public AstNodeBiCom { class AstLogEq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public: public:
AstLogEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstLogEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) { : ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) {
@ -4224,6 +4254,7 @@ public:
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
}; };
class AstNeq final : public AstNodeBiCom { class AstNeq final : public AstNodeBiCom {
// @astgen makeDfgVertex
public: public:
AstNeq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstNeq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Neq(fl, lhsp, rhsp) { : ASTGEN_SUPER_Neq(fl, lhsp, rhsp) {
@ -4245,6 +4276,7 @@ public:
bool sizeMattersRhs() const override { return false; } bool sizeMattersRhs() const override { return false; }
}; };
class AstNeqCase final : public AstNodeBiCom { class AstNeqCase final : public AstNodeBiCom {
// @astgen makeDfgVertex
public: public:
AstNeqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstNeqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) { : ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) {
@ -4327,6 +4359,7 @@ public:
// === AstNodeBiComAsv === // === AstNodeBiComAsv ===
class AstAdd final : public AstNodeBiComAsv { class AstAdd final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstAdd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstAdd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Add(fl, lhsp, rhsp) { : ASTGEN_SUPER_Add(fl, lhsp, rhsp) {
@ -4368,6 +4401,7 @@ public:
bool doubleFlavor() const override { return true; } bool doubleFlavor() const override { return true; }
}; };
class AstAnd final : public AstNodeBiComAsv { class AstAnd final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_And(fl, lhsp, rhsp) { : ASTGEN_SUPER_And(fl, lhsp, rhsp) {
@ -4389,6 +4423,7 @@ public:
const char* widthMismatch() const override VL_MT_STABLE; const char* widthMismatch() const override VL_MT_STABLE;
}; };
class AstMul final : public AstNodeBiComAsv { class AstMul final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstMul(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstMul(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Mul(fl, lhsp, rhsp) { : ASTGEN_SUPER_Mul(fl, lhsp, rhsp) {
@ -4431,6 +4466,7 @@ public:
bool doubleFlavor() const override { return true; } bool doubleFlavor() const override { return true; }
}; };
class AstMulS final : public AstNodeBiComAsv { class AstMulS final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstMulS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstMulS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_MulS(fl, lhsp, rhsp) { : ASTGEN_SUPER_MulS(fl, lhsp, rhsp) {
@ -4454,6 +4490,7 @@ public:
bool signedFlavor() const override { return true; } bool signedFlavor() const override { return true; }
}; };
class AstOr final : public AstNodeBiComAsv { class AstOr final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Or(fl, lhsp, rhsp) { : ASTGEN_SUPER_Or(fl, lhsp, rhsp) {
@ -4475,6 +4512,7 @@ public:
const char* widthMismatch() const override VL_MT_STABLE; const char* widthMismatch() const override VL_MT_STABLE;
}; };
class AstXor final : public AstNodeBiComAsv { class AstXor final : public AstNodeBiComAsv {
// @astgen makeDfgVertex
public: public:
AstXor(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstXor(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_Xor(fl, lhsp, rhsp) { : ASTGEN_SUPER_Xor(fl, lhsp, rhsp) {
@ -4532,6 +4570,7 @@ public:
// === AstNodeSel === // === AstNodeSel ===
class AstArraySel final : public AstNodeSel { class AstArraySel final : public AstNodeSel {
// @astgen makeDfgVertex
void init(const AstNode* fromp) { void init(const AstNode* fromp) {
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) { if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) {
// Strip off array to find what array references // Strip off array to find what array references
@ -4644,6 +4683,7 @@ public:
// === AstNodeStream === // === AstNodeStream ===
class AstStreamL final : public AstNodeStream { class AstStreamL final : public AstNodeStream {
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
// @astgen makeDfgVertex
public: public:
AstStreamL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstStreamL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {} : ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {}
@ -4662,6 +4702,7 @@ public:
}; };
class AstStreamR final : public AstNodeStream { class AstStreamR final : public AstNodeStream {
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
// @astgen makeDfgVertex
public: public:
AstStreamR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) AstStreamR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
: ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {} : ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {}
@ -4950,6 +4991,7 @@ class AstCond final : public AstNodeTriop {
// @astgen alias op1 := condp // @astgen alias op1 := condp
// @astgen alias op2 := thenp // @astgen alias op2 := thenp
// @astgen alias op3 := elsep // @astgen alias op3 := elsep
// @astgen makeDfgVertex
public: public:
AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep); AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep);
ASTGEN_MEMBERS_AstCond; ASTGEN_MEMBERS_AstCond;
@ -5298,6 +5340,7 @@ public:
}; };
class AstCountOnes final : public AstNodeUniop { class AstCountOnes final : public AstNodeUniop {
// Number of bits set in vector // Number of bits set in vector
// @astgen makeDfgVertex
public: public:
AstCountOnes(FileLine* fl, AstNodeExpr* lhsp) AstCountOnes(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_CountOnes(fl, lhsp) {} : ASTGEN_SUPER_CountOnes(fl, lhsp) {}
@ -5329,6 +5372,7 @@ public:
}; };
class AstExtend final : public AstNodeUniop { class AstExtend final : public AstNodeUniop {
// Expand a value into a wider entity by 0 extension. Width is implied from nodep->width() // Expand a value into a wider entity by 0 extension. Width is implied from nodep->width()
// @astgen makeDfgVertex
public: public:
AstExtend(FileLine* fl, AstNodeExpr* lhsp) AstExtend(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Extend(fl, lhsp) {} : ASTGEN_SUPER_Extend(fl, lhsp) {}
@ -5352,6 +5396,7 @@ public:
}; };
class AstExtendS final : public AstNodeUniop { class AstExtendS final : public AstNodeUniop {
// Expand a value into a wider entity by sign extension. Width is implied from nodep->width() // Expand a value into a wider entity by sign extension. Width is implied from nodep->width()
// @astgen makeDfgVertex
public: public:
AstExtendS(FileLine* fl, AstNodeExpr* lhsp) AstExtendS(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_ExtendS(fl, lhsp) {} : ASTGEN_SUPER_ExtendS(fl, lhsp) {}
@ -5498,6 +5543,7 @@ public:
bool sizeMattersLhs() const override { return false; } bool sizeMattersLhs() const override { return false; }
}; };
class AstLogNot final : public AstNodeUniop { class AstLogNot final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstLogNot(FileLine* fl, AstNodeExpr* lhsp) AstLogNot(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_LogNot(fl, lhsp) { : ASTGEN_SUPER_LogNot(fl, lhsp) {
@ -5529,6 +5575,7 @@ public:
bool sizeMattersLhs() const override { return false; } bool sizeMattersLhs() const override { return false; }
}; };
class AstNegate final : public AstNodeUniop { class AstNegate final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstNegate(FileLine* fl, AstNodeExpr* lhsp) AstNegate(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Negate(fl, lhsp) { : ASTGEN_SUPER_Negate(fl, lhsp) {
@ -5562,6 +5609,7 @@ public:
bool doubleFlavor() const override { return true; } bool doubleFlavor() const override { return true; }
}; };
class AstNot final : public AstNodeUniop { class AstNot final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstNot(FileLine* fl, AstNodeExpr* lhsp) AstNot(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Not(fl, lhsp) { : ASTGEN_SUPER_Not(fl, lhsp) {
@ -5683,6 +5731,7 @@ public:
bool isSystemFunc() const override { return true; } bool isSystemFunc() const override { return true; }
}; };
class AstRedAnd final : public AstNodeUniop { class AstRedAnd final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstRedAnd(FileLine* fl, AstNodeExpr* lhsp) AstRedAnd(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedAnd(fl, lhsp) { : ASTGEN_SUPER_RedAnd(fl, lhsp) {
@ -5697,6 +5746,7 @@ public:
bool sizeMattersLhs() const override { return false; } bool sizeMattersLhs() const override { return false; }
}; };
class AstRedOr final : public AstNodeUniop { class AstRedOr final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstRedOr(FileLine* fl, AstNodeExpr* lhsp) AstRedOr(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedOr(fl, lhsp) { : ASTGEN_SUPER_RedOr(fl, lhsp) {
@ -5711,6 +5761,7 @@ public:
bool sizeMattersLhs() const override { return false; } bool sizeMattersLhs() const override { return false; }
}; };
class AstRedXor final : public AstNodeUniop { class AstRedXor final : public AstNodeUniop {
// @astgen makeDfgVertex
public: public:
AstRedXor(FileLine* fl, AstNodeExpr* lhsp) AstRedXor(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_RedXor(fl, lhsp) { : ASTGEN_SUPER_RedXor(fl, lhsp) {

View File

@ -656,7 +656,8 @@ class AstCell final : public AstNode {
// A instantiation cell or interface call (don't know which until link) // A instantiation cell or interface call (don't know which until link)
// @astgen op1 := pinsp : List[AstPin] // List of port assignments // @astgen op1 := pinsp : List[AstPin] // List of port assignments
// @astgen op2 := paramsp : List[AstPin] // List of parameter 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 op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing
// //
// @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced // @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
@ -680,7 +681,7 @@ public:
, m_trace{true} { , m_trace{true} {
addPinsp(pinsp); addPinsp(pinsp);
addParamsp(paramsp); addParamsp(paramsp);
this->rangep(rangep); addRangep(rangep);
} }
ASTGEN_MEMBERS_AstCell; ASTGEN_MEMBERS_AstCell;
// No cloneRelink, we presume cloneee's want the same module linkages // 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_attrIsolateAssign : 1; // User isolate_assignments attribute
bool m_attrSFormat : 1; // User sformat attribute bool m_attrSFormat : 1; // User sformat attribute
bool m_attrSplitVar : 1; // declared with split_var metacomment 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_fileDescr : 1; // File descriptor
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
bool m_isConst : 1; // Table contains constant data bool m_isConst : 1; // Table contains constant data
@ -1991,6 +1995,9 @@ class AstVar final : public AstNode {
m_attrIsolateAssign = false; m_attrIsolateAssign = false;
m_attrSFormat = false; m_attrSFormat = false;
m_attrSplitVar = false; m_attrSplitVar = false;
m_attrFsmState = false;
m_attrFsmResetArc = false;
m_attrFsmArcInclCond = false;
m_fileDescr = false; m_fileDescr = false;
m_gotNansiType = false; m_gotNansiType = false;
m_isConst = false; m_isConst = false;
@ -2135,6 +2142,9 @@ public:
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; }
void attrSplitVar(bool flag) { m_attrSplitVar = 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 rand(const VRandAttr flag) { m_rand = flag; }
void usedParam(bool flag) { m_usedParam = flag; } void usedParam(bool flag) { m_usedParam = flag; }
void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
@ -2298,6 +2308,9 @@ public:
bool attrFileDescr() const { return m_fileDescr; } bool attrFileDescr() const { return m_fileDescr; }
bool attrSFormat() const { return m_attrSFormat; } bool attrSFormat() const { return m_attrSFormat; }
bool attrSplitVar() const { return m_attrSplitVar; } 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; } bool attrIsolateAssign() const { return m_attrIsolateAssign; }
AstIface* sensIfacep() const { return m_sensIfacep; } AstIface* sensIfacep() const { return m_sensIfacep; }
VRandAttr rand() const { return m_rand; } VRandAttr rand() const { return m_rand; }
@ -2383,12 +2396,22 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl {
// Coverage analysis point declaration // Coverage analysis point declaration
// Used for other than toggle types of coverage // Used for other than toggle types of coverage
string m_linescov; 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 int m_offset; // Offset column numbers to uniq-ify IFs
public: public:
AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment, 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) : ASTGEN_SUPER_CoverOtherDecl(fl, page, comment)
, m_linescov{linescov} , m_linescov{linescov}
, m_fsmVar{fsmVar}
, m_fsmFrom{fsmFrom}
, m_fsmTo{fsmTo}
, m_fsmTag{fsmTag}
, m_offset{offset} {} , m_offset{offset} {}
ASTGEN_MEMBERS_AstCoverOtherDecl; ASTGEN_MEMBERS_AstCoverOtherDecl;
void dump(std::ostream& str) const override; void dump(std::ostream& str) const override;
@ -2396,6 +2419,10 @@ public:
int offset() const { return m_offset; } int offset() const { return m_offset; }
int size() const override { return 1; } int size() const override { return 1; }
const string& linescov() const { return m_linescov; } 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 { bool sameNode(const AstNode* samep) const override {
const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl); const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl);
return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov(); return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov();

View File

@ -3004,6 +3004,9 @@ void AstVar::dump(std::ostream& str) const {
if (processQueue()) str << " [PROCQ]"; if (processQueue()) str << " [PROCQ]";
if (sampled()) str << " [SAMPLED]"; if (sampled()) str << " [SAMPLED]";
if (attrIsolateAssign()) str << " [aISO]"; if (attrIsolateAssign()) str << " [aISO]";
if (attrFsmState()) str << " [aFSMSTATE]";
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
if (attrFileDescr()) str << " [aFD]"; if (attrFileDescr()) str << " [aFD]";
if (isFuncReturn()) { if (isFuncReturn()) {
str << " [FUNCRTN]"; str << " [FUNCRTN]";
@ -3036,6 +3039,9 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, processQueue);
dumpJsonBoolFuncIf(str, sampled); dumpJsonBoolFuncIf(str, sampled);
dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrIsolateAssign);
dumpJsonBoolFuncIf(str, attrFsmState);
dumpJsonBoolFuncIf(str, attrFsmResetArc);
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
dumpJsonBoolFuncIf(str, attrFileDescr); dumpJsonBoolFuncIf(str, attrFileDescr);
dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isDpiOpenArray);
dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncReturn);
@ -3283,10 +3289,18 @@ void AstNodeCoverDecl::dumpJson(std::ostream& str) const {
void AstCoverOtherDecl::dump(std::ostream& str) const { void AstCoverOtherDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str); this->AstNodeCoverDecl::dump(str);
if (!linescov().empty()) str << " lc=" << linescov(); 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 { void AstCoverOtherDecl::dumpJson(std::ostream& str) const {
this->AstNodeCoverDecl::dumpJson(str); this->AstNodeCoverDecl::dumpJson(str);
dumpJsonStrFunc(str, linescov); dumpJsonStrFunc(str, linescov);
dumpJsonStrFunc(str, fsmVar);
dumpJsonStrFunc(str, fsmFrom);
dumpJsonStrFunc(str, fsmTo);
dumpJsonStrFunc(str, fsmTag);
} }
void AstCoverToggleDecl::dump(std::ostream& str) const { void AstCoverToggleDecl::dump(std::ostream& str) const {
this->AstNodeCoverDecl::dump(str); this->AstNodeCoverDecl::dump(str);

View File

@ -329,8 +329,12 @@ class DeadVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override { void visit(AstNodeFTask* nodep) override {
iterateChildren(nodep); iterateChildren(nodep);
checkAll(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); m_tasksp.push(nodep);
}
if (nodep->classOrPackagep()) { if (nodep->classOrPackagep()) {
if (m_elimCells) { if (m_elimCells) {
nodep->classOrPackagep(nullptr); nodep->classOrPackagep(nullptr);

View File

@ -672,7 +672,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
case VDfgType::Add: case VDfgType::Add:
case VDfgType::And: case VDfgType::And:
case VDfgType::BufIf1:
case VDfgType::Div: case VDfgType::Div:
case VDfgType::DivS: case VDfgType::DivS:
case VDfgType::ModDiv: case VDfgType::ModDiv:
@ -759,18 +758,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
return; return;
} }
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout:
case VDfgType::SWithin: {
// LCOV_EXCL_START // Lowered before DFG
UASSERT_OBJ(false, this,
"SAnd/SIntersect/SOr/SThroughout/SWithin should be removed before DFG");
return;
// LCOV_EXCL_STOP
}
case VDfgType::LogAnd: case VDfgType::LogAnd:
case VDfgType::LogEq: case VDfgType::LogEq:
case VDfgType::LogIf: case VDfgType::LogIf:
@ -938,6 +925,160 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
delete this; 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 // DfgVisitor

View File

@ -352,6 +352,9 @@ public:
// Human-readable name for source operand with given index for debugging // Human-readable name for source operand with given index for debugging
virtual std::string srcName(size_t idx) const = 0; 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 // DfgVertex visitor
@ -812,9 +815,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
bool DfgVertex::isCheaperThanLoad() const { bool DfgVertex::isCheaperThanLoad() const {
// Constants // Constants
if (is<DfgConst>()) return true; if (is<DfgConst>()) return true;
// Variables
if (is<DfgVertexVar>()) return true;
// Array sels are just address computation // Array sels are just address computation
if (is<DfgArraySel>()) return true; if (is<DfgArraySel>()) return true;
// Small constant select from variable // Small select from variable
if (const DfgSel* const selp = cast<DfgSel>()) { if (const DfgSel* const selp = cast<DfgSel>()) {
if (!selp->fromp()->is<DfgVarPacked>()) return false; if (!selp->fromp()->is<DfgVarPacked>()) return false;
if (selp->fromp()->width() <= VL_QUADSIZE) return true; 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, _) // Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
if (const DfgConcat* const catp = cast<DfgConcat>()) { if (const DfgConcat* const catp = cast<DfgConcat>()) {
if (catp->width() > VL_QUADSIZE) return false; if (catp->width() > VL_QUADSIZE) return false;
const DfgConst* const lCatp = catp->lhsp()->cast<DfgConst>(); const DfgConst* const lConstp = catp->lhsp()->cast<DfgConst>();
if (!lCatp) return false; if (!lConstp || !lConstp->isZero()) return false;
if (!lCatp->isZero()) return false;
return catp->rhsp()->isCheaperThanLoad(); return catp->rhsp()->isCheaperThanLoad();
} }
// Reduction of a cheap vertex // Reduction of a narrow cheap vertex
if (const DfgRedOr* const redOrp = cast<DfgRedOr>()) { if (is<DfgRedOr>() //
return redOrp->srcp()->isCheaperThanLoad(); || 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>()) { // Comparisons of a narrow cheap vertex with constant
return redAndp->srcp()->isCheaperThanLoad(); if (is<DfgEq>() //
} || is<DfgNeq>() //
if (const DfgRedXor* const redXorp = cast<DfgRedXor>()) { || is<DfgLt>() //
return redXorp->srcp()->isCheaperThanLoad(); || 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 // Otherwise probably not
return false; return false;

View File

@ -78,7 +78,6 @@ class V3DfgCse final {
case VDfgType::Add: case VDfgType::Add:
case VDfgType::And: case VDfgType::And:
case VDfgType::ArraySel: case VDfgType::ArraySel:
case VDfgType::BufIf1:
case VDfgType::Concat: case VDfgType::Concat:
case VDfgType::Cond: case VDfgType::Cond:
case VDfgType::CountOnes: case VDfgType::CountOnes:
@ -125,11 +124,6 @@ class V3DfgCse final {
case VDfgType::ShiftRS: case VDfgType::ShiftRS:
case VDfgType::StreamL: case VDfgType::StreamL:
case VDfgType::StreamR: case VDfgType::StreamR:
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout:
case VDfgType::SWithin:
case VDfgType::Sub: case VDfgType::Sub:
case VDfgType::Xor: return V3Hash{}; case VDfgType::Xor: return V3Hash{};
} }
@ -206,7 +200,6 @@ class V3DfgCse final {
case VDfgType::Add: case VDfgType::Add:
case VDfgType::And: case VDfgType::And:
case VDfgType::ArraySel: case VDfgType::ArraySel:
case VDfgType::BufIf1:
case VDfgType::Concat: case VDfgType::Concat:
case VDfgType::Cond: case VDfgType::Cond:
case VDfgType::CountOnes: case VDfgType::CountOnes:
@ -252,11 +245,6 @@ class V3DfgCse final {
case VDfgType::ShiftR: case VDfgType::ShiftR:
case VDfgType::ShiftRS: case VDfgType::ShiftRS:
case VDfgType::StreamL: case VDfgType::StreamL:
case VDfgType::SAnd:
case VDfgType::SIntersect:
case VDfgType::SOr:
case VDfgType::SThroughout:
case VDfgType::SWithin:
case VDfgType::StreamR: case VDfgType::StreamR:
case VDfgType::Sub: case VDfgType::Sub:
case VDfgType::Xor: return true; case VDfgType::Xor: return true;

View File

@ -125,6 +125,8 @@ public:
// Thanks to the interning, equality is identity // 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; }
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 // Type of elements, for arrays only
const DfgDataType& elemDtype() const { const DfgDataType& elemDtype() const {
@ -132,13 +134,6 @@ public:
return *m_elemDtypep; 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 // Static factory and management functions

View File

@ -21,133 +21,27 @@
#include "V3DfgPasses.h" #include "V3DfgPasses.h"
#include "V3File.h" #include "V3File.h"
#include <algorithm>
#include <map>
#include <unordered_map> #include <unordered_map>
class V3DfgPatternStats final { class V3DfgPatternStats final {
static constexpr uint32_t MIN_PATTERN_DEPTH = 1; static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
static constexpr uint32_t MAX_PATTERN_DEPTH = 4; 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 // 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}; 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) { void dump(std::ostream& os) {
using Line = std::pair<std::string, size_t>; using Line = std::pair<std::string, size_t>;
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
os << "DFG patterns with depth " << i << '\n'; os << "DFG patterns with depth " << i << '\n';
// Pick up pattern accumulators with given depth // 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 // Sort patterns, first by descending frequency, then lexically
std::vector<Line> lines; std::vector<Line> lines;
@ -173,8 +67,8 @@ public:
V3DfgPatternStats() = default; V3DfgPatternStats() = default;
~V3DfgPatternStats() { ~V3DfgPatternStats() {
// File to dump to // File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() const std::string filename
+ "__stats_dfg_patterns.txt"; = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt";
// Open, write, close // Open, write, close
const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)}; const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)};
if (ofp->fail()) v3fatal("Can't write file: " << filename); if (ofp->fail()) v3fatal("Can't write file: " << filename);
@ -184,13 +78,7 @@ public:
void accumulate(const DfgGraph& dfg) { void accumulate(const DfgGraph& dfg) {
dfg.forEachVertex([&](const DfgVertex& vtx) { dfg.forEachVertex([&](const DfgVertex& vtx) {
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
std::ostringstream ss; m_patterCounts[i][vtx.patternString(i)] += 1;
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();
} }
}); });
} }

View File

@ -154,9 +154,9 @@ class DataflowOptimize final {
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext); for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
endOfStage("peephole", dfg, acyclicComps); endOfStage("peephole", dfg, acyclicComps);
// Accumulate patterns for reporting // Accumulate patterns for reporting
if (v3Global.opt.stats()) { if (v3Global.opt.dumpDfgPatterns()) {
V3DfgPasses::dumpPatterns(acyclicComps); V3DfgPasses::dumpPatterns(acyclicComps);
endOfStage("patterns"); endOfStage("dumpPatterns");
} }
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext); for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
endOfStage("pushDownSels", dfg, acyclicComps); endOfStage("pushDownSels", dfg, acyclicComps);

View File

@ -208,6 +208,9 @@ class V3DfgPeephole final : public DfgVisitor {
size_t m_currentGeneration = 0; // Current generation number size_t m_currentGeneration = 0; // Current generation number
size_t m_lastId = 0; // Last unique vertex ID assigned size_t m_lastId = 0; // Last unique vertex ID assigned
size_t m_nTemps = 0; // Number of temporary variables created 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 STATE
static V3DebugBisect s_debugBisect; // Debug aid static V3DebugBisect s_debugBisect; // Debug aid
@ -404,6 +407,12 @@ class V3DfgPeephole final : public DfgVisitor {
return make<Vertex>(examplep->fileline(), examplep->dtype(), operands...); return make<Vertex>(examplep->fileline(), examplep->dtype(), operands...);
} }
// Replicate 'bitp' to 'vtxp->width()' bits
DfgVertex* replicate(DfgVertex* vtxp, DfgVertex* bitp) {
if (vtxp->dtype() == m_bitDType) return bitp;
return make<DfgReplicate>(vtxp, bitp, makeI32(vtxp->fileline(), vtxp->width()));
}
// Check two vertex are the same, or the same constant value // Check two vertex are the same, or the same constant value
static bool isSame(const DfgVertex* ap, const DfgVertex* bp) { static bool isSame(const DfgVertex* ap, const DfgVertex* bp) {
if (ap == bp) return true; if (ap == bp) return true;
@ -561,15 +570,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()) { if (rSamep && !rSamep->hasMultipleSinks()) {
DfgVertex* const rlVtxp = rSamep->lhsp(); DfgVertex* const rlVtxp = rSamep->lhsp();
DfgVertex* const rrVtxp = rSamep->rhsp(); 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' // '(a OP (b OP c))' -> '(a OP b) OP c'
if (Vertex* const existingp if (Vertex* const existingp
= m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) { = m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) {
@ -700,10 +719,10 @@ class V3DfgPeephole final : public DfgVisitor {
tryPushBitwiseOpThroughConcat(Vertex* const vtxp, DfgConst* constp, DfgConcat* concatp) { tryPushBitwiseOpThroughConcat(Vertex* const vtxp, DfgConst* constp, DfgConcat* concatp) {
FileLine* const flp = vtxp->fileline(); FileLine* const flp = vtxp->fileline();
// If at least one of the sides of the Concat constant, or width 1 (i.e.: can be // If at least one of the sides of the Concat constant, then push Vertex past Concat
// further simplified), then push the Vertex past the Concat DfgConst* const catLConstp = concatp->lhsp()->cast<DfgConst>();
if (concatp->lhsp()->is<DfgConst>() || concatp->rhsp()->is<DfgConst>() // DfgConst* const catRConstp = concatp->rhsp()->cast<DfgConst>();
|| concatp->lhsp()->dtype() == m_bitDType || concatp->rhsp()->dtype() == m_bitDType) { if (catLConstp || catRConstp) {
APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) {
const uint32_t width = concatp->width(); const uint32_t width = concatp->width();
const DfgDataType& lDtype = concatp->lhsp()->dtype(); const DfgDataType& lDtype = concatp->lhsp()->dtype();
@ -712,14 +731,30 @@ class V3DfgPeephole final : public DfgVisitor {
const uint32_t rWidth = rDtype.size(); const uint32_t rWidth = rDtype.size();
// The new Lhs vertex // The new Lhs vertex
DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); DfgVertex* const newLhsp = [&]() -> DfgVertex* {
newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
Vertex* const newLhsp = make<Vertex>(flp, lDtype, newLhsConstp, concatp->lhsp()); 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 // The new Rhs vertex
DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); DfgVertex* const newRhsp = [&]() -> DfgVertex* {
newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
Vertex* const newRhsp = make<Vertex>(flp, rDtype, newRhsConstp, concatp->rhsp()); 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 this vertex
replace(make<DfgConcat>(concatp, newLhsp, newRhsp)); replace(make<DfgConcat>(concatp, newLhsp, newRhsp));
@ -794,6 +829,46 @@ class V3DfgPeephole final : public DfgVisitor {
return false; 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> template <typename Bitwise>
VL_ATTR_WARN_UNUSED_RESULT bool tryReplaceBitwiseWithReduction(Bitwise* vtxp) { VL_ATTR_WARN_UNUSED_RESULT bool tryReplaceBitwiseWithReduction(Bitwise* vtxp) {
UASSERT_OBJ(vtxp->width() == 1, vtxp, "Width must be 1"); UASSERT_OBJ(vtxp->width() == 1, vtxp, "Width must be 1");
@ -803,13 +878,15 @@ class V3DfgPeephole final : public DfgVisitor {
DfgVertex* const rhsp = vtxp->rhsp(); DfgVertex* const rhsp = vtxp->rhsp();
if (DfgSel* const lSelp = lhsp->template cast<DfgSel>()) { if (DfgSel* const lSelp = lhsp->template cast<DfgSel>()) {
DfgSel* rSelp = rhsp->template cast<DfgSel>(); DfgSel* rSelp = nullptr;
DfgVertex* extrap = nullptr; DfgVertex* extrap = nullptr;
if (!rSelp) { if (DfgSel* const selp = rhsp->template cast<DfgSel>()) {
if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) { rSelp = selp;
rSelp = rBitwisep->lhsp()->template cast<DfgSel>(); } else if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) {
extrap = rBitwisep->rhsp(); 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) { if (rSelp) {
uint32_t lsb = 0; uint32_t lsb = 0;
@ -1021,6 +1098,94 @@ class V3DfgPeephole final : public DfgVisitor {
return {nullptr, 0, 0}; 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 (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;
const uint32_t newMsb = newLsb + width - 1;
if (newMsb < srcWidth) {
APPLYING(PUSH_SEL_THROUGH_REPLICATE) {
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 // VISIT methods
void visit(DfgVertex*) override {} void visit(DfgVertex*) override {}
@ -1148,38 +1313,12 @@ class V3DfgPeephole final : public DfgVisitor {
} }
} }
// Sel from Concat // Unwind through bit packing in one go
if (DfgConcat* const concatp = fromp->cast<DfgConcat>()) { {
DfgVertex* const lhsp = concatp->lhsp(); const auto res = unwindSel(fromp, lsb, width);
DfgVertex* const rhsp = concatp->rhsp(); if (res.first != fromp || res.second != lsb) {
replace(make<DfgSel>(vtxp, res.first, res.second));
if (msb < rhsp->width()) { return;
// 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;
}
}
} }
} }
@ -1197,15 +1336,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 // Sel from Cond
if (DfgCond* const condp = fromp->cast<DfgCond>()) { if (DfgCond* const condp = fromp->cast<DfgCond>()) {
if (!condp->hasMultipleSinks()) { if (!condp->hasMultipleSinks()) {
@ -1239,31 +1369,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 { void visit(DfgMux* const vtxp) override {
@ -1349,18 +1454,27 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return; 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 // ~A & A is all zeroes
if (lhsNotp->srcp() == rhsp) { if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
APPLYING(REPLACE_CONTRADICTORY_AND) { APPLYING(REPLACE_CONTRADICTORY_AND) {
replace(makeZero(flp, vtxp->width())); replace(makeZero(flp, vtxp->width()));
return; return;
} }
} }
// ~A & (A & _) or ~A & (_ & A) is all zeroes if (DfgAnd* const rSamep = rhsp->cast<DfgAnd>()) {
if (DfgAnd* const rhsAndp = rhsp->cast<DfgAnd>()) { DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
if (lhsNotp->srcp() == rhsAndp->lhsp() || lhsNotp->srcp() == rhsAndp->rhsp()) { 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) { APPLYING(REPLACE_CONTRADICTORY_AND_3) {
replace(makeZero(flp, vtxp->width())); replace(makeZero(flp, vtxp->width()));
return; return;
@ -1463,24 +1577,29 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return; 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 // ~A | A is all ones
if (lhsNotp->srcp() == rhsp) { if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
APPLYING(REPLACE_TAUTOLOGICAL_OR) { APPLYING(REPLACE_TAUTOLOGICAL_OR) {
DfgConst* const resp = makeZero(flp, vtxp->width()); replace(makeOnes(flp, vtxp->width()));
resp->num().setAllBits1();
replace(resp);
return; return;
} }
} }
// ~A | (A | _) or ~A | (_ | A) is all ones if (DfgOr* const rSamep = rhsp->cast<DfgOr>()) {
if (DfgOr* const rhsOrp = rhsp->cast<DfgOr>()) { DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
if (lhsNotp->srcp() == rhsOrp->lhsp() || lhsNotp->srcp() == rhsOrp->rhsp()) { 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) { APPLYING(REPLACE_TAUTOLOGICAL_OR_3) {
DfgConst* const resp = makeZero(flp, vtxp->width()); replace(makeOnes(flp, vtxp->width()));
resp->num().setAllBits1();
replace(resp);
return; return;
} }
} }
@ -1525,6 +1644,8 @@ class V3DfgPeephole final : public DfgVisitor {
if (tryPushBitwiseOpThroughReductions(vtxp)) return; if (tryPushBitwiseOpThroughReductions(vtxp)) return;
if (tryPushBitwiseOpThrougSel(vtxp)) return;
if (vtxp->dtype() == m_bitDType) { if (vtxp->dtype() == m_bitDType) {
if (tryReplaceBitwiseWithReduction(vtxp)) return; if (tryReplaceBitwiseWithReduction(vtxp)) return;
} }
@ -1847,10 +1968,9 @@ class V3DfgPeephole final : public DfgVisitor {
DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()}; DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()};
m_vInfo[sp].m_id = ++m_lastId; m_vInfo[sp].m_id = ++m_lastId;
sp->addDriver(catp, lsb, flp); 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++); 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()); varp->tmpForp(varp->vscp());
m_vInfo[varp].m_id = ++m_lastId; m_vInfo[varp].m_id = ++m_lastId;
varp->vscp()->varp()->isInternal(true); varp->vscp()->varp()->isInternal(true);
@ -2511,53 +2631,63 @@ class V3DfgPeephole final : public DfgVisitor {
} }
if (vtxp->dtype() == m_bitDType) { if (vtxp->dtype() == m_bitDType) {
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b if (isSame(condp, thenp)) { // a ? a : 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
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) { APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) {
replace(make<DfgOr>(vtxp, condp, elsep)); replace(make<DfgOr>(vtxp, condp, elsep));
return; 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) { APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) {
replace(make<DfgAnd>(vtxp, condp, thenp)); replace(make<DfgAnd>(vtxp, condp, thenp));
return; 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 if (isOnes(thenp)) { // a ? 1 : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { 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; return;
} }
} }
if (isZero(elsep)) { // a ? b : 0 becomes a & b if (isZero(elsep)) { // a ? b : 0 becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { 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; return;
} }
} }
if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { 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; return;
} }
} }
if (DfgOr* const tOrp = thenp->cast<DfgOr>()) { if (DfgOr* const tOrp = thenp->cast<DfgOr>()) {
if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c) if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c)
APPLYING(REPLACE_COND_THEN_OR_LHS) { 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)); replace(make<DfgOr>(vtxp, tOrp->lhsp(), andp));
return; return;
} }
} }
if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b) if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b)
APPLYING(REPLACE_COND_THEN_OR_RHS) { 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)); replace(make<DfgOr>(vtxp, tOrp->rhsp(), andp));
return; return;
} }
@ -2590,6 +2720,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)) { if (isEqOne(thenp) && isZero(elsep)) {

View File

@ -51,6 +51,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ _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_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \ _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_COMMUTATIVE_BINARY_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_COND_LHS) \
@ -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_CONST_ZERO_ONES) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _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_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_LHS) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ _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) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \
@ -160,6 +162,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_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, 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, 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_NEQ_CONDITION) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_SIDES_IN_BINARY) _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_SIDES_IN_BINARY)

View File

@ -195,24 +195,35 @@ class DfgRegularize final {
// Scope cache for below // Scope cache for below
DfgVertex::ScopeCache scopeCache; 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()) { for (DfgVertex& vtx : m_dfg.opVertices()) {
// LValue vertices feed into variables eventually and need no temporaries // LValue vertices feed into variables eventually and need no temporaries
if (vtx.is<DfgVertexSplice>()) continue; if (vtx.is<DfgVertexSplice>()) continue;
if (vtx.is<DfgUnitArray>()) 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; // Ensure intermediate values used multiple times are written to variables
for (size_t fanout = fanout2Vtxps.size() - 1; fanout > 0; --fanout) {
// Need to create an intermediate variable for (DfgVertex* const vtxp : fanout2Vtxps[fanout]) {
++m_ctx.m_temporariesIntroduced; if (!needsTemporary(*vtxp, *vtxp)) continue;
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps); // Need to create an intermediate variable
FileLine* const flp = vtx.fileline(); ++m_ctx.m_temporariesIntroduced;
AstScope* const scopep = vtx.scopep(scopeCache); const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtx.dtype(), scopep); FileLine* const flp = vtxp->fileline();
++m_nTmps; AstScope* const scopep = vtxp->scopep(scopeCache);
// Replace vertex with the variable, make it drive the variable DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
vtx.replaceWith(newp); ++m_nTmps;
newp->srcp(&vtx); // Replace vertex with the variable, make it drive the variable
vtxp->replaceWith(newp);
newp->srcp(vtxp);
}
} }
} }

View File

@ -826,6 +826,14 @@ public:
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect())); putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
puts(", "); puts(", ");
putsQuoted(nodep->linescov()); 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"); puts(");\n");
} }
void visit(AstCoverToggleDecl* nodep) override { void visit(AstCoverToggleDecl* nodep) override {

View File

@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit {
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t"); puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n"); puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
puts("const char* hierp, const char* pagep, const char* commentp, const char* " 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)) { 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.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.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; std::set<string> cuse_set;
auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); }; auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };

View File

@ -180,7 +180,9 @@ class EmitCImp final : public EmitCFunc {
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t"); puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n"); puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
puts("const char* hierp, const char* pagep, const char* commentp, const char* " 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) { if (v3Global.opt.threads() > 1) {
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n"); puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n"); puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n");
@ -198,10 +200,14 @@ class EmitCImp final : public EmitCFunc {
puts(" \"filename\",filenamep,"); puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,"); puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n"); puts(" \"column\",column,\n");
puts("\"hier\",fullhier,"); puts("\"hier\",fullhier.c_str(),");
puts(" \"page\",pagep,"); puts(" \"page\",pagep,");
puts(" \"comment\",commentp,"); 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"); puts("}\n");
} }
if (v3Global.opt.coverageToggle()) { if (v3Global.opt.coverageToggle()) {
@ -237,7 +243,7 @@ class EmitCImp final : public EmitCFunc {
puts(" \"filename\",filenamep,"); puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,"); puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n"); puts(" \"column\",column,\n");
puts("\"hier\",fullhier,"); puts("\"hier\",fullhier.c_str(),");
puts(" \"page\",pagep,"); puts(" \"page\",pagep,");
puts(" \"comment\",commentWithIndex.c_str(),"); puts(" \"comment\",commentWithIndex.c_str(),");
puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always

View File

@ -106,6 +106,7 @@ public:
ENUMITEMWIDTH, // Error: enum item width mismatch ENUMITEMWIDTH, // Error: enum item width mismatch
ENUMVALUE, // Error: enum type needs explicit cast ENUMVALUE, // Error: enum type needs explicit cast
EOFNEWLINE, // End-of-file missing newline EOFNEWLINE, // End-of-file missing newline
FSMMULTI, // Multiple FSM candidates in one always block
FUNCTIMECTL, // Functions cannot have timing/delay/wait FUNCTIMECTL, // Functions cannot have timing/delay/wait
FUTURE, // Feature is under development and not yet supported FUTURE, // Feature is under development and not yet supported
GENCLK, // Generated Clock. Historical, never issued. GENCLK, // Generated Clock. Historical, never issued.
@ -223,14 +224,14 @@ public:
"BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST",
"CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN",
"CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL", "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI",
"FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN", "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH",
"IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
"INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH",
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN",
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT", "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT",
"PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
"PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE", "PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
"SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",

File diff suppressed because it is too large Load Diff

788
src/V3FsmDetect.cpp Normal file
View File

@ -0,0 +1,788 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: FSM coverage detect pass
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// FSM COVERAGE DETECT:
// Walk clocked always blocks while the original FSM structure is still
// present, build a per-FSM V3Graph representation of the extracted
// states/transitions, then immediately lower that completed graph state
// into the final coverage declarations, previous-state tracking, and
// active blocks needed to implement FSM state and arc coverage in the
// generated model.
//
//*************************************************************************
#include "V3PchAstNoMT.h"
#include "V3FsmDetect.h"
#include "V3Ast.h"
#include "V3Graph.h"
#include <cctype>
#include <map>
#include <memory>
#include <unordered_map>
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
// Captures one sensitivity-list entry so the lowering phase can later rebuild
// an active block with the same triggering event control.
struct FsmSenDesc final {
// Encoded edge kind copied from AstSenItem::edgeType() so lowering can
// rebuild the same trigger semantics on the synthesized coverage block.
VEdgeType::en edgeType = static_cast<VEdgeType::en>(0);
// Triggering signal in the saved scoped AST.
AstVarScope* varScopep = nullptr;
};
// Captures the simple reset predicate shape that survives to this pass after
// earlier normalization so reset arcs can be reconstructed during lowering.
struct FsmResetCondDesc final {
// Reset signal used by the FSM in the saved scoped AST.
AstVarScope* varScopep = nullptr;
};
class FsmGraph;
class FsmVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(FsmVertex, V3GraphVertex)
public:
enum class Kind : uint8_t { STATE, RESET_ANY, DEFAULT_ANY };
private:
Kind m_kind; // State vs synthetic ANY/default vertex role.
string m_label; // User-facing state or pseudo-state label.
int m_value = 0; // Encoded state value for real state vertices.
protected:
FsmVertex(V3Graph* graphp, Kind kind, string label, int value) VL_MT_DISABLED
: V3GraphVertex{graphp},
m_kind{kind},
m_label{label},
m_value{value} {}
~FsmVertex() override = default;
public:
Kind kind() const { return m_kind; }
bool isState() const { return m_kind == Kind::STATE; }
bool isResetAny() const { return m_kind == Kind::RESET_ANY; }
bool isDefaultAny() const { return m_kind == Kind::DEFAULT_ANY; }
const string& label() const { return m_label; }
int value() const { return m_value; }
string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); }
};
class FsmStateVertex final : public FsmVertex {
VL_RTTI_IMPL(FsmStateVertex, FsmVertex)
public:
FsmStateVertex(V3Graph* graphp, string label, int value) VL_MT_DISABLED
: FsmVertex{graphp, Kind::STATE, label, value} {}
~FsmStateVertex() override = default;
string dotColor() const override { return "lightblue"; }
string dotShape() const override { return "ellipse"; }
};
class FsmPseudoVertex final : public FsmVertex {
VL_RTTI_IMPL(FsmPseudoVertex, FsmVertex)
public:
FsmPseudoVertex(V3Graph* graphp, Kind kind, string label) VL_MT_DISABLED
: FsmVertex{graphp, kind, label, 0} {}
~FsmPseudoVertex() override = default;
string name() const override VL_MT_SAFE { return label(); }
string dotColor() const override { return isResetAny() ? "darkgreen" : "orange"; }
string dotShape() const override { return "diamond"; }
};
class FsmArcEdge final : public V3GraphEdge {
VL_RTTI_IMPL(FsmArcEdge, V3GraphEdge)
bool m_isReset = false; // Arc originates from the synthetic reset source.
bool m_isCond = false; // Arc came from a conditional next-state split.
bool m_isDefault = false; // Arc represents a case default source.
FileLine* m_flp = nullptr; // Source location for emitted coverage metadata.
public:
FsmArcEdge(V3Graph* graphp, FsmVertex* fromp, FsmStateVertex* top, bool isReset, bool isCond,
bool isDefault, FileLine* flp) VL_MT_DISABLED : V3GraphEdge{graphp, fromp, top, 1},
m_isReset{isReset},
m_isCond{isCond},
m_isDefault{isDefault},
m_flp{flp} {}
~FsmArcEdge() override = default;
bool isReset() const { return m_isReset; }
bool isCond() const { return m_isCond; }
bool isDefault() const { return m_isDefault; }
FileLine* fileline() const { return m_flp; }
string dotLabel() const override {
if (m_isReset) return "reset";
if (m_isDefault) return "default";
if (m_isCond) return "cond";
return "";
}
string dotColor() const override {
if (m_isReset) return "darkgreen";
if (m_isDefault) return "orange";
if (m_isCond) return "blue";
return "black";
}
};
// One graph per detected FSM. Graph-level metadata captures the non-graph
// context needed to lower states/arcs back into the AST after detection.
class FsmGraph final : public V3Graph {
AstScope* m_scopep = nullptr; // Owning scoped block for the detected FSM.
AstAlways* m_alwaysp = nullptr; // Original always block being instrumented.
string m_stateVarName; // Pretty state variable name for user-visible output.
string m_stateVarInternalName; // Internal state symbol name for dump tags.
AstVarScope* m_stateVarScopep = nullptr; // Scoped state variable being tracked.
std::vector<FsmSenDesc> m_senses; // Saved event controls for recreated active blocks.
FsmResetCondDesc m_resetCond; // Saved reset predicate shape, if one exists.
bool m_hasResetCond = false; // Whether the detected FSM had a reset branch.
bool m_resetInclude = false; // Whether reset arcs count toward coverage totals.
bool m_inclCond = false; // Whether conditional arcs should be kept explicitly.
FileLine* m_flp = nullptr; // Representative source location for declarations/arcs.
std::unordered_map<int, FsmStateVertex*> m_stateVertices; // Value to state-vertex map.
FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs.
FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults.
public:
FsmGraph() VL_MT_DISABLED
: m_resetVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::RESET_ANY, "ANY"}},
m_defaultVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::DEFAULT_ANY, "default"}} {}
AstScope* scopep() const { return m_scopep; }
void scopep(AstScope* scopep) { m_scopep = scopep; }
AstAlways* alwaysp() const { return m_alwaysp; }
void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; }
const string& stateVarName() const { return m_stateVarName; }
void stateVarName(const string& name) { m_stateVarName = name; }
const string& stateVarInternalName() const { return m_stateVarInternalName; }
void stateVarInternalName(const string& name) { m_stateVarInternalName = name; }
AstVarScope* stateVarScopep() const { return m_stateVarScopep; }
void stateVarScopep(AstVarScope* vscp) { m_stateVarScopep = vscp; }
const std::vector<FsmSenDesc>& senses() const { return m_senses; }
std::vector<FsmSenDesc>& senses() { return m_senses; }
const FsmResetCondDesc& resetCond() const { return m_resetCond; }
FsmResetCondDesc& resetCond() { return m_resetCond; }
bool hasResetCond() const { return m_hasResetCond; }
void hasResetCond(bool flag) { m_hasResetCond = flag; }
bool resetInclude() const { return m_resetInclude; }
void resetInclude(bool flag) { m_resetInclude = flag; }
bool inclCond() const { return m_inclCond; }
void inclCond(bool flag) { m_inclCond = flag; }
FileLine* fileline() const { return m_flp; }
void fileline(FileLine* flp) { m_flp = flp; }
FsmStateVertex* addStateVertex(string label, int value) VL_MT_DISABLED {
FsmStateVertex* const vertexp = new FsmStateVertex{this, label, value};
m_stateVertices.emplace(value, vertexp);
return vertexp;
}
FsmPseudoVertex* resetAnyVertex() VL_MT_DISABLED { return m_resetVertexp; }
FsmPseudoVertex* defaultAnyVertex() VL_MT_DISABLED { return m_defaultVertexp; }
FsmArcEdge* addArc(int fromValue, int toValue, bool isReset, bool isCond, bool isDefault,
FileLine* flp) VL_MT_DISABLED {
FsmStateVertex* const top = m_stateVertices.at(toValue);
FsmVertex* fromp = nullptr;
if (isReset) {
fromp = resetAnyVertex();
} else if (isDefault) {
fromp = defaultAnyVertex();
} else {
fromp = m_stateVertices.at(fromValue);
}
return new FsmArcEdge{this, fromp, top, isReset, isCond, isDefault, flp};
}
string name() const VL_MT_SAFE {
return "FSM "
+ (m_stateVarName.empty() ? (m_stateVarScopep ? m_stateVarScopep->name() : "")
: m_stateVarName);
}
string dumpTag(size_t index) const {
string tag = stateVarInternalName();
for (char& ch : tag) {
if (!std::isalnum(static_cast<unsigned char>(ch))) ch = '_';
}
return "fsm_" + cvtToStr(index) + "_" + tag;
}
};
struct DetectedFsm final {
std::unique_ptr<FsmGraph> graphp; // Extracted graph for one detected FSM candidate.
};
using DetectedFsmMap = std::map<string, DetectedFsm>;
// Local shared state between the two adjacent FSM coverage phases. Detection
// fills this with recovered FSM graphs; lowering consumes the completed graphs
// immediately afterward without needing any AST serialization bridge.
class FsmState final {
// All detected FSMs keyed by state varscope name. This is the only bridge
// between the adjacent detect and lower phases, so the second phase never
// needs to rediscover or serialize the extracted machine.
DetectedFsmMap m_fsms;
public:
DetectedFsmMap& fsms() { return m_fsms; }
const DetectedFsmMap& fsms() const { return m_fsms; }
};
// Detection runs while the original clocked/case structure is still intact and
// populates graph-backed FSM models without mutating the tree mid-traversal.
// This pass is intentionally conservative: for this PR we only lock down the
// small set of transition/selector forms that are already stable in the
// normalized AST we see here. The remaining reject branches are therefore
// mostly future-feature boundaries, not accidental dead code.
class FsmDetectVisitor final : public VNVisitor {
// STATE - for current visit position (use VL_RESTORER)
FsmState& m_state;
AstScope* m_scopep = nullptr;
// METHODS
// Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the
// underlying enum type before deciding whether a case is a candidate.
static AstNodeDType* unwrapEnumCandidate(AstNodeDType* dtypep) {
return dtypep->skipRefToEnump();
}
// Reset arcs are only modeled for the simple signal form that survives to
// this pass after earlier normalization.
static bool isSimpleResetCond(AstNodeExpr* condp) { return VN_IS(condp, VarRef); }
// Normalize the reset condition into a compact description so the lowering
// phase can regenerate the same predicate after detection. By the time
// this pass runs, active-low source forms such as "!rst_n" have already
// been canonicalized to a positive-condition if/else shape, so only a
// plain VarRef survives here.
static FsmResetCondDesc describeResetCond(AstNodeExpr* condp) {
FsmResetCondDesc desc;
if (AstVarRef* const vrefp = VN_CAST(condp, VarRef)) {
desc.varScopep = vrefp->varScopep();
}
return desc;
}
// Snapshot the original event control so the lowering phase can rebuild an
// active block with the same edge semantics.
static std::vector<FsmSenDesc> describeSenTree(AstSenTree* sentreep) {
std::vector<FsmSenDesc> senses;
for (AstSenItem* itemp = sentreep->sensesp(); itemp;
itemp = VN_AS(itemp->nextp(), SenItem)) {
AstNodeVarRef* const vrefp = itemp->varrefp();
if (!vrefp) continue;
FsmSenDesc desc;
desc.edgeType = itemp->edgeType().m_e;
desc.varScopep = vrefp->varScopep();
senses.push_back(desc);
}
return senses;
}
// Ignore existing coverage increments so FSM detection sees the user logic
// rather than other instrumentation already attached to the block.
static bool isIgnorableStmt(AstNode* nodep) { return VN_IS(nodep, CoverInc); }
// Conservative extractor: only treat a branch as simple when exactly one
// non-coverage statement remains after unwrapping. Richer multi-statement
// or control-flow forms are intentionally left for follow-on FSM-detection
// work instead of being partially inferred here.
static AstNode* singleMeaningfulStmt(AstNode* stmtp) {
AstNode* resultp = nullptr;
for (AstNode* nodep = stmtp; nodep; nodep = nodep->nextp()) {
if (isIgnorableStmt(nodep)) continue;
if (resultp) return nullptr;
resultp = nodep;
}
return resultp;
}
// Recognize the direct "state <= X" form that gives us an unambiguous arc
// target without needing deeper control-flow reasoning. Branches that fall
// out here represent currently unsupported next-state shapes rather than
// bugs in the implemented subset.
static AstNodeAssign* directStateAssign(AstNode* stmtp, AstVarScope* stateVscp) {
AstNode* const nodep = singleMeaningfulStmt(stmtp);
if (!nodep) return nullptr;
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
if (!vrefp || vrefp->varScopep() != stateVscp) return nullptr;
return assp;
}
// Prefer enum labels in reports; fall back to synthetic labels for forced
// non-enum FSMs so coverage points remain human-readable.
static string labelForValue(const std::unordered_map<int, string>& labels, int value) {
const std::unordered_map<int, string>::const_iterator it = labels.find(value);
return it == labels.end() ? ("S" + cvtToStr(value)) : it->second;
}
// The extractor only models constant-valued state transitions, and by the
// time detect runs those values have already been constant-folded.
static bool exprConstValue(AstNodeExpr* exprp, int& value) {
if (AstConst* const constp = VN_CAST(exprp, Const)) {
value = constp->toSInt();
return true;
}
return false;
}
// Enum-backed FSMs should only transition to values that were interned as
// known states. If a constant transition targets some other encoding, warn
// and skip FSM instrumentation for that edge rather than silently dropping
// it or turning optional coverage into a hard compile failure.
static bool validateKnownStateValue(AstNode* nodep,
const std::unordered_map<int, string>& labels, int value) {
if (labels.find(value) != labels.end()) return true;
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state transitions "
"that assign a constant not present in the declared enum");
return false;
}
// Extract supported case-item transitions in one place so the conservative
// policy for direct and ternary forms stays consistent. The false exits in
// this helper are deliberate subset boundaries: they document shapes we do
// not yet model in this PR and that future FSM-detection work may widen.
static bool emitCaseItemArcs(FsmGraph& graph, AstCaseItem* itemp, AstVarScope* stateVscp,
const std::unordered_map<int, string>& labels, bool inclCond) {
std::vector<std::pair<string, int>> froms;
if (itemp->isDefault()) {
if (!inclCond) return false;
froms.emplace_back("default", 0);
} else {
for (AstNodeExpr* condp = itemp->condsp(); condp;
condp = VN_CAST(condp->nextp(), NodeExpr)) {
int value = 0;
if (!exprConstValue(condp, value)) continue;
froms.emplace_back(labelForValue(labels, value), value);
}
if (froms.empty()) return false;
}
if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) {
int toValue = 0;
if (exprConstValue(assp->rhsp(), toValue)) {
if (!validateKnownStateValue(assp, labels, toValue)) return true;
for (const std::pair<string, int>& from : froms) {
graph.addArc(from.second, toValue, false, false, itemp->isDefault(),
assp->fileline());
}
return true;
}
if (AstCond* const condp = VN_CAST(assp->rhsp(), Cond)) {
int thenValue = 0;
int elseValue = 0;
const bool simpleCond = exprConstValue(condp->thenp(), thenValue)
&& exprConstValue(condp->elsep(), elseValue);
if (simpleCond || inclCond) {
if (!validateKnownStateValue(condp->thenp(), labels, thenValue)) return true;
if (!validateKnownStateValue(condp->elsep(), labels, elseValue)) return true;
for (const int branchValue : {thenValue, elseValue}) {
for (const std::pair<string, int>& from : froms) {
graph.addArc(from.second, branchValue, false, true, itemp->isDefault(),
assp->fileline());
}
}
return true;
}
}
}
return false;
}
// Reset transitions are described separately because they live in the reset
// branch outside the steady-state case statement.
static void addResetArcs(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp,
const std::unordered_map<int, string>& labels) {
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) {
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
int toValue = 0;
if (vrefp && vrefp->varScopep() == stateVscp
&& exprConstValue(assp->rhsp(), toValue)) {
if (!validateKnownStateValue(assp, labels, toValue)) continue;
graph.addArc(0, toValue, true, false, false, assp->fileline());
}
}
}
}
// Turn one candidate case statement into the graph representation that the
// later lowering phase will consume directly, while reviewers can still
// inspect the extracted machine via DOT dumps.
void processCase(AstCase* casep, AstNodeExpr* resetCondp, AstAlways* alwaysp) {
AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef);
if (!selp) return;
AstVarScope* const stateVscp = selp->varScopep();
AstVar* const stateVarp = selp->varp();
AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType);
if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType);
const bool forced = stateVarp->attrFsmState();
if (!enump && !forced) return;
std::vector<std::pair<string, int>> states;
std::unordered_map<int, string> labels;
if (enump) {
if (stateVscp->width() < 1 || stateVscp->width() > 32) {
casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state "
"variables wider than 32 bits");
return;
}
for (AstEnumItem* itemp = enump->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const AstConst* const constp = VN_AS(itemp->valuep(), Const);
const int value = constp->toSInt();
states.emplace_back(itemp->name(), value);
labels.emplace(value, itemp->name());
}
if (states.size() < 2) return;
} else {
const int width = stateVarp->width();
if (width < 1 || width >= 31) return;
const unsigned stateCount = 1U << width;
for (unsigned value = 0; value < stateCount; ++value) {
const string label = "S" + cvtToStr(value);
states.emplace_back(label, static_cast<int>(value));
labels.emplace(static_cast<int>(value), label);
}
}
DetectedFsm& entry = m_state.fsms()[stateVscp->name()];
if (!entry.graphp) {
entry.graphp.reset(new FsmGraph{});
entry.graphp->scopep(m_scopep);
entry.graphp->alwaysp(alwaysp);
entry.graphp->stateVarName(stateVscp->prettyName());
entry.graphp->stateVarInternalName(stateVarp->name());
entry.graphp->stateVarScopep(stateVscp);
entry.graphp->senses() = describeSenTree(alwaysp->sentreep());
entry.graphp->resetCond() = describeResetCond(resetCondp);
entry.graphp->hasResetCond(entry.graphp->resetCond().varScopep != nullptr);
entry.graphp->resetInclude(stateVarp->attrFsmResetArc());
entry.graphp->inclCond(stateVarp->attrFsmArcInclCond());
entry.graphp->fileline(casep->fileline());
for (const std::pair<string, int>& state : states) {
entry.graphp->addStateVertex(state.first, state.second);
}
}
for (AstCaseItem* itemp = casep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
emitCaseItemArcs(*entry.graphp, itemp, stateVscp, labels, entry.graphp->inclCond());
}
}
// Find the first supported FSM candidate in a clocked always block, warn on
// additional candidates, and attach reset arcs when present. Candidate
// filtering stays narrow on purpose: we prefer to skip ambiguous shapes now
// and expand detection in a later PR rather than over-infer coverage from
// forms we do not yet model confidently.
void processAlways(AstAlways* alwaysp) {
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasClocked()) return;
std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates;
AstNode* stmtsp = alwaysp->stmtsp();
AstIf* const firstIfp = VN_CAST(stmtsp, If);
if (firstIfp) {
if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) {
candidates.emplace_back(
casep, isSimpleResetCond(firstIfp->condp()) ? firstIfp->condp() : nullptr);
}
}
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
if (AstCase* const casep = VN_CAST(nodep, Case))
candidates.emplace_back(casep, nullptr);
}
if (candidates.empty()) return;
AstVarScope* firstVscp = nullptr;
for (const std::pair<AstCase*, AstNodeExpr*>& cand : candidates) {
AstVarRef* const selp = VN_CAST(cand.first->exprp(), VarRef);
AstVarScope* const vscp = selp ? selp->varScopep() : nullptr;
if (!vscp) continue;
if (!firstVscp) {
firstVscp = vscp;
processCase(cand.first, cand.second, alwaysp);
} else if (vscp != firstVscp) {
cand.first->v3warn(FSMMULTI,
"FSM coverage: multiple enum-typed case statements found in "
"the same always block. Only the first candidate will be "
"instrumented.");
} else {
cand.first->v3warn(COVERIGN,
"Ignoring unsupported: FSM coverage on multiple supported case "
"statements found in the same always block. Only the first "
"candidate will be instrumented.");
}
}
if (!(firstIfp && firstVscp)) return;
const DetectedFsmMap& fsms = m_state.fsms();
const DetectedFsmMap::const_iterator it = fsms.find(firstVscp->name());
if (it == fsms.end()) return;
FsmGraph* const graphp = it->second.graphp.get();
if (!graphp->hasResetCond()) return;
std::unordered_map<int, string> labels;
for (const V3GraphVertex& vtx : graphp->vertices()) {
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
if (!vertexp->isState()) continue;
labels.emplace(vertexp->value(), vertexp->label());
}
addResetArcs(*graphp, firstIfp->thensp(), firstVscp, labels);
}
// Track the current scope so each detected FSM records the module/scope
// where instrumentation must later be inserted.
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
}
// FSM extraction only cares about clocked always processes.
void visit(AstAlways* nodep) override { processAlways(nodep); }
// Continue the walk through the rest of the design hierarchy.
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
// Collect all FSM graphs into the shared local state before the lowering
// phase starts mutating the AST with coverage machinery.
FsmDetectVisitor(FsmState& state, AstNetlist* rootp)
: m_state{state} {
iterate(rootp);
}
};
// Lower the completed FSM graphs into the concrete coverage declarations,
// previous-state tracking, and pre/post-triggered instrumentation that the
// runtime uses to record state and transition coverage.
class FsmLowerVisitor final {
// STATE - across all visitors
const FsmState& m_state;
// METHODS
// Rebuild a state-typed constant using the tracked state variable
// width/sign so emitted comparisons match the original representation.
static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, int value) {
V3Number num{flp, vscp->width(), static_cast<uint32_t>(value)};
num.isSigned(vscp->dtypep()->isSigned());
return new AstConst{flp, num};
}
// Build guards incrementally without forcing callers to special-case the
// first predicate; this keeps emitted state/arc conditions readable.
static AstNodeExpr* andExpr(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) {
if (!lhsp) return rhsp;
return new AstLogAnd{flp, lhsp, rhsp};
}
static AstNodeExpr* buildResetCond(FileLine* flp, AstVarScope* resetVscp,
const FsmResetCondDesc&) {
return new AstVarRef{flp, resetVscp, VAccess::READ};
}
// Rebuild the original event control from the saved sense description so
// post-state coverage sampling runs on the same triggering edges.
static AstSenTree* buildSenTree(FileLine* flp, const std::vector<FsmSenDesc>& senses) {
AstSenTree* const sentreep = new AstSenTree{flp, nullptr};
for (const FsmSenDesc& sense : senses) {
AstSenItem* const senItemp
= new AstSenItem{flp, VEdgeType{sense.edgeType},
new AstVarRef{flp, sense.varScopep, VAccess::READ}};
sentreep->addSensesp(senItemp);
}
return sentreep;
}
// Lower one fully detected FSM graph into the concrete coverage machinery
// used by generated models: declarations, previous-state tracking, and the
// pre/post-triggered increment logic for states and arcs.
void buildOne(const FsmGraph& graph) {
AstAlways* const alwaysp = graph.alwaysp();
AstScope* const scopep = graph.scopep();
AstVarScope* const stateVscp = graph.stateVarScopep();
FileLine* const flp = graph.fileline();
AstNodeModule* const modp = scopep->modp();
AstNodeDType* const prevDTypep = scopep->findLogicDType(
stateVscp->width(), stateVscp->width(), stateVscp->dtypep()->numeric());
AstVarScope* const prevVscp
= scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep);
// The saved previous-state temp crosses the scheduler's pre/post split
// in the same way as Verilator's built-in NBA shadow variables, so keep
// both vars marked as post-life participants for stable MT ordering.
stateVscp->optimizeLifePost(true);
prevVscp->optimizeLifePost(true);
AstActive* const initActivep
= new AstActive{flp, "fsm-coverage-init",
new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
initActivep->senTreeStorep(initActivep->sentreep());
// Seed the previous-state temp during initialization so the first
// clock edge compares against a defined state value.
initActivep->addStmtsp(new AstInitialStatic{
flp, new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}}});
scopep->addBlocksp(initActivep);
AstAlwaysPost* const covPostp = new AstAlwaysPost{flp};
// Save the previous state as plain sequential logic at the front of
// the original always_ff body, then evaluate coverage in post logic
// after the delayed state update commits. This avoids a scheduler race
// between a separate AstAlwaysPre task and the real state commit.
AstNode* const bodysp = alwaysp->stmtsp()->unlinkFrBackWithNext();
alwaysp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}});
alwaysp->addStmtsp(bodysp);
for (const V3GraphVertex& vtx : graph.vertices()) {
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
if (!vertexp->isState()) continue;
const FsmStateVertex* const statep = vtx.as<FsmStateVertex>();
// State coverage fires when the FSM enters a state from any other
// value, so repeated self-holds do not count as new entries.
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{flp,
"v_fsm_state/" + modp->prettyName(),
graph.stateVarName() + "::" + statep->label(),
"",
0,
graph.stateVarName(),
"",
statep->label()};
declp->hier(scopep->prettyName());
modp->addStmtsp(declp);
AstNodeExpr* const guardp
= andExpr(flp,
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, statep->value())},
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, statep->value())});
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
}
for (const V3GraphVertex& vtx : graph.vertices()) {
const FsmVertex* const fromVertexp = vtx.as<FsmVertex>();
for (const V3GraphEdge& edge : fromVertexp->outEdges()) {
const FsmArcEdge* const arcp = edge.as<FsmArcEdge>();
const FsmStateVertex* const toStatep = arcp->top()->as<FsmStateVertex>();
// Arc coverage mirrors the extracted graph exactly, including
// reset and synthetic-default sources, so reports match the
// reviewer-visible graph dump and the user-visible annotation.
const string resetTag
= arcp->isReset() ? (graph.resetInclude() ? "[reset_include]" : "[reset]")
: "";
const string fsmTag = arcp->isReset()
? (graph.resetInclude() ? "reset_include" : "reset")
: arcp->isDefault() ? "default"
: "";
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{flp,
"v_fsm_arc/" + modp->prettyName(),
graph.stateVarName() + "::" + fromVertexp->label()
+ "->" + toStatep->label() + resetTag,
"",
0,
graph.stateVarName(),
fromVertexp->label(),
toStatep->label(),
fsmTag};
declp->hier(scopep->prettyName());
modp->addStmtsp(declp);
AstNodeExpr* guardp = nullptr;
if (fromVertexp->isResetAny()) {
// Reset arcs are modeled as pseudo-source edges in the
// graph, then reconstructed here into the original simple
// reset predicate combined with the destination state.
guardp = buildResetCond(flp, graph.resetCond().varScopep, graph.resetCond());
guardp = andExpr(flp, guardp,
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
} else if (fromVertexp->isDefaultAny()) {
// Synthetic default arcs mean "none of the explicit
// source states matched", so rebuild that as a conjunction
// of previous-state != known-state tests.
for (const V3GraphVertex& stateVtx : graph.vertices()) {
const FsmVertex* const stateVertexp = stateVtx.as<FsmVertex>();
if (!stateVertexp->isState()) continue;
guardp = andExpr(
flp, guardp,
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, stateVertexp->value())});
}
guardp = andExpr(flp, guardp,
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
} else {
guardp
= andExpr(flp,
new AstEq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
makeStateConst(flp, prevVscp, fromVertexp->value())},
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
makeStateConst(flp, stateVscp, toStatep->value())});
}
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
}
}
AstSenTree* const sentreep = buildSenTree(flp, graph.senses());
AstActive* const activep = new AstActive{flp, "fsm-coverage", sentreep};
activep->senTreeStorep(sentreep);
scopep->addBlocksp(activep);
activep->addStmtsp(covPostp);
}
public:
// CONSTRUCTORS
// Lower every detected FSM graph from the shared local state into
// concrete coverage instrumentation while the saved scoped pointers are
// still valid in the same pass.
explicit FsmLowerVisitor(const FsmState& state)
: m_state{state} {
for (const std::pair<const string, DetectedFsm>& it : m_state.fsms()) {
buildOne(*it.second.graphp);
}
}
};
} // namespace
void V3FsmDetect::detect(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
FsmState state;
// Phase 1: recover each supported FSM into a complete graph while the
// original clocked/case structure is still easy to recognize.
FsmDetectVisitor detect{state, rootp};
if (dumpGraphLevel() >= 6) {
size_t index = 0;
for (const std::pair<const string, DetectedFsm>& it : state.fsms()) {
it.second.graphp->dumpDotFilePrefixed(it.second.graphp->dumpTag(index++));
}
}
// Phase 2: lower the completed in-memory graph state immediately, without
// crossing into another pass owner or serializing through AST placeholders.
{ FsmLowerVisitor lower{state}; }
V3Global::dumpCheckGlobalTree("fsm-detect", 0, dumpTreeEitherLevel() >= 3);
}

33
src/V3FsmDetect.h Normal file
View File

@ -0,0 +1,33 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: FSM coverage detect pass
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3FSMDETECT_H_
#define VERILATOR_V3FSMDETECT_H_
#include "config_build.h"
#include "verilatedos.h"
class AstNetlist;
class V3FsmDetect final {
public:
// Detect FSMs while the original clocked/case structure is still visible,
// then immediately lower the recovered graphs into concrete coverage
// instrumentation as a second local phase in the same pass.
static void detect(AstNetlist* rootp) VL_MT_DISABLED;
};
#endif

View File

@ -125,6 +125,7 @@ class V3Global final {
bool m_usesProbDist = false; // Uses $dist_* bool m_usesProbDist = false; // Uses $dist_*
bool m_usesStdPackage = false; // Design uses the std package bool m_usesStdPackage = false; // Design uses the std package
bool m_usesTiming = false; // Design uses timing constructs 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_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay)
bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasForceableSignals = false; // Need to apply V3Force pass
bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted
@ -205,6 +206,8 @@ public:
void setUsesZeroDelay() { m_usesZeroDelay = true; } void setUsesZeroDelay() { m_usesZeroDelay = true; }
bool hasForceableSignals() const { return m_hasForceableSignals; } bool hasForceableSignals() const { return m_hasForceableSignals; }
void setHasForceableSignals() { m_hasForceableSignals = true; } 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; } bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; }
void setHasSystemCSections() { m_hasSystemCSections = true; } void setHasSystemCSections() { m_hasSystemCSections = true; }
V3HierGraph* hierGraphp() const { return m_hierGraphp; } V3HierGraph* hierGraphp() const { return m_hierGraphp; }

View File

@ -40,7 +40,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// Changes user() and weight() // Changes user() and weight()
class GraphRemoveRedundant final : GraphAlg<> { class GraphRemoveRedundant final : GraphAlg<> {
const bool m_sumWeights; ///< Sum, rather then maximize weights const bool m_sumWeights; // Sum, rather then maximize weights
private: private:
void main() { void main() {
for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex); for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex);

View File

@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor {
// Find all cells with arrays, and convert to non-arrayed // Find all cells with arrays, and convert to non-arrayed
private: private:
// STATE // STATE
// Range for arrayed instantiations, nullptr for normal instantiations const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells
const AstRange* m_cellRangep = nullptr; int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion
int m_instSelNum = 0; // Current instantiation count 0..N-1
InstDeModVarVisitor m_deModVars; // State of variables for current cell module InstDeModVarVisitor m_deModVars; // State of variables for current cell module
// VISITORS // VISITORS
@ -233,52 +232,72 @@ private:
m_deModVars.main(nodep->modp()); m_deModVars.main(nodep->modp());
// //
if (nodep->rangep()) { 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); AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var);
// cppcheck-suppress constVariablePointer // Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType.
AstNodeDType* const ifaceVarDtp AstIfaceRefDType* origIfaceRefp = nullptr;
= ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; AstUnpackArrayDType* innermostArrp = nullptr;
const bool isIface for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) {
= ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType) if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) {
&& VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), innermostArrp = arrp;
IfaceRefDType) dp = arrp->subDTypep()->skipRefp();
&& !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), } else {
IfaceRefDType) origIfaceRefp = VN_CAST(dp, IfaceRefDType);
->isVirtual(); break;
}
}
const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual();
// Make all of the required clones std::vector<int> idx(ndim, 0);
for (int i = 0; i < m_cellRangep->elementsConst(); i++) { for (int n = 0; n < totalElems; ++n) {
m_instSelNum // Unflatten n into a row-major cartesian index; outer dim most significant.
= m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i; int rem = n;
const int instNum = m_cellRangep->loConst() + i; 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); AstCell* const newp = nodep->cloneTree(false);
nodep->addNextHere(newp); nodep->addNextHere(newp);
// Remove ranging and fix name while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
newp->rangep()->unlinkFrBack()->deleteTree(); newp->name(newp->name() + suffix);
// Somewhat illogically, we need to rename the original name of the cell too. newp->origName(newp->origName() + suffix);
// 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__");
UINFO(8, " CELL loop " << newp); UINFO(8, " CELL loop " << newp);
// If this AstCell is actually an interface instantiation, also clone the IfaceRef // Interface instantiation: also clone the IfaceRef in the parent module.
// within the same parent module as the cell
if (isIface) { if (isIface) {
AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType);
AstIfaceRefDType* const origIfaceRefp
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType);
origIfaceRefp->cellp(nullptr); origIfaceRefp->cellp(nullptr);
AstVar* const varNewp = ifaceVarp->cloneTree(false); AstVar* const varNewp = ifaceVarp->cloneTree(false);
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false); AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
arrdtype->addNextHere(ifaceRefp); innermostArrp->addNextHere(ifaceRefp);
ifaceRefp->cellp(newp); ifaceRefp->cellp(newp);
ifaceRefp->cellName(newp->name()); ifaceRefp->cellName(newp->name());
varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__"); varNewp->name(varNewp->name() + suffix);
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum) varNewp->origName(varNewp->origName() + suffix);
+ "__KET__");
varNewp->dtypep(ifaceRefp); varNewp->dtypep(ifaceRefp);
newp->addNextHere(varNewp); newp->addNextHere(varNewp);
if (debug() == 9) { if (debug() == 9) {
@ -389,6 +408,105 @@ private:
} }
} else { } else {
AstVar* const pinVarp = nodep->modVarp(); 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 const AstUnpackArrayDType* const pinArrp
= VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType); = VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType);
if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return; if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
@ -472,28 +590,50 @@ private:
} }
} }
void visit(AstArraySel* nodep) override { void visit(AstArraySel* nodep) override {
if (const AstUnpackArrayDType* const arrp // If a parent is also an ArraySel into the same iface array, let it handle the chain.
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) { if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return;
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return; // Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index).
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; std::vector<AstArraySel*> sels;
V3Const::constifyParamsEdit(nodep->bitp()); AstNode* curp = nodep;
const AstConst* const constp = VN_CAST(nodep->bitp(), Const); 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) { if (!constp) {
nodep->bitp()->v3warn(E_UNSUPPORTED, asp->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection"); "Non-constant index in RHS interface array selection");
return; return;
} }
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo()); indices[sels.size() - 1 - i] = constp->toSInt();
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);
} }
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 { void visit(AstNodeAssign* nodep) override {
if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) { if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) {

View File

@ -776,12 +776,19 @@ class LinkCellsVisitor final : public VNVisitor {
idtypep->cellp(nodep); // Only set when real parent cell known. idtypep->cellp(nodep); // Only set when real parent cell known.
AstVar* varp; AstVar* varp;
if (nodep->rangep()) { if (nodep->rangep()) {
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst // For arrayed interfaces, we replace cellp when de-arraying in V3Inst.
AstNodeArrayDType* const arrp // Multi-dim arrays wrap one UnpackArrayDType per range, innermost first.
= new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep, std::vector<AstRange*> rangesp;
nodep->rangep()->cloneTree(true)}; 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, varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, arrp}; VFlagChildDType{}, dtp};
} else { } else {
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName, varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
VFlagChildDType{}, idtypep}; VFlagChildDType{}, idtypep};

View File

@ -524,17 +524,19 @@ public:
void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE
m_ifaceVarSyms.push_back(symp); 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) { static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) {
AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType); while (nodep) {
if (!ifacerefp) { if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp;
if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) { 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)) { } 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). // Given a pin expression, resolve it to a live AstIface* (or nullptr).
// Handles both simple VarRef and dotted VarXRef pin connections. // Handles both simple VarRef and dotted VarXRef pin connections.
@ -770,10 +772,16 @@ public:
if (forPrearray()) { if (forPrearray()) {
// GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled, // GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled,
// but presently should be just "foo". // but presently should be just "foo".
// Likewise cell foo__[array] before we've expanded arrays is just foo // Likewise cell foo__[array] before we've expanded arrays is just foo.
if ((pos = ident.rfind("__BRA__")) != string::npos) { // Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them
altIdent = ident.substr(0, pos); // 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 UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname
<< " at se" << lookupSymp); << " at se" << lookupSymp);
@ -5316,10 +5324,18 @@ class LinkDotResolveVisitor final : public VNVisitor {
symIterateNull(nodep->attrp(), m_curSymp); symIterateNull(nodep->attrp(), m_curSymp);
if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) { if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) {
AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack(); AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack();
AstCellArrayRef* const newp if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) {
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp}; // Multi-dim iface array access: append this select to the existing chain
nodep->replaceWith(newp); fromArrRefp->addSelp(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep); 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 { void visit(AstNodePreSel* nodep) override {

View File

@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor {
m_varp->attrSplitVar(true); m_varp->attrSplitVar(true);
} }
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); 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) { } else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrScBigUint(true); m_varp->attrScBigUint(true);

View File

@ -1352,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", CbOnOff, [this](bool flag) { coverage(flag); });
DECL_OPTION("-coverage-expr", OnOff, &m_coverageExpr); DECL_OPTION("-coverage-expr", OnOff, &m_coverageExpr);
DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax); 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-line", OnOff, &m_coverageLine);
DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth); DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth);
DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle); DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle);
@ -2295,7 +2296,10 @@ void V3Options::setDebugMode(int level) {
if (!m_dumpLevel.count("tree")) m_dumpLevel["tree"] = 3; // Don't override if already set. if (!m_dumpLevel.count("tree")) m_dumpLevel["tree"] = 3; // Don't override if already set.
m_stats = true; m_stats = true;
m_debugCheck = 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 { unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE {

View File

@ -226,6 +226,7 @@ private:
bool m_build = false; // main switch: --build bool m_build = false; // main switch: --build
bool m_context = true; // main switch: --Wcontext bool m_context = true; // main switch: --Wcontext
bool m_coverageExpr = false; // main switch: --coverage-expr 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_coverageLine = false; // main switch: --coverage-block
bool m_coverageToggle = false; // main switch: --coverage-toggle bool m_coverageToggle = false; // main switch: --coverage-toggle
bool m_coverageUnderscore = false; // main switch: --coverage-underscore bool m_coverageUnderscore = false; // main switch: --coverage-underscore
@ -447,7 +448,7 @@ private:
void optimize(int level); void optimize(int level);
void showVersion(bool verbose); void showVersion(bool verbose);
void coverage(bool flag) { 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 bool suffixed(const string& sw, const char* arg);
static string parseFileArg(const string& optdir, const string& relfilename); static string parseFileArg(const string& optdir, const string& relfilename);
@ -508,9 +509,19 @@ public:
void buildDepBin(const string& flag) { m_buildDepBin = flag; } void buildDepBin(const string& flag) { m_buildDepBin = flag; }
bool context() const VL_MT_SAFE { return m_context; } bool context() const VL_MT_SAFE { return m_context; }
bool coverage() const VL_MT_SAFE { 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; return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser;
} }
bool coverageExpr() const { return m_coverageExpr; } bool coverageExpr() const { return m_coverageExpr; }
bool coverageFsm() const { return m_coverageFsm; }
bool coverageLine() const { return m_coverageLine; } bool coverageLine() const { return m_coverageLine; }
bool coverageToggle() const { return m_coverageToggle; } bool coverageToggle() const { return m_coverageToggle; }
bool coverageUnderscore() const { return m_coverageUnderscore; } bool coverageUnderscore() const { return m_coverageUnderscore; }
@ -534,6 +545,9 @@ public:
bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; }
bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dpiHdrOnly() const { return m_dpiHdrOnly; }
bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } 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 { bool dumpTreeDot() const {
return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot"); return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot");
} }

View File

@ -475,6 +475,12 @@ class ParamProcessor final {
} }
return nullptr; 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) { static bool isString(AstNodeDType* nodep) {
if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType)) if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType))
return basicp->isString(); return basicp->isString();
@ -1406,13 +1412,16 @@ class ParamProcessor final {
} }
cloneVarp->valuep(exprp->cloneTree(false)); cloneVarp->valuep(exprp->cloneTree(false));
if (AstNodeDType* const origDTypep = modvarp->subDTypep()) { if (AstNodeDType* const origDTypep = modvarp->subDTypep()) {
AstNodeDType* const dtypeClonep = origDTypep->cloneTree(false); // Attach clone under cloneVarp so the root has a back pointer.
// Inline every param ref so widthing doesn't reach back into the template if (cloneVarp->childDTypep())
// (#7411). Cycle detector for dependent parameters in the same module. 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; constexpr int maxSubstIters = 1000;
for (int it = 0; it < maxSubstIters; ++it) { for (int it = 0; it < maxSubstIters; ++it) {
bool any = false; bool any = false;
dtypeClonep->foreach([&](AstVarRef* varrefp) { cloneVarp->foreach([&](AstVarRef* varrefp) {
AstVar* const targetp = varrefp->varp(); AstVar* const targetp = varrefp->varp();
AstNode* replacep = nullptr; AstNode* replacep = nullptr;
for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) { for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) {
@ -1432,19 +1441,40 @@ class ParamProcessor final {
any = true; any = true;
} }
}); });
// Substitute RefDType to an overridden paramtype. RefDType is
// not a foreach leaf, so collect matches and replace after the
// walk. Reverse order so descendants are replaced before
// ancestors -- replacing an ancestor would free its descendants.
std::vector<std::pair<AstRefDType*, AstNodeDType*>> toReplace;
cloneVarp->foreach([&](AstRefDType* refp) {
AstParamTypeDType* const ptdp
= VN_CAST(refp->refDTypep(), ParamTypeDType);
if (!ptdp) return;
for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) {
if (pp->modPTypep() == ptdp) {
if (AstNodeDType* const overDtp
= VN_CAST(pp->exprp(), NodeDType)) {
toReplace.emplace_back(refp, overDtp);
}
break;
}
}
});
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; if (!any) break;
} }
// Bail if anything still points at the template. // Bail if anything still points at the template.
dtypeClonep->foreach([&](AstVarRef* varrefp) { cloneVarp->foreach([&](AstVarRef* varrefp) {
varrefp->v3fatalSrc( varrefp->v3fatalSrc(
"Unresolved VarRef '" "Unresolved VarRef '"
<< varrefp->prettyName() << "' in pin dtype clone. Pin: " << varrefp->prettyName() << "' in pin dtype clone. Pin: "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); << pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
}); });
if (cloneVarp->childDTypep())
cloneVarp->childDTypep()->unlinkFrBack()->deleteTree();
cloneVarp->childDTypep(dtypeClonep);
cloneVarp->dtypep(nullptr);
} }
V3Const::constifyParamsEdit(cloneVarp); V3Const::constifyParamsEdit(cloneVarp);
if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) { if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) {
@ -1589,38 +1619,32 @@ class ParamProcessor final {
const AstVar* const modvarp = pinp->modVarp(); const AstVar* const modvarp = pinp->modVarp();
if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue; if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue;
if (modvarp->isIfaceRef()) { if (modvarp->isIfaceRef()) {
AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType); // arraySubDTypeDeepp returns input unchanged if not an array.
if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) { AstIfaceRefDType* const portIrefp
portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType); = VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType);
}
AstIfaceRefDType* pinIrefp = nullptr;
const AstNode* const exprp = pinp->exprp(); const AstNode* const exprp = pinp->exprp();
const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef)) if (VN_IS(exprp, CellArrayRef)) {
? VN_AS(exprp, NodeVarRef)->varp() // CellArrayRef not yet linked; V3LinkDot resolves this pin later.
: 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.
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp); UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp);
continue; 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); UINFO(9, " portIfaceRef " << portIrefp);
@ -2813,33 +2837,46 @@ class ParamVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep); VL_DO_DANGLING(pushDeletep(nodep), nodep);
} }
void visit(AstCellArrayRef* nodep) override { void visit(AstCellArrayRef* nodep) override {
V3Const::constifyParamsEdit(nodep->selp()); // Multi-dim iface arrays chain multiple selps via nextp(); constify each in place.
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) { for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) {
const string index = AstNode::encodeNumber(constp->toSInt()); nxt = s->nextp(); // cache before constifyParamsEdit replaces s
// For nested interface array ports, the node name may have a __Viftop suffix V3Const::constifyParamsEdit(s);
// that doesn't exist in the original unlinked text. Try without the suffix. }
const string viftopSuffix = "__Viftop"; std::vector<int> indices;
const string baseName for (AstNode* s = nodep->selp(); s; s = s->nextp()) {
= VString::endsWith(nodep->name(), viftopSuffix) const AstConst* const constp = VN_CAST(s, Const);
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size()) if (!constp) {
: nodep->name(); nodep->v3error("Could not expand constant selection inside dotted reference: "
const string replacestr = baseName + "__BRA__??__KET__"; << s->prettyNameQ());
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);
return; return;
} }
m_unlinkedTxt.replace(pos, replacestr.length(), indices.push_back(constp->toSInt());
baseName + "__BRA__" + index + "__KET__"); }
} else { // Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt.
nodep->v3error("Could not expand constant selection inside dotted reference: " const string viftopSuffix = "__Viftop";
<< nodep->selp()->prettyNameQ()); 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; 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 // Generate Statements

View File

@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n
return assignp; return assignp;
} }
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list // Remove any UnsizedRange's from list; preserves multi-dim chain via nextp().
for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) { for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) {
nextp = VN_AS(nodep->nextp(), NodeRange); nextp = VN_AS(nodep->nextp(), NodeRange);
if (!VN_IS(nodep, Range)) { if (!VN_IS(nodep, Range)) {
@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
VL_DO_DANGLING(nodep->deleteTree(), nodep); 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); 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 { AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED {
// SEL(PARSELVALUE, ...) -> SEL(fromp, ...) // SEL(PARSELVALUE, ...) -> SEL(fromp, ...)

View File

@ -71,6 +71,7 @@ public:
return v3Global.opt.trace() && m_tracingParse && fl->tracingOn(); return v3Global.opt.trace() && m_tracingParse && fl->tracingOn();
} }
AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED; AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED;
AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED;
AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED; AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED;
AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep, AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep,
bool isPacked) VL_MT_DISABLED; bool isPacked) VL_MT_DISABLED;
@ -90,7 +91,7 @@ public:
singletonp()->m_instModule, singletonp()->m_instModule,
pinlistp, pinlistp,
(singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr), (singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr),
singletonp()->scrubRange(rangelistp)}; singletonp()->scrubRangeMulti(rangelistp)};
nodep->trace(singletonp()->allTracingOn(fileline)); nodep->trace(singletonp()->allTracingOn(fileline));
return nodep; return nodep;
} }

View File

@ -263,6 +263,10 @@ class PremitVisitor final : public VNVisitor {
iterateChildren(nodep); iterateChildren(nodep);
checkNode(nodep); checkNode(nodep);
} }
void visit(AstCMethodHard* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCvtArrayToPacked* nodep) override { void visit(AstCvtArrayToPacked* nodep) override {
iterateChildren(nodep); iterateChildren(nodep);
checkNode(nodep); checkNode(nodep);

View File

@ -221,7 +221,7 @@ class ProtectVisitor final : public VNVisitor {
+ "_protectlib_final(chandle handle__V);\n\n"); + "_protectlib_final(chandle handle__V);\n\n");
// Local variables // 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 // Likewise other internals aren't interesting to the user
txtp->add("// verilator tracing_off\n"); txtp->add("// verilator tracing_off\n");

View File

@ -2760,7 +2760,9 @@ class CaptureVisitor final : public VNVisitor {
m_ignore.emplace(thisRefp); m_ignore.emplace(thisRefp);
AstMemberSel* const memberSelp AstMemberSel* const memberSelp
= new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()}; = 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); memberSelp->user2p(m_targetp);
nodep->replaceWith(memberSelp); nodep->replaceWith(memberSelp);
VL_DO_DANGLING(pushDeletep(nodep), nodep); VL_DO_DANGLING(pushDeletep(nodep), nodep);

View File

@ -47,6 +47,7 @@
#include "V3UniqueNames.h" #include "V3UniqueNames.h"
#include <limits> #include <limits>
#include <map>
#include <set> #include <set>
#include <unordered_map> #include <unordered_map>
@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor {
using ActCodeSet = std::set<uint32_t>; using ActCodeSet = std::set<uint32_t>;
// For activity set, what traces apply // For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>; 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 // METHODS
@ -1125,6 +1129,13 @@ class TraceVisitor final : public VNVisitor {
if (nodep->isTop()) m_topModp = nodep; if (nodep->isTop()) m_topModp = nodep;
iterateChildren(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 { void visit(AstStmtExpr* nodep) override {
if (!m_finding && !nodep->user2()) { if (!m_finding && !nodep->user2()) {
if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) { if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) {
@ -1214,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); } void visit(AstNode* nodep) override { iterateChildren(nodep); }

View File

@ -55,6 +55,7 @@
#include "V3File.h" #include "V3File.h"
#include "V3Force.h" #include "V3Force.h"
#include "V3Fork.h" #include "V3Fork.h"
#include "V3FsmDetect.h"
#include "V3FuncOpt.h" #include "V3FuncOpt.h"
#include "V3Gate.h" #include "V3Gate.h"
#include "V3Global.h" #include "V3Global.h"
@ -232,9 +233,11 @@ static void process() {
v3Global.vlExit(0); v3Global.vlExit(0);
} }
// Coverage insertion // Insert generic non-FSM coverage before dead code elimination and
// Before we do dead code elimination and inlining, or we'll lose it. // inlining, or those opportunities may be optimized away. FSM
if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp()); // 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 // Resolve randsequence if they are used by the design
if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp()); if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp());
@ -347,6 +350,12 @@ static void process() {
// No more AstAlias after linkDotScope // No more AstAlias after linkDotScope
V3Scope::scopeAll(v3Global.rootp()); V3Scope::scopeAll(v3Global.rootp());
V3LinkDot::linkDotScope(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) // Relocate classes (after linkDot)
V3Class::classAll(v3Global.rootp()); V3Class::classAll(v3Global.rootp());
@ -428,8 +437,9 @@ static void process() {
"This may cause ordering problems."); "This may cause ordering problems.");
} }
// Combine COVERINCs with duplicate terms // Combine generic COVERINCs with duplicate terms. FSM coverage is
if (v3Global.opt.coverage()) V3CoverageJoin::coverageJoin(v3Global.rootp()); // already lowered separately inside V3FsmDetect.
if (v3Global.opt.coverageNonFsm()) V3CoverageJoin::coverageJoin(v3Global.rootp());
// Remove unused vars // Remove unused vars
V3Const::constifyAll(v3Global.rootp()); V3Const::constifyAll(v3Global.rootp());
@ -447,7 +457,7 @@ static void process() {
} }
// Create delayed assignments // 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()); V3Delayed::delayedAll(v3Global.rootp());
// Make Active's on the top level. // Make Active's on the top level.

View File

@ -69,6 +69,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); }); DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); });
DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); }); DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); });
DECL_OPTION("-filter-type", Set, &m_filterType); DECL_OPTION("-filter-type", Set, &m_filterType);
DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs);
DECL_OPTION("-rank", OnOff, &m_rank); DECL_OPTION("-rank", OnOff, &m_rank);
DECL_OPTION("-unlink", OnOff, &m_unlink); DECL_OPTION("-unlink", OnOff, &m_unlink);
DECL_OPTION("-V", CbCall, []() { DECL_OPTION("-V", CbCall, []() {
@ -140,6 +141,10 @@ int main(int argc, char** argv) {
top.points().dump(); top.points().dump();
} }
if (!top.opt.rank() && top.opt.writeFile().empty() && top.opt.writeInfoFile().empty()) {
top.printTypeSummary();
}
V3Error::abortIfWarnings(); V3Error::abortIfWarnings();
if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut()); if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut());

View File

@ -39,6 +39,7 @@ class VlcOptions final {
bool m_annotateAll = false; // main switch: --annotate-all bool m_annotateAll = false; // main switch: --annotate-all
int m_annotateMin = 10; // main switch: --annotate-min I<count> int m_annotateMin = 10; // main switch: --annotate-min I<count>
bool m_annotatePoints = false; // main switch: --annotate-points bool m_annotatePoints = false; // main switch: --annotate-points
bool m_includeResetArcs = false; // main switch: --include-reset-arcs
string m_filterType = "*"; // main switch: --filter-type string m_filterType = "*"; // main switch: --filter-type
VlStringSet m_readFiles; // main switch: --read VlStringSet m_readFiles; // main switch: --read
bool m_rank = false; // main switch: --rank bool m_rank = false; // main switch: --rank
@ -67,6 +68,7 @@ public:
int annotateMin() const { return m_annotateMin; } int annotateMin() const { return m_annotateMin; }
bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); } bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); }
bool annotatePoints() const { return m_annotatePoints; } bool annotatePoints() const { return m_annotatePoints; }
bool includeResetArcs() const { return m_includeResetArcs; }
bool rank() const { return m_rank; } bool rank() const { return m_rank; }
bool unlink() const { return m_unlink; } bool unlink() const { return m_unlink; }
string writeFile() const { return m_writeFile; } string writeFile() const { return m_writeFile; }

View File

@ -70,6 +70,18 @@ public:
return keyExtract(VL_CIK_THRESH, m_name.c_str()); return keyExtract(VL_CIK_THRESH, m_name.c_str());
} }
string linescov() const { return keyExtract(VL_CIK_LINESCOV, 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 { int lineno() const {
const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str()); const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str());
return std::atoi(lineStr.c_str()); return std::atoi(lineStr.c_str());

View File

@ -25,6 +25,8 @@
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
#include <map>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) {
int branchesHit = 0; int branchesHit = 0;
for (auto& li : lines) { for (auto& li : lines) {
VlcSourceCount& sc = li.second; VlcSourceCount& sc = li.second;
os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n"; uint64_t daCount = 0;
const int num_branches = sc.points().size(); std::vector<const VlcPoint*> infoPoints;
if (num_branches == 1) continue;
branchesFound += num_branches;
int point_num = 0;
for (const auto& point : sc.points()) { 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 << "BRDA:" << sc.lineno() << ",";
os << "0,"; os << "0,";
os << point_num << ","; os << point_num << ",";
@ -269,8 +277,10 @@ void VlcTop::annotateCalcNeeded() {
} }
} }
const float pct = totCases ? (100 * totOk / totCases) : 0; const float pct = totCases ? (100 * totOk / totCases) : 0;
std::cout << "Total coverage (" << totOk << "/" << totCases << ") "; std::cout << "Annotation Summary:\n";
std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\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'; 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()) { if (opt.annotatePoints()) {
for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin()); 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,47 @@ void VlcTop::annotate(const string& dirname) {
annotateCalcNeeded(); annotateCalcNeeded();
annotateOutputFiles(dirname); annotateOutputFiles(dirname);
} }
void VlcTop::printTypeSummary() {
static const std::vector<std::string> orderedTypes = {"line", "toggle", "branch", "expr"};
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 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);
if (it == tally.end()) continue;
printed.insert(type);
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) << 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";
}
}

View File

@ -55,6 +55,7 @@ public:
// METHODS // METHODS
void annotate(const string& dirname); void annotate(const string& dirname);
void printTypeSummary();
void readCoverage(const string& filename, bool nonfatal = false); void readCoverage(const string& filename, bool nonfatal = false);
void writeCoverage(const string& filename); void writeCoverage(const string& filename);
void writeInfo(const string& filename); void writeInfo(const string& filename);

View File

@ -31,6 +31,7 @@ class Node:
self._arity = -1 # Arity of node self._arity = -1 # Arity of node
self._ops = {} # Operands of node self._ops = {} # Operands of node
self._ptrs = [] # Pointer members of node (name, types) self._ptrs = [] # Pointer members of node (name, types)
self._makeDfgVertex = False # Create a corresponding DfgVertex sub-class
@property @property
def name(self): def name(self):
@ -68,6 +69,10 @@ class Node:
assert self.isCompleted assert self.isCompleted
return self._ptrs return self._ptrs
@property
def makeDfgVertex(self):
return self._makeDfgVertex
# Pre completion methods # Pre completion methods
def addSubClass(self, subClass): def addSubClass(self, subClass):
assert not self.isCompleted assert not self.isCompleted
@ -91,6 +96,9 @@ class Node:
name = re.sub(r'^m_', '', name) name = re.sub(r'^m_', '', name)
self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals}) self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals})
def setMakeDfgVertex(self):
self._makeDfgVertex = True
# Computes derived properties over entire class hierarchy. # Computes derived properties over entire class hierarchy.
# No more changes to the hierarchy are allowed once this was called # No more changes to the hierarchy are allowed once this was called
def complete(self, typeId=0, ordIdx=0): def complete(self, typeId=0, ordIdx=0):
@ -609,9 +617,12 @@ def read_types(filename, Nodes, prefix):
if prefix != "Ast": if prefix != "Ast":
continue continue
match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line) if match := re.match(r'^\s*//\s*@astgen\s+(.*)$', line):
if match:
decl = re.sub(r'//.*$', '', match.group(1)) decl = re.sub(r'//.*$', '', match.group(1))
if decl == "makeDfgVertex":
node.setMakeDfgVertex()
continue
what, sep, rest = partitionAndStrip(decl, ":=") what, sep, rest = partitionAndStrip(decl, ":=")
what = re.sub(r'\s+', ' ', what) what = re.sub(r'\s+', ' ', what)
if not sep: if not sep:
@ -668,10 +679,12 @@ def read_types(filename, Nodes, prefix):
error( error(
lineno, "Malformed @astgen what (expecting 'op1'..'op4'," + lineno, "Malformed @astgen what (expecting 'op1'..'op4'," +
" 'alias op1'.., 'ptr'): " + what) " 'alias op1'.., 'ptr'): " + what)
else: continue
line = re.sub(r'//.*$', '', line)
if re.match(r'.*[Oo]p[1-9].*', line): line = re.sub(r'//.*$', '', line)
error(lineno, "Use generated accessors to access op<N> operands")
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): 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) 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. # These are standalone so we don't need to parse the sources for this.
DfgVertices["Vertex"] = Node("Vertex", None) 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 # Read DfgVertex definitions
for filename in Args.dfgdef: for filename in Args.dfgdef:
read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg") read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg")
@ -1527,12 +1430,8 @@ for node in AstNodeList:
if not node.isLeaf: if not node.isLeaf:
continue continue
# Ignore any explicitly defined vertex # Only create vertices for explcitly marked AstNode sub-types
if node.name in DfgVertices: if not node.makeDfgVertex:
continue
# Ignore expressions types that DFG cannot handle
if node.name in DfgIgnored:
continue continue
if node.isSubClassOf(AstNodes["NodeUniop"]): if node.isSubClassOf(AstNodes["NodeUniop"]):
@ -1542,7 +1441,9 @@ for node in AstNodeList:
elif node.isSubClassOf(AstNodes["NodeTriop"]): elif node.isSubClassOf(AstNodes["NodeTriop"]):
base = DfgVertices["VertexTernary"] base = DfgVertices["VertexTernary"]
else: else:
continue sys.exit(
"%Error: cannot create DfgVertex for node not derived from AstNodeUnary, AstNodeBinary, or AstNodeTernary: "
+ node.name)
vertex = Node(node.name, base) vertex = Node(node.name, base)
DfgVertices[node.name] = vertex DfgVertices[node.name] = vertex

View File

@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; } "/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; }
"/*verilator sformat*/" { FL; return yVL_SFORMAT; } "/*verilator sformat*/" { FL; return yVL_SFORMAT; }
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; } "/*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)); "/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
return yVL_TAG; } return yVL_TAG; }
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; } "/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }

View File

@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVL_SC_BV "/*verilator sc_bv*/" %token<fl> yVL_SC_BV "/*verilator sc_bv*/"
%token<fl> yVL_SFORMAT "/*verilator sformat*/" %token<fl> yVL_SFORMAT "/*verilator sformat*/"
%token<fl> yVL_SPLIT_VAR "/*verilator split_var*/" %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<strp> yVL_TAG "/*verilator tag*/"
%token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/" %token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/"
%token<fl> yVL_UNROLL_FULL "/*verilator unroll_full*/" %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_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
| yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; } | 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}] rangeListE<nodeRangep>: // IEEE: [{packed_dimension}]

View File

@ -7,7 +7,6 @@ import collections
import ctypes import ctypes
import glob import glob
import hashlib import hashlib
import json
import logging import logging
import multiprocessing import multiprocessing
import os import os
@ -2511,104 +2510,19 @@ class VlTest:
print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr) print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr)
shutil.copy(fn1, fn2) shutil.copy(fn1, fn2)
def vcd_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None: def vcd_identical(self, fn1: str, fn2: str) -> None:
"""Test if two VCD files have logically-identical contents""" """Test if two VCD/FST files have logically-identical contents"""
# vcddiff to check transitions, if installed cmd = VtOs.getenv_def('WAVEDIFF', 'wavediff') + ' --epsilon 0.0000001 ' + fn1 + ' ' + fn2
cmd = "vcddiff --help" proc = subprocess.run([cmd], capture_output=True, text=True, shell=True, check=False)
out = test.run_capture(cmd, check=True) if proc.returncode:
cmd = 'vcddiff ' + fn1 + ' ' + fn2 print(proc.stderr)
out = test.run_capture(cmd, check=True) print(proc.stdout)
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:
self.copy_if_golden(fn1, fn2) self.copy_if_golden(fn1, fn2)
self.error("VCD hier miscompares " + fn1 + " " + fn2 + "\nGOT=" + a + "\nEXP=" + b + self.error("VCD miscompares " + fn1 + " " + fn2)
"\n")
def fst2vcd(self, fn1: str, fn2: str) -> None: def fst_identical(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:
"""Test if two FST files have logically-identical contents""" """Test if two FST files have logically-identical contents"""
if fn1.endswith(".fst"): self.vcd_identical(fn1, fn2)
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)
def saif_identical(self, fn1: str, fn2: str) -> None: def saif_identical(self, fn1: str, fn2: str) -> None:
"""Test if two SAIF files have logically-identical contents""" """Test if two SAIF files have logically-identical contents"""
@ -2621,96 +2535,17 @@ class VlTest:
self.copy_if_golden(fn1, fn2) self.copy_if_golden(fn1, fn2)
self.error("SAIF files miscompare") 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]: match traceFn.rpartition(".")[-1]:
case "vcd": case "vcd":
self.vcd_identical(traceFn, goldenFn, ignore_attr) self.vcd_identical(traceFn, goldenFn)
case "fst": case "fst":
self.fst_identical(traceFn, goldenFn, ignore_attr) self.fst_identical(traceFn, goldenFn)
case "saif": case "saif":
self.saif_identical(traceFn, goldenFn) self.saif_identical(traceFn, goldenFn)
case _: case _:
self.error("Unknown trace file format " + traceFn) 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: def inline_checks(self) -> None:
covfn = self.coverage_filename covfn = self.coverage_filename
contents = self.file_contents(covfn) contents = self.file_contents(covfn)

View File

@ -1,6 +1,6 @@
%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:11:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2) %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' : ... note: In instance 't'
11 | assert property (@(posedge clk) (a ##1 b) [* 2] |-> a); 13 | assert property (@(posedge clk) (a ##1 b) [* 2] |-> a);
| ^~ | ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to %Error: Exiting due to

View File

@ -4,7 +4,9 @@
// SPDX-FileCopyrightText: 2026 PlanV GmbH // SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0 // SPDX-License-Identifier: CC0-1.0
module t (input clk); module t (
input clk
);
logic a, b; logic a, b;
// Unsupported: multi-cycle sequence expression inside consecutive repetition // Unsupported: multi-cycle sequence expression inside consecutive repetition

View File

@ -0,0 +1,65 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage basic test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [1:0] {
S_IDLE = 2'd0,
S_RUN = 2'd1,
S_DONE = 2'd2,
S_ERR = 2'd3
} state_t;
logic rst;
logic start;
integer cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) start <= 1'b1;
if (cyc == 3) start <= 1'b0;
if (cyc == 8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
end else begin
%000004 case (state)
// [FSM coverage]
%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %]
%000004 // [fsm_arc t.state::S_DONE->S_DONE]
%000003 // [fsm_arc t.state::S_IDLE->S_IDLE]
%000001 // [fsm_arc t.state::S_IDLE->S_RUN]
%000001 // [fsm_arc t.state::S_RUN->S_DONE]
%000001 // [fsm_state t.state::S_DONE]
%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED ***
%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED ***
%000001 // [fsm_state t.state::S_RUN]
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
S_RUN: state <= S_DONE;
S_DONE: state <= S_DONE;
default: state <= S_ERR;
endcase
end
end
endmodule

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage basic test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,53 @@
// DESCRIPTION: Verilator: FSM coverage basic test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [1:0] {
S_IDLE = 2'd0,
S_RUN = 2'd1,
S_DONE = 2'd2,
S_ERR = 2'd3
} state_t;
logic rst;
logic start;
integer cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) start <= 1'b1;
if (cyc == 3) start <= 1'b0;
if (cyc == 8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
end else begin
case (state)
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
S_RUN: state <= S_DONE;
S_DONE: state <= S_DONE;
default: state <= S_ERR;
endcase
end
end
endmodule

View File

@ -0,0 +1,63 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
//
// 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
module t(
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
logic sel;
int cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
sel = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) sel <= 1'b1;
if (cyc == 3) sel <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
%000003 case (state)
// [FSM coverage]
%000001 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
%000000 // [fsm_arc t.state::S0->S1]
%000003 // [fsm_arc t.state::S0->S2]
%000000 // [fsm_arc t.state::S1->S0]
%000002 // [fsm_state t.state::S0]
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
%000003 // [fsm_state t.state::S2]
S0: if (sel) state <= S1; else state <= S2;
S1: state <= S0;
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else extraction test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the expected file captures both the extracted
# FSM shape and the per-point hit counts.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,53 @@
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
//
// 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
module t(
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
logic sel;
int cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
sel = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) sel <= 1'b1;
if (cyc == 3) sel <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
case (state)
S0: if (sel) state <= S1; else state <= S2;
S1: state <= S0;
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM lowered coverage declaration dump test
#
# 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
from pathlib import Path
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_cover_fsm_styles.v"
# Dump the lowered AST so AstCoverOtherDecl::dump() sees FSM metadata-bearing
# coverage declarations directly. This avoids JSON/schema coupling while still
# covering the dump-side formatting for fv/ff/ft/fg.
test.lint(v_flags=["--coverage-fsm", "--dump-tree"])
tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")]
tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files]
assert any("COVEROTHERDECL" in text and " fv=t.state" in text for text in tree_texts)
assert any(
"COVEROTHERDECL" in text and " ff=ANY" in text and " ft=S0" in text and " fg=reset" in text
for text in tree_texts)
assert any("COVEROTHERDECL" in text and " ff=default" in text and " ft=S0" in text
and " fg=default" in text for text in tree_texts)
test.passes()

View File

@ -0,0 +1,6 @@
%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant not present in the declared enum
27 | S0: state <= 2'd3;
| ^~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM enum transition bad-value test
#
# 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
import vltest_bootstrap
test.scenarios('vlt')
# When an enum-backed FSM assigns a constant that is not one of the declared
# enum items, FSM coverage should warn and skip the unsupported edge rather
# than turning optional coverage into a hard compile failure.
test.lint(verilator_flags2=["--coverage-fsm"], fails=True)
test.file_grep(
test.compile_log_filename,
r'%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage '
r'on enum state transitions that assign a constant not present in the declared enum')
test.passes()

View File

@ -0,0 +1,34 @@
// DESCRIPTION: Verilator: FSM enum transition rejects unknown constant values
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk,
input logic rst
);
typedef enum logic [1:0] {
S0, S1
} state_t;
state_t state;
// FSM coverage should reject a constant next-state value that is not one of
// the declared enum items. This keeps graph construction aligned with the
// enum-backed state set instead of silently dropping the transition.
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
case (state)
/* verilator lint_off ENUMVALUE */
S0: state <= 2'd3;
/* verilator lint_on ENUMVALUE */
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,6 @@
%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: FSM coverage on enum-typed state variables wider than 32 bits
25 | case (state)
| ^~~~
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM enum width limit test
#
# 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
import vltest_bootstrap
test.scenarios('vlt')
# FSM coverage currently stores recovered enum state values in the detector's
# 32-bit internal representation, so wider enum-backed FSMs are rejected.
test.lint(verilator_flags2=["--coverage-fsm"], fails=True)
test.file_grep(
test.compile_log_filename,
r'%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: '
r'FSM coverage on enum-typed state variables wider than 32 bits')
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: FSM enum width limit rejects >32-bit enums
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk,
input logic rst
);
typedef enum logic [32:0] {
S0 = 33'd0,
S1 = 33'd1
} state_t;
state_t state;
// FSM coverage currently supports enum-backed state variables only up to
// 32 bits wide, so this wider enum should be rejected at FSM detection time.
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
case (state)
S0: state <= S1;
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm
#
# 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
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-line'])
test.execute()
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_state")
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_arc")
test.passes()

View File

@ -0,0 +1,53 @@
// DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [1:0] {
S_IDLE = 2'd0,
S_RUN = 2'd1,
S_DONE = 2'd2,
S_ERR = 2'd3
} state_t;
logic rst;
logic start;
integer cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) start <= 1'b1;
if (cyc == 3) start <= 1'b0;
if (cyc == 8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
end else begin
case (state)
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
S_RUN: state <= S_DONE;
S_DONE: state <= S_DONE;
default: state <= S_ERR;
endcase
end
end
endmodule

View File

@ -0,0 +1,51 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
integer cyc;
logic rst;
logic [1:0] state /*verilator fsm_state*/;
initial begin
cyc = 0;
rst = 1'b1;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= 2'd0;
end else begin
%000002 case (state)
// [FSM coverage]
%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %]
%000002 // [fsm_arc t.state::S0->S1]
%000002 // [fsm_arc t.state::S1->S2]
%000001 // [fsm_state t.state::S0]
%000002 // [fsm_state t.state::S1]
%000002 // [fsm_state t.state::S2]
%000000 // [fsm_state t.state::S3] *** UNCOVERED ***
2'd0: state <= 2'd1;
2'd1: state <= 2'd2;
default: state <= 2'd0;
endcase
end
end
endmodule

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage forced non-enum test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source golden output so hit-count regressions are visible in the
# expected file instead of being hidden behind coarse coverage.dat greps.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,41 @@
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
integer cyc;
logic rst;
logic [1:0] state /*verilator fsm_state*/;
initial begin
cyc = 0;
rst = 1'b1;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= 2'd0;
end else begin
case (state)
2'd0: state <= 2'd1;
2'd1: state <= 2'd2;
default: state <= 2'd0;
endcase
end
end
endmodule

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage graph dump test
#
# 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
import vltest_bootstrap
test.scenarios('vltmt')
test.top_filename = "t/t_cover_fsm_styles.v"
test.compile(v_flags2=["--coverage-fsm", "--dumpi-graph", "6"], threads=2)
dot_files = test.glob_some(test.obj_dir + "/*fsm_*.dot")
for dot_filename in dot_files:
test.file_grep(dot_filename, r'digraph v3graph')
test.file_grep_any(dot_files, r'ANY')
test.file_grep_any(dot_files, r'default')
test.passes()

View File

@ -0,0 +1,64 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage negative extraction test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
int cyc;
logic side;
state_t state /*verilator fsm_reset_arc*/;
initial begin
cyc = 0;
side = 1'b0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) side <= 1'b1;
if (cyc == 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// The S0 arm is the supported baseline. The S1 and default arms are
// deliberately unsupported extractor shapes: one has two meaningful
// statements, the other writes a different lhs first. Coverage should ignore
// those arcs rather than guessing.
always_ff @(posedge clk) begin
if (cyc == 0) begin
state <= S0;
end else begin
%000002 case (state)
// [FSM coverage]
%000002 // [fsm_arc t.state::S0->S1]
%000001 // [fsm_state t.state::S0]
%000002 // [fsm_state t.state::S1]
%000002 // [fsm_state t.state::S2]
S0: state <= S1;
S1: begin
side <= ~side;
state <= S2;
end
default: begin
side <= 1'b0;
state <= S0;
end
endcase
end
end
endmodule

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage negative extraction test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
# This test is intentionally "half supported": one case item is a simple
# direct state assignment, while the others use shapes the extractor should
# ignore (multiple meaningful statements or assignment to a non-state lhs).
# That lets us hit the conservative negative branches in directStateAssign()
# and singleMeaningfulStmt() without changing user-visible behavior.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the golden locks down which candidate arcs
# survive extraction and which unsupported shapes are intentionally skipped.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,57 @@
// DESCRIPTION: Verilator: FSM coverage negative extraction test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
int cyc;
logic side;
state_t state /*verilator fsm_reset_arc*/;
initial begin
cyc = 0;
side = 1'b0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) side <= 1'b1;
if (cyc == 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// The S0 arm is the supported baseline. The S1 and default arms are
// deliberately unsupported extractor shapes: one has two meaningful
// statements, the other writes a different lhs first. Coverage should ignore
// those arcs rather than guessing.
always_ff @(posedge clk) begin
if (cyc == 0) begin
state <= S0;
end else begin
case (state)
S0: state <= S1;
S1: begin
side <= ~side;
state <= S2;
end
default: begin
side <= 1'b0;
state <= S0;
end
endcase
end
end
endmodule

View File

@ -0,0 +1,47 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage no-reset lowering test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk
);
typedef enum logic [0:0] {
S0 = 1'b0,
S1 = 1'b1
} state_t;
int cyc;
state_t state;
initial begin
cyc = 0;
state = S0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 4) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// No reset branch on purpose: this keeps the test focused on the branch in
// lowering that skips reset reconstruction entirely.
always_ff @(posedge clk) begin
%000003 case (state)
// [FSM coverage]
%000003 // [fsm_arc t.state::S0->S1]
%000002 // [fsm_state t.state::S0]
%000003 // [fsm_state t.state::S1]
S0: state <= S1;
default: state <= S0;
endcase
end
endmodule

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage no-reset lowering test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
# This test deliberately uses a clocked FSM with no outer reset branch. It
# keeps coverage extraction in the supported subset, but forces lowering down
# the "hasResetCond() == false" path so we validate the no-reset machinery
# rather than only reset-wrapped FSMs.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the expected file captures the no-reset shape
# directly, including the absence of reset pseudo-arcs.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,41 @@
// DESCRIPTION: Verilator: FSM coverage no-reset lowering test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input logic clk
);
typedef enum logic [0:0] {
S0 = 1'b0,
S1 = 1'b1
} state_t;
int cyc;
state_t state;
initial begin
cyc = 0;
state = S0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 4) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// No reset branch on purpose: this keeps the test focused on the branch in
// lowering that skips reset reconstruction entirely.
always_ff @(posedge clk) begin
case (state)
S0: state <= S1;
default: state <= S0;
endcase
end
endmodule

View File

@ -0,0 +1,63 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage reset policy test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
%000006 input clk
);
typedef enum logic [0:0] {
S0 = 1'b0,
S1 = 1'b1
} state_t;
%000001 logic rst;
integer cyc;
%000001 state_t state_incl /*verilator fsm_reset_arc*/;
%000001 state_t state_excl;
%000001 initial begin
%000001 rst = 1'b1;
%000001 cyc = 0;
end
%000006 always @(posedge clk) begin
%000006 cyc <= cyc + 1;
%000005 if (cyc == 1) rst <= 1'b0;
%000005 if (cyc == 5) begin
%000001 $write("*-* All Finished *-*\n");
%000001 $finish;
end
end
%000006 always_ff @(posedge clk) begin
%000004 if (rst) state_incl <= S0;
%000004 else case (state_incl)
// [FSM coverage]
%000001 // [fsm_arc t.state_incl::ANY->S0[reset_include]] [reset arc, excluded from %]
%000001 // [fsm_arc t.state_incl::S0->S1]
%000000 // [fsm_state t.state_incl::S0] *** UNCOVERED ***
%000001 // [fsm_state t.state_incl::S1]
%000001 S0: state_incl <= S1;
%000003 default: state_incl <= S1;
endcase
end
%000006 always_ff @(posedge clk) begin
%000004 if (rst) state_excl <= S0;
%000004 else case (state_excl)
// [FSM coverage]
%000001 // [fsm_arc t.state_excl::ANY->S0[reset]] [reset arc, excluded from %]
%000001 // [fsm_arc t.state_excl::S0->S1]
%000000 // [fsm_state t.state_excl::S0] *** UNCOVERED ***
%000001 // [fsm_state t.state_excl::S1]
%000001 S0: state_excl <= S1;
%000003 default: state_excl <= S1;
endcase
end
endmodule

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage reset policy test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage'])
test.execute()
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--include-reset-arcs",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,51 @@
// DESCRIPTION: Verilator: FSM coverage reset policy test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [0:0] {
S0 = 1'b0,
S1 = 1'b1
} state_t;
logic rst;
integer cyc;
state_t state_incl /*verilator fsm_reset_arc*/;
state_t state_excl;
initial begin
rst = 1'b1;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) state_incl <= S0;
else case (state_incl)
S0: state_incl <= S1;
default: state_incl <= S1;
endcase
end
always_ff @(posedge clk) begin
if (rst) state_excl <= S0;
else case (state_excl)
S0: state_excl <= S1;
default: state_excl <= S1;
endcase
end
endmodule

View File

@ -0,0 +1,62 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
integer cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// This reset block is intentionally non-idiomatic. The detector only collects
// reset arcs from top-level direct assignments in the reset branch, so two
// sequential assignments are the narrowest way to force multiple reset arcs
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
state <= S1;
end else begin
%000001 case (state)
// [FSM coverage]
%000000 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
%000001 // [fsm_arc t.state::ANY->S1[reset_include]] [reset arc, excluded from %]
%000000 // [fsm_arc t.state::S0->S2]
%000001 // [fsm_arc t.state::S1->S2]
%000000 // [fsm_state t.state::S0] *** UNCOVERED ***
%000001 // [fsm_state t.state::S1]
%000001 // [fsm_state t.state::S2]
S0: state <= S2;
S1: state <= S2;
default: state <= S2;
endcase
end
end
endmodule

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
#
# 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
import os
import vltest_bootstrap
test.scenarios('simulator')
# This regression is aimed at the graph helper, not at recommending RTL style.
# We deliberately create two reset arcs in a single FSM so graph construction
# has to reuse the synthetic ANY reset pseudo-vertex rather than allocating it
# only once for a one-arc machine.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the golden proves both reset arcs remain
# visible and share the same synthetic ANY reset source.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,52 @@
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
integer cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 5) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
// This reset block is intentionally non-idiomatic. The detector only collects
// reset arcs from top-level direct assignments in the reset branch, so two
// sequential assignments are the narrowest way to force multiple reset arcs
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
state <= S1;
end else begin
case (state)
S0: state <= S2;
S1: state <= S2;
default: state <= S2;
endcase
end
end
endmodule

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