Merge branch 'master' into fix/sva-within

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

View File

@ -48,7 +48,7 @@ jobs:
ls -lsha
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
View File

@ -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

View File

@ -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

View File

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

View File

@ -40,12 +40,32 @@ if [ "$CI_OS_NAME" = "linux" ]; then
echo "path-exclude /usr/share/info/*" | sudo tee -a /etc/dpkg/dpkg.cfg.d/01_nodoc
fi
install-vcddiff() {
TMP_DIR="$(mktemp -d)"
git clone https://github.com/veripool/vcddiff "$TMP_DIR"
git -C "${TMP_DIR}" checkout 4db0d84a27e8f148b127e916fc71d650837955c5
"$MAKE" -C "${TMP_DIR}"
sudo cp "${TMP_DIR}/vcddiff" /usr/local/bin
install-wavediff() {
source ci/docker/buildenv/wavetools.conf
local _base_url="https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}"
local _platform
if [ "$CI_OS_NAME" = "linux" ]; then
_platform="linux-x86_64"
elif [ "$CI_OS_NAME" = "osx" ]; then
_platform="macos-arm64"
elif [ "$CI_OS_NAME" = "windows" ]; then
_platform="windows-x86_64"
else
echo "WARNING: No wavetools binary available for CI_OS_NAME=$CI_OS_NAME, skipping"
return 0
fi
local _tmpdir
_tmpdir=$(mktemp -d)
local _archive="wavetools-${WAVETOOLS_VERSION}-${_platform}"
if [ "$CI_OS_NAME" = "windows" ]; then
wget -q -O "${_tmpdir}/${_archive}.zip" "${_base_url}/${_archive}.zip"
unzip -o "${_tmpdir}/${_archive}.zip" -d "${_tmpdir}"
else
wget -q -O "${_tmpdir}/${_archive}.tar.gz" "${_base_url}/${_archive}.tar.gz"
tar -xzf "${_tmpdir}/${_archive}.tar.gz" -C "${_tmpdir}"
fi
sudo cp "${_tmpdir}/${_archive}/wavediff" /usr/local/bin/wavediff
rm -rf "${_tmpdir}"
}
if [ "$CI_BUILD_STAGE_NAME" = "build" ]; then
@ -120,7 +140,7 @@ elif [ "$CI_BUILD_STAGE_NAME" = "test" ]; then
fatal "Unknown CI_OS_NAME: '$CI_OS_NAME'"
fi
# Common installs
install-vcddiff
install-wavediff
# Workaround -fsanitize=address crash
sudo sysctl -w vm.mmap_rnd_bits=28
else

View File

@ -53,10 +53,14 @@ RUN apt-get update \
WORKDIR /tmp
RUN git clone https://github.com/veripool/vcddiff.git && \
make -C vcddiff && \
cp -p vcddiff/vcddiff /usr/local/bin/vcddiff && \
rm -rf vcddiff
COPY wavetools.conf /tmp/wavetools.conf
ARG WGET_EXTRA_ARGS=
RUN . /tmp/wavetools.conf && \
wget -q ${WGET_EXTRA_ARGS} -O /tmp/wavetools.tar.gz \
"https://github.com/hudson-trading/wavetools/releases/download/${WAVETOOLS_VERSION}/wavetools-${WAVETOOLS_VERSION}-linux-x86_64.tar.gz" && \
tar -xzf /tmp/wavetools.tar.gz -C /tmp && \
cp -p /tmp/wavetools-${WAVETOOLS_VERSION}-linux-x86_64/wavediff /usr/local/bin/wavediff && \
rm -rf /tmp/wavetools.tar.gz /tmp/wavetools-* /tmp/wavetools.conf
COPY build.sh /tmp/build.sh

View File

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

View File

@ -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

View File

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

View File

@ -129,13 +129,20 @@ verilator_coverage Arguments
.. option:: --filter-type <regex>
Skips records of coverage types that matches with <regex>
Possible values are `toggle`, `line`, `branch`, `expr`, `user` and
a wildcard with `\*` or `?`. The default value is `\*`.
Possible values are `toggle`, `line`, `branch`, `expr`, `user`,
`fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default
value is `\*`.
.. option:: --help
Displays a help summary, the program version, and exits.
.. option:: --include-reset-arcs
Includes FSM reset arcs in the printed summaries and annotated output.
By default, reset arcs are tracked but summarized separately from the
non-reset FSM arcs.
.. option:: --rank
Prints an experimental report listing the relative importance of each

View File

@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables
all forms of coverage:
- :ref:`User Coverage`
- :ref:`FSM Coverage`
- :ref:`Line Coverage`
- :ref:`Toggle Coverage`
@ -208,6 +209,47 @@ point under the coverage name "DefaultClock":
DefaultClock: cover property (@(posedge clk) cyc==3);
.. _fsm coverage:
FSM Coverage
------------
With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can
instrument a conservative subset of single-process FSMs and report both
state coverage (`fsm_state`) and transition coverage (`fsm_arc`).
This feature is currently experimental and might change in subsequent
releases. In particular, the native FSM coverage extraction heuristics,
:vlopt:`--coverage-fsm`, and the Verilator-specific FSM metacomments below
should be treated as subject to change while the interface settles.
FSM extraction is intentionally narrow. The current implementation targets
clocked, enum-driven state machines that can be recovered directly from the
RTL. It does not claim broad support for two-process FSMs, one-hot
inference, helper-function next-state recovery, or deeply nested control
recovery.
The following metacomments may be attached to the state variable to steer
the extracted coverage model:
- ``/*verilator fsm_state*/`` forces the variable to be treated as
FSM state.
- ``/*verilator fsm_reset_arc*/`` marks reset transitions as
user-visible reset arcs instead of defaulting to a hidden reset-only
summary.
- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch
arcs that would otherwise be skipped by the conservative extractor.
Reset transitions are included in the collected data either way. By
default, :command:`verilator_coverage` summarizes reset-only arcs rather
than printing them alongside non-reset arcs. Use
:option:`verilator_coverage --include-reset-arcs` to include
those arcs in the printed summary and annotated output.
Annotated output produced by :command:`verilator_coverage --annotate` will
label FSM points with `fsm_state` and `fsm_arc`, and synthetic fallback
transitions with `SYNTHETIC DEFAULT ARC`.
.. _line coverage:

View File

@ -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

View File

@ -306,7 +306,7 @@ List Of Warnings
else
array[address] <= data;
While this is supported in typical synthesizeable code (including the
While this is supported in typical synthesizable code (including the
example above), some complicated cases are not supported. Namely:
1. If the above loop is inside a suspendable process or fork statement.
@ -837,6 +837,18 @@ List Of Warnings
with a newline."
.. option:: FSMMULTI
Warns that the same always block contains multiple enum-typed case
statements that look like FSM candidates for native FSM coverage when
:vlopt:`--coverage-fsm` or :vlopt:`--coverage` is enabled.
Verilator's FSM coverage instruments only the first such candidate in
source order. Split the FSMs into separate always blocks, or explicitly
annotate the intended state variables and restructure the RTL for full
coverage of such multiple state machines.
.. option:: FUNCTIMECTL
Error that a function contains a time-controlling statement or call of a

View File

@ -1568,8 +1568,8 @@ For all tests to pass, you must install the following packages:
- SystemC to compile the SystemC outputs, see https://systemc.org
- 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.

View File

@ -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

View File

@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7
C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24),
N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and C-string-valued metadata pairs.
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
A(7)) VL_MT_SAFE {
const std::string val2str = std::to_string(val2);
const std::string val3str = std::to_string(val3);
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
N(8), N(9), N(10), N(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and additional FSM metadata pairs.
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
A(7), A(8), A(9), A(10), A(11)) VL_MT_SAFE {
const std::string val2str = std::to_string(val2);
const std::string val3str = std::to_string(val3);
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
C(8), C(9), C(10), C(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
}
// Backward compatibility for Verilator
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4),
const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE {

View File

@ -191,6 +191,13 @@ public:
void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12),
A(13), A(14), A(15), A(16), A(17), A(18), A(19), A(20), D(21), D(22), D(23),
D(24), D(25), D(26), D(27), D(28), D(29)) VL_MT_SAFE;
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and C-string-valued metadata pairs.
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7)) VL_MT_SAFE;
// Backward compatibility for mixed inserts with integer-valued
// lineno/column pairs and additional FSM metadata pairs.
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7), A(8), A(9),
A(10), A(11)) VL_MT_SAFE;
// Backward compatibility for Verilator
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5),
A(6), A(7)) VL_MT_SAFE;

View File

@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
// Bin attributes
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'")
VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'")
VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'")
VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'")
VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'")
VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'")
@ -49,6 +53,10 @@ VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'd
#define VL_CIK_COLUMN "n"
#define VL_CIK_COMMENT "o"
#define VL_CIK_FILENAME "f"
#define VL_CIK_FSM_FROM "Ff"
#define VL_CIK_FSM_TAG "Fg"
#define VL_CIK_FSM_TO "Ft"
#define VL_CIK_FSM_VAR "Fv"
#define VL_CIK_HIER "h"
#define VL_CIK_LINENO "l"
#define VL_CIK_LINESCOV "S"
@ -70,6 +78,10 @@ public:
if (key == "column") return VL_CIK_COLUMN;
if (key == "comment") return VL_CIK_COMMENT;
if (key == "filename") return VL_CIK_FILENAME;
if (key == "fsm_from") return VL_CIK_FSM_FROM;
if (key == "fsm_tag") return VL_CIK_FSM_TAG;
if (key == "fsm_to") return VL_CIK_FSM_TO;
if (key == "fsm_var") return VL_CIK_FSM_VAR;
if (key == "hier") return VL_CIK_HIER;
if (key == "lineno") return VL_CIK_LINENO;
if (key == "linescov") return VL_CIK_LINESCOV;

258
include/verilated_force.h Normal file
View File

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

View File

@ -100,6 +100,7 @@ set(HEADERS
V3File.h
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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -329,8 +329,12 @@ class DeadVisitor final : public VNVisitor {
void visit(AstNodeFTask* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
if (!nodep->taskPublic() && !nodep->dpiExport() && !nodep->dpiImport())
if (nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()) {
if (m_modp && !m_modp->dead() && !m_modp->verilatorLib())
m_modp->user1Inc(); // Keep container
} else {
m_tasksp.push(nodep);
}
if (nodep->classOrPackagep()) {
if (m_elimCells) {
nodep->classOrPackagep(nullptr);

View File

@ -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

View File

@ -352,6 +352,9 @@ public:
// Human-readable name for source operand with given index for debugging
virtual std::string srcName(size_t idx) const = 0;
// S-expression inspired dump of vertex and operands for debugging
std::string patternString(uint32_t depth = 0) const;
};
// DfgVertex visitor
@ -812,9 +815,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
bool DfgVertex::isCheaperThanLoad() const {
// Constants
if (is<DfgConst>()) return true;
// Variables
if (is<DfgVertexVar>()) return true;
// Array sels are just address computation
if (is<DfgArraySel>()) return true;
// Small constant select from variable
// Small select from variable
if (const DfgSel* const selp = cast<DfgSel>()) {
if (!selp->fromp()->is<DfgVarPacked>()) return false;
if (selp->fromp()->width() <= VL_QUADSIZE) return true;
@ -825,20 +830,32 @@ bool DfgVertex::isCheaperThanLoad() const {
// Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
if (const DfgConcat* const catp = cast<DfgConcat>()) {
if (catp->width() > VL_QUADSIZE) return false;
const DfgConst* const lCatp = catp->lhsp()->cast<DfgConst>();
if (!lCatp) return false;
if (!lCatp->isZero()) return false;
const DfgConst* const lConstp = catp->lhsp()->cast<DfgConst>();
if (!lConstp || !lConstp->isZero()) return false;
return catp->rhsp()->isCheaperThanLoad();
}
// Reduction of a cheap vertex
if (const DfgRedOr* const redOrp = cast<DfgRedOr>()) {
return redOrp->srcp()->isCheaperThanLoad();
// Reduction of a narrow cheap vertex
if (is<DfgRedOr>() //
|| is<DfgRedAnd>() //
|| is<DfgRedXor>()) {
const DfgVertex* const srcp = as<DfgVertexUnary>()->srcp();
return srcp->width() <= VL_QUADSIZE && srcp->isCheaperThanLoad();
}
if (const DfgRedAnd* const redAndp = cast<DfgRedAnd>()) {
return redAndp->srcp()->isCheaperThanLoad();
}
if (const DfgRedXor* const redXorp = cast<DfgRedXor>()) {
return redXorp->srcp()->isCheaperThanLoad();
// Comparisons of a narrow cheap vertex with constant
if (is<DfgEq>() //
|| is<DfgNeq>() //
|| is<DfgLt>() //
|| is<DfgLte>() //
|| is<DfgGt>() //
|| is<DfgGte>() //
|| is<DfgLtS>() //
|| is<DfgLteS>() //
|| is<DfgGtS>() //
|| is<DfgGteS>()) {
const DfgVertexBinary* const binp = as<DfgVertexBinary>();
const DfgVertex* const lhsp = binp->inputp(0);
const DfgVertex* const rhsp = binp->inputp(1);
return lhsp->width() <= VL_QUADSIZE && lhsp->is<DfgConst>() && rhsp->isCheaperThanLoad();
}
// Otherwise probably not
return false;

View File

@ -78,7 +78,6 @@ class V3DfgCse final {
case VDfgType::Add:
case VDfgType::And:
case VDfgType::ArraySel:
case VDfgType::BufIf1:
case VDfgType::Concat:
case VDfgType::Cond:
case VDfgType::CountOnes:
@ -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;

View File

@ -125,6 +125,8 @@ public:
// Thanks to the interning, equality is identity
bool operator==(const DfgDataType& that) const { return this == &that; }
bool operator!=(const DfgDataType& that) const { return this != &that; }
// Similarly for hash
V3Hash hash() const { return V3Hash{this}; }
// Type of elements, for arrays only
const DfgDataType& elemDtype() const {
@ -132,13 +134,6 @@ public:
return *m_elemDtypep;
}
V3Hash hash() const {
V3Hash hash{static_cast<uint32_t>(m_kind)};
hash += m_size;
if (m_elemDtypep) hash += m_elemDtypep->hash();
return hash;
}
//-----------------------------------------------------------------------
// Static factory and management functions

View File

@ -21,133 +21,27 @@
#include "V3DfgPasses.h"
#include "V3File.h"
#include <algorithm>
#include <map>
#include <unordered_map>
class V3DfgPatternStats final {
static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Maps from pattern to the number of times it appears, for each pattern depth
std::vector<std::unordered_map<std::string, size_t>> m_patterCounts{MAX_PATTERN_DEPTH + 1};
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) {
bool deep = depth == 0;
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
ss << "'0";
} else if (constp->isOnes()) {
ss << "'1";
} else {
ss << internConst(*constp);
}
} else if (depth == 0) {
// Base case 2: deep vertex
ss << "_";
} else {
// Recursively print an S-expression for the vertex
// S-expression begin
ss << '(';
// Name
ss << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
ss << '@';
if (selp->lsb() == 0) {
ss << '0';
} else {
ss << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
ss << ' ';
if (render(ss, src, depth - 1)) deep = true;
return false;
});
// S-expression end
ss << ')';
}
// Annotate identity
ss << ":" << internVertex(vtx);
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) ss << '*';
// Annotate type
ss << '/';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(ss);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
ss << '1';
} else if (width <= VL_QUADSIZE) {
ss << internWordWidth(width);
} else {
ss << internWideWidth(width);
}
}
// Done
return deep;
}
void dump(std::ostream& os) {
using Line = std::pair<std::string, size_t>;
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
os << "DFG patterns with depth " << i << '\n';
// Pick up pattern accumulators with given depth
const auto& patternCounts = m_patterCounts[i];
auto& patternCounts = m_patterCounts[i];
// Remove patterns also present at shallower depths
for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) {
for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first);
}
// Sort patterns, first by descending frequency, then lexically
std::vector<Line> lines;
@ -173,8 +67,8 @@ public:
V3DfgPatternStats() = default;
~V3DfgPatternStats() {
// File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix()
+ "__stats_dfg_patterns.txt";
const std::string filename
= v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt";
// Open, write, close
const std::unique_ptr<std::ofstream> ofp{V3File::new_ofstream(filename)};
if (ofp->fail()) v3fatal("Can't write file: " << filename);
@ -184,13 +78,7 @@ public:
void accumulate(const DfgGraph& dfg) {
dfg.forEachVertex([&](const DfgVertex& vtx) {
for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) {
std::ostringstream ss;
if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1;
m_internedConsts.clear();
m_internedSelLsbs.clear();
m_internedWordWidths.clear();
m_internedWideWidths.clear();
m_internedVertices.clear();
m_patterCounts[i][vtx.patternString(i)] += 1;
}
});
}

View File

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

View File

@ -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)) {

View File

@ -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)

View File

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

View File

@ -826,6 +826,14 @@ public:
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
puts(", ");
putsQuoted(nodep->linescov());
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
puts(", ");
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
puts(");\n");
}
void visit(AstCoverToggleDecl* nodep) override {

View File

@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit {
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
"linescovp);\n");
"linescovp,\n");
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
"fsmTagp);\n");
}
if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) {
@ -663,6 +665,7 @@ class EmitCHeader final : public EmitCConstInit {
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");
std::set<string> cuse_set;
auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };

View File

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

View File

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

File diff suppressed because it is too large Load Diff

788
src/V3FsmDetect.cpp Normal file
View File

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

33
src/V3FsmDetect.h Normal file
View File

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

View File

@ -125,6 +125,7 @@ class V3Global final {
bool m_usesProbDist = false; // Uses $dist_*
bool m_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; }

View File

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

View File

@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor {
// Find all cells with arrays, and convert to non-arrayed
private:
// STATE
// Range for arrayed instantiations, nullptr for normal instantiations
const AstRange* m_cellRangep = nullptr;
int m_instSelNum = 0; // Current instantiation count 0..N-1
const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells
int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion
InstDeModVarVisitor m_deModVars; // State of variables for current cell module
// VISITORS
@ -233,52 +232,72 @@ private:
m_deModVars.main(nodep->modp());
//
if (nodep->rangep()) {
m_cellRangep = nodep->rangep();
// Collect the full range chain (outer first).
std::vector<const AstRange*> rangesp;
for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) {
rangesp.push_back(rp);
}
m_cellRangep = rangesp.front();
const int ndim = static_cast<int>(rangesp.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = rangesp[d]->elementsConst();
totalElems *= sizes[d];
}
AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var);
// cppcheck-suppress constVariablePointer
AstNodeDType* const ifaceVarDtp
= ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr;
const bool isIface
= ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType)
&& VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
&& !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(),
IfaceRefDType)
->isVirtual();
// Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType.
AstIfaceRefDType* origIfaceRefp = nullptr;
AstUnpackArrayDType* innermostArrp = nullptr;
for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) {
if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) {
innermostArrp = arrp;
dp = arrp->subDTypep()->skipRefp();
} else {
origIfaceRefp = VN_CAST(dp, IfaceRefDType);
break;
}
}
const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual();
// Make all of the required clones
for (int i = 0; i < m_cellRangep->elementsConst(); i++) {
m_instSelNum
= m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i;
const int instNum = m_cellRangep->loConst() + i;
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
// Unflatten n into a row-major cartesian index; outer dim most significant.
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
// Flat select number for 1D-compat pin expansion; ascending dims invert.
// Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx).
int flatSel = 0;
string suffix;
for (int d = 0; d < ndim; ++d) {
const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d];
flatSel = flatSel * sizes[d] + sel;
suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d])
+ "__KET__";
}
m_instSelNum = flatSel;
AstCell* const newp = nodep->cloneTree(false);
nodep->addNextHere(newp);
// Remove ranging and fix name
newp->rangep()->unlinkFrBack()->deleteTree();
// Somewhat illogically, we need to rename the original name of the cell too.
// as that is the name users expect for dotting
// The spec says we add [x], but that won't work in C...
newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__");
while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree();
newp->name(newp->name() + suffix);
newp->origName(newp->origName() + suffix);
UINFO(8, " CELL loop " << newp);
// If this AstCell is actually an interface instantiation, also clone the IfaceRef
// within the same parent module as the cell
// Interface instantiation: also clone the IfaceRef in the parent module.
if (isIface) {
AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType);
AstIfaceRefDType* const origIfaceRefp
= VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType);
origIfaceRefp->cellp(nullptr);
AstVar* const varNewp = ifaceVarp->cloneTree(false);
AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false);
arrdtype->addNextHere(ifaceRefp);
innermostArrp->addNextHere(ifaceRefp);
ifaceRefp->cellp(newp);
ifaceRefp->cellName(newp->name());
varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__");
varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum)
+ "__KET__");
varNewp->name(varNewp->name() + suffix);
varNewp->origName(varNewp->origName() + suffix);
varNewp->dtypep(ifaceRefp);
newp->addNextHere(varNewp);
if (debug() == 9) {
@ -389,6 +408,105 @@ private:
}
} else {
AstVar* const pinVarp = nodep->modVarp();
// Multi-dim whole-array iface pin fanout: cartesian-product the port's
// nested UnpackArrayDType layers and emit one pin + per-element var per cell.
// For 1-dim falls through to the original code below.
std::vector<const AstUnpackArrayDType*> portArrs;
for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
portArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
if (portArrs.size() >= 2) {
AstIfaceRefDType* const portIrp
= VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType);
if (!portIrp || portIrp->isVirtual()) return;
const int ndim = static_cast<int>(portArrs.size());
std::vector<int> sizes(ndim);
int totalElems = 1;
for (int d = 0; d < ndim; ++d) {
sizes[d] = portArrs[d]->elementsConst();
totalElems *= sizes[d];
}
// V3Width should have already rejected non-VarRef and rank-mismatch pins;
// these are defensive internal-invariant guards.
const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef);
UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port");
std::vector<const AstUnpackArrayDType*> exprArrs;
for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) {
if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) {
exprArrs.push_back(arrp);
d = arrp->subDTypep()->skipRefp();
} else {
break;
}
}
UASSERT_OBJ(exprArrs.size() == static_cast<size_t>(ndim), nodep->exprp(),
"Multi-dim iface pin expression rank does not match port");
AstNode* prevp = nullptr;
AstNode* prevPinp = nullptr;
std::vector<int> idx(ndim, 0);
for (int n = 0; n < totalElems; ++n) {
int rem = n;
for (int d = ndim - 1; d >= 0; --d) {
idx[d] = rem % sizes[d];
rem /= sizes[d];
}
string portSuffix;
string exprSuffix;
for (int d = 0; d < ndim; ++d) {
portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d])
+ "__KET__";
exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d])
+ "__KET__";
}
const string varNewName = pinVarp->name() + portSuffix;
AstVar* varNewp = nullptr;
if (!pinVarp->backp()) {
varNewp = m_deModVars.find(varNewName);
} else {
portIrp->cellp(nullptr);
varNewp = pinVarp->cloneTree(false);
varNewp->name(varNewName);
varNewp->origName(varNewp->origName() + portSuffix);
varNewp->dtypep(portIrp);
m_deModVars.insert(varNewp);
if (!prevp) {
prevp = varNewp;
} else {
prevp->addNextHere(varNewp);
}
}
if (!varNewp) {
if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE
nodep->v3fatalSrc("Module dearray failed for "
<< AstNode::prettyNameQ(varNewName));
}
AstPin* const newp = nodep->cloneTree(false);
newp->modVarp(varNewp);
newp->name(newp->name() + portSuffix);
AstVarXRef* const newVarXRefp = new AstVarXRef{
nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE};
newVarXRefp->varp(newp->modVarp());
newp->exprp()->unlinkFrBack()->deleteTree();
newp->exprp(newVarXRefp);
if (!prevPinp) {
prevPinp = newp;
} else {
prevPinp->addNextHere(newp);
}
}
if (prevp) {
pinVarp->replaceWith(prevp);
pushDeletep(pinVarp);
}
nodep->replaceWith(prevPinp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
const AstUnpackArrayDType* const pinArrp
= VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType);
if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
@ -472,28 +590,50 @@ private:
}
}
void visit(AstArraySel* nodep) override {
if (const AstUnpackArrayDType* const arrp
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return;
if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return;
V3Const::constifyParamsEdit(nodep->bitp());
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
// If a parent is also an ArraySel into the same iface array, let it handle the chain.
if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return;
// Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index).
std::vector<AstArraySel*> sels;
AstNode* curp = nodep;
while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) {
sels.push_back(asp);
curp = asp->fromp();
}
const AstVarRef* const varrefp = VN_CAST(curp, VarRef);
if (!varrefp) return;
// Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType.
std::vector<AstUnpackArrayDType*> arrs; // outer dim first
AstNodeDType* dtp = varrefp->dtypep()->skipRefp();
while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) {
arrs.push_back(ap);
dtp = ap->subDTypep()->skipRefp();
}
if (arrs.empty() || sels.size() != arrs.size()) return;
AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType);
if (!irp || irp->isVirtual()) return;
// Constify bitps and collect indices in outer-dim-first order (sels is inner-first).
std::vector<int> indices(sels.size());
for (size_t i = 0; i < sels.size(); ++i) {
AstArraySel* const asp = sels[i];
V3Const::constifyParamsEdit(asp->bitp());
const AstConst* const constp = VN_CAST(asp->bitp(), Const);
if (!constp) {
nodep->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
asp->bitp()->v3warn(E_UNSUPPORTED,
"Non-constant index in RHS interface array selection");
return;
}
const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo());
const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
UASSERT_OBJ(varrefp, nodep, "No interface varref under array");
AstVarXRef* const newp = new AstVarXRef{
nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "",
VAccess::READ};
newp->dtypep(arrp->subDTypep());
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
indices[sels.size() - 1 - i] = constp->toSInt();
}
string indexStr;
for (size_t i = 0; i < indices.size(); ++i) {
indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__";
}
AstVarXRef* const newp
= new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ};
newp->dtypep(irp);
newp->classOrPackagep(varrefp->classOrPackagep());
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstNodeAssign* nodep) override {
if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) {

View File

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

View File

@ -524,17 +524,19 @@ public:
void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE
m_ifaceVarSyms.push_back(symp);
}
// Iface for a raw or arrayed iface
// Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays.
static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) {
AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType);
if (!ifacerefp) {
while (nodep) {
if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp;
if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) {
ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType);
nodep = arrp->subDTypep();
} else {
return nullptr;
}
}
return ifacerefp;
return nullptr;
}
// Given a pin expression, resolve it to a live AstIface* (or nullptr).
// Handles both simple VarRef and dotted VarXRef pin connections.
@ -770,10 +772,16 @@ public:
if (forPrearray()) {
// GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled,
// but presently should be just "foo".
// Likewise cell foo__[array] before we've expanded arrays is just foo
if ((pos = ident.rfind("__BRA__")) != string::npos) {
altIdent = ident.substr(0, pos);
// Likewise cell foo__[array] before we've expanded arrays is just foo.
// Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them
// all.
altIdent = ident;
while (VString::endsWith(altIdent, "__KET__")) {
const auto braPos = altIdent.rfind("__BRA__");
if (braPos == string::npos) break;
altIdent = altIdent.substr(0, braPos);
}
if (altIdent == ident) altIdent.clear();
}
UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname
<< " at se" << lookupSymp);
@ -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 {

View File

@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor {
m_varp->attrSplitVar(true);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_ARC_INCLUDE_COND) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmArcInclCond(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_RESET_ARC) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmResetArc(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_FSM_STATE) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrFsmState(true);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) {
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
m_varp->attrScBigUint(true);

View File

@ -1352,6 +1352,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-coverage", CbOnOff, [this](bool flag) { coverage(flag); });
DECL_OPTION("-coverage-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 {

View File

@ -226,6 +226,7 @@ private:
bool m_build = false; // main switch: --build
bool m_context = true; // main switch: --Wcontext
bool m_coverageExpr = false; // main switch: --coverage-expr
bool m_coverageFsm = false; // main switch: --coverage-fsm
bool m_coverageLine = false; // main switch: --coverage-block
bool m_coverageToggle = false; // main switch: --coverage-toggle
bool m_coverageUnderscore = false; // main switch: --coverage-underscore
@ -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");
}

View File

@ -475,6 +475,12 @@ class ParamProcessor final {
}
return nullptr;
}
// Peel all unpacked-array layers to reach the innermost subDTypep.
// Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType.
static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) {
while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp;
return nodep;
}
static bool isString(AstNodeDType* nodep) {
if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType))
return basicp->isString();
@ -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

View File

@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n
return assignp;
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list
AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) {
// Remove any UnsizedRange's from list; preserves multi-dim chain via nextp().
for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) {
nextp = VN_AS(nodep->nextp(), NodeRange);
if (!VN_IS(nodep, Range)) {
@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
if (nrangep && nrangep->nextp()) {
// Not supported by at least 2 of big 3
nrangep->nextp()->v3warn(E_UNSUPPORTED,
"Unsupported: Multidimensional instances/interfaces.");
nrangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return VN_CAST(nrangep, Range);
}
AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) {
AstRange* const rangep = scrubRangeMulti(nrangep);
if (rangep && rangep->nextp()) {
// Gate primitives only support a single dimension
rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances.");
rangep->nextp()->unlinkFrBackWithNext()->deleteTree();
}
return rangep;
}
AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED {
// SEL(PARSELVALUE, ...) -> SEL(fromp, ...)

View File

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

View File

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

View File

@ -221,7 +221,7 @@ class ProtectVisitor final : public VNVisitor {
+ "_protectlib_final(chandle handle__V);\n\n");
// Local variables
// Avoid tracing handle, as it is not a stable value, so breaks vcddiff
// Avoid tracing handle, as it is not a stable value, so breaks wavediff
// Likewise other internals aren't interesting to the user
txtp->add("// verilator tracing_off\n");

View File

@ -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);

View File

@ -47,6 +47,7 @@
#include "V3UniqueNames.h"
#include <limits>
#include <map>
#include <set>
#include <unordered_map>
@ -216,6 +217,9 @@ class TraceVisitor final : public VNVisitor {
using ActCodeSet = std::set<uint32_t>;
// For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
// Candidate interface-member VarScopes keyed by (interface type, member name)
std::map<std::pair<const AstIface*, std::string>, std::vector<AstVarScope*>>
m_ifaceMemberVscps;
// METHODS
@ -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); }

View File

@ -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.

View File

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

View File

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

View File

@ -70,6 +70,18 @@ public:
return keyExtract(VL_CIK_THRESH, m_name.c_str());
}
string linescov() const { return keyExtract(VL_CIK_LINESCOV, m_name.c_str()); }
bool isFsmState() const { return type() == "fsm_state"; }
bool isFsmArc() const { return type() == "fsm_arc"; }
// Arc-specific helpers are used after callers have already filtered to
// FSM arc points, so they do not repeat the type check here.
string fsmVarName() const { return keyExtract(VL_CIK_FSM_VAR, m_name.c_str()); }
string fsmFromState() const { return keyExtract(VL_CIK_FSM_FROM, m_name.c_str()); }
string fsmToState() const { return keyExtract(VL_CIK_FSM_TO, m_name.c_str()); }
string fsmTag() const { return keyExtract(VL_CIK_FSM_TAG, m_name.c_str()); }
bool isFsmResetInclude() const { return fsmTag() == "reset_include"; }
bool isFsmResetArc() const { return fsmTag() == "reset"; }
bool isFsmDefaultArc() const { return fsmTag() == "default"; }
bool fsmIsReset() const { return isFsmResetArc() || isFsmResetInclude(); }
int lineno() const {
const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str());
return std::atoi(lineStr.c_str());

View File

@ -25,6 +25,8 @@
#include <algorithm>
#include <fstream>
#include <map>
#include <set>
#include <string>
#include <vector>
@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) {
int branchesHit = 0;
for (auto& li : lines) {
VlcSourceCount& sc = li.second;
os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n";
const int num_branches = sc.points().size();
if (num_branches == 1) continue;
branchesFound += num_branches;
int point_num = 0;
uint64_t daCount = 0;
std::vector<const VlcPoint*> infoPoints;
for (const auto& point : sc.points()) {
if (point->isFsmArc()) continue;
daCount = std::max(daCount, point->count());
if (!point->isFsmState()) infoPoints.push_back(point);
}
os << "DA:" << sc.lineno() << "," << daCount << "\n";
if (infoPoints.size() <= 1) continue;
branchesFound += static_cast<int>(infoPoints.size());
int point_num = 0;
for (const VlcPoint* point : infoPoints) {
os << "BRDA:" << sc.lineno() << ",";
os << "0,";
os << point_num << ",";
@ -269,8 +277,10 @@ void VlcTop::annotateCalcNeeded() {
}
}
const float pct = totCases ? (100 * totOk / totCases) : 0;
std::cout << "Total coverage (" << totOk << "/" << totCases << ") ";
std::cout << std::fixed << std::setw(3) << std::setprecision(2) << pct << "%\n";
std::cout << "Annotation Summary:\n";
std::cout << " lines with all attached points covered : ";
std::cout << std::fixed << std::setw(5) << std::setprecision(2) << pct << "% (" << totOk
<< "/" << totCases << ")\n";
if (totOk != totCases) cout << "See lines with '%00' in " << opt.annotateOut() << '\n';
}
@ -326,6 +336,29 @@ void VlcTop::annotateOutputFiles(const string& dirname) {
if (opt.annotatePoints()) {
for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin());
}
bool printedFsmHeader = false;
for (const auto& pit : sc.points()) {
if (!pit->isFsmState() && !pit->isFsmArc()) continue;
if (!printedFsmHeader) {
os << " // [FSM coverage]\n";
printedFsmHeader = true;
}
os << (opt.countOk(pit->count()) ? " " : "%");
os << std::setfill('0') << std::setw(6) << pit->count() << " ";
if (pit->isFsmState()) {
os << "// [fsm_state " << pit->comment() << "]";
if (pit->count() == 0) os << " *** UNCOVERED ***";
os << "\n";
} else if (pit->isFsmDefaultArc()) {
os << "// [SYNTHETIC DEFAULT ARC: " << pit->comment() << "]\n";
} else {
os << "// [fsm_arc " << pit->comment() << "]";
if (pit->fsmIsReset() && !opt.includeResetArcs()) {
os << " [reset arc, excluded from %]";
}
os << "\n";
}
}
}
}
}
@ -337,3 +370,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";
}
}

View File

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

View File

@ -31,6 +31,7 @@ class Node:
self._arity = -1 # Arity of node
self._ops = {} # Operands of node
self._ptrs = [] # Pointer members of node (name, types)
self._makeDfgVertex = False # Create a corresponding DfgVertex sub-class
@property
def name(self):
@ -68,6 +69,10 @@ class Node:
assert self.isCompleted
return self._ptrs
@property
def makeDfgVertex(self):
return self._makeDfgVertex
# Pre completion methods
def addSubClass(self, subClass):
assert not self.isCompleted
@ -91,6 +96,9 @@ class Node:
name = re.sub(r'^m_', '', name)
self._ptrs.append({'name': name, 'monad': monad, 'kind': kind, 'legals': legals})
def setMakeDfgVertex(self):
self._makeDfgVertex = True
# Computes derived properties over entire class hierarchy.
# No more changes to the hierarchy are allowed once this was called
def complete(self, typeId=0, ordIdx=0):
@ -609,9 +617,12 @@ def read_types(filename, Nodes, prefix):
if prefix != "Ast":
continue
match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line)
if match:
if match := re.match(r'^\s*//\s*@astgen\s+(.*)$', line):
decl = re.sub(r'//.*$', '', match.group(1))
if decl == "makeDfgVertex":
node.setMakeDfgVertex()
continue
what, sep, rest = partitionAndStrip(decl, ":=")
what = re.sub(r'\s+', ' ', what)
if not sep:
@ -668,10 +679,12 @@ def read_types(filename, Nodes, prefix):
error(
lineno, "Malformed @astgen what (expecting 'op1'..'op4'," +
" 'alias op1'.., 'ptr'): " + what)
else:
line = re.sub(r'//.*$', '', line)
if re.match(r'.*[Oo]p[1-9].*', line):
error(lineno, "Use generated accessors to access op<N> operands")
continue
line = re.sub(r'//.*$', '', line)
if re.match(r'.*[Oo]p[1-9].*', line):
error(lineno, "Use generated accessors to access op<N> operands")
if re.match(r'^\s*Ast[A-Z][A-Za-z0-9_]+\s*\*(\s*const)?\s+m_[A-Za-z0-9_]+\s*;', line):
error(lineno, "Use '@astgen ptr' for Ast pointer members: " + line)
@ -1407,116 +1420,6 @@ check_types(AstNodeList, "Ast", "Node")
# These are standalone so we don't need to parse the sources for this.
DfgVertices["Vertex"] = Node("Vertex", None)
# AstNodeExpr that are not representable in Dfg
DfgIgnored = (
# Floating point operations
"AcosD",
"AcoshD",
"AddD",
"AsinD",
"AsinhD",
"Atan2D",
"AtanD",
"AtanhD",
"BitsToRealD",
"CeilD",
"CosD",
"CoshD",
"DivD",
"EqD",
"ExpD",
"FloorD",
"GtD",
"GteD",
"HypotD",
"ISToRD",
"IToRD",
"Log10D",
"LogD",
"LtD",
"LteD",
"MulD",
"NegateD",
"NeqD",
"PowD",
"RealToBits",
"RToIRoundS",
"RToIS",
"SinD",
"SinhD",
"SqrtD",
"SubD",
"TanD",
"TanhD",
# String operations
"AtoN",
"CompareNN",
"ConcatN",
"CvtPackString",
"EqN",
"GetcN",
"GetcRefN",
"GteN",
"GtN",
"LenN",
"LteN",
"LtN",
"NeqN",
"NToI",
"PutcN",
"ReplicateN",
"SubstrN",
"ToLowerN",
"ToStringN",
"ToUpperN",
# Effectful
"PostAdd",
"PostSub",
"PreAdd",
"PreSub",
# Only used after DFG
"ShiftLOvr",
"ShiftROvr",
"ShiftRSOvr",
"WordSel",
# File operations
"FEof",
"FGetC",
"FGetS",
"FUngetC",
# Dynamic array operations
"AssocSel",
"IsUnbounded",
"WildcardSel",
# Type comparison
"EqT",
"NeqT",
# Distributions
"DistChiSquare",
"DistErlang",
"DistExponential",
"DistNormal",
"DistPoisson",
"DistT",
"DistUniform",
# Specials
"CastDynamic",
"CastWrap",
"CAwait",
"CCast",
"CLog2",
"IsUnknown",
"NullCheck",
"OneHot",
"OneHot0",
"ResizeLValue",
"Signed",
"SliceSel",
"TimeImport",
"Unsigned",
"URandomRange",
)
# Read DfgVertex definitions
for filename in Args.dfgdef:
read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg")
@ -1527,12 +1430,8 @@ for node in AstNodeList:
if not node.isLeaf:
continue
# Ignore any explicitly defined vertex
if node.name in DfgVertices:
continue
# Ignore expressions types that DFG cannot handle
if node.name in DfgIgnored:
# Only create vertices for explcitly marked AstNode sub-types
if not node.makeDfgVertex:
continue
if node.isSubClassOf(AstNodes["NodeUniop"]):
@ -1542,7 +1441,9 @@ for node in AstNodeList:
elif node.isSubClassOf(AstNodes["NodeTriop"]):
base = DfgVertices["VertexTernary"]
else:
continue
sys.exit(
"%Error: cannot create DfgVertex for node not derived from AstNodeUnary, AstNodeBinary, or AstNodeTernary: "
+ node.name)
vertex = Node(node.name, base)
DfgVertices[node.name] = vertex

View File

@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; }
"/*verilator sformat*/" { FL; return yVL_SFORMAT; }
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; }
/* Experimental Verilator-specific FSM coverage controls. These names were
* chosen to match the current extractor behavior, not a published synthesis
* or simulator pragma standard, so they may evolve as we settle on longer-
* term compatibility/aliasing.
*/
"/*verilator fsm_arc_include_cond*/" { FL; return yVL_FSM_ARC_INCL_COND; }
"/*verilator fsm_reset_arc*/" { FL; return yVL_FSM_RESET_ARC; }
"/*verilator fsm_state*/" { FL; return yVL_FSM_STATE; }
"/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
return yVL_TAG; }
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }

View File

@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVL_SC_BV "/*verilator sc_bv*/"
%token<fl> yVL_SFORMAT "/*verilator sformat*/"
%token<fl> yVL_SPLIT_VAR "/*verilator split_var*/"
%token<fl> yVL_FSM_ARC_INCL_COND "/*verilator fsm_arc_include_cond*/"
%token<fl> yVL_FSM_RESET_ARC "/*verilator fsm_reset_arc*/"
%token<fl> yVL_FSM_STATE "/*verilator fsm_state*/"
%token<strp> yVL_TAG "/*verilator tag*/"
%token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/"
%token<fl> yVL_UNROLL_FULL "/*verilator unroll_full*/"
@ -3123,6 +3126,9 @@ sigAttr<nodep>:
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
| yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; }
| yVL_FSM_ARC_INCL_COND { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_ARC_INCLUDE_COND}; }
| yVL_FSM_RESET_ARC { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_RESET_ARC}; }
| yVL_FSM_STATE { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_STATE}; }
;
rangeListE<nodeRangep>: // IEEE: [{packed_dimension}]

View File

@ -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)

View File

@ -1,6 +1,6 @@
%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:11:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2)
%Error-UNSUPPORTED: t/t_assert_consec_rep_unsup.v:13:45: Unsupported: multi-cycle sequence expression inside consecutive repetition (IEEE 1800-2023 16.9.2)
: ... note: In instance 't'
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

View File

@ -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

View File

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

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage basic test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

View File

@ -0,0 +1,63 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
module t(
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
logic sel;
int cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
sel = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) sel <= 1'b1;
if (cyc == 3) sel <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
%000003 case (state)
// [FSM coverage]
%000001 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
%000000 // [fsm_arc t.state::S0->S1]
%000003 // [fsm_arc t.state::S0->S2]
%000000 // [fsm_arc t.state::S1->S0]
%000002 // [fsm_state t.state::S0]
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
%000003 // [fsm_state t.state::S2]
S0: if (sel) state <= S1; else state <= S2;
S1: state <= S0;
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else extraction test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the expected file captures both the extracted
# FSM shape and the per-point hit counts.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

@ -0,0 +1,53 @@
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
module t(
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic rst;
logic sel;
int cyc;
state_t state /*verilator fsm_reset_arc*/;
initial begin
rst = 1'b1;
sel = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst <= 1'b0;
if (cyc == 2) sel <= 1'b1;
if (cyc == 3) sel <= 1'b0;
if (cyc == 6) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S0;
end else begin
case (state)
S0: if (sel) state <= S1; else state <= S2;
S1: state <= S0;
default: state <= S0;
endcase
end
end
endmodule

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM lowered coverage declaration dump test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
from pathlib import Path
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_cover_fsm_styles.v"
# Dump the lowered AST so AstCoverOtherDecl::dump() sees FSM metadata-bearing
# coverage declarations directly. This avoids JSON/schema coupling while still
# covering the dump-side formatting for fv/ff/ft/fg.
test.lint(v_flags=["--coverage-fsm", "--dump-tree"])
tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")]
tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files]
assert any("COVEROTHERDECL" in text and " fv=t.state" in text for text in tree_texts)
assert any(
"COVEROTHERDECL" in text and " ff=ANY" in text and " ft=S0" in text and " fg=reset" in text
for text in tree_texts)
assert any("COVEROTHERDECL" in text and " ff=default" in text and " ft=S0" in text
and " fg=default" in text for text in tree_texts)
test.passes()

View File

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

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM enum transition bad-value test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# When an enum-backed FSM assigns a constant that is not one of the declared
# enum items, FSM coverage should warn and skip the unsupported edge rather
# than turning optional coverage into a hard compile failure.
test.lint(verilator_flags2=["--coverage-fsm"], fails=True)
test.file_grep(
test.compile_log_filename,
r'%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage '
r'on enum state transitions that assign a constant not present in the declared enum')
test.passes()

View File

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

View File

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

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM enum width limit test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
# FSM coverage currently stores recovered enum state values in the detector's
# 32-bit internal representation, so wider enum-backed FSMs are rejected.
test.lint(verilator_flags2=["--coverage-fsm"], fails=True)
test.file_grep(
test.compile_log_filename,
r'%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: '
r'FSM coverage on enum-typed state variables wider than 32 bits')
test.passes()

View File

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

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-line'])
test.execute()
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_state")
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_arc")
test.passes()

View File

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

View File

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

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage forced non-enum test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source golden output so hit-count regressions are visible in the
# expected file instead of being hidden behind coarse coverage.dat greps.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage graph dump test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vltmt')
test.top_filename = "t/t_cover_fsm_styles.v"
test.compile(v_flags2=["--coverage-fsm", "--dumpi-graph", "6"], threads=2)
dot_files = test.glob_some(test.obj_dir + "/*fsm_*.dot")
for dot_filename in dot_files:
test.file_grep(dot_filename, r'digraph v3graph')
test.file_grep_any(dot_files, r'ANY')
test.file_grep_any(dot_files, r'default')
test.passes()

View File

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

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage negative extraction test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
# This test is intentionally "half supported": one case item is a simple
# direct state assignment, while the others use shapes the extractor should
# ignore (multiple meaningful statements or assignment to a non-state lhs).
# That lets us hit the conservative negative branches in directStateAssign()
# and singleMeaningfulStmt() without changing user-visible behavior.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the golden locks down which candidate arcs
# survive extraction and which unsupported shapes are intentionally skipped.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

View File

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

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage no-reset lowering test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
# This test deliberately uses a clocked FSM with no outer reset branch. It
# keeps coverage extraction in the supported subset, but forces lowering down
# the "hasResetCond() == false" path so we validate the no-reset machinery
# rather than only reset-wrapped FSMs.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the expected file captures the no-reset shape
# directly, including the absence of reset pseudo-arcs.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

View File

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

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage reset policy test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--cc --coverage'])
test.execute()
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--include-reset-arcs",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

View File

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

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import vltest_bootstrap
test.scenarios('simulator')
# This regression is aimed at the graph helper, not at recommending RTL style.
# We deliberately create two reset arcs in a single FSM so graph construction
# has to reuse the synthetic ANY reset pseudo-vertex rather than allocating it
# only once for a one-arc machine.
test.compile(verilator_flags2=['--cc --coverage-fsm'])
test.execute()
# Use annotated-source output so the golden proves both reset arcs remain
# visible and share the same synthetic ANY reset source.
test.run(cmd=[
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
"--annotate",
test.obj_dir + "/annotated",
test.obj_dir + "/coverage.dat",
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
test.passes()

View File

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

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