Merge branch 'master' into fix/sva-within
This commit is contained in:
commit
f831ea1962
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
ls -lsha
|
||||
tree -L 3 pages
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: pages
|
||||
|
||||
|
|
|
|||
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 error message when variable used as data type (#7318). [Ryszard Rozak, Antmicro Ltd.]
|
||||
* Improve E_UNSUPPORTED warning messages (#7329). [Eunseo Song]
|
||||
* Improve NFA-based multi-cycle SVA evaluation engine (#7430). [Yilou Wang]
|
||||
* Change array tracing to dump left index to right index (#7205). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Change `--converge-limit` default to 10000 (#7209).
|
||||
* 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 size of trace declaration object code (#7150). [Szymon Gizler, Antmicro Ltd.]
|
||||
* 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 read references in DFG (#7354). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize DFG only once, after scoping (#7362). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize more DFG peephole patterns (#7423). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize more DFG peephole patterns (#7423) (#7452). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize DfgBreakCycles IndependentBits analysis ordering (#7446). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize select patterns in DfgPeephole. [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize temporary insertion in DfgPeephole. [Geza Lore, Testorrent USA, Inc.]
|
||||
* Optimize arithmetic right shift (>>>) in DfgBreakCycles (#7447). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Fix recursive default assignment for sub-arrays (#4589) (#7202). [Julian Carrier]
|
||||
* Fix virtual interface member trigger convergence (#5116) (#7323). [Yilou Wang]
|
||||
* Fix shift width mismatch in constraint solver SMT emission (#5420) (#7265). [Yilou Wang]
|
||||
* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418). [em2machine]
|
||||
* Fix returning wrong type from static function in parameterized class (#5479) (#7387) (#7411) (#7418) (#7445) (#7450). [em2machine]
|
||||
* Fix randomize size+element queue constraints (#5582) (#7225). [Rahul Behl, Testorrent USA, Inc.]
|
||||
* Fix null assignment to virtual interfaces (#5974) (#5990). [Maxim Fonarev]
|
||||
* Fix typedef scope resolution for parameterized class aliases (#5977) (#7319). [Nick Brereton]
|
||||
|
|
@ -151,8 +157,10 @@ Verilator 5.047 devel
|
|||
* Fix delete inside foreach skipping elements (#7407) (#7410)
|
||||
* Fix std::randomize in parameterized-derived class (#7409) (#7416). [Yilou Wang]
|
||||
* Fix virtual interface implied comparison with null (#7421). [Alex Solomatnikov]
|
||||
* Fix uvm_hdl_release_and_read to release value and check success (#7425). [Christian Hecken, Heidelberg University]
|
||||
* Fix inline constraint on array-indexed randomize target (#7431) (#7434). [Yilou Wang]
|
||||
* Fix modification of members of object with const handle (#7433). [Kamil Danecki, Antmicro Ltd.]
|
||||
* Fix `dist` under implication in constraints (#7440) (#7442). [Alex Solomatnikov] [Yilou Wang]
|
||||
|
||||
|
||||
Verilator 5.046 2026-02-28
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@ detailed descriptions of these arguments.
|
|||
--coverage Enable all coverage
|
||||
--coverage-expr Enable expression coverage
|
||||
--coverage-expr-max <value> Maximum permutations allowed for an expression
|
||||
--coverage-fsm Enable FSM state/arc coverage
|
||||
--coverage-line Enable line coverage
|
||||
--coverage-max-width <width> Maximum array depth for coverage
|
||||
--coverage-toggle Enable toggle coverage
|
||||
|
|
@ -379,6 +380,7 @@ detailed descriptions of these arguments.
|
|||
--dump-<srcfile> Enable dumping everything in source file
|
||||
--dump-defines Show preprocessor defines with -E
|
||||
--dump-dfg Enable dumping DfgGraphs to .dot files
|
||||
--dump-dfg-patterns Enable dumping Dfg pattern statistics
|
||||
--dump-graph Enable dumping V3Graphs to .dot files
|
||||
--dump-inputs Enable dumping preprocessed input files
|
||||
--dump-tree Enable dumping Ast .tree files
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ L<https://verilator.org/guide/latest/exe_verilator_coverage.html>.
|
|||
--annotate-points Annotates info from each coverage point.
|
||||
--filter-type <regex> Keep only records of given coverage type.
|
||||
--help Displays this message and version and exits.
|
||||
--include-reset-arcs Include reset arcs in FSM arc summaries.
|
||||
--rank Compute relative importance of tests.
|
||||
--unlink With --write, unlink all inputs
|
||||
--version Displays program version and exits.
|
||||
|
|
|
|||
|
|
@ -40,12 +40,32 @@ if [ "$CI_OS_NAME" = "linux" ]; then
|
|||
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
|
||||
fi
|
||||
|
||||
install-vcddiff() {
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
git clone https://github.com/veripool/vcddiff "$TMP_DIR"
|
||||
git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5
|
||||
"$MAKE" -C "${TMP_DIR}"
|
||||
sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin
|
||||
install-wavediff() {
|
||||
source ci/docker/buildenv/wavetools.conf
|
||||
local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
|
||||
local _platform
|
||||
if [ "$CI_OS_NAME" = "linux" ]; then
|
||||
_platform="linux-x86_64"
|
||||
elif [ "$CI_OS_NAME" = "osx" ]; then
|
||||
_platform="macos-arm64"
|
||||
elif [ "$CI_OS_NAME" = "windows" ]; then
|
||||
_platform="windows-x86_64"
|
||||
else
|
||||
echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping"
|
||||
return 0
|
||||
fi
|
||||
local _tmpdir
|
||||
_tmpdir=$(mktemp -d)
|
||||
local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}"
|
||||
if [ "$CI_OS_NAME" = "windows" ]; then
|
||||
wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip"
|
||||
unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}"
|
||||
else
|
||||
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
|
||||
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
|
||||
fi
|
||||
sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff
|
||||
rm -rf "${_tmpdir}"
|
||||
}
|
||||
|
||||
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
|
||||
|
|
@ -120,7 +140,7 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
|
|||
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
|
||||
fi
|
||||
# Common installs
|
||||
install-vcddiff
|
||||
install-wavediff
|
||||
# Workaround -fsanitize=address crash
|
||||
sudo sysctl -w vm.mmap_rnd_bits=28
|
||||
else
|
||||
|
|
|
|||
|
|
@ -53,10 +53,14 @@ RUN apt-get update \
|
|||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN git clone https://github.com/veripool/vcddiff.git && \
|
||||
make -C vcddiff && \
|
||||
cp -p vcddiff/vcddiff /usr/local/bin/vcddiff && \
|
||||
rm -rf vcddiff
|
||||
COPY wavetools.conf /tmp/wavetools.conf
|
||||
ARG WGET_EXTRA_ARGS=
|
||||
RUN . /tmp/wavetools.conf && \
|
||||
wget -q ${WGET_EXTRA_ARGS} -O /tmp/wavetools.tar.gz \
|
||||
"https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}/wavetools-${WAVETOOLS_VERSION}-linux-x86_64.tar.gz" && \
|
||||
tar -xzf /tmp/wavetools.tar.gz -C /tmp && \
|
||||
cp -p /tmp/wavetools-${WAVETOOLS_VERSION}-linux-x86_64/wavediff /usr/local/bin/wavediff && \
|
||||
rm -rf /tmp/wavetools.tar.gz /tmp/wavetools-* /tmp/wavetools.conf
|
||||
|
||||
COPY build.sh /tmp/build.sh
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
WAVETOOLS_VERSION=v0.1.2
|
||||
|
|
@ -58,6 +58,7 @@ Drew Ranck
|
|||
Drew Taussig
|
||||
Driss Hafdi
|
||||
Edgar E. Iglesias
|
||||
Eric Mejdrich
|
||||
Eric Müller
|
||||
Eric Rippey
|
||||
Eunseo Song
|
||||
|
|
@ -302,3 +303,4 @@ em2machine
|
|||
emmettifelts
|
||||
Àlex Torregrosa
|
||||
Ícaro Lima
|
||||
Yogish Sekhar
|
||||
|
|
|
|||
|
|
@ -281,7 +281,8 @@ Summary:
|
|||
.. option:: --coverage
|
||||
|
||||
Enables all forms of coverage, an alias for :vlopt:`--coverage-line`
|
||||
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-user`.
|
||||
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-fsm`
|
||||
:vlopt:`--coverage-user`.
|
||||
|
||||
.. option:: --coverage-expr
|
||||
|
||||
|
|
@ -293,6 +294,10 @@ Summary:
|
|||
covered for a given expression. Defaults to 32. Increasing may slow
|
||||
coverage simulations and make analyzing the results unwieldy.
|
||||
|
||||
.. option:: --coverage-fsm
|
||||
|
||||
Enables native FSM state and arc coverage. See :ref:`FSM Coverage`.
|
||||
|
||||
.. option:: --coverage-line
|
||||
|
||||
Enables basic block line coverage analysis. See :ref:`Line Coverage`.
|
||||
|
|
@ -478,6 +483,10 @@ Summary:
|
|||
Rarely needed. Enable dumping DfgGraph .dot debug files with dumping
|
||||
level 3.
|
||||
|
||||
.. option:: --dump-dfg-patterns
|
||||
|
||||
Rarely needed. Enable dumping DfgGraph pattern statistics.
|
||||
|
||||
.. option:: --dump-graph
|
||||
|
||||
Rarely needed. Enable dumping V3Graph .dot debug files with dumping
|
||||
|
|
|
|||
|
|
@ -129,13 +129,20 @@ verilator_coverage Arguments
|
|||
.. option:: --filter-type <regex>
|
||||
|
||||
Skips records of coverage types that matches with <regex>
|
||||
Possible values are `toggle`, `line`, `branch`, `expr`, `user` and
|
||||
a wildcard with `\*` or `?`. The default value is `\*`.
|
||||
Possible values are `toggle`, `line`, `branch`, `expr`, `user`,
|
||||
`fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default
|
||||
value is `\*`.
|
||||
|
||||
.. option:: --help
|
||||
|
||||
Displays a help summary, the program version, and exits.
|
||||
|
||||
.. option:: --include-reset-arcs
|
||||
|
||||
Includes FSM reset arcs in the printed summaries and annotated output.
|
||||
By default, reset arcs are tracked but summarized separately from the
|
||||
non-reset FSM arcs.
|
||||
|
||||
.. option:: --rank
|
||||
|
||||
Prints an experimental report listing the relative importance of each
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables
|
|||
all forms of coverage:
|
||||
|
||||
- :ref:`User Coverage`
|
||||
- :ref:`FSM Coverage`
|
||||
- :ref:`Line Coverage`
|
||||
- :ref:`Toggle Coverage`
|
||||
|
||||
|
|
@ -208,6 +209,47 @@ point under the coverage name "DefaultClock":
|
|||
|
||||
DefaultClock: cover property (@(posedge clk) cyc==3);
|
||||
|
||||
.. _fsm coverage:
|
||||
|
||||
FSM Coverage
|
||||
------------
|
||||
|
||||
With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can
|
||||
instrument a conservative subset of single-process FSMs and report both
|
||||
state coverage (`fsm_state`) and transition coverage (`fsm_arc`).
|
||||
|
||||
This feature is currently experimental and might change in subsequent
|
||||
releases. In particular, the native FSM coverage extraction heuristics,
|
||||
:vlopt:`--coverage-fsm`, and the Verilator-specific FSM metacomments below
|
||||
should be treated as subject to change while the interface settles.
|
||||
|
||||
FSM extraction is intentionally narrow. The current implementation targets
|
||||
clocked, enum-driven state machines that can be recovered directly from the
|
||||
RTL. It does not claim broad support for two-process FSMs, one-hot
|
||||
inference, helper-function next-state recovery, or deeply nested control
|
||||
recovery.
|
||||
|
||||
The following metacomments may be attached to the state variable to steer
|
||||
the extracted coverage model:
|
||||
|
||||
- ``/*verilator fsm_state*/`` forces the variable to be treated as
|
||||
FSM state.
|
||||
- ``/*verilator fsm_reset_arc*/`` marks reset transitions as
|
||||
user-visible reset arcs instead of defaulting to a hidden reset-only
|
||||
summary.
|
||||
- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch
|
||||
arcs that would otherwise be skipped by the conservative extractor.
|
||||
|
||||
Reset transitions are included in the collected data either way. By
|
||||
default, :command:`verilator_coverage` summarizes reset-only arcs rather
|
||||
than printing them alongside non-reset arcs. Use
|
||||
:option:`verilator_coverage --include-reset-arcs` to include
|
||||
those arcs in the printed summary and annotated output.
|
||||
|
||||
Annotated output produced by :command:`verilator_coverage --annotate` will
|
||||
label FSM points with `fsm_state` and `fsm_arc`, and synthetic fallback
|
||||
transitions with `SYNTHETIC DEFAULT ARC`.
|
||||
|
||||
|
||||
.. _line coverage:
|
||||
|
||||
|
|
|
|||
|
|
@ -505,7 +505,7 @@ include directories and link to the SystemC libraries.
|
|||
|
||||
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`.
|
||||
|
||||
.. describe:: TRACE_VCD
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ List Of Warnings
|
|||
else
|
||||
array[address] <= data;
|
||||
|
||||
While this is supported in typical synthesizeable code (including the
|
||||
While this is supported in typical synthesizable code (including the
|
||||
example above), some complicated cases are not supported. Namely:
|
||||
|
||||
1. If the above loop is inside a suspendable process or fork statement.
|
||||
|
|
@ -837,6 +837,18 @@ List Of Warnings
|
|||
with a newline."
|
||||
|
||||
|
||||
.. option:: FSMMULTI
|
||||
|
||||
Warns that the same always block contains multiple enum-typed case
|
||||
statements that look like FSM candidates for native FSM coverage when
|
||||
:vlopt:`--coverage-fsm` or :vlopt:`--coverage` is enabled.
|
||||
|
||||
Verilator's FSM coverage instruments only the first such candidate in
|
||||
source order. Split the FSMs into separate always blocks, or explicitly
|
||||
annotate the intended state variables and restructure the RTL for full
|
||||
coverage of such multiple state machines.
|
||||
|
||||
|
||||
.. option:: FUNCTIMECTL
|
||||
|
||||
Error that a function contains a time-controlling statement or call of a
|
||||
|
|
|
|||
|
|
@ -1568,8 +1568,8 @@ For all tests to pass, you must install the following packages:
|
|||
|
||||
- SystemC to compile the SystemC outputs, see https://systemc.org
|
||||
|
||||
- vcddiff to find differences in VCD outputs. See the readme at
|
||||
https://github.com/veripool/vcddiff
|
||||
- wavediff to find differences in waveform outputs. See the readme at
|
||||
https://github.com/hudson-trading/wavetools
|
||||
|
||||
- Cmake for build paths that use it.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ ABCp
|
|||
Aadi
|
||||
Accellera
|
||||
Aditya
|
||||
allocator
|
||||
Affe
|
||||
Aleksander
|
||||
Alexandre
|
||||
|
|
@ -333,6 +334,7 @@ Muhlestein
|
|||
Multithreaded
|
||||
Multithreading
|
||||
Mykyta
|
||||
NFA
|
||||
NOUNOPTFLAT
|
||||
NaN
|
||||
Nalbantis
|
||||
|
|
@ -361,6 +363,7 @@ Olofsson
|
|||
Ondrej
|
||||
Oron
|
||||
Oyvind
|
||||
output
|
||||
PLI
|
||||
Pakanati
|
||||
Palaniappan
|
||||
|
|
@ -401,8 +404,10 @@ Ranjan
|
|||
Rapp
|
||||
Redhat
|
||||
Reitan
|
||||
reentrant
|
||||
Renga
|
||||
Requin
|
||||
reusability
|
||||
Riaz
|
||||
Rodas
|
||||
Rodionov
|
||||
|
|
@ -837,6 +842,7 @@ gotFinish
|
|||
goto
|
||||
gprof
|
||||
gtkwave
|
||||
hdl
|
||||
hdr
|
||||
hdzhangdoc
|
||||
hh
|
||||
|
|
@ -1211,6 +1217,7 @@ upcasting
|
|||
urandom
|
||||
uselib
|
||||
utimes
|
||||
uvm
|
||||
uwire
|
||||
uwires
|
||||
valgrind
|
||||
|
|
@ -1257,6 +1264,7 @@ vpm
|
|||
vpp
|
||||
warmup
|
||||
wavealloca
|
||||
wavediff
|
||||
waveforms
|
||||
whitespace
|
||||
widthed
|
||||
|
|
|
|||
|
|
@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7
|
|||
C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24),
|
||||
N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and C-string-valued metadata pairs.
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
|
||||
A(7)) VL_MT_SAFE {
|
||||
const std::string val2str = std::to_string(val2);
|
||||
const std::string val3str = std::to_string(val3);
|
||||
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
|
||||
N(8), N(9), N(10), N(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
|
||||
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and additional FSM metadata pairs.
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
|
||||
A(7), A(8), A(9), A(10), A(11)) VL_MT_SAFE {
|
||||
const std::string val2str = std::to_string(val2);
|
||||
const std::string val3str = std::to_string(val3);
|
||||
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
|
||||
C(8), C(9), C(10), C(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
|
||||
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for Verilator
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4),
|
||||
const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,13 @@ public:
|
|||
void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12),
|
||||
A(13), A(14), A(15), A(16), A(17), A(18), A(19), A(20), D(21), D(22), D(23),
|
||||
D(24), D(25), D(26), D(27), D(28), D(29)) VL_MT_SAFE;
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and C-string-valued metadata pairs.
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7)) VL_MT_SAFE;
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and additional FSM metadata pairs.
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7), A(8), A(9),
|
||||
A(10), A(11)) VL_MT_SAFE;
|
||||
// Backward compatibility for Verilator
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5),
|
||||
A(6), A(7)) VL_MT_SAFE;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd
|
|||
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
|
||||
// Bin attributes
|
||||
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'")
|
||||
VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'")
|
||||
VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'")
|
||||
|
|
@ -49,6 +53,10 @@ VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'd
|
|||
#define VL_CIK_COLUMN "n"
|
||||
#define VL_CIK_COMMENT "o"
|
||||
#define VL_CIK_FILENAME "f"
|
||||
#define VL_CIK_FSM_FROM "Ff"
|
||||
#define VL_CIK_FSM_TAG "Fg"
|
||||
#define VL_CIK_FSM_TO "Ft"
|
||||
#define VL_CIK_FSM_VAR "Fv"
|
||||
#define VL_CIK_HIER "h"
|
||||
#define VL_CIK_LINENO "l"
|
||||
#define VL_CIK_LINESCOV "S"
|
||||
|
|
@ -70,6 +78,10 @@ public:
|
|||
if (key == "column") return VL_CIK_COLUMN;
|
||||
if (key == "comment") return VL_CIK_COMMENT;
|
||||
if (key == "filename") return VL_CIK_FILENAME;
|
||||
if (key == "fsm_from") return VL_CIK_FSM_FROM;
|
||||
if (key == "fsm_tag") return VL_CIK_FSM_TAG;
|
||||
if (key == "fsm_to") return VL_CIK_FSM_TO;
|
||||
if (key == "fsm_var") return VL_CIK_FSM_VAR;
|
||||
if (key == "hier") return VL_CIK_HIER;
|
||||
if (key == "lineno") return VL_CIK_LINENO;
|
||||
if (key == "linescov") return VL_CIK_LINESCOV;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
// Copyright 2026-2026 by Wilson Snyder. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
///
|
||||
/// \file
|
||||
/// \brief Verilator: Runtime support for force/release statements
|
||||
///
|
||||
/// This file provides runtime data structures for efficient dynamic
|
||||
/// resolution of force/release statements. A sorted list of active
|
||||
/// forces is maintained that can be efficiently queried and modified
|
||||
/// at runtime.
|
||||
///
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_VERILATED_FORCE_H_
|
||||
#define VERILATOR_VERILATED_FORCE_H_
|
||||
|
||||
#include "verilatedos.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
template <typename T>
|
||||
using VlForceBaseType = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||
|
||||
// VlForceRead - Helper functions to read a forced value
|
||||
//
|
||||
// These functions combine original value with forced values based on
|
||||
// VlForceVec entries.
|
||||
// This achieves O(k) complexity where k = number of active forces.
|
||||
|
||||
template <typename T>
|
||||
struct VlForceTypeInfo final {
|
||||
using Type = VlForceBaseType<T>;
|
||||
static constexpr bool bitwise
|
||||
= std::is_integral<Type>::value || std::is_enum<Type>::value || VlIsVlWide<Type>::value;
|
||||
static constexpr bool unpackedArray = false;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct VlForceArrayIndexer final {
|
||||
static constexpr std::size_t size = 1;
|
||||
|
||||
static T& elem(T& value, std::size_t) { return value; }
|
||||
};
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct VlForceArrayIndexer<VlUnpacked<T, N>> final {
|
||||
static constexpr std::size_t size = N * VlForceArrayIndexer<T>::size;
|
||||
|
||||
static auto& elem(VlUnpacked<T, N>& array, std::size_t index) {
|
||||
constexpr std::size_t subSize = VlForceArrayIndexer<T>::size;
|
||||
return VlForceArrayIndexer<T>::elem(array[index / subSize], index % subSize);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct VlForceTypeInfo<VlUnpacked<T, N>> final {
|
||||
using Type = VlUnpacked<T, N>;
|
||||
static constexpr bool bitwise = false;
|
||||
static constexpr bool unpackedArray = true;
|
||||
};
|
||||
|
||||
template <typename T, bool = std::is_enum<T>::value>
|
||||
struct VlForceStorageTypeOf final {
|
||||
using type = typename std::make_unsigned<T>::type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct VlForceStorageTypeOf<T, true> final {
|
||||
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using VlForceStorageType = typename VlForceStorageTypeOf<VlForceBaseType<T>>::type;
|
||||
|
||||
//=============================================================================
|
||||
// VlForceVec - Vector of active force entries for a signal
|
||||
//
|
||||
// This class maintains a sorted vector of non-overlapping force entries.
|
||||
// When a new force is added, it removes or trims existing entries that
|
||||
// overlap with the new range.
|
||||
//
|
||||
// The generated code will:
|
||||
// 1. Use addForce/release to update the active forces
|
||||
// 2. Call a generated read function that iterates entries and evaluates RHS
|
||||
|
||||
class VlForceVec final {
|
||||
private:
|
||||
struct Entry final {
|
||||
int m_lsb; // Inclusive lower bit
|
||||
int m_msb; // Inclusive upper bit
|
||||
int m_rhsLsb; // Destination index that maps to RHS index 0
|
||||
const void* m_rhsDatap; // Pointer to RHS storage
|
||||
|
||||
bool operator<(const Entry& other) const { return m_msb < other.m_msb; }
|
||||
};
|
||||
|
||||
std::vector<Entry> m_entries; // Sorted by msb, non-overlapping
|
||||
|
||||
std::vector<Entry>::iterator trimEntries(int lsb, int msb) {
|
||||
auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb,
|
||||
[](const Entry& e, int bit) { return e.m_msb < bit; });
|
||||
while (it != m_entries.end() && it->m_lsb <= msb) {
|
||||
if (it->m_lsb < lsb && it->m_msb > msb) {
|
||||
const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap};
|
||||
it->m_msb = lsb - 1;
|
||||
return m_entries.insert(++it, right);
|
||||
}
|
||||
if (it->m_lsb < lsb) {
|
||||
it->m_msb = lsb - 1;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (it->m_msb > msb) {
|
||||
it->m_lsb = msb + 1;
|
||||
return it;
|
||||
}
|
||||
it = m_entries.erase(it);
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) {
|
||||
assert(width > 0 && width <= VL_QUADSIZE);
|
||||
assert(rhsLsb >= 0);
|
||||
|
||||
const QData mask = static_cast<QData>(VL_MASK_Q(width));
|
||||
const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1;
|
||||
if (rhsWidth <= VL_QUADSIZE) {
|
||||
const QData rhsVal = static_cast<QData>(*static_cast<const QData*>(entry.m_rhsDatap));
|
||||
return (rhsVal >> rhsLsb) & mask;
|
||||
}
|
||||
|
||||
const EData* const rhswp = static_cast<const EData*>(entry.m_rhsDatap);
|
||||
return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) {
|
||||
const T lowMask = static_cast<T>(VL_MASK_Q(width));
|
||||
const T mask = static_cast<T>(lowMask << lsb);
|
||||
const T rhsBits = static_cast<T>(
|
||||
(static_cast<T>(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb);
|
||||
return static_cast<T>((cur & ~mask) | (rhsBits & mask));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static typename std::enable_if<VlIsVlWide<T>::value, T>::type applyEntry(T result,
|
||||
const Entry& entry) {
|
||||
EData* const reswp = result.data();
|
||||
const int lword = VL_BITWORD_E(entry.m_lsb);
|
||||
const int hword = VL_BITWORD_E(entry.m_msb);
|
||||
for (int word = lword; word <= hword; ++word) {
|
||||
const int wordLsb = word * VL_EDATASIZE;
|
||||
const int segLsb = std::max(entry.m_lsb, wordLsb);
|
||||
const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1);
|
||||
const int segWidth = segMsb - segLsb + 1;
|
||||
const int bitOffset = segLsb - wordLsb;
|
||||
const int rhsLsb = segLsb - entry.m_rhsLsb;
|
||||
reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static typename std::enable_if<!VlIsVlWide<T>::value && VlForceTypeInfo<T>::bitwise, T>::type
|
||||
applyEntry(T result, const Entry& entry) {
|
||||
using U = VlForceStorageType<T>;
|
||||
const int width = entry.m_msb - entry.m_lsb + 1;
|
||||
const int bits = static_cast<int>(sizeof(U) * 8);
|
||||
const int rhsLsb = entry.m_lsb - entry.m_rhsLsb;
|
||||
const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width);
|
||||
if (width >= bits) return static_cast<T>(static_cast<U>(rhsChunk));
|
||||
return static_cast<T>(
|
||||
applyBits(static_cast<U>(result), entry, entry.m_lsb, width, rhsLsb));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static typename std::enable_if<!VlForceTypeInfo<T>::bitwise, T>::type
|
||||
applyEntry(T result, const Entry& entry) {
|
||||
static_cast<void>(result);
|
||||
return *static_cast<const VlForceBaseType<T>*>(entry.m_rhsDatap);
|
||||
}
|
||||
|
||||
public:
|
||||
VlForceVec() = default;
|
||||
|
||||
template <typename T>
|
||||
T read(T val) const {
|
||||
if VL_CONSTEXPR_CXX17 (VlForceTypeInfo<T>::unpackedArray) {
|
||||
// Handling the case of a nested flattened array using recursion
|
||||
using ElemRef
|
||||
= decltype(VlForceArrayIndexer<T>::elem(val, static_cast<std::size_t>(0)));
|
||||
using Elem = VlForceBaseType<ElemRef>;
|
||||
const int total = static_cast<int>(VlForceArrayIndexer<T>::size);
|
||||
for (const auto& entry : m_entries) {
|
||||
const Elem* const rhsBasep = static_cast<const Elem*>(entry.m_rhsDatap);
|
||||
const int startIdx = entry.m_lsb;
|
||||
const int endIdx = entry.m_msb;
|
||||
for (int idx = startIdx; idx <= endIdx; idx++) {
|
||||
const int rhsIndex = idx - entry.m_rhsLsb;
|
||||
const std::size_t uidx = static_cast<std::size_t>(idx);
|
||||
VlForceArrayIndexer<T>::elem(val, uidx) = rhsBasep[rhsIndex];
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
for (const auto& entry : m_entries) { val = applyEntry(val, entry); }
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T readIndex(T origVal, int index) const {
|
||||
if (m_entries.empty()) return origVal;
|
||||
|
||||
const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index,
|
||||
[](const Entry& e, int idx) { return e.m_msb < idx; });
|
||||
if (it != m_entries.end() && it->m_lsb <= index) {
|
||||
const int rhsIndex = index - it->m_rhsLsb;
|
||||
const T* const rhsBasep = static_cast<const T*>(it->m_rhsDatap);
|
||||
return rhsBasep[rhsIndex];
|
||||
}
|
||||
return origVal;
|
||||
}
|
||||
|
||||
void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) {
|
||||
assert(lsb <= msb);
|
||||
assert(rhsDatap);
|
||||
assert(rhsLsb <= lsb);
|
||||
|
||||
auto it = trimEntries(lsb, msb);
|
||||
m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap});
|
||||
}
|
||||
|
||||
void release(int lsb, int msb) {
|
||||
assert(lsb <= msb);
|
||||
trimEntries(lsb, msb);
|
||||
}
|
||||
|
||||
void touch() {}
|
||||
};
|
||||
|
||||
#endif // guard
|
||||
|
|
@ -100,6 +100,7 @@ set(HEADERS
|
|||
V3File.h
|
||||
V3FileLine.h
|
||||
V3Force.h
|
||||
V3FsmDetect.h
|
||||
V3Fork.h
|
||||
V3FuncOpt.h
|
||||
V3FunctionTraits.h
|
||||
|
|
@ -277,6 +278,7 @@ set(COMMON_SOURCES
|
|||
V3File.cpp
|
||||
V3FileLine.cpp
|
||||
V3Force.cpp
|
||||
V3FsmDetect.cpp
|
||||
V3Fork.cpp
|
||||
V3FuncOpt.cpp
|
||||
V3Gate.cpp
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ RAW_OBJS_PCH_ASTNOMT = \
|
|||
V3ExecGraph.o \
|
||||
V3Expand.o \
|
||||
V3Force.o \
|
||||
V3FsmDetect.o \
|
||||
V3Fork.o \
|
||||
V3Gate.o \
|
||||
V3HierBlock.o \
|
||||
|
|
|
|||
|
|
@ -311,6 +311,9 @@ public:
|
|||
//
|
||||
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
|
||||
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
|
||||
VAR_FSM_ARC_INCLUDE_COND, // V3LinkParse moves to AstVar::attrFsmArcInclCond
|
||||
VAR_FSM_RESET_ARC, // V3LinkParse moves to AstVar::attrFsmResetArc
|
||||
VAR_FSM_STATE, // V3LinkParse moves to AstVar::attrFsmState
|
||||
VAR_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype
|
||||
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
|
||||
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
||||
|
|
@ -336,10 +339,10 @@ public:
|
|||
"ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID",
|
||||
"FUNC_ARG_PROTO", "FUNC_RETURN_PROTO",
|
||||
"TYPEID", "TYPENAME",
|
||||
"VAR_BASE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC",
|
||||
"VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW",
|
||||
"VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT",
|
||||
"VAR_SPLIT_VAR"
|
||||
"VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
|
||||
"VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT",
|
||||
"VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS",
|
||||
"VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
|
||||
};
|
||||
// clang-format on
|
||||
return names[m_e];
|
||||
|
|
@ -809,6 +812,11 @@ public:
|
|||
EVENT_FIRE,
|
||||
EVENT_IS_FIRED,
|
||||
EVENT_IS_TRIGGERED,
|
||||
FORCE_ADD,
|
||||
FORCE_READ,
|
||||
FORCE_READ_INDEX,
|
||||
FORCE_RELEASE,
|
||||
FORCE_TOUCH,
|
||||
FORK_DONE,
|
||||
FORK_INIT,
|
||||
FORK_JOIN,
|
||||
|
|
@ -955,6 +963,11 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
|||
{EVENT_FIRE, "fire", false}, \
|
||||
{EVENT_IS_FIRED, "isFired", true}, \
|
||||
{EVENT_IS_TRIGGERED, "isTriggered", true}, \
|
||||
{FORCE_ADD, "addForce", false}, \
|
||||
{FORCE_READ, "read", true}, \
|
||||
{FORCE_READ_INDEX, "readIndex", true}, \
|
||||
{FORCE_RELEASE, "release", false}, \
|
||||
{FORCE_TOUCH, "touch", false}, \
|
||||
{FORK_DONE, "done", false}, \
|
||||
{FORK_INIT, "init", false}, \
|
||||
{FORK_JOIN, "join", false}, \
|
||||
|
|
|
|||
|
|
@ -2889,6 +2889,7 @@ public:
|
|||
};
|
||||
class AstConcat final : public AstNodeBiop {
|
||||
// If you're looking for {#{}}, see AstReplicate
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstConcat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Concat(fl, lhsp, rhsp) {
|
||||
|
|
@ -2938,6 +2939,7 @@ public:
|
|||
bool stringFlavor() const override { return true; }
|
||||
};
|
||||
class AstDiv final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Div(fl, lhsp, rhsp) {
|
||||
|
|
@ -2980,6 +2982,7 @@ public:
|
|||
bool doubleFlavor() const override { return true; }
|
||||
};
|
||||
class AstDivS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_DivS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3003,6 +3006,7 @@ public:
|
|||
};
|
||||
class AstEqWild final : public AstNodeBiop {
|
||||
// Note wildcard operator rhs differs from lhs
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstEqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) {
|
||||
|
|
@ -3109,6 +3113,7 @@ public:
|
|||
bool sizeMattersRhs() const override { return false; }
|
||||
};
|
||||
class AstGt final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstGt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Gt(fl, lhsp, rhsp) {
|
||||
|
|
@ -3171,6 +3176,7 @@ public:
|
|||
bool stringFlavor() const override { return true; }
|
||||
};
|
||||
class AstGtS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstGtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_GtS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3192,6 +3198,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstGte final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstGte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Gte(fl, lhsp, rhsp) {
|
||||
|
|
@ -3254,6 +3261,7 @@ public:
|
|||
bool stringFlavor() const override { return true; }
|
||||
};
|
||||
class AstGteS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstGteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_GteS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3275,6 +3283,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstLogAnd final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLogAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) {
|
||||
|
|
@ -3296,6 +3305,7 @@ public:
|
|||
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
|
||||
};
|
||||
class AstLogIf final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLogIf(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) {
|
||||
|
|
@ -3317,6 +3327,7 @@ public:
|
|||
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
|
||||
};
|
||||
class AstLogOr final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLogOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) {
|
||||
|
|
@ -3338,6 +3349,7 @@ public:
|
|||
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
|
||||
};
|
||||
class AstLt final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLt(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Lt(fl, lhsp, rhsp) {
|
||||
|
|
@ -3400,6 +3412,7 @@ public:
|
|||
bool stringFlavor() const override { return true; }
|
||||
};
|
||||
class AstLtS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLtS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LtS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3421,6 +3434,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstLte final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLte(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Lte(fl, lhsp, rhsp) {
|
||||
|
|
@ -3483,6 +3497,7 @@ public:
|
|||
bool stringFlavor() const override { return true; }
|
||||
};
|
||||
class AstLteS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLteS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LteS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3504,6 +3519,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstModDiv final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstModDiv(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) {
|
||||
|
|
@ -3525,6 +3541,7 @@ public:
|
|||
int instrCount() const override { return widthInstrs() * INSTR_COUNT_INT_DIV; }
|
||||
};
|
||||
class AstModDivS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstModDivS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3547,6 +3564,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstNeqWild final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstNeqWild(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) {
|
||||
|
|
@ -3566,6 +3584,7 @@ public:
|
|||
bool sizeMattersRhs() const override { return false; }
|
||||
};
|
||||
class AstPow final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstPow(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Pow(fl, lhsp, rhsp) {
|
||||
|
|
@ -3606,6 +3625,7 @@ public:
|
|||
bool doubleFlavor() const override { return true; }
|
||||
};
|
||||
class AstPowSS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstPowSS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3627,6 +3647,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstPowSU final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstPowSU(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) {
|
||||
|
|
@ -3648,6 +3669,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstPowUS final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstPowUS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) {
|
||||
|
|
@ -3673,6 +3695,7 @@ class AstReplicate final : public AstNodeBiop {
|
|||
// Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp()
|
||||
// @astgen alias op1 := srcp
|
||||
// @astgen alias op2 := countp
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstReplicate(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) {
|
||||
|
|
@ -3900,6 +3923,7 @@ public:
|
|||
void declElWidth(int flag) { m_declElWidth = flag; }
|
||||
};
|
||||
class AstShiftL final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstShiftL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
|
||||
: ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) {
|
||||
|
|
@ -3943,6 +3967,7 @@ public:
|
|||
bool sizeMattersRhs() const override { return false; }
|
||||
};
|
||||
class AstShiftR final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstShiftR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
|
||||
: ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) {
|
||||
|
|
@ -3990,6 +4015,7 @@ public:
|
|||
class AstShiftRS final : public AstNodeBiop {
|
||||
// Shift right with sign extension, >>> operator
|
||||
// Output data type's width determines which bit is used for sign extension
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstShiftRS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp, int setwidth = 0)
|
||||
: ASTGEN_SUPER_ShiftRS(fl, lhsp, rhsp) {
|
||||
|
|
@ -4036,6 +4062,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstSub final : public AstNodeBiop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstSub(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Sub(fl, lhsp, rhsp) {
|
||||
|
|
@ -4101,6 +4128,7 @@ public:
|
|||
|
||||
// === AstNodeBiCom ===
|
||||
class AstEq final : public AstNodeBiCom {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Eq(fl, lhsp, rhsp) {
|
||||
|
|
@ -4123,6 +4151,7 @@ public:
|
|||
bool sizeMattersRhs() const override { return false; }
|
||||
};
|
||||
class AstEqCase final : public AstNodeBiCom {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstEqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) {
|
||||
|
|
@ -4203,6 +4232,7 @@ public:
|
|||
int instrCount() const override { return INSTR_COUNT_STR; }
|
||||
};
|
||||
class AstLogEq final : public AstNodeBiCom {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLogEq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) {
|
||||
|
|
@ -4224,6 +4254,7 @@ public:
|
|||
int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; }
|
||||
};
|
||||
class AstNeq final : public AstNodeBiCom {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstNeq(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Neq(fl, lhsp, rhsp) {
|
||||
|
|
@ -4245,6 +4276,7 @@ public:
|
|||
bool sizeMattersRhs() const override { return false; }
|
||||
};
|
||||
class AstNeqCase final : public AstNodeBiCom {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstNeqCase(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) {
|
||||
|
|
@ -4327,6 +4359,7 @@ public:
|
|||
|
||||
// === AstNodeBiComAsv ===
|
||||
class AstAdd final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstAdd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Add(fl, lhsp, rhsp) {
|
||||
|
|
@ -4368,6 +4401,7 @@ public:
|
|||
bool doubleFlavor() const override { return true; }
|
||||
};
|
||||
class AstAnd final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstAnd(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_And(fl, lhsp, rhsp) {
|
||||
|
|
@ -4389,6 +4423,7 @@ public:
|
|||
const char* widthMismatch() const override VL_MT_STABLE;
|
||||
};
|
||||
class AstMul final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstMul(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Mul(fl, lhsp, rhsp) {
|
||||
|
|
@ -4431,6 +4466,7 @@ public:
|
|||
bool doubleFlavor() const override { return true; }
|
||||
};
|
||||
class AstMulS final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstMulS(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_MulS(fl, lhsp, rhsp) {
|
||||
|
|
@ -4454,6 +4490,7 @@ public:
|
|||
bool signedFlavor() const override { return true; }
|
||||
};
|
||||
class AstOr final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstOr(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Or(fl, lhsp, rhsp) {
|
||||
|
|
@ -4475,6 +4512,7 @@ public:
|
|||
const char* widthMismatch() const override VL_MT_STABLE;
|
||||
};
|
||||
class AstXor final : public AstNodeBiComAsv {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstXor(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_Xor(fl, lhsp, rhsp) {
|
||||
|
|
@ -4532,6 +4570,7 @@ public:
|
|||
|
||||
// === AstNodeSel ===
|
||||
class AstArraySel final : public AstNodeSel {
|
||||
// @astgen makeDfgVertex
|
||||
void init(const AstNode* fromp) {
|
||||
if (fromp && VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) {
|
||||
// Strip off array to find what array references
|
||||
|
|
@ -4644,6 +4683,7 @@ public:
|
|||
// === AstNodeStream ===
|
||||
class AstStreamL final : public AstNodeStream {
|
||||
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstStreamL(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {}
|
||||
|
|
@ -4662,6 +4702,7 @@ public:
|
|||
};
|
||||
class AstStreamR final : public AstNodeStream {
|
||||
// Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp()
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstStreamR(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp)
|
||||
: ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {}
|
||||
|
|
@ -4950,6 +4991,7 @@ class AstCond final : public AstNodeTriop {
|
|||
// @astgen alias op1 := condp
|
||||
// @astgen alias op2 := thenp
|
||||
// @astgen alias op3 := elsep
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstCond(FileLine* fl, AstNodeExpr* condp, AstNodeExpr* thenp, AstNodeExpr* elsep);
|
||||
ASTGEN_MEMBERS_AstCond;
|
||||
|
|
@ -5298,6 +5340,7 @@ public:
|
|||
};
|
||||
class AstCountOnes final : public AstNodeUniop {
|
||||
// Number of bits set in vector
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstCountOnes(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_CountOnes(fl, lhsp) {}
|
||||
|
|
@ -5329,6 +5372,7 @@ public:
|
|||
};
|
||||
class AstExtend final : public AstNodeUniop {
|
||||
// Expand a value into a wider entity by 0 extension. Width is implied from nodep->width()
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstExtend(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_Extend(fl, lhsp) {}
|
||||
|
|
@ -5352,6 +5396,7 @@ public:
|
|||
};
|
||||
class AstExtendS final : public AstNodeUniop {
|
||||
// Expand a value into a wider entity by sign extension. Width is implied from nodep->width()
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstExtendS(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_ExtendS(fl, lhsp) {}
|
||||
|
|
@ -5498,6 +5543,7 @@ public:
|
|||
bool sizeMattersLhs() const override { return false; }
|
||||
};
|
||||
class AstLogNot final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstLogNot(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_LogNot(fl, lhsp) {
|
||||
|
|
@ -5529,6 +5575,7 @@ public:
|
|||
bool sizeMattersLhs() const override { return false; }
|
||||
};
|
||||
class AstNegate final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstNegate(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_Negate(fl, lhsp) {
|
||||
|
|
@ -5562,6 +5609,7 @@ public:
|
|||
bool doubleFlavor() const override { return true; }
|
||||
};
|
||||
class AstNot final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstNot(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_Not(fl, lhsp) {
|
||||
|
|
@ -5683,6 +5731,7 @@ public:
|
|||
bool isSystemFunc() const override { return true; }
|
||||
};
|
||||
class AstRedAnd final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstRedAnd(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_RedAnd(fl, lhsp) {
|
||||
|
|
@ -5697,6 +5746,7 @@ public:
|
|||
bool sizeMattersLhs() const override { return false; }
|
||||
};
|
||||
class AstRedOr final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstRedOr(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_RedOr(fl, lhsp) {
|
||||
|
|
@ -5711,6 +5761,7 @@ public:
|
|||
bool sizeMattersLhs() const override { return false; }
|
||||
};
|
||||
class AstRedXor final : public AstNodeUniop {
|
||||
// @astgen makeDfgVertex
|
||||
public:
|
||||
AstRedXor(FileLine* fl, AstNodeExpr* lhsp)
|
||||
: ASTGEN_SUPER_RedXor(fl, lhsp) {
|
||||
|
|
|
|||
|
|
@ -656,7 +656,8 @@ class AstCell final : public AstNode {
|
|||
// A instantiation cell or interface call (don't know which until link)
|
||||
// @astgen op1 := pinsp : List[AstPin] // List of port assignments
|
||||
// @astgen op2 := paramsp : List[AstPin] // List of parameter assignments
|
||||
// @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances
|
||||
// @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains
|
||||
// via nextp()
|
||||
// @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing
|
||||
//
|
||||
// @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced
|
||||
|
|
@ -680,7 +681,7 @@ public:
|
|||
, m_trace{true} {
|
||||
addPinsp(pinsp);
|
||||
addParamsp(paramsp);
|
||||
this->rangep(rangep);
|
||||
addRangep(rangep);
|
||||
}
|
||||
ASTGEN_MEMBERS_AstCell;
|
||||
// No cloneRelink, we presume cloneee's want the same module linkages
|
||||
|
|
@ -1933,6 +1934,9 @@ class AstVar final : public AstNode {
|
|||
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
||||
bool m_attrSFormat : 1; // User sformat attribute
|
||||
bool m_attrSplitVar : 1; // declared with split_var metacomment
|
||||
bool m_attrFsmState : 1; // declared with fsm_state metacomment
|
||||
bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment
|
||||
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
|
||||
bool m_fileDescr : 1; // File descriptor
|
||||
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
|
||||
bool m_isConst : 1; // Table contains constant data
|
||||
|
|
@ -1991,6 +1995,9 @@ class AstVar final : public AstNode {
|
|||
m_attrIsolateAssign = false;
|
||||
m_attrSFormat = false;
|
||||
m_attrSplitVar = false;
|
||||
m_attrFsmState = false;
|
||||
m_attrFsmResetArc = false;
|
||||
m_attrFsmArcInclCond = false;
|
||||
m_fileDescr = false;
|
||||
m_gotNansiType = false;
|
||||
m_isConst = false;
|
||||
|
|
@ -2135,6 +2142,9 @@ public:
|
|||
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
|
||||
void attrSFormat(bool flag) { m_attrSFormat = flag; }
|
||||
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
|
||||
void attrFsmState(bool flag) { m_attrFsmState = flag; }
|
||||
void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; }
|
||||
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
|
||||
void rand(const VRandAttr flag) { m_rand = flag; }
|
||||
void usedParam(bool flag) { m_usedParam = flag; }
|
||||
void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
|
||||
|
|
@ -2298,6 +2308,9 @@ public:
|
|||
bool attrFileDescr() const { return m_fileDescr; }
|
||||
bool attrSFormat() const { return m_attrSFormat; }
|
||||
bool attrSplitVar() const { return m_attrSplitVar; }
|
||||
bool attrFsmState() const { return m_attrFsmState; }
|
||||
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
|
||||
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
|
||||
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
|
||||
AstIface* sensIfacep() const { return m_sensIfacep; }
|
||||
VRandAttr rand() const { return m_rand; }
|
||||
|
|
@ -2383,12 +2396,22 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl {
|
|||
// Coverage analysis point declaration
|
||||
// Used for other than toggle types of coverage
|
||||
string m_linescov;
|
||||
string m_fsmVar;
|
||||
string m_fsmFrom;
|
||||
string m_fsmTo;
|
||||
string m_fsmTag;
|
||||
int m_offset; // Offset column numbers to uniq-ify IFs
|
||||
public:
|
||||
AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment,
|
||||
const string& linescov, int offset)
|
||||
const string& linescov, int offset, const string& fsmVar = "",
|
||||
const string& fsmFrom = "", const string& fsmTo = "",
|
||||
const string& fsmTag = "")
|
||||
: ASTGEN_SUPER_CoverOtherDecl(fl, page, comment)
|
||||
, m_linescov{linescov}
|
||||
, m_fsmVar{fsmVar}
|
||||
, m_fsmFrom{fsmFrom}
|
||||
, m_fsmTo{fsmTo}
|
||||
, m_fsmTag{fsmTag}
|
||||
, m_offset{offset} {}
|
||||
ASTGEN_MEMBERS_AstCoverOtherDecl;
|
||||
void dump(std::ostream& str) const override;
|
||||
|
|
@ -2396,6 +2419,10 @@ public:
|
|||
int offset() const { return m_offset; }
|
||||
int size() const override { return 1; }
|
||||
const string& linescov() const { return m_linescov; }
|
||||
const string& fsmVar() const { return m_fsmVar; }
|
||||
const string& fsmFrom() const { return m_fsmFrom; }
|
||||
const string& fsmTo() const { return m_fsmTo; }
|
||||
const string& fsmTag() const { return m_fsmTag; }
|
||||
bool sameNode(const AstNode* samep) const override {
|
||||
const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl);
|
||||
return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov();
|
||||
|
|
|
|||
|
|
@ -3004,6 +3004,9 @@ void AstVar::dump(std::ostream& str) const {
|
|||
if (processQueue()) str << " [PROCQ]";
|
||||
if (sampled()) str << " [SAMPLED]";
|
||||
if (attrIsolateAssign()) str << " [aISO]";
|
||||
if (attrFsmState()) str << " [aFSMSTATE]";
|
||||
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
|
||||
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
|
||||
if (attrFileDescr()) str << " [aFD]";
|
||||
if (isFuncReturn()) {
|
||||
str << " [FUNCRTN]";
|
||||
|
|
@ -3036,6 +3039,9 @@ void AstVar::dumpJson(std::ostream& str) const {
|
|||
dumpJsonBoolFuncIf(str, processQueue);
|
||||
dumpJsonBoolFuncIf(str, sampled);
|
||||
dumpJsonBoolFuncIf(str, attrIsolateAssign);
|
||||
dumpJsonBoolFuncIf(str, attrFsmState);
|
||||
dumpJsonBoolFuncIf(str, attrFsmResetArc);
|
||||
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
|
||||
dumpJsonBoolFuncIf(str, attrFileDescr);
|
||||
dumpJsonBoolFuncIf(str, isDpiOpenArray);
|
||||
dumpJsonBoolFuncIf(str, isFuncReturn);
|
||||
|
|
@ -3283,10 +3289,18 @@ void AstNodeCoverDecl::dumpJson(std::ostream& str) const {
|
|||
void AstCoverOtherDecl::dump(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dump(str);
|
||||
if (!linescov().empty()) str << " lc=" << linescov();
|
||||
if (!fsmVar().empty()) str << " fv=" << fsmVar();
|
||||
if (!fsmFrom().empty()) str << " ff=" << fsmFrom();
|
||||
if (!fsmTo().empty()) str << " ft=" << fsmTo();
|
||||
if (!fsmTag().empty()) str << " fg=" << fsmTag();
|
||||
}
|
||||
void AstCoverOtherDecl::dumpJson(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dumpJson(str);
|
||||
dumpJsonStrFunc(str, linescov);
|
||||
dumpJsonStrFunc(str, fsmVar);
|
||||
dumpJsonStrFunc(str, fsmFrom);
|
||||
dumpJsonStrFunc(str, fsmTo);
|
||||
dumpJsonStrFunc(str, fsmTag);
|
||||
}
|
||||
void AstCoverToggleDecl::dump(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dump(str);
|
||||
|
|
|
|||
|
|
@ -329,8 +329,12 @@ class DeadVisitor final : public VNVisitor {
|
|||
void visit(AstNodeFTask* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
checkAll(nodep);
|
||||
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
|
||||
if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) {
|
||||
if (m_modp && !m_modp->dead() && !m_modp->verilatorLib())
|
||||
m_modp->user1Inc(); // Keep container
|
||||
} else {
|
||||
m_tasksp.push(nodep);
|
||||
}
|
||||
if (nodep->classOrPackagep()) {
|
||||
if (m_elimCells) {
|
||||
nodep->classOrPackagep(nullptr);
|
||||
|
|
|
|||
167
src/V3Dfg.cpp
167
src/V3Dfg.cpp
|
|
@ -672,7 +672,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
|
|||
|
||||
case VDfgType::Add:
|
||||
case VDfgType::And:
|
||||
case VDfgType::BufIf1:
|
||||
case VDfgType::Div:
|
||||
case VDfgType::DivS:
|
||||
case VDfgType::ModDiv:
|
||||
|
|
@ -759,18 +758,6 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
|
|||
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::LogEq:
|
||||
case VDfgType::LogIf:
|
||||
|
|
@ -938,6 +925,160 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
|
|||
delete this;
|
||||
}
|
||||
|
||||
class DfgPatternString final {
|
||||
std::ostream& m_os;
|
||||
|
||||
std::map<std::string, std::string> m_internedConsts; // Interned constants
|
||||
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
|
||||
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
|
||||
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
|
||||
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
|
||||
// Multiplicity and depth of vertices
|
||||
std::map<const DfgVertex*, std::pair<uint32_t, uint32_t>> m_multiplicityAndDepth;
|
||||
|
||||
static std::string toLetters(size_t value, bool lowerCase = false) {
|
||||
const char base = lowerCase ? 'a' : 'A';
|
||||
std::string s;
|
||||
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
|
||||
return s;
|
||||
}
|
||||
|
||||
const std::string& internConst(const DfgConst& vtx) {
|
||||
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internSelLsb(uint32_t value) {
|
||||
const auto pair = m_internedSelLsbs.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internWordWidth(uint32_t value) {
|
||||
const auto pair = m_internedWordWidths.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internWideWidth(uint32_t value) {
|
||||
const auto pair = m_internedWideWidths.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internVertex(const DfgVertex& vtx) {
|
||||
const auto pair = m_internedVertices.emplace(&vtx, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) {
|
||||
std::pair<uint32_t, uint32_t>& value = m_multiplicityAndDepth
|
||||
.emplace(std::piecewise_construct, //
|
||||
std::forward_as_tuple(&vtx), //
|
||||
std::forward_as_tuple(0, depth))
|
||||
.first->second;
|
||||
value.first += 1;
|
||||
value.second = std::max(value.second, depth);
|
||||
if (!depth) return;
|
||||
vtx.foreachSource([&](const DfgVertex& src) {
|
||||
recordMultiplicityAndDepth(src, depth - 1);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Render the vertx into ss, and return true if the recursion reached the given depth,
|
||||
// meaning an S-expression with that nesting level has been rendered.
|
||||
void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) {
|
||||
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
|
||||
// Base case 1: constant
|
||||
if (constp->isZero()) {
|
||||
m_os << "(CONST ZERO)";
|
||||
} else if (constp->isOnes()) {
|
||||
m_os << "(CONST ONES)";
|
||||
} else {
|
||||
m_os << "(CONST #" << internConst(*constp) << ')';
|
||||
}
|
||||
} else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) {
|
||||
// Base case 2: vertex appearing multiple times
|
||||
m_os << internVertex(vtx);
|
||||
} else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) {
|
||||
// Base case 3: vertex with no inputs (input variable)
|
||||
m_os << '(' << vtx.typeName() << ')';
|
||||
} else if (depth == 0) {
|
||||
// Base case 4: deep vertex (apperaing only once)
|
||||
m_os << "_";
|
||||
} else {
|
||||
// Recursively print an S-expression for the vertex
|
||||
m_os << '(';
|
||||
// Name
|
||||
m_os << vtx.typeName();
|
||||
// Specials
|
||||
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
|
||||
m_os << '@';
|
||||
if (selp->lsb() == 0) {
|
||||
m_os << '0';
|
||||
} else {
|
||||
m_os << internSelLsb(selp->lsb());
|
||||
}
|
||||
}
|
||||
// Operands
|
||||
vtx.foreachSource([&](const DfgVertex& src) {
|
||||
m_os << ' ';
|
||||
render(src, depth - 1, false);
|
||||
return false;
|
||||
});
|
||||
// S-expression end
|
||||
m_os << ')';
|
||||
}
|
||||
|
||||
// Annotate type
|
||||
m_os << ':';
|
||||
if (!vtx.dtype().isPacked()) {
|
||||
vtx.dtype().astDtypep()->dumpSmall(m_os);
|
||||
} else {
|
||||
const uint32_t width = vtx.size();
|
||||
if (width == 1) {
|
||||
m_os << '1';
|
||||
} else if (width <= VL_QUADSIZE) {
|
||||
m_os << internWordWidth(width);
|
||||
} else {
|
||||
m_os << internWideWidth(width);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark it if it has multiple sinks
|
||||
if (vtx.hasMultipleSinks()) m_os << '*';
|
||||
}
|
||||
|
||||
public:
|
||||
DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth)
|
||||
: m_os{os} {
|
||||
recordMultiplicityAndDepth(vtx, depth);
|
||||
render(vtx, depth, false);
|
||||
using Pair = std::pair<std::string, const DfgVertex*>;
|
||||
std::vector<Pair> vertices;
|
||||
for (const auto& pair : m_multiplicityAndDepth) {
|
||||
if (pair.second.first == 1) continue;
|
||||
vertices.emplace_back(internVertex(*pair.first), pair.first);
|
||||
}
|
||||
std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) { //
|
||||
return a.first < b.first;
|
||||
});
|
||||
for (const Pair& pair : vertices) {
|
||||
m_os << " | " << pair.first << " is ";
|
||||
render(*pair.second, m_multiplicityAndDepth.at(pair.second).second);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::string DfgVertex::patternString(uint32_t depth) const {
|
||||
std::ostringstream oss;
|
||||
DfgPatternString{oss, *this, depth};
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// DfgVisitor
|
||||
|
||||
|
|
|
|||
41
src/V3Dfg.h
41
src/V3Dfg.h
|
|
@ -352,6 +352,9 @@ public:
|
|||
|
||||
// Human-readable name for source operand with given index for debugging
|
||||
virtual std::string srcName(size_t idx) const = 0;
|
||||
|
||||
// S-expression inspired dump of vertex and operands for debugging
|
||||
std::string patternString(uint32_t depth = 0) const;
|
||||
};
|
||||
|
||||
// DfgVertex visitor
|
||||
|
|
@ -812,9 +815,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
|
|||
bool DfgVertex::isCheaperThanLoad() const {
|
||||
// Constants
|
||||
if (is<DfgConst>()) return true;
|
||||
// Variables
|
||||
if (is<DfgVertexVar>()) return true;
|
||||
// Array sels are just address computation
|
||||
if (is<DfgArraySel>()) return true;
|
||||
// Small constant select from variable
|
||||
// Small select from variable
|
||||
if (const DfgSel* const selp = cast<DfgSel>()) {
|
||||
if (!selp->fromp()->is<DfgVarPacked>()) return false;
|
||||
if (selp->fromp()->width() <= VL_QUADSIZE) return true;
|
||||
|
|
@ -825,20 +830,32 @@ bool DfgVertex::isCheaperThanLoad() const {
|
|||
// Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
|
||||
if (const DfgConcat* const catp = cast<DfgConcat>()) {
|
||||
if (catp->width() > VL_QUADSIZE) return false;
|
||||
const DfgConst* const lCatp = catp->lhsp()->cast<DfgConst>();
|
||||
if (!lCatp) return false;
|
||||
if (!lCatp->isZero()) return false;
|
||||
const DfgConst* const lConstp = catp->lhsp()->cast<DfgConst>();
|
||||
if (!lConstp || !lConstp->isZero()) return false;
|
||||
return catp->rhsp()->isCheaperThanLoad();
|
||||
}
|
||||
// Reduction of a cheap vertex
|
||||
if (const DfgRedOr* const redOrp = cast<DfgRedOr>()) {
|
||||
return redOrp->srcp()->isCheaperThanLoad();
|
||||
// Reduction of a narrow cheap vertex
|
||||
if (is<DfgRedOr>() //
|
||||
|| is<DfgRedAnd>() //
|
||||
|| is<DfgRedXor>()) {
|
||||
const DfgVertex* const srcp = as<DfgVertexUnary>()->srcp();
|
||||
return srcp->width() <= VL_QUADSIZE && srcp->isCheaperThanLoad();
|
||||
}
|
||||
if (const DfgRedAnd* const redAndp = cast<DfgRedAnd>()) {
|
||||
return redAndp->srcp()->isCheaperThanLoad();
|
||||
}
|
||||
if (const DfgRedXor* const redXorp = cast<DfgRedXor>()) {
|
||||
return redXorp->srcp()->isCheaperThanLoad();
|
||||
// Comparisons of a narrow cheap vertex with constant
|
||||
if (is<DfgEq>() //
|
||||
|| is<DfgNeq>() //
|
||||
|| is<DfgLt>() //
|
||||
|| is<DfgLte>() //
|
||||
|| is<DfgGt>() //
|
||||
|| is<DfgGte>() //
|
||||
|| is<DfgLtS>() //
|
||||
|| is<DfgLteS>() //
|
||||
|| is<DfgGtS>() //
|
||||
|| is<DfgGteS>()) {
|
||||
const DfgVertexBinary* const binp = as<DfgVertexBinary>();
|
||||
const DfgVertex* const lhsp = binp->inputp(0);
|
||||
const DfgVertex* const rhsp = binp->inputp(1);
|
||||
return lhsp->width() <= VL_QUADSIZE && lhsp->is<DfgConst>() && rhsp->isCheaperThanLoad();
|
||||
}
|
||||
// Otherwise probably not
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ class V3DfgCse final {
|
|||
case VDfgType::Add:
|
||||
case VDfgType::And:
|
||||
case VDfgType::ArraySel:
|
||||
case VDfgType::BufIf1:
|
||||
case VDfgType::Concat:
|
||||
case VDfgType::Cond:
|
||||
case VDfgType::CountOnes:
|
||||
|
|
@ -125,11 +124,6 @@ class V3DfgCse final {
|
|||
case VDfgType::ShiftRS:
|
||||
case VDfgType::StreamL:
|
||||
case VDfgType::StreamR:
|
||||
case VDfgType::SAnd:
|
||||
case VDfgType::SIntersect:
|
||||
case VDfgType::SOr:
|
||||
case VDfgType::SThroughout:
|
||||
case VDfgType::SWithin:
|
||||
case VDfgType::Sub:
|
||||
case VDfgType::Xor: return V3Hash{};
|
||||
}
|
||||
|
|
@ -206,7 +200,6 @@ class V3DfgCse final {
|
|||
case VDfgType::Add:
|
||||
case VDfgType::And:
|
||||
case VDfgType::ArraySel:
|
||||
case VDfgType::BufIf1:
|
||||
case VDfgType::Concat:
|
||||
case VDfgType::Cond:
|
||||
case VDfgType::CountOnes:
|
||||
|
|
@ -252,11 +245,6 @@ class V3DfgCse final {
|
|||
case VDfgType::ShiftR:
|
||||
case VDfgType::ShiftRS:
|
||||
case VDfgType::StreamL:
|
||||
case VDfgType::SAnd:
|
||||
case VDfgType::SIntersect:
|
||||
case VDfgType::SOr:
|
||||
case VDfgType::SThroughout:
|
||||
case VDfgType::SWithin:
|
||||
case VDfgType::StreamR:
|
||||
case VDfgType::Sub:
|
||||
case VDfgType::Xor: return true;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ public:
|
|||
// Thanks to the interning, equality is identity
|
||||
bool operator==(const DfgDataType& that) const { return this == &that; }
|
||||
bool operator!=(const DfgDataType& that) const { return this != &that; }
|
||||
// Similarly for hash
|
||||
V3Hash hash() const { return V3Hash{this}; }
|
||||
|
||||
// Type of elements, for arrays only
|
||||
const DfgDataType& elemDtype() const {
|
||||
|
|
@ -132,13 +134,6 @@ public:
|
|||
return *m_elemDtypep;
|
||||
}
|
||||
|
||||
V3Hash hash() const {
|
||||
V3Hash hash{static_cast<uint32_t>(m_kind)};
|
||||
hash += m_size;
|
||||
if (m_elemDtypep) hash += m_elemDtypep->hash();
|
||||
return hash;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Static factory and management functions
|
||||
|
||||
|
|
|
|||
|
|
@ -21,133 +21,27 @@
|
|||
#include "V3DfgPasses.h"
|
||||
#include "V3File.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
class V3DfgPatternStats final {
|
||||
static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
|
||||
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
|
||||
|
||||
std::map<std::string, std::string> m_internedConsts; // Interned constants
|
||||
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
|
||||
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
|
||||
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
|
||||
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
|
||||
|
||||
// Maps from pattern to the number of times it appears, for each pattern depth
|
||||
std::vector<std::unordered_map<std::string, size_t>> m_patterCounts{MAX_PATTERN_DEPTH + 1};
|
||||
|
||||
static std::string toLetters(size_t value, bool lowerCase = false) {
|
||||
const char base = lowerCase ? 'a' : 'A';
|
||||
std::string s;
|
||||
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
|
||||
return s;
|
||||
}
|
||||
|
||||
const std::string& internConst(const DfgConst& vtx) {
|
||||
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internSelLsb(uint32_t value) {
|
||||
const auto pair = m_internedSelLsbs.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internWordWidth(uint32_t value) {
|
||||
const auto pair = m_internedWordWidths.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internWideWidth(uint32_t value) {
|
||||
const auto pair = m_internedWideWidths.emplace(value, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
const std::string& internVertex(const DfgVertex& vtx) {
|
||||
const auto pair = m_internedVertices.emplace(&vtx, "");
|
||||
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
|
||||
return pair.first->second;
|
||||
}
|
||||
|
||||
// Render the vertx into ss, and return true if the recursion reached the given depth,
|
||||
// meaning an S-expression with that nesting level has been rendered.
|
||||
bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) {
|
||||
bool deep = depth == 0;
|
||||
|
||||
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
|
||||
// Base case 1: constant
|
||||
if (constp->isZero()) {
|
||||
ss << "'0";
|
||||
} else if (constp->isOnes()) {
|
||||
ss << "'1";
|
||||
} else {
|
||||
ss << internConst(*constp);
|
||||
}
|
||||
} else if (depth == 0) {
|
||||
// Base case 2: deep vertex
|
||||
ss << "_";
|
||||
} else {
|
||||
// Recursively print an S-expression for the vertex
|
||||
|
||||
// S-expression begin
|
||||
ss << '(';
|
||||
// Name
|
||||
ss << vtx.typeName();
|
||||
// Specials
|
||||
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
|
||||
ss << '@';
|
||||
if (selp->lsb() == 0) {
|
||||
ss << '0';
|
||||
} else {
|
||||
ss << internSelLsb(selp->lsb());
|
||||
}
|
||||
}
|
||||
// Operands
|
||||
vtx.foreachSource([&](const DfgVertex& src) {
|
||||
ss << ' ';
|
||||
if (render(ss, src, depth - 1)) deep = true;
|
||||
return false;
|
||||
});
|
||||
// S-expression end
|
||||
ss << ')';
|
||||
}
|
||||
|
||||
// Annotate identity
|
||||
ss << ":" << internVertex(vtx);
|
||||
// Mark it if it has multiple sinks
|
||||
if (vtx.hasMultipleSinks()) ss << '*';
|
||||
// Annotate type
|
||||
ss << '/';
|
||||
if (!vtx.dtype().isPacked()) {
|
||||
vtx.dtype().astDtypep()->dumpSmall(ss);
|
||||
} else {
|
||||
const uint32_t width = vtx.size();
|
||||
if (width == 1) {
|
||||
ss << '1';
|
||||
} else if (width <= VL_QUADSIZE) {
|
||||
ss << internWordWidth(width);
|
||||
} else {
|
||||
ss << internWideWidth(width);
|
||||
}
|
||||
}
|
||||
|
||||
// Done
|
||||
return deep;
|
||||
}
|
||||
|
||||
void dump(std::ostream& os) {
|
||||
using Line = std::pair<std::string, size_t>;
|
||||
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
|
||||
os << "DFG patterns with depth " << i << '\n';
|
||||
|
||||
// Pick up pattern accumulators with given depth
|
||||
const auto& patternCounts = m_patterCounts[i];
|
||||
auto& patternCounts = m_patterCounts[i];
|
||||
|
||||
// Remove patterns also present at shallower depths
|
||||
for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) {
|
||||
for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first);
|
||||
}
|
||||
|
||||
// Sort patterns, first by descending frequency, then lexically
|
||||
std::vector<Line> lines;
|
||||
|
|
@ -173,8 +67,8 @@ public:
|
|||
V3DfgPatternStats() = default;
|
||||
~V3DfgPatternStats() {
|
||||
// File to dump to
|
||||
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix()
|
||||
+ "__stats_dfg_patterns.txt";
|
||||
const std::string filename
|
||||
= v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt";
|
||||
// Open, write, close
|
||||
const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)};
|
||||
if (ofp->fail()) v3fatal("Can't write file: " << filename);
|
||||
|
|
@ -184,13 +78,7 @@ public:
|
|||
void accumulate(const DfgGraph& dfg) {
|
||||
dfg.forEachVertex([&](const DfgVertex& vtx) {
|
||||
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
|
||||
std::ostringstream ss;
|
||||
if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1;
|
||||
m_internedConsts.clear();
|
||||
m_internedSelLsbs.clear();
|
||||
m_internedWordWidths.clear();
|
||||
m_internedWideWidths.clear();
|
||||
m_internedVertices.clear();
|
||||
m_patterCounts[i][vtx.patternString(i)] += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,9 +154,9 @@ class DataflowOptimize final {
|
|||
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
|
||||
endOfStage("peephole", dfg, acyclicComps);
|
||||
// Accumulate patterns for reporting
|
||||
if (v3Global.opt.stats()) {
|
||||
if (v3Global.opt.dumpDfgPatterns()) {
|
||||
V3DfgPasses::dumpPatterns(acyclicComps);
|
||||
endOfStage("patterns");
|
||||
endOfStage("dumpPatterns");
|
||||
}
|
||||
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
|
||||
endOfStage("pushDownSels", dfg, acyclicComps);
|
||||
|
|
|
|||
|
|
@ -208,6 +208,9 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
size_t m_currentGeneration = 0; // Current generation number
|
||||
size_t m_lastId = 0; // Last unique vertex ID assigned
|
||||
size_t m_nTemps = 0; // Number of temporary variables created
|
||||
// Scope for transient temporariy variables cerated in this pass. They should all be
|
||||
// eliminated wihtin this pass, so anything should be ok, pick the top scope as easy to find.
|
||||
AstScope* const m_tmpScopep = v3Global.rootp()->topScopep()->scopep();
|
||||
|
||||
// STATIC STATE
|
||||
static V3DebugBisect s_debugBisect; // Debug aid
|
||||
|
|
@ -404,6 +407,12 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
return make<Vertex>(examplep->fileline(), examplep->dtype(), operands...);
|
||||
}
|
||||
|
||||
// Replicate 'bitp' to 'vtxp->width()' bits
|
||||
DfgVertex* replicate(DfgVertex* vtxp, DfgVertex* bitp) {
|
||||
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
|
||||
static bool isSame(const DfgVertex* ap, const DfgVertex* bp) {
|
||||
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()) {
|
||||
DfgVertex* const rlVtxp = rSamep->lhsp();
|
||||
DfgVertex* const rrVtxp = rSamep->rhsp();
|
||||
|
||||
if VL_CONSTEXPR_CXX17 (IsCommutative<Vertex>::value) {
|
||||
if (!lhsp->hasMultipleSinks() && rlVtxp->hasMultipleSinks()) {
|
||||
APPLYING(ROTATE_ASSOC_COMM_MULTIUSE) {
|
||||
replace(make<Vertex>(vtxp, rlVtxp, make<Vertex>(vtxp, lhsp, rrVtxp)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to reuse associative binary expressions if hey already exist, e.g.:
|
||||
// '(a OP (b OP c))' -> '(a OP b) OP c', iff '(a OP b)' already exists, or
|
||||
// '(a OP c) OP b' iff '(a OP c)' already exists and the vertex is commutative.
|
||||
// Only do this if 'b OP c' has a single use and can subsequently be removed,
|
||||
// otherwise there is no improvement.
|
||||
|
||||
// '(a OP (b OP c))' -> '(a OP b) OP c'
|
||||
if (Vertex* const existingp
|
||||
= m_cache.get<Vertex>(resultDType<Vertex>(lhsp, rlVtxp), lhsp, rlVtxp)) {
|
||||
|
|
@ -700,10 +719,10 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
tryPushBitwiseOpThroughConcat(Vertex* const vtxp, DfgConst* constp, DfgConcat* concatp) {
|
||||
FileLine* const flp = vtxp->fileline();
|
||||
|
||||
// If at least one of the sides of the Concat constant, or width 1 (i.e.: can be
|
||||
// further simplified), then push the Vertex past the Concat
|
||||
if (concatp->lhsp()->is<DfgConst>() || concatp->rhsp()->is<DfgConst>() //
|
||||
|| concatp->lhsp()->dtype() == m_bitDType || concatp->rhsp()->dtype() == m_bitDType) {
|
||||
// If at least one of the sides of the Concat constant, then push Vertex past Concat
|
||||
DfgConst* const catLConstp = concatp->lhsp()->cast<DfgConst>();
|
||||
DfgConst* const catRConstp = concatp->rhsp()->cast<DfgConst>();
|
||||
if (catLConstp || catRConstp) {
|
||||
APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) {
|
||||
const uint32_t width = concatp->width();
|
||||
const DfgDataType& lDtype = concatp->lhsp()->dtype();
|
||||
|
|
@ -712,14 +731,30 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
const uint32_t rWidth = rDtype.size();
|
||||
|
||||
// The new Lhs vertex
|
||||
DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
|
||||
newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
|
||||
Vertex* const newLhsp = make<Vertex>(flp, lDtype, newLhsConstp, concatp->lhsp());
|
||||
DfgVertex* const newLhsp = [&]() -> DfgVertex* {
|
||||
DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth);
|
||||
if (catLConstp) {
|
||||
V3Number num{constp->fileline(), static_cast<int>(lWidth), 0u};
|
||||
num.opSel(constp->num(), width - 1, rWidth);
|
||||
foldOp<Vertex>(newLhsConstp->num(), num, catLConstp->num());
|
||||
return newLhsConstp;
|
||||
}
|
||||
newLhsConstp->num().opSel(constp->num(), width - 1, rWidth);
|
||||
return make<Vertex>(flp, lDtype, newLhsConstp, concatp->lhsp());
|
||||
}();
|
||||
|
||||
// The new Rhs vertex
|
||||
DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
|
||||
newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
|
||||
Vertex* const newRhsp = make<Vertex>(flp, rDtype, newRhsConstp, concatp->rhsp());
|
||||
DfgVertex* const newRhsp = [&]() -> DfgVertex* {
|
||||
DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth);
|
||||
if (catRConstp) {
|
||||
V3Number num{constp->fileline(), static_cast<int>(rWidth), 0u};
|
||||
num.opSel(constp->num(), rWidth - 1, 0);
|
||||
foldOp<Vertex>(newRhsConstp->num(), num, catRConstp->num());
|
||||
return newRhsConstp;
|
||||
}
|
||||
newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0);
|
||||
return make<Vertex>(flp, rDtype, newRhsConstp, concatp->rhsp());
|
||||
}();
|
||||
|
||||
// Replace this vertex
|
||||
replace(make<DfgConcat>(concatp, newLhsp, newRhsp));
|
||||
|
|
@ -794,6 +829,46 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
return false;
|
||||
}
|
||||
|
||||
template <typename Bitwise>
|
||||
VL_ATTR_WARN_UNUSED_RESULT bool tryPushBitwiseOpThrougSel(Bitwise* const vtxp) {
|
||||
DfgVertex* const lhsp = vtxp->lhsp();
|
||||
DfgVertex* const rhsp = vtxp->rhsp();
|
||||
|
||||
if (DfgSel* const lSelp = lhsp->cast<DfgSel>()) {
|
||||
DfgSel* rSelp = nullptr;
|
||||
DfgVertex* extrap = nullptr;
|
||||
if (DfgSel* const selp = rhsp->cast<DfgSel>()) {
|
||||
rSelp = selp;
|
||||
} else if (Bitwise* const bitwisep = rhsp->cast<Bitwise>()) {
|
||||
if (DfgSel* const rlSelp = bitwisep->lhsp()->template cast<DfgSel>()) {
|
||||
rSelp = rlSelp;
|
||||
extrap = bitwisep->rhsp();
|
||||
} else if (DfgSel* const rrSelp = bitwisep->rhsp()->template cast<DfgSel>()) {
|
||||
rSelp = rrSelp;
|
||||
extrap = bitwisep->lhsp();
|
||||
}
|
||||
}
|
||||
if (rSelp) {
|
||||
DfgVertex* const lFromp = lSelp->fromp();
|
||||
DfgVertex* const rFromp = rSelp->fromp();
|
||||
if (lFromp->dtype() == rFromp->dtype() && lFromp->width() <= VL_QUADSIZE //
|
||||
&& lSelp->lsb() == rSelp->lsb()) {
|
||||
APPLYING(PUSH_BITWISE_THROUGH_SEL) {
|
||||
Bitwise* const bwp
|
||||
= make<Bitwise>(vtxp->fileline(), lSelp->fromp()->dtype(),
|
||||
lSelp->fromp(), rSelp->fromp());
|
||||
DfgVertex* resp = make<DfgSel>(vtxp, bwp, lSelp->lsb());
|
||||
if (extrap) resp = make<Bitwise>(vtxp, resp, extrap);
|
||||
replace(resp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Bitwise>
|
||||
VL_ATTR_WARN_UNUSED_RESULT bool tryReplaceBitwiseWithReduction(Bitwise* vtxp) {
|
||||
UASSERT_OBJ(vtxp->width() == 1, vtxp, "Width must be 1");
|
||||
|
|
@ -803,13 +878,15 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
DfgVertex* const rhsp = vtxp->rhsp();
|
||||
|
||||
if (DfgSel* const lSelp = lhsp->template cast<DfgSel>()) {
|
||||
DfgSel* rSelp = rhsp->template cast<DfgSel>();
|
||||
DfgSel* rSelp = nullptr;
|
||||
DfgVertex* extrap = nullptr;
|
||||
if (!rSelp) {
|
||||
if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) {
|
||||
rSelp = rBitwisep->lhsp()->template cast<DfgSel>();
|
||||
extrap = rBitwisep->rhsp();
|
||||
}
|
||||
if (DfgSel* const selp = rhsp->template cast<DfgSel>()) {
|
||||
rSelp = selp;
|
||||
} else if (Bitwise* const rBitwisep = rhsp->template cast<Bitwise>()) {
|
||||
rSelp = rBitwisep->lhsp()->template cast<DfgSel>();
|
||||
extrap = rBitwisep->rhsp();
|
||||
} else if (Reduction* const rRedp = rhsp->template cast<Reduction>()) {
|
||||
rSelp = rRedp->srcp()->template cast<DfgSel>();
|
||||
}
|
||||
if (rSelp) {
|
||||
uint32_t lsb = 0;
|
||||
|
|
@ -1021,6 +1098,94 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
return {nullptr, 0, 0};
|
||||
}
|
||||
|
||||
// The following patterns all unwind a Sel throgh it's source and replace it with
|
||||
// another single Sel. Doing this one at a time can take a long time with nested
|
||||
// concatenations/selects/etc, so instead unwind as much as possible in one go.
|
||||
std::pair<DfgVertex*, uint32_t> unwindSel(DfgVertex* fromp, uint32_t lsb,
|
||||
const uint32_t width) {
|
||||
while (true) {
|
||||
const uint32_t msb = lsb + width - 1;
|
||||
|
||||
// Sel from Concat
|
||||
if (DfgConcat* const concatp = fromp->cast<DfgConcat>()) {
|
||||
DfgVertex* const lhsp = concatp->lhsp();
|
||||
DfgVertex* const rhsp = concatp->rhsp();
|
||||
|
||||
if (msb < rhsp->width()) {
|
||||
// If the select is entirely from rhs, then replace with sel from rhs
|
||||
APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) {
|
||||
fromp = rhsp;
|
||||
continue;
|
||||
}
|
||||
} else if (lsb >= rhsp->width()) {
|
||||
// If the select is entirely from the lhs, then replace with sel from lhs
|
||||
APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) {
|
||||
fromp = lhsp;
|
||||
lsb -= rhsp->width();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (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
|
||||
|
||||
void visit(DfgVertex*) override {}
|
||||
|
|
@ -1148,38 +1313,12 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
// Sel from Concat
|
||||
if (DfgConcat* const concatp = fromp->cast<DfgConcat>()) {
|
||||
DfgVertex* const lhsp = concatp->lhsp();
|
||||
DfgVertex* const rhsp = concatp->rhsp();
|
||||
|
||||
if (msb < rhsp->width()) {
|
||||
// If the select is entirely from rhs, then replace with sel from rhs
|
||||
APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { //
|
||||
replace(make<DfgSel>(vtxp, rhsp, vtxp->lsb()));
|
||||
return;
|
||||
}
|
||||
} else if (lsb >= rhsp->width()) {
|
||||
// If the select is entirely from the lhs, then replace with sel from lhs
|
||||
APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) {
|
||||
replace(make<DfgSel>(vtxp, lhsp, lsb - rhsp->width()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DfgReplicate* const repp = fromp->cast<DfgReplicate>()) {
|
||||
// If the Sel is wholly into the source of the Replicate, push the Sel through the
|
||||
// Replicate and apply it directly to the source of the Replicate.
|
||||
const uint32_t srcWidth = repp->srcp()->width();
|
||||
if (width <= srcWidth) {
|
||||
const uint32_t newLsb = lsb % srcWidth;
|
||||
if (newLsb + width <= srcWidth) {
|
||||
APPLYING(PUSH_SEL_THROUGH_REPLICATE) {
|
||||
replace(make<DfgSel>(vtxp, repp->srcp(), newLsb));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Unwind through bit packing in one go
|
||||
{
|
||||
const auto res = unwindSel(fromp, lsb, width);
|
||||
if (res.first != fromp || res.second != lsb) {
|
||||
replace(make<DfgSel>(vtxp, res.first, res.second));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1197,15 +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
|
||||
if (DfgCond* const condp = fromp->cast<DfgCond>()) {
|
||||
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 {
|
||||
|
|
@ -1349,18 +1454,27 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
|
||||
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
|
||||
|
||||
if (DfgNot* const lhsNotp = lhsp->cast<DfgNot>()) {
|
||||
if (tryPushBitwiseOpThrougSel(vtxp)) return;
|
||||
|
||||
{
|
||||
DfgNot* const lNotp = lhsp->cast<DfgNot>();
|
||||
DfgNot* const rNotp = rhsp->cast<DfgNot>();
|
||||
// ~A & A is all zeroes
|
||||
if (lhsNotp->srcp() == rhsp) {
|
||||
if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
|
||||
APPLYING(REPLACE_CONTRADICTORY_AND) {
|
||||
replace(makeZero(flp, vtxp->width()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ~A & (A & _) or ~A & (_ & A) is all zeroes
|
||||
if (DfgAnd* const rhsAndp = rhsp->cast<DfgAnd>()) {
|
||||
if (lhsNotp->srcp() == rhsAndp->lhsp() || lhsNotp->srcp() == rhsAndp->rhsp()) {
|
||||
if (DfgAnd* const rSamep = rhsp->cast<DfgAnd>()) {
|
||||
DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
|
||||
DfgNot* const rrNotp = rSamep->rhsp()->cast<DfgNot>();
|
||||
// ~A & (A & _) or ~A & (_ & A) is all zeroes
|
||||
if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp()))
|
||||
|| (lNotp && isSame(lNotp->srcp(), rSamep->rhsp()))
|
||||
|| (rlNotp && isSame(lhsp, rlNotp->srcp()))
|
||||
|| (rrNotp && isSame(lhsp, rrNotp->srcp()))) {
|
||||
APPLYING(REPLACE_CONTRADICTORY_AND_3) {
|
||||
replace(makeZero(flp, vtxp->width()));
|
||||
return;
|
||||
|
|
@ -1463,24 +1577,29 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
|
||||
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
|
||||
|
||||
if (DfgNot* const lhsNotp = lhsp->cast<DfgNot>()) {
|
||||
if (tryPushBitwiseOpThrougSel(vtxp)) return;
|
||||
|
||||
{
|
||||
DfgNot* const lNotp = lhsp->cast<DfgNot>();
|
||||
DfgNot* const rNotp = rhsp->cast<DfgNot>();
|
||||
// ~A | A is all ones
|
||||
if (lhsNotp->srcp() == rhsp) {
|
||||
if ((lNotp && isSame(lNotp->srcp(), rhsp)) || (rNotp && isSame(lhsp, rNotp->srcp()))) {
|
||||
APPLYING(REPLACE_TAUTOLOGICAL_OR) {
|
||||
DfgConst* const resp = makeZero(flp, vtxp->width());
|
||||
resp->num().setAllBits1();
|
||||
replace(resp);
|
||||
replace(makeOnes(flp, vtxp->width()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ~A | (A | _) or ~A | (_ | A) is all ones
|
||||
if (DfgOr* const rhsOrp = rhsp->cast<DfgOr>()) {
|
||||
if (lhsNotp->srcp() == rhsOrp->lhsp() || lhsNotp->srcp() == rhsOrp->rhsp()) {
|
||||
if (DfgOr* const rSamep = rhsp->cast<DfgOr>()) {
|
||||
DfgNot* const rlNotp = rSamep->lhsp()->cast<DfgNot>();
|
||||
DfgNot* const rrNotp = rSamep->rhsp()->cast<DfgNot>();
|
||||
// ~A | (A | _) or ~A | (_ | A) is all ones
|
||||
if ((lNotp && isSame(lNotp->srcp(), rSamep->lhsp()))
|
||||
|| (lNotp && isSame(lNotp->srcp(), rSamep->rhsp()))
|
||||
|| (rlNotp && isSame(lhsp, rlNotp->srcp()))
|
||||
|| (rrNotp && isSame(lhsp, rrNotp->srcp()))) {
|
||||
APPLYING(REPLACE_TAUTOLOGICAL_OR_3) {
|
||||
DfgConst* const resp = makeZero(flp, vtxp->width());
|
||||
resp->num().setAllBits1();
|
||||
replace(resp);
|
||||
replace(makeOnes(flp, vtxp->width()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1525,6 +1644,8 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
|
||||
if (tryPushBitwiseOpThroughReductions(vtxp)) return;
|
||||
|
||||
if (tryPushBitwiseOpThrougSel(vtxp)) return;
|
||||
|
||||
if (vtxp->dtype() == m_bitDType) {
|
||||
if (tryReplaceBitwiseWithReduction(vtxp)) return;
|
||||
}
|
||||
|
|
@ -1847,10 +1968,9 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()};
|
||||
m_vInfo[sp].m_id = ++m_lastId;
|
||||
sp->addDriver(catp, lsb, flp);
|
||||
DfgVertex::ScopeCache scopeCache;
|
||||
AstScope* const scopep = vtxp->scopep(scopeCache, true);
|
||||
const std::string name = m_dfg.makeUniqueName("PeepholeNarrow", m_nTemps++);
|
||||
DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
|
||||
DfgVertexVar* const varp
|
||||
= m_dfg.makeNewVar(flp, name, vtxp->dtype(), m_tmpScopep);
|
||||
varp->tmpForp(varp->vscp());
|
||||
m_vInfo[varp].m_id = ++m_lastId;
|
||||
varp->vscp()->varp()->isInternal(true);
|
||||
|
|
@ -2511,53 +2631,63 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
}
|
||||
|
||||
if (vtxp->dtype() == m_bitDType) {
|
||||
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
|
||||
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
|
||||
replace(make<DfgAnd>(vtxp, make<DfgNot>(vtxp, condp), elsep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (thenp == condp) { // a ? a : b becomes a | b
|
||||
if (isSame(condp, thenp)) { // a ? a : b becomes a | b
|
||||
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) {
|
||||
replace(make<DfgOr>(vtxp, condp, elsep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (elsep == condp) { // a ? b : a becomes a & b
|
||||
if (isSame(condp, elsep)) { // a ? b : a becomes a & b
|
||||
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) {
|
||||
replace(make<DfgAnd>(vtxp, condp, thenp));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vtxp->width() <= VL_QUADSIZE) {
|
||||
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
|
||||
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
|
||||
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
|
||||
replace(make<DfgAnd>(vtxp, maskp, elsep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isOnes(thenp)) { // a ? 1 : b becomes a | b
|
||||
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) {
|
||||
replace(make<DfgOr>(vtxp, condp, elsep));
|
||||
DfgVertex* const maskp = replicate(vtxp, condp);
|
||||
replace(make<DfgOr>(vtxp, maskp, elsep));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isZero(elsep)) { // a ? b : 0 becomes a & b
|
||||
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) {
|
||||
replace(make<DfgAnd>(vtxp, condp, thenp));
|
||||
DfgVertex* const maskp = replicate(vtxp, condp);
|
||||
replace(make<DfgAnd>(vtxp, maskp, thenp));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b
|
||||
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) {
|
||||
replace(make<DfgOr>(vtxp, make<DfgNot>(vtxp, condp), thenp));
|
||||
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
|
||||
replace(make<DfgOr>(vtxp, maskp, thenp));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (DfgOr* const tOrp = thenp->cast<DfgOr>()) {
|
||||
if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c)
|
||||
APPLYING(REPLACE_COND_THEN_OR_LHS) {
|
||||
DfgAnd* const andp = make<DfgAnd>(vtxp, condp, tOrp->rhsp());
|
||||
DfgVertex* const maskp = replicate(vtxp, condp);
|
||||
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->rhsp());
|
||||
replace(make<DfgOr>(vtxp, tOrp->lhsp(), andp));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b)
|
||||
APPLYING(REPLACE_COND_THEN_OR_RHS) {
|
||||
DfgAnd* const andp = make<DfgAnd>(vtxp, condp, tOrp->lhsp());
|
||||
DfgVertex* const maskp = replicate(vtxp, condp);
|
||||
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->lhsp());
|
||||
replace(make<DfgOr>(vtxp, tOrp->rhsp(), andp));
|
||||
return;
|
||||
}
|
||||
|
|
@ -2590,6 +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)) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_SEL) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMMUTATIVE_BINARY_THROUGH_COND) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_COND_LHS) \
|
||||
|
|
@ -110,6 +111,7 @@
|
|||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \
|
||||
|
|
@ -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_RHS_OF_RHS) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, ROTATE_ASSOC_COMM_MULTIUSE) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \
|
||||
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_SIDES_IN_BINARY)
|
||||
|
|
|
|||
|
|
@ -195,24 +195,35 @@ class DfgRegularize final {
|
|||
// Scope cache for below
|
||||
DfgVertex::ScopeCache scopeCache;
|
||||
|
||||
// Ensure intermediate values used multiple times are written to variables
|
||||
// Build map from fanout to list of vertices with that fanout
|
||||
std::vector<std::vector<DfgVertex*>> fanout2Vtxps;
|
||||
for (DfgVertex& vtx : m_dfg.opVertices()) {
|
||||
// LValue vertices feed into variables eventually and need no temporaries
|
||||
if (vtx.is<DfgVertexSplice>()) continue;
|
||||
if (vtx.is<DfgUnitArray>()) continue;
|
||||
// Add to map
|
||||
const uint32_t fanout = vtx.fanout();
|
||||
if (!fanout) continue;
|
||||
if (fanout >= fanout2Vtxps.size()) fanout2Vtxps.resize(2 * fanout);
|
||||
fanout2Vtxps[fanout].push_back(&vtx);
|
||||
}
|
||||
if (fanout2Vtxps.empty()) return;
|
||||
|
||||
if (!needsTemporary(vtx, vtx)) continue;
|
||||
|
||||
// Need to create an intermediate variable
|
||||
++m_ctx.m_temporariesIntroduced;
|
||||
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
|
||||
FileLine* const flp = vtx.fileline();
|
||||
AstScope* const scopep = vtx.scopep(scopeCache);
|
||||
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtx.dtype(), scopep);
|
||||
++m_nTmps;
|
||||
// Replace vertex with the variable, make it drive the variable
|
||||
vtx.replaceWith(newp);
|
||||
newp->srcp(&vtx);
|
||||
// Ensure intermediate values used multiple times are written to variables
|
||||
for (size_t fanout = fanout2Vtxps.size() - 1; fanout > 0; --fanout) {
|
||||
for (DfgVertex* const vtxp : fanout2Vtxps[fanout]) {
|
||||
if (!needsTemporary(*vtxp, *vtxp)) continue;
|
||||
// Need to create an intermediate variable
|
||||
++m_ctx.m_temporariesIntroduced;
|
||||
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
|
||||
FileLine* const flp = vtxp->fileline();
|
||||
AstScope* const scopep = vtxp->scopep(scopeCache);
|
||||
DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep);
|
||||
++m_nTmps;
|
||||
// Replace vertex with the variable, make it drive the variable
|
||||
vtxp->replaceWith(newp);
|
||||
newp->srcp(vtxp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -826,6 +826,14 @@ public:
|
|||
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(nodep->linescov());
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
|
||||
puts(");\n");
|
||||
}
|
||||
void visit(AstCoverToggleDecl* nodep) override {
|
||||
|
|
|
|||
|
|
@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp);\n");
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
"fsmTagp);\n");
|
||||
}
|
||||
|
||||
if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) {
|
||||
|
|
@ -663,6 +665,7 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
|
||||
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
|
||||
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
|
||||
if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");
|
||||
|
||||
std::set<string> cuse_set;
|
||||
auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };
|
||||
|
|
|
|||
|
|
@ -180,7 +180,9 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp) {\n");
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
"fsmTagp) {\n");
|
||||
if (v3Global.opt.threads() > 1) {
|
||||
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
|
||||
puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n");
|
||||
|
|
@ -198,10 +200,14 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(" \"filename\",filenamep,");
|
||||
puts(" \"lineno\",lineno,");
|
||||
puts(" \"column\",column,\n");
|
||||
puts("\"hier\",fullhier,");
|
||||
puts("\"hier\",fullhier.c_str(),");
|
||||
puts(" \"page\",pagep,");
|
||||
puts(" \"comment\",commentp,");
|
||||
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n");
|
||||
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp,");
|
||||
puts(" (fsmVarp[0] ? \"fsm_var\" : \"\"), fsmVarp,");
|
||||
puts(" (fsmFromp[0] ? \"fsm_from\" : \"\"), fsmFromp,");
|
||||
puts(" (fsmTop[0] ? \"fsm_to\" : \"\"), fsmTop,");
|
||||
puts(" (fsmTagp[0] ? \"fsm_tag\" : \"\"), fsmTagp);\n");
|
||||
puts("}\n");
|
||||
}
|
||||
if (v3Global.opt.coverageToggle()) {
|
||||
|
|
@ -237,7 +243,7 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(" \"filename\",filenamep,");
|
||||
puts(" \"lineno\",lineno,");
|
||||
puts(" \"column\",column,\n");
|
||||
puts("\"hier\",fullhier,");
|
||||
puts("\"hier\",fullhier.c_str(),");
|
||||
puts(" \"page\",pagep,");
|
||||
puts(" \"comment\",commentWithIndex.c_str(),");
|
||||
puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ public:
|
|||
ENUMITEMWIDTH, // Error: enum item width mismatch
|
||||
ENUMVALUE, // Error: enum type needs explicit cast
|
||||
EOFNEWLINE, // End-of-file missing newline
|
||||
FSMMULTI, // Multiple FSM candidates in one always block
|
||||
FUNCTIMECTL, // Functions cannot have timing/delay/wait
|
||||
FUTURE, // Feature is under development and not yet supported
|
||||
GENCLK, // Generated Clock. Historical, never issued.
|
||||
|
|
@ -223,14 +224,14 @@ public:
|
|||
"BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST",
|
||||
"CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN",
|
||||
"CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
|
||||
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL",
|
||||
"FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN",
|
||||
"IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH",
|
||||
"INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN",
|
||||
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP",
|
||||
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT",
|
||||
"PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL",
|
||||
"PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
|
||||
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI",
|
||||
"FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH",
|
||||
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
|
||||
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH",
|
||||
"LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN",
|
||||
"MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT",
|
||||
"PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
|
||||
"PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
|
||||
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
|
||||
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
|
||||
"SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",
|
||||
|
|
|
|||
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_usesStdPackage = false; // Design uses the std package
|
||||
bool m_usesTiming = false; // Design uses timing constructs
|
||||
bool m_usesForce = false; // Design uses force/release statements
|
||||
bool m_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay)
|
||||
bool m_hasForceableSignals = false; // Need to apply V3Force pass
|
||||
bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted
|
||||
|
|
@ -205,6 +206,8 @@ public:
|
|||
void setUsesZeroDelay() { m_usesZeroDelay = true; }
|
||||
bool hasForceableSignals() const { return m_hasForceableSignals; }
|
||||
void setHasForceableSignals() { m_hasForceableSignals = true; }
|
||||
bool usesForce() const { return m_usesForce; }
|
||||
void setUsesForce() { m_usesForce = true; }
|
||||
bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; }
|
||||
void setHasSystemCSections() { m_hasSystemCSections = true; }
|
||||
V3HierGraph* hierGraphp() const { return m_hierGraphp; }
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
|||
// Changes user() and weight()
|
||||
|
||||
class GraphRemoveRedundant final : GraphAlg<> {
|
||||
const bool m_sumWeights; ///< Sum, rather then maximize weights
|
||||
const bool m_sumWeights; // Sum, rather then maximize weights
|
||||
private:
|
||||
void main() {
|
||||
for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex);
|
||||
|
|
|
|||
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
|
||||
private:
|
||||
// STATE
|
||||
// Range for arrayed instantiations, nullptr for normal instantiations
|
||||
const AstRange* m_cellRangep = nullptr;
|
||||
int m_instSelNum = 0; // Current instantiation count 0..N-1
|
||||
const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells
|
||||
int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion
|
||||
InstDeModVarVisitor m_deModVars; // State of variables for current cell module
|
||||
|
||||
// VISITORS
|
||||
|
|
@ -233,52 +232,72 @@ private:
|
|||
m_deModVars.main(nodep->modp());
|
||||
//
|
||||
if (nodep->rangep()) {
|
||||
m_cellRangep = nodep->rangep();
|
||||
// Collect the full range chain (outer first).
|
||||
std::vector<const AstRange*> rangesp;
|
||||
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
|
||||
rangesp.push_back(rp);
|
||||
}
|
||||
m_cellRangep = rangesp.front();
|
||||
const int ndim = static_cast<int>(rangesp.size());
|
||||
std::vector<int> sizes(ndim);
|
||||
int totalElems = 1;
|
||||
for (int d = 0; d < ndim; ++d) {
|
||||
sizes[d] = rangesp[d]->elementsConst();
|
||||
totalElems *= sizes[d];
|
||||
}
|
||||
|
||||
AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var);
|
||||
// cppcheck-suppress constVariablePointer
|
||||
AstNodeDType* const ifaceVarDtp
|
||||
= ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr;
|
||||
const bool isIface
|
||||
= ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType)
|
||||
&& VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
|
||||
IfaceRefDType)
|
||||
&& !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
|
||||
IfaceRefDType)
|
||||
->isVirtual();
|
||||
// Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType.
|
||||
AstIfaceRefDType* origIfaceRefp = nullptr;
|
||||
AstUnpackArrayDType* innermostArrp = nullptr;
|
||||
for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) {
|
||||
if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) {
|
||||
innermostArrp = arrp;
|
||||
dp = arrp->subDTypep()->skipRefp();
|
||||
} else {
|
||||
origIfaceRefp = VN_CAST(dp, IfaceRefDType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual();
|
||||
|
||||
// Make all of the required clones
|
||||
for (int i = 0; i < m_cellRangep->elementsConst(); i++) {
|
||||
m_instSelNum
|
||||
= m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i;
|
||||
const int instNum = m_cellRangep->loConst() + i;
|
||||
std::vector<int> idx(ndim, 0);
|
||||
for (int n = 0; n < totalElems; ++n) {
|
||||
// Unflatten n into a row-major cartesian index; outer dim most significant.
|
||||
int rem = n;
|
||||
for (int d = ndim - 1; d >= 0; --d) {
|
||||
idx[d] = rem % sizes[d];
|
||||
rem /= sizes[d];
|
||||
}
|
||||
// Flat select number for 1D-compat pin expansion; ascending dims invert.
|
||||
// Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx).
|
||||
int flatSel = 0;
|
||||
string suffix;
|
||||
for (int d = 0; d < ndim; ++d) {
|
||||
const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d];
|
||||
flatSel = flatSel * sizes[d] + sel;
|
||||
suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d])
|
||||
+ "__KET__";
|
||||
}
|
||||
m_instSelNum = flatSel;
|
||||
|
||||
AstCell* const newp = nodep->cloneTree(false);
|
||||
nodep->addNextHere(newp);
|
||||
// Remove ranging and fix name
|
||||
newp->rangep()->unlinkFrBack()->deleteTree();
|
||||
// Somewhat illogically, we need to rename the original name of the cell too.
|
||||
// as that is the name users expect for dotting
|
||||
// The spec says we add [x], but that won't work in C...
|
||||
newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
|
||||
newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__");
|
||||
while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
|
||||
newp->name(newp->name() + suffix);
|
||||
newp->origName(newp->origName() + suffix);
|
||||
UINFO(8, " CELL loop " << newp);
|
||||
|
||||
// If this AstCell is actually an interface instantiation, also clone the IfaceRef
|
||||
// within the same parent module as the cell
|
||||
// Interface instantiation: also clone the IfaceRef in the parent module.
|
||||
if (isIface) {
|
||||
AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType);
|
||||
AstIfaceRefDType* const origIfaceRefp
|
||||
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType);
|
||||
origIfaceRefp->cellp(nullptr);
|
||||
AstVar* const varNewp = ifaceVarp->cloneTree(false);
|
||||
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
|
||||
arrdtype->addNextHere(ifaceRefp);
|
||||
innermostArrp->addNextHere(ifaceRefp);
|
||||
ifaceRefp->cellp(newp);
|
||||
ifaceRefp->cellName(newp->name());
|
||||
varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
|
||||
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum)
|
||||
+ "__KET__");
|
||||
varNewp->name(varNewp->name() + suffix);
|
||||
varNewp->origName(varNewp->origName() + suffix);
|
||||
varNewp->dtypep(ifaceRefp);
|
||||
newp->addNextHere(varNewp);
|
||||
if (debug() == 9) {
|
||||
|
|
@ -389,6 +408,105 @@ private:
|
|||
}
|
||||
} else {
|
||||
AstVar* const pinVarp = nodep->modVarp();
|
||||
// Multi-dim whole-array iface pin fanout: cartesian-product the port's
|
||||
// nested UnpackArrayDType layers and emit one pin + per-element var per cell.
|
||||
// For 1-dim falls through to the original code below.
|
||||
std::vector<const AstUnpackArrayDType*> portArrs;
|
||||
for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) {
|
||||
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
|
||||
portArrs.push_back(arrp);
|
||||
d = arrp->subDTypep()->skipRefp();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (portArrs.size() >= 2) {
|
||||
AstIfaceRefDType* const portIrp
|
||||
= VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType);
|
||||
if (!portIrp || portIrp->isVirtual()) return;
|
||||
const int ndim = static_cast<int>(portArrs.size());
|
||||
std::vector<int> sizes(ndim);
|
||||
int totalElems = 1;
|
||||
for (int d = 0; d < ndim; ++d) {
|
||||
sizes[d] = portArrs[d]->elementsConst();
|
||||
totalElems *= sizes[d];
|
||||
}
|
||||
// V3Width should have already rejected non-VarRef and rank-mismatch pins;
|
||||
// these are defensive internal-invariant guards.
|
||||
const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef);
|
||||
UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port");
|
||||
std::vector<const AstUnpackArrayDType*> exprArrs;
|
||||
for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) {
|
||||
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
|
||||
exprArrs.push_back(arrp);
|
||||
d = arrp->subDTypep()->skipRefp();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
UASSERT_OBJ(exprArrs.size() == static_cast<size_t>(ndim), nodep->exprp(),
|
||||
"Multi-dim iface pin expression rank does not match port");
|
||||
AstNode* prevp = nullptr;
|
||||
AstNode* prevPinp = nullptr;
|
||||
std::vector<int> idx(ndim, 0);
|
||||
for (int n = 0; n < totalElems; ++n) {
|
||||
int rem = n;
|
||||
for (int d = ndim - 1; d >= 0; --d) {
|
||||
idx[d] = rem % sizes[d];
|
||||
rem /= sizes[d];
|
||||
}
|
||||
string portSuffix;
|
||||
string exprSuffix;
|
||||
for (int d = 0; d < ndim; ++d) {
|
||||
portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d])
|
||||
+ "__KET__";
|
||||
exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d])
|
||||
+ "__KET__";
|
||||
}
|
||||
const string varNewName = pinVarp->name() + portSuffix;
|
||||
AstVar* varNewp = nullptr;
|
||||
if (!pinVarp->backp()) {
|
||||
varNewp = m_deModVars.find(varNewName);
|
||||
} else {
|
||||
portIrp->cellp(nullptr);
|
||||
varNewp = pinVarp->cloneTree(false);
|
||||
varNewp->name(varNewName);
|
||||
varNewp->origName(varNewp->origName() + portSuffix);
|
||||
varNewp->dtypep(portIrp);
|
||||
m_deModVars.insert(varNewp);
|
||||
if (!prevp) {
|
||||
prevp = varNewp;
|
||||
} else {
|
||||
prevp->addNextHere(varNewp);
|
||||
}
|
||||
}
|
||||
if (!varNewp) {
|
||||
if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE
|
||||
nodep->v3fatalSrc("Module dearray failed for "
|
||||
<< AstNode::prettyNameQ(varNewName));
|
||||
}
|
||||
AstPin* const newp = nodep->cloneTree(false);
|
||||
newp->modVarp(varNewp);
|
||||
newp->name(newp->name() + portSuffix);
|
||||
AstVarXRef* const newVarXRefp = new AstVarXRef{
|
||||
nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE};
|
||||
newVarXRefp->varp(newp->modVarp());
|
||||
newp->exprp()->unlinkFrBack()->deleteTree();
|
||||
newp->exprp(newVarXRefp);
|
||||
if (!prevPinp) {
|
||||
prevPinp = newp;
|
||||
} else {
|
||||
prevPinp->addNextHere(newp);
|
||||
}
|
||||
}
|
||||
if (prevp) {
|
||||
pinVarp->replaceWith(prevp);
|
||||
pushDeletep(pinVarp);
|
||||
}
|
||||
nodep->replaceWith(prevPinp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
return;
|
||||
}
|
||||
const AstUnpackArrayDType* const pinArrp
|
||||
= VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType);
|
||||
if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
|
||||
|
|
@ -472,28 +590,50 @@ private:
|
|||
}
|
||||
}
|
||||
void visit(AstArraySel* nodep) override {
|
||||
if (const AstUnpackArrayDType* const arrp
|
||||
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
|
||||
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
|
||||
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
|
||||
V3Const::constifyParamsEdit(nodep->bitp());
|
||||
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
|
||||
// If a parent is also an ArraySel into the same iface array, let it handle the chain.
|
||||
if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return;
|
||||
// Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index).
|
||||
std::vector<AstArraySel*> sels;
|
||||
AstNode* curp = nodep;
|
||||
while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) {
|
||||
sels.push_back(asp);
|
||||
curp = asp->fromp();
|
||||
}
|
||||
const AstVarRef* const varrefp = VN_CAST(curp, VarRef);
|
||||
if (!varrefp) return;
|
||||
// Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType.
|
||||
std::vector<AstUnpackArrayDType*> arrs; // outer dim first
|
||||
AstNodeDType* dtp = varrefp->dtypep()->skipRefp();
|
||||
while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) {
|
||||
arrs.push_back(ap);
|
||||
dtp = ap->subDTypep()->skipRefp();
|
||||
}
|
||||
if (arrs.empty() || sels.size() != arrs.size()) return;
|
||||
AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType);
|
||||
if (!irp || irp->isVirtual()) return;
|
||||
// Constify bitps and collect indices in outer-dim-first order (sels is inner-first).
|
||||
std::vector<int> indices(sels.size());
|
||||
for (size_t i = 0; i < sels.size(); ++i) {
|
||||
AstArraySel* const asp = sels[i];
|
||||
V3Const::constifyParamsEdit(asp->bitp());
|
||||
const AstConst* const constp = VN_CAST(asp->bitp(), Const);
|
||||
if (!constp) {
|
||||
nodep->bitp()->v3warn(E_UNSUPPORTED,
|
||||
"Non-constant index in RHS interface array selection");
|
||||
asp->bitp()->v3warn(E_UNSUPPORTED,
|
||||
"Non-constant index in RHS interface array selection");
|
||||
return;
|
||||
}
|
||||
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo());
|
||||
const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
|
||||
UASSERT_OBJ(varrefp, nodep, "No interface varref under array");
|
||||
AstVarXRef* const newp = new AstVarXRef{
|
||||
nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "",
|
||||
VAccess::READ};
|
||||
newp->dtypep(arrp->subDTypep());
|
||||
newp->classOrPackagep(varrefp->classOrPackagep());
|
||||
nodep->addNextHere(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
indices[sels.size() - 1 - i] = constp->toSInt();
|
||||
}
|
||||
string indexStr;
|
||||
for (size_t i = 0; i < indices.size(); ++i) {
|
||||
indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__";
|
||||
}
|
||||
AstVarXRef* const newp
|
||||
= new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ};
|
||||
newp->dtypep(irp);
|
||||
newp->classOrPackagep(varrefp->classOrPackagep());
|
||||
nodep->addNextHere(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
||||
}
|
||||
void visit(AstNodeAssign* nodep) override {
|
||||
if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) {
|
||||
|
|
|
|||
|
|
@ -776,12 +776,19 @@ class LinkCellsVisitor final : public VNVisitor {
|
|||
idtypep->cellp(nodep); // Only set when real parent cell known.
|
||||
AstVar* varp;
|
||||
if (nodep->rangep()) {
|
||||
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst
|
||||
AstNodeArrayDType* const arrp
|
||||
= new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep,
|
||||
nodep->rangep()->cloneTree(true)};
|
||||
// For arrayed interfaces, we replace cellp when de-arraying in V3Inst.
|
||||
// Multi-dim arrays wrap one UnpackArrayDType per range, innermost first.
|
||||
std::vector<AstRange*> rangesp;
|
||||
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
|
||||
rangesp.push_back(rp);
|
||||
}
|
||||
AstNodeDType* dtp = idtypep;
|
||||
for (auto it = rangesp.rbegin(); it != rangesp.rend(); ++it) {
|
||||
dtp = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, dtp,
|
||||
(*it)->cloneTree(false)};
|
||||
}
|
||||
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
|
||||
VFlagChildDType{}, arrp};
|
||||
VFlagChildDType{}, dtp};
|
||||
} else {
|
||||
varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName,
|
||||
VFlagChildDType{}, idtypep};
|
||||
|
|
|
|||
|
|
@ -524,17 +524,19 @@ public:
|
|||
void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE
|
||||
m_ifaceVarSyms.push_back(symp);
|
||||
}
|
||||
// Iface for a raw or arrayed iface
|
||||
// Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays.
|
||||
static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) {
|
||||
AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType);
|
||||
if (!ifacerefp) {
|
||||
while (nodep) {
|
||||
if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp;
|
||||
if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) {
|
||||
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
|
||||
nodep = arrp->subDTypep();
|
||||
} else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) {
|
||||
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
|
||||
nodep = arrp->subDTypep();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return ifacerefp;
|
||||
return nullptr;
|
||||
}
|
||||
// Given a pin expression, resolve it to a live AstIface* (or nullptr).
|
||||
// Handles both simple VarRef and dotted VarXRef pin connections.
|
||||
|
|
@ -770,10 +772,16 @@ public:
|
|||
if (forPrearray()) {
|
||||
// GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled,
|
||||
// but presently should be just "foo".
|
||||
// Likewise cell foo__[array] before we've expanded arrays is just foo
|
||||
if ((pos = ident.rfind("__BRA__")) != string::npos) {
|
||||
altIdent = ident.substr(0, pos);
|
||||
// Likewise cell foo__[array] before we've expanded arrays is just foo.
|
||||
// Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them
|
||||
// all.
|
||||
altIdent = ident;
|
||||
while (VString::endsWith(altIdent, "__KET__")) {
|
||||
const auto braPos = altIdent.rfind("__BRA__");
|
||||
if (braPos == string::npos) break;
|
||||
altIdent = altIdent.substr(0, braPos);
|
||||
}
|
||||
if (altIdent == ident) altIdent.clear();
|
||||
}
|
||||
UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname
|
||||
<< " at se" << lookupSymp);
|
||||
|
|
@ -5316,10 +5324,18 @@ class LinkDotResolveVisitor final : public VNVisitor {
|
|||
symIterateNull(nodep->attrp(), m_curSymp);
|
||||
if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) {
|
||||
AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack();
|
||||
AstCellArrayRef* const newp
|
||||
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) {
|
||||
// Multi-dim iface array access: append this select to the existing chain
|
||||
fromArrRefp->addSelp(exprp);
|
||||
AstCellArrayRef* const movedp = fromArrRefp->unlinkFrBack();
|
||||
nodep->replaceWith(movedp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
} else {
|
||||
AstCellArrayRef* const newp
|
||||
= new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp};
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
}
|
||||
}
|
||||
void visit(AstNodePreSel* nodep) override {
|
||||
|
|
|
|||
|
|
@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor {
|
|||
m_varp->attrSplitVar(true);
|
||||
}
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_ARC_INCLUDE_COND) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmArcInclCond(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_RESET_ARC) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmResetArc(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_STATE) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmState(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrScBigUint(true);
|
||||
|
|
|
|||
|
|
@ -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-expr", OnOff, &m_coverageExpr);
|
||||
DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax);
|
||||
DECL_OPTION("-coverage-fsm", OnOff, &m_coverageFsm);
|
||||
DECL_OPTION("-coverage-line", OnOff, &m_coverageLine);
|
||||
DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth);
|
||||
DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle);
|
||||
|
|
@ -2295,7 +2296,10 @@ void V3Options::setDebugMode(int level) {
|
|||
if (!m_dumpLevel.count("tree")) m_dumpLevel["tree"] = 3; // Don't override if already set.
|
||||
m_stats = true;
|
||||
m_debugCheck = true;
|
||||
if (level) cout << "Starting " << version() << "\n";
|
||||
if (level) {
|
||||
cout << "- Starting " << version() << "\n";
|
||||
UINFO(1, "Current working directory (CWD) is " << V3Os::cwd());
|
||||
}
|
||||
}
|
||||
|
||||
unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE {
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ private:
|
|||
bool m_build = false; // main switch: --build
|
||||
bool m_context = true; // main switch: --Wcontext
|
||||
bool m_coverageExpr = false; // main switch: --coverage-expr
|
||||
bool m_coverageFsm = false; // main switch: --coverage-fsm
|
||||
bool m_coverageLine = false; // main switch: --coverage-block
|
||||
bool m_coverageToggle = false; // main switch: --coverage-toggle
|
||||
bool m_coverageUnderscore = false; // main switch: --coverage-underscore
|
||||
|
|
@ -447,7 +448,7 @@ private:
|
|||
void optimize(int level);
|
||||
void showVersion(bool verbose);
|
||||
void coverage(bool flag) {
|
||||
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageUser = flag;
|
||||
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageFsm = m_coverageUser = flag;
|
||||
}
|
||||
static bool suffixed(const string& sw, const char* arg);
|
||||
static string parseFileArg(const string& optdir, const string& relfilename);
|
||||
|
|
@ -508,9 +509,19 @@ public:
|
|||
void buildDepBin(const string& flag) { m_buildDepBin = flag; }
|
||||
bool context() const VL_MT_SAFE { return m_context; }
|
||||
bool coverage() const VL_MT_SAFE {
|
||||
// Any enabled coverage kind, including FSM coverage. Code generation
|
||||
// and runtime support should generally query this accessor.
|
||||
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser
|
||||
|| m_coverageFsm;
|
||||
}
|
||||
bool coverageNonFsm() const VL_MT_SAFE {
|
||||
// The broad line/toggle/expr/user coverage transforms use this
|
||||
// accessor. FSM coverage shares the overall coverage umbrella, but its
|
||||
// extraction still happens through a separate early-recognition path.
|
||||
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser;
|
||||
}
|
||||
bool coverageExpr() const { return m_coverageExpr; }
|
||||
bool coverageFsm() const { return m_coverageFsm; }
|
||||
bool coverageLine() const { return m_coverageLine; }
|
||||
bool coverageToggle() const { return m_coverageToggle; }
|
||||
bool coverageUnderscore() const { return m_coverageUnderscore; }
|
||||
|
|
@ -534,6 +545,9 @@ public:
|
|||
bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; }
|
||||
bool dpiHdrOnly() const { return m_dpiHdrOnly; }
|
||||
bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); }
|
||||
bool dumpDfgPatterns() const {
|
||||
return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns");
|
||||
}
|
||||
bool dumpTreeDot() const {
|
||||
return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot");
|
||||
}
|
||||
|
|
|
|||
157
src/V3Param.cpp
157
src/V3Param.cpp
|
|
@ -475,6 +475,12 @@ class ParamProcessor final {
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
// Peel all unpacked-array layers to reach the innermost subDTypep.
|
||||
// Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType.
|
||||
static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) {
|
||||
while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp;
|
||||
return nodep;
|
||||
}
|
||||
static bool isString(AstNodeDType* nodep) {
|
||||
if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType))
|
||||
return basicp->isString();
|
||||
|
|
@ -1406,13 +1412,16 @@ class ParamProcessor final {
|
|||
}
|
||||
cloneVarp->valuep(exprp->cloneTree(false));
|
||||
if (AstNodeDType* const origDTypep = modvarp->subDTypep()) {
|
||||
AstNodeDType* const dtypeClonep = origDTypep->cloneTree(false);
|
||||
// Inline every param ref so widthing doesn't reach back into the template
|
||||
// (#7411). Cycle detector for dependent parameters in the same module.
|
||||
// Attach clone under cloneVarp so the root has a back pointer.
|
||||
if (cloneVarp->childDTypep())
|
||||
cloneVarp->childDTypep()->unlinkFrBack()->deleteTree();
|
||||
cloneVarp->childDTypep(origDTypep->cloneTree(false));
|
||||
cloneVarp->dtypep(nullptr);
|
||||
// Inline param refs so widthing doesn't touch the template (#7411).
|
||||
constexpr int maxSubstIters = 1000;
|
||||
for (int it = 0; it < maxSubstIters; ++it) {
|
||||
bool any = false;
|
||||
dtypeClonep->foreach([&](AstVarRef* varrefp) {
|
||||
cloneVarp->foreach([&](AstVarRef* varrefp) {
|
||||
AstVar* const targetp = varrefp->varp();
|
||||
AstNode* replacep = nullptr;
|
||||
for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) {
|
||||
|
|
@ -1432,19 +1441,40 @@ class ParamProcessor final {
|
|||
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;
|
||||
}
|
||||
// Bail if anything still points at the template.
|
||||
dtypeClonep->foreach([&](AstVarRef* varrefp) {
|
||||
cloneVarp->foreach([&](AstVarRef* varrefp) {
|
||||
varrefp->v3fatalSrc(
|
||||
"Unresolved VarRef '"
|
||||
<< varrefp->prettyName() << "' in pin dtype clone. Pin: "
|
||||
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
|
||||
});
|
||||
if (cloneVarp->childDTypep())
|
||||
cloneVarp->childDTypep()->unlinkFrBack()->deleteTree();
|
||||
cloneVarp->childDTypep(dtypeClonep);
|
||||
cloneVarp->dtypep(nullptr);
|
||||
}
|
||||
V3Const::constifyParamsEdit(cloneVarp);
|
||||
if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) {
|
||||
|
|
@ -1589,38 +1619,32 @@ class ParamProcessor final {
|
|||
const AstVar* const modvarp = pinp->modVarp();
|
||||
if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue;
|
||||
if (modvarp->isIfaceRef()) {
|
||||
AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType);
|
||||
if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) {
|
||||
portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType);
|
||||
}
|
||||
AstIfaceRefDType* pinIrefp = nullptr;
|
||||
// arraySubDTypeDeepp returns input unchanged if not an array.
|
||||
AstIfaceRefDType* const portIrefp
|
||||
= VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType);
|
||||
const AstNode* const exprp = pinp->exprp();
|
||||
const AstVar* const varp = (exprp && VN_IS(exprp, NodeVarRef))
|
||||
? VN_AS(exprp, NodeVarRef)->varp()
|
||||
: nullptr;
|
||||
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
|
||||
pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType);
|
||||
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
|
||||
&& VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) {
|
||||
pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType);
|
||||
} else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef)
|
||||
&& VN_CAST(exprp->op1p(), VarRef)->varp()
|
||||
&& VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()
|
||||
&& arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep())
|
||||
&& VN_CAST(
|
||||
arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()),
|
||||
IfaceRefDType)) {
|
||||
pinIrefp
|
||||
= VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()),
|
||||
IfaceRefDType);
|
||||
} else if (VN_IS(exprp, CellArrayRef)) {
|
||||
// Interface array element selection (e.g., l1(l2.l1[0]) for nested iface
|
||||
// array) The CellArrayRef is not yet fully linked to an interface type. Skip
|
||||
// interface cleanup for this pin - V3LinkDot will resolve this later. Just
|
||||
// continue to the next pin without error.
|
||||
if (VN_IS(exprp, CellArrayRef)) {
|
||||
// CellArrayRef not yet linked; V3LinkDot resolves this pin later.
|
||||
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp);
|
||||
continue;
|
||||
}
|
||||
AstIfaceRefDType* pinIrefp = nullptr;
|
||||
// Pin is a VarRef to a var of (possibly arrayed) iface type.
|
||||
if (const AstNodeVarRef* const vrp = VN_CAST(exprp, NodeVarRef)) {
|
||||
if (vrp->varp()) {
|
||||
pinIrefp
|
||||
= VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType);
|
||||
}
|
||||
}
|
||||
// Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array).
|
||||
if (!pinIrefp && exprp) {
|
||||
if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) {
|
||||
if (vrp->varp()) {
|
||||
pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()),
|
||||
IfaceRefDType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UINFO(9, " portIfaceRef " << portIrefp);
|
||||
|
||||
|
|
@ -2813,33 +2837,46 @@ class ParamVisitor final : public VNVisitor {
|
|||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
void visit(AstCellArrayRef* nodep) override {
|
||||
V3Const::constifyParamsEdit(nodep->selp());
|
||||
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) {
|
||||
const string index = AstNode::encodeNumber(constp->toSInt());
|
||||
// For nested interface array ports, the node name may have a __Viftop suffix
|
||||
// that doesn't exist in the original unlinked text. Try without the suffix.
|
||||
const string viftopSuffix = "__Viftop";
|
||||
const string baseName
|
||||
= VString::endsWith(nodep->name(), viftopSuffix)
|
||||
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
|
||||
: nodep->name();
|
||||
const string replacestr = baseName + "__BRA__??__KET__";
|
||||
const size_t pos = m_unlinkedTxt.find(replacestr);
|
||||
// For interface port array element selections (e.g., l1(l2.l1[0])),
|
||||
// the AstCellArrayRef may be visited outside of an AstUnlinkedRef context.
|
||||
// In such cases, m_unlinkedTxt won't contain the expected pattern.
|
||||
// Simply skip the replacement - the cell array ref will be resolved later.
|
||||
if (pos == string::npos) {
|
||||
UINFO(9, "Skipping unlinked text replacement for " << nodep);
|
||||
// Multi-dim iface arrays chain multiple selps via nextp(); constify each in place.
|
||||
for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) {
|
||||
nxt = s->nextp(); // cache before constifyParamsEdit replaces s
|
||||
V3Const::constifyParamsEdit(s);
|
||||
}
|
||||
std::vector<int> indices;
|
||||
for (AstNode* s = nodep->selp(); s; s = s->nextp()) {
|
||||
const AstConst* const constp = VN_CAST(s, Const);
|
||||
if (!constp) {
|
||||
nodep->v3error("Could not expand constant selection inside dotted reference: "
|
||||
<< s->prettyNameQ());
|
||||
return;
|
||||
}
|
||||
m_unlinkedTxt.replace(pos, replacestr.length(),
|
||||
baseName + "__BRA__" + index + "__KET__");
|
||||
} else {
|
||||
nodep->v3error("Could not expand constant selection inside dotted reference: "
|
||||
<< nodep->selp()->prettyNameQ());
|
||||
indices.push_back(constp->toSInt());
|
||||
}
|
||||
// Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt.
|
||||
const string viftopSuffix = "__Viftop";
|
||||
const string baseName
|
||||
= VString::endsWith(nodep->name(), viftopSuffix)
|
||||
? nodep->name().substr(0, nodep->name().size() - viftopSuffix.size())
|
||||
: nodep->name();
|
||||
const string placeholder = "__BRA__??__KET__";
|
||||
size_t pos = m_unlinkedTxt.find(baseName + placeholder);
|
||||
// An AstCellArrayRef for an iface-array pin expr (e.g. l1(l2.l1[0])) is visited
|
||||
// outside AstUnlinkedRef, so m_unlinkedTxt has no placeholder; V3LinkDot resolves later.
|
||||
if (pos == string::npos) {
|
||||
UINFO(9, "Skipping unlinked text replacement for " << nodep);
|
||||
return;
|
||||
}
|
||||
pos += baseName.length();
|
||||
for (int idx : indices) {
|
||||
const string replacement = "__BRA__" + AstNode::encodeNumber(idx) + "__KET__";
|
||||
if (m_unlinkedTxt.compare(pos, placeholder.length(), placeholder) != 0) {
|
||||
nodep->v3fatalSrc( // LCOV_EXCL_LINE
|
||||
"Expected placeholder at position in multi-dim iface dotted reference");
|
||||
return;
|
||||
}
|
||||
m_unlinkedTxt.replace(pos, placeholder.length(), replacement);
|
||||
pos += replacement.length();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Statements
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n
|
|||
return assignp;
|
||||
}
|
||||
|
||||
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
|
||||
// Remove any UnsizedRange's from list
|
||||
AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) {
|
||||
// Remove any UnsizedRange's from list; preserves multi-dim chain via nextp().
|
||||
for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) {
|
||||
nextp = VN_AS(nodep->nextp(), NodeRange);
|
||||
if (!VN_IS(nodep, Range)) {
|
||||
|
|
@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
|
|||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
}
|
||||
if (nrangep && nrangep->nextp()) {
|
||||
// Not supported by at least 2 of big 3
|
||||
nrangep->nextp()->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: Multidimensional instances/interfaces.");
|
||||
nrangep->nextp()->unlinkFrBackWithNext()->deleteTree();
|
||||
}
|
||||
return VN_CAST(nrangep, Range);
|
||||
}
|
||||
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
|
||||
AstRange* const rangep = scrubRangeMulti(nrangep);
|
||||
if (rangep && rangep->nextp()) {
|
||||
// Gate primitives only support a single dimension
|
||||
rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances.");
|
||||
rangep->nextp()->unlinkFrBackWithNext()->deleteTree();
|
||||
}
|
||||
return rangep;
|
||||
}
|
||||
|
||||
AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED {
|
||||
// SEL(PARSELVALUE, ...) -> SEL(fromp, ...)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ public:
|
|||
return v3Global.opt.trace() && m_tracingParse && fl->tracingOn();
|
||||
}
|
||||
AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED;
|
||||
AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED;
|
||||
AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED;
|
||||
AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep,
|
||||
bool isPacked) VL_MT_DISABLED;
|
||||
|
|
@ -90,7 +91,7 @@ public:
|
|||
singletonp()->m_instModule,
|
||||
pinlistp,
|
||||
(singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr),
|
||||
singletonp()->scrubRange(rangelistp)};
|
||||
singletonp()->scrubRangeMulti(rangelistp)};
|
||||
nodep->trace(singletonp()->allTracingOn(fileline));
|
||||
return nodep;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,6 +263,10 @@ class PremitVisitor final : public VNVisitor {
|
|||
iterateChildren(nodep);
|
||||
checkNode(nodep);
|
||||
}
|
||||
void visit(AstCMethodHard* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
checkNode(nodep);
|
||||
}
|
||||
void visit(AstCvtArrayToPacked* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
checkNode(nodep);
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ class ProtectVisitor final : public VNVisitor {
|
|||
+ "_protectlib_final(chandle handle__V);\n\n");
|
||||
|
||||
// Local variables
|
||||
// Avoid tracing handle, as it is not a stable value, so breaks vcddiff
|
||||
// Avoid tracing handle, as it is not a stable value, so breaks wavediff
|
||||
// Likewise other internals aren't interesting to the user
|
||||
txtp->add("// verilator tracing_off\n");
|
||||
|
||||
|
|
|
|||
|
|
@ -2760,7 +2760,9 @@ class CaptureVisitor final : public VNVisitor {
|
|||
m_ignore.emplace(thisRefp);
|
||||
AstMemberSel* const memberSelp
|
||||
= new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()};
|
||||
if (!m_targetp) memberSelp->user1(true);
|
||||
// Propagate random-dependence marking; forcing it on read-only operands
|
||||
// would route them through the solver write path.
|
||||
if (!m_targetp && nodep->user1()) memberSelp->user1(true);
|
||||
memberSelp->user2p(m_targetp);
|
||||
nodep->replaceWith(memberSelp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include "V3UniqueNames.h"
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor {
|
|||
using ActCodeSet = std::set<uint32_t>;
|
||||
// For activity set, what traces apply
|
||||
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
|
||||
// Candidate interface-member VarScopes keyed by (interface type, member name)
|
||||
std::map<std::pair<const AstIface*, std::string>, std::vector<AstVarScope*>>
|
||||
m_ifaceMemberVscps;
|
||||
|
||||
// METHODS
|
||||
|
||||
|
|
@ -1125,6 +1129,13 @@ class TraceVisitor final : public VNVisitor {
|
|||
if (nodep->isTop()) m_topModp = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstVarScope* nodep) override {
|
||||
if (!m_finding) {
|
||||
if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) {
|
||||
m_ifaceMemberVscps[{ifacep, nodep->varp()->name()}].push_back(nodep);
|
||||
}
|
||||
}
|
||||
}
|
||||
void visit(AstStmtExpr* nodep) override {
|
||||
if (!m_finding && !nodep->user2()) {
|
||||
if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) {
|
||||
|
|
@ -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); }
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#include "V3File.h"
|
||||
#include "V3Force.h"
|
||||
#include "V3Fork.h"
|
||||
#include "V3FsmDetect.h"
|
||||
#include "V3FuncOpt.h"
|
||||
#include "V3Gate.h"
|
||||
#include "V3Global.h"
|
||||
|
|
@ -232,9 +233,11 @@ static void process() {
|
|||
v3Global.vlExit(0);
|
||||
}
|
||||
|
||||
// Coverage insertion
|
||||
// Before we do dead code elimination and inlining, or we'll lose it.
|
||||
if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp());
|
||||
// Insert generic non-FSM coverage before dead code elimination and
|
||||
// inlining, or those opportunities may be optimized away. FSM
|
||||
// coverage is handled later in V3FsmDetect, after scoping has created
|
||||
// the AST context needed to recover and lower FSMs reliably.
|
||||
if (v3Global.opt.coverageNonFsm()) V3Coverage::coverage(v3Global.rootp());
|
||||
|
||||
// Resolve randsequence if they are used by the design
|
||||
if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp());
|
||||
|
|
@ -347,6 +350,12 @@ static void process() {
|
|||
// No more AstAlias after linkDotScope
|
||||
V3Scope::scopeAll(v3Global.rootp());
|
||||
V3LinkDot::linkDotScope(v3Global.rootp());
|
||||
// FSM coverage needs scopes, but should otherwise run as early as
|
||||
// possible before later lowering rewrites user-visible clocked
|
||||
// case structure. This entry point runs two adjacent phases:
|
||||
// detect into local graph state, then lower that completed state
|
||||
// into the concrete coverage machinery.
|
||||
if (v3Global.opt.coverageFsm()) V3FsmDetect::detect(v3Global.rootp());
|
||||
|
||||
// Relocate classes (after linkDot)
|
||||
V3Class::classAll(v3Global.rootp());
|
||||
|
|
@ -428,8 +437,9 @@ static void process() {
|
|||
"This may cause ordering problems.");
|
||||
}
|
||||
|
||||
// Combine COVERINCs with duplicate terms
|
||||
if (v3Global.opt.coverage()) V3CoverageJoin::coverageJoin(v3Global.rootp());
|
||||
// Combine generic COVERINCs with duplicate terms. FSM coverage is
|
||||
// already lowered separately inside V3FsmDetect.
|
||||
if (v3Global.opt.coverageNonFsm()) V3CoverageJoin::coverageJoin(v3Global.rootp());
|
||||
|
||||
// Remove unused vars
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
|
|
@ -447,7 +457,7 @@ static void process() {
|
|||
}
|
||||
|
||||
// Create delayed assignments
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step.
|
||||
V3Delayed::delayedAll(v3Global.rootp());
|
||||
|
||||
// Make Active's on the top level.
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
|
|||
DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); });
|
||||
DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); });
|
||||
DECL_OPTION("-filter-type", Set, &m_filterType);
|
||||
DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs);
|
||||
DECL_OPTION("-rank", OnOff, &m_rank);
|
||||
DECL_OPTION("-unlink", OnOff, &m_unlink);
|
||||
DECL_OPTION("-V", CbCall, []() {
|
||||
|
|
@ -140,6 +141,10 @@ int main(int argc, char** argv) {
|
|||
top.points().dump();
|
||||
}
|
||||
|
||||
if (!top.opt.rank() && top.opt.writeFile().empty() && top.opt.writeInfoFile().empty()) {
|
||||
top.printTypeSummary();
|
||||
}
|
||||
|
||||
V3Error::abortIfWarnings();
|
||||
if (!top.opt.annotateOut().empty()) top.annotate(top.opt.annotateOut());
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class VlcOptions final {
|
|||
bool m_annotateAll = false; // main switch: --annotate-all
|
||||
int m_annotateMin = 10; // main switch: --annotate-min I<count>
|
||||
bool m_annotatePoints = false; // main switch: --annotate-points
|
||||
bool m_includeResetArcs = false; // main switch: --include-reset-arcs
|
||||
string m_filterType = "*"; // main switch: --filter-type
|
||||
VlStringSet m_readFiles; // main switch: --read
|
||||
bool m_rank = false; // main switch: --rank
|
||||
|
|
@ -67,6 +68,7 @@ public:
|
|||
int annotateMin() const { return m_annotateMin; }
|
||||
bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); }
|
||||
bool annotatePoints() const { return m_annotatePoints; }
|
||||
bool includeResetArcs() const { return m_includeResetArcs; }
|
||||
bool rank() const { return m_rank; }
|
||||
bool unlink() const { return m_unlink; }
|
||||
string writeFile() const { return m_writeFile; }
|
||||
|
|
|
|||
|
|
@ -70,6 +70,18 @@ public:
|
|||
return keyExtract(VL_CIK_THRESH, m_name.c_str());
|
||||
}
|
||||
string linescov() const { return keyExtract(VL_CIK_LINESCOV, m_name.c_str()); }
|
||||
bool isFsmState() const { return type() == "fsm_state"; }
|
||||
bool isFsmArc() const { return type() == "fsm_arc"; }
|
||||
// Arc-specific helpers are used after callers have already filtered to
|
||||
// FSM arc points, so they do not repeat the type check here.
|
||||
string fsmVarName() const { return keyExtract(VL_CIK_FSM_VAR, m_name.c_str()); }
|
||||
string fsmFromState() const { return keyExtract(VL_CIK_FSM_FROM, m_name.c_str()); }
|
||||
string fsmToState() const { return keyExtract(VL_CIK_FSM_TO, m_name.c_str()); }
|
||||
string fsmTag() const { return keyExtract(VL_CIK_FSM_TAG, m_name.c_str()); }
|
||||
bool isFsmResetInclude() const { return fsmTag() == "reset_include"; }
|
||||
bool isFsmResetArc() const { return fsmTag() == "reset"; }
|
||||
bool isFsmDefaultArc() const { return fsmTag() == "default"; }
|
||||
bool fsmIsReset() const { return isFsmResetArc() || isFsmResetInclude(); }
|
||||
int lineno() const {
|
||||
const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str());
|
||||
return std::atoi(lineStr.c_str());
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) {
|
|||
int branchesHit = 0;
|
||||
for (auto& li : lines) {
|
||||
VlcSourceCount& sc = li.second;
|
||||
os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n";
|
||||
const int num_branches = sc.points().size();
|
||||
if (num_branches == 1) continue;
|
||||
branchesFound += num_branches;
|
||||
int point_num = 0;
|
||||
uint64_t daCount = 0;
|
||||
std::vector<const VlcPoint*> infoPoints;
|
||||
for (const auto& point : sc.points()) {
|
||||
if (point->isFsmArc()) continue;
|
||||
daCount = std::max(daCount, point->count());
|
||||
if (!point->isFsmState()) infoPoints.push_back(point);
|
||||
}
|
||||
os << "DA:" << sc.lineno() << "," << daCount << "\n";
|
||||
if (infoPoints.size() <= 1) continue;
|
||||
branchesFound += static_cast<int>(infoPoints.size());
|
||||
int point_num = 0;
|
||||
for (const VlcPoint* point : infoPoints) {
|
||||
os << "BRDA:" << sc.lineno() << ",";
|
||||
os << "0,";
|
||||
os << point_num << ",";
|
||||
|
|
@ -269,8 +277,10 @@ void VlcTop::annotateCalcNeeded() {
|
|||
}
|
||||
}
|
||||
const float pct = totCases ? (100 * totOk / totCases) : 0;
|
||||
std::cout << "Total coverage (" << totOk << "/" << totCases << ") ";
|
||||
std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\n";
|
||||
std::cout << "Annotation Summary:\n";
|
||||
std::cout << " lines with all attached points covered : ";
|
||||
std::cout << std::fixed << std::setw(5) << std::setprecision(2) << pct << "% (" << totOk
|
||||
<< "/" << totCases << ")\n";
|
||||
if (totOk != totCases) cout << "See lines with '%00' in " << opt.annotateOut() << '\n';
|
||||
}
|
||||
|
||||
|
|
@ -326,6 +336,29 @@ void VlcTop::annotateOutputFiles(const string& dirname) {
|
|||
if (opt.annotatePoints()) {
|
||||
for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin());
|
||||
}
|
||||
bool printedFsmHeader = false;
|
||||
for (const auto& pit : sc.points()) {
|
||||
if (!pit->isFsmState() && !pit->isFsmArc()) continue;
|
||||
if (!printedFsmHeader) {
|
||||
os << " // [FSM coverage]\n";
|
||||
printedFsmHeader = true;
|
||||
}
|
||||
os << (opt.countOk(pit->count()) ? " " : "%");
|
||||
os << std::setfill('0') << std::setw(6) << pit->count() << " ";
|
||||
if (pit->isFsmState()) {
|
||||
os << "// [fsm_state " << pit->comment() << "]";
|
||||
if (pit->count() == 0) os << " *** UNCOVERED ***";
|
||||
os << "\n";
|
||||
} else if (pit->isFsmDefaultArc()) {
|
||||
os << "// [SYNTHETIC DEFAULT ARC: " << pit->comment() << "]\n";
|
||||
} else {
|
||||
os << "// [fsm_arc " << pit->comment() << "]";
|
||||
if (pit->fsmIsReset() && !opt.includeResetArcs()) {
|
||||
os << " [reset arc, excluded from %]";
|
||||
}
|
||||
os << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -337,3 +370,47 @@ void VlcTop::annotate(const string& dirname) {
|
|||
annotateCalcNeeded();
|
||||
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
|
||||
void annotate(const string& dirname);
|
||||
void printTypeSummary();
|
||||
void readCoverage(const string& filename, bool nonfatal = false);
|
||||
void writeCoverage(const string& filename);
|
||||
void writeInfo(const string& filename);
|
||||
|
|
|
|||
147
src/astgen
147
src/astgen
|
|
@ -31,6 +31,7 @@ class Node:
|
|||
self._arity = -1 # Arity of node
|
||||
self._ops = {} # Operands of node
|
||||
self._ptrs = [] # Pointer members of node (name, types)
|
||||
self._makeDfgVertex = False # Create a corresponding DfgVertex sub-class
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
@ -68,6 +69,10 @@ class Node:
|
|||
assert self.isCompleted
|
||||
return self._ptrs
|
||||
|
||||
@property
|
||||
def makeDfgVertex(self):
|
||||
return self._makeDfgVertex
|
||||
|
||||
# Pre completion methods
|
||||
def addSubClass(self, subClass):
|
||||
assert not self.isCompleted
|
||||
|
|
@ -91,6 +96,9 @@ class Node:
|
|||
name = re.sub(r'^m_', '', name)
|
||||
self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals})
|
||||
|
||||
def setMakeDfgVertex(self):
|
||||
self._makeDfgVertex = True
|
||||
|
||||
# Computes derived properties over entire class hierarchy.
|
||||
# No more changes to the hierarchy are allowed once this was called
|
||||
def complete(self, typeId=0, ordIdx=0):
|
||||
|
|
@ -609,9 +617,12 @@ def read_types(filename, Nodes, prefix):
|
|||
if prefix != "Ast":
|
||||
continue
|
||||
|
||||
match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line)
|
||||
if match:
|
||||
if match := re.match(r'^\s*//\s*@astgen\s+(.*)$', line):
|
||||
decl = re.sub(r'//.*$', '', match.group(1))
|
||||
if decl == "makeDfgVertex":
|
||||
node.setMakeDfgVertex()
|
||||
continue
|
||||
|
||||
what, sep, rest = partitionAndStrip(decl, ":=")
|
||||
what = re.sub(r'\s+', ' ', what)
|
||||
if not sep:
|
||||
|
|
@ -668,10 +679,12 @@ def read_types(filename, Nodes, prefix):
|
|||
error(
|
||||
lineno, "Malformed @astgen what (expecting 'op1'..'op4'," +
|
||||
" 'alias op1'.., 'ptr'): " + what)
|
||||
else:
|
||||
line = re.sub(r'//.*$', '', line)
|
||||
if re.match(r'.*[Oo]p[1-9].*', line):
|
||||
error(lineno, "Use generated accessors to access op<N> operands")
|
||||
continue
|
||||
|
||||
line = re.sub(r'//.*$', '', line)
|
||||
|
||||
if re.match(r'.*[Oo]p[1-9].*', line):
|
||||
error(lineno, "Use generated accessors to access op<N> operands")
|
||||
|
||||
if re.match(r'^\s*Ast[A-Z][A-Za-z0-9_]+\s*\*(\s*const)?\s+m_[A-Za-z0-9_]+\s*;', line):
|
||||
error(lineno, "Use '@astgen ptr' for Ast pointer members: " + line)
|
||||
|
|
@ -1407,116 +1420,6 @@ check_types(AstNodeList, "Ast", "Node")
|
|||
# These are standalone so we don't need to parse the sources for this.
|
||||
DfgVertices["Vertex"] = Node("Vertex", None)
|
||||
|
||||
# AstNodeExpr that are not representable in Dfg
|
||||
DfgIgnored = (
|
||||
# Floating point operations
|
||||
"AcosD",
|
||||
"AcoshD",
|
||||
"AddD",
|
||||
"AsinD",
|
||||
"AsinhD",
|
||||
"Atan2D",
|
||||
"AtanD",
|
||||
"AtanhD",
|
||||
"BitsToRealD",
|
||||
"CeilD",
|
||||
"CosD",
|
||||
"CoshD",
|
||||
"DivD",
|
||||
"EqD",
|
||||
"ExpD",
|
||||
"FloorD",
|
||||
"GtD",
|
||||
"GteD",
|
||||
"HypotD",
|
||||
"ISToRD",
|
||||
"IToRD",
|
||||
"Log10D",
|
||||
"LogD",
|
||||
"LtD",
|
||||
"LteD",
|
||||
"MulD",
|
||||
"NegateD",
|
||||
"NeqD",
|
||||
"PowD",
|
||||
"RealToBits",
|
||||
"RToIRoundS",
|
||||
"RToIS",
|
||||
"SinD",
|
||||
"SinhD",
|
||||
"SqrtD",
|
||||
"SubD",
|
||||
"TanD",
|
||||
"TanhD",
|
||||
# String operations
|
||||
"AtoN",
|
||||
"CompareNN",
|
||||
"ConcatN",
|
||||
"CvtPackString",
|
||||
"EqN",
|
||||
"GetcN",
|
||||
"GetcRefN",
|
||||
"GteN",
|
||||
"GtN",
|
||||
"LenN",
|
||||
"LteN",
|
||||
"LtN",
|
||||
"NeqN",
|
||||
"NToI",
|
||||
"PutcN",
|
||||
"ReplicateN",
|
||||
"SubstrN",
|
||||
"ToLowerN",
|
||||
"ToStringN",
|
||||
"ToUpperN",
|
||||
# Effectful
|
||||
"PostAdd",
|
||||
"PostSub",
|
||||
"PreAdd",
|
||||
"PreSub",
|
||||
# Only used after DFG
|
||||
"ShiftLOvr",
|
||||
"ShiftROvr",
|
||||
"ShiftRSOvr",
|
||||
"WordSel",
|
||||
# File operations
|
||||
"FEof",
|
||||
"FGetC",
|
||||
"FGetS",
|
||||
"FUngetC",
|
||||
# Dynamic array operations
|
||||
"AssocSel",
|
||||
"IsUnbounded",
|
||||
"WildcardSel",
|
||||
# Type comparison
|
||||
"EqT",
|
||||
"NeqT",
|
||||
# Distributions
|
||||
"DistChiSquare",
|
||||
"DistErlang",
|
||||
"DistExponential",
|
||||
"DistNormal",
|
||||
"DistPoisson",
|
||||
"DistT",
|
||||
"DistUniform",
|
||||
# Specials
|
||||
"CastDynamic",
|
||||
"CastWrap",
|
||||
"CAwait",
|
||||
"CCast",
|
||||
"CLog2",
|
||||
"IsUnknown",
|
||||
"NullCheck",
|
||||
"OneHot",
|
||||
"OneHot0",
|
||||
"ResizeLValue",
|
||||
"Signed",
|
||||
"SliceSel",
|
||||
"TimeImport",
|
||||
"Unsigned",
|
||||
"URandomRange",
|
||||
)
|
||||
|
||||
# Read DfgVertex definitions
|
||||
for filename in Args.dfgdef:
|
||||
read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg")
|
||||
|
|
@ -1527,12 +1430,8 @@ for node in AstNodeList:
|
|||
if not node.isLeaf:
|
||||
continue
|
||||
|
||||
# Ignore any explicitly defined vertex
|
||||
if node.name in DfgVertices:
|
||||
continue
|
||||
|
||||
# Ignore expressions types that DFG cannot handle
|
||||
if node.name in DfgIgnored:
|
||||
# Only create vertices for explcitly marked AstNode sub-types
|
||||
if not node.makeDfgVertex:
|
||||
continue
|
||||
|
||||
if node.isSubClassOf(AstNodes["NodeUniop"]):
|
||||
|
|
@ -1542,7 +1441,9 @@ for node in AstNodeList:
|
|||
elif node.isSubClassOf(AstNodes["NodeTriop"]):
|
||||
base = DfgVertices["VertexTernary"]
|
||||
else:
|
||||
continue
|
||||
sys.exit(
|
||||
"%Error: cannot create DfgVertex for node not derived from AstNodeUnary, AstNodeBinary, or AstNodeTernary: "
|
||||
+ node.name)
|
||||
|
||||
vertex = Node(node.name, base)
|
||||
DfgVertices[node.name] = vertex
|
||||
|
|
|
|||
|
|
@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
|
|||
"/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; }
|
||||
"/*verilator sformat*/" { FL; return yVL_SFORMAT; }
|
||||
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; }
|
||||
/* Experimental Verilator-specific FSM coverage controls. These names were
|
||||
* chosen to match the current extractor behavior, not a published synthesis
|
||||
* or simulator pragma standard, so they may evolve as we settle on longer-
|
||||
* term compatibility/aliasing.
|
||||
*/
|
||||
"/*verilator fsm_arc_include_cond*/" { FL; return yVL_FSM_ARC_INCL_COND; }
|
||||
"/*verilator fsm_reset_arc*/" { FL; return yVL_FSM_RESET_ARC; }
|
||||
"/*verilator fsm_state*/" { FL; return yVL_FSM_STATE; }
|
||||
"/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
|
||||
return yVL_TAG; }
|
||||
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }
|
||||
|
|
|
|||
|
|
@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
|
|||
%token<fl> yVL_SC_BV "/*verilator sc_bv*/"
|
||||
%token<fl> yVL_SFORMAT "/*verilator sformat*/"
|
||||
%token<fl> yVL_SPLIT_VAR "/*verilator split_var*/"
|
||||
%token<fl> yVL_FSM_ARC_INCL_COND "/*verilator fsm_arc_include_cond*/"
|
||||
%token<fl> yVL_FSM_RESET_ARC "/*verilator fsm_reset_arc*/"
|
||||
%token<fl> yVL_FSM_STATE "/*verilator fsm_state*/"
|
||||
%token<strp> yVL_TAG "/*verilator tag*/"
|
||||
%token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/"
|
||||
%token<fl> yVL_UNROLL_FULL "/*verilator unroll_full*/"
|
||||
|
|
@ -3123,6 +3126,9 @@ sigAttr<nodep>:
|
|||
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
|
||||
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
|
||||
| yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; }
|
||||
| yVL_FSM_ARC_INCL_COND { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_ARC_INCLUDE_COND}; }
|
||||
| yVL_FSM_RESET_ARC { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_RESET_ARC}; }
|
||||
| yVL_FSM_STATE { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_STATE}; }
|
||||
;
|
||||
|
||||
rangeListE<nodeRangep>: // IEEE: [{packed_dimension}]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import collections
|
|||
import ctypes
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
|
|
@ -2511,104 +2510,19 @@ class VlTest:
|
|||
print("%Warning: HARNESS_UPDATE_GOLDEN set: cp " + fn1 + " " + fn2, file=sys.stderr)
|
||||
shutil.copy(fn1, fn2)
|
||||
|
||||
def vcd_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
|
||||
"""Test if two VCD files have logically-identical contents"""
|
||||
# vcddiff to check transitions, if installed
|
||||
cmd = "vcddiff --help"
|
||||
out = test.run_capture(cmd, check=True)
|
||||
cmd = 'vcddiff ' + fn1 + ' ' + fn2
|
||||
out = test.run_capture(cmd, check=True)
|
||||
if out != "":
|
||||
cmd = 'vcddiff ' + fn2 + " " + fn1 # Reversed arguments
|
||||
out = VtOs.run_capture(cmd, check=False)
|
||||
if out != "":
|
||||
print(out)
|
||||
self.copy_if_golden(fn1, fn2)
|
||||
self.error("VCD miscompares " + fn2 + " " + fn1)
|
||||
|
||||
# vcddiff doesn't check module and variable scope, so check that
|
||||
# Also provides backup if vcddiff not installed
|
||||
h1 = self._vcd_read(fn1)
|
||||
h2 = self._vcd_read(fn2)
|
||||
if ignore_attr:
|
||||
h1 = {k: v for k, v in h1.items() if "$attr" not in v}
|
||||
h2 = {k: v for k, v in h2.items() if "$attr" not in v}
|
||||
a = json.dumps(h1, sort_keys=True, indent=1)
|
||||
b = json.dumps(h2, sort_keys=True, indent=1)
|
||||
if a != b:
|
||||
def vcd_identical(self, fn1: str, fn2: str) -> None:
|
||||
"""Test if two VCD/FST files have logically-identical contents"""
|
||||
cmd = VtOs.getenv_def('WAVEDIFF', 'wavediff') + ' --epsilon 0.0000001 ' + fn1 + ' ' + fn2
|
||||
proc = subprocess.run([cmd], capture_output=True, text=True, shell=True, check=False)
|
||||
if proc.returncode:
|
||||
print(proc.stderr)
|
||||
print(proc.stdout)
|
||||
self.copy_if_golden(fn1, fn2)
|
||||
self.error("VCD hier miscompares " + fn1 + " " + fn2 + "\nGOT=" + a + "\nEXP=" + b +
|
||||
"\n")
|
||||
self.error("VCD miscompares " + fn1 + " " + fn2)
|
||||
|
||||
def fst2vcd(self, fn1: str, fn2: str) -> None:
|
||||
cmd = "fst2vcd -h"
|
||||
out = VtOs.run_capture(cmd, check=False)
|
||||
if out == "" or not re.search(r'Usage:', out):
|
||||
self.skip("No fst2vcd installed")
|
||||
return
|
||||
|
||||
cmd = 'fst2vcd -e -f "' + fn1 + '" -o "' + fn2 + '"'
|
||||
print("\t " + cmd + "\n") # Always print to help debug race cases
|
||||
out = VtOs.run_capture(cmd, check=False)
|
||||
print(out)
|
||||
|
||||
# Post-process file and fix up to match upcoming fst2vcd output
|
||||
# also reindent for readability
|
||||
|
||||
# Slurp whole file
|
||||
with open(fn2, 'r', encoding='latin-1') as fd:
|
||||
lines = fd.readlines()
|
||||
|
||||
# Process line by line
|
||||
new_lines = []
|
||||
fixup_array_scope = False
|
||||
indent = ""
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# Change "$attrbegin class" to "$attrbegin pack"
|
||||
if match := re.match(r'^(\$attrbegin\s+)class(.*)', line):
|
||||
line = indent + match.group(1) + "pack" + match.group(2)
|
||||
# Check for "$attrbegin array"
|
||||
elif re.search(r'^\$attrbegin\s+array', line):
|
||||
line = indent + line
|
||||
fixup_array_scope = True
|
||||
# Check for "$scope"
|
||||
elif match := re.match(r'(\$scope\s)(\S+)(.*)', line.lstrip('\r\n')):
|
||||
if not indent:
|
||||
indent = " "
|
||||
# Fix up array scope
|
||||
if (match.group(2) == "module") and fixup_array_scope:
|
||||
line = indent + match.group(1) + "sv_array" + match.group(3)
|
||||
fixup_array_scope = False
|
||||
else:
|
||||
line = indent + line
|
||||
indent += " "
|
||||
# Check for "$upscope"
|
||||
elif re.search(r'^\$upscope', line):
|
||||
indent = indent[0:-1]
|
||||
line = indent + line
|
||||
if len(indent) == 1:
|
||||
indent = ""
|
||||
# Just reindent
|
||||
else:
|
||||
line = indent + line
|
||||
new_lines.append(line + "\n")
|
||||
|
||||
# Write back to file
|
||||
with open(fn2, 'w', encoding='latin-1') as fd:
|
||||
fd.writelines(new_lines)
|
||||
|
||||
def fst_identical(self, fn1: str, fn2: str, ignore_attr: bool = False) -> None:
|
||||
def fst_identical(self, fn1: str, fn2: str) -> None:
|
||||
"""Test if two FST files have logically-identical contents"""
|
||||
if fn1.endswith(".fst"):
|
||||
tmp = fn1 + ".vcd"
|
||||
self.fst2vcd(fn1, tmp)
|
||||
fn1 = tmp
|
||||
if fn2.endswith(".fst"):
|
||||
tmp = fn2 + ".vcd"
|
||||
self.fst2vcd(fn2, tmp)
|
||||
fn2 = tmp
|
||||
self.vcd_identical(fn1, fn2, ignore_attr)
|
||||
self.vcd_identical(fn1, fn2)
|
||||
|
||||
def saif_identical(self, fn1: str, fn2: str) -> None:
|
||||
"""Test if two SAIF files have logically-identical contents"""
|
||||
|
|
@ -2621,96 +2535,17 @@ class VlTest:
|
|||
self.copy_if_golden(fn1, fn2)
|
||||
self.error("SAIF files miscompare")
|
||||
|
||||
def trace_identical(self, traceFn: str, goldenFn: str, ignore_attr: bool = False) -> None:
|
||||
def trace_identical(self, traceFn: str, goldenFn: str) -> None:
|
||||
match traceFn.rpartition(".")[-1]:
|
||||
case "vcd":
|
||||
self.vcd_identical(traceFn, goldenFn, ignore_attr)
|
||||
self.vcd_identical(traceFn, goldenFn)
|
||||
case "fst":
|
||||
self.fst_identical(traceFn, goldenFn, ignore_attr)
|
||||
self.fst_identical(traceFn, goldenFn)
|
||||
case "saif":
|
||||
self.saif_identical(traceFn, goldenFn)
|
||||
case _:
|
||||
self.error("Unknown trace file format " + traceFn)
|
||||
|
||||
@staticmethod
|
||||
def _vcd_parse_header(filename: str, root_scope: 'str | None' = None) -> 'tuple[dict, dict]':
|
||||
"""Parse VCD header into hierarchy data and signal-code mapping.
|
||||
|
||||
Returns (hier_data, var_codes) where:
|
||||
hier_data: dict used by _vcd_read for hierarchy comparison
|
||||
(scope paths -> scope description, var paths -> $var prefix,
|
||||
attr paths -> $attrbegin text)
|
||||
var_codes: dict mapping 'scope.signal' -> VCD code identifier
|
||||
"""
|
||||
hier_data: dict = {}
|
||||
var_codes: dict = {}
|
||||
hier_stack = [root_scope] if root_scope else []
|
||||
attr: list = []
|
||||
with open(filename, 'r', encoding='latin-1') as fh:
|
||||
for line in fh:
|
||||
m_scope = re.search(r'\$scope\s+(\S*)\s+(\S+)', line)
|
||||
m_var = re.search(r'(\$var\s+\S+\s+\d+\s+)(\S+)\s+(.+)\s+\$end', line)
|
||||
m_attr = re.search(r'(\$attrbegin .* \$end)', line)
|
||||
if m_scope:
|
||||
scope_type = m_scope.group(1)
|
||||
name = m_scope.group(2)
|
||||
hier_stack.append(name)
|
||||
scope = '.'.join(hier_stack)
|
||||
hier_data[scope] = scope_type + " " + name
|
||||
if attr:
|
||||
hier_data[scope + "#"] = " ".join(attr)
|
||||
attr = []
|
||||
elif m_var:
|
||||
scope = '.'.join(hier_stack)
|
||||
decl_prefix = m_var.group(1)
|
||||
code = m_var.group(2)
|
||||
var_name = m_var.group(3)
|
||||
hier_data[scope + "." + var_name] = decl_prefix
|
||||
if attr:
|
||||
hier_data[scope + "." + var_name + "#"] = " ".join(attr)
|
||||
attr = []
|
||||
bare_name = var_name.split()[0]
|
||||
var_codes[scope + "." + bare_name] = code
|
||||
elif m_attr:
|
||||
attr.append(m_attr.group(1))
|
||||
elif re.search(r'\$enddefinitions', line):
|
||||
break
|
||||
n = len(re.findall(r'\$upscope', line))
|
||||
if n:
|
||||
for _i in range(0, n):
|
||||
hier_stack.pop()
|
||||
return hier_data, var_codes
|
||||
|
||||
def _vcd_read(self, filename: str) -> dict:
|
||||
data, _ = self._vcd_parse_header(filename, root_scope="TOP")
|
||||
return data
|
||||
|
||||
def vcd_extract_codes(self, filename: str) -> dict:
|
||||
_, codes = self._vcd_parse_header(filename)
|
||||
return codes
|
||||
|
||||
def vcd_check_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None:
|
||||
code_a = codes.get(sig_a)
|
||||
code_b = codes.get(sig_b)
|
||||
if code_a is None:
|
||||
self.error(f"Signal '{sig_a}' not found in VCD")
|
||||
if code_b is None:
|
||||
self.error(f"Signal '{sig_b}' not found in VCD")
|
||||
if code_a != code_b:
|
||||
self.error(f"Expected '{sig_a}' (code {code_a}) to alias "
|
||||
f"'{sig_b}' (code {code_b})")
|
||||
|
||||
def vcd_check_not_aliased(self, codes: dict, sig_a: str, sig_b: str) -> None:
|
||||
code_a = codes.get(sig_a)
|
||||
code_b = codes.get(sig_b)
|
||||
if code_a is None:
|
||||
self.error(f"Signal '{sig_a}' not found in VCD")
|
||||
if code_b is None:
|
||||
self.error(f"Signal '{sig_b}' not found in VCD")
|
||||
if code_a == code_b:
|
||||
self.error(f"Expected '{sig_a}' and '{sig_b}' to have different codes, "
|
||||
f"both have code {code_a}")
|
||||
|
||||
def inline_checks(self) -> None:
|
||||
covfn = self.coverage_filename
|
||||
contents = self.file_contents(covfn)
|
||||
|
|
|
|||
|
|
@ -1,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'
|
||||
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
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
// SPDX-FileCopyrightText: 2026 PlanV GmbH
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (input clk);
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
logic a, b;
|
||||
|
||||
// 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