Merge remote-tracking branch 'upstream/master' into fix/7797-seq-event

This commit is contained in:
Yilou Wang 2026-07-04 10:49:13 +02:00
commit d996b4a70e
212 changed files with 4758 additions and 1862 deletions

View File

@ -158,7 +158,7 @@ jobs:
with:
path: repo
- name: Cache $CCACHE_DIR
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: msbuild-msvc-cmake

View File

@ -68,7 +68,7 @@ jobs:
fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history
- name: Cache $CCACHE_DIR
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache
with:

View File

@ -50,7 +50,7 @@ jobs:
sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev
- name: Use saved ccache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ccache
key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }}

View File

@ -84,7 +84,7 @@ jobs:
- name: Use saved ccache
if: ${{ env.CCACHE_DISABLE == 0 }}
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
with:
path: ${{ env.CCACHE_DIR }}
key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }}

View File

@ -67,7 +67,7 @@ jobs:
ls -lsha
- name: Cache $CCACHE_DIR
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
env:
CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2
with:

View File

@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # Use MSVC_RUNTIME_LIBRARY to select the runtime
project(
Verilator
VERSION 5.049
VERSION 5.051
HOMEPAGE_URL https://verilator.org
LANGUAGES CXX
)

52
Changes
View File

@ -10,7 +10,21 @@ The changes in each Verilator version are described below. The
contributors that suggested or implemented a given issue are shown in []. Thanks!
Verilator 5.049 devel
Verilator 5.051 devel
==========================
**Other:**
* Add comments as a branch description in coverage .info files (#7843). [Eryk Szpotanski]
* Optimize random initialization. [Geza Lore, Testorrent USA, Inc.]
* Fix DFG misoptimizing bound checks (#7755). [Jakub Michalski]
* Fix unique0 case side effects (#7787). [Pawel Klopotek]
* Fix cleaning purity cache after assertions. [Geza Lore, Testorrent USA, Inc.]
* Fix clang++ ambiguous overload of '==' operator (#7863). [Pawel Kojma, Antmicro Ltd.]
* Fix heap-use-after-free in `VlRNG::VlRNG()` (#7865). [Dragon-Git]
Verilator 5.050 2026-07-01
==========================
**Important:**
@ -32,7 +46,7 @@ Verilator 5.049 devel
* Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar]
* Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel]
* Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar]
* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.]
* Support assert property `default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.]
* Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton]
* Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang]
* Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.]
@ -67,6 +81,7 @@ Verilator 5.049 devel
* Support property case (#7682) (#7721). [Artur Bieniek, Antmicro Ltd.]
* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.]
* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance]
* Support hierarchical reference cross members (#7749) (#7820). [Matthew Ballance]
* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.]
* Support NBAs in initial blocks (#7754). [Igor Zaworski, Antmicro Ltd.]
* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang]
@ -75,6 +90,11 @@ Verilator 5.049 devel
* Support $assertcontrol control_type from lock to kill (#7788). [Yilou Wang]
* Support unbounded always [m:$] and strong s_always liveness (#7798). [Yilou Wang]
* Support method calls on a sub-interface via a virtual interface (#7800). [Yilou Wang]
* Support global $assertcontrol (#7807). [Artur Bieniek, Antmicro Ltd.]
* Support VPI access to unpacked struct members (#7823). [Nick Brereton]
* Support variable-length intersect in SVA sequences (#7835). [Yilou Wang]
* Support dynamic loading of VPI extensions (#7727). [Matthew Ballance]
* Optimize DFG cycle breaking to do less work (#7210). [Geza Lore, Testorrent USA, Inc.]
* Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.]
* Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.]
* Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.]
@ -97,7 +117,12 @@ Verilator 5.049 devel
* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.]
* Optimize decoder case statements into lookup tables (#7795). [Geza Lore, Testorrent USA, Inc.]
* Optimize wide decoder case statements into decoder expressions (#7804). [Geza Lore, Testorrent USA, Inc.]
* Optimize DFG cycle breaking to do less work (#7210). [Geza Lore, Testorrent USA, Inc.]
* Optimize statically known oversize shifts (#7806). [Geza Lore, Testorrent USA, Inc.]
* Optimize generated function inlining (#7811). [Geza Lore, Testorrent USA, Inc.]
* Optimize bit-scan loops into most-set-bit or $countones (#7822). [Thomas Santerre]
* Optimize additional expression patterns (#7824). [Geza Lore, Testorrent USA, Inc.]
* Optimize module inlining heuristic (#7837). [Geza Lore, Testorrent USA, Inc.]
* Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton]
* Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal]
* Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.]
* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli]
@ -147,6 +172,7 @@ Verilator 5.049 devel
* Fix reference counting for modport task references (#7628). [Nick Brereton]
* Fix internal error when handling typedefs containing parameterized class type members (#7635) (#7661). [em2machine]
* Fix forceable signal with a procedural continuous assign (#7638) (#7639). [Zubin Jain]
* Fix scheduling of virtual interface method writes (#7641). [Artur Bieniek, Antmicro Ltd.]
* Fix implicit conversions of VlWide (#7642). [Geza Lore, Testorrent USA, Inc.]
* Fix CASEINCOMPLETE to not warn on `unique0 case` (#7647).
* Fix hierarchical coverage counts for duplicate no-inline module instances (#7649). [Yogish Sekhar]
@ -173,17 +199,29 @@ Verilator 5.049 devel
* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton]
* Fix dpi export pointers (#7742) (#7751). [Yilin Li]
* Fix force on unpacked bit select (#7744) (#7745). [Nikolai Kumar]
* Fix `$` as unsupported coverpoint-bin range bounds (#7750) (#7825). [Matthew Ballance]
* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.]
* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang]
* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.]
* Fix `case (_) inside` with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.]
* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.]
* Fix $fflush and autoflush with --threads (#7782).
* Fix out-of-bounds read value for 2-state types (#7785). [Jakub Michalski]
* Fix `cover property` of an implication counting vacuous matches (#7789). [Yilou Wang]
* Fix randomization of dynamic arrays of objects (#7790). [Ryszard Rozak, Antmicro Ltd.]
* Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton]
* Fix randomize() with skipping derived pre/post_randomize (#7799). [Yilou Wang]
* Fix skewed dist operator for arrays (#7802). [Jakub Wasilewski, Antmicro Ltd.]
* Fix assertion when loop unrolling failed (#7810). [Geza Lore, Testorrent USA, Inc.]
* Fix CASEINCOMPLETE for all uncovered enum items (#7815) (#7817). [Saksham]
* Fix split optimization nested-class crash (#7826). [Igor Zaworski, Antmicro Ltd.]
* Fix class/var named identically to an enclosing-scope type (#7827) (#7828). [Tom Jackson]
* Fix performance on large package-scoped structs (#7830). [Wolfgang Mayerwieser]
* Fix unclocked concurrent assertion misreported as unsupported (#7831). [Yilou Wang]
* Fix insertion of expression coverage statement (#7832). [Ryszard Rozak, Antmicro Ltd.]
* Fix lifetime of expression coverage variable (#7834). [Ryszard Rozak, Antmicro Ltd.]
* Fix disable iff ignored when its condition is held continuously true (#7841). [Yilou Wang]
* Fix constant pool cache after dead scope removal (#7845). [Nick Brereton]
* Fix covergroups without --coverage (#7848) (#7849). [Joshua Leahy]
* Fix class scope '::' reference through an inherited type parameter (#7844). [Sergey Chusov]
Verilator 5.048 2026-04-26
@ -520,7 +558,7 @@ Verilator 5.044 2026-01-01
* Support clocking output delay `1step` (#6681). [Ondrej Ille]
* Support parsing of dotted `bins_expression` (#6683). [Pawel Kojma, Antmicro Ltd.]
* Support constant expression cycle delays in sequences (#6691). [Ryszard Rozak, Antmicro Ltd.]
* Support general global constraints (#6709) (#6711). [Yilou Wang]
* Support general global constraints (#6709) (#6711) (#7833) (#7838). [Yilou Wang]
* Support complex std::randomize patterns (#6736) (#6737). [Yilou Wang]
* Support `rand_mode` in global constraint gathering (#6740) (#6752). [Yilou Wang]
* Support reduction or in constraints (#6840). [Pawel Kojma, Antmicro Ltd.]
@ -5714,7 +5752,7 @@ Verilator 3.104 2003-04-30
**Major:**
* Indicate direction of ports with VL_IN and VL_OUT.
* Allow $c32, etc, to specify width of the $c statement for VCS.
* Allow $c32, etc, to specify width of the $c statement.
* Numerous performance improvements, worth about 25%
**Minor:**

View File

@ -653,6 +653,7 @@ description of these arguments.
+verilator+solver+file+<filename> Set random solver log filename
+verilator+V Show verbose version and config
+verilator+version Show version and exit
+verilator+vpi+<library>[:<bootstrap>] Load VPI shared library
+verilator+wno+unsatconstr+<value> Disable constraint warnings

View File

@ -12,7 +12,7 @@
# Then 'make maintainer-dist'
#AC_INIT([Verilator],[#.### YYYY-MM-DD])
#AC_INIT([Verilator],[#.### devel])
AC_INIT([Verilator],[5.049 devel],
AC_INIT([Verilator],[5.051 devel],
[https://verilator.org],
[verilator],[https://verilator.org])
@ -589,6 +589,34 @@ m4_foreach([ldflag], [
AC_SUBST(CFG_LDLIBS_THREADS)
AC_SUBST(CFG_LDFLAGS_THREADS_CMAKE)
# Find link flags for runtime VPI library loading (+verilator+vpi+<lib>).
# The model executable must export its VPI symbols so the dlopen'd library can
# resolve them: -rdynamic (GNU ld) or -Wl,-export_dynamic (Darwin); the first the
# linker accepts wins. -ldl provides dlopen/dlsym where it is a separate library.
_MY_LDLIBS_CHECK_SET(CFG_LDFLAGS_DYNAMIC, -rdynamic)
# -Wl,-export_dynamic contains a comma, so probe it directly rather than through
# the _MY_LDLIBS_CHECK_* macros (which re-expand their flag argument unquoted).
if test "$CFG_LDFLAGS_DYNAMIC" = ""; then
ACO_SAVE_LIBS="$LIBS"
LIBS="$LIBS -Wl,-export_dynamic"
AC_MSG_CHECKING([whether $CXX linker accepts -Wl,-export_dynamic])
AC_LINK_IFELSE(
[AC_LANG_PROGRAM([[]])],
[_my_result=yes
if test -s conftest.err; then
if grep -e "-export_dynamic" conftest.err >/dev/null; then
_my_result=no
fi
fi],
[_my_result=no])
AC_MSG_RESULT($_my_result)
LIBS="$ACO_SAVE_LIBS"
if test "$_my_result" = "yes"; then CFG_LDFLAGS_DYNAMIC="-Wl,-export_dynamic"; fi
fi
AC_SUBST(CFG_LDFLAGS_DYNAMIC)
_MY_LDLIBS_CHECK_OPT(CFG_LDLIBS_DYNAMIC, -ldl)
AC_SUBST(CFG_LDLIBS_DYNAMIC)
# If 'mold' is installed, use it to link for faster buildtimes
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_SRC, -fuse-ld=mold)
_MY_LDLIBS_CHECK_OPT(CFG_LDFLAGS_VERILATED, -fuse-ld=mold)

View File

@ -61,6 +61,7 @@ David Turner
Dercury
Diego Roux
Dominick Grochowina
Dragon-Git
Don Williamson
Drew Ranck
Drew Taussig
@ -69,6 +70,7 @@ Edgar E. Iglesias
Eric Mejdrich
Eric Müller
Eric Rippey
Eryk Szpotański
Eunseo Song
Ethan Sifferman
Eyck Jentzsch
@ -146,6 +148,7 @@ Jose Loyola
Josep Sans
Joseph Nwabueze
Josh Redford
Joshua Leahy
Julian Carrier
Julian Daube
Julie Schwartz
@ -257,6 +260,7 @@ Samuel Riedel
Sean Cross
Sebastien Van Cauwenberghe
Secturion
Sergey Chusov
Sergey Fedorov
Sergi Granell
Seth Pellegrino
@ -273,6 +277,7 @@ Teng Huang
Thomas Aldrian
Thomas Brown
Thomas Dybdahl Ahle
Thomas Santerre
Tim Hutt
Tim Snyder
Tobias Jensen
@ -301,6 +306,7 @@ Vito Gamberini
Wei-Lun Chiu
William D. Jones
Wilson Snyder
Wolfgang Mayerwieser
Xi Zhang
Yan Xu
Yangyu Chen

View File

@ -179,7 +179,7 @@ DPI Example
In the SYSTEMC example above, if you wanted to import C++ functions into
Verilog, put in our.v:
.. code-block:: sv
.. code-block::
import "DPI-C" function int add (input int a, input int b);
@ -211,7 +211,7 @@ Verilator extends the DPI format to allow using the same scheme to
efficiently add system functions. Use a dollar-sign prefixed system
function name for the import, but note it must be escaped.
.. code-block:: sv
.. code-block::
import "DPI-C" function integer \$myRand;

View File

@ -139,6 +139,20 @@ Options:
Displays program version and exits.
.. option:: +verilator+vpi+<library>[:<bootstrap>]
Load a VPI shared library before simulation starts. Only available when the
model was Verilated with :vlopt:`--vpi` and :vlopt:`--main` (or
:vlopt:`--binary`). ``<library>`` is the path to the shared library. If
``:<bootstrap>`` is given, that named no-argument function is called;
otherwise the library's ``vlog_startup_routines`` array (IEEE 1800 38.37.2) is
invoked. May be repeated to load multiple libraries.
Runtime loading is supported on POSIX platforms only (it relies on the
executable exporting its VPI symbols to the loaded library); on Windows the
argument is rejected and the VPI code must instead be statically linked
into the model.
.. option:: +verilator+wno+unsatconstr+<value>
Disable unsatisfied constraint warnings at simulation runtime. When set to

View File

@ -662,6 +662,10 @@ Summary:
.. option:: -fno-assemble
.. option:: -fno-bit-scan-loops
Rarely needed. Disable converting bit counting loops into built-in operations.
.. option:: -fno-case
Rarely needed. Disable all case statement optimizations.

View File

@ -477,6 +477,7 @@ Syms
Synopsys
SystemC
SystemVerilog
Szpotanski
Takatsukasa
Tambe
Tarik
@ -537,6 +538,7 @@ Verilog
Vighnesh
Viktor
Vilp
VlRNG
VlWide
Vlip
Vm
@ -684,6 +686,7 @@ countones
cout
covergroup
covergroups
coverpoint
coverpoints
cpp
cppstyle
@ -946,6 +949,7 @@ misconnected
misconversion
misdetecting
misoptimized
misoptimizing
missized
mk
mno
@ -1207,6 +1211,7 @@ typename
uint
un
unbased
unclocked
uncomment
undef
undefineall

View File

@ -89,6 +89,12 @@
# include <unistd.h>
# define _VL_HAVE_GETRLIMIT
#endif
#if VM_VPI
# include <cstring>
# ifndef _WIN32
# include <dlfcn.h> // dlopen
# endif
#endif
#include "verilated_threads.h"
// clang-format on
@ -260,6 +266,58 @@ void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char*
}});
}
//===========================================================================
// Runtime VPI shared library loading (--vpi)
// Load one VPI shared library named by a +verilator+vpi+<lib>[:<bootstrap>] argument.
// 'arg' is the payload after the prefix: either "<lib>" (invoke the library's
// vlog_startup_routines array) or "<lib>:<bootstrap>" (invoke the named bootstrap).
void Verilated::loadVpiLib(const std::string& arg) VL_MT_UNSAFE {
#if VM_VPI
if (arg.empty()) return;
#ifdef _WIN32
VL_FATAL_MT("", 0, "",
"+verilator+vpi+: runtime VPI library loading is not supported on"
" Windows; link the VPI code into the model instead");
#else
using vlog_startup_t = void (*)();
// Split <lib>:<bootstrap> on the last ':'
const std::string::size_type colon_pos = arg.rfind(':');
const bool has_entry = (colon_pos != std::string::npos);
const std::string libpath = has_entry ? arg.substr(0, colon_pos) : arg;
const std::string entry_name = has_entry ? arg.substr(colon_pos + 1) : std::string{};
void* handle = dlopen(libpath.c_str(), RTLD_LAZY);
if (!handle)
// The library path is stable; the dlerror() text is platform-specific, so put it on
// a separate "- " line (test golden files strip "- " lines, keeping output portable).
VL_FATAL_MT(
"", 0, "",
(std::string{"Cannot load VPI library: "} + libpath + "\n- dlerror: " + dlerror())
.c_str());
if (has_entry) {
vlog_startup_t bsp = reinterpret_cast<vlog_startup_t>(dlsym(handle, entry_name.c_str()));
if (!bsp)
VL_FATAL_MT(
"", 0, "",
(std::string{"Cannot find VPI bootstrap '"} + entry_name + "' in: " + libpath)
.c_str());
bsp();
} else {
vlog_startup_t* routinesp
= reinterpret_cast<vlog_startup_t*>(dlsym(handle, "vlog_startup_routines"));
if (!routinesp)
VL_FATAL_MT(
"", 0, "",
(std::string{"Cannot find 'vlog_startup_routines' in: "} + libpath).c_str());
for (int j = 0; routinesp[j]; ++j) routinesp[j]();
}
#endif
#else
// Never reached: the command-line handler only calls this when compiled with --vpi.
(void)arg;
#endif
}
//===========================================================================
// Debug prints
@ -319,7 +377,6 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE {
}
void VL_FFLUSH_MT() VL_MT_SAFE {
va_list ap;
VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { //
Verilated::runFlushCallbacks();
}});
@ -506,9 +563,10 @@ IData VL_URANDOM_SEEDED_II(IData seed) VL_MT_SAFE {
}
IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
IData data = ~0;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
if (randReset != 1) { // if 2, randomize
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
data = rng.rand64();
}
@ -517,9 +575,10 @@ IData VL_SCOPED_RAND_RESET_I(int obits, uint64_t scopeHash, uint64_t salt) VL_MT
}
QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
QData data = ~0ULL;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
if (randReset != 1) { // if 2, randomize
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
data = rng.rand64();
}
@ -529,10 +588,17 @@ QData VL_SCOPED_RAND_RESET_Q(int obits, uint64_t scopeHash, uint64_t salt) VL_MT
WDataOutP VL_SCOPED_RAND_RESET_W(int obits, WDataOutP outwp, uint64_t scopeHash,
uint64_t salt) VL_MT_UNSAFE {
if (Verilated::threadContextp()->randReset() != 2) { return VL_RAND_RESET_W(obits, outwp); }
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = rng.rand64();
outwp[VL_WORDS_I(obits) - 1] = rng.rand64() & VL_MASK_E(obits);
const int words = VL_WORDS_I(obits);
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) {
VL_MEMSET_ZERO_W(outwp, words);
} else if (randReset == 1) {
VL_MEMSET_ONES_W(outwp, words);
} else {
VlRNG rng{Verilated::threadContextp()->randSeed() ^ scopeHash ^ salt};
for (int i = 0; i < words; ++i) outwp[i] = rng.rand64();
}
outwp[words - 1] &= VL_MASK_E(obits);
return outwp;
}
@ -557,30 +623,14 @@ WDataOutP VL_SCOPED_RAND_RESET_ASSIGN_W(int obits, WDataOutP outwp, uint64_t sco
}
IData VL_RAND_RESET_I(int obits) VL_MT_SAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
const int randReset = Verilated::threadContextp()->randReset();
if (randReset == 0) return 0;
IData data = ~0;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
data = VL_RANDOM_I();
}
if (randReset != 1) data = VL_RANDOM_I(); // if 2, randomize
data &= VL_MASK_I(obits);
return data;
}
QData VL_RAND_RESET_Q(int obits) VL_MT_SAFE {
if (Verilated::threadContextp()->randReset() == 0) return 0;
QData data = ~0ULL;
if (Verilated::threadContextp()->randReset() != 1) { // if 2, randomize
data = VL_RANDOM_Q();
}
data &= VL_MASK_Q(obits);
return data;
}
WDataOutP VL_RAND_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE {
for (int i = 0; i < VL_WORDS_I(obits) - 1; ++i) outwp[i] = VL_RAND_RESET_I(32);
outwp[VL_WORDS_I(obits) - 1] = VL_RAND_RESET_I(32) & VL_MASK_E(obits);
return outwp;
}
WDataOutP VL_ZERO_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE {
// Not inlined to speed up compilation of slowpath code
return VL_ZERO_W(obits, outwp);
@ -3544,6 +3594,17 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
// and the run can be reproduced by passing +verilator+seed+<that_value>.
if (u64 == 0) u64 = pickRandomSeed();
randSeed(static_cast<int>(u64));
} else if (commandArgVlString(arg, "+verilator+vpi+", str)) {
// With --vpi, load the requested shared library now. Without --vpi there is
// no VPI runtime, so warn the argument is ignored.
#if VM_VPI
Verilated::loadVpiLib(str);
#else
VL_WARN_MT(
"COMMAND_LINE", 0, "",
("+verilator+vpi+ ignored: simulation was not compiled with --vpi '" + arg + "'")
.c_str()); // LCOV_EXCL_LINE (gcov zeroes this wrapped continuation line)
#endif
} else if (arg == "+verilator+V") {
VerilatedImp::versionDump(); // Someday more info too
VL_FATAL_MT("COMMAND_LINE", 0, "",

View File

@ -1036,6 +1036,9 @@ public:
static void scTraceBeforeElaborationError() VL_ATTR_NORETURN VL_MT_SAFE;
static void stackCheck(QData needSize) VL_MT_UNSAFE;
// Internal: Load a VPI shared library (+verilator+vpi+<lib>[:<bootstrap>])
static void loadVpiLib(const std::string& arg) VL_MT_UNSAFE;
// Internal: Get and set DPI context
static const VerilatedScope* dpiScope() VL_MT_SAFE { return t_s.t_dpiScopep; }
static void dpiScope(const VerilatedScope* scopep) VL_MT_SAFE { t_s.t_dpiScopep = scopep; }

View File

@ -52,6 +52,9 @@ CFG_GCH_IF_CLANG = @CFG_GCH_IF_CLANG@
CFG_LDFLAGS_VERILATED = @CFG_LDFLAGS_VERILATED@
# Linker libraries for multithreading
CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@
# Linker flags/libraries for runtime VPI library loading (+verilator+vpi+<lib>)
CFG_LDFLAGS_DYNAMIC = @CFG_LDFLAGS_DYNAMIC@
CFG_LDLIBS_DYNAMIC = @CFG_LDLIBS_DYNAMIC@
######################################################################
# Programs
@ -93,6 +96,7 @@ VK_CPPFLAGS_ALWAYS += \
-DVM_TRACE_FST=$(VM_TRACE_FST) \
-DVM_TRACE_VCD=$(VM_TRACE_VCD) \
-DVM_TRACE_SAIF=$(VM_TRACE_SAIF) \
-DVM_VPI=$(VM_VPI) \
$(CFG_CXXFLAGS_NO_UNUSED) \
ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users

View File

@ -14,7 +14,8 @@
/// \file
/// \brief Verilated functional-coverage collection runtime implementation
///
/// Compiled and linked when "verilator --coverage" is used with covergroups.
/// Linked when covergroups are present. The coverage-database registration
/// is compiled only with "verilator --coverage".
///
//=============================================================================
@ -22,7 +23,9 @@
#include "verilated_covergroup.h"
#if VM_COVERAGE
#include "verilated_cov.h"
#endif
void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) {
m_hier = hier;
@ -54,6 +57,7 @@ std::string VlCoverpoint::binName(int i) const {
return name;
}
#if VM_COVERAGE
void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) {
for (int i = 0; i < binCount(); ++i) {
const VlCovNamer& nm = namerFor(i);
@ -76,3 +80,4 @@ void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* pa
}
}
}
#endif // VM_COVERAGE

View File

@ -124,10 +124,6 @@ extern WDataOutP VL_SCOPED_RAND_RESET_ASSIGN_W(int obits, WDataOutP outwp, uint6
/// Random reset a signal of given width (init time only)
extern IData VL_RAND_RESET_I(int obits) VL_MT_SAFE;
/// Random reset a signal of given width (init time only)
extern QData VL_RAND_RESET_Q(int obits) VL_MT_SAFE;
/// Random reset a signal of given width (init time only)
extern WDataOutP VL_RAND_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE;
/// Zero reset a signal (slow - else use VL_ZERO_W)
extern WDataOutP VL_ZERO_RESET_W(int obits, WDataOutP outwp) VL_MT_SAFE;
@ -903,15 +899,31 @@ static inline IData VL_CLOG2_W(int words, WDataInP const lwp) VL_PURE {
return 0;
}
static inline IData VL_MOSTSETBITP1_I(IData lhs) VL_PURE {
if (VL_UNLIKELY(!lhs)) return 0; // __builtin_clz is undefined for 0
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS)
return VL_EDATASIZE - __builtin_clz(lhs);
#else
for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) {
if (VL_BITISSET_E(lhs, bit)) return bit + 1;
}
return 0; // LCOV_EXCL_LINE // Can't get here - one bit must be set
#endif
}
static inline IData VL_MOSTSETBITP1_Q(QData lhs) VL_PURE {
if (VL_UNLIKELY(!lhs)) return 0;
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(VL_NO_BUILTINS)
return 64 - __builtin_clzll(static_cast<unsigned long long>(lhs));
#else
const IData hi = static_cast<IData>(lhs >> 32ULL);
return hi ? (VL_EDATASIZE + VL_MOSTSETBITP1_I(hi))
: VL_MOSTSETBITP1_I(static_cast<IData>(lhs));
#endif
}
static inline IData VL_MOSTSETBITP1_W(int words, WDataInP const lwp) VL_PURE {
// MSB set bit plus one; similar to FLS. 0=value is zero
for (int i = words - 1; i >= 0; --i) {
if (VL_UNLIKELY(lwp[i])) { // Shorter worst case if predict not taken
for (int bit = VL_EDATASIZE - 1; bit >= 0; --bit) {
if (VL_UNLIKELY(VL_BITISSET_E(lwp[i], bit))) return i * VL_EDATASIZE + bit + 1;
}
// Can't get here - one bit must be set
}
// Shorter worst case if predict not taken
if (VL_UNLIKELY(lwp[i])) return i * VL_EDATASIZE + VL_MOSTSETBITP1_I(lwp[i]);
}
return 0;
}
@ -1009,14 +1021,10 @@ static inline IData VL_EQ_R(int words, VlQueue<T> q, WDataInP const rwp) VL_PURE
} else if (sizeof(T) == 4) {
for (int i = 0; (i < wordsInQ + 1); ++i) { nequal |= (q.at(wordsInQ - i) ^ rwp[i]); }
} else if (sizeof(T) == 8) {
QData temp = 0;
int qSize = q.size() - 1;
for (int i = 0; (i < qSize); i += 2) {
temp = q.at(qSize - i);
nequal |= (static_cast<QData>(q.at(qSize - i)) >> 32 ^ rwp[i + 1]);
temp = rwp[i + 1];
nequal |= (static_cast<QData>(q.at(qSize - i)) ^ rwp[i]);
temp = rwp[i];
}
}
return (nequal == 0);
@ -1026,7 +1034,6 @@ template <std::size_t N_Words>
static inline IData VL_EQ_R(int words, const VlQueue<VlWide<N_Words>>& q,
WDataInP const rwp) VL_PURE {
EData nequal = 0;
const int wordsInQ = q.size() * N_Words;
if ((q.size() * N_Words) != words) { return false; }
int count = 0;
for (int qIndex = q.size() - 1; qIndex >= 0; qIndex--) {
@ -1937,7 +1944,6 @@ static inline void VL_STREAMR_RRI(int lbits, VlQueue<T_Value>& to_q,
const VlQueue<VlWide<N_Words>>& from_q, IData rd) VL_MT_SAFE {
to_q.clear();
VL_CONSTEXPR_CXX17 size_t otherSize = 4 * N_Words;
VL_CONSTEXPR_CXX17 size_t sizeOfThis = sizeof(T_Value);
T_Value temp = 0;
for (auto val : from_q) {

View File

@ -337,6 +337,7 @@ public:
~VlProcess() {
if (m_parentp) m_parentp->detach(this);
if (t_currentp == this) t_currentp = m_parentp.get();
}
void attach(VlProcess* childp) { m_children.insert(childp); }
@ -2069,8 +2070,24 @@ struct VlNull final {
operator T*() const {
return nullptr;
}
template <class T>
bool operator==(T* rhs) const {
return !rhs;
}
template <class T>
bool operator==(const T* rhs) const {
return !rhs;
}
};
inline bool operator==(const void* ptr, VlNull) { return !ptr; }
template <class T>
inline bool operator==(T* lhs, VlNull) {
return !lhs;
}
template <class T>
inline bool operator==(const T* lhs, VlNull) {
return !lhs;
}
//===================================================================
// Verilog class reference container

View File

@ -288,6 +288,9 @@ class AssertVisitor final : public VNVisitor {
AstNode* m_failsp = nullptr; // Current fail statement
AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion
AstFinal* m_finalp = nullptr; // Current final block
VDouble0 m_statLiftedCaseExprs; // Count of purified case expressions
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
V3UniqueNames m_caseTempNames{"__VCase"};
// Map from (expression, senTree) to AstAlways that computes delayed values of the expression
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
@ -338,30 +341,26 @@ class AssertVisitor final : public VNVisitor {
}
VL_UNREACHABLE;
}
static string assertActionControlPrefix(VAssertDirectiveType directiveType) {
const int controlled = !!(static_cast<int>(directiveType)
& (static_cast<int>(VAssertDirectiveType::ASSERT)
| static_cast<int>(VAssertDirectiveType::COVER)
| static_cast<int>(VAssertDirectiveType::ASSUME)));
const int checkRuntime = controlled & static_cast<int>(v3Global.opt.assertOn());
return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime)
+ " && "s;
static bool isControlled(VAssertDirectiveType directiveType) {
return (static_cast<int>(directiveType)
& (static_cast<int>(VAssertDirectiveType::ASSERT)
| static_cast<int>(VAssertDirectiveType::COVER)
| static_cast<int>(VAssertDirectiveType::ASSUME)));
}
static AstNodeExpr* assertPassOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType, bool vacuous) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertActionControlPrefix(directiveType)
+ assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType)
+ "))"s,
1};
assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType), 1};
}
static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertActionControlPrefix(directiveType)
+ assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType)
+ "))"s,
1};
assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType), 1};
}
string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message,
VDisplayType severity) {
@ -434,6 +433,7 @@ class AssertVisitor final : public VNVisitor {
AstNodeIf* const newp = new AstIf{fl, condp, bodyp};
newp->isBoundsCheck(true); // To avoid LATCH warning
newp->user1(true); // Don't assert/cover this if
newp->user2(true); // Mark as an assertOn() check
return newp;
}
static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType,
@ -445,6 +445,7 @@ class AssertVisitor final : public VNVisitor {
AstNodeIf* const newp = new AstIf{fl, condp, bodyp};
newp->isBoundsCheck(true); // To avoid LATCH warning
newp->user1(true); // Don't assert/cover this if
newp->user2(true); // Mark as an assertOn() check
return newp;
}
@ -760,10 +761,10 @@ class AssertVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
iterateChildren(nodep);
}
iterateChildren(nodep);
if (nodep->user2()) {
// Combine consecutive assertOn checks if possible
if (AstIf* const backp = VN_CAST(nodep->backp(), If)) {
@ -821,6 +822,28 @@ class AssertVisitor final : public VNVisitor {
//========== Case assertions
void visit(AstCase* nodep) override {
// Introduce temporary variable for AstCase if needed - it is done here and not in V3Case
// because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures
// that V3Scope will take care of a scope creation
// We also need to do it before V3Begin, co that pragmas like `unique0` also work correctly
if (!nodep->exprp()->isPure()) {
++m_statLiftedCaseExprs;
FileLine* const fl = nodep->exprp()->fileline();
AstVar* const varp = new AstVar{fl, VVarType::BLOCKTEMP, m_caseTempNames.get(nodep),
nodep->exprp()->dtypep()};
AstNodeExpr* const origp = nodep->exprp()->unlinkFrBack();
nodep->addHereThisAsNext(
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, origp});
nodep->exprp(new AstVarRef{fl, varp, VAccess::READ});
if (m_ftaskp) {
varp->funcLocal(true);
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
} else {
m_modp->stmtsp()->addHereThisAsNext(varp);
}
VIsCached::clearCacheTree();
}
iterateChildren(nodep);
if (!nodep->user1SetOnce()) {
bool has_default = false;
@ -1246,6 +1269,9 @@ public:
V3Stats::addStat("Assertions, $past variables", m_statPastVars);
V3Stats::addStat("Assertions, assertOn checks combined", m_statAssertOnCombined);
V3Stats::addStat("Assertions, assertOn checks hoisted", m_statAssertOnHoisted);
V3Stats::addStat("Assertions, lifted impure case expressions", m_statLiftedCaseExprs);
// Rewrites can change purity, e.g. by compiling out assertion statements with --no-assert
VIsCached::clearCacheTree();
}
};

View File

@ -384,6 +384,74 @@ class SvaNfaBuilder final {
return 0;
}
// Contiguous match-length range [lo,hi] for an operand whose length varies
// from AT MOST ONE ranged cycle delay; {-1,-1} otherwise. Drives the
// variable-length `intersect` lowering (IEEE 1800-2023 16.9.6): more than
// one ranged delay would make the per-length realization ambiguous, so it
// is reported unsupported rather than mis-paired.
static std::pair<int, int> lengthRange(AstNodeExpr* nodep) {
if (AstSExpr* const sexprp = VN_CAST(nodep, SExpr)) {
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
if (!delayp || !delayp->isCycleDelay()) return {-1, -1};
std::pair<int, int> delayRange;
if (delayp->isRangeDelay()) {
if (delayp->isUnbounded()) return {-1, -1};
const int minD = getConstInt(delayp->lhsp());
const int maxD = getConstInt(delayp->rhsp());
if (minD < 0 || maxD < 0 || maxD < minD) return {-1, -1};
delayRange = {minD, maxD};
} else {
const int d = getConstInt(delayp->lhsp());
if (d < 0) return {-1, -1};
delayRange = {d, d};
}
std::pair<int, int> preRange{0, 0};
if (AstNodeExpr* const prep = sexprp->preExprp()) {
preRange = lengthRange(prep);
if (preRange.first < 0) return {-1, -1};
}
const std::pair<int, int> bodyRange = lengthRange(sexprp->exprp());
if (bodyRange.first < 0) return {-1, -1};
const int variableParts = (preRange.first != preRange.second)
+ (delayRange.first != delayRange.second)
+ (bodyRange.first != bodyRange.second);
if (variableParts > 1) return {-1, -1};
return {preRange.first + delayRange.first + bodyRange.first,
preRange.second + delayRange.second + bodyRange.second};
}
if (AstSThroughout* const throughp = VN_CAST(nodep, SThroughout)) {
return lengthRange(throughp->rhsp());
}
if (nodep->isMultiCycleSva()) return {-1, -1};
return {0, 0}; // plain boolean -- 0 cycles
}
// Clone `operand` with its sole variable ranged cycle delay pinned so the
// total match length is exactly `len`. `lo` is the operand's minimum length
// (lengthRange().first). A fixed operand has no such delay and is returned
// as a plain clone (callers only request its single achievable length).
static AstNodeExpr* realizeAtLength(AstNodeExpr* operand, int len, int lo) {
AstNodeExpr* const clonep = operand->cloneTreePure(false);
AstDelay* rangeDelayp = nullptr;
clonep->foreach([&](AstDelay* dp) {
if (!rangeDelayp && dp->isRangeDelay() && !dp->isUnbounded()
&& getConstInt(dp->lhsp()) != getConstInt(dp->rhsp())) {
rangeDelayp = dp;
}
});
if (rangeDelayp) {
FileLine* const flp = rangeDelayp->fileline();
const int pinned = getConstInt(rangeDelayp->lhsp()) + (len - lo);
AstNodeExpr* const oldMinp = rangeDelayp->lhsp();
oldMinp->replaceWith(new AstConst{flp, static_cast<uint32_t>(pinned)});
VL_DO_DANGLING(oldMinp->deleteTree(), oldMinp);
// Drop the max bound so it lowers as a fixed `##d`, not `##[d:d]`.
AstNode* const oldMaxp = rangeDelayp->rhsp()->unlinkFrBack();
VL_DO_DANGLING(oldMaxp->deleteTree(), oldMaxp);
}
return clonep;
}
// Cuts AST size from O(N * sizeof(exprp)) to O(N) + O(sizeof(exprp)) by
// sharing a single `VarRef` across N check edges. Hoist also matches the
// IEEE 1800-2023 16.9.9 "single preponed-region snapshot" semantic for
@ -1028,6 +1096,188 @@ class SvaNfaBuilder final {
return result;
}
// Collect the boolean leaf checks of a fixed-length sequence keyed by their
// clock offset from the start. Returns false for anything other than nested
// AstSExpr with fixed cycle delays over boolean leaves (e.g. throughout).
static bool flattenFixedSeq(AstNodeExpr* nodep, int baseOffset,
std::map<int, std::vector<AstNodeExpr*>>& out) {
if (AstSExpr* const sexprp = VN_CAST(nodep, SExpr)) {
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
if (!delayp || !delayp->isCycleDelay() || delayp->isUnbounded()) return false;
const int delayCycles = getConstInt(delayp->lhsp());
if (delayCycles < 0) return false;
if (delayp->isRangeDelay() && getConstInt(delayp->rhsp()) != delayCycles) return false;
int preLen = 0;
if (AstNodeExpr* const prep = sexprp->preExprp()) {
if (!flattenFixedSeq(prep, baseOffset, out)) return false;
preLen = fixedLength(prep);
if (preLen < 0) return false;
}
return flattenFixedSeq(sexprp->exprp(), baseOffset + preLen + delayCycles, out);
}
if (nodep->isMultiCycleSva()) return false;
out[baseOffset].push_back(nodep);
return true;
}
// Conjoin two equal-length fixed sequences into one: at each clock offset
// AND the boolean checks of both operands (IEEE 1800-2023 16.9.6 -- both
// operands match the same window). Returns null if either operand is not a
// plain fixed sequence of boolean leaves.
static AstNodeExpr* conjoinFixedSeqs(AstNodeExpr* lhsp, AstNodeExpr* rhsp, FileLine* flp) {
std::map<int, std::vector<AstNodeExpr*>> checks;
if (!flattenFixedSeq(lhsp, 0, checks) || !flattenFixedSeq(rhsp, 0, checks)) return nullptr;
if (checks.empty()) return nullptr;
AstNodeExpr* resultp = nullptr;
int prevOffset = 0;
for (const auto& offsetChecks : checks) {
const int offset = offsetChecks.first;
AstNodeExpr* condp = nullptr;
for (AstNodeExpr* const leafp : offsetChecks.second) {
AstNodeExpr* const clonep = leafp->cloneTreePure(false);
if (!condp) {
condp = clonep;
} else {
condp = new AstLogAnd{flp, condp, clonep};
condp->dtypeSetBit();
}
}
if (!resultp) {
if (offset > 0) {
AstDelay* const delayp = new AstDelay{
flp, new AstConst{flp, static_cast<uint32_t>(offset)}, /*isCycle=*/true};
resultp
= new AstSExpr{flp, new AstConst{flp, AstConst::BitTrue{}}, delayp, condp};
resultp->dtypeSetBit();
} else {
resultp = condp;
}
} else {
AstDelay* const delayp = new AstDelay{
flp, new AstConst{flp, static_cast<uint32_t>(offset - prevOffset)},
/*isCycle=*/true};
resultp = new AstSExpr{flp, resultp, delayp, condp};
resultp->dtypeSetBit();
}
prevOffset = offset;
}
return resultp;
}
// `seq` is a simple ranged sequence `start ##[m:n] end` (start/end boolean,
// start may be absent). Used to collapse a both-variable intersect to one
// ranged delay.
struct SimpleRanged final {
bool ok = false;
AstNodeExpr* startp = nullptr; // may be null (absent start)
AstNodeExpr* endp = nullptr;
};
static SimpleRanged asSimpleRanged(AstNodeExpr* nodep) {
AstSExpr* const sexprp = VN_CAST(nodep, SExpr);
if (!sexprp) return {};
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
if (!delayp || !delayp->isCycleDelay() || !delayp->isRangeDelay() || delayp->isUnbounded())
return {};
if (getConstInt(delayp->lhsp()) == getConstInt(delayp->rhsp())) return {};
AstNodeExpr* const prep = sexprp->preExprp();
if (prep && fixedLength(prep) != 0) return {};
if (fixedLength(sexprp->exprp()) != 0) return {};
return {true, prep, sexprp->exprp()};
}
// Build the NFA for a synthesized intersect lowering tree, then free it.
// buildExpr returns the terminal condition (finalCondp) by reference into the
// tree; detach a clone so the tree can be freed here. The graph already holds
// clones/hoists of every edge condition, so nothing else dangles.
BuildResult buildFromLoweringTree(AstNodeExpr* treep, SvaStateVertex* entryVtxp,
bool isTopLevelStep) {
BuildResult result = buildExpr(treep, entryVtxp, isTopLevelStep);
if (result.valid() && result.finalCondp) {
result.finalCondp = result.finalCondp->cloneTreePure(false);
}
VL_DO_DANGLING(treep->deleteTree(), treep);
return result;
}
// Empty common-length intersection -- unequal fixed lengths, or disjoint
// ranged lengths. IEEE 1800-2023 16.9.6 requires both operands to match
// over a window of the same length, so with no common length the intersect
// simply never matches. This is legal (matching nothing), not an error, so
// lower to a constant false rather than rejecting legal code.
BuildResult buildNeverMatchIntersect(AstNodeExpr* nodep, SvaStateVertex* entryVtxp,
bool isTopLevelStep) {
AstNodeExpr* const falsep = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
return buildFromLoweringTree(falsep, entryVtxp, isTopLevelStep);
}
// Lower `seq1 intersect seq2` when an operand's match length varies
// (IEEE 1800-2023 16.9.6: both match over one window, equal start and end).
// The common length range is [lo,hi] = intersection of the two operands'
// achievable lengths. The equal-length combiner is avoided -- it mis-handles
// operands with an internal boolean check -- by lowering to plain sequences:
// - lo == hi (one shared length, e.g. one fixed + one ranged operand):
// pin each operand to that length and conjoin them cycle-by-cycle into a
// single fixed sequence.
// - lo < hi with simple `bool ##[m:n] bool` operands: collapse to one
// ranged delay `(start1 & start2) ##[lo:hi] (end1 & end2)`. (An OR of
// per-length branches cannot reject correctly -- a single missed length
// would fail the whole intersect, cf. Lesson 48.)
// - otherwise unsupported (clean error, not the legacy fall-through crash).
BuildResult buildVarLenIntersect(AstSIntersect* nodep, SvaStateVertex* entryVtxp,
bool isTopLevelStep) {
const std::pair<int, int> lhsRange = lengthRange(nodep->lhsp());
const std::pair<int, int> rhsRange = lengthRange(nodep->rhsp());
if (lhsRange.first < 0 || rhsRange.first < 0) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: intersect with this variable-length operand");
return BuildResult::failWithError();
}
const int lo = std::max(lhsRange.first, rhsRange.first);
const int hi = std::min(lhsRange.second, rhsRange.second);
if (lo > hi) {
// Disjoint length ranges share no common length -> never matches.
return buildNeverMatchIntersect(nodep, entryVtxp, isTopLevelStep);
}
FileLine* const flp = nodep->fileline();
if (lo == hi) {
AstNodeExpr* const lp = realizeAtLength(nodep->lhsp(), lo, lhsRange.first);
AstNodeExpr* const rp = realizeAtLength(nodep->rhsp(), lo, rhsRange.first);
AstNodeExpr* const conjp = conjoinFixedSeqs(lp, rp, flp);
VL_DO_DANGLING(lp->deleteTree(), lp);
VL_DO_DANGLING(rp->deleteTree(), rp);
if (!conjp) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: intersect operand is not a plain boolean sequence");
return BuildResult::failWithError();
}
return buildFromLoweringTree(conjp, entryVtxp, isTopLevelStep);
}
const SimpleRanged sl = asSimpleRanged(nodep->lhsp());
const SimpleRanged sr = asSimpleRanged(nodep->rhsp());
if (!sl.ok || !sr.ok) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: intersect of two sequences that each vary in length over a"
" range with internal structure");
return BuildResult::failWithError();
}
const auto andBool = [&](AstNodeExpr* ap, AstNodeExpr* bp) -> AstNodeExpr* {
AstNodeExpr* const aClonep
= ap ? ap->cloneTreePure(false) : new AstConst{flp, AstConst::BitTrue{}};
AstNodeExpr* const bClonep
= bp ? bp->cloneTreePure(false) : new AstConst{flp, AstConst::BitTrue{}};
AstLogAnd* const andp = new AstLogAnd{flp, aClonep, bClonep};
andp->dtypeSetBit();
return andp;
};
AstDelay* const delayp = new AstDelay{flp, new AstConst{flp, static_cast<uint32_t>(lo)},
/*isCycle=*/true};
delayp->rhsp(new AstConst{flp, static_cast<uint32_t>(hi)});
AstSExpr* const reducedp
= new AstSExpr{flp, andBool(sl.startp, sr.startp), delayp, andBool(sl.endp, sr.endp)};
reducedp->dtypeSetBit();
return buildFromLoweringTree(reducedp, entryVtxp, isTopLevelStep);
}
BuildResult buildThroughout(AstSThroughout* nodep, SvaStateVertex* entryVtxp,
bool isTopLevelStep = false) {
// Mark entryVtxp so "cond false at tick 0" is detected as throughout-drop.
@ -1232,18 +1482,26 @@ public:
return buildAndCombiner(andp->lhsp(), andp->rhsp(), entryVtxp, andp->fileline());
}
if (AstSIntersect* const intp = VN_CAST(nodep, SIntersect)) {
// IEEE 1800-2023 16.9.6: SAnd with equal-length constraint.
// Variable-length intersect deferred to follow-up.
// IEEE 1800-2023 16.9.6: both operands match over one window with
// equal start and end (equal length). Lower to a single sequence
// that conjoins both operands' per-cycle checks -- correct under
// concurrent attempts, where the done-latch combiner conflates the
// two operands' start times and over-accepts. The combiner remains
// only as a fallback for operands that do not flatten.
const int lhsLen = fixedLength(intp->lhsp());
const int rhsLen = fixedLength(intp->rhsp());
if (lhsLen < 0 || rhsLen < 0) return BuildResult::fail();
if (lhsLen != rhsLen) {
intp->v3error("Intersect sequence length mismatch: left " + std::to_string(lhsLen)
+ " cycles, right " + std::to_string(rhsLen)
+ " cycles (IEEE 1800-2023 16.9.6)");
return BuildResult::failWithError();
if (lhsLen >= 0 && rhsLen >= 0) {
if (lhsLen != rhsLen) {
// Unequal fixed lengths share no common length -> never matches.
return buildNeverMatchIntersect(intp, entryVtxp, isTopLevelStep);
}
if (AstNodeExpr* const conjp
= conjoinFixedSeqs(intp->lhsp(), intp->rhsp(), intp->fileline())) {
return buildFromLoweringTree(conjp, entryVtxp, isTopLevelStep);
}
return buildAndCombiner(intp->lhsp(), intp->rhsp(), entryVtxp, intp->fileline());
}
return buildAndCombiner(intp->lhsp(), intp->rhsp(), entryVtxp, intp->fileline());
return buildVarLenIntersect(intp, entryVtxp, isTopLevelStep);
}
if (AstSWithin* const withinp = VN_CAST(nodep, SWithin)) {
return buildSWithin(withinp, entryVtxp, isTopLevelStep);
@ -1725,8 +1983,18 @@ class SvaNfaLowering final {
AstNodeExpr*& sigp = c.vtx[i]->datap()->stateSigp;
if (sigp) VL_DO_DANGLING(sigp->deleteTree(), sigp);
}
// Disable iff gating on throughout/required-step rejects (IEEE 16.12).
// Disable iff gating (IEEE 1800-2023 16.12). The edge counter misses a
// continuously-true disable, so gate on the current level value too.
if (c.disableExprp) {
// terminalActivep is always set, so gate it unconditionally.
AstNodeExpr* const notTermp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
sigs.terminalActivep = new AstLogAnd{c.flp, sigs.terminalActivep, notTermp};
if (sigs.rejectBasep) {
AstNodeExpr* const notDisp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
sigs.rejectBasep = new AstLogAnd{c.flp, sigs.rejectBasep, notDisp};
}
if (sigs.throughoutRejectp) {
AstNodeExpr* const notDisp
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};

View File

@ -93,7 +93,10 @@ private:
fromAlways = true;
}
if (!senip) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Unclocked assertion");
nodep->v3error("Concurrent assertion has no clock (IEEE 1800-2023 16.16)\n"
<< nodep->warnMore()
<< "... Suggest provide a clocking event, a default"
" clocking, or a clocked procedural context");
newp = new AstSenTree{nodep->fileline(), nullptr};
} else {
if (cassertp && fromAlways) cassertp->senFromAlways(true);

View File

@ -5737,6 +5737,22 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
};
class AstMostSetBitP1 final : public AstNodeUniop {
// Most-significant set bit plus one (bit-width); 0 if value is zero
public:
AstMostSetBitP1(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_MostSetBitP1(fl, lhsp) {
dtypeSetInteger2State();
}
ASTGEN_MEMBERS_AstMostSetBitP1;
void numberOperate(V3Number& out, const V3Number& lhs) override { out.opMostSetBitP1(lhs); }
string emitVerilog() override { return "%f$mostsetbitp1(%l)"; }
string emitC() override { return "VL_MOSTSETBITP1_%lq(%lW, %P, %li)"; }
bool cleanOut() const override { return true; }
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }
int instrCount() const override { return widthInstrs() * 16; }
};
class AstNToI final : public AstNodeUniop {
// String to any-size integral
public:

View File

@ -1000,8 +1000,8 @@ public:
// this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is
// false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true.
AstVarScope* findConst(AstConst* initp, bool mergeDType);
// Rebuild hashes after potential removals
void reCache();
// Rebuild hashes and missing variable scopes after potential removals
void rebuildVarScopesAndCache();
};
class AstConstraint final : public AstNode {
// Constraint
@ -1185,12 +1185,21 @@ public:
};
class AstCoverpointRef final : public AstNode {
// Reference to a coverpoint used in a cross
const string m_name; // coverpoint name
// @astgen op1 := exprp : Optional[AstNodeExpr] // Non-standard: hierarchical/dotted
// // reference (implicit coverpoint), e.g.
// // 'cross a.b'; nullptr for a plain coverpoint
// // name. An AstDot at parse time, resolved
// // by the time V3Covergroup runs.
const string m_name; // coverpoint name; empty when exprp() carries a hierarchical reference
public:
AstCoverpointRef(FileLine* fl, const string& name)
: ASTGEN_SUPER_CoverpointRef(fl)
, m_name{name} {}
AstCoverpointRef(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_CoverpointRef(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstCoverpointRef;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;

View File

@ -31,6 +31,7 @@
#include <iterator>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
// Routines for dumping dict fields (NOTE: due to leading ',' they can't be used for first field in
@ -1779,14 +1780,28 @@ AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) {
return varScopep;
}
void AstConstPool::reCache() {
void AstConstPool::rebuildVarScopesAndCache() {
m_tables.clear();
m_consts.clear();
std::unordered_map<const AstVar*, AstVarScope*> varScopeps;
for (AstVarScope* vscp = m_scopep->varsp(); vscp; vscp = VN_CAST(vscp->nextp(), VarScope)) {
AstNode* const valuep = vscp->varp()->valuep();
const V3Hash hash = V3Hasher::uncachedHash(valuep);
if (VN_IS(valuep, InitArray)) m_tables.emplace(hash.value(), vscp);
if (VN_IS(valuep, Const)) m_consts.emplace(hash.value(), vscp);
varScopeps.emplace(vscp->varp(), vscp);
}
for (AstNode* nodep = m_modp->stmtsp(); nodep; nodep = nodep->nextp()) {
AstVar* const varp = VN_CAST(nodep, Var);
if (!varp) continue;
AstNode* const valuep = varp->valuep();
if (!valuep) continue;
const bool isTable = VN_IS(valuep, InitArray);
const AstConst* const constp = VN_CAST(valuep, Const);
if (!isTable && !constp) continue;
AstVarScope*& vscp = varScopeps[varp];
if (!vscp) {
vscp = new AstVarScope{varp->fileline(), m_scopep, varp};
m_scopep->addVarsp(vscp);
}
if (isTable) m_tables.emplace(V3Hasher::uncachedHash(valuep).value(), vscp);
if (constp) m_consts.emplace(constp->num().toHash().value(), vscp);
}
}

View File

@ -65,6 +65,7 @@ class CUseVisitor final : public VNVisitorConst {
iterateConst(nodep->lhsp()->dtypep());
}
void visit(AstNodeDType* nodep) override {
if (nodep->user1SetOnce()) return; // Process once
if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep());
if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p());

View File

@ -375,6 +375,8 @@ class CoverageVisitor final : public VNVisitor {
} else {
itemp->addElsesp(stmtp);
}
} else if (AstBegin* const itemp = VN_CAST(nodep, Begin)) {
itemp->addStmtsp(stmtp);
} else {
nodep->v3fatalSrc("Bad node type");
}
@ -776,6 +778,8 @@ class CoverageVisitor final : public VNVisitor {
// covers the code in that line.)
VL_RESTORER(m_beginHier);
VL_RESTORER(m_inToggleOff);
VL_RESTORER(m_exprStmtsp);
m_exprStmtsp = nodep;
m_inToggleOff = true;
if (nodep->name() != "") {
m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name();
@ -820,6 +824,7 @@ class CoverageVisitor final : public VNVisitor {
if (pair.second) {
varp = new AstVar{fl, VVarType::MODULETEMP, m_exprTempNames.get(frefp),
dtypep};
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
pair.first->second = varp;
if (m_ftaskp) {
varp->funcLocal(true);

View File

@ -98,7 +98,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
for (AstCoverCross* crossp : m_coverCrosses) {
for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) {
if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef))
m_crossedCpNames.insert(refp->name());
if (!refp->exprp()) m_crossedCpNames.insert(refp->name());
}
}
@ -1295,6 +1295,18 @@ class FunctionalCoverageVisitor final : public VNVisitor {
while (itemp) {
AstNode* const nextp = itemp->nextp();
AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef);
if (refp->exprp()) {
// Non-standard hierarchical/dotted cross item (e.g. 'cross a.b'): an implicit
// coverpoint over the referenced expression (carried in refp->exprp()). The
// grammar already warned NONSTD; implicit coverpoints are not yet implemented, so
// generate no sampling code for this cross. When support is added the implicit
// coverpoint should be synthesized upstream (V3LinkParse) as a real AstCoverpoint
// so it flows through the normal coverpoint path - by here coverpoint lowering has
// already run.
refp->v3warn(COVERIGN,
"Unsupported: cross of hierarchical reference (implicit coverpoint)");
return;
}
// Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan)
const auto it = m_coverpointMap.find(refp->name());
AstCoverpoint* const foundCpp = (it != m_coverpointMap.end()) ? it->second : nullptr;

View File

@ -597,7 +597,7 @@ public:
// We may have removed some datatypes, cleanup
nodep->typeTablep()->repairCache();
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
nodep->constPoolp()->reCache();
nodep->constPoolp()->rebuildVarScopesAndCache();
}
~DeadVisitor() override {
V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified);

View File

@ -620,6 +620,12 @@ DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataT
dfg.addVertex(*this);
}
bool DfgVertex::unsafe() const {
if (is<DfgMux>()) return true;
if (is<DfgArraySel>()) return !as<DfgArraySel>()->bitp()->is<DfgConst>();
return false;
}
void DfgVertex::typeCheck(const DfgGraph& dfg) const {
#define CHECK(cond, msg) \

View File

@ -195,6 +195,8 @@ public:
UASSERT_OBJ(m_dtype.isPacked(), this, "Non packed vertex has no 'width'");
return m_dtype.size();
}
// Has terminating side-effect
bool unsafe() const;
// Type check vertex (for debugging)
void typeCheck(const DfgGraph& dfg) const;

View File

@ -2269,7 +2269,7 @@ class V3DfgPeephole final : public DfgVisitor {
}
void visit(DfgLogAnd* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
DfgVertex* const lhsp = vtxp->lhsp();
DfgVertex* const rhsp = vtxp->rhsp();
@ -2287,11 +2287,11 @@ class V3DfgPeephole final : public DfgVisitor {
}
void visit(DfgLogIf* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
}
void visit(DfgLogOr* const vtxp) override {
if (binary(vtxp)) return;
if (binary(vtxp) || vtxp->rhsp()->unsafe()) return;
DfgVertex* const lhsp = vtxp->lhsp();
DfgVertex* const rhsp = vtxp->rhsp();
@ -2890,13 +2890,13 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (vtxp->dtype() == m_bitDType) {
if (isSame(condp, thenp)) { // a ? a : b becomes a | b
if (isSame(condp, thenp) && !elsep->unsafe()) { // a ? a : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_COND) {
replace(make<DfgOr>(vtxp, condp, elsep));
return;
}
}
if (isSame(condp, elsep)) { // a ? b : a becomes a & b
if (isSame(condp, elsep) && !thenp->unsafe()) { // a ? b : a becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_COND) {
replace(make<DfgAnd>(vtxp, condp, thenp));
return;
@ -2905,28 +2905,28 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (vtxp->width() <= VL_QUADSIZE) {
if (isZero(thenp)) { // a ? 0 : b becomes ~a & b
if (isZero(thenp) && !elsep->unsafe()) { // a ? 0 : b becomes ~a & b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) {
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgAnd>(vtxp, maskp, elsep));
return;
}
}
if (isOnes(thenp)) { // a ? 1 : b becomes a | b
if (isOnes(thenp) && !elsep->unsafe()) { // a ? 1 : b becomes a | b
APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) {
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgOr>(vtxp, maskp, elsep));
return;
}
}
if (isZero(elsep)) { // a ? b : 0 becomes a & b
if (isZero(elsep) && !thenp->unsafe()) { // a ? b : 0 becomes a & b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) {
DfgVertex* const maskp = replicate(vtxp, condp);
replace(make<DfgAnd>(vtxp, maskp, thenp));
return;
}
}
if (isOnes(elsep)) { // a ? b : 1 becomes ~a | b
if (isOnes(elsep) && !thenp->unsafe()) { // a ? b : 1 becomes ~a | b
APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) {
DfgVertex* const maskp = replicate(vtxp, make<DfgNot>(condp, condp));
replace(make<DfgOr>(vtxp, maskp, thenp));
@ -2935,7 +2935,8 @@ class V3DfgPeephole final : public DfgVisitor {
}
if (DfgOr* const tOrp = thenp->cast<DfgOr>()) {
if (isSame(tOrp->lhsp(), elsep)) { // a ? b | c : b becomes b | (a & c)
if (isSame(tOrp->lhsp(), elsep)
&& !tOrp->rhsp()->unsafe()) { // a ? b | c : b becomes b | (a & c)
APPLYING(REPLACE_COND_THEN_OR_LHS) {
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->rhsp());
@ -2943,7 +2944,8 @@ class V3DfgPeephole final : public DfgVisitor {
return;
}
}
if (isSame(tOrp->rhsp(), elsep)) { // a ? b | c : c becomes c | (a & b)
if (isSame(tOrp->rhsp(), elsep)
&& !tOrp->lhsp()->unsafe()) { // a ? b | c : c becomes c | (a & b)
APPLYING(REPLACE_COND_THEN_OR_RHS) {
DfgVertex* const maskp = replicate(vtxp, condp);
DfgAnd* const andp = make<DfgAnd>(vtxp, maskp, tOrp->lhsp());

View File

@ -683,7 +683,8 @@ class EmitCHeader final : public EmitCConstInit {
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
if (v3Global.opt.coverage() || v3Global.useCovergroup())
puts("#include \"verilated_covergroup.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");

View File

@ -104,6 +104,8 @@ private:
puts("\n");
if (v3Global.opt.vpi()) {
// VPI shared libraries requested via +verilator+vpi+<lib> are loaded by
// contextp->commandArgs() above, before the statically-linked startup routines.
puts("// Hook VPI startup routines and invoke callback\n");
puts("if (vlog_startup_routines) {\n");
puts(/**/ "for (auto routinep = &vlog_startup_routines[0]; *routinep; routinep++)"

View File

@ -70,7 +70,8 @@ class EmitCModel final : public EmitCFunc {
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n");
if (v3Global.opt.coverage() || v3Global.useCovergroup())
puts("#include \"verilated_covergroup.h\"\n");
if (v3Global.dpi()) puts("#include \"svdpi.h\"\n");
// Declare foreign instances up front to make C++ happy

View File

@ -567,6 +567,12 @@ public:
of.puts("VM_TRACE_VCD = ");
of.puts(v3Global.opt.traceEnabledVcd() ? "1" : "0");
of.puts("\n");
of.puts("# VPI enabled? 0/1 (from --vpi)\n");
of.puts("VM_VPI = ");
of.puts(v3Global.opt.vpi() ? "1" : "0");
of.puts("\n");
// Link flags for runtime VPI library loading are emitted by emitOverallMake() after
// verilated.mk is included (so $(CFG_LDFLAGS_DYNAMIC)/$(CFG_LDLIBS_DYNAMIC) are defined).
of.puts("\n### Object file lists...\n");
for (int support = 0; support < 3; ++support) {
@ -729,6 +735,17 @@ public:
of.puts("\n### Executable rules... (from --exe)\n");
of.puts("VPATH += $(VM_USER_DIR)\n");
of.puts("\n");
if (v3Global.opt.vpi()) {
// Runtime VPI library loading (+verilator+vpi+<lib>) needs the executable to
// export its VPI symbols so the dlopen'd library can resolve them, plus the
// dl library for dlopen/dlsym. The exact flags are probed at configure time
// (CFG_LDFLAGS_DYNAMIC / CFG_LDLIBS_DYNAMIC in verilated.mk).
of.puts("# Runtime VPI library loading (+verilator+vpi+) link requirements\n");
of.puts("LDFLAGS += $(CFG_LDFLAGS_DYNAMIC)\n");
of.puts("LDLIBS += $(CFG_LDLIBS_DYNAMIC)\n");
of.puts("\n");
}
}
const string compilerIncludePch

View File

@ -245,7 +245,8 @@ std::vector<std::string> V3Global::verilatedCppFiles() {
if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp");
if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp");
if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp");
if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp");
if (v3Global.opt.coverage() || v3Global.useCovergroup())
result.emplace_back("verilated_covergroup.cpp");
for (const string& base : v3Global.opt.traceSourceBases())
result.emplace_back(base + "_c.cpp");
if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp");

View File

@ -29,6 +29,7 @@
#include "V3Inline.h"
#include "V3AstUserAllocator.h"
#include "V3Graph.h"
#include "V3Inst.h"
#include "V3Stats.h"
@ -41,208 +42,334 @@ VL_DEFINE_DEBUG_FUNCTIONS;
static const int INLINE_MODS_SMALLER = 100; // If a mod is < this # nodes, can always inline it
//######################################################################
// Inlining state. Kept as AstNodeModule::user1p via AstUser1Allocator
// Bipartite module instantiation graph containing module and cell vertices
namespace {
class InlineModModuleVertex;
class InlineModCellVertex;
struct ModuleState final {
bool m_inlined = false; // Whether to inline this module
unsigned m_cellRefs = 0; // Number of AstCells instantiating this module
std::vector<AstCell*> m_childCells; // AstCells under this module (to speed up traversal)
};
using ModuleStateUser1Allocator = AstUser1Allocator<AstNodeModule, ModuleState>;
} // namespace
//######################################################################
// Visitor that determines which modules will be inlined
class InlineMarkVisitor final : public VNVisitor {
class InlineModGraph final : public V3Graph {
// NODE STATE
// Output
// AstNodeModule::user1() // OUTPUT: ModuleState instance (via m_moduleState)
// Internal state (can be cleared after this visit completes)
// AstNodeModule::user2() // CIL_*. Allowed to automatically inline module
// AstNodeModule::user4() // int. Statements in module
const VNUser2InUse m_inuser2;
const VNUser4InUse m_inuser4;
// AstNodeModule::user4p() -> InlineModModuleVertex*, the module vertex
// AstCell::user4p() -> InlineModCellVertex*, the cell vertex
ModuleStateUser1Allocator& m_moduleState;
VNUser4InUse m_user4InUse;
// For the user2 field:
enum : uint8_t {
CIL_NOTHARD = 0, // Inline not supported
CIL_NOTSOFT, // Don't inline unless user overrides
CIL_MAYBE, // Might inline
CIL_USER
}; // Pragma suggests inlining
// STATE
AstNodeModule* m_modp = nullptr; // Current module
VDouble0 m_statUnsup; // Statistic tracking
std::vector<AstNodeModule*> m_allMods; // All modules, in top-down order.
// Within the context of a given module, LocalInstanceMap maps
// from child modules to the count of each child's local instantiations.
using LocalInstanceMap = std::unordered_map<AstNodeModule*, unsigned>;
// We keep a LocalInstanceMap for each module in the design
std::unordered_map<AstNodeModule*, LocalInstanceMap> m_instances;
public:
InlineModGraph()
: V3Graph{} {}
~InlineModGraph() override = default;
// METHODS
void cantInline(const char* reason, bool hard) {
if (hard) {
if (m_modp->user2() != CIL_NOTHARD) {
UINFO(4, " No inline hard: " << reason << " " << m_modp);
m_modp->user2(CIL_NOTHARD);
++m_statUnsup;
}
} else {
if (m_modp->user2() == CIL_MAYBE) {
UINFO(4, " No inline soft: " << reason << " " << m_modp);
m_modp->user2(CIL_NOTSOFT);
InlineModModuleVertex* getInlineModModuleVertexp(AstNodeModule* modp);
InlineModCellVertex* getInlineModCellVertexp(AstCell* cellp);
void addEdge(InlineModModuleVertex& from, InlineModCellVertex& to);
void addEdge(InlineModCellVertex& from, InlineModModuleVertex& to);
// debug
std::string dotRankDir() const override { return "LR"; }
};
class InlineModEitherVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(InlineModEitherVertex, V3GraphVertex)
protected:
explicit InlineModEitherVertex(InlineModGraph& graph)
: V3GraphVertex{&graph} {}
};
class InlineModModuleVertex final : public InlineModEitherVertex {
VL_RTTI_IMPL(InlineModModuleVertex, InlineModEitherVertex)
AstNodeModule* const m_modp; // The module
const char* m_noInlineHardWyp = nullptr; // First reason the module can never be inlined
const char* m_noInlineSoftWyp = nullptr; // First reason not to inline unless forced
const char* m_shouldInlineWhyp = nullptr; // First reason why this module should be inlined
size_t m_size = 0; // The size (statement count) of the module
size_t mutable m_flattenedSize = 0; // The size of the module if flattened
bool mutable m_flattenedSizeValid = false; // Whether the flattened size is valid
size_t mutable m_instanceCount = 0; // The number of total instances of this module
bool mutable m_instanceCountValid = false; // Whether the instance count is valid
public:
InlineModModuleVertex(InlineModGraph& graph, AstNodeModule* modp)
: InlineModEitherVertex{graph}
, m_modp{modp} {}
~InlineModModuleVertex() override = default;
// ACCESSORS
AstNodeModule* modp() const { return m_modp; }
size_t size() const { return m_size; }
void size(size_t value) { m_size = value; }
void sizeInc(size_t value = 1) { m_size += value; }
bool noInlineHard() const { return m_noInlineHardWyp; }
void setNoInlineHard(const char* whyp) {
if (!m_noInlineHardWyp) m_noInlineHardWyp = whyp;
}
bool noInlineSoft() const { return m_noInlineSoftWyp; }
void setNoInlineSoft(const char* whyp) {
if (!m_noInlineSoftWyp) m_noInlineSoftWyp = whyp;
}
bool shouldInline() const { return m_shouldInlineWhyp; }
void setShouldInline(const char* whyp) {
if (!m_shouldInlineWhyp) m_shouldInlineWhyp = whyp;
}
// Mark every instance below this module for inlining
void setFlatten();
// Total size of module, with all hierarchy below flattened
size_t flattenedSize() const {
if (!m_flattenedSizeValid) {
m_flattenedSizeValid = true;
m_flattenedSize = m_size;
for (const V3GraphEdge& e1 : outEdges()) {
for (const V3GraphEdge& e2 : e1.top()->outEdges()) {
InlineModModuleVertex* const mVtxp = e2.top()->as<InlineModModuleVertex>();
m_flattenedSize += mVtxp->flattenedSize();
}
}
}
return m_flattenedSize;
}
// Total number of instances of this module in the whole hierarchy of the design
size_t instanceCount() const {
if (!m_instanceCountValid) {
m_instanceCountValid = true;
m_instanceCount = 0;
for (const V3GraphEdge& e1 : inEdges()) {
for (const V3GraphEdge& e2 : e1.fromp()->inEdges()) {
InlineModModuleVertex* const mVtxp = e2.fromp()->as<InlineModModuleVertex>();
m_instanceCount += mVtxp->instanceCount();
}
}
if (!m_instanceCount) {
UASSERT_OBJ(m_modp->isTop(), m_modp, "non-top level module should have instances");
m_instanceCount = 1;
}
}
return m_instanceCount;
}
// debug
FileLine* fileline() const override { return m_modp->fileline(); }
std::string dotShape() const override { return "box"; }
std::string dotColor() const override {
return m_noInlineHardWyp ? "red"
: m_shouldInlineWhyp ? "blue"
: m_noInlineSoftWyp ? "orange"
: "black";
}
std::string name() const override VL_MT_STABLE {
std::string str = m_modp->typeName() + " "s + cvtToHex(m_modp);
str += "\n" + m_modp->name() + " @ " + fileline()->ascii();
str += "\ninstanceCount: " + std::to_string(instanceCount());
str += "\nsize: " + std::to_string(m_size);
str += "\nflattenedSize: " + std::to_string(flattenedSize());
if (m_shouldInlineWhyp) str += "\nShouldInline: "s + m_shouldInlineWhyp;
if (m_noInlineHardWyp) str += "\nNoInlineHard: "s + m_noInlineHardWyp;
if (m_noInlineSoftWyp) str += "\nNoInlineSoft: "s + m_noInlineSoftWyp;
str += "\n";
return str;
}
};
class InlineModCellVertex final : public InlineModEitherVertex {
VL_RTTI_IMPL(InlineModCellVertex, InlineModEitherVertex)
AstCell* const m_cellp; // The cell (instance)
const char* m_doInlineWyp = nullptr; // First reason this instance should be inlined
bool m_flatten = false; // Whether this cell and below already flattened (avoid O(n^2))
public:
InlineModCellVertex(InlineModGraph& graph, AstCell* cellp)
: InlineModEitherVertex{graph}
, m_cellp{cellp} {}
~InlineModCellVertex() override = default;
// ACCESSORS
AstCell* cellp() const { return m_cellp; }
bool doInline() const { return m_doInlineWyp; }
void setDoInline(const char* whyp) {
if (!m_doInlineWyp) m_doInlineWyp = whyp;
}
bool flatten() const { return m_flatten; }
void setFlatten() { m_flatten = true; }
// The module vertx this cell is instantiating
InlineModModuleVertex& instanceOf() const {
UASSERT_OBJ(outSize1(), this, "Cell should have exactly one outgoing edge");
return *outEdges().frontp()->top()->as<InlineModModuleVertex>();
}
// The module vertex this cell is instantiated in
InlineModModuleVertex& instanceIn() const {
UASSERT_OBJ(inSize1(), this, "Cell should have exactly one incoming edge");
return *inEdges().frontp()->fromp()->as<InlineModModuleVertex>();
}
// debug
FileLine* fileline() const override { return m_cellp->fileline(); }
std::string dotColor() const override { return m_doInlineWyp ? "green" : "black"; }
std::string dotShape() const override { return "ellipse"; }
std::string name() const override VL_MT_STABLE {
std::string str = m_cellp->typeName() + " "s + cvtToHex(m_cellp);
str += "\n" + m_cellp->name() + " @ " + fileline()->ascii();
if (m_doInlineWyp) str += "\nDoInline: "s + m_doInlineWyp;
str += "\n";
return str;
}
};
InlineModModuleVertex* InlineModGraph::getInlineModModuleVertexp(AstNodeModule* modp) {
if (!modp->user4p()) modp->user4p(new InlineModModuleVertex{*this, modp});
return modp->user4u().to<InlineModModuleVertex*>();
}
InlineModCellVertex* InlineModGraph::getInlineModCellVertexp(AstCell* cellp) {
if (!cellp->user4p()) cellp->user4p(new InlineModCellVertex{*this, cellp});
return cellp->user4u().to<InlineModCellVertex*>();
}
void InlineModGraph::addEdge(InlineModModuleVertex& parent, InlineModCellVertex& cell) {
UASSERT_OBJ(cell.inEmpty(), &cell, "Cell should have at most one incoming edge");
new V3GraphEdge{this, &parent, &cell, 1, /* cutable: */ false};
}
void InlineModGraph::addEdge(InlineModCellVertex& cell, InlineModModuleVertex& submodule) {
UASSERT_OBJ(cell.outEmpty(), &cell, "Cell should have at most one outgoing edge");
new V3GraphEdge{this, &cell, &submodule, 1, /* cutable: */ false};
}
void InlineModModuleVertex::setFlatten() {
for (V3GraphEdge& edge : outEdges()) {
InlineModCellVertex& cVtx = *edge.top()->as<InlineModCellVertex>();
if (cVtx.flatten()) continue;
cVtx.setFlatten();
InlineModModuleVertex& iVtx = cVtx.instanceOf();
if (!iVtx.noInlineHard() && !iVtx.noInlineSoft()) cVtx.setDoInline("flatten parent");
iVtx.setFlatten();
}
}
//######################################################################
// Visitor that builds the bipartite module instantiation graph
class InlineModGraphBuilder final : public VNVisitor {
// STATE
std::unique_ptr<InlineModGraph> m_graphp{new InlineModGraph}; // The graph being built
InlineModModuleVertex* m_modVtxp = nullptr; // Vertex of module currently being iterated
// VISITORS
void visit(AstNodeModule* nodep) override {
UASSERT_OBJ(!m_modp, nodep, "Unsupported: Nested modules");
VL_RESTORER(m_modp);
m_modp = nodep;
m_allMods.push_back(nodep);
m_modp->user2(CIL_MAYBE);
m_modp->user4(0); // statement count
if (VN_IS(m_modp, Iface)) {
// Inlining an interface means we no longer have a cell handle to resolve to.
// If inlining moves post-scope this can perhaps be relaxed.
cantInline("modIface", true);
}
if (m_modp->modPublic() && (m_modp->isTop() || !v3Global.opt.flatten())) {
cantInline("modPublic", false);
}
// If the instance is a --lib-create library stub instance, and need tracing,
// then don't inline as we need to know its a lib stub for sepecial handling
// in V3TraceDecl. See #7001.
if (m_modp->verilatorLib() && v3Global.opt.trace()) {
cantInline("verilatorLib with --trace", false);
if (nodep == v3Global.rootp()->constPoolp()->modp()) return; // Ignore const pool module
UASSERT_OBJ(!m_modVtxp, nodep, "Unsupported: Nested modules");
// Create the module vertex
InlineModModuleVertex* const vtxp = m_graphp->getInlineModModuleVertexp(nodep);
// Check if the module itself is not inlineable
// Inlining an interface means we no longer have a cell handle to resolve to.
// If inlining moves post-scope this can perhaps be relaxed.
if (VN_IS(nodep, Iface)) vtxp->setNoInlineHard("Interface");
// Never inline packages - TODO: conceptually fine, but why not?
if (VN_IS(nodep, Package)) vtxp->setNoInlineHard("Package");
// A --lib-create library stub instance that needs tracing must not be
// inlined, so we still know it is a lib stub in V3TraceDecl (see #7001)
if (nodep->verilatorLib() && v3Global.opt.trace()) {
vtxp->setNoInlineHard("verilatorLib with --trace");
}
iterateChildren(nodep);
// Don't inline public modules by default
if (nodep->modPublic()) vtxp->setNoInlineSoft("Public module");
// Iterate children
VL_RESTORER(m_modVtxp);
m_modVtxp = vtxp;
iterateChildrenConst(nodep);
}
void visit(AstClass* nodep) override {
// TODO allow inlining of modules that have classes
// (Probably wait for new inliner scheme)
cantInline("class", true);
iterateChildren(nodep);
// TODO allow inlining of modules that contain classes
if (m_modVtxp) m_modVtxp->setNoInlineHard("Contains class");
iterateChildrenConst(nodep); // TODO: this is only needed or FTaskRef cleanup
}
// Cells instantiate modules
void visit(AstCell* nodep) override {
m_moduleState(nodep->modp()).m_cellRefs++;
m_moduleState(m_modp).m_childCells.push_back(nodep);
m_instances[m_modp][nodep->modp()]++;
iterateChildren(nodep);
UASSERT_OBJ(m_modVtxp, nodep, "Cell should be under a module");
// Create the cell vertex
InlineModCellVertex* const vtxp = m_graphp->getInlineModCellVertexp(nodep);
// Add containing-module/instantiated-module edges
m_graphp->addEdge(*m_modVtxp, *vtxp);
m_graphp->addEdge(*vtxp, *m_graphp->getInlineModModuleVertexp(nodep->modp()));
// Iterate children
iterateChildrenConst(nodep);
}
void visit(AstPragma* nodep) override {
if (nodep->pragType() == VPragmaType::INLINE_MODULE) {
if (!m_modp) {
if (!m_modVtxp) {
nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE
} else if (m_modp->user2() == CIL_MAYBE || m_modp->user2() == CIL_NOTSOFT) {
m_modp->user2(CIL_USER);
} else {
m_modVtxp->setShouldInline("Pragma INLINE_MODULE");
}
// Remove so it does not propagate to upper cell
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) {
if (!m_modp) {
nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE
} else if (!v3Global.opt.flatten()) {
cantInline("Pragma NO_INLINE_MODULE", false);
}
// Remove so it does not propagate to upper cell
// Remove so don't propagate to upper cell...
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) {
if (!m_modVtxp) {
nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE
} else {
m_modVtxp->setNoInlineSoft("Pragma NO_INLINE_MODULE");
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
iterateChildrenConst(nodep);
}
void visit(AstVarXRef* nodep) override {
// Keep varp - V3Const::constifyEdit is called during pinReconnectSimple
// which needs varp to be set. V3LinkDot will re-resolve after inlining.
}
// TODO: Bit nasty to do this here, but historically present, and still necessary
void visit(AstNodeFTaskRef* nodep) override {
if (m_modVtxp) m_modVtxp->sizeInc();
// Remove link. V3LinkDot will reestablish it after inlining.
// MethodCalls not currently supported by inliner, so keep linked
if (!nodep->classOrPackagep() && !VN_IS(nodep, MethodCall)) {
nodep->taskp(nullptr);
VIsCached::clearCacheTree();
}
iterateChildren(nodep);
iterateChildrenConst(nodep);
}
void visit(AstAlways* nodep) override {
if (nodep->keyword() != VAlwaysKwd::CONT_ASSIGN) nodep->user4Inc(); // statement count
iterateChildren(nodep);
}
void visit(AstNodeAssign* nodep) override {
// Don't count assignments, as they'll likely flatten out
// Still need to iterate though to nullify VarXRefs
const int oldcnt = m_modp->user4();
iterateChildren(nodep);
m_modp->user4(oldcnt);
}
void visit(AstNetlist* nodep) override {
// Build ModuleState, user2, and user4 for all modules.
// Also build m_allMods and m_instances.
iterateChildren(nodep);
// Iterate through all modules in bottom-up order.
// Make a final inlining decision for each.
for (AstNodeModule* const modp : vlstd::reverse_view(m_allMods)) {
// If we're going to inline some modules into this one,
// update user4 (statement count) to reflect that:
int statements = modp->user4();
for (const auto& pair : m_instances[modp]) {
const AstNodeModule* const childp = pair.first;
if (m_moduleState(childp).m_inlined) { // inlining child
statements += childp->user4() * pair.second;
}
}
modp->user4(statements);
const int allowed = modp->user2();
const int refs = m_moduleState(modp).m_cellRefs;
// Should we automatically inline this module?
// If --flatten is specified, then force everything to be inlined that can be.
// inlineMult = 2000 by default.
// If a mod*#refs is < this # nodes, can inline it
// Packages aren't really "under" anything so they confuse this algorithm
const bool doit = !VN_IS(modp, Package) //
&& allowed != CIL_NOTHARD //
&& allowed != CIL_NOTSOFT //
&& (allowed == CIL_USER //
|| v3Global.opt.flatten() //
|| refs == 1 //
|| statements < INLINE_MODS_SMALLER //
|| v3Global.opt.inlineMult() < 1 //
|| refs * statements < v3Global.opt.inlineMult());
m_moduleState(modp).m_inlined = doit;
UINFO(4, " Inline=" << doit << " Possible=" << allowed << " Refs=" << refs
<< " Stmts=" << statements << " " << modp);
}
}
//--------------------
// Base node
void visit(AstNode* nodep) override {
if (m_modp) m_modp->user4Inc(); // Inc statement count
iterateChildren(nodep);
if (m_modVtxp) m_modVtxp->sizeInc();
iterateChildrenConst(nodep);
}
// CONSTRUCTORS
explicit InlineModGraphBuilder(AstNetlist* nodep) {
// Build the module instantiation graph
iterateConst(nodep);
// Order vertices (any topological order is fine), can't be cyclic at this point
m_graphp->order();
// Check that the first vertex is the top level module (everything, including packages,
// have a corresponding AstCell under the top level at this point).
UASSERT_OBJ(m_graphp->vertices().frontp()->as<InlineModModuleVertex>()->modp()->isTop(),
nodep, "First vertex should be top level module");
#ifdef VL_DEBUG
for (const V3GraphVertex& vtx : m_graphp->vertices()) {
// First vertex is the top levelmodule, we checked above
if (&vtx == m_graphp->vertices().frontp()) continue;
// Otherwise it should have instantiations
UASSERT_OBJ(!vtx.inEmpty(), &vtx, "Should have edges from root");
}
#endif
}
~InlineModGraphBuilder() override = default;
public:
// CONSTRUCTORS
explicit InlineMarkVisitor(AstNode* nodep, ModuleStateUser1Allocator& moduleState)
: m_moduleState{moduleState} {
iterate(nodep);
}
~InlineMarkVisitor() override {
V3Stats::addStat("Optimizations, Inline unsupported", m_statUnsup);
static std::unique_ptr<InlineModGraph> apply(AstNetlist* nodep) {
return std::move(InlineModGraphBuilder{nodep}.m_graphp);
}
};
@ -264,8 +391,12 @@ class InlineRelinkVisitor final : public VNVisitor {
m_pinSubstitutedXRefs; // VarXRefs created by pin substitution in this relink pass.
// Their dotted/inlinedDots already represent the parent's scope
// and must not be rewritten on the immediate visit.
InlineModGraph& m_graph; // The instance graph
AstNodeModule* const m_modp; // The module we are inlining into
// The vertex of the module we are inlining into, for updating the graph
InlineModModuleVertex* const m_mVtxp = m_graph.getInlineModModuleVertexp(m_modp);
const AstCell* const m_cellp; // The cell being inlined
size_t m_nPlaceholders = 0; // Unique identifier sequence number for placeholder variables
// VISITORS
@ -282,6 +413,11 @@ class InlineRelinkVisitor final : public VNVisitor {
void visit(AstCell* nodep) override {
// Cell under the inline cell, need to rename to avoid conflicts
nodep->name(m_cellp->name() + "__DOT__" + nodep->name());
// Need to update graph
nodep->user4p(nullptr); // clone copied user4p, reset to make new vertex
InlineModCellVertex* const vtxp = m_graph.getInlineModCellVertexp(nodep);
m_graph.addEdge(*m_mVtxp, *vtxp);
m_graph.addEdge(*vtxp, *m_graph.getInlineModModuleVertexp(nodep->modp()));
iterateChildren(nodep);
}
void visit(AstClass* nodep) override {
@ -464,8 +600,10 @@ class InlineRelinkVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
InlineRelinkVisitor(AstNodeModule* cloneModp, AstNodeModule* oldModp, AstCell* cellp)
: m_modp{oldModp}
InlineRelinkVisitor(AstNodeModule* cloneModp, AstNodeModule* oldModp, AstCell* cellp,
InlineModGraph& graph)
: m_graph{graph}
, m_modp{oldModp}
, m_cellp{cellp} {
// CellInlines added by V3Begin for generate/named blocks have origModName
// "__BEGIN__"; only those added by prior V3Inline passes carry a real module
@ -607,7 +745,7 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
}
// Inline 'cellp' into 'modp'. 'last' indicatest this is tha last instance of the inlined module
void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) {
void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last, InlineModGraph& graph) {
UINFO(5, " Inline Cell " << cellp);
UINFO(5, " into Module " << modp);
@ -662,8 +800,8 @@ void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) {
connectPort(modp, newModVarp, pinExprp);
}
// Cleanup var names, etc, to not conflict, relink replaced variables
{ InlineRelinkVisitor{inlinedp, modp, cellp}; }
// Cleanup var names, etc, to not conflict, relink replaced variables, adjust graph
{ InlineRelinkVisitor{inlinedp, modp, cellp, graph}; }
// Move statements from the inlined module into the module we are inlining into
if (AstNode* const stmtsp = inlinedp->stmtsp()) {
modp->addStmtsp(stmtsp->unlinkFrBackWithNext());
@ -675,39 +813,60 @@ void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last) {
}
// Apply all inlining decisions
void process(AstNetlist* netlistp, ModuleStateUser1Allocator& moduleStates) {
void process(AstNetlist* netlistp, InlineModGraph& graph) {
// NODE STATE
// Input:
// AstNodeModule::user1p() // ModuleState instance (via moduleState)
//
// Cleared entire netlist
// AstIfaceRefDType::user1() // Whether the cell pointed to by this
// // AstIfaceRefDType has been inlined
// AstCell::user3p() // AstCell*, the clone
// AstVar::user3p() // AstVar*, the clone clone
// AstVar::user3p() // AstVar*, the clone
// Cleared each cell
// AstVar::user2p() // AstVarRef*/AstConst* This port is connected to (AstPin::expr())
const VNUser3InUse m_user3InUse;
const VNUser1InUse user1InUse;
const VNUser3InUse user3InUse;
// Number of inlined instances, for statistics
VDouble0 m_nInlined;
// We want to inline bottom up. The modules under the netlist are in
// dependency order (top first, leaves last), so find the end of the list.
AstNode* nodep = netlistp->modulesp();
while (nodep->nextp()) nodep = nodep->nextp();
// Gather all cells that need to be inlined (this is in topological order)
std::vector<InlineModCellVertex*> cVtxps;
for (V3GraphVertex& vtx : graph.vertices()) {
InlineModCellVertex* const cVtxp = vtx.cast<InlineModCellVertex>();
if (!cVtxp) continue;
if (!cVtxp->doInline()) continue;
cVtxps.push_back(cVtxp);
}
// Iterate module list backwards (stop when we get back to the Netlist)
while (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) {
nodep = nodep->backp();
// Inline cells bottom up (leaves into roots)
for (InlineModCellVertex* const cVtxp : vlstd::reverse_view(cVtxps)) {
// Pick up parts before deleting
InlineModModuleVertex& mVtx = cVtxp->instanceIn();
InlineModModuleVertex* const iVtxp = &cVtxp->instanceOf();
AstCell* const cellp = cVtxp->cellp();
const bool last = iVtxp->inSize1();
UASSERT_OBJ(!iVtxp->noInlineHard(), cellp, "Should not be inlining if not possible");
// Consider each cell inside the current module for inlining
for (AstCell* const cellp : moduleStates(modp).m_childCells) {
ModuleState& childState = moduleStates(cellp->modp());
if (!childState.m_inlined) continue;
++m_nInlined;
inlineCell(modp, cellp, --childState.m_cellRefs == 0);
// Update
++m_nInlined;
mVtx.sizeInc(iVtxp->size()); // For debug dump only
// Delete the cell we are inlining
VL_DO_DANGLING(cVtxp->unlinkDelete(&graph), cVtxp);
// Delete the module we are inlining if this is the last instance
if (last) {
while (!iVtxp->outEmpty()) {
InlineModCellVertex* const tVtxp
= iVtxp->outEdges().frontp()->top()->as<InlineModCellVertex>();
// Bottom up ordering ensures this
UASSERT_OBJ(!tVtxp->doInline(), tVtxp, "Should have been inlined");
VL_DO_DANGLING(tVtxp->unlinkDelete(&graph), tVtxp);
}
VL_DO_DANGLING(iVtxp->unlinkDelete(&graph), iVtxp);
}
// Do it
inlineCell(mVtx.modp(), cellp, last, graph);
if (dumpGraphLevel() >= 9) graph.dumpDotFilePrefixed("inlinemod-cell");
}
V3Stats::addStat("Optimizations, Inlined instances", m_nInlined);
@ -729,25 +888,65 @@ void process(AstNetlist* netlistp, ModuleStateUser1Allocator& moduleStates) {
void V3Inline::inlineAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{
const VNUser1InUse m_inuser1; // output of InlineMarkVisitor, input to InlineVisitor.
ModuleStateUser1Allocator moduleState; // AstUser1Allocator
// Build the bipartite module instantiation graph
std::unique_ptr<InlineModGraph> graphp = InlineModGraphBuilder::apply(nodep);
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-graph");
// Scoped to clean up temp userN's
{ InlineMarkVisitor{nodep, moduleState}; }
// Inline the modles we decided to inline
ModuleInliner::process(nodep, moduleState);
// Check inlined modules have been removed during traversal. Otherwise we might have blown
// up Verilator memory consumption.
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
UASSERT_OBJ(!moduleState(modp).m_inlined, modp,
"Inlined module should have been deleted when the last instance "
"referencing it was inlined");
// Decide which instances to inline
const size_t designSize
= graphp->vertices().frontp()->as<InlineModModuleVertex>()->flattenedSize();
for (V3GraphVertex& vtx : graphp->vertices()) {
if (InlineModModuleVertex* const mVtxp = vtx.cast<InlineModModuleVertex>()) {
// If this module is less than 10% of the design, flatten this module
if (mVtxp->flattenedSize() * 10 < designSize) mVtxp->setFlatten();
// Don't inline if can't inline
if (mVtxp->noInlineHard()) continue;
// Don't inline if soft off
if (mVtxp->noInlineSoft()) continue;
// If all instances of this module combined are less than 20% of the design, inline all
size_t totalSize = mVtxp->flattenedSize() * mVtxp->instanceCount();
if (totalSize * 5 < designSize) {
for (V3GraphEdge& edge : mVtxp->inEdges()) {
InlineModCellVertex* const cVtxp = edge.fromp()->as<InlineModCellVertex>();
cVtxp->setDoInline("< 20% of design");
}
}
// No more decisions based on module vertex
continue;
}
// The instantiation
InlineModCellVertex& cVtx = *vtx.as<InlineModCellVertex>();
// The module instantiated by this cell
InlineModModuleVertex& mVtx = cVtx.instanceOf();
// Don't inline if can't inline, duh!
if (mVtx.noInlineHard()) continue;
// If it should be inlined, inlined it
if (mVtx.shouldInline()) cVtx.setDoInline("should inline");
// If --flatten, inline it
if (v3Global.opt.flatten()) cVtx.setDoInline("--flatten");
// Don't inline for other reasons if soft off
if (mVtx.noInlineSoft()) continue;
// If instatiated in exactly one static site, inline it
if (mVtx.inSize1()) cVtx.setDoInline("Single static instance");
// If small, inline it
if (mVtx.size() < INLINE_MODS_SMALLER) cVtx.setDoInline("Small");
// If inlineMult is 0, inline it
if (v3Global.opt.inlineMult() < 1) cVtx.setDoInline("inlineMult < 1");
// If it would yield less than the given number of ops, inline it
const size_t inlinedSize = mVtx.inEdges().size() * mVtx.size();
const size_t limit = v3Global.opt.inlineMult();
if (inlinedSize < limit) cVtx.setDoInline("inlinedSize < inlineMult");
}
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-decision");
// Inline the modles we decided to inline
ModuleInliner::process(nodep, *graphp);
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-inlined");
V3Global::dumpCheckGlobalTree("inline", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -1013,18 +1013,23 @@ public:
if (checkUnresolvedRef(VN_CAST(dtypep, RefDType))) return true;
} else if (const AstParamTypeDType* const paramTypep
= VN_CAST(symp->nodep(), ParamTypeDType)) {
// ParamTypeDType child may be wrapped in RequireDType or unwrapped
// Before V3Param the declared default is in childDTypep (possibly
// wrapped in a RequireDType); after V3Param it is consumed and the
// bound type is the resolved data type, e.g. a type parameter
// inherited from a specialized base class (REQ #(Item) -> class Item).
AstNode* childp = paramTypep->childDTypep();
if (const AstRequireDType* const reqp = VN_CAST(childp, RequireDType)) {
childp = reqp->lhsp();
}
if (isValidTypeNode(childp)) return true;
if (checkUnresolvedRef(VN_CAST(childp, RefDType))) return true;
const AstNode* const checkp = childp ? childp : paramTypep->skipRefp();
if (isValidTypeNode(checkp)) return true;
if (checkUnresolvedRef(VN_CAST(checkp, RefDType))) return true;
}
return false;
}
VSymEnt* resolveClassOrPackage(VSymEnt* lookSymp, AstClassOrPackageRef* nodep, bool fallback,
bool classOnly, const string& forWhat) {
bool classOnly, const string& forWhat,
bool deferIfUnresolved = false) {
if (nodep->classOrPackageSkipp()) return getNodeSym(nodep->classOrPackageSkipp());
VSymEnt* foundp;
VSymEnt* searchSymp = lookSymp;
@ -1050,6 +1055,7 @@ public:
nodep->classOrPackageNodep(foundp->nodep());
return foundp;
}
if (deferIfUnresolved) return nullptr;
const string suggest
= suggestSymFallback(lookSymp, nodep->name(), LinkNodeMatcherClassOrPackage{});
nodep->v3error((classOnly ? "Class" : "Package/class")
@ -4758,8 +4764,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
VL_RESTORER(m_pinSymp);
if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") {
const bool deferIfUnresolved = m_statep->forPrimary() && m_insideClassExtParam;
m_statep->resolveClassOrPackage(m_ds.m_dotSymp, nodep, m_ds.m_dotPos != DP_PACKAGE,
false, ":: reference");
false, ":: reference", deferIfUnresolved);
}
// ClassRef's have pins, so track

View File

@ -307,6 +307,7 @@ class LinkIncVisitor final : public VNVisitor {
return new AstSub{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Xor:
return new AstXor{nodep->fileline(), lhsp, rhsp};
default:; // Error below // LCOV_EXCL_LINE
}
}
nodep->v3fatalSrc("Unhandled compound assignment operation");

View File

@ -1464,6 +1464,20 @@ V3Number& V3Number::opCLog2(const V3Number& lhs) {
setZero();
return *this;
}
V3Number& V3Number::opMostSetBitP1(const V3Number& lhs) {
// Most-significant set bit plus one (bit-width / find-last-set); 0 if value is zero
NUM_ASSERT_OP_ARGS1(lhs);
NUM_ASSERT_LOGIC_ARGS1(lhs);
if (lhs.isFourState()) return setAllBitsX();
for (int bit = lhs.width() - 1; bit >= 0; bit--) {
if (lhs.bitIs1(bit)) {
setLong(bit + 1);
return *this;
}
}
setZero();
return *this;
}
V3Number& V3Number::opLogNot(const V3Number& lhs) {
NUM_ASSERT_OP_ARGS1(lhs);

View File

@ -761,6 +761,7 @@ public:
V3Number& opOneHot(const V3Number& lhs);
V3Number& opOneHot0(const V3Number& lhs);
V3Number& opCLog2(const V3Number& lhs);
V3Number& opMostSetBitP1(const V3Number& lhs);
V3Number& opClean(const V3Number& lhs, uint32_t bits);
V3Number& opConcat(const V3Number& lhs, const V3Number& rhs);
V3Number& opLenN(const V3Number& lhs);

View File

@ -542,7 +542,7 @@ bool V3Options::fileStatNormal(const string& filename) {
}
string V3Options::fileExists(const string& filename) {
// Surprisingly, for VCS and other simulators, this process
// Surprisingly, for some other simulators, this process
// is quite slow; presumably because of re-reading each directory
// many times. So we read a whole dir at once and cache it
@ -1448,6 +1448,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp);
DECL_OPTION("-fassemble", FOnOff, &m_fAssemble);
DECL_OPTION("-fbit-scan-loops", FOnOff, &m_fBitScanLoops);
DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) {
m_fCaseDecoder = flag;
m_fCaseTable = flag;
@ -2359,6 +2360,7 @@ void V3Options::optimize(int level) {
const bool flag = level > 0;
m_fAcycSimp = flag;
m_fAssemble = flag;
m_fBitScanLoops = flag;
m_fCaseDecoder = flag;
m_fCaseTable = flag;
m_fCaseTree = flag;

View File

@ -392,6 +392,7 @@ private:
// MEMBERS (optimizations)
bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations
bool m_fAssemble; // main switch: -fno-assemble: assign assemble
bool m_fBitScanLoops; // main switch: -fno-bit-scan-loops: convert bit scan loops to builtins
bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion
bool m_fCaseTable; // main switch: -fno-case-table: case table conversion
bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion
@ -731,6 +732,7 @@ public:
// ACCESSORS (optimization options)
bool fAcycSimp() const { return m_fAcycSimp; }
bool fAssemble() const { return m_fAssemble; }
bool fBitScanLoops() const { return m_fBitScanLoops; }
bool fCaseDecoder() const { return m_fCaseDecoder; }
bool fCaseTable() const { return m_fCaseTable; }
bool fCaseTree() const { return m_fCaseTree; }

View File

@ -1053,7 +1053,7 @@ class ConstraintExprVisitor final : public VNVisitor {
nodep->user1(false);
return;
}
bool anyChild = false;
int anyChild = false; // Used as bool
if (AstNodeExpr* const cp = VN_CAST(nodep->op1p(), NodeExpr)) {
propagateUser1InlineRecurse(cp);
anyChild |= cp->user1();
@ -1171,16 +1171,18 @@ class ConstraintExprVisitor final : public VNVisitor {
// else: Global constraints keep nodep alive for write_var processing
relinker.relink(exprp);
// For global constraints: check shared path-level set
// For inline constraints: check per-instance set (each __Vrandwith has own randomizer)
// For class-level constraints: check varp->user3()
// Global / inline / class-level member-select refs key on the full path
// (so same-type sub-objects c1.x, c2.x stay distinct); a plain class-level
// variable keys on user3().
const bool alreadyWritten = isGlobalConstrained ? m_writtenVars.count(smtName) > 0
: m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0
: membersel ? m_writtenVars.count(smtName) > 0
: varp->user3();
const bool shouldWriteVar = !alreadyWritten;
if (shouldWriteVar) {
// Track this variable path as written
if (isGlobalConstrained) m_writtenVars.insert(smtName);
if (isGlobalConstrained || (membersel && !m_inlineInitTaskp))
m_writtenVars.insert(smtName);
if (m_inlineInitTaskp) m_inlineWrittenVars.insert(smtName);
// For global constraints, delete nodep after processing
if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep);
@ -3882,6 +3884,22 @@ class RandomizeVisitor final : public VNVisitor {
return VN_IS(dtypep, ClassRefDType);
});
}
// True if this class owns a global constraint: a member-select chain rooted
// at a rand class-typed handle reaching into a sub-object.
bool classOwnsGlobalConstraint(const AstClass* classp) const {
return classp->existsMember([](const AstClass*, const AstConstraint* constrp) {
bool owns = false;
constrp->foreach([&](const AstMemberSel* memberSelp) {
const AstNode* rootp = memberSelp->fromp();
while (const AstMemberSel* const sp = VN_CAST(rootp, MemberSel))
rootp = sp->fromp();
if (const AstVarRef* const refp = VN_CAST(rootp, VarRef)) {
if (VN_IS(refp->varp()->dtypep()->skipRefp(), ClassRefDType)) owns = true;
}
});
return owns;
});
}
// Get or create __VrandCb_pre/__VrandCb_post task for nested callbacks
AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) {
const string name = "__VrandCb_" + suffix;
@ -4773,6 +4791,10 @@ class RandomizeVisitor final : public VNVisitor {
UINFO(9, "Define randomize() for " << nodep);
nodep->baseMostClassp()->needRNG(true);
// Detect global-constraint ownership BEFORE the constraint items are
// unlinked into setup tasks below (after that the member-selects are gone).
const bool basicFirst = classOwnsGlobalConstraint(nodep);
FileLine* fl = nodep->fileline();
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
@ -5081,29 +5103,41 @@ class RandomizeVisitor final : public VNVisitor {
beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
}
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
// A basicFirst owner basic-randomizes first, then the solver overrides the
// constrained leaves, so a globally-constrained leaf (not user3) is still
// basic-randomized when its type is randomized standalone.
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
randomizep->addStmtsp(new AstAssign{
fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp});
const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
const bool needsSizePhase
= sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty();
if (!needsSizePhase) {
// Size-only resize fallback (no element constraint, so no two-pass phase).
// Must run after the solver .next() that sets the size variable; emit it
// after whichever assignment below holds the solver call.
const auto emitResizeFallback = [&]() {
if (needsSizePhase) return;
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp};
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
}
}
};
if (!basicFirst) emitResizeFallback();
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
fvarRefReadp->access(VAccess::READ);
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep};
AstNodeExpr* const secondHalfp
= basicFirst ? beginValp : new AstFuncRef{fl, basicRandomizep};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
new AstAnd{fl, fvarRefReadp, secondHalfp}});
if (basicFirst) emitResizeFallback();
// Call nested post_randomize on rand class-type members (IEEE 18.4.1)
if (classHasRandClassMembers(nodep)) {
@ -5643,15 +5677,21 @@ public:
explicit RandomizeVisitor(AstNetlist* nodep)
: m_inlineUniqueNames{"__Vrandwith"} {
createRandomizeClassVars(nodep);
// Mark variables in global constraints
// These should not be randomized in nested class's __VBasicRand
nodep->foreach([&](AstConstraint* constrp) {
constrp->foreach([&](AstMemberSel* memberSelp) {
// Only mark if this MemberSel was created during constraint cloning
if (memberSelp->user2p()) {
// Flag local constraint leaves as solver-owned so __VBasicRand skips them.
// Runs before any class is lowered. Only a randomized class counts, and
// only a leaf owned by the constraint's own class: a leaf reached through
// a global constraint stays basic-randomized so a standalone randomize()
// of its type still randomizes it (issue #7833); a class-typed sub-object
// is skipped so its own members are still basic-randomized.
nodep->foreach([&](AstClass* const classp) {
if (!classp->user1()) return;
classp->foreachMember([&](AstClass* const ownerClassp, AstConstraint* const constrp) {
constrp->foreach([&](AstMemberSel* const memberSelp) {
AstVar* const varp = memberSelp->varp();
if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return;
if (VN_AS(varp->user2p(), NodeModule) != ownerClassp) return;
if (!varp->user3()) varp->user3(true);
}
});
});
});

View File

@ -39,11 +39,18 @@ namespace {
class VirtIfaceVisitor final : public VNVisitor {
private:
// STATE
using IfaceMember = std::pair<const AstIface*, std::string>;
using IfaceCallable = std::pair<AstIface*, AstCFunc*>;
// Set of (iface, member) pairs written through VIF -- defines which members need triggers
std::set<std::pair<const AstIface*, const std::string>> m_vifWrittenMembers;
std::set<IfaceMember> m_vifWrittenMembers;
// All candidate VarScopes of interface members (keyed by interface type + member name)
std::map<std::pair<const AstIface*, const std::string>, std::vector<AstVarScope*>>
m_candidateVscps;
std::map<IfaceMember, std::vector<AstVarScope*>> m_candidateVscps;
std::set<std::pair<IfaceMember, AstVarScope*>> m_seenCandidateVscps;
// VarScope index and callable worklist for VIF method-body writes.
std::map<const AstVar*, std::vector<AstVarScope*>> m_vscpsByVar;
std::vector<IfaceCallable> m_reachableIfaceCallables;
std::set<IfaceCallable> m_seenReachableIfaceCallables;
VirtIfaceTriggers m_triggers;
// METHODS
@ -70,12 +77,24 @@ private:
}
iterateChildren(nodep);
}
void visit(AstCMethodCall* nodep) override {
if (const AstIfaceRefDType* const dtypep
= VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType)) {
if (VL_UNCOVERABLE(!dtypep->isVirtual())) {
// Concrete interface method calls are lowered before this pass.
} else {
addReachableIfaceCallable(dtypep->ifaceViaCellp(), nodep->funcp());
}
}
iterateChildren(nodep);
}
void visit(AstVarScope* nodep) override {
// Collect candidate VarScopes. sensIfacep() is set on interface members
// accessed via any MemberSel (virtual or non-virtual).
if (const AstIface* const ifacep = nodep->varp()->sensIfacep()) {
m_candidateVscps[{ifacep, nodep->varp()->name()}].push_back(nodep);
}
AstVar* const varp = nodep->varp();
if (varp->isTemp()) return;
m_vscpsByVar[varp].push_back(nodep);
if (const AstIface* const ifacep = varp->sensIfacep()) addCandidateVscp(ifacep, nodep);
}
void visit(AstNodeProcedure* nodep) override {
// Disable lifetime optimization for variables in AlwaysPost blocks
@ -87,6 +106,19 @@ private:
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
void addCandidateVscp(const AstIface* const ifacep, AstVarScope* const vscp) {
const IfaceMember member{ifacep, vscp->varp()->name()};
if (m_seenCandidateVscps.emplace(member, vscp).second)
m_candidateVscps[member].push_back(vscp);
}
void addReachableIfaceCallable(AstIface* const ifacep, AstCFunc* const funcp) {
const IfaceCallable callable{ifacep, funcp};
if (m_seenReachableIfaceCallables.emplace(callable).second) {
m_reachableIfaceCallables.push_back(callable);
}
}
// Build final trigger list by intersecting VIF writes with candidate VarScopes
void buildTriggers() {
for (const auto& written : m_vifWrittenMembers) {
@ -103,6 +135,29 @@ public:
// CONSTRUCTORS
explicit VirtIfaceVisitor(AstNetlist* nodep) {
iterate(nodep);
for (size_t i = 0; i < m_reachableIfaceCallables.size(); ++i) {
const IfaceCallable callable = m_reachableIfaceCallables[i];
callable.second->foreach([this, callable](AstNodeExpr* const nodep) {
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
// Only persistent interface storage is observable through a VIF read.
UASSERT_OBJ(refp->varScopep(), refp, "No var scope");
AstVar* const varp = refp->varp();
if (!refp->access().isWriteOrRW() || varp->isFuncLocal() || varp->isTemp()
|| varp->isEvent() || !VN_IS(refp->varScopep()->scopep()->modp(), Iface)) {
return;
}
varp->sensIfacep(callable.first);
m_vifWrittenMembers.emplace(callable.first, varp->name());
const auto it = m_vscpsByVar.find(varp);
UASSERT_OBJ(it != m_vscpsByVar.end(), varp,
"No VarScope for interface member");
for (AstVarScope* const vscp : it->second)
addCandidateVscp(callable.first, vscp);
} else if (AstNodeCCall* const callp = VN_CAST(nodep, NodeCCall)) {
addReachableIfaceCallable(callable.first, callp->funcp());
}
});
}
buildTriggers();
}
~VirtIfaceVisitor() override = default;

View File

@ -62,6 +62,8 @@ struct UnrollStats final {
Stat m_nPragmaDisabled{"Pragma unroll_disable"};
Stat m_nUnrolledLoops{"Unrolled loops"};
Stat m_nUnrolledIters{"Unrolled iterations"};
Stat m_bitScanLowered{"Lowered priority-encoder to mostsetbitp1"};
Stat m_countOnesLowered{"Lowered count-set-bits to countones"};
};
//######################################################################
@ -422,6 +424,157 @@ class UnrollAllVisitor final : VNVisitor {
UnrollStats m_stats; // Statistic tracking
UnrolllBindings m_bindings; // Variable bindings
// METHODS
// Peel value-preserving width casts (Extend/ExtendS, or a low-bits Sel with lsb 0) to the
// underlying VarRef. A Sel kept narrower than 'minWidth' is a lossy narrowing (idx[1:0])
// and is rejected.
static AstVarRef* unwrapToVarRef(AstNodeExpr* nodep, int minWidth) {
while (true) {
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) return refp;
if (AstExtend* const ep = VN_CAST(nodep, Extend)) {
nodep = ep->lhsp();
} else if (AstExtendS* const ep = VN_CAST(nodep, ExtendS)) {
nodep = ep->lhsp();
} else if (AstSel* const sp = VN_CAST(nodep, Sel)) {
const AstConst* const lsbp = VN_CAST(sp->lsbp(), Const);
if (!lsbp || lsbp->toUInt() != 0 || sp->width() < minWidth) return nullptr;
nodep = sp->fromp();
} else {
return nullptr;
}
}
}
// True if 'nodep' is exactly '1 + var' for 'vscp' (V3Const puts the constant on the LHS).
// Passing the add's width as minWidth rejects a lossy increment like 32'(i[1:0]) + 1.
bool isVarPlus1(AstNode* nodep, const AstVarScope* vscp) {
AstAdd* const addp = VN_CAST(nodep, Add);
if (!addp || !addp->lhsp()->isOne()) return false;
const AstVarRef* const r = unwrapToVarRef(addp->rhsp(), addp->width());
return r && r->varScopep() == vscp;
}
// Resize the 32-bit reduction to the accumulator width; truncating the low bits matches
// the original counted loop's wrap-around.
static AstNodeExpr* resizeToWidth(AstNodeExpr* exprp, const AstVarRef* targetRefp) {
const int width = targetRefp->width();
if (width == 32) return exprp;
FileLine* const flp = exprp->fileline();
if (width < 32) return new AstSel{flp, exprp, 0, width};
AstExtend* const extp = new AstExtend{flp, exprp};
extp->dtypeFrom(targetRefp);
return extp;
}
// Match a strict ascending loop bound 'idx < W'. V3Const canonicalizes this to the
// 'W > idx' form (Gt unsigned, GtS signed), so only that form is matched.
static bool ascendingBound(AstNodeExpr* condp, AstConst*& wp, AstVarRef*& idxRefp) {
if (!VN_IS(condp, Gt) && !VN_IS(condp, GtS)) return false;
AstNodeBiop* const bp = VN_AS(condp, NodeBiop);
wp = VN_CAST(bp->lhsp(), Const);
idxRefp = VN_CAST(bp->rhsp(), VarRef);
return wp && idxRefp && !wp->num().isFourState();
}
// Recognize the redundant in-range guard Verilator auto-inserts for a select into a
// non-power-of-two vector. V3Const canonicalizes 'idx <= C' to '(C >= idx)' (Gte/GteS,
// const on the LHS), so only that form occurs; with C >= W-1 it is always true for idx
// in 0..W-1.
static bool isInRangeGuard(AstNodeExpr* condp, const AstVarScope* idxVscp, uint32_t width,
int addrBits) {
if (!VN_IS(condp, Gte) && !VN_IS(condp, GteS)) return false;
AstNodeBiop* const bp = VN_AS(condp, NodeBiop);
const AstConst* const cp = VN_CAST(bp->lhsp(), Const);
if (!cp || cp->num().isFourState() || cp->toUInt() < width - 1) return false;
const AstVarRef* const r = unwrapToVarRef(bp->rhsp(), addrBits);
return r && r->varScopep() == idxVscp;
}
// Recognize a single-bit scan loop over all W bits of 'vec' (idx 0..W-1, target
// pre-zeroed) and lower it to a bit-reduction primitive. Two idioms are matched:
// target = 0; idx = 0;
// loop { looptest(W > idx); if (...vec[idx]...) target = <e>; idx = idx + 1; }
// where, when W == width(vec):
// <e> = idx + 1 => target = $mostsetbitp1(vec) (leading-one / bit-width)
// <e> = target + 1 => target = $countones(vec) (population count)
bool tryLowerBitScanLoop(AstLoop* loopp) {
AstLoopTest* const testp = VN_CAST(loopp->stmtsp(), LoopTest);
if (!testp) return false;
AstIf* const ifp = VN_CAST(testp->nextp(), If);
if (!ifp) return false;
AstAssign* const incp = VN_CAST(ifp->nextp(), Assign);
if (!incp || incp->nextp()) return false;
AstConst* wp = nullptr;
AstVarRef* idxRefp = nullptr;
if (!ascendingBound(testp->condp(), wp, idxRefp)) return false;
AstVarScope* const idxVscp = idxRefp->varScopep();
const uint32_t width = wp->toUInt();
// Bits needed to address all W bits of 'vec' (clog2(W)); a narrower index is lossy.
const int addrBits = width <= 1 ? 1 : V3Number::log2b(width - 1) + 1;
const AstConst* const idxInitp = m_bindings.get(idxVscp);
if (!idxInitp || !idxInitp->isZero()) return false;
AstVarRef* const incLhsp = VN_CAST(incp->lhsp(), VarRef);
if (!incLhsp || incLhsp->varScopep() != idxVscp) return false;
if (!isVarPlus1(incp->rhsp(), idxVscp)) return false;
if (ifp->elsesp()) return false;
AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign);
if (!thenp || thenp->nextp()) return false;
AstVarRef* const targetRefp = VN_CAST(thenp->lhsp(), VarRef);
if (!targetRefp) return false;
AstVarScope* const targetVscp = targetRefp->varScopep();
if (targetVscp == idxVscp) return false;
const bool isLeadingOne = isVarPlus1(thenp->rhsp(), idxVscp);
const bool isCountOnes = !isLeadingOne && isVarPlus1(thenp->rhsp(), targetVscp);
if (!isLeadingOne && !isCountOnes) return false;
// If-cond is the 1-bit select 'vec[idx]', possibly wrapped in the redundant in-range
// guard Verilator auto-inserts (as 'guard && sel') for a non-power-of-two vector:
// '(idx <= W-1) && vec[idx]' (default / --x-assign 0; a LogAnd), or
// '(idx <= W-1) ? vec[idx] : <x>' (--x-assign unique; a Cond).
// The guard is always true for idx in 0..W-1, so peel it to reach the select. Any
// other compound condition (e.g. 'vec[idx] && en') leaves a non-select, rejected below.
AstNodeExpr* condp = ifp->condp();
if (AstLogAnd* const andp = VN_CAST(condp, LogAnd)) {
if (isInRangeGuard(andp->lhsp(), idxVscp, width, addrBits)) condp = andp->rhsp();
} else if (AstCond* const ternp = VN_CAST(condp, Cond)) {
if (isInRangeGuard(ternp->condp(), idxVscp, width, addrBits)) condp = ternp->thenp();
}
AstSel* const selp = VN_CAST(condp, Sel);
if (!selp || selp->width() != 1) return false;
const AstVarRef* const fromp = VN_CAST(selp->fromp(), VarRef);
if (!fromp) return false;
const AstVarScope* const fromVscp = fromp->varScopep();
if (fromVscp == idxVscp || fromVscp == targetVscp) return false;
AstNodeExpr* const vecExprp = selp->fromp();
// Must scan all W bits of 'vec', indexed by exactly 'idx' (address kept >= clog2(W),
// so a lossy narrowing like vec[idx[2:0]] is rejected).
if (static_cast<int>(width) != vecExprp->width()) return false;
const AstVarRef* const idxInSel = unwrapToVarRef(selp->lsbp(), addrBits);
if (!idxInSel || idxInSel->varScopep() != idxVscp) return false;
// 'target' must be const-0 immediately before the loop (collected in m_bindings),
// so that an all-zero 'vec' yields 0, matching $mostsetbitp1's definition.
const AstConst* const targetInitp = m_bindings.get(targetVscp);
if (!targetInitp || !targetInitp->isZero()) return false;
// Rewrite to 'target = <reduction>(vec); idx = W'. The 'idx = W' store preserves the
// loop's exit value, so this is sound even if idx is read afterwards (else DCE drops it).
FileLine* const flp = loopp->fileline();
AstNodeExpr* reducep;
if (isLeadingOne) {
reducep = new AstMostSetBitP1{flp, vecExprp->cloneTree(false)};
} else {
AstCountOnes* const conep = new AstCountOnes{flp, vecExprp->cloneTree(false)};
conep->dtypeSetInteger2State();
reducep = conep;
}
reducep = resizeToWidth(reducep, targetRefp);
AstAssign* const newp = new AstAssign{flp, targetRefp->cloneTree(false), reducep};
newp->addNext(new AstAssign{flp, incLhsp->cloneTree(false), wp->cloneTree(false)});
loopp->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(loopp), loopp);
if (isLeadingOne) {
UINFO(4, "Lowered priority-encoder loop to $mostsetbitp1: " << newp);
++m_stats.m_bitScanLowered;
} else {
UINFO(4, "Lowered count-set-bits loop to $countones: " << newp);
++m_stats.m_countOnesLowered;
}
return true;
}
// VISIT
void visit(AstLoop* nodep) override {
// Gather variable bindings from the preceding statements
@ -450,6 +603,9 @@ class UnrollAllVisitor final : VNVisitor {
m_bindings.set(lhsp->varScopep(), valp);
}
// Recognize a bit counting loop and lower it to a builtin
if (v3Global.opt.fBitScanLoops() && tryLowerBitScanLoop(nodep)) return;
// Attempt to unroll this loop
const std::pair<AstNode*, bool> pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep);

View File

@ -240,7 +240,7 @@ void VlcTop::writeInfo(const string& filename) {
// FNF:<number_functions_found>
// FNH:<number_functions_hit>
// Branches:
// BRDA:<line_number>,<block_number>,<branch_number>,<taken_count_or_-_for_zero>
// BRDA:<line_number>,<block_number>,<branch>,<taken_count_or_-_for_zero>
// BRF:<number_of_branches_found>
// BRH:<number_of_branches_hit>
// Line counts:
@ -273,8 +273,14 @@ void VlcTop::writeInfo(const string& filename) {
for (const VlcPoint* point : infoPoints) {
os << "BRDA:" << sc.lineno() << ",";
os << "0,";
os << point_num << ",";
os << point->count() << "\n";
if (point->comment().empty()) {
os << point_num;
} else {
std::string comment(point->comment());
std::replace(comment.begin(), comment.end(), ',', '_');
os << comment;
}
os << "," << point->count() << "\n";
branchesHit += opt.countOk(point->count());
++point_num;

View File

@ -7339,8 +7339,29 @@ cross_itemList<nodep>: // IEEE: part of list_of_cross_items
;
cross_item<nodep>: // ==IEEE: cross_item
id/*cover_point_identifier*/
{ $$ = new AstCoverpointRef{$<fl>1, *$1}; }
// // IEEE: cover_point_identifier | variable_identifier - both are a
// // simple identifier. We parse idDotted (a plain hierarchical
// // reference a.b.c, with no bit/array selects) to also accept the
// // non-standard dotted form (e.g. 'cross a.b') that several
// // simulators support; the common simple-identifier case is detected
// // and handled exactly as before.
idDotted
{
if (AstParseRef* const refp = VN_CAST($1, ParseRef)) {
// Standard: simple cover_point_identifier / variable_identifier
$$ = new AstCoverpointRef{refp->fileline(), refp->name()};
VL_DO_DANGLING(refp->deleteTree(), refp);
} else {
// Verilator extension beyond strict IEEE (cross_item is a simple
// identifier): some tools accept a hierarchical/dotted reference.
// Carry the reference expression (still an AstDot here) out of the
// parser unchanged; later stages resolve and, eventually, implement
// it as an implicit coverpoint.
$1->v3warn(NONSTD, "Non-standard hierarchical reference as a coverage "
"cross item (an implicit coverpoint)");
$$ = new AstCoverpointRef{$1->fileline(), $1};
}
}
;
cross_body<nodep>: // ==IEEE: cross_body

View File

@ -33,9 +33,7 @@ module t (
initial begin
`checkh($dimensions (array_unpk), 3);
`ifndef VCS
`checkh($unpacked_dimensions (array_unpk), 2); // IEEE 2009
`endif
`checkh($bits (array_unpk), 2*2*2);
`checkh($low (array_unpk), 2);
`checkh($high (array_unpk), 3);

View File

@ -35,7 +35,7 @@ module t (
assert property (@(posedge clk) always [2:$] a_low) else low2_fail_q.push_back(cyc);
// a_drop is high then drops at cyc 5 and stays low: deterministic single
// transition, so Verilator and Questa agree on the failing ticks exactly.
// transition, so Verilator and others agree on the failing ticks exactly.
assert property (@(posedge clk) always [2:$] a_drop) else drop_fail_q.push_back(cyc);
always @(posedge clk) begin
@ -43,12 +43,12 @@ module t (
if (cyc >= 4) a_drop <= 1'b0;
if (cyc == 19) begin
// Counts pinned to Verilator (NFA per-cycle reject). For all-fail windows
// Questa is one lower (it does not fire the end-of-sim tick); see the sva
// others are one lower (it does not fire the end-of-sim tick); see the sva
// lessons "multi-cycle end-of-simulation offset" note.
`checkd(high_fail_q.size(), 0); // Questa: 0
`checkd(low0_fail_q.size(), 20); // Questa: 19
`checkd(low2_fail_q.size(), 18); // Questa: 17
`checkd(drop_fail_q[0], 5); // first fail tick: a_drop sampled low from cyc 5
`checkd(high_fail_q.size(), 0);
`checkd(low0_fail_q.size(), 20); // All others: 19
`checkd(low2_fail_q.size(), 18); // All others: 17
`checkd(drop_fail_q[0], 5); // All others: 6; first fail tick: a_drop sampled low from cyc 5
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -66,12 +66,12 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail1, 66); // Questa: 66
`checkd(count_fail2, 69); // Questa: 69
`checkd(count_fail3, 26); // Questa: 26
`checkd(count_fail4, 66); // Questa: 66
`checkd(count_fail5, 80); // Questa: 80
`checkd(count_fail6, 27); // Questa: 27
`checkd(count_fail1, 66);
`checkd(count_fail2, 69);
`checkd(count_fail3, 26);
`checkd(count_fail4, 66);
`checkd(count_fail5, 80);
`checkd(count_fail6, 27);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -95,23 +95,23 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail1, 5); // Questa: 5
`checkd(count_fail2, 25); // Questa: 25
`checkd(count_fail3, 9); // Questa: 9
`checkd(count_fail4, 49); // Questa: 49
`checkd(count_fail5, 0); // Questa: 0
// NFA merge-node range [*M:N] over-counts rejects (Questa: 51); match
`checkd(count_fail1, 5);
`checkd(count_fail2, 25); // One other sim: 19
`checkd(count_fail3, 9);
`checkd(count_fail4, 49);
`checkd(count_fail5, 0);
// NFA merge-node range [*M:N] over-counts rejects; match
// detection is correct, only reject counting is imprecise
`checkd(count_fail6, 59);
`checkd(count_fail7, 51); // Questa: 51
`checkd(count_fail8, 20); // Questa: 20
`checkd(count_fail6, 59); // All other sims: 51
`checkd(count_fail7, 51);
`checkd(count_fail8, 20);
// IEEE 1800-2023 16.9.2 permits empty match of [*0]; NFA reports
// rejects on each tick while Questa suppresses (Questa: 20)
`checkd(count_fail9, 49);
`checkd(count_fail10, 59); // Questa: 59
// rejects on each tick while others suppress
`checkd(count_fail9, 49); // Most others: 20, one other 49
`checkd(count_fail10, 59);
// a[*] ##1 b: NFA treats unbounded [*] as liveness (no reject);
// Questa treats as definite antecedent (Questa: 29)
`checkd(count_fail11, 0);
// Should be definite antecedent
`checkd(count_fail11, 0); // All other sims: 29
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -40,8 +40,8 @@ module t (
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail_257, 0);
// Questa: 31 -- pre-existing ~26.5% NFA reject gap on |-> ##1 [*N]
`checkd(count_fail_513, 23);
// Mismatch due to pre-existing ~26.5% NFA reject gap on |-> ##1 [*N]
`checkd(count_fail_513, 23); // All other sims: 31
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -5,6 +5,8 @@
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`ifdef verilator
`define no_optimize(v) $c(v)
`else
@ -12,7 +14,7 @@
`endif
// verilog_format: on
module t ( /*AUTOARG*/);
module t;
logic clk = 0;
int imm_fails = 0, conc_fails = 0;
logic a = 1'b1; // antecedent always true
@ -58,11 +60,8 @@ module t ( /*AUTOARG*/);
end
final begin
// Concrete counts cross-checked against Questa 2022.3: imm_fails=5 conc_fails=7.
if (imm_fails != 5 || conc_fails != 7) begin
$display("%%Error: imm_fails=%0d (exp 5) conc_fails=%0d (exp 7)", imm_fails, conc_fails);
$stop;
end
`checkd(imm_fails, 5);
`checkd(conc_fails, 7); // Other sims: 7 or 1
$write("*-* All Finished *-*\n");
end
endmodule

View File

@ -36,7 +36,8 @@ interface AssertCtlIface;
$assertcontrol(3, 2, 1);
endfunction
function void fail_check();
assert (0) `stop; else fails++;
assert (0) `stop;
else fails++;
endfunction
function void run_checks();
assert_off();
@ -69,12 +70,13 @@ module t;
$assertcontrol(3, 2, 1);
endfunction
function void fail_check();
assert (0) `stop; else class_fails++;
assert (0) `stop;
else class_fails++;
endfunction
endclass
AssertCtlClass assert_ctl_class;
AssertCtlIface assert_ctl_iface();
AssertCtlIface assert_ctl_iface ();
virtual AssertCtlIface v_assert_ctl_iface = assert_ctl_iface;
always #5 clk = !clk;
@ -84,17 +86,17 @@ module t;
assert property (@(posedge clk) 1'b0 |-> ##1 1'b1) begin
vacuous_passes++;
end else
`stop;
end
else `stop;
assert property (@(posedge clk) 1'b1 |-> ##1 1'b1) begin
nonvacuous_passes++;
end else
`stop;
end
else `stop;
assert property (@(posedge clk) 1'b1 |-> ##1 1'b0) begin
end else
concurrent_fails++;
end
else concurrent_fails++;
task automatic tick_and_check(input int exp_vacuous, input int exp_nonvacuous,
input int exp_concurrent_fails);
@ -110,62 +112,73 @@ module t;
$assertcontrol(4, 16, 1);
$assertcontrol(5, 16, 1);
$assertcontrol(3*`IMPURE_ONE, 2*`IMPURE_ONE);
$assertcontrol(3 * `IMPURE_ONE, 2 * `IMPURE_ONE);
$assertcontrol(3, 255, 7);
$assertcontrol(6, 255, 7);
$assertcontrol(8, 255, 7);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 1);
$assertcontrol(1, 2, 1);
$assertcontrol(4, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 2);
$assertcontrol(2, 2, 1);
$assertcontrol(4, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 2);
$assertcontrol(3, 2, 1);
$assertcontrol(1, 2, 1);
$assertcontrol(7, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 3);
$assertcontrol(2, 2, 1);
$assertcontrol(7, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 3);
$assertcontrol(10, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 4);
$assertcontrol(11, 2, 1);
assert (1) imm_passes++; else `stop;
assert (1) imm_passes++;
else `stop;
`checkd(imm_passes, 5);
$assertcontrol(6, 2, 1);
assert (0) `stop; else imm_fails++;
assert (0) `stop;
else imm_fails++;
`checkd(imm_fails, 1);
$assertcontrol(1, 2, 1);
$assertcontrol(9, 2, 1);
assert (0) `stop; else imm_fails++;
assert (0) `stop;
else imm_fails++;
`checkd(imm_fails, 2);
$assertcontrol(2, 2, 1);
$assertcontrol(9, 2, 1);
assert (0) `stop; else imm_fails++;
assert (0) `stop;
else imm_fails++;
`checkd(imm_fails, 2);
$assertcontrol(8, 2, 1);
assert (0) `stop; else imm_fails++;
assert (0) `stop;
else imm_fails++;
`checkd(imm_fails, 3);
assert_ctl_class.assert_off();

View File

@ -14,7 +14,7 @@
module t;
initial begin
$assertcontrol(100*`IMPURE_ONE);
$assertcontrol(100 * `IMPURE_ONE);
$finish;
end
endmodule

View File

@ -16,10 +16,8 @@ module t (
integer action_hits = 0;
integer cyc = 0;
assert property (@(posedge clk) ##1 1'b1)
action_hits++;
else
action_hits--;
assert property (@(posedge clk) ##1 1'b1) action_hits++;
else action_hits--;
always @(posedge clk) begin
cyc++;

View File

@ -75,14 +75,14 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail1, 20); // Questa: 20
`checkd(count_fail2, 25); // Questa: 25
`checkd(count_fail3, 19); // Questa: 19
`checkd(count_fail4, 0); // Questa: 0
`checkd(count_fail5, 20); // Questa: 20
`checkd(count_fail6, 25); // Questa: 25
`checkd(count_fail7, 20); // Questa: 20
`checkd(count_fail8, 20); // Questa: 20
`checkd(count_fail1, 20);
`checkd(count_fail2, 25);
`checkd(count_fail3, 19);
`checkd(count_fail4, 0);
`checkd(count_fail5, 20);
`checkd(count_fail6, 25);
`checkd(count_fail7, 20);
`checkd(count_fail8, 20);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -56,10 +56,10 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
`checkd(count_fail1, 34); // Questa: 29
`checkd(count_fail2, 27); // Questa: 32
`checkd(count_fail3, 25); // Questa: 29
`checkd(count_fail4, 0); // Questa: 0
`checkd(count_fail1, 34); // Other sims: 29, one other: 20
`checkd(count_fail2, 27); // Other sims: 32, one other: 25
`checkd(count_fail3, 25); // Other sims: 29, one other: 25
`checkd(count_fail4, 0);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats'])
test.execute(check_finished=True)
test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3)
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 16)
test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 4)
test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 42)
test.passes()

View File

@ -57,9 +57,8 @@ module t (
end
else if (cyc == 99) begin
`checkh(crc, 64'hc77bb9b3784ea091);
// Questa 2022.3 golden: count_gated=5, count_ref=12.
`checkd(count_gated, 5);
`checkd(count_ref, 12);
`checkd(count_gated, 5); // Other sims same, one other: 4
`checkd(count_ref, 12); // Other sims same, one other: 10
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -44,10 +44,9 @@ module t (
end
// Counts read in final (Postponed) to avoid same-timestep races.
// Concrete Verilator counts; Questa: fails_single=17 fails_multi=17
final begin
`checkd(fails_single, 17);
`checkd(fails_multi, 17);
`checkd(fails_single, 17); // Other sims: 0
`checkd(fails_multi, 17); // Other sims: 0
$write("*-* All Finished *-*\n");
end
endmodule

View File

@ -15,16 +15,20 @@
: ... note: In instance 't'
27 | @clk a
| ^
%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:32:3: Unsupported: Unclocked assertion
: ... note: In instance 't'
%Error: t/t_assert_seq_clocking_unsup.v:32:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
32 | assert property (s_nest ##1 a);
| ^~~~~~
%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:33:3: Unsupported: Unclocked assertion
: ... note: In instance 't'
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_assert_seq_clocking_unsup.v:33:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
33 | assert property (s_level);
| ^~~~~~
%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:34:3: Unsupported: Unclocked assertion
: ... note: In instance 't'
%Error: t/t_assert_seq_clocking_unsup.v:34:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
34 | assert property (s_level2);
| ^~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,22 @@
%Error: t/t_assert_unclocked_bad.v:9:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
9 | assert property (a);
| ^~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_assert_unclocked_bad.v:10:22: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
10 | assert property (a |=> b);
| ^~~
%Error: t/t_assert_unclocked_bad.v:10:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
10 | assert property (a |=> b);
| ^~~~~~
%Error: t/t_assert_unclocked_bad.v:11:3: Concurrent assertion has no clock (IEEE 1800-2023 16.16)
: ... note: In instance 't'
: ... Suggest provide a clocking event, a default clocking, or a clocked procedural context
11 | cover property (a);
| ^~~~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
test.lint(expect_filename=test.golden_filename, fails=True)
test.passes()

View File

@ -0,0 +1,12 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
module t;
logic a, b;
assert property (a);
assert property (a |=> b);
cover property (a);
endmodule

View File

@ -33,13 +33,12 @@ module t;
task automatic run_pass();
assert (1) begin
pass_count++;
end else
`stop;
end
else `stop;
endtask
task automatic run_fail();
assert (0)
`stop;
assert (0) `stop;
else begin
fail_count++;
end

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
# --unroll-count 0 so the loops are recognized without relying on unrolling.
test.compile(verilator_flags2=['--stats', '--unroll-count', '0'])
# The leading-one positives lower to $mostsetbitp1, the count-ones positive to
# $countones; the negatives are left as loops (a wrong lowering would raise a count).
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Lowered priority-encoder to mostsetbitp1\s+(\d+)',
8)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Lowered count-set-bits to countones\s+(\d+)', 1)
test.execute()
test.passes()

View File

@ -0,0 +1,169 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Exercises the bit-counting loop idioms that V3Unroll lowers to builtins:
// leading-one for (b=0;b<W;b++) if (vec[b]) n = b + 1; -> $mostsetbitp1(vec)
// count-ones for (b=0;b<W;b++) if (vec[b]) n = n + 1; -> $countones(vec)
// Positives must lower (counted via --stats by the .py); negatives compute a
// different value than the builtin and so must be left as loops.
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
module t (
input clk
);
// ---- positives: must lower ----
logic [31:0] p32;
logic [5:0] n32; // I path, narrow target (select resize)
logic [47:0] p48;
logic [6:0] n48; // Q path
logic [79:0] p80;
logic [6:0] n80; // W path
logic [31:0] pu;
logic [5:0] nu; // unsigned loop index
logic [31:0] p32e;
logic [31:0] n32e; // 32-bit target (no resize)
logic [31:0] p32w;
logic [39:0] n40; // >32-bit target (extend resize)
logic [31:0] pc;
logic [5:0] nc; // count-ones -> $countones
logic [31:0] kvec; // const (set in initial) -> exercises $mostsetbitp1 fold
logic [5:0] kn;
initial kvec = 32'h0000_0100;
logic [31:0] kvec0; // const 0 -> $mostsetbitp1(0)=0 (covers the zero path)
logic [5:0] kn0;
initial kvec0 = 32'h0;
always_comb begin
n32 = 0;
for (int b = 0; b < 32; b++) if (p32[b]) n32 = 6'(b + 1);
end
always_comb begin
n48 = 0;
for (int b = 0; b < 48; b++) if (p48[b]) n48 = 7'(b + 1);
end
always_comb begin
n80 = 0;
for (int b = 0; b < 80; b++) if (p80[b]) n80 = 7'(b + 1);
end
always_comb begin
nu = 0;
for (int unsigned b = 0; b < 32; b++) if (pu[b]) nu = 6'(b + 1);
end
always_comb begin
n32e = 0;
for (int b = 0; b < 32; b++) if (p32e[b]) n32e = 32'(b + 1);
end
always_comb begin
n40 = 0;
for (int b = 0; b < 32; b++) if (p32w[b]) n40 = 40'(b + 1);
end
always_comb begin
nc = 0;
for (int b = 0; b < 32; b++) if (pc[b]) nc = nc + 1;
end
always_comb begin
kn = 0;
for (int b = 0; b < 32; b++) if (kvec[b]) kn = 6'(b + 1);
end
always_comb begin
kn0 = 0;
for (int b = 0; b < 32; b++) if (kvec0[b]) kn0 = 6'(b + 1);
end
// ---- negatives: must NOT lower (each yields a different value than the builtin) ----
logic [31:0] vn; // shared input, bits {2,4,5,7}
logic [31:0] vw; // has a set bit above the scan bound
logic [31:0] vt; // for the truncated-index case
logic en1; // runtime gate for the compound-condition case
logic [5:0] e_step2;
logic [6:0] e_start1;
logic [6:0] e_mul;
logic [5:0] e_off;
logic [5:0] e_noP1;
logic [5:0] e_narrow;
logic [5:0] e_comp;
logic [5:0] e_trunc;
always_comb begin
e_step2 = 0;
for (int b = 0; b < 32; b += 2) if (vn[b]) e_step2 = 6'(b + 1);
end
always_comb begin
e_start1 = 0;
for (int b = 1; b < 32; b++) if (vn[b]) e_start1 = 7'(b + 1);
end
always_comb begin
e_mul = 0;
for (int b = 0; b < 32; b++) if (vn[b]) e_mul = 7'(2 * b + 1);
end
always_comb begin
e_off = 0;
for (int b = 0; b < 31; b++) if (vn[b+1]) e_off = 6'(b + 1);
end
always_comb begin
e_noP1 = 0;
for (int b = 0; b < 32; b++) if (vn[b]) e_noP1 = 6'(b);
end
always_comb begin
e_narrow = 0;
for (int b = 0; b < 16; b++) if (vw[b]) e_narrow = 6'(b + 1);
end
always_comb begin
e_comp = 0;
for (int b = 0; b < 32; b++) if (vn[b] && en1) e_comp = 6'(b + 1);
end
// verilator lint_off WIDTHEXPAND
always_comb begin
e_trunc = 0;
for (int b = 0; b < 32; b++) if (vt[b[2:0]]) e_trunc = 6'(b + 1);
end
// verilator lint_on WIDTHEXPAND
int cyc = 0;
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 0) begin
p32 <= 32'h8000_0000;
p48 <= 48'h0;
p48[47] <= 1'b1;
p80 <= 80'h0;
p80[79] <= 1'b1;
pu <= 32'h0001_0000; // bit 16
p32e <= 32'h8000_0000;
p32w <= 32'h8000_0000;
pc <= 32'hf0f0_f0f0; // 16 ones
vn <= 32'h0000_00b4; // bits {2,4,5,7}
vw <= 32'h0010_0008; // bits {3,20}
vt <= 32'h0000_0080; // bit 7
en1 <= 1'b0; // gate off -> compound loop yields 0
end
else if (cyc == 1) begin
`checkh(n32, 6'd32);
`checkh(n48, 7'd48);
`checkh(n80, 7'd80);
`checkh(nu, 6'd17); // unsigned-index leading-one, bit 16 -> 17
`checkh(n32e, 32'd32);
`checkh(n40, 40'd32);
`checkh(nc, 6'd16); // popcount(0xF0F0F0F0)
`checkh(kn, 6'd9); // mostsetbitp1(0x100), constant-folded
`checkh(kn0, 6'd0); // mostsetbitp1(0)=0, constant-folded (zero path)
// negatives, hand-computed for vn = 0xB4 (bits 2,4,5,7):
`checkh(e_step2, 6'd5); // highest even set bit (4) + 1
`checkh(e_start1, 7'd8); // highest set bit in [1,32) (7) + 1
`checkh(e_mul, 7'd15); // 2*7 + 1
`checkh(e_off, 6'd7); // idx where vec[idx+1]; highest 6 -> 7
`checkh(e_noP1, 6'd7); // highest set bit (7), no +1
`checkh(e_narrow, 6'd4); // W=16 != width(vec): only low bits scanned (bit 3)
`checkh(e_comp, 6'd0); // && en1 (=0); a wrong lowering would give 8
`checkh(e_trunc, 6'd32); // vt[b[2:0]] last hits b=31; a wrong lowering would give 8
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
# Reuse the same design; only the optimization switch differs.
test.top_filename = "t/t_bit_scan_loops.v"
test.compile(verilator_flags2=['--stats', '--unroll-count', '0', '-fno-bit-scan-loops'])
# With the optimization disabled, nothing lowers.
test.file_grep(test.stats, r'Lowered priority-encoder to mostsetbitp1\s+([0-9])', 0)
test.execute()
test.passes()

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
# Reuse the same design. '--x-assign 0' makes the auto-inserted out-of-range guard on a
# non-power-of-two bit-select a plain '(idx <= W-1) && vec[idx]' (AstLogAnd), rather than
# the ternary '(idx <= W-1) ? vec[idx] : <x>' (AstCond) produced under the driver's default
# '--x-assign unique'. This exercises the matcher's other guard-peel branch.
test.top_filename = "t/t_bit_scan_loops.v"
test.compile(verilator_flags2=['--stats', '--unroll-count', '0', '--x-assign', '0'])
# Same lowering counts as the default run -- only the guard shape differs, not the result.
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Lowered priority-encoder to mostsetbitp1\s+(\d+)',
8)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Lowered count-set-bits to countones\s+(\d+)', 1)
test.execute()
test.passes()

View File

@ -15,6 +15,7 @@ test.compile(verilator_flags2=['--stats'])
test.execute()
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3)
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 2)
test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 2)
test.passes()

View File

@ -15,6 +15,7 @@ test.compile(verilator_flags2=['--stats'])
test.execute()
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3)
test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 2)
test.file_grep(test.stats, r'Assertions, lifted impure case expressions\s+(\d+)', 2)
test.passes()

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
test.compile(verilator_flags2=['--binary', '-Wno-WIDTHTRUNC'])
test.passes()

View File

@ -0,0 +1,39 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
interface irq_if (
input logic clk,
input logic resetn
);
logic irq;
logic te;
logic halted;
logic fault;
logic wfi;
clocking cb @(posedge clk);
default input #1step output #2ns;
output irq;
output te;
input halted;
input fault;
input wfi;
endclocking
modport DUT_IRQ_PORT(input clk, resetn, output halted, fault, wfi);
endinterface
class base_test_class;
function int foo();
endfunction
virtual irq_if.DUT_IRQ_PORT irq_vif;
function new(string name);
endfunction
virtual function void build_phase();
if (irq_vif == null) begin
if (foo()) $display();
end
endfunction
endclass
module tb_top;
endmodule

View File

@ -9,10 +9,10 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.compile(expect_filename=test.golden_filename,
verilator_flags2=['--timing', '--error-limit 1000'],
fails=test.vlt_all)
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Sergey Chusov
// SPDX-License-Identifier: CC0-1.0
// A factory-like static method, as used by UVM's type_id::create().
class registry #(type T = int);
static function T create();
T r = new;
return r;
endfunction
endclass
class item;
int val;
typedef registry#(item) type_id;
function new();
val = 42;
endfunction
endclass
class seq_base #(type REQ = int, type RSP = REQ);
REQ req;
endclass
// Non-parameterized class extending a parameterized base.
// REQ is a type parameter inherited from the (specialized) base class and is
// here used as a '::' class scope: REQ::type_id::create(). Resolving REQ in
// this position requires deferring until after V3Param has bound REQ to item.
class seq extends seq_base #(item);
function int make();
REQ r;
r = REQ::type_id::create();
return r.val;
endfunction
endclass
module t;
seq s;
initial begin
s = new;
if (s.make() != 42) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -21,7 +21,7 @@ class C;
endclass
class D;
my_t a; // second use of the type name after the shadowing variable...
my_t a; // second use of the type name after the shadowing variable...
my_t my_t; // ... is also legal; both resolve to the $unit typedef
endclass

View File

@ -9,7 +9,6 @@
import vltest_bootstrap
test.scenarios('simulator')
test.scenarios('linter')
test.lint(verilator_flags2=["-libmap t/t_config_libmap_inc.map"],

View File

@ -8,17 +8,21 @@
// values only within the declared distribution and cover all buckets.
// verilog_format: off
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0);
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// foreach (a[i]) if (gate) a[i] dist {...}
class ClsIf;
rand bit [3:0] a [4];
rand bit [3:0] a[4];
bit gate;
constraint c {
foreach (a[i]) {
if (gate == 1'b1) {
a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 };
a[i] dist {
4'd0 := 3,
[4'd1 : 4'd4] := 1
};
}
}
}
@ -26,22 +30,30 @@ endclass
// foreach (a[i]) gate -> a[i] dist {...}
class ClsImpl;
rand bit [3:0] a [4];
rand bit [3:0] a[4];
bit gate;
constraint c {
foreach (a[i]) {
gate -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 });
gate ->
(a[i] dist {
4'd0 := 3,
[4'd1 : 4'd4] := 1
});
}
}
endclass
// foreach (a[i]) gateA -> (gateB -> a[i] dist {...}) -- doubly-nested implication
class ClsImplChained;
rand bit [3:0] a [4];
rand bit [3:0] a[4];
bit gateA, gateB;
constraint c {
foreach (a[i]) {
gateA -> (gateB -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }));
gateA ->
(gateB -> (a[i] dist {
4'd0 := 3,
[4'd1 : 4'd4] := 1
}));
}
}
endclass
@ -59,8 +71,8 @@ module t;
`checkd(obj.randomize(), 1)
foreach (obj.a[i]) begin
if (obj.a[i] > 4) begin
$write("%%Error: %s:%0d: if: value out of dist range: %0d\n",
`__FILE__, `__LINE__, obj.a[i]);
$write("%%Error: %s:%0d: if: value out of dist range: %0d\n", `__FILE__, `__LINE__,
obj.a[i]);
$stop;
end
if (obj.a[i] == 0) seen_zero++;
@ -68,8 +80,9 @@ module t;
end
end
if (seen_zero == 0 || seen_nonzero == 0) begin
$write("%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$write(
"%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$stop;
end
end
@ -85,8 +98,8 @@ module t;
`checkd(obj.randomize(), 1)
foreach (obj.a[i]) begin
if (obj.a[i] > 4) begin
$write("%%Error: %s:%0d: ->: value out of dist range: %0d\n",
`__FILE__, `__LINE__, obj.a[i]);
$write("%%Error: %s:%0d: ->: value out of dist range: %0d\n", `__FILE__, `__LINE__,
obj.a[i]);
$stop;
end
if (obj.a[i] == 0) seen_zero++;
@ -94,8 +107,9 @@ module t;
end
end
if (seen_zero == 0 || seen_nonzero == 0) begin
$write("%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$write(
"%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$stop;
end
end
@ -112,8 +126,8 @@ module t;
`checkd(obj.randomize(), 1)
foreach (obj.a[i]) begin
if (obj.a[i] > 4) begin
$write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n",
`__FILE__, `__LINE__, obj.a[i]);
$write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n", `__FILE__, `__LINE__,
obj.a[i]);
$stop;
end
if (obj.a[i] == 0) seen_zero++;
@ -121,8 +135,9 @@ module t;
end
end
if (seen_zero == 0 || seen_nonzero == 0) begin
$write("%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$write(
"%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n",
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
$stop;
end
end

View File

@ -14,13 +14,20 @@
// Scalar: uniform over [0:9] (10% each)
class DistScalarRange;
rand bit [3:0] x;
constraint c { x dist {[4'd0:4'd9] := 1}; }
constraint c {
x dist {
[4'd0 : 4'd9] := 1
};
}
endclass
// Array foreach: uniform over [0:4] (20% each per element)
class DistForeachUniform;
rand bit [2:0] a[5];
constraint c { foreach (a[i]) a[i] dist {[3'd0:3'd4] := 1}; }
constraint c {
foreach (a[i])
a[i] dist {[3'd0 : 3'd4] := 1};
}
endclass
// Array foreach: mixed single + range
@ -28,32 +35,50 @@ endclass
// 0: 5/14 ~= 35.7%, 1..9: 1/14 ~= 7.1% each
class DistForeachMixed;
rand bit [3:0] a[5];
constraint c { foreach (a[i]) a[i] dist {4'd0 := 5, [4'd1:4'd9] := 1}; }
constraint c {
foreach (a[i])
a[i] dist {
4'd0 := 5,
[4'd1 : 4'd9] := 1
};
}
endclass
// Scalar signed int: uniform over negative range [-9:0] (10% each)
class DistNegRange;
rand int x;
constraint c { x dist {[-9:0] := 1}; }
constraint c {
x dist {
[-9 : 0] := 1
};
}
endclass
// Non-constant unsigned range bounds: uniform over [lo_val:hi_val] = [2:7] (6 values)
class DistVarRangeUnsigned;
rand bit [3:0] x;
bit [3:0] lo_val = 4'd2, hi_val = 4'd7;
constraint c { x dist {[lo_val:hi_val] := 1}; }
constraint c {
x dist {
[lo_val : hi_val] := 1
};
}
endclass
// Mixed const/non-const bounds: lo is constant, hi is a variable [1:hi_val] = [1:7]
class DistMixedBounds;
rand bit [3:0] x;
bit [3:0] hi_val = 4'd7;
constraint c { x dist {[4'd1:hi_val] := 1}; }
constraint c {
x dist {
[4'd1 : hi_val] := 1
};
}
endclass
module t;
parameter int N = 2000; // randomize() calls per test
parameter int TOL_PCT = 30; // +-% tolerance on expected counts
parameter int N = 2000; // randomize() calls per test
parameter int TOL_PCT = 30; // +-% tolerance on expected counts
initial begin
@ -71,8 +96,7 @@ module t;
end
cnt[obj.x]++;
end
foreach (cnt[v])
`check_tol(cnt[v], N/10)
foreach (cnt[v]) `check_tol(cnt[v], N / 10)
end
// --- T2: array foreach uniform [0:4], 5 elements * N calls ---
@ -91,8 +115,7 @@ module t;
cnt[obj.a[i]]++;
end
end
foreach (cnt[v])
`check_tol(cnt[v], N)
foreach (cnt[v]) `check_tol(cnt[v], N)
end
// --- T3: array foreach mixed {0:=5, [1:9]:=1}, 5 elements * N calls ---
@ -113,9 +136,8 @@ module t;
cnt[obj.a[i]]++;
end
end
`check_tol(cnt[0], N*5*5/14)
for (int v = 1; v <= 9; v++)
`check_tol(cnt[v], N*5*1/14)
`check_tol(cnt[0], N * 5 * 5 / 14)
for (int v = 1; v <= 9; v++) `check_tol(cnt[v], N * 5 * 1 / 14)
end
// --- T4: signed int, uniform over negative range [-9:0] ---
@ -131,10 +153,9 @@ module t;
$write("%%Error: x=%0d outside valid range [-9:0]\n", obj.x);
`stop;
end
cnt[obj.x + 9]++;
cnt[obj.x+9]++;
end
foreach (cnt[v])
`check_tol(cnt[v], N/10)
foreach (cnt[v]) `check_tol(cnt[v], N / 10)
end
// --- T5: non-constant unsigned range bounds [lo_val:hi_val] = [2:7] ---
@ -151,8 +172,7 @@ module t;
end
cnt[obj.x]++;
end
for (int v = 2; v <= 7; v++)
`check_tol(cnt[v], N/6)
for (int v = 2; v <= 7; v++) `check_tol(cnt[v], N / 6)
end
// --- T6: mixed const/non-const bounds [4'd1:hi_val] = [1:7] ---
@ -169,8 +189,7 @@ module t;
end
cnt[obj.x]++;
end
for (int v = 1; v <= 7; v++)
`check_tol(cnt[v], N/7)
for (int v = 1; v <= 7; v++) `check_tol(cnt[v], N / 7)
end
$write("*-* All Finished *-*\n");

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,178 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// Scenario 1 (issue #7833 literal): owner SA1 declares a global constraint on a
// sub-object's type but is NEVER randomized; a standalone randomize() of the
// holder must still randomize the nested fields.
class C1;
rand int x;
rand int y;
endclass
class B1;
rand C1 c;
function new();
c = new();
endfunction
endclass
class SA1;
rand B1 b;
constraint c_foo {b.c.x == 123;}
endclass
// Scenario 2 (combined): the SAME design randomizes both the constraint owner
// and a standalone holder of the constrained type.
class C2;
rand int x;
rand int y;
endclass
class B2;
rand C2 c;
function new();
c = new();
endfunction
endclass
class SA2;
rand B2 b;
constraint c_foo {b.c.x == 123;}
function new();
b = new();
endfunction
endclass
// Scenario 3 (multiple same-type sub-objects): two sub-objects of one type are
// each pinned by a global constraint while that type is also randomized
// standalone; the two share one underlying rand variable.
class C3;
rand int x;
endclass
class M3;
rand C3 c1;
rand C3 c2;
constraint c {
c1.x == 11;
c2.x == 22;
}
function new();
c1 = new();
c2 = new();
endfunction
endclass
// Scenario 4 (global constraint owner with its own size-constrained array): the
// owner basic-randomizes first, the solver overrides last, and the size-only
// resize fallback still resizes from the solver-determined size.
class C4;
rand int x;
endclass
class A4;
rand C4 c;
rand int arr[];
constraint c_sub {c.x == 5;}
constraint c_size {arr.size() == 4;}
function new();
c = new();
endfunction
endclass
module t_constraint_global_subobj;
B1 b1;
SA2 a2;
B2 b2;
M3 m3;
C3 s3;
A4 a4;
int prevx, prevy, p3;
bit varyx, varyy, vary3;
initial begin
// Scenario 1: SA1 never randomized; standalone B1 must vary both fields.
b1 = new();
varyx = 0;
varyy = 0;
if (b1.randomize() != 1) $stop;
prevx = b1.c.x;
prevy = b1.c.y;
for (int i = 0; i < 20; i++) begin
if (b1.randomize() != 1) $stop;
if (b1.c.x != prevx) varyx = 1;
if (b1.c.y != prevy) varyy = 1;
prevx = b1.c.x;
prevy = b1.c.y;
end
if (!varyx || !varyy) begin
$display("ERROR: standalone holder fields stuck (varyx=%0d varyy=%0d)", varyx, varyy);
$stop;
end
// Scenario 2: randomize the owner (constraint applies) and a standalone
// holder (fields random) in the same design.
a2 = new();
if (a2.randomize() != 1) $stop;
if (a2.b.c.x != 123) begin
$display("ERROR: owner constraint not applied, a2.b.c.x=%0d", a2.b.c.x);
$stop;
end
b2 = new();
varyx = 0;
if (b2.randomize() != 1) $stop;
prevx = b2.c.x;
for (int i = 0; i < 20; i++) begin
if (b2.randomize() != 1) $stop;
if (b2.c.x != prevx) varyx = 1;
prevx = b2.c.x;
end
if (!varyx) begin
$display("ERROR: standalone holder x stuck while owner also randomized");
$stop;
end
// Scenario 3: two same-type sub-objects each pinned, plus standalone vary.
m3 = new();
if (m3.randomize() != 1) $stop;
if (m3.c1.x != 11) begin
$display("ERROR: m3.c1.x should be 11, got %0d", m3.c1.x);
$stop;
end
if (m3.c2.x != 22) begin
$display("ERROR: m3.c2.x should be 22, got %0d", m3.c2.x);
$stop;
end
s3 = new();
vary3 = 0;
if (s3.randomize() != 1) $stop;
p3 = s3.x;
for (int i = 0; i < 20; i++) begin
if (s3.randomize() != 1) $stop;
if (s3.x != p3) vary3 = 1;
p3 = s3.x;
end
if (!vary3) begin
$display("ERROR: standalone same-type x stuck");
$stop;
end
// Scenario 4: owner with a global constraint AND its own size constraint.
a4 = new();
if (a4.randomize() != 1) $stop;
if (a4.c.x != 5) begin
$display("ERROR: a4.c.x should be 5, got %0d", a4.c.x);
$stop;
end
if (a4.arr.size() != 4) begin
$display("ERROR: a4.arr.size() should be 4, got %0d", a4.arr.size());
$stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -11,8 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator')
test.lint(expect_filename=test.golden_filename,
verilator_flags2=['--assert --error-limit 1000'],
fails=True)
test.compile(verilator_flags2=['--coverage-expr --binary'])
test.execute(all_run_flags=[" +verilator+coverage+file+" + test.obj_dir + "/coverage.dat"])
test.passes()

View File

@ -0,0 +1,31 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
module t;
int cnt = 0;
task automatic myTask;
fork
begin
bit x;
if (!x) begin
cnt++;
end
if (!$onehot(x)) begin
cnt++;
end
end
join_none
endtask
initial begin
myTask();
#1;
if (cnt != 2) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,33 +1,33 @@
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:26:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_then_u.state_q': assigned value 3 is not present in the declared enum
26 | S0: state_d = sel ? 2'd3 : S1;
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:274:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_wide_direct_u.state_q': assigned value 40'hffffffffff is not present in the declared enum
274 | S0: state_d = 40'hffff_ffff_ff;
| ^
... 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.
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:53:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_else_u.state_q': assigned value 3 is not present in the declared enum
53 | S0: state_d = sel ? S1 : 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:79:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_direct_u.state_q': assigned value 3 is not present in the declared enum
79 | S0: state_d = 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:126:15: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_reset_u.state_q': assigned value 3 is not present in the declared enum
126 | state_q <= 2'd3;
| ^~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:150:7: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_source_u.state_q': case item value 3 is not present in the declared enum
150 | 2'd3: state_d = S0;
| ^~~~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:175:5: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_source_u.state_q': case item value 3 is not present in the declared enum
175 | if (state_q == 2'd3) state_d = S0;
| ^~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:199:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_direct_target_u.state_q': assigned value 3 is not present in the declared enum
199 | if (state_q == S0) state_d = 2'd3;
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:249:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_else_target_u.state_q': assigned value 3 is not present in the declared enum
249 | if (state_q == S0) state_d = sel ? S1 : 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:224:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_then_target_u.state_q': assigned value 3 is not present in the declared enum
224 | if (state_q == S0) state_d = sel ? 2'd3 : S1;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:249:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_else_target_u.state_q': assigned value 3 is not present in the declared enum
249 | if (state_q == S0) state_d = sel ? S1 : 2'd3;
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:199:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_direct_target_u.state_q': assigned value 3 is not present in the declared enum
199 | if (state_q == S0) state_d = 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:274:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_wide_direct_u.state_q': assigned value 40'hffffffffff is not present in the declared enum
274 | S0: state_d = 40'hffff_ffff_ff;
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:175:5: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_source_u.state_q': case item value 3 is not present in the declared enum
175 | if (state_q == 2'd3) state_d = S0;
| ^~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:150:7: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_source_u.state_q': case item value 3 is not present in the declared enum
150 | 2'd3: state_d = S0;
| ^~~~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:126:15: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_reset_u.state_q': assigned value 3 is not present in the declared enum
126 | state_q <= 2'd3;
| ^~
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:79:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_direct_u.state_q': assigned value 3 is not present in the declared enum
79 | S0: state_d = 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:53:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_else_u.state_q': assigned value 3 is not present in the declared enum
53 | S0: state_d = sel ? S1 : 2'd3;
| ^
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:26:19: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_then_u.state_q': assigned value 3 is not present in the declared enum
26 | S0: state_d = sel ? 2'd3 : S1;
| ^
%Error: Exiting due to

View File

@ -1,12 +1,12 @@
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:25:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
25 | case (state_q)
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:86:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
86 | case (state_q)
| ^~~~
... 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.
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:55:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
55 | case (state_d)
| ^~~~
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:86:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
86 | case (state_q)
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:25:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
25 | case (state_q)
| ^~~~
%Error: Exiting due to

View File

@ -4,32 +4,32 @@ DA:15,1
DA:18,1
DA:55,10
DA:56,10
BRDA:56,0,0,10
BRDA:56,0,1,0
BRDA:56,0,if,10
BRDA:56,0,else,0
DA:57,10
DA:58,10
DA:60,9
BRDA:60,0,0,1
BRDA:60,0,1,9
BRDA:60,0,if,1
BRDA:60,0,else,9
DA:61,9
BRDA:61,0,0,1
BRDA:61,0,1,9
BRDA:61,0,if,1
BRDA:61,0,else,9
DA:62,1
DA:63,1
DA:66,9
BRDA:66,0,0,1
BRDA:66,0,1,9
BRDA:66,0,if,1
BRDA:66,0,else,9
DA:67,9
BRDA:67,0,0,1
BRDA:67,0,1,9
BRDA:67,0,if,1
BRDA:67,0,else,9
DA:69,9
DA:70,9
DA:73,9
BRDA:73,0,0,1
BRDA:73,0,1,9
BRDA:73,0,if,1
BRDA:73,0,else,9
DA:74,9
BRDA:74,0,0,1
BRDA:74,0,1,9
BRDA:74,0,if,1
BRDA:74,0,else,9
DA:75,1
DA:76,1
DA:79,9
@ -41,8 +41,8 @@ DA:87,1
DA:88,1
DA:89,1
DA:91,7
BRDA:91,0,0,1
BRDA:91,0,1,7
BRDA:91,0,if,1
BRDA:91,0,else,7
DA:92,1
DA:93,1
DA:96,7
@ -52,67 +52,67 @@ DA:101,0
DA:102,0
DA:104,0
DA:105,10
BRDA:105,0,0,0
BRDA:105,0,1,10
BRDA:105,0,block,0
BRDA:105,0,block,10
DA:106,10
DA:107,10
BRDA:107,0,0,0
BRDA:107,0,1,10
BRDA:107,0,block,0
BRDA:107,0,block,10
DA:110,1
DA:111,1
DA:113,1
DA:115,1
DA:120,7
BRDA:120,0,0,1
BRDA:120,0,1,7
BRDA:120,0,if,1
BRDA:120,0,else,7
DA:121,1
DA:122,1
DA:127,1
DA:129,1
DA:140,20
DA:141,18
BRDA:141,0,0,2
BRDA:141,0,1,18
BRDA:141,0,if,2
BRDA:141,0,else,18
DA:142,2
DA:145,18
DA:164,20
DA:165,20
DA:166,20
BRDA:166,0,0,0
BRDA:166,0,1,20
BRDA:166,0,if,0
BRDA:166,0,else,20
DA:168,0
DA:170,18
BRDA:170,0,0,2
BRDA:170,0,1,18
BRDA:170,0,if,2
BRDA:170,0,else,18
DA:172,2
DA:174,18
DA:188,11
DA:189,11
DA:190,11
BRDA:190,0,0,11
BRDA:190,0,1,0
BRDA:190,0,if,11
BRDA:190,0,else,0
DA:191,11
DA:194,11
DA:195,11
BRDA:195,0,0,11
BRDA:195,0,1,0
BRDA:195,0,if,11
BRDA:195,0,else,0
DA:196,11
DA:199,11
DA:200,11
BRDA:200,0,0,11
BRDA:200,0,1,0
BRDA:200,0,if,11
BRDA:200,0,else,0
DA:201,11
DA:215,10
DA:216,10
DA:219,11
DA:221,11
DA:222,10
BRDA:222,0,0,1
BRDA:222,0,1,10
BRDA:222,0,if,1
BRDA:222,0,else,10
DA:223,1
DA:225,10
BRDA:225,0,0,1
BRDA:225,0,1,10
BRDA:225,0,if,1
BRDA:225,0,else,10
DA:226,1
DA:229,11
DA:230,11
@ -121,12 +121,12 @@ DA:232,11
DA:233,11
DA:253,10
DA:254,9
BRDA:254,0,0,1
BRDA:254,0,1,9
BRDA:254,0,if,1
BRDA:254,0,else,9
DA:256,1
DA:257,1
BRDA:257,0,0,0
BRDA:257,0,1,1
BRDA:257,0,if,0
BRDA:257,0,else,1
DA:266,10
DA:267,10
DA:268,1
@ -139,8 +139,8 @@ DA:277,10
DA:278,10
DA:288,0
DA:289,0
BRDA:289,0,0,0
BRDA:289,0,1,0
BRDA:289,0,if,0
BRDA:289,0,else,0
DA:290,0
DA:292,0
DA:293,0
@ -157,66 +157,66 @@ DA:328,10
DA:329,10
DA:330,10
DA:333,31
BRDA:333,0,0,0
BRDA:333,0,1,31
BRDA:333,0,cond_then,0
BRDA:333,0,cond_else,31
DA:334,28
BRDA:334,0,0,3
BRDA:334,0,1,28
BRDA:334,0,cond_then,3
BRDA:334,0,cond_else,28
DA:335,1
BRDA:335,0,0,1
BRDA:335,0,1,0
BRDA:335,0,cond_then,1
BRDA:335,0,cond_else,0
DA:336,10
DA:337,10
BRDA:337,0,0,10
BRDA:337,0,1,3
BRDA:337,0,2,7
BRDA:337,0,block,10
BRDA:337,0,cond_then,3
BRDA:337,0,cond_else,7
DA:338,10
BRDA:338,0,0,10
BRDA:338,0,1,0
BRDA:338,0,2,10
BRDA:338,0,block,10
BRDA:338,0,cond_then,0
BRDA:338,0,cond_else,10
DA:340,19
BRDA:340,0,0,12
BRDA:340,0,1,19
BRDA:340,0,2,7
BRDA:340,0,3,5
BRDA:340,0,cond_then,12
BRDA:340,0,cond_else,19
BRDA:340,0,cond_then,7
BRDA:340,0,cond_else,5
DA:343,11
BRDA:343,0,0,11
BRDA:343,0,1,0
BRDA:343,0,cond_then,11
BRDA:343,0,cond_else,0
DA:349,22
BRDA:349,0,0,20
BRDA:349,0,1,22
BRDA:349,0,cond_then,20
BRDA:349,0,cond_else,22
DA:352,11
DA:353,10
BRDA:353,0,0,0
BRDA:353,0,1,1
BRDA:353,0,2,1
BRDA:353,0,3,10
BRDA:353,0,cond_then,0
BRDA:353,0,cond_else,1
BRDA:353,0,if,1
BRDA:353,0,else,10
DA:354,10
DA:356,11
BRDA:356,0,0,11
BRDA:356,0,1,1
BRDA:356,0,2,10
BRDA:356,0,block,11
BRDA:356,0,cond_then,1
BRDA:356,0,cond_else,10
DA:359,55
BRDA:359,0,0,11
BRDA:359,0,1,55
BRDA:359,0,block,11
BRDA:359,0,block,55
DA:360,55
DA:362,44
BRDA:362,0,0,11
BRDA:362,0,1,11
BRDA:362,0,2,33
BRDA:362,0,3,44
BRDA:362,0,block,11
BRDA:362,0,cond_then,11
BRDA:362,0,cond_else,33
BRDA:362,0,block,44
DA:363,44
DA:366,11
BRDA:366,0,0,0
BRDA:366,0,1,11
BRDA:366,0,if,0
BRDA:366,0,else,11
DA:367,11
DA:370,10
BRDA:370,0,0,1
BRDA:370,0,1,10
BRDA:370,0,cond_then,1
BRDA:370,0,cond_else,10
DA:373,10
DA:374,9
BRDA:374,0,0,1
BRDA:374,0,1,9
BRDA:374,0,if,1
BRDA:374,0,else,9
BRF:83
BRH:32
end_of_record

View File

@ -60,18 +60,17 @@ module t (
// A cover of an implication counts only non-vacuous matches (IEEE
// 1800-2023 16.15.2): the antecedent must match. So it is identical to the
// corresponding sequence cover, not the vacuous implication value.
`checkd(n_imp_no, n_seq)
`checkd(n_imp_ov, n_seq0)
`checkd(n_imp_no, n_seq); // Other sims: pass, 73
`checkd(n_imp_ov, n_seq0); // Other sims: pass, 45
// A named-property cover lowers the same implication, so it also counts
// non-vacuously (regression guard for the property-inlining path).
`checkd(n_named, n_imp_no)
// Pinned Verilator counts; Questa golden cross-checked.
`checkd(n_imp_no, 28) // Questa: 28
`checkd(n_imp_ov, 27) // Questa: 27
`checkd(n_seq, 28) // Questa: 28
`checkd(n_seq0, 27) // Questa: 27
`checkd(n_bool, 55) // Questa: 54
`checkd(n_named, 28) // Questa: 28
`checkd(n_named, n_imp_no);
`checkd(n_imp_no, 28);
`checkd(n_imp_ov, 27); // Other sims: pass, 73
`checkd(n_seq, 28); // Other sims: 45, 27
`checkd(n_seq0, 27);
`checkd(n_bool, 55); // Other sims: pass, 25
`checkd(n_named, 28); // Other sims: 73, 54, 54
end
endmodule

View File

@ -69,22 +69,21 @@ module t (
// Read the counters in 'final', not the clocked block: a same-cycle read of a
// cover counter races the cover's increment under --threads (vltmt). Verilator
// counts one more end-of-match than Questa 2022.3 on some forms at the
// simulation boundary; the Questa value is noted per check.
// counts one more end-of-match than others on some forms.
final begin
`ifdef TEST_VERBOSE
$write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple,
hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2,
hit_consrep_3);
`endif
`checkd(hit_simple, 96); // Questa: 95
`checkd(hit_clocked, 149); // Questa: 149
`checkd(hit_clocked_disable, 28); // Questa: 27
`checkd(hit_default_disable, 30); // Questa: 30
`checkd(hit_consrep_2, 30); // Questa: 29
`checkd(hit_consrep_3, 14); // Questa: 13
`checkd(hit_simple, 96); // Other sims: 5, 95
`checkd(hit_clocked, 149);
`checkd(hit_clocked_disable, 27);
`checkd(hit_default_disable, 30);
`checkd(hit_consrep_2, 30); // Other sims: 29
`checkd(hit_consrep_3, 14); // Other sims: 13
// a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2)
`checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); // 44; Questa: 42
`checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3);
$write("*-* All Finished *-*\n");
end
endmodule

View File

@ -1,20 +1,20 @@
TN:verilator_coverage
SF:t/t_cover_toggle_min.v
DA:10,1
BRDA:10,0,0,1
BRDA:10,0,1,0
BRDA:10,0,2,0
BRDA:10,0,3,0
BRDA:10,0,a[0]:0->1,1
BRDA:10,0,a[0]:1->0,0
BRDA:10,0,a[1]:0->1,0
BRDA:10,0,a[1]:1->0,0
DA:11,1
BRDA:11,0,0,0
BRDA:11,0,1,0
BRDA:11,0,2,1
BRDA:11,0,3,0
BRDA:11,0,b[0]:0->1,0
BRDA:11,0,b[0]:1->0,0
BRDA:11,0,b[1]:0->1,1
BRDA:11,0,b[1]:1->0,0
DA:12,1
BRDA:12,0,0,1
BRDA:12,0,1,1
BRDA:12,0,2,1
BRDA:12,0,3,0
BRDA:12,0,c[0]:0->1,1
BRDA:12,0,c[0]:1->0,1
BRDA:12,0,c[1]:0->1,1
BRDA:12,0,c[1]:1->0,0
BRF:12
BRH:0
end_of_record

View File

@ -109,10 +109,10 @@ cg_range.cp_addr.hi_range: 2
cg_range.cp_addr.lo_range: 2
cg_range.cp_cmd.read: 2
cg_range.cp_cmd.write: 2
cg_unnamed_cross.__cross7.a0_x_read [cross]: 1
cg_unnamed_cross.__cross7.a0_x_write [cross]: 0
cg_unnamed_cross.__cross7.a1_x_read [cross]: 0
cg_unnamed_cross.__cross7.a1_x_write [cross]: 1
cg_unnamed_cross.__cross8.a0_x_read [cross]: 1
cg_unnamed_cross.__cross8.a0_x_write [cross]: 0
cg_unnamed_cross.__cross8.a1_x_read [cross]: 0
cg_unnamed_cross.__cross8.a1_x_write [cross]: 1
cg_unnamed_cross.cp_a.a0: 1
cg_unnamed_cross.cp_a.a1: 1
cg_unnamed_cross.cp_c.read: 1

View File

@ -17,6 +17,9 @@ module t;
logic mode;
logic parity;
typedef struct packed {logic m_p; logic h_mode;} cfg_t;
cfg_t s_cfg = '0;
// 2-way cross
covergroup cg2;
cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};}
@ -90,6 +93,12 @@ module t;
addr_cmd_unsup: cross cp_addr, cp_cmd{
option.per_instance = 1; // unsupported for cross - expect COVERIGN warning
}
// Non-standard hierarchical reference as a cross item (an implicit coverpoint):
// accepted with NONSTD, but implicit coverpoints are unsupported so the whole
// cross is dropped (COVERIGN, suppressed here) - it contributes no bins.
/* verilator lint_off NONSTD */
cross_hier: cross cp_addr, s_cfg.m_p;
/* verilator lint_on NONSTD */
endgroup
// Covergroup with an unnamed cross - the cross is reported under the default name "cross"

View File

@ -10,9 +10,11 @@
import vltest_bootstrap
test.scenarios('vlt_all')
test.top_filename = 't/t_covergroup_cross.v'
test.compile(verilator_flags2=['--assert', '--timing', '--lint-only'],
fails=True,
expect_filename=test.golden_filename)
# runs without --coverage
test.compile(verilator_flags2=['--Wno-COVERIGN'])
test.execute()
test.passes()

View File

@ -1,3 +1,8 @@
%Warning-NONSTD: t/t_covergroup_cross_opt_unsup.v:19:34: Non-standard hierarchical reference as a coverage cross item (an implicit coverpoint)
19 | cross_hier: cross cp_a, s_cfg.m_p;
| ^
... For warning description see https://verilator.org/warn/NONSTD?v=latest
... Use "/* verilator lint_off NONSTD */" and lint_on around source to disable this message.
%Warning-COVERIGN: t/t_covergroup_cross_opt_unsup.v:13:7: Ignoring unsupported coverage cross option: 'per_instance'
13 | option.per_instance = 1;
| ^~~~~~
@ -7,4 +12,8 @@
: ... note: In instance 't'
15 | cross_implicit: cross cp_a, var_x;
| ^~~~~
%Warning-COVERIGN: t/t_covergroup_cross_opt_unsup.v:19:34: Unsupported: cross of hierarchical reference (implicit coverpoint)
: ... note: In instance 't'
19 | cross_hier: cross cp_a, s_cfg.m_p;
| ^
%Error: Exiting due to

View File

@ -13,7 +13,13 @@ module t;
option.per_instance = 1; // unsupported for cross; triggers COVERIGN
}
cross_implicit: cross cp_a, var_x;
// Non-standard hierarchical/dotted cross item: can only be a data reference
// (implicit coverpoint), never a coverpoint. Accepted with a NONSTD warning;
// implicit coverpoints are unsupported so the cross is dropped (COVERIGN).
cross_hier: cross cp_a, s_cfg.m_p;
endgroup
typedef struct packed {logic m_p; logic h_mode;} cfg_t;
cfg_t s_cfg = '0;
logic var_x = 1'b0;
cg cg_i = new;
initial begin

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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')
test.top_filename = 't/t_covergroup_option.v'
# runs without --coverage
test.compile(verilator_flags2=['--Wno-COVERIGN'])
test.execute()
test.passes()

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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()
test.execute()
test.passes()

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