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