Merge remote-tracking branch 'upstream/master' into fix/7797-seq-event
This commit is contained in:
commit
d996b4a70e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
52
Changes
|
|
@ -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:**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
30
configure.ac
30
configure.ac
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, "",
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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)};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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++)"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
603
src/V3Inline.cpp
603
src/V3Inline.cpp
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
156
src/V3Unroll.cpp
156
src/V3Unroll.cpp
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
module t;
|
||||
initial begin
|
||||
$assertcontrol(100*`IMPURE_ONE);
|
||||
$assertcontrol(100 * `IMPURE_ONE);
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(verilator_flags2=["-libmap t/t_config_libmap_inc.map"],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
Loading…
Reference in New Issue